http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gce.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gce.py b/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gce.py deleted file mode 100644 index 4aa92ac..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gce.py +++ /dev/null @@ -1,5860 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Module for Google Compute Engine Driver. -""" -from __future__ import with_statement - -import datetime -import time -import sys - -from libcloud.common.base import LazyObject -from libcloud.common.google import GoogleOAuth2Credential -from libcloud.common.google import GoogleResponse -from libcloud.common.google import GoogleBaseConnection -from libcloud.common.google import GoogleBaseError -from libcloud.common.google import ResourceNotFoundError -from libcloud.common.google import ResourceExistsError -from libcloud.common.types import ProviderError - -from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation -from libcloud.compute.base import NodeSize, StorageVolume, VolumeSnapshot -from libcloud.compute.base import UuidMixin -from libcloud.compute.providers import Provider -from libcloud.compute.types import NodeState -from libcloud.utils.iso8601 import parse_date - -API_VERSION = 'v1' -DEFAULT_TASK_COMPLETION_TIMEOUT = 180 - - -def timestamp_to_datetime(timestamp): - """ - Return a datetime object that corresponds to the time in an RFC3339 - timestamp. - - :param timestamp: RFC3339 timestamp string - :type timestamp: ``str`` - - :return: Datetime object corresponding to timestamp - :rtype: :class:`datetime.datetime` - """ - # We remove timezone offset and microseconds (Python 2.5 strptime doesn't - # support %f) - ts = datetime.datetime.strptime(timestamp[:-10], '%Y-%m-%dT%H:%M:%S') - tz_hours = int(timestamp[-5:-3]) - tz_mins = int(timestamp[-2:]) * int(timestamp[-6:-5] + '1') - tz_delta = datetime.timedelta(hours=tz_hours, minutes=tz_mins) - return ts + tz_delta - - -class GCEResponse(GoogleResponse): - pass - - -class GCEConnection(GoogleBaseConnection): - """ - Connection class for the GCE driver. - - GCEConnection extends :class:`google.GoogleBaseConnection` for 2 reasons: - 1. modify request_path for GCE URI. - 2. Implement gce_params functionality described below. - - If the parameter gce_params is set to a dict prior to calling request(), - the URL parameters will be updated to include those key/values FOR A - SINGLE REQUEST. If the response contains a nextPageToken, - gce_params['pageToken'] will be set to its value. This can be used to - implement paging in list: - - >>> params, more_results = {'maxResults': 2}, True - >>> while more_results: - ... driver.connection.gce_params=params - ... driver.ex_list_urlmaps() - ... more_results = 'pageToken' in params - ... - [<GCEUrlMap id="..." name="cli-map">, <GCEUrlMap id="..." name="lc-map">] - [<GCEUrlMap id="..." name="web-map">] - """ - host = 'www.googleapis.com' - responseCls = GCEResponse - - def __init__(self, user_id, key, secure, auth_type=None, - credential_file=None, project=None, **kwargs): - super(GCEConnection, self).__init__(user_id, key, secure=secure, - auth_type=auth_type, - credential_file=credential_file, - **kwargs) - self.request_path = '/compute/%s/projects/%s' % (API_VERSION, project) - self.gce_params = None - - def pre_connect_hook(self, params, headers): - """ - Update URL parameters with values from self.gce_params. - - @inherits: :class:`GoogleBaseConnection.pre_connect_hook` - """ - params, headers = super(GCEConnection, self).pre_connect_hook(params, - headers) - if self.gce_params: - params.update(self.gce_params) - return params, headers - - def request(self, *args, **kwargs): - """ - Perform request then do GCE-specific processing of URL params. - - @inherits: :class:`GoogleBaseConnection.request` - """ - response = super(GCEConnection, self).request(*args, **kwargs) - - # If gce_params has been set, then update the pageToken with the - # nextPageToken so it can be used in the next request. - if self.gce_params: - if 'nextPageToken' in response.object: - self.gce_params['pageToken'] = response.object['nextPageToken'] - elif 'pageToken' in self.gce_params: - del self.gce_params['pageToken'] - self.gce_params = None - - return response - - -class GCEList(object): - """ - An Iterator that wraps list functions to provide additional features. - - GCE enforces a limit on the number of objects returned by a list operation, - so users with more than 500 objects of a particular type will need to use - filter(), page() or both. - - >>> l=GCEList(driver, driver.ex_list_urlmaps) - >>> for sublist in l.filter('name eq ...-map').page(1): - ... sublist - ... - [<GCEUrlMap id="..." name="cli-map">] - [<GCEUrlMap id="..." name="web-map">] - - One can create a GCEList manually, but it's slightly easier to use the - ex_list() method of :class:`GCENodeDriver`. - """ - - def __init__(self, driver, list_fn, **kwargs): - """ - :param driver: An initialized :class:``GCENodeDriver`` - :type driver: :class:``GCENodeDriver`` - - :param list_fn: A bound list method from :class:`GCENodeDriver`. - :type list_fn: ``instancemethod`` - """ - self.driver = driver - self.list_fn = list_fn - self.kwargs = kwargs - self.params = {} - - def __iter__(self): - list_fn = self.list_fn - more_results = True - while more_results: - self.driver.connection.gce_params = self.params - yield list_fn(**self.kwargs) - more_results = 'pageToken' in self.params - - def __repr__(self): - return '<GCEList list="%s" params="%s">' % ( - self.list_fn.__name__, repr(self.params)) - - def filter(self, expression): - """ - Filter results of a list operation. - - GCE supports server-side filtering of resources returned by a list - operation. Syntax of the filter expression is fully described in the - GCE API reference doc, but in brief it is:: - - FIELD_NAME COMPARISON_STRING LITERAL_STRING - - where FIELD_NAME is the resource's property name, COMPARISON_STRING is - 'eq' or 'ne', and LITERAL_STRING is a regular expression in RE2 syntax. - - >>> for sublist in l.filter('name eq ...-map'): - ... sublist - ... - [<GCEUrlMap id="..." name="cli-map">, \ - <GCEUrlMap id="..." name="web-map">] - - API reference: https://cloud.google.com/compute/docs/reference/latest/ - RE2 syntax: https://github.com/google/re2/blob/master/doc/syntax.txt - - :param expression: Filter expression described above. - :type expression: ``str`` - - :return: This :class:`GCEList` instance - :rtype: :class:`GCEList` - """ - self.params['filter'] = expression - return self - - def page(self, max_results=500): - """ - Limit the number of results by each iteration. - - This implements the paging functionality of the GCE list methods and - returns this GCEList instance so that results can be chained: - - >>> for sublist in GCEList(driver, driver.ex_list_urlmaps).page(2): - ... sublist - ... - [<GCEUrlMap id="..." name="cli-map">, \ - <GCEUrlMap id="..." name="lc-map">] - [<GCEUrlMap id="..." name="web-map">] - - :keyword max_results: Maximum number of results to return per - iteration. Defaults to the GCE default of 500. - :type max_results: ``int`` - - :return: This :class:`GCEList` instance - :rtype: :class:`GCEList` - """ - self.params['maxResults'] = max_results - return self - - -class GCELicense(UuidMixin, LazyObject): - """A GCE License used to track software usage in GCE nodes.""" - def __init__(self, name, project, driver): - UuidMixin.__init__(self) - self.id = name - self.name = name - self.project = project - self.driver = driver - self.charges_use_fee = None # init in _request - self.extra = None # init in _request - - self._request() - - def _request(self): - # TODO([email protected]): create new connection? or make - # connection thread-safe? Saving, modifying, and restoring - # driver.connection.request_path is really hacky and thread-unsafe. - saved_request_path = self.driver.connection.request_path - new_request_path = saved_request_path.replace(self.driver.project, - self.project) - self.driver.connection.request_path = new_request_path - - request = '/global/licenses/%s' % self.name - response = self.driver.connection.request(request, method='GET').object - self.driver.connection.request_path = saved_request_path - - self.extra = { - 'selfLink': response.get('selfLink'), - 'kind': response.get('kind') - } - self.charges_use_fee = response['chargesUseFee'] - - def destroy(self): - raise ProviderError("Can not destroy a License resource.") - - def __repr__(self): - return '<GCELicense id="%s" name="%s" charges_use_fee="%s">' % ( - self.id, self.name, self.charges_use_fee) - - -class GCEDiskType(UuidMixin): - """A GCE DiskType resource.""" - def __init__(self, id, name, zone, driver, extra=None): - self.id = str(id) - self.name = name - self.zone = zone - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - raise ProviderError("Can not destroy a DiskType resource.") - - def __repr__(self): - return '<GCEDiskType id="%s" name="%s" zone="%s">' % ( - self.id, self.name, self.zone) - - -class GCEAddress(UuidMixin): - """A GCE Static address.""" - def __init__(self, id, name, address, region, driver, extra=None): - self.id = str(id) - self.name = name - self.address = address - self.region = region - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this address. - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_address(address=self) - - def __repr__(self): - return '<GCEAddress id="%s" name="%s" address="%s" region="%s">' % ( - self.id, self.name, self.address, - (hasattr(self.region, "name") and self.region.name or self.region)) - - -class GCEBackendService(UuidMixin): - """A GCE Backend Service.""" - - def __init__(self, id, name, backends, healthchecks, port, port_name, - protocol, timeout, driver, extra=None): - self.id = str(id) - self.name = name - self.backends = backends or [] - self.healthchecks = healthchecks or [] - self.port = port - self.port_name = port_name - self.protocol = protocol - self.timeout = timeout - self.driver = driver - self.extra = extra or {} - UuidMixin.__init__(self) - - def __repr__(self): - return '<GCEBackendService id="%s" name="%s">' % ( - self.id, self.name) - - def destroy(self): - """ - Destroy this Backend Service. - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_backendservice(backendservice=self) - - -class GCEFailedDisk(object): - """Dummy Node object for disks that are not created.""" - def __init__(self, name, error, code): - self.name = name - self.error = error - self.code = code - - def __repr__(self): - return '<GCEFailedDisk name="%s" error_code="%s">' % ( - self.name, self.code) - - -class GCEFailedNode(object): - """Dummy Node object for nodes that are not created.""" - def __init__(self, name, error, code): - self.name = name - self.error = error - self.code = code - - def __repr__(self): - return '<GCEFailedNode name="%s" error_code="%s">' % ( - self.name, self.code) - - -class GCEHealthCheck(UuidMixin): - """A GCE Http Health Check class.""" - def __init__(self, id, name, path, port, interval, timeout, - unhealthy_threshold, healthy_threshold, driver, extra=None): - self.id = str(id) - self.name = name - self.path = path - self.port = port - self.interval = interval - self.timeout = timeout - self.unhealthy_threshold = unhealthy_threshold - self.healthy_threshold = healthy_threshold - self.driver = driver - self.extra = extra or {} - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this Health Check. - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_healthcheck(healthcheck=self) - - def update(self): - """ - Commit updated healthcheck values. - - :return: Updated Healthcheck object - :rtype: :class:`GCEHealthcheck` - """ - return self.driver.ex_update_healthcheck(healthcheck=self) - - def __repr__(self): - return '<GCEHealthCheck id="%s" name="%s" path="%s" port="%s">' % ( - self.id, self.name, self.path, self.port) - - -class GCEFirewall(UuidMixin): - """A GCE Firewall rule class.""" - def __init__(self, id, name, allowed, network, source_ranges, source_tags, - target_tags, driver, extra=None): - self.id = str(id) - self.name = name - self.network = network - self.allowed = allowed - self.source_ranges = source_ranges - self.source_tags = source_tags - self.target_tags = target_tags - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this firewall. - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_firewall(firewall=self) - - def update(self): - """ - Commit updated firewall values. - - :return: Updated Firewall object - :rtype: :class:`GCEFirewall` - """ - return self.driver.ex_update_firewall(firewall=self) - - def __repr__(self): - return '<GCEFirewall id="%s" name="%s" network="%s">' % ( - self.id, self.name, self.network.name) - - -class GCEForwardingRule(UuidMixin): - def __init__(self, id, name, region, address, protocol, targetpool, driver, - extra=None): - self.id = str(id) - self.name = name - self.region = region - self.address = address - self.protocol = protocol - # TODO: 'targetpool' should more correctly be 'target' since a - # forwarding rule's target can be something besides a targetpool - self.targetpool = targetpool - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this Forwarding Rule - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_forwarding_rule(forwarding_rule=self) - - def __repr__(self): - return '<GCEForwardingRule id="%s" name="%s" address="%s">' % ( - self.id, self.name, self.address) - - -class GCENodeImage(NodeImage): - """A GCE Node Image class.""" - def __init__(self, id, name, driver, extra=None): - super(GCENodeImage, self).__init__(id, name, driver, extra=extra) - - def delete(self): - """ - Delete this image - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_delete_image(image=self) - - def deprecate(self, replacement, state, deprecated=None, obsolete=None, - deleted=None): - """ - Deprecate this image - - :param replacement: Image to use as a replacement - :type replacement: ``str`` or :class: `GCENodeImage` - - :param state: Deprecation state of this image. Possible values include - \'DELETED\', \'DEPRECATED\' or \'OBSOLETE\'. - :type state: ``str`` - - :param deprecated: RFC3339 timestamp to mark DEPRECATED - :type deprecated: ``str`` or ``None`` - - :param obsolete: RFC3339 timestamp to mark OBSOLETE - :type obsolete: ``str`` or ``None`` - - :param deleted: RFC3339 timestamp to mark DELETED - :type deleted: ``str`` or ``None`` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_deprecate_image(self, replacement, state, - deprecated, obsolete, deleted) - - -class GCENetwork(UuidMixin): - """A GCE Network object class.""" - def __init__(self, id, name, cidr, driver, extra=None): - self.id = str(id) - self.name = name - self.cidr = cidr - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this network - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_network(network=self) - - def __repr__(self): - return '<GCENetwork id="%s" name="%s" cidr="%s">' % ( - self.id, self.name, self.cidr) - - -class GCERoute(UuidMixin): - """A GCE Route object class.""" - def __init__(self, id, name, dest_range, priority, network="default", - tags=None, driver=None, extra=None): - self.id = str(id) - self.name = name - self.dest_range = dest_range - self.priority = priority - self.network = network - self.tags = tags - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this route - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_route(route=self) - - def __repr__(self): - return '<GCERoute id="%s" name="%s" dest_range="%s" network="%s">' % ( - self.id, self.name, self.dest_range, - hasattr(self.network, 'name') and self.network.name or - self.network) - - -class GCENodeSize(NodeSize): - """A GCE Node Size (MachineType) class.""" - def __init__(self, id, name, ram, disk, bandwidth, price, driver, - extra=None): - self.extra = extra - super(GCENodeSize, self).__init__(id, name, ram, disk, bandwidth, - price, driver, extra=extra) - - -class GCEProject(UuidMixin): - """GCE Project information.""" - def __init__(self, id, name, metadata, quotas, driver, extra=None): - self.id = str(id) - self.name = name - self.metadata = metadata - self.quotas = quotas - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def set_common_instance_metadata(self, metadata=None, force=False): - """ - Set common instance metadata for the project. Common uses - are for setting 'sshKeys', or setting a project-wide - 'startup-script' for all nodes (instances). Passing in - ``None`` for the 'metadata' parameter will clear out all common - instance metadata *except* for 'sshKeys'. If you also want to - update 'sshKeys', set the 'force' parameter to ``True``. - - :param metadata: Dictionary of metadata. Can be either a standard - python dictionary, or the format expected by - GCE (e.g. {'items': [{'key': k1, 'value': v1}, ...}] - :type metadata: ``dict`` or ``None`` - - :param force: Force update of 'sshKeys'. If force is ``False`` (the - default), existing sshKeys will be retained. Setting - force to ``True`` will either replace sshKeys if a new - a new value is supplied, or deleted if no new value - is supplied. - :type force: ``bool`` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_set_common_instance_metadata(self, metadata) - - def set_usage_export_bucket(self, bucket, prefix=None): - """ - Used to retain Compute Engine resource usage, storing the CSV data in - a Google Cloud Storage bucket. See the - `docs <https://cloud.google.com/compute/docs/usage-export>`_ for more - information. Please ensure you have followed the necessary setup steps - prior to enabling this feature (e.g. bucket exists, ACLs are in place, - etc.) - - :param bucket: Name of the Google Cloud Storage bucket. Specify the - name in either 'gs://<bucket_name>' or the full URL - 'https://storage.googleapis.com/<bucket_name>'. - :type bucket: ``str`` - - :param prefix: Optional prefix string for all reports. - :type prefix: ``str`` or ``None`` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_set_usage_export_bucket(self, bucket, prefix) - - def __repr__(self): - return '<GCEProject id="%s" name="%s">' % (self.id, self.name) - - -class GCERegion(UuidMixin): - def __init__(self, id, name, status, zones, quotas, deprecated, driver, - extra=None): - self.id = str(id) - self.name = name - self.status = status - self.zones = zones - self.quotas = quotas - self.deprecated = deprecated - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def __repr__(self): - return '<GCERegion id="%s" name="%s", status="%s">' % ( - self.id, self.name, self.status) - - -class GCESnapshot(VolumeSnapshot): - def __init__(self, id, name, size, status, driver, extra=None, - created=None): - self.name = name - self.status = status - super(GCESnapshot, self).__init__(id, driver, size, extra, created) - - -class GCETargetHttpProxy(UuidMixin): - def __init__(self, id, name, urlmap, driver, extra=None): - self.id = str(id) - self.name = name - self.urlmap = urlmap - self.driver = driver - self.extra = extra or {} - UuidMixin.__init__(self) - - def __repr__(self): - return '<GCETargetHttpProxy id="%s" name="%s">' % ( - self.id, self.name) - - def destroy(self): - """ - Destroy this Target HTTP Proxy. - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_targethttpproxy(targethttpproxy=self) - - -class GCETargetInstance(UuidMixin): - def __init__(self, id, name, zone, node, driver, extra=None): - self.id = str(id) - self.name = name - self.zone = zone - self.node = node - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def destroy(self): - """ - Destroy this Target Instance - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_targetinstance(targetinstance=self) - - def __repr__(self): - return '<GCETargetInstance id="%s" name="%s" zone="%s" node="%s">' % ( - self.id, self.name, self.zone.name, - (hasattr(self.node, 'name') and self.node.name or self.node)) - - -class GCETargetPool(UuidMixin): - def __init__(self, id, name, region, healthchecks, nodes, driver, - extra=None): - self.id = str(id) - self.name = name - self.region = region - self.healthchecks = healthchecks - self.nodes = nodes - self.driver = driver - self.extra = extra - UuidMixin.__init__(self) - - def add_node(self, node): - """ - Add a node to this target pool. - - :param node: Node to add - :type node: ``str`` or :class:`Node` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_targetpool_add_node(targetpool=self, node=node) - - def remove_node(self, node): - """ - Remove a node from this target pool. - - :param node: Node to remove - :type node: ``str`` or :class:`Node` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_targetpool_remove_node(targetpool=self, - node=node) - - def add_healthcheck(self, healthcheck): - """ - Add a healthcheck to this target pool. - - :param healthcheck: Healthcheck to add - :type healthcheck: ``str`` or :class:`GCEHealthCheck` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_targetpool_add_healthcheck( - targetpool=self, healthcheck=healthcheck) - - def remove_healthcheck(self, healthcheck): - """ - Remove a healthcheck from this target pool. - - :param healthcheck: Healthcheck to remove - :type healthcheck: ``str`` or :class:`GCEHealthCheck` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_targetpool_remove_healthcheck( - targetpool=self, healthcheck=healthcheck) - - def set_backup_targetpool(self, backup_targetpool, failover_ratio=0.1): - """ - Set a backup targetpool. - - :param backup_targetpool: The existing targetpool to use for - failover traffic. - :type backup_targetpool: :class:`GCETargetPool` - - :param failover_ratio: The percentage of healthy VMs must fall at or - below this value before traffic will be sent - to the backup targetpool (default 0.10) - :type failover_ratio: ``float`` - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_targetpool_set_backup_targetpool( - targetpool=self, backup_targetpool=backup_targetpool, - failover_ratio=failover_ratio) - - def get_health(self, node=None): - """ - Return a hash of target pool instances and their health. - - :param node: Optional node to specify if only a specific node's - health status should be returned - :type node: ``str``, ``Node``, or ``None`` - - :return: List of hashes of nodes and their respective health - :rtype: ``list`` of ``dict`` - """ - return self.driver.ex_targetpool_get_health(targetpool=self, node=node) - - def destroy(self): - """ - Destroy this Target Pool - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_targetpool(targetpool=self) - - def __repr__(self): - return '<GCETargetPool id="%s" name="%s" region="%s">' % ( - self.id, self.name, self.region.name) - - -class GCEUrlMap(UuidMixin): - """A GCE URL Map.""" - - def __init__(self, id, name, default_service, host_rules, path_matchers, - tests, driver, extra=None): - self.id = str(id) - self.name = name - self.default_service = default_service - self.host_rules = host_rules or [] - self.path_matchers = path_matchers or [] - self.tests = tests or [] - self.driver = driver - self.extra = extra or {} - UuidMixin.__init__(self) - - def __repr__(self): - return '<GCEUrlMap id="%s" name="%s">' % ( - self.id, self.name) - - def destroy(self): - """ - Destroy this URL Map - - :return: True if successful - :rtype: ``bool`` - """ - return self.driver.ex_destroy_urlmap(urlmap=self) - - -class GCEZone(NodeLocation): - """Subclass of NodeLocation to provide additional information.""" - def __init__(self, id, name, status, maintenance_windows, deprecated, - driver, extra=None): - self.status = status - self.maintenance_windows = maintenance_windows - self.deprecated = deprecated - self.extra = extra - country = name.split('-')[0] - super(GCEZone, self).__init__(id=str(id), name=name, country=country, - driver=driver) - - @property - def time_until_mw(self): - """ - Returns the time until the next Maintenance Window as a - datetime.timedelta object. - """ - return self._get_time_until_mw() - - @property - def next_mw_duration(self): - """ - Returns the duration of the next Maintenance Window as a - datetime.timedelta object. - """ - return self._get_next_mw_duration() - - def _now(self): - """ - Returns current UTC time. - - Can be overridden in unittests. - """ - return datetime.datetime.utcnow() - - def _get_next_maint(self): - """ - Returns the next Maintenance Window. - - :return: A dictionary containing maintenance window info (or None if - no maintenance windows are scheduled) - The dictionary contains 4 keys with values of type ``str`` - - name: The name of the maintenance window - - description: Description of the maintenance window - - beginTime: RFC3339 Timestamp - - endTime: RFC3339 Timestamp - :rtype: ``dict`` or ``None`` - """ - begin = None - next_window = None - if not self.maintenance_windows: - return None - if len(self.maintenance_windows) == 1: - return self.maintenance_windows[0] - for mw in self.maintenance_windows: - begin_next = timestamp_to_datetime(mw['beginTime']) - if (not begin) or (begin_next < begin): - begin = begin_next - next_window = mw - return next_window - - def _get_time_until_mw(self): - """ - Returns time until next maintenance window. - - :return: Time until next maintenance window (or None if no - maintenance windows are scheduled) - :rtype: :class:`datetime.timedelta` or ``None`` - """ - next_window = self._get_next_maint() - if not next_window: - return None - now = self._now() - next_begin = timestamp_to_datetime(next_window['beginTime']) - return next_begin - now - - def _get_next_mw_duration(self): - """ - Returns the duration of the next maintenance window. - - :return: Duration of next maintenance window (or None if no - maintenance windows are scheduled) - :rtype: :class:`datetime.timedelta` or ``None`` - """ - next_window = self._get_next_maint() - if not next_window: - return None - next_begin = timestamp_to_datetime(next_window['beginTime']) - next_end = timestamp_to_datetime(next_window['endTime']) - return next_end - next_begin - - def __repr__(self): - return '<GCEZone id="%s" name="%s" status="%s">' % (self.id, self.name, - self.status) - - -class GCENodeDriver(NodeDriver): - """ - GCE Node Driver class. - - This is the primary driver for interacting with Google Compute Engine. It - contains all of the standard libcloud methods, plus additional ex_* methods - for more features. - - Note that many methods allow either objects or strings (or lists of - objects/strings). In most cases, passing strings instead of objects will - result in additional GCE API calls. - """ - connectionCls = GCEConnection - api_name = 'google' - name = "Google Compute Engine" - type = Provider.GCE - website = 'https://cloud.google.com/' - - # Google Compute Engine node states are mapped to Libcloud node states - # per the following dict. GCE does not have an actual 'stopped' state - # but instead uses a 'terminated' state to indicate the node exists - # but is not running. In order to better match libcloud, GCE maps this - # 'terminated' state to 'STOPPED'. - # Also, when a node is deleted from GCE, it no longer exists and instead - # will result in a ResourceNotFound error versus returning a placeholder - # node in a 'terminated' state. - # For more details, please see GCE's docs, - # https://cloud.google.com/compute/docs/instances#checkmachinestatus - NODE_STATE_MAP = { - "PROVISIONING": NodeState.PENDING, - "STAGING": NodeState.PENDING, - "RUNNING": NodeState.RUNNING, - "STOPPING": NodeState.PENDING, - "TERMINATED": NodeState.STOPPED, - "UNKNOWN": NodeState.UNKNOWN - } - - AUTH_URL = "https://www.googleapis.com/auth/" - SA_SCOPES_MAP = { - # list derived from 'gcloud compute instances create --help' - "bigquery": "bigquery", - "cloud-platform": "cloud-platform", - "compute-ro": "compute.readonly", - "compute-rw": "compute", - "datastore": "datastore", - "logging-write": "logging.write", - "monitoring": "monitoring", - "sql": "sqlservice", - "sql-admin": "sqlservice.admin", - "storage-full": "devstorage.full_control", - "storage-ro": "devstorage.read_only", - "storage-rw": "devstorage.read_write", - "taskqueue": "taskqueue", - "useraccounts-ro": "cloud.useraccounts.readonly", - "useraccounts-rw": "cloud.useraccounts", - "userinfo-email": "userinfo.email" - } - - IMAGE_PROJECTS = { - "centos-cloud": ["centos"], - "coreos-cloud": ["coreos"], - "debian-cloud": ["debian", "backports"], - "gce-nvme": ["nvme-backports"], - "google-containers": ["container-vm"], - "opensuse-cloud": ["opensuse"], - "rhel-cloud": ["rhel"], - "suse-cloud": ["sles", "suse"], - "ubuntu-os-cloud": ["ubuntu"], - "windows-cloud": ["windows"], - } - - def __init__(self, user_id, key=None, datacenter=None, project=None, - auth_type=None, scopes=None, credential_file=None, **kwargs): - """ - :param user_id: The email address (for service accounts) or Client ID - (for installed apps) to be used for authentication. - :type user_id: ``str`` - - :param key: The RSA Key (for service accounts) or file path containing - key or Client Secret (for installed apps) to be used for - authentication. - :type key: ``str`` - - :keyword datacenter: The name of the datacenter (zone) used for - operations. - :type datacenter: ``str`` - - :keyword project: Your GCE project name. (required) - :type project: ``str`` - - :keyword auth_type: Accepted values are "SA" or "IA" or "GCE" - ("Service Account" or "Installed Application" or - "GCE" if libcloud is being used on a GCE instance - with service account enabled). - If not supplied, auth_type will be guessed based - on value of user_id or if the code is being - executed in a GCE instance. - :type auth_type: ``str`` - - :keyword scopes: List of authorization URLs. Default is empty and - grants read/write to Compute, Storage, DNS. - :type scopes: ``list`` - - :keyword credential_file: Path to file for caching authentication - information used by GCEConnection. - :type credential_file: ``str`` - """ - if not project: - raise ValueError('Project name must be specified using ' - '"project" keyword.') - - self.auth_type = auth_type - self.project = project - self.scopes = scopes - self.credential_file = credential_file or \ - GoogleOAuth2Credential.default_credential_file + '.' + self.project - - super(GCENodeDriver, self).__init__(user_id, key, **kwargs) - - # Cache Zone and Region information to reduce API calls and - # increase speed - self.base_path = '/compute/%s/projects/%s' % (API_VERSION, - self.project) - self.zone_list = self.ex_list_zones() - self.zone_dict = {} - for zone in self.zone_list: - self.zone_dict[zone.name] = zone - if datacenter: - self.zone = self.ex_get_zone(datacenter) - else: - self.zone = None - - self.region_list = self.ex_list_regions() - self.region_dict = {} - for region in self.region_list: - self.region_dict[region.name] = region - - if self.zone: - self.region = self._get_region_from_zone(self.zone) - else: - self.region = None - - def ex_add_access_config(self, node, name, nic, nat_ip=None, - config_type=None): - """ - Add a network interface access configuration to a node. - - :keyword node: The existing target Node (instance) that will receive - the new access config. - :type node: ``Node`` - - :keyword name: Name of the new access config. - :type node: ``str`` - - :keyword nat_ip: The external existing static IP Address to use for - the access config. If not provided, an ephemeral - IP address will be allocated. - :type nat_ip: ``str`` or ``None`` - - :keyword config_type: The type of access config to create. Currently - the only supported type is 'ONE_TO_ONE_NAT'. - :type config_type: ``str`` or ``None`` - - :return: True if successful - :rtype: ``bool`` - """ - if not isinstance(node, Node): - raise ValueError("Must specify a valid libcloud node object.") - node_name = node.name - zone_name = node.extra['zone'].name - - config = {'name': name} - if config_type is None: - config_type = 'ONE_TO_ONE_NAT' - config['type'] = config_type - - if nat_ip is not None: - config['natIP'] = nat_ip - params = {'networkInterface': nic} - request = '/zones/%s/instances/%s/addAccessConfig' % (zone_name, - node_name) - self.connection.async_request(request, method='POST', - data=config, params=params) - return True - - def ex_delete_access_config(self, node, name, nic): - """ - Delete a network interface access configuration from a node. - - :keyword node: The existing target Node (instance) for the request. - :type node: ``Node`` - - :keyword name: Name of the access config. - :type node: ``str`` - - :keyword nic: Name of the network interface. - :type nic: ``str`` - - :return: True if successful - :rtype: ``bool`` - """ - if not isinstance(node, Node): - raise ValueError("Must specify a valid libcloud node object.") - node_name = node.name - zone_name = node.extra['zone'].name - - params = {'accessConfig': name, 'networkInterface': nic} - request = '/zones/%s/instances/%s/deleteAccessConfig' % (zone_name, - node_name) - self.connection.async_request(request, method='POST', params=params) - return True - - def ex_set_node_metadata(self, node, metadata): - """ - Set metadata for the specified node. - - :keyword node: The existing target Node (instance) for the request. - :type node: ``Node`` - - :keyword metadata: Set (or clear with None) metadata for this - particular node. - :type metadata: ``dict`` or ``None`` - - :return: True if successful - :rtype: ``bool`` - """ - if not isinstance(node, Node): - raise ValueError("Must specify a valid libcloud node object.") - node_name = node.name - zone_name = node.extra['zone'].name - if 'metadata' in node.extra and \ - 'fingerprint' in node.extra['metadata']: - current_fp = node.extra['metadata']['fingerprint'] - else: - current_fp = 'absent' - body = self._format_metadata(current_fp, metadata) - request = '/zones/%s/instances/%s/setMetadata' % (zone_name, - node_name) - self.connection.async_request(request, method='POST', data=body) - return True - - def ex_get_serial_output(self, node): - """ - Fetch the console/serial port output from the node. - - :keyword node: The existing target Node (instance) for the request. - :type node: ``Node`` - - :return: A string containing serial port output of the node. - :rtype: ``str`` - """ - if not isinstance(node, Node): - raise ValueError("Must specify a valid libcloud node object.") - node_name = node.name - zone_name = node.extra['zone'].name - request = '/zones/%s/instances/%s/serialPort' % (zone_name, - node_name) - response = self.connection.request(request, method='GET').object - return response['contents'] - - def ex_list(self, list_fn, **kwargs): - """ - Wrap a list method in a :class:`GCEList` iterator. - - >>> for sublist in driver.ex_list(driver.ex_list_urlmaps).page(1): - ... sublist - ... - [<GCEUrlMap id="..." name="cli-map">] - [<GCEUrlMap id="..." name="lc-map">] - [<GCEUrlMap id="..." name="web-map">] - - :param list_fn: A bound list method from :class:`GCENodeDriver`. - :type list_fn: ``instancemethod`` - - :return: An iterator that returns sublists from list_fn. - :rtype: :class:`GCEList` - """ - return GCEList(driver=self, list_fn=list_fn, **kwargs) - - def ex_list_disktypes(self, zone=None): - """ - Return a list of DiskTypes for a zone or all. - - :keyword zone: The zone to return DiskTypes from. For example: - 'us-central1-a'. If None, will return DiskTypes from - self.zone. If 'all', will return all DiskTypes. - :type zone: ``str`` or ``None`` - - :return: A list of static DiskType objects. - :rtype: ``list`` of :class:`GCEDiskType` - """ - list_disktypes = [] - zone = self._set_zone(zone) - if zone is None: - request = '/aggregated/diskTypes' - else: - request = '/zones/%s/diskTypes' % (zone.name) - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated result returns dictionaries for each region - if zone is None: - for v in response['items'].values(): - zone_disktypes = [self._to_disktype(a) for a in - v.get('diskTypes', [])] - list_disktypes.extend(zone_disktypes) - else: - list_disktypes = [self._to_disktype(a) for a in - response['items']] - return list_disktypes - - def ex_set_usage_export_bucket(self, bucket, prefix=None): - """ - Used to retain Compute Engine resource usage, storing the CSV data in - a Google Cloud Storage bucket. See the - `docs <https://cloud.google.com/compute/docs/usage-export>`_ for more - information. Please ensure you have followed the necessary setup steps - prior to enabling this feature (e.g. bucket exists, ACLs are in place, - etc.) - - :param bucket: Name of the Google Cloud Storage bucket. Specify the - name in either 'gs://<bucket_name>' or the full URL - 'https://storage.googleapis.com/<bucket_name>'. - :type bucket: ``str`` - - :param prefix: Optional prefix string for all reports. - :type prefix: ``str`` or ``None`` - - :return: True if successful - :rtype: ``bool`` - """ - if bucket.startswith('https://www.googleapis.com/') or \ - bucket.startswith('gs://'): - data = {'bucketName': bucket} - else: - raise ValueError("Invalid bucket name: %s" % bucket) - if prefix: - data['reportNamePrefix'] = prefix - - request = '/setUsageExportBucket' - self.connection.async_request(request, method='POST', data=data) - return True - - def ex_set_common_instance_metadata(self, metadata=None, force=False): - """ - Set common instance metadata for the project. Common uses - are for setting 'sshKeys', or setting a project-wide - 'startup-script' for all nodes (instances). Passing in - ``None`` for the 'metadata' parameter will clear out all common - instance metadata *except* for 'sshKeys'. If you also want to - update 'sshKeys', set the 'force' parameter to ``True``. - - :param metadata: Dictionary of metadata. Can be either a standard - python dictionary, or the format expected by - GCE (e.g. {'items': [{'key': k1, 'value': v1}, ...}] - :type metadata: ``dict`` or ``None`` - - :param force: Force update of 'sshKeys'. If force is ``False`` (the - default), existing sshKeys will be retained. Setting - force to ``True`` will either replace sshKeys if a new - a new value is supplied, or deleted if no new value - is supplied. - :type force: ``bool`` - - :return: True if successful - :rtype: ``bool`` - """ - if metadata: - metadata = self._format_metadata('na', metadata) - - request = '/setCommonInstanceMetadata' - - project = self.ex_get_project() - current_metadata = project.extra['commonInstanceMetadata'] - fingerprint = current_metadata['fingerprint'] - md_items = [] - if 'items' in current_metadata: - md_items = current_metadata['items'] - - # grab copy of current 'sshKeys' in case we want to retain them - current_keys = "" - for md in md_items: - if md['key'] == 'sshKeys': - current_keys = md['value'] - - new_md = self._set_project_metadata(metadata, force, current_keys) - - md = {'fingerprint': fingerprint, 'items': new_md} - self.connection.async_request(request, method='POST', data=md) - return True - - def ex_list_addresses(self, region=None): - """ - Return a list of static addresses for a region, 'global', or all. - - :keyword region: The region to return addresses from. For example: - 'us-central1'. If None, will return addresses from - region of self.zone. If 'all', will return all - addresses. If 'global', it will return addresses in - the global namespace. - :type region: ``str`` or ``None`` - - :return: A list of static address objects. - :rtype: ``list`` of :class:`GCEAddress` - """ - list_addresses = [] - if region != 'global': - region = self._set_region(region) - if region is None: - request = '/aggregated/addresses' - elif region == 'global': - request = '/global/addresses' - else: - request = '/regions/%s/addresses' % (region.name) - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated result returns dictionaries for each region - if region is None: - for v in response['items'].values(): - region_addresses = [self._to_address(a) for a in - v.get('addresses', [])] - list_addresses.extend(region_addresses) - else: - list_addresses = [self._to_address(a) for a in - response['items']] - return list_addresses - - def ex_list_backendservices(self): - """ - Return a list of backend services. - - :return: A list of backend service objects. - :rtype: ``list`` of :class:`GCEBackendService` - """ - list_backendservices = [] - response = self.connection.request('/global/backendServices', - method='GET').object - - list_backendservices = [self._to_backendservice(d) for d in - response.get('items', [])] - - return list_backendservices - - def ex_list_healthchecks(self): - """ - Return the list of health checks. - - :return: A list of health check objects. - :rtype: ``list`` of :class:`GCEHealthCheck` - """ - list_healthchecks = [] - request = '/global/httpHealthChecks' - response = self.connection.request(request, method='GET').object - list_healthchecks = [self._to_healthcheck(h) for h in - response.get('items', [])] - return list_healthchecks - - def ex_list_firewalls(self): - """ - Return the list of firewalls. - - :return: A list of firewall objects. - :rtype: ``list`` of :class:`GCEFirewall` - """ - list_firewalls = [] - request = '/global/firewalls' - response = self.connection.request(request, method='GET').object - list_firewalls = [self._to_firewall(f) for f in - response.get('items', [])] - return list_firewalls - - def ex_list_forwarding_rules(self, region=None, global_rules=False): - """ - Return the list of forwarding rules for a region or all. - - :keyword region: The region to return forwarding rules from. For - example: 'us-central1'. If None, will return - forwarding rules from the region of self.region - (which is based on self.zone). If 'all', will - return forwarding rules for all regions, which does - not include the global forwarding rules. - :type region: ``str`` or :class:`GCERegion` or ``None`` - - :keyword global_rules: List global forwarding rules instead of - per-region rules. Setting True will cause - 'region' parameter to be ignored. - :type global_rules: ``bool`` - - :return: A list of forwarding rule objects. - :rtype: ``list`` of :class:`GCEForwardingRule` - """ - list_forwarding_rules = [] - if global_rules: - region = None - request = '/global/forwardingRules' - else: - region = self._set_region(region) - if region is None: - request = '/aggregated/forwardingRules' - else: - request = '/regions/%s/forwardingRules' % (region.name) - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated result returns dictionaries for each region - if not global_rules and region is None: - for v in response['items'].values(): - region_forwarding_rules = [self._to_forwarding_rule(f) for - f in v.get('forwardingRules', - [])] - list_forwarding_rules.extend(region_forwarding_rules) - else: - list_forwarding_rules = [self._to_forwarding_rule(f) for f in - response['items']] - return list_forwarding_rules - - def list_images(self, ex_project=None, ex_include_deprecated=False): - """ - Return a list of image objects. If no project is specified, a list of - all non-deprecated global and vendor images images is returned. By - default, only non-deprecated images are returned. - - :keyword ex_project: Optional alternate project name. - :type ex_project: ``str``, ``list`` of ``str``, or ``None`` - - :keyword ex_include_deprecated: If True, even DEPRECATED images will - be returned. - :type ex_include_deprecated: ``bool`` - - :return: List of GCENodeImage objects - :rtype: ``list`` of :class:`GCENodeImage` - """ - dep = ex_include_deprecated - if ex_project is not None: - return self.ex_list_project_images(ex_project=ex_project, - ex_include_deprecated=dep) - image_list = self.ex_list_project_images(ex_project=None, - ex_include_deprecated=dep) - for img_proj in list(self.IMAGE_PROJECTS.keys()): - try: - image_list.extend( - self.ex_list_project_images(ex_project=img_proj, - ex_include_deprecated=dep)) - except: - # do not break if an OS type is invalid - pass - return image_list - - def ex_list_project_images(self, ex_project=None, - ex_include_deprecated=False): - """ - Return a list of image objects for a project. If no project is - specified, only a list of 'global' images is returned. - - :keyword ex_project: Optional alternate project name. - :type ex_project: ``str``, ``list`` of ``str``, or ``None`` - - :keyword ex_include_deprecated: If True, even DEPRECATED images will - be returned. - :type ex_include_deprecated: ``bool`` - - :return: List of GCENodeImage objects - :rtype: ``list`` of :class:`GCENodeImage` - """ - list_images = [] - request = '/global/images' - if ex_project is None: - response = self.connection.request(request, method='GET').object - for img in response.get('items', []): - if 'deprecated' not in img: - list_images.append(self._to_node_image(img)) - else: - if ex_include_deprecated: - list_images.append(self._to_node_image(img)) - else: - list_images = [] - # Save the connection request_path - save_request_path = self.connection.request_path - if isinstance(ex_project, str): - ex_project = [ex_project] - for proj in ex_project: - # Override the connection request path - new_request_path = save_request_path.replace(self.project, - proj) - self.connection.request_path = new_request_path - try: - response = self.connection.request(request, - method='GET').object - except: - raise - finally: - # Restore the connection request_path - self.connection.request_path = save_request_path - for img in response.get('items', []): - if 'deprecated' not in img: - list_images.append(self._to_node_image(img)) - else: - if ex_include_deprecated: - list_images.append(self._to_node_image(img)) - return list_images - - def list_locations(self): - """ - Return a list of locations (zones). - - The :class:`ex_list_zones` method returns more comprehensive results, - but this is here for compatibility. - - :return: List of NodeLocation objects - :rtype: ``list`` of :class:`NodeLocation` - """ - list_locations = [] - request = '/zones' - response = self.connection.request(request, method='GET').object - list_locations = [self._to_node_location(l) for l in response['items']] - return list_locations - - def ex_list_routes(self): - """ - Return the list of routes. - - :return: A list of route objects. - :rtype: ``list`` of :class:`GCERoute` - """ - list_routes = [] - request = '/global/routes' - response = self.connection.request(request, method='GET').object - list_routes = [self._to_route(n) for n in - response.get('items', [])] - return list_routes - - def ex_list_networks(self): - """ - Return the list of networks. - - :return: A list of network objects. - :rtype: ``list`` of :class:`GCENetwork` - """ - list_networks = [] - request = '/global/networks' - response = self.connection.request(request, method='GET').object - list_networks = [self._to_network(n) for n in - response.get('items', [])] - return list_networks - - def list_nodes(self, ex_zone=None): - """ - Return a list of nodes in the current zone or all zones. - - :keyword ex_zone: Optional zone name or 'all' - :type ex_zone: ``str`` or :class:`GCEZone` or - :class:`NodeLocation` or ``None`` - - :return: List of Node objects - :rtype: ``list`` of :class:`Node` - """ - list_nodes = [] - zone = self._set_zone(ex_zone) - if zone is None: - request = '/aggregated/instances' - else: - request = '/zones/%s/instances' % (zone.name) - - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated response returns a dict for each zone - if zone is None: - for v in response['items'].values(): - for i in v.get('instances', []): - try: - list_nodes.append(self._to_node(i)) - # If a GCE node has been deleted between - # - is was listed by `request('.../instances', 'GET') - # - it is converted by `self._to_node(i)` - # `_to_node()` will raise a ResourceNotFoundError. - # - # Just ignore that node and return the list of the - # other nodes. - except ResourceNotFoundError: - pass - else: - for i in response['items']: - try: - list_nodes.append(self._to_node(i)) - # If a GCE node has been deleted between - # - is was listed by `request('.../instances', 'GET') - # - it is converted by `self._to_node(i)` - # `_to_node()` will raise a ResourceNotFoundError. - # - # Just ignore that node and return the list of the - # other nodes. - except ResourceNotFoundError: - pass - return list_nodes - - def ex_list_regions(self): - """ - Return the list of regions. - - :return: A list of region objects. - :rtype: ``list`` of :class:`GCERegion` - """ - list_regions = [] - request = '/regions' - response = self.connection.request(request, method='GET').object - list_regions = [self._to_region(r) for r in response['items']] - return list_regions - - def list_sizes(self, location=None): - """ - Return a list of sizes (machineTypes) in a zone. - - :keyword location: Location or Zone for sizes - :type location: ``str`` or :class:`GCEZone` or - :class:`NodeLocation` or ``None`` - - :return: List of GCENodeSize objects - :rtype: ``list`` of :class:`GCENodeSize` - """ - list_sizes = [] - zone = self._set_zone(location) - if zone is None: - request = '/aggregated/machineTypes' - else: - request = '/zones/%s/machineTypes' % (zone.name) - - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated response returns a dict for each zone - if zone is None: - for v in response['items'].values(): - zone_sizes = [self._to_node_size(s) for s in - v.get('machineTypes', [])] - list_sizes.extend(zone_sizes) - else: - list_sizes = [self._to_node_size(s) for s in response['items']] - return list_sizes - - def ex_list_snapshots(self): - """ - Return the list of disk snapshots in the project. - - :return: A list of snapshot objects - :rtype: ``list`` of :class:`GCESnapshot` - """ - list_snapshots = [] - request = '/global/snapshots' - response = self.connection.request(request, method='GET').object - list_snapshots = [self._to_snapshot(s) for s in - response.get('items', [])] - return list_snapshots - - def ex_list_targethttpproxies(self): - """ - Return the list of target HTTP proxies. - - :return: A list of target http proxy objects - :rtype: ``list`` of :class:`GCETargetHttpProxy` - """ - request = '/global/targetHttpProxies' - response = self.connection.request(request, method='GET').object - return [self._to_targethttpproxy(u) for u in - response.get('items', [])] - - def ex_list_targetinstances(self, zone=None): - """ - Return the list of target instances. - - :return: A list of target instance objects - :rtype: ``list`` of :class:`GCETargetInstance` - """ - list_targetinstances = [] - zone = self._set_zone(zone) - if zone is None: - request = '/aggregated/targetInstances' - else: - request = '/zones/%s/targetInstances' % (zone.name) - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated result returns dictionaries for each region - if zone is None: - for v in response['items'].values(): - zone_targetinstances = [self._to_targetinstance(t) for t in - v.get('targetInstances', [])] - list_targetinstances.extend(zone_targetinstances) - else: - list_targetinstances = [self._to_targetinstance(t) for t in - response['items']] - return list_targetinstances - - def ex_list_targetpools(self, region=None): - """ - Return the list of target pools. - - :return: A list of target pool objects - :rtype: ``list`` of :class:`GCETargetPool` - """ - list_targetpools = [] - region = self._set_region(region) - if region is None: - request = '/aggregated/targetPools' - else: - request = '/regions/%s/targetPools' % (region.name) - response = self.connection.request(request, method='GET').object - - if 'items' in response: - # The aggregated result returns dictionaries for each region - if region is None: - for v in response['items'].values(): - region_targetpools = [self._to_targetpool(t) for t in - v.get('targetPools', [])] - list_targetpools.extend(region_targetpools) - else: - list_targetpools = [self._to_targetpool(t) for t in - response['items']] - return list_targetpools - - def ex_list_urlmaps(self): - """ - Return the list of URL Maps in the project. - - :return: A list of url map objects - :rtype: ``list`` of :class:`GCEUrlMap` - """ - request = '/global/urlMaps' - response = self.connection.request(request, method='GET').object - return [self._to_urlmap(u) for u in response.get('items', [])] - - def list_volumes(self, ex_zone=None): - """ - Return a list of volumes for a zone or all. - - Will return list from provided zone, or from the default zone unless - given the value of 'all'. - - :keyword ex_zone: The zone to return volumes from. - :type ex_zone: ``str`` or :class:`GCEZone` or - :class:`NodeLocation` or ``None`` - - :return: A list of volume objects. - :rtype: ``list`` of :class:`StorageVolume` - """ - list_volumes = [] - zone = self._set_zone(ex_zone) - if zone is None: - request = '/aggregated/disks' - else: - request = '/zones/%s/disks' % (zone.name) - - response = self.connection.request(request, method='GET').object - if 'items' in response: - # The aggregated response returns a dict for each zone - if zone is None: - for v in response['items'].values(): - zone_volumes = [self._to_storage_volume(d) for d in - v.get('disks', [])] - list_volumes.extend(zone_volumes) - else: - list_volumes = [self._to_storage_volume(d) for d in - response['items']] - return list_volumes - - def ex_list_zones(self): - """ - Return the list of zones. - - :return: A list of zone objects. - :rtype: ``list`` of :class:`GCEZone` - """ - list_zones = [] - request = '/zones' - response = self.connection.request(request, method='GET').object - list_zones = [self._to_zone(z) for z in response['items']] - return list_zones - - def ex_create_address(self, name, region=None, address=None, - description=None): - """ - Create a static address in a region, or a global address. - - :param name: Name of static address - :type name: ``str`` - - :keyword region: Name of region for the address (e.g. 'us-central1') - Use 'global' to create a global address. - :type region: ``str`` or :class:`GCERegion` - - :keyword address: Ephemeral IP address to promote to a static one - (e.g. 'xxx.xxx.xxx.xxx') - :type address: ``str`` or ``None`` - - :keyword description: Optional descriptive comment. - :type description: ``str`` or ``None`` - - :return: Static Address object - :rtype: :class:`GCEAddress` - """ - region = region or self.region - if region != 'global' and not hasattr(region, 'name'): - region = self.ex_get_region(region) - elif region is None: - raise ValueError('REGION_NOT_SPECIFIED', - 'Region must be provided for an address') - address_data = {'name': name} - if address: - address_data['address'] = address - if description: - address_data['description'] = description - if region == 'global': - request = '/global/addresses' - else: - request = '/regions/%s/addresses' % (region.name) - self.connection.async_request(request, method='POST', - data=address_data) - return self.ex_get_address(name, region=region) - - def ex_create_backendservice(self, name, healthchecks): - """ - Create a global backend service. - - :param name: Name of the backend service - :type name: ``str`` - - :keyword healthchecks: A list of HTTP Health Checks to use for this - service. There must be at least one. - :type healthchecks: ``list`` of (``str`` or - :class:`GCEHealthCheck`) - - :return: A Backend Service object - :rtype: :class:`GCEBackendService` - """ - backendservice_data = {'name': name, 'healthChecks': []} - - for hc in healthchecks: - if not hasattr(hc, 'extra'): - hc = self.ex_get_healthcheck(name=hc) - backendservice_data['healthChecks'].append(hc.extra['selfLink']) - - request = '/global/backendServices' - self.connection.async_request(request, method='POST', - data=backendservice_data) - return self.ex_get_backendservice(name) - - def ex_create_healthcheck(self, name, host=None, path=None, port=None, - interval=None, timeout=None, - unhealthy_threshold=None, - healthy_threshold=None, - description=None): - """ - Create an Http Health Check. - - :param name: Name of health check - :type name: ``str`` - - :keyword host: Hostname of health check request. Defaults to empty - and public IP is used instead. - :type host: ``str`` - - :keyword path: The request path for the check. Defaults to /. - :type path: ``str`` - - :keyword port: The TCP port number for the check. Defaults to 80. - :type port: ``int`` - - :keyword interval: How often (in seconds) to check. Defaults to 5. - :type interval: ``int`` - - :keyword timeout: How long to wait before failing. Defaults to 5. - :type timeout: ``int`` - - :keyword unhealthy_threshold: How many failures before marking - unhealthy. Defaults to 2. - :type unhealthy_threshold: ``int`` - - :keyword healthy_threshold: How many successes before marking as - healthy. Defaults to 2. - :type healthy_threshold: ``int`` - - :keyword description: The description of the check. Defaults to None. - :type description: ``str`` or ``None`` - - :return: Health Check object - :rtype: :class:`GCEHealthCheck` - """ - hc_data = {} - hc_data['name'] = name - if host: - hc_data['host'] = host - if description: - hc_data['description'] = description - # As of right now, the 'default' values aren't getting set when called - # through the API, so set them explicitly - hc_data['requestPath'] = path or '/' - hc_data['port'] = port or 80 - hc_data['checkIntervalSec'] = interval or 5 - hc_data['timeoutSec'] = timeout or 5 - hc_data['unhealthyThreshold'] = unhealthy_threshold or 2 - hc_data['healthyThreshold'] = healthy_threshold or 2 - - request = '/global/httpHealthChecks' - - self.connection.async_request(request, method='POST', data=hc_data) - return self.ex_get_healthcheck(name) - - def ex_create_firewall(self, name, allowed, network='default', - source_ranges=None, source_tags=None, - target_tags=None): - """ - Create a firewall on a network. - - Firewall rules should be supplied in the "allowed" field. This is a - list of dictionaries formated like so ("ports" is optional):: - - [{"IPProtocol": "<protocol string or number>", - "ports": "<port_numbers or ranges>"}] - - For example, to allow tcp on port 8080 and udp on all ports, 'allowed' - would be:: - - [{"IPProtocol": "tcp", - "ports": ["8080"]}, - {"IPProtocol": "udp"}] - - See `Firewall Reference <https://developers.google.com/compute/docs/ - reference/latest/firewalls/insert>`_ for more information. - - :param name: Name of the firewall to be created - :type name: ``str`` - - :param allowed: List of dictionaries with rules - :type allowed: ``list`` of ``dict`` - - :keyword network: The network that the firewall applies to. - :type network: ``str`` or :class:`GCENetwork` - - :keyword source_ranges: A list of IP ranges in CIDR format that the - firewall should apply to. Defaults to - ['0.0.0.0/0'] - :type source_ranges: ``list`` of ``str`` - - :keyword source_tags: A list of source instance tags the rules apply - to. - :type source_tags: ``list`` of ``str`` - - :keyword target_tags: A list of target instance tags the rules apply - to. - :type target_tags: ``list`` of ``str`` - - :return: Firewall object - :rtype: :class:`GCEFirewall` - """ - firewall_data = {} - if not hasattr(network, 'name'): - nw = self.ex_get_network(network) - else: - nw = network - - firewall_data['name'] = name - firewall_data['allowed'] = allowed - firewall_data['network'] = nw.extra['selfLink'] - if source_ranges is None and source_tags is None: - source_ranges = ['0.0.0.0/0'] - if source_ranges is not None: - firewall_data['sourceRanges'] = source_ranges - if source_tags is not None: - firewall_data['sourceTags'] = source_tags - if target_tags is not None: - firewall_data['targetTags'] = target_tags - - request = '/global/firewalls' - - self.connection.async_request(request, method='POST', - data=firewall_data) - return self.ex_get_firewall(name) - - def ex_create_forwarding_rule(self, name, target=None, region=None, - protocol='tcp', port_range=None, - address=None, description=None, - global_rule=False, targetpool=None): - """ - Create a forwarding rule. - - :param name: Name of forwarding rule to be created - :type name: ``str`` - - :keyword target: The target of this forwarding rule. For global - forwarding rules this must be a global - TargetHttpProxy. For regional rules this may be - either a TargetPool or TargetInstance. If passed - a string instead of the object, it will be the name - of a TargetHttpProxy for global rules or a - TargetPool for regional rules. A TargetInstance - must be passed by object. (required) - :type target: ``str`` or :class:`GCETargetHttpProxy` or - :class:`GCETargetInstance` or :class:`GCETargetPool` - - :keyword region: Region to create the forwarding rule in. Defaults to - self.region. Ignored if global_rule is True. - :type region: ``str`` or :class:`GCERegion` - - :keyword protocol: Should be 'tcp' or 'udp' - :type protocol: ``str`` - - :keyword port_range: Single port number or range separated by a dash. - Examples: '80', '5000-5999'. Required for global - forwarding rules, optional for regional rules. - :type port_range: ``str`` - - :keyword address: Optional static address for forwarding rule. Must be - in same region. - :type address: ``str`` or :class:`GCEAddress` - - :keyword description: The description of the forwarding rule. - Defaults to None. - :type description: ``str`` or ``None`` - - :keyword targetpool: Deprecated parameter for backwards compatibility. - Use target instead. - :type targetpool: ``str`` or :class:`GCETargetPool` - - :return: Forwarding Rule object - :rtype: :class:`GCEForwardingRule` - """ - forwarding_rule_data = {'name': name} - if global_rule: - if not hasattr(target, 'name'): - target = self.ex_get_targethttpproxy(target) - else: - region = region or self.region - if not hasattr(region, 'name'): - region = self.ex_get_region(region) - forwarding_rule_data['region'] = region.extra['selfLink'] - - if not target: - target = targetpool # Backwards compatibility - if not hasattr(target, 'name'): - target = self.ex_get_targetpool(target, region) - - forwarding_rule_data['target'] = target.extra['selfLink'] - forwarding_rule_data['IPProtocol'] = protocol.upper() - if address: - if not hasattr(address, 'name'): - address = self.ex_get_address( - address, 'global' if global_rule else region) - forwarding_rule_data['IPAddress'] = address.address - if port_range: - forwarding_rule_data['portRange'] = port_range - if description: - forwarding_rule_data['description'] = description - - if global_rule: - request = '/global/forwardingRules' - else: - request = '/regions/%s/forwardingRules' % (region.name) - - self.connection.async_request(request, method='POST', - data=forwarding_rule_data) - - return self.ex_get_forwarding_rule(name, global_rule=global_rule) - - def ex_create_image(self, name, volume, description=None, family=None, - use_existing=True, wait_for_completion=True): - """ - Create an image from the provided volume. - - :param name: The name of the image to create. - :type name: ``str`` - - :param volume: The volume to use to create the image, or the - Google Cloud Storage URI - :type volume: ``str`` or :class:`StorageVolume` - - :keyword description: Description of the new Image - :type description: ``str`` - - :keyword family: The name of the image family to which this image - belongs. If you create resources by specifying an - image family instead of a specific image name, the - resource uses the latest non-deprecated image that - is set with that family name. - :type family: ``str`` - - :keyword use_existing: If True and an image with the given name - already exists, return an object for that - image instead of attempting to create - a new image. - :type use_existing: ``bool`` - - :keyword wait_for_completion: If True, wait until the new image is - created before returning a new NodeImage - Otherwise, return a new NodeImage - instance, and let the user track the - creation progress - :type wait_for_completion: ``bool`` - - :return: A GCENodeImage object for the new image - :rtype: :class:`GCENodeImage` - - """ - image_data = {} - image_data['name'] = name - image_data['description'] = description - image_data['family'] = family - if isinstance(volume, StorageVolume): - image_data['sourceDisk'] = volume.extra['selfLink'] - image_data['zone'] = volume.extra['zone'].name - elif (isinstance(volume, str) and - volume.startswith('https://') and - volume.endswith('tar.gz')): - image_data['rawDisk'] = {'source': volume, 'containerType': 'TAR'} - else: - raise ValueError('Source must be instance of StorageVolume or URI') - - request = '/global/images' - - try: - if wait_for_completion: - self.connection.async_request(request, method='POST', - data=image_data) - else: - self.connection.request(request, method='POST', - data=image_data) - - except ResourceExistsError: - e = sys.exc_info()[1] - if not use_existing: - raise e - - return self.ex_get_image(name) - - def ex_create_route(self, name, dest_range, priority=500, - network="default", tags=None, next_hop=None, - description=None): - """ - Create a route. - - :param name: Name of route to be created - :type name: ``str`` - - :param dest_range: Address range of route in CIDR format. - :type dest_range: ``str`` - - :param priority: Priority value, lower values take precedence - :type priority: ``int`` - - :param network: The network the route belongs to. Can be either the - full URL of the network or a libcloud object. - :type network: ``str`` or ``GCENetwork`` - - :param tags: List of instance-tags for routing, empty for all nodes - :type tags: ``list`` of ``str`` or ``None`` - - :param next_hop: Next traffic hop. Use ``None`` for the default - Internet gateway, or specify an instance or IP - address. - :type next_hop: ``str``, ``Node``, or ``None`` - - :param description: Custom description for the route. - :type description: ``str`` or ``None`` - - :return: Route object - :rtype: :class:`GCERoute` - """ - route_data = {} - route_data['name'] = name - route_data['destRange'] = dest_range - route_data['priority'] = priority - route_data['description'] = description - if isinstance(network, str) and network.startswith('https://'): - network_uri = network - elif isinstance(network, str): - network = self.ex_get_network(network) - network_uri = network.extra['selfLink'] - else: - network_uri = network.extra['selfLink'] - route_data['network'] = network_uri - route_data['tags'] = tags - if next_hop is None: - url = 'https://www.googleapis.com/compute/%s/projects/%s/%s' % ( - API_VERSION, self.project, - "global/gateways/default-internet-gateway") - route_data['nextHopGateway'] = url - elif isinstance(next_hop, str): - route_data['nextHopIp'] = next_hop - else: - route_data['nextHopInstance'] = next_hop.extra['selfLink'] - - request = '/global/routes' - self.connection.async_request(request, method='POST', - data=route_data) - - return self.ex_get_route(name) - - def ex_create_network(self, name, cidr, description=None): - """ - Create a network. - - :param name: Name of network to be created - :type name: ``str`` - - :param cidr: Address range of network in CIDR format. - :type cidr: ``str`` - - :param description: Custom description for the network. - :type description: ``str`` or ``None`` - - :return: Network object - :rtype: :class:`GCENetwork` - """ - network_data = {} - network_data['name'] = name - network_data['IPv4Range'] = cidr - network_data['description'] = description - - request = '/global/networks' - - self.connection.async_request(request, method='POST', - data=network_data) - - return self.ex_get_network(name) - - def create_node(self, name, size, image, location=None, - ex_network='default', ex_tags=None, ex_metadata=None, - ex_boot_disk=None, use_existing_disk=True, - external_ip='ephemeral', ex_disk_type='pd-standard', - ex_disk_auto_delete=True, ex_service_accounts=None, - description=None, ex_can_ip_forward=None, - ex_disks_gce_struct=None, ex_nic_gce_struct=None, - ex_on_host_maintenance=None, ex_automatic_restart=None, - ex_preemptible=None): - """ - Create a new node and return a node object for the node. - - :param name: The name of the node to create. - :type name: ``str`` - - :param size: The machine type to use. - :type size: ``str`` or :class:`GCENodeSize` - - :param image: The image to use to create the node (or, if attaching - a persistent disk, the image used to create the disk) - :type image: ``str`` or :class:`GCENodeImage` or ``None`` - - :keyword location: The location (zone) to create the node in. - :type location: ``str`` or :class:`NodeLocation` or - :class:`GCEZone` or ``None`` - - :keyword ex_network: The network to associate with the node. -
<TRUNCATED>
