diff --git a/demos/gce_demo.py b/demos/gce_demo.py index 6322c9e13c..c49e5e6b8d 100755 --- a/demos/gce_demo.py +++ b/demos/gce_demo.py @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - # This example performs several tasks on Google Compute Platform. It can be # run directly or can be imported into an interactive python session. This # can also serve as live integration tests. @@ -66,8 +65,8 @@ sys.exit(1) # Add parent dir of this file's dir to sys.path (OS-agnostically) -sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__), - os.path.pardir))) +sys.path.append( + os.path.normpath(os.path.join(os.path.dirname(__file__), os.path.pardir))) from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver @@ -92,6 +91,7 @@ # Datacenter to create resources in DATACENTER = 'us-central1-f' +BACKUP_DATACENTER = 'us-east1-c' # Clean up resources at the end (can be set to false in order to # inspect resources at the end of the run). Resources will be cleaned @@ -136,6 +136,46 @@ def get_dns_driver(gce_driver=None): return driver +def create_mig(gce, mig_base_name, zone, template, postfix, num_instances=2): + """ + Creates MIG, sets named ports, modifies various text with 'postfix'. + + :param gce: An initalized GCE driver. + :type gce: :class`GCENodeDriver` + + :param zone: Zone to create Managed Instance Group in. + :type zone: :class:`GCEZone` or ``str`` + + :param template: Instance Template to use in creating MIG. + :type template: :class:`GCEInstanceTemplate` + + :param postfix: string to append to mig name, etc. Example: 'east', + 'central' + :type postfix: ``str`` + + :param num_instances: number of instances to create in MIG. Default is 2. + :type num_instances: ``int`` + + :returns: initialized Managed Instance Group. + :rtype: :class:`GCEInstanceGroupManager` + """ + mig_name = '%s-%s' % (mig_base_name, postfix) + mig = gce.ex_create_instancegroupmanager( + mig_name, zone, template, num_instances, base_instance_name=mig_name, + description='Demo for %s' % postfix) + display(' Managed Instance Group [%s] "%s" created' % (postfix.upper(), + mig.name)) + display(' ... MIG instances created: %s' % + ','.join([x['name'] for x in mig.list_managed_instances()])) + + # set the named_ports on the Instance Group. + named_ports = [{'name': '%s-http' % DEMO_BASE_NAME, 'port': 80}] + mig.set_named_ports(named_ports=named_ports) + display(' ... MIG ports set: %s' % named_ports) + + return mig + + def display(title, resource_list=[]): """ Display a list of resources. @@ -214,8 +254,32 @@ def cleanup_only(): snapshots = gce.ex_list_snapshots() display('Snapshots:', snapshots) + gfrs = gce.ex_list_forwarding_rules(global_rules=True) + display("Global Forwarding Rules", gfrs) + targetproxies = gce.ex_list_targethttpproxies() + display("Target HTTP Proxies", targetproxies) + urlmaps = gce.ex_list_urlmaps() + display("URLMaps", urlmaps) + bes = gce.ex_list_backendservices() + display("Backend Services", bes) + migs = gce.ex_list_instancegroupmanagers(zone='all') + display("Instance Group Managers", migs) + its = gce.ex_list_instancetemplates() + display("Instance Templates", its) + hcs = gce.ex_list_healthchecks() + display("Health Checks", hcs) + # == Clean up any old demo resources == display('Cleaning up any "%s" resources' % DEMO_BASE_NAME) + clean_up(gce, DEMO_BASE_NAME, None, + gfrs + targetproxies + urlmaps + bes + hcs + migs + its) + + # == Pause to let cleanup occur and repopulate volume and node lists == + if len(migs): + time.sleep(10) + all_volumes = gce.list_volumes(ex_zone='all') + all_nodes = gce.list_nodes(ex_zone='all') + clean_up(gce, DEMO_BASE_NAME, all_nodes, all_addresses + all_volumes + firewalls + networks + snapshots) volumes = gce.list_volumes() @@ -260,6 +324,8 @@ def clean_up(gce, base_name, node_list=None, resource_list=None): if resrc.name.startswith(base_name): try: resrc.destroy() + class_name = resrc.__class__.__name__ + display(' Deleted %s (%s)' % (resrc.name, class_name)) except ResourceNotFoundError: display(' Not found: %s (%s)' % (resrc.name, resrc.__class__.__name__)) @@ -380,8 +446,7 @@ def main_compute(): name = '%s-subnet-node' % DEMO_BASE_NAME node_1 = gce.create_node(name, 'g1-small', 'debian-8', ex_disk_auto_delete=True, - ex_network=network_custom, - ex_subnetwork=subnet) + ex_network=network_custom, ex_subnetwork=subnet) display(' Node %s created' % name) # == Destroy instance in custom subnetwork == @@ -424,8 +489,7 @@ def main_compute(): }, "boot": True, "autoDelete": True - }, - { + }, { "type": "SCRATCH", "deviceName": '%s-gstruct-lssd' % DEMO_BASE_NAME, "initializeParams": { @@ -452,7 +516,7 @@ def main_compute(): display('Stopping node, setting custom size, starting node:') name = '%s-np-node' % DEMO_BASE_NAME gce.ex_stop_node(node_1) - gce.ex_set_machine_type(node_1, 'custom-2-4096') # 2 vCPU, 4GB RAM + gce.ex_set_machine_type(node_1, 'custom-2-4096') # 2 vCPU, 4GB RAM gce.ex_start_node(node_1) node_1 = gce.ex_get_node(name) display(' %s: state=%s, size=%s' % (name, node_1.extra['status'], @@ -471,8 +535,7 @@ def main_compute(): if CLEANUP: # == Detach the disk == if gce.detach_volume(volume, ex_node=node_1): - display(' Detached %s from %s' % (volume.name, - node_1.name)) + display(' Detached %s from %s' % (volume.name, node_1.name)) # == Create Snapshot == display('Creating a snapshot from existing disk:') @@ -499,8 +562,7 @@ def main_compute(): display(' Created %s from snapshot' % volume.name) # Create Node with Disk node_2 = gce.create_node(name, size, image, ex_tags=['libcloud'], - ex_boot_disk=volume, - ex_disk_auto_delete=False) + ex_boot_disk=volume, ex_disk_auto_delete=False) display(' Node %s created with attached disk %s' % (node_2.name, volume.name)) @@ -525,10 +587,9 @@ def main_compute(): number = MAX_NODES - 2 if number > 0: display('Creating Multiple Nodes (%s):' % number) - multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image, - number, - ex_tags=['libcloud'], - ex_disk_auto_delete=True) + multi_nodes = gce.ex_create_multiple_nodes( + base_name, size, image, number, ex_tags=['libcloud'], + ex_disk_auto_delete=True) for node in multi_nodes: display(' Node %s created' % node.name) @@ -542,8 +603,7 @@ def main_compute(): # == Create a Firewall == display('Creating a Firewall:') name = '%s-firewall' % DEMO_BASE_NAME - allowed = [{'IPProtocol': 'tcp', - 'ports': ['3141']}] + allowed = [{'IPProtocol': 'tcp', 'ports': ['3141']}] firewall_1 = gce.ex_create_firewall(name, allowed, network=network_1, source_tags=['libcloud']) display(' Firewall %s created' % firewall_1.name) @@ -629,20 +689,16 @@ def main_load_balancer(): size = gce.ex_get_size('n1-standard-1') number = 3 display('Creating %d nodes' % number) - metadata = {'items': [{'key': 'startup-script', - 'value': startup_script}]} - lb_nodes = gce.ex_create_multiple_nodes(base_name, size, image, - number, ex_tags=[tag], - ex_metadata=metadata, - ex_disk_auto_delete=True, - ignore_errors=False) + metadata = {'items': [{'key': 'startup-script', 'value': startup_script}]} + lb_nodes = gce.ex_create_multiple_nodes( + base_name, size, image, number, ex_tags=[tag], ex_metadata=metadata, + ex_disk_auto_delete=True, ignore_errors=False) display('Created Nodes', lb_nodes) # == Create a Firewall for instances == display('Creating a Firewall') name = '%s-firewall' % DEMO_BASE_NAME - allowed = [{'IPProtocol': 'tcp', - 'ports': ['80']}] + allowed = [{'IPProtocol': 'tcp', 'ports': ['80']}] firewall = gce.ex_create_firewall(name, allowed, target_tags=[tag]) display(' Firewall %s created' % firewall.name) @@ -652,10 +708,9 @@ def main_load_balancer(): # These are all the default values, but listed here as an example. To # create a healthcheck with the defaults, only name is required. - hc = gcelb.ex_create_healthcheck(name, host=None, path='/', port='80', - interval=5, timeout=5, - unhealthy_threshold=2, - healthy_threshold=2) + hc = gcelb.ex_create_healthcheck( + name, host=None, path='/', port='80', interval=5, timeout=5, + unhealthy_threshold=2, healthy_threshold=2) display('Healthcheck %s created' % hc.name) # == Create Load Balancer == @@ -739,6 +794,114 @@ def main_load_balancer(): display('Total runtime: %s' % str(end_time - start_time)) +# ==== BACKEND SERVICE LOAD BALANCER CODE STARTS HERE ==== +def main_backend_service(): + start_time = datetime.datetime.now() + display('Backend Service w/Global Forwarding Rule demo/test start time: %s' + % str(start_time)) + gce = get_gce_driver() + # Get project info and print name + project = gce.ex_get_project() + display('Project: %s' % project.name) + + # Based on the instructions at: + # https://cloud.google.com/compute/docs/load-balancing/http/#overview + + zone_central = DATACENTER + zone_east = BACKUP_DATACENTER + it_name = '%s-instancetemplate' % DEMO_BASE_NAME + mig_name = '%s-mig' % DEMO_BASE_NAME + hc_name = '%s-healthcheck' % DEMO_BASE_NAME + bes_name = '%s-bes' % DEMO_BASE_NAME + urlmap_name = '%s-urlmap' % DEMO_BASE_NAME + targethttpproxy_name = '%s-httptargetproxy' % DEMO_BASE_NAME + address_name = '%s-address' % DEMO_BASE_NAME + gfr_name = '%s-gfr' % DEMO_BASE_NAME + firewall_name = '%s-firewall' % DEMO_BASE_NAME + + startup_script = ('apt-get -y update && ' + 'apt-get -y install apache2 && ' + 'echo "$(hostname)" > /var/www/html/index.html') + tag = '%s-mig-www' % DEMO_BASE_NAME + metadata = {'items': [{'key': 'startup-script', 'value': startup_script}]} + + mig_central = None + mig_east = None + bes = None + urlmap = None + tp = None + address = None + gfr = None + firewall = None + + display('Create a BackendService') + # == Create an Instance Template == + it = gce.ex_create_instancetemplate(it_name, size='n1-standard-1', + image='debian-8', network='default', + metadata=metadata, tags=[tag]) + display(' InstanceTemplate "%s" created' % it.name) + + # == Create a MIG == + mig_central = create_mig(gce, mig_name, zone_central, it, 'central') + mig_east = create_mig(gce, mig_name, zone_east, it, 'east') + + # == Create a Health Check == + hc = gce.ex_create_healthcheck(hc_name, host=None, path='/', port='80', + interval=30, timeout=10, + unhealthy_threshold=10, healthy_threshold=1) + display(' Healthcheck %s created' % hc.name) + + # == Create a Backend Service == + be_central = gce.ex_create_backend( + instance_group=mig_central.instance_group) + be_east = gce.ex_create_backend(instance_group=mig_east.instance_group) + bes = gce.ex_create_backendservice( + bes_name, [hc], backends=[be_central, be_east], port_name='%s-http' % + DEMO_BASE_NAME, protocol='HTTP', description='%s bes desc' % + DEMO_BASE_NAME, timeout_sec=60, enable_cdn=False) + display(' Backend Service "%s" created' % bes.name) + + # == Create a URLMap == + urlmap = gce.ex_create_urlmap(urlmap_name, default_service=bes) + display(' URLMap "%s" created' % urlmap.name) + + # == Create a Target (HTTP) Proxy == + tp = gce.ex_create_targethttpproxy(targethttpproxy_name, urlmap) + display(' TargetProxy "%s" created' % tp.name) + + # == Create a Static Address == + address = gce.ex_create_address(address_name, region='global') + display(' Address "%s" created with IP "%s"' % (address.name, + address.address)) + # == Create a Global Forwarding Rule == + gfr = gce.ex_create_forwarding_rule( + gfr_name, target=tp, address=address, port_range='80', + description='%s libcloud forwarding rule http test' % DEMO_BASE_NAME, + global_rule=True) + display(' Global Forwarding Rule "%s" created' % (gfr.name)) + + # == Create a Firewall for instances == + allowed = [{'IPProtocol': 'tcp', 'ports': ['80']}] + firewall = gce.ex_create_firewall(firewall_name, allowed, + target_tags=[tag]) + display(' Firewall %s created' % firewall.name) + + # TODO(supertom): launch instances to demostrate that it works + # take backends out of service. Adding in this functionality + # will also add 10-15 minutes to the demo. + # display("Sleeping for 10 minutes, starting at %s" % + # str(datetime.datetime.now())) + # time.sleep(600) + + if CLEANUP: + display('Cleaning up %s resources created' % DEMO_BASE_NAME) + clean_up(gce, DEMO_BASE_NAME, None, + resource_list=[firewall, gfr, address, tp, urlmap, bes, hc, + mig_central, mig_east, it]) + end_time = datetime.datetime.now() + display('Total runtime: %s' % str(end_time - start_time)) + + # ==== GOOGLE DNS CODE STARTS HERE ==== def main_dns(): start_time = datetime.datetime.now() @@ -768,17 +931,19 @@ def main_dns(): end_time = datetime.datetime.now() display('Total runtime: %s' % str(end_time - start_time)) + if __name__ == '__main__': parser = argparse.ArgumentParser( description='Google Cloud Platform Demo / Live Test Script') - parser.add_argument("--compute", - help="perform compute demo / live tests", + parser.add_argument("--compute", help="perform compute demo / live tests", dest="compute", action="store_true") parser.add_argument("--load-balancer", help="perform load-balancer demo / live tests", dest="lb", action="store_true") - parser.add_argument("--dns", - help="perform DNS demo / live tests", + parser.add_argument("--backend-service", + help="perform backend-service demo / live tests", + dest="bes", action="store_true") + parser.add_argument("--dns", help="perform DNS demo / live tests", dest="dns", action="store_true") parser.add_argument("--cleanup-only", help="perform clean-up (skips all tests)", @@ -794,3 +959,5 @@ def main_dns(): main_load_balancer() if cl_args.dns: main_dns() + if cl_args.bes: + main_backend_service() diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py index 6b004916f8..7eaffd3515 100644 --- a/libcloud/compute/drivers/gce.py +++ b/libcloud/compute/drivers/gce.py @@ -318,6 +318,77 @@ def __repr__(self): (hasattr(self.region, "name") and self.region.name or self.region)) +class GCEBackend(UuidMixin): + """A GCE Backend. Only used for creating Backend Services.""" + + def __init__(self, instance_group, balancing_mode='UTILIZATION', + max_utilization=None, max_rate=None, + max_rate_per_instance=None, capacity_scaler=1, + description=None): + + if isinstance(instance_group, GCEInstanceGroup): + self.instance_group = instance_group + elif isinstance(instance_group, GCEInstanceGroupManager): + self.instance_group = instance_group.instance_group + else: + raise ValueError('instance_group must be of type GCEInstanceGroup' + 'or of type GCEInstanceGroupManager') + + self.instance_group = instance_group + self.balancing_mode = balancing_mode + self.max_utilization = max_utilization + self.max_rate = max_rate + self.max_rate_per_instance = max_rate_per_instance + self.capacity_scaler = capacity_scaler + + # 'id' and 'name' aren't actually used or provided by the GCE API. + # We create them for convenience. + self.id = self._gen_id() + self.name = self.id + + self.description = description or self.name + UuidMixin.__init__(self) + + def _gen_id(self): + """ + Use the Instance Group information to fill in name and id fields. + + :return: id in the format of: + ZONE/instanceGroups/INSTANCEGROUPNAME + Ex: us-east1-c/instanceGroups/my-instance-group + :rtype: ``str`` + """ + zone_name = self.instance_group.zone.name + return "%s/instanceGroups/%s" % (zone_name, self.instance_group.name) + + def to_backend_dict(self): + """ + Returns dict formatted for inclusion in Backend Service Request. + + :return: dict formatted as a list entry for Backend Service 'backend'. + :rtype: ``dict`` + """ + d = {} + d['group'] = self.instance_group.extra['selfLink'] + + if self.balancing_mode: + d['balancingMode'] = self.balancing_mode + if self.max_utilization: + d['maxUtilization'] = self.max_utilization + if self.max_rate: + d['maxRate'] = self.max_rate + if self.max_rate_per_instance: + d['maxRatePerInstance'] = self.max_rate_per_instance + if self.capacity_scaler: + d['capacityScaler'] = self.capacity_scaler + + return d + + def __repr__(self): + return '' % ( + self.id, self.balancing_mode) + + class GCEBackendService(UuidMixin): """A GCE Backend Service.""" @@ -525,6 +596,67 @@ def deprecate(self, replacement, state, deprecated=None, obsolete=None, deprecated, obsolete, deleted) +class GCESslCertificate(UuidMixin): + """ GCESslCertificate represents the SslCertificate resource. """ + + def __init__(self, id, name, certificate, driver, extra, private_key=None, + description=None): + """ + :param name: Name of the resource. Provided by the client when the + resource is created. The name must be 1-63 characters + long, and comply with RFC1035. Specifically, the name + must be 1-63 characters long and match the regular + expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all + following characters must be a dash, lowercase letter, + or digit, except the last character, which cannot be a + dash. + :type name: ``str`` + + :param certificate: A local certificate file. The certificate must + be in PEM format. The certificate chain must be + no greater than 5 certs long. The chain must + include at least one intermediate cert. + :type certificate: ``str`` + + :param private_key: A write-only private key in PEM format. Only + insert RPCs will include this field. + :type private_key: ``str`` + + :keyword description: An optional description of this resource. + Provide this property when you create the + resource. + :type description: ``str`` + + :keyword driver: An initialized :class: `GCENodeDriver` + :type driver: :class:`:class: `GCENodeDriver`` + + :keyword extra: A dictionary of extra information. + :type extra: ``:class: ``dict```` + + """ + + self.name = name + self.certificate = certificate + self.private_key = private_key + self.description = description + self.driver = driver + self.extra = extra + UuidMixin.__init__(self) + + def __repr__(self): + return '' % (self.name) + + def destroy(self): + """ + Destroy this SslCertificate. + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_destroy_sslcertificate(sslcertificate=self) + + class GCESubnetwork(UuidMixin): """A GCE Subnetwork object class.""" @@ -710,8 +842,8 @@ class GCESnapshot(VolumeSnapshot): def __init__(self, id, name, size, status, driver, extra=None, created=None): self.status = status - super(GCESnapshot, self).__init__(id, driver, size, extra, - created, name=name) + super(GCESnapshot, self).__init__(id, driver, size, extra, created, + name=name) class GCETargetHttpProxy(UuidMixin): @@ -736,6 +868,109 @@ def destroy(self): return self.driver.ex_destroy_targethttpproxy(targethttpproxy=self) +class GCETargetHttpsProxy(UuidMixin): + """ GCETargetHttpsProxy represents the TargetHttpsProxy resource. """ + + def __init__(self, id, name, description=None, sslcertificates=None, + urlmap=None, driver=None, extra=None): + """ + :param name: Name of the resource. Provided by the client when the + resource is created. The name must be 1-63 characters + long, and comply with RFC1035. Specifically, the name + must be 1-63 characters long and match the regular + expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all + following characters must be a dash, lowercase letter, + or digit, except the last character, which cannot be a + dash. + :type name: ``str`` + + :param description: An optional description of this resource. + Provide this property when you create the + resource. + :type description: ``str`` + + :param sslcertificates: URLs to SslCertificate resources that are + used to authenticate connections between + users and the load balancer. Currently, + exactly one SSL certificate must be + specified. + :type sslcertificates: ``list`` of :class:`GCESslcertificates` + + :param urlmap: A fully-qualified or valid partial URL to the + UrlMap resource that defines the mapping from URL + to the BackendService. For example, the following + are all valid URLs for specifying a URL map: - ht + tps://www.googleapis.compute/v1/projects/project/gl + obal/urlMaps/url-map - + projects/project/global/urlMaps/url-map - + global/urlMaps/url-map + :type urlmap: :class:`GCEUrlMap` + + :keyword driver: An initialized :class: `GCENodeDriver` + :type driver: :class:`:class: `GCENodeDriver`` + + :keyword extra: A dictionary of extra information. + :type extra: ``:class: ``dict```` + + """ + + self.name = name + self.description = description + self.sslcertificates = sslcertificates + self.urlmap = urlmap + self.driver = driver + self.extra = extra + UuidMixin.__init__(self) + + def __repr__(self): + return '' % (self.name) + + def set_sslcertificates(self, sslcertificates): + """ + Set the SSL Certificates for this TargetHTTPSProxy + + :param sslcertificates: SSL Certificates to set. + :type sslcertificates: ``list`` of :class:`GCESslCertificate` + + :return: True if successful + :rtype: ``bool`` + """ + return self.driver.ex_targethttpsproxy_set_sslcertificates( + targethttpsproxy=self, sslcertificates=sslcertificates) + + def set_urlmap(self, urlmap): + """ + Changes the URL map for TargetHttpsProxy. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param targethttpsproxy: Name of the TargetHttpsProxy resource + whose URL map is to be set. + :type targethttpsproxy: ``str`` + + :param urlmap: UrlMap to set. + :type urlmap: :class:`GCEUrlMap` + + :return: True + :rtype: ``bool`` + """ + + return self.driver.ex_targethttpsproxy_set_urlmap( + targethttpsproxy=self, urlmap=urlmap) + + def destroy(self): + """ + Destroy this TargetHttpsProxy. + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_destroy_targethttpsproxy(targethttpsproxy=self) + + class GCETargetInstance(UuidMixin): def __init__(self, id, name, zone, node, driver, extra=None): self.id = str(id) @@ -803,12 +1038,21 @@ def __repr__(self): self.id, self.name, self.extra['properties'].get('machineType', 'UNKNOWN')) + def destroy(self): + """ + Destroy this InstanceTemplate. + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_destroy_instancetemplate(instancetemplate=self) + class GCEInstanceGroup(UuidMixin): """ GCEInstanceGroup represents the InstanceGroup resource. """ - def __init__(self, id, name, zone, driver, extra=None, description=None, - network=None, subnetwork=None, named_ports=None): + def __init__(self, id, name, zone, driver, extra=None, network=None, + subnetwork=None, named_ports=None): """ :param name: Required. The name of the instance group. The name must be 1-63 characters long, and comply with RFC1035. @@ -818,11 +1062,6 @@ def __init__(self, id, name, zone, driver, extra=None, description=None, located. :type zone: :class:`GCEZone` - :param description: An optional description of this resource. - Provide this property when you create the - resource. - :type description: ``str`` - :param network: The URL of the network to which all instances in the instance group belong. :type network: :class:`GCENetwork` @@ -845,7 +1084,6 @@ def __init__(self, id, name, zone, driver, extra=None, description=None, self.name = name self.zone = zone - self.description = description self.network = network self.subnetwork = subnetwork self.named_ports = named_ports @@ -855,7 +1093,7 @@ def __init__(self, id, name, zone, driver, extra=None, description=None, def __repr__(self): return '' % (self.name, - self.zone) + self.zone.name) def destroy(self): """ @@ -866,6 +1104,91 @@ def destroy(self): """ return self.driver.ex_destroy_instancegroup(instancegroup=self) + def add_instances(self, node_list): + """ + Adds a list of instances to the specified instance group. All of the + instances in the instance group must be in the same + network/subnetwork. Read Adding instances for more information. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancegroup: The Instance Group where you are + adding instances. + :type instancegroup: :class:``GCEInstanceGroup`` + + :param node_list: List of nodes to add. + :type node_list: ``list`` of :class:`Node` or ``list`` of + :class:`GCENode` + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_instancegroup_add_instances(instancegroup=self, + node_list=node_list) + + def list_instances(self): + """ + Lists the instances in the specified instance group. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + * https://www.googleapis.com/auth/compute.readonly + + :return: List of :class:`GCENode` objects. + :rtype: ``list`` of :class:`GCENode` objects. + """ + return self.driver.ex_instancegroup_list_instances(instancegroup=self) + + def remove_instances(self, node_list): + """ + Removes one or more instances from the specified instance group, + but does not delete those instances. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancegroup: The Instance Group where you are + removng instances. + :type instancegroup: :class:``GCEInstanceGroup`` + + :param node_list: List of nodes to add. + :type node_list: ``list`` of :class:`Node` or ``list`` of + :class:`GCENode` + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_instancegroup_remove_instances( + instancegroup=self, node_list=node_list) + + def set_named_ports(self, named_ports): + """ + Sets the named ports for the specified instance group. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param named_ports: Assigns a name to a port number. For example: + {name: "http", port: 80} This allows the + system to reference ports by the assigned name + instead of a port number. Named ports can also + contain multiple ports. For example: [{name: + "http", port: 80},{name: "http", port: 8080}] + Named ports apply to all instances in this + instance group. + :type named_ports: ``list`` of {'name': ``str``, 'port`: ``int``} + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_instancegroup_set_named_ports( + instancegroup=self, named_ports=named_ports) + class GCEInstanceGroupManager(UuidMixin): """ @@ -982,6 +1305,30 @@ def resize(self, size): return self.driver.ex_instancegroupmanager_resize(manager=self, size=size) + def set_named_ports(self, named_ports): + """ + Sets the named ports for the instance group controlled by this manager. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param named_ports: Assigns a name to a port number. For example: + {name: "http", port: 80} This allows the + system to reference ports by the assigned name + instead of a port number. Named ports can also + contain multiple ports. For example: [{name: + "http", port: 80},{name: "http", port: 8080}] + Named ports apply to all instances in this + instance group. + :type named_ports: ``list`` of {'name': ``str``, 'port`: ``int``} + + :return: Return True if successful. + :rtype: ``bool`` + """ + return self.driver.ex_instancegroup_set_named_ports( + instancegroup=self.instance_group, named_ports=named_ports) + def __repr__(self): return '' % ( self.name, self.zone.name, self.size) @@ -1297,6 +1644,7 @@ class GCENodeDriver(NodeDriver): "windows-cloud": ["windows"], } + BACKEND_SERVICE_PROTOCOLS = ['HTTP', 'HTTPS', 'HTTP2', 'TCP', 'SSL'] GUEST_OS_FEATURES = ['VIRTIO_SCSI_MULTIQUEUE', 'WINDOWS'] def __init__(self, user_id, key=None, datacenter=None, project=None, @@ -1858,6 +2206,26 @@ def ex_list_routes(self): list_routes = [self._to_route(n) for n in response.get('items', [])] return list_routes + def ex_list_sslcertificates(self): + """ + Retrieves the list of SslCertificate resources available to the + specified project. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + * https://www.googleapis.com/auth/compute.readonly + + :return: A list of SSLCertificate objects. + :rtype: ``list`` of :class:`GCESslCertificate` + """ + list_data = [] + request = '/global/sslCertificates' + response = self.connection.request(request, method='GET').object + list_data = [self._to_sslcertificate(a) + for a in response.get('items', [])] + return list_data + def ex_list_subnetworks(self, region=None): """ Return the list of subnetworks. @@ -2029,6 +2397,18 @@ def ex_list_targethttpproxies(self): response = self.connection.request(request, method='GET').object return [self._to_targethttpproxy(u) for u in response.get('items', [])] + def ex_list_targethttpsproxies(self): + """ + Return the list of target HTTPs proxies. + + :return: A list of target https proxy objects + :rtype: ``list`` of :class:`GCETargetHttpsProxy` + """ + request = '/global/targetHttpsProxies' + response = self.connection.request(request, method='GET').object + return [self._to_targethttpsproxy(x) + for x in response.get('items', [])] + def ex_list_targetinstances(self, zone=None): """ Return the list of target instances. @@ -2337,28 +2717,160 @@ def ex_create_autoscaler(self, name, zone, instance_group, policy, data=autoscaler_data) return self.ex_get_autoscaler(name, zone) - def ex_create_backendservice(self, name, healthchecks): + def ex_create_backend(self, instance_group, balancing_mode='UTILIZATION', + max_utilization=None, max_rate=None, + max_rate_per_instance=None, capacity_scaler=1, + description=None): + """ + Helper Object to create a backend. + + :param instance_group: The Instance Group for this Backend. + :type instance_group: :class: `GCEInstanceGroup` + + :param balancing_mode: Specifies the balancing mode for this backend. + For global HTTP(S) load balancing, the valid + values are UTILIZATION (default) and RATE. + For global SSL load balancing, the valid + values are UTILIZATION (default) and + CONNECTION. + :type balancing_mode: ``str`` + + :param max_utilization: Used when balancingMode is UTILIZATION. + This ratio defines the CPU utilization + target for the group. The default is 0.8. + Valid range is [0.0, 1.0]. + :type max_utilization: ``float`` + + :param max_rate: The max requests per second (RPS) of the group. + Can be used with either RATE or UTILIZATION balancing + modes, but required if RATE mode. For RATE mode, + either maxRate or maxRatePerInstance must be set. + :type max_rate: ``int`` + + :param max_rate_per_instance: The max requests per second (RPS) that + a single backend instance can handle. + This is used to calculate the capacity + of the group. Can be used in either + balancing mode. For RATE mode, either + maxRate or maxRatePerInstance must be + set. + :type max_rate_per_instance: ``float`` + + :param capacity_scaler: A multiplier applied to the group's maximum + servicing capacity (based on UTILIZATION, + RATE, or CONNECTION). Default value is 1, + which means the group will serve up to 100% + of its configured capacity (depending on + balancingMode). A setting of 0 means the + group is completely drained, offering 0% + of its available capacity. Valid range is + [0.0,1.0]. + :type capacity_scaler: ``float`` + + :param description: An optional description of this resource. + Provide this property when you create the + resource. + :type description: ``str`` + + :return: A GCEBackend object. + :rtype: :class: `GCEBackend` + """ + + return GCEBackend( + instance_group=instance_group, balancing_mode=balancing_mode, + max_utilization=max_utilization, max_rate=max_rate, + max_rate_per_instance=max_rate_per_instance, + capacity_scaler=capacity_scaler, description=description) + + def ex_create_backendservice(self, name, healthchecks, backends=[], + protocol=None, description=None, + timeout_sec=None, enable_cdn=False, port=None, + port_name=None): """ Create a global Backend Service. - :param name: Name of the Backend Service - :type name: ``str`` + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param name: Name of the resource. Provided by the client when the + resource is created. The name must be 1-63 characters + long, and comply with RFC1035. Specifically, the name + must be 1-63 characters long and match the regular + expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all + following characters must be a dash, lowercase letter, + or digit, except the last character, which cannot be a + dash. + :type name: ``str`` :param 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`) + :keyword backends: The list of backends that serve this + BackendService. + :type backends: ``list`` of :class `GCEBackend` or list of ``dict`` + + :keyword timeout_sec: How many seconds to wait for the backend + before considering it a failed request. + Default is 30 seconds. + :type timeout_sec: ``integer`` + + :keyword enable_cdn: If true, enable Cloud CDN for this + BackendService. When the load balancing + scheme is INTERNAL, this field is not used. + :type enable_cdn: ``bool`` + + :keyword port: Deprecated in favor of port_name. The TCP port to + connect on the backend. The default value is 80. + This cannot be used for internal load balancing. + :type port: ``integer`` + + :keyword port_name: Name of backend port. The same name should appear + in the instance groups referenced by this service. + :type port_name: ``str`` + + :keyword protocol: The protocol this Backend Service uses to + communicate with backends. + Possible values are HTTP, HTTPS, HTTP2, TCP + and SSL. + :type protocol: ``str`` + :return: A Backend Service object. :rtype: :class:`GCEBackendService` """ - backendservice_data = {'name': name, 'healthChecks': []} + backendservice_data = {'name': name, + 'healthChecks': [], + 'backends': [], + 'enableCDN': enable_cdn} for hc in healthchecks: if not hasattr(hc, 'extra'): hc = self.ex_get_healthcheck(name=hc) backendservice_data['healthChecks'].append(hc.extra['selfLink']) + for be in backends: + if isinstance(be, GCEBackend): + backendservice_data['backends'].append(be.to_backend_dict()) + else: + backendservice_data['backends'].append(be) + if port: + backendservice_data['port'] = port + if port_name: + backendservice_data['portName'] = port_name + if timeout_sec: + backendservice_data['timeoutSec'] = timeout_sec + if protocol: + if protocol in self.BACKEND_SERVICE_PROTOCOLS: + backendservice_data['protocol'] = protocol + else: + raise ValueError('Protocol must be one of %s' % + ','.join(self.BACKEND_SERVICE_PROTOCOLS)) + if description: + backendservice_data['description'] = description + request = '/global/backendServices' self.connection.async_request(request, method='POST', data=backendservice_data) @@ -2715,6 +3227,72 @@ def ex_copy_image(self, name, url, description=None, family=None, self.connection.async_request(request, method='POST', data=image_data) return self.ex_get_image(name) + def ex_create_instancegroup(self, name, zone, description=None, + network=None, subnetwork=None, + named_ports=None): + """ + Creates an instance group in the specified project using the + parameters that are included in the request. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param name: Required. The name of the instance group. The name + must be 1-63 characters long, and comply with RFC1035. + :type name: ``str`` + + :param zone: The URL of the zone where the instance group is + located. + :type zone: :class:`GCEZone` + + :keyword description: An optional description of this resource. + Provide this property when you create the + resource. + :type description: ``str`` + + :keyword network: The URL of the network to which all instances in + the instance group belong. + :type network: :class:`GCENetwork` + + :keyword subnetwork: The URL of the subnetwork to which all + instances in the instance group belong. + :type subnetwork: :class:`GCESubnetwork` + + :keyword named_ports: Assigns a name to a port number. For example: + {name: "http", port: 80} This allows the + system to reference ports by the assigned + name instead of a port number. Named ports + can also contain multiple ports. For example: + [{name: "http", port: 80},{name: "http", + port: 8080}] Named ports apply to all + instances in this instance group. + :type named_ports: ``list`` of {'name': ``str``, 'port`: ``int``} + + :return: `GCEInstanceGroup` object. + :rtype: :class:`GCEInstanceGroup` + """ + zone = zone or self.zone + if not hasattr(zone, 'name'): + zone = self.ex_get_zone(zone) + request = "/zones/%s/instanceGroups" % (zone.name) + request_data = {} + request_data['name'] = name + request_data['zone'] = zone.extra['selfLink'] + if description: + request_data['description'] = description + if network: + request_data['network'] = network.extra['selfLink'] + if subnetwork: + request_data['subnetwork'] = subnetwork.extra['selfLink'] + if named_ports: + request_data['namedPorts'] = named_ports + + self.connection.async_request(request, method='POST', + data=request_data) + + return self.ex_get_instancegroup(name, zone) + def ex_create_instancegroupmanager(self, name, zone, template, size, base_instance_name=None, description=None): @@ -2830,7 +3408,61 @@ def ex_create_route(self, name, dest_range, priority=500, return self.ex_get_route(name) - def ex_create_subnetwork(self, name, cidr=None, network=None, region=None, + def ex_create_sslcertificate(self, name, certificate=None, + private_key=None, description=None): + """ + Creates a SslCertificate resource in the specified project using the + data included in the request. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param name: Name of the resource. Provided by the client when the + resource is created. The name must be 1-63 characters + long, and comply with RFC1035. Specifically, the name + must be 1-63 characters long and match the regular + expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all + following characters must be a dash, lowercase letter, + or digit, except the last character, which cannot be a + dash. + :type name: ``str`` + + :param certificate: A string containing local certificate file in + PEM format. The certificate chain + must be no greater than 5 certs long. The + chain must include at least one intermediate + cert. + :type certificate: ``str`` + + :param private_key: A string containing a write-only private key + in PEM format. Only insert RPCs will include + this field. + :type private_key: ``str`` + + :keyword description: An optional description of this resource. + Provide this property when you create the + resource. + :type description: ``str`` + + :return: `GCESslCertificate` object. + :rtype: :class:`GCESslCertificate` + """ + + request = "/global/sslCertificates" % () + request_data = {} + request_data['name'] = name + request_data['certificate'] = certificate + request_data['privateKey'] = private_key + request_data['description'] = description + + self.connection.async_request(request, method='POST', + data=request_data) + + return self.ex_get_sslcertificate(name) + + def ex_create_subnetwork(self, name, cidr=None, network=None, region=None, description=None): """ Create a subnetwork. @@ -3133,6 +3765,679 @@ def create_node( self.connection.async_request(request, method='POST', data=node_data) return self.ex_get_node(name, location.name) + def ex_create_instancetemplate( + self, name, size, source=None, image=None, disk_type='pd-standard', + disk_auto_delete=True, network='default', subnetwork=None, + can_ip_forward=None, external_ip='ephemeral', + service_accounts=None, on_host_maintenance=None, + automatic_restart=None, preemptible=None, tags=None, metadata=None, + description=None, disks_gce_struct=None, nic_gce_struct=None): + """ + Creates an instance template in the specified project using the data + that is included in the request. If you are creating a new template to + update an existing instance group, your new instance template must + use the same network or, if applicable, the same subnetwork as the + original template. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :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 network: The network to associate with the template. + :type network: ``str`` or :class:`GCENetwork` + + :keyword subnetwork: The subnetwork to associate with the node. + :type subnetwork: ``str`` or :class:`GCESubnetwork` + + :keyword tags: A list of tags to associate with the node. + :type tags: ``list`` of ``str`` or ``None`` + + :keyword metadata: Metadata dictionary for instance. + :type metadata: ``dict`` or ``None`` + + :keyword external_ip: The external IP address to use. If 'ephemeral' + (default), a new non-static address will be + used. If 'None', then no external address will + be used. To use an existing static IP address, + a GCEAddress object should be passed in. + :type external_ip: :class:`GCEAddress` or ``str`` or ``None`` + + :keyword disk_type: Specify a pd-standard (default) disk or pd-ssd + for an SSD disk. + :type disk_type: ``str`` or :class:`GCEDiskType` + + :keyword disk_auto_delete: Indicate that the boot disk should be + deleted when the Node is deleted. Set to + True by default. + :type disk_auto_delete: ``bool`` + + :keyword service_accounts: Specify a list of serviceAccounts when + creating the instance. The format is a + list of dictionaries containing email + and list of scopes, e.g. + [{'email':'default', + 'scopes':['compute', ...]}, ...] + Scopes can either be full URLs or short + names. If not provided, use the + 'default' service account email and a + scope of 'devstorage.read_only'. Also + accepts the aliases defined in + 'gcloud compute'. + :type service_accounts: ``list`` + + :keyword description: The description of the node (instance). + :type description: ``str`` or ``None`` + + :keyword can_ip_forward: Set to ``True`` to allow this node to + send/receive non-matching src/dst packets. + :type can_ip_forward: ``bool`` or ``None`` + + :keyword disks_gce_struct: Support for passing in the GCE-specific + formatted disks[] structure. No attempt + is made to ensure proper formatting of + the disks[] structure. Using this + structure obviates the need of using + other disk params like 'ex_boot_disk', + etc. See the GCE docs for specific + details. + :type disks_gce_struct: ``list`` or ``None`` + + :keyword nic_gce_struct: Support passing in the GCE-specific + formatted networkInterfaces[] structure. + No attempt is made to ensure proper + formatting of the networkInterfaces[] + data. Using this structure obviates the + need of using 'external_ip' and + 'ex_network'. See the GCE docs for + details. + :type nic_gce_struct: ``list`` or ``None`` + + :keyword on_host_maintenance: Defines whether node should be + terminated or migrated when host + machine goes down. Acceptable values + are: 'MIGRATE' or 'TERMINATE' (If + not supplied, value will be reset to + GCE default value for the instance + type.) + :type ex_on_host_maintenance: ``str`` or ``None`` + + :keyword automatic_restart: Defines whether the instance should be + automatically restarted when it is + terminated by Compute Engine. (If not + supplied, value will be set to the GCE + default value for the instance type.) + :type automatic_restart: ``bool`` or ``None`` + + :keyword preemptible: Defines whether the instance is preemptible. + (If not supplied, the instance will not be + preemptible) + :type preemptible: ``bool`` or ``None`` + + :return: An Instance Template object. + :rtype: :class:`GCEInstanceTemplate` + """ + request = "/global/instanceTemplates" + + properties = self._create_instance_properties( + name, node_size=size, source=source, image=image, + disk_type='pd-standard', disk_auto_delete=True, + external_ip=external_ip, network=network, subnetwork=subnetwork, + can_ip_forward=can_ip_forward, service_accounts=service_accounts, + on_host_maintenance=on_host_maintenance, + automatic_restart=automatic_restart, preemptible=preemptible, + tags=tags, metadata=metadata, description=description, + disks_gce_struct=disks_gce_struct, nic_gce_struct=nic_gce_struct, + use_selflinks=False) + + request_data = {'name': name, + 'description': description, + 'properties': properties} + + self.connection.async_request(request, method='POST', + data=request_data) + + return self.ex_get_instancetemplate(name) + + def _create_instance_properties( + self, name, node_size, source=None, image=None, + disk_type='pd-standard', disk_auto_delete=True, network='default', + subnetwork=None, external_ip='ephemeral', can_ip_forward=None, + service_accounts=None, on_host_maintenance=None, + automatic_restart=None, preemptible=None, tags=None, metadata=None, + description=None, disks_gce_struct=None, nic_gce_struct=None, + use_selflinks=True): + """ + Create the GCE instance properties needed for instance templates. + + :param node_size: The machine type to use. + :type node_size: ``str`` or :class:`GCENodeSize` + + :keyword source: A source disk to attach to the instance. Cannot + specify both 'image' and 'source'. + :type source: :class:`StorageVolume` or ``str`` or ``None`` + + :param image: The image to use to create the node. Cannot specify + both 'image' and 'source'. + :type image: ``str`` or :class:`GCENodeImage` or ``None`` + + :keyword disk_type: Specify a pd-standard (default) disk or pd-ssd + for an SSD disk. + :type disk_type: ``str`` or :class:`GCEDiskType` + + :keyword disk_auto_delete: Indicate that the boot disk should be + deleted when the Node is deleted. Set to + True by default. + :type disk_auto_delete: ``bool`` + + :keyword network: The network to associate with the node. + :type network: ``str`` or :class:`GCENetwork` + + :keyword subnetwork: The Subnetwork resource for this instance. If + the network resource is in legacy mode, do not + provide this property. If the network is in auto + subnet mode, providing the subnetwork is + optional. If the network is in custom subnet + mode, then this field should be specified. + :type subnetwork: :class: `GCESubnetwork` or None + + :keyword external_ip: The external IP address to use. If 'ephemeral' + (default), a new non-static address will be + used. If 'None', then no external address will + be used. To use an existing static IP address, + a GCEAddress object should be passed in. + :type external_ip: :class:`GCEAddress` or ``str`` or ``None`` + + :keyword can_ip_forward: Set to ``True`` to allow this node to + send/receive non-matching src/dst packets. + :type can_ip_forward: ``bool`` or ``None`` + + :keyword service_accounts: Specify a list of serviceAccounts when + creating the instance. The format is a + list of dictionaries containing email + and list of scopes, e.g. + [{'email':'default', + 'scopes':['compute', ...]}, ...] + Scopes can either be full URLs or short + names. If not provided, use the + 'default' service account email and a + scope of 'devstorage.read_only'. Also + accepts the aliases defined in + 'gcloud compute'. + :type service_accounts: ``list`` + + :keyword on_host_maintenance: Defines whether node should be + terminated or migrated when host + machine goes down. Acceptable values + are: 'MIGRATE' or 'TERMINATE' (If + not supplied, value will be reset to + GCE default value for the instance + type.) + :type on_host_maintenance: ``str`` or ``None`` + + :keyword automatic_restart: Defines whether the instance should be + automatically restarted when it is + terminated by Compute Engine. (If not + supplied, value will be set to the GCE + default value for the instance type.) + :type automatic_restart: ``bool`` or ``None`` + + :keyword preemptible: Defines whether the instance is preemptible. + (If not supplied, the instance will not be + preemptible) + :type preemptible: ``bool`` or ``None`` + + :keyword tags: A list of tags to associate with the node. + :type tags: ``list`` of ``str`` or ``None`` + + :keyword metadata: Metadata dictionary for instance. + :type metadata: ``dict`` or ``None`` + + :keyword description: The description of the node (instance). + :type description: ``str`` or ``None`` + + :keyword disks_gce_struct: Support for passing in the GCE-specific + formatted disks[] structure. No attempt + is made to ensure proper formatting of + the disks[] structure. Using this + structure obviates the need of using + other disk params like 'boot_disk', + etc. See the GCE docs for specific + details. + :type disks_gce_struct: ``list`` or ``None`` + + :keyword nic_gce_struct: Support passing in the GCE-specific + formatted networkInterfaces[] structure. + No attempt is made to ensure proper + formatting of the networkInterfaces[] + data. Using this structure obviates the + need of using 'external_ip' and + 'network'. See the GCE docs for + details. + :type nic_gce_struct: ``list`` or ``None`` + + :return: A dictionary formatted for use with the GCE API. + :rtype: ``dict`` + """ + instance_properties = {} + + # build disks + if not image and not source and not disks_gce_struct: + raise ValueError("Missing root device or image. Must specify an " + "'image', source, or use the " + "'disks_gce_struct'.") + + if source and disks_gce_struct: + raise ValueError("Cannot specify both 'source' and " + "'disks_gce_struct'. Use one or the other.") + + if disks_gce_struct: + instance_properties['disks'] = disks_gce_struct + else: + disk_name = None + device_name = None + if source: + disk_name = source.name + # TODO(supertom): what about device name? + device_name = source.name + image = None + + instance_properties['disks'] = [self._build_disk_gce_struct( + device_name, source=source, disk_type=disk_type, image=image, + disk_name=disk_name, usage_type='PERSISTENT', + mount_mode='READ_WRITE', auto_delete=disk_auto_delete, + is_boot=True, use_selflinks=use_selflinks)] + + # build network interfaces + if nic_gce_struct is not None: + if hasattr(external_ip, 'address'): + raise ValueError("Cannot specify both a static IP address " + "and 'nic_gce_struct'. Use one or the " + "other.") + if hasattr(network, 'name'): + if network.name == 'default': + # assume this is just the default value from create_node() + # and since the user specified ex_nic_gce_struct, the + # struct should take precedence + network = None + else: + raise ValueError("Cannot specify both 'network' and " + "'nic_gce_struct'. Use one or the " + "other.") + instance_properties['networkInterfaces'] = nic_gce_struct + else: + instance_properties['networkInterfaces'] = [ + self._build_network_gce_struct( + network=network, subnetwork=subnetwork, + external_ip=external_ip, use_selflinks=True) + ] + + # build scheduling + scheduling = self._build_scheduling_gce_struct( + on_host_maintenance, automatic_restart, preemptible) + if scheduling: + instance_properties['scheduling'] = scheduling + + # build service accounts/scopes + instance_properties[ + 'serviceAccounts'] = self._build_service_accounts_gce_list( + service_accounts) + + # include general properties + if description: + instance_properties['description'] = str(description) + if tags: + instance_properties['tags'] = {'items': tags} + if metadata: + instance_properties['metadata'] = self._format_metadata( + fingerprint='na', metadata=metadata) + if can_ip_forward: + instance_properties['canIpForward'] = True + + instance_properties['machineType'] = self._get_selflink_or_name( + obj=node_size, get_selflinks=use_selflinks, objname='size') + + return instance_properties + + def _build_disk_gce_struct( + self, device_name, source=None, disk_type=None, disk_size=None, + image=None, disk_name=None, is_boot=True, mount_mode='READ_WRITE', + usage_type='PERSISTENT', auto_delete=True, use_selflinks=True): + """ + Generates the GCP dict for a disk. + + :param device_name: Specifies a unique device name of your + choice that is reflected into the + /dev/disk/by-id/google-* tree + of a Linux operating system running within the + instance. This name can be used to reference the + device for mounting, resizing, and so on, from + within the instance. Defaults to disk_name. + :type device_name: ``str`` + + :keyword source: The disk to attach to the instance. + :type source: ``str`` of selfLink, :class:`StorageVolume` or None + + :keyword disk_type: Specify a URL or DiskType object. + :type disk_type: ``str`` or :class:`GCEDiskType` or ``None`` + + :keyword image: The image to use to create the disk. + :type image: :class:`GCENodeImage` or ``None`` + + :keyword disk_size: Integer in gigabytes. + :type disk_size: ``int`` + + :param disk_name: Specifies the disk name. If not specified, the + default is to use the device_name. + :type disk_name: ``str`` + + :keyword mount_mode: The mode in which to attach this disk, either + READ_WRITE or READ_ONLY. If not specified, + the default is to attach the disk in READ_WRITE + mode. + :type mount_mode: ``str`` + + :keyword usage_type: Specifies the type of the disk, either SCRATCH + or PERSISTENT. If not specified, the default + is PERSISTENT. + :type usage_type: ``str`` + + :keyword auto_delete: Indicate that the boot disk should be + deleted when the Node is deleted. Set to + True by default. + :type auto_delete: ``bool`` + + :return: Dictionary to be used in disk-portion of + instance API call. + :rtype: ``dict`` + """ + # validation + if source is None and image is None: + raise ValueError( + "Either the 'source' or 'image' argument must be specified.") + + if not isinstance(auto_delete, bool): + raise ValueError("auto_delete field is not a bool.") + + if disk_size is not None and not disk_size.isdigit(): + raise ValueError("disk_size must be a digit, '%s' provided." % + (disk_size)) + + mount_modes = ['READ_WRITE', 'READ_ONLY'] + if mount_mode not in mount_modes: + raise ValueError("mount mode must be one of: %s." % + (','.join(mount_modes))) + usage_types = ['PERSISTENT', 'SCRATCH'] + if usage_type not in usage_types: + raise ValueError("usage type must be one of: %s." % + (','.join(usage_types))) + + disk = {} + if not disk_name: + disk_name = device_name + + if source is not None: + disk['source'] = self._get_selflink_or_name( + obj=source, get_selflinks=use_selflinks, objname='volume') + + else: + # create new disk + # we need the URL of the image, always. + image = self._get_selflink_or_name(obj=image, get_selflinks=True, + objname='image') + disk_type = self._get_selflink_or_name( + obj=disk_type, get_selflinks=use_selflinks, objname='disktype') + + disk['initializeParams'] = { + 'diskName': disk_name, + 'diskType': disk_type, + 'sourceImage': image, + } + if disk_size is not None: + disk['initializeParams']['diskSizeGb'] = disk_size + + # add in basic attributes + disk.update({'boot': is_boot, + 'type': usage_type, + 'mode': mount_mode, + 'deviceName': device_name, + 'autoDelete': auto_delete}) + return disk + + def _get_selflink_or_name(self, obj, get_selflinks=True, objname=None): + """ + Return the selflink or name, given a name or object. + + Will try to fetch the appropriate object if necessary (assumes + we only need one parameter to fetch the object, no introspection + is performed). + + :param obj: object to test. + :type obj: ``str`` or ``object`` + + :param get_selflinks: Inform if we should return selfLinks or just + the name. Default is True. + :param get_selflinks: ``bool`` + + :param objname: string to use in constructing method call + :type objname: ``str`` or None + + :return: URL from extra['selfLink'] or name + :rtype: ``str`` + """ + if get_selflinks: + if not hasattr(obj, 'name'): + if objname: + getobj = getattr(self, 'ex_get_%s' % (objname)) + obj = getobj(obj) + else: + raise ValueError( + "objname must be set if selflinks is True.") + return obj.extra['selfLink'] + else: + if not hasattr(obj, 'name'): + return obj + else: + return obj.name + + def _build_network_gce_struct(self, network, subnetwork=None, + external_ip=None, use_selflinks=True): + """ + Build network interface dict for use in the GCE API. + + Note: Must be wrapped in a list before passing to the GCE API. + + :param network: The network to associate with the node. + :type network: :class:`GCENetwork` + + :keyword subnetwork: The subnetwork to include. + :type subnetwork: :class:`GCESubNetwork` + + :keyword external_ip: The external IP address to use. If 'ephemeral' + (default), a new non-static address will be + used. If 'None', then no external address will + be used. To use an existing static IP address, + a GCEAddress object should be passed in. + :type external_ip: :class:`GCEAddress` + + :return: network interface dict + :rtype: ``dict`` + """ + ni = {} + ni = {'kind': 'compute#instanceNetworkInterface'} + if network is None: + network = 'default' + + ni['network'] = self._get_selflink_or_name( + obj=network, get_selflinks=use_selflinks, objname='network') + + if subnetwork: + ni['subnetwork'] = self._get_selflink_or_name( + obj=subnetwork, get_selflinks=use_selflinks, + objname='subnetwork') + + if external_ip: + access_configs = [{'name': 'External NAT', + 'type': 'ONE_TO_ONE_NAT'}] + if hasattr(external_ip, 'address'): + access_configs[0]['natIP'] = external_ip.address + ni['accessConfigs'] = access_configs + + return ni + + def _build_service_account_gce_struct( + self, service_account, default_email='default', + default_scope='devstorage.read_only'): + """ + Helper to create Service Account dict. Use + _build_service_accounts_gce_list to create a list ready for the + GCE API. + + :param: service_account: dictionarie containing email + and list of scopes, e.g. + [{'email':'default', + 'scopes':['compute', ...]}, ...] + Scopes can either be full URLs or short + names. If not provided, use the + 'default' service account email and a + scope of 'devstorage.read_only'. Also + accepts the aliases defined in + 'gcloud compute'. + :type service_account: ``dict`` or None + + :return: dict usable in GCE API call. + :rtype: ``dict`` + """ + if not isinstance(service_account, dict): + raise ValueError( + "service_account not in the correct format," + "'%s - %s'" % + (str(type(service_account)), str(service_account))) + sa = {} + if 'email' not in service_account: + sa['email'] = default_email + + if 'scopes' not in service_account: + sa['scopes'] = [self.AUTH_URL + default_scope] + else: + ps = [] + for scope in service_account['scopes']: + if scope.startswith(self.AUTH_URL): + ps.append(scope) + elif scope in self.SA_SCOPES_MAP: + ps.append(self.AUTH_URL + self.SA_SCOPES_MAP[scope]) + else: + ps.append(self.AUTH_URL + scope) + sa['scopes'] = ps + + return sa + + def _build_service_accounts_gce_list(self, service_accounts=None, + default_email='default', + default_scope='devstorage.read_only'): + """ + Helper to create service account list for GCE API. + + :keyword service_accounts: Specify a list of serviceAccounts when + creating the instance. The format is a + list of dictionaries containing email + and list of scopes, e.g. + [{'email':'default', + 'scopes':['compute', ...]}, ...] + Scopes can either be full URLs or short + names. If not provided, use the + 'default' service account email and a + scope of 'devstorage.read_only'. Also + accepts the aliases defined in + 'gcloud compute'. + + :type service_accounts: ``list`` of ``dict`` or None + + :return: list of dictionaries usable in the GCE API. + :rtype: ``list`` of ``dict`` + """ + gce_service_accounts = [] + if not service_accounts: + gce_service_accounts = [{ + 'email': default_email, + 'scopes': [self.AUTH_URL + default_scope] + }] + elif not isinstance(service_accounts, list): + raise ValueError("service_accounts field is not a list.") + else: + for sa in service_accounts: + gce_service_accounts.append( + self._build_service_account_gce_struct(service_account=sa)) + + return gce_service_accounts + + def _build_scheduling_gce_struct(self, on_host_maintenance=None, + automatic_restart=None, preemptible=None): + """ + Build the scheduling dict suitable for use with the GCE API. + + :param on_host_maintenance: Defines whether node should be + terminated or migrated when host + machine goes down. Acceptable values + are: 'MIGRATE' or 'TERMINATE' (If + not supplied, value will be reset to + GCE default value for the instance + type.) + :type on_host_maintenance: ``str`` or ``None`` + + :param automatic_restart: Defines whether the instance should be + automatically restarted when it is + terminated by Compute Engine. (If not + supplied, value will be set to the GCE + default value for the instance type.) + :type automatic_restart: ``bool`` or ``None`` + + :param preemptible: Defines whether the instance is preemptible. + (If not supplied, the instance will + not be preemptible) + :type preemptible: ``bool`` or ``None`` + + :return: A dictionary of scheduling options for the GCE API. + :rtype: ``dict`` + """ + scheduling = {} + if preemptible is not None: + if isinstance(preemptible, bool): + scheduling['preemptible'] = preemptible + else: + raise ValueError("boolean expected for preemptible") + if on_host_maintenance is not None: + maint_opts = ['MIGRATE', 'TERMINATE'] + if isinstance(on_host_maintenance, + str) and on_host_maintenance in maint_opts: + if preemptible is True and on_host_maintenance is 'MIGRATE': + raise ValueError(("host maintenance cannot be 'MIGRATE' " + "if instance is preemptible.")) + scheduling['onHostMaintenance'] = on_host_maintenance + else: + raise ValueError("host maintenance must be one of %s" % + (','.join(maint_opts))) + if automatic_restart is not None: + if isinstance(automatic_restart, bool): + if automatic_restart is True and preemptible is True: + raise ValueError( + "instance cannot be restarted if it is preemptible.") + scheduling['automaticRestart'] = automatic_restart + + else: + raise ValueError("boolean expected for automatic") + + return scheduling + def ex_create_multiple_nodes( self, base_name, size, image, number, location=None, ex_network='default', ex_tags=None, ex_metadata=None, @@ -3376,6 +4681,61 @@ def ex_create_targethttpproxy(self, name, urlmap): return self.ex_get_targethttpproxy(name) + def ex_create_targethttpsproxy(self, name, urlmap, sslcertificates, + description=None): + """ + Creates a TargetHttpsProxy resource in the specified project + using the data included in the request. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param name: Name of the resource. Provided by the client when the + resource is created. The name must be 1-63 characters + long, and comply with RFC1035. Specifically, the name + must be 1-63 characters long and match the regular + expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all + following characters must be a dash, lowercase letter, + or digit, except the last character, which cannot be a + dash. + :type name: ``str`` + + :param sslcertificates: URLs to SslCertificate resources that + are used to authenticate connections + between users and the load balancer. + Currently, exactly one SSL certificate + must be specified. + :type sslcertificates: ``list`` of :class:`GCESslcertificates` + + :param urlmap: A fully-qualified or valid partial URL to the + UrlMap resource that defines the mapping from URL + to the BackendService. + :type urlmap: :class:`GCEUrlMap` + + :keyword description: An optional description of this resource. + Provide this property when you create the + resource. + :type description: ``str`` + + :return: `GCETargetHttpsProxy` object. + :rtype: :class:`GCETargetHttpsProxy` + """ + + request = "/global/targetHttpsProxies" % () + request_data = {} + request_data['name'] = name + request_data['description'] = description + request_data['sslCertificates'] = [x.extra['selfLink'] + for x in sslcertificates] + request_data['urlMap'] = urlmap.extra['selfLink'] + + self.connection.async_request(request, method='POST', + data=request_data) + + return self.ex_get_targethttpsproxy(name) + def ex_create_targetinstance(self, name, zone=None, node=None, description=None, nat_policy="NO_NAT"): """ @@ -3705,6 +5065,61 @@ def ex_update_firewall(self, firewall): return self.ex_get_firewall(firewall.name) + def ex_targethttpsproxy_set_sslcertificates(self, targethttpsproxy, + sslcertificates): + """ + Replaces SslCertificates for TargetHttpsProxy. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param targethttpsproxy: Name of the TargetHttpsProxy resource to + set an SslCertificates resource for. + :type targethttpsproxy: ``str`` + + :param sslcertificates: sslcertificates to set. + :type sslcertificates: ``list`` of :class:`GCESslCertificates` + + :return: True + :rtype: ``bool`` + """ + + request = "/targetHttpsProxies/%s/setSslCertificates" % ( + targethttpsproxy.name) + request_data = {'sslCertificates': [x.extra['selfLink'] + for x in sslcertificates]} + self.connection.async_request(request, method='POST', + data=request_data) + + return True + + def ex_targethttpsproxy_set_urlmap(self, targethttpsproxy, urlmap): + """ + Changes the URL map for TargetHttpsProxy. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param targethttpsproxy: Name of the TargetHttpsProxy resource + whose URL map is to be set. + :type targethttpsproxy: ``str`` + + :param urlmap: urlmap to set. + :type urlmap: :class:`GCEUrlMap` + + :return: True + :rtype: ``bool`` + """ + + request = "/targetHttpsProxies/%s/setUrlMap" % (targethttpsproxy.name) + request_data = {'urlMap': urlmap.extra['selfLink']} + self.connection.async_request(request, method='POST', + data=request_data) + + return True + def ex_targetpool_get_health(self, targetpool, node=None): """ Return a hash of target pool instances and their health. @@ -3920,6 +5335,158 @@ def ex_targetpool_remove_healthcheck(self, targetpool, healthcheck): targetpool.healthchecks.pop(index) return True + def ex_instancegroup_add_instances(self, instancegroup, node_list): + """ + Adds a list of instances to the specified instance group. All of the + instances in the instance group must be in the same + network/subnetwork. Read Adding instances for more information. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancegroup: The Instance Group where you are + adding instances. + :type instancegroup: :class:``GCEInstanceGroup`` + + :param node_list: List of nodes to add. + :type node_list: ``list`` of :class:`Node` or ``list`` of + :class:`GCENode` + + :return: Return True if successful. + :rtype: ``bool`` + """ + request = "/zones/%s/instanceGroups/%s/addInstances" % ( + instancegroup.zone.name, instancegroup.name) + request_data = {'instances': [{'instance': x.extra['selfLink']} + for x in node_list]} + self.connection.async_request(request, method='POST', + data=request_data) + return True + + def ex_instancegroup_remove_instances(self, instancegroup, node_list): + """ + Removes one or more instances from the specified instance group, + but does not delete those instances. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancegroup: The Instance Group where the + specified instances will be removed. + :type instancegroup: :class:``GCEInstanceGroup`` + + :param node_list: List of nodes to add. + :type node_list: ``list`` of :class:`Node` or ``list`` of + :class:`GCENode` + + :return: True if successful. + :rtype: ``bool`` + """ + request = "/zones/%s/instanceGroups/%s/removeInstances" % ( + instancegroup.zone.name, instancegroup.name) + request_data = {'instances': [{'instance': x.extra['selfLink']} + for x in node_list]} + self.connection.async_request(request, method='POST', + data=request_data) + return True + + def ex_instancegroup_list_instances(self, instancegroup): + """ + Lists the instances in the specified instance group. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + * https://www.googleapis.com/auth/compute.readonly + + :param instancegroup: The Instance Group where from which you + want to generate a list of included + instances. + :type instancegroup: :class:``GCEInstanceGroup`` + + :return: List of :class:`GCENode` objects. + :rtype: ``list`` of :class:`GCENode` objects. + """ + request = "/zones/%s/instanceGroups/%s/listInstances" % ( + instancegroup.zone.name, instancegroup.name) + + # Note: This API requires a 'POST'. + response = self.connection.request(request, method='POST').object + + list_data = [] + if 'items' in response: + for v in response['items']: + instance_info = self._get_components_from_path(v['instance']) + list_data.append( + self.ex_get_node(instance_info['name'], instance_info[ + 'zone'])) + return list_data + + def ex_instancegroup_set_named_ports(self, instancegroup, named_ports=[]): + """ + Sets the named ports for the specified instance group. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancegroup: The Instance Group where where the + named ports are updated. + :type instancegroup: :class:`GCEInstanceGroup` + + :param named_ports: Assigns a name to a port number. For example: + {name: "http", port: 80} This allows the + system to reference ports by the assigned name + instead of a port number. Named ports can also + contain multiple ports. For example: [{name: + "http", port: 80},{name: "http", port: 8080}] + Named ports apply to all instances in this + instance group. + :type named_ports: ``list`` of {'name': ``str``, 'port`: ``int``} + + :return: Return True if successful. + :rtype: ``bool`` + """ + + if not isinstance(named_ports, list): + raise ValueError("'named_ports' must be a list of name/port" + " dictionaries.") + + request = "/zones/%s/instanceGroups/%s/setNamedPorts" % ( + instancegroup.zone.name, instancegroup.name) + request_data = {'namedPorts': named_ports, + 'fingerprint': instancegroup.extra['fingerprint']} + self.connection.async_request(request, method='POST', + data=request_data) + return True + + def ex_destroy_instancegroup(self, instancegroup): + """ + Deletes the specified instance group. The instances in the group + are not deleted. Note that instance group must not belong to a backend + service. Read Deleting an instance group for more information. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancegroup: The name of the instance group to delete. + :type instancegroup: :class:`GCEInstanceGroup` + + :return: Return True if successful. + :rtype: ``bool`` + """ + + request = "/zones/%s/instanceGroups/%s" % (instancegroup.zone.name, + instancegroup.name) + request_data = {} + self.connection.async_request(request, method='DELETE', + data=request_data) + + return True + def ex_instancegroupmanager_list_managed_instances(self, manager): """ Lists all of the instances in the Managed Instance Group. @@ -4479,8 +6046,8 @@ def ex_deprecate_image(self, image, replacement, state=None, try: timestamp_to_datetime(value) except: - raise ValueError('%s must be an RFC3339 timestamp' - % attribute) + raise ValueError('%s must be an RFC3339 timestamp' % + attribute) image_data[attribute] = value request = '/global/images/%s/deprecate' % (image.name) @@ -4634,6 +6201,33 @@ def ex_destroy_instancegroupmanager(self, manager): self.connection.async_request(request, method='DELETE') return True + def ex_destroy_instancetemplate(self, instancetemplate): + """ + Deletes the specified instance template. If you delete an instance + template that is being referenced from another instance group, the + instance group will not be able to create or recreate virtual machine + instances. Deleting an instance template is permanent and cannot be + undone. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param instancetemplate: The name of the instance template to + delete. + :type instancetemplate: ``str`` + + :return instanceTemplate: Return True if successful. + :rtype instanceTemplate: ````bool```` + """ + + request = "/global/instanceTemplates/%s" % (instancetemplate.name) + request_data = {} + self.connection.async_request(request, method='DELETE', + data=request_data) + + return True + def ex_destroy_autoscaler(self, autoscaler): """ Destroy an Autoscaler. @@ -4792,6 +6386,29 @@ def ex_destroy_targethttpproxy(self, targethttpproxy): self.connection.async_request(request, method='DELETE') return True + def ex_destroy_targethttpsproxy(self, targethttpsproxy): + """ + Deletes the specified TargetHttpsProxy resource. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param targethttpsproxy: Name of the TargetHttpsProxy resource to + delete. + :type targethttpsproxy: ``str`` + + :return targetHttpsProxy: Return True if successful. + :rtype targetHttpsProxy: ````bool```` + """ + + request = "/global/targetHttpsProxies/%s" % (targethttpsproxy.name) + request_data = {} + self.connection.async_request(request, method='DELETE', + data=request_data) + + return True + def ex_destroy_targetinstance(self, targetinstance): """ Destroy a target instance. @@ -5109,6 +6726,29 @@ def ex_get_route(self, name): response = self.connection.request(request, method='GET').object return self._to_route(response) + def ex_destroy_sslcertificate(self, sslcertificate): + """ + Deletes the specified SslCertificate resource. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + + :param sslcertificate: Name of the SslCertificate resource to + delete. + :type sslcertificate: ``str`` + + :return sslCertificate: Return True if successful. + :rtype sslCertificate: ````bool```` + """ + + request = "/global/sslCertificates/%s" % (sslcertificate.name) + request_data = {} + self.connection.async_request(request, method='DELETE', + data=request_data) + + return True + def ex_destroy_subnetwork(self, name, region=None): """ Delete a Subnetwork object based on name and region. @@ -5318,6 +6958,29 @@ def ex_get_region(self, name): response = self.connection.request(request, method='GET').object return self._to_region(response) + def ex_get_sslcertificate(self, name): + """ + Returns the specified SslCertificate resource. Get a list of available + SSL certificates by making a list() request. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + * https://www.googleapis.com/auth/compute.readonly + + :param name: Name of the SslCertificate resource to + return. + :type name: ``str`` + + :return: `GCESslCertificate` object. + :rtype: :class:`GCESslCertificate` + """ + + request = "/global/sslCertificates/%s" % (name) + response = self.connection.request(request, method='GET').object + + return self._to_sslcertificate(response) + def ex_get_targethttpproxy(self, name): """ Return a Target HTTP Proxy object based on its name. @@ -5332,6 +6995,29 @@ def ex_get_targethttpproxy(self, name): response = self.connection.request(request, method='GET').object return self._to_targethttpproxy(response) + def ex_get_targethttpsproxy(self, name): + """ + Returns the specified TargetHttpsProxy resource. Get a list of + available target HTTPS proxies by making a list() request. + + Scopes needed - one of the following: + * https://www.googleapis.com/auth/cloud-platform + * https://www.googleapis.com/auth/compute + * https://www.googleapis.com/auth/compute.readonly + + :param name: Name of the TargetHttpsProxy resource to + return. + :type name: ``str`` + + :return: `GCETargetHttpsProxy` object. + :rtype: :class:`GCETargetHttpsProxy` + """ + + request = "/global/targetHttpsProxies/%s" % (name) + response = self.connection.request(request, method='GET').object + + return self._to_targethttpsproxy(response) + def ex_get_targetinstance(self, name, zone=None): """ Return a TargetInstance object based on a name and optional zone. @@ -5700,8 +7386,9 @@ def _create_node_req( ex_on_host_maintenance=None, ex_automatic_restart=None, ex_preemptible=None, ex_subnetwork=None): """ - Returns a request and body to create a new node. This is a helper - method to support both :class:`create_node` and + Returns a request and body to create a new node. + + This is a helper method to support both :class:`create_node` and :class:`ex_create_multiple_nodes`. :param name: The name of the node to create. @@ -5815,140 +7502,34 @@ def _create_node_req( :return: A tuple containing a request string and a node_data dict. :rtype: ``tuple`` of ``str`` and ``dict`` """ - node_data = {} - node_data['machineType'] = size.extra['selfLink'] - node_data['name'] = name - if tags: - node_data['tags'] = {'items': tags} - if metadata: - node_data['metadata'] = self._format_metadata(fingerprint='na', - metadata=metadata) - - # by default, new instances will match the same serviceAccount and - # scope set in the Developers Console and Cloud SDK - if not ex_service_accounts: - set_scopes = [{ - 'email': 'default', - 'scopes': [self.AUTH_URL + 'devstorage.read_only'] - }] - elif not isinstance(ex_service_accounts, list): - raise ValueError("ex_service_accounts field is not a list.") - else: - set_scopes = [] - for sa in ex_service_accounts: - if not isinstance(sa, dict): - raise ValueError("ex_service_accounts needs to be a list " - "of dicts, got: '%s - %s'" % - (str(type(sa)), str(sa))) - if 'email' not in sa: - sa['email'] = 'default' - if 'scopes' not in sa: - sa['scopes'] = [self.AUTH_URL + 'devstorage.read_only'] - ps = [] - for scope in sa['scopes']: - if scope.startswith(self.AUTH_URL): - ps.append(scope) - elif scope in self.SA_SCOPES_MAP: - ps.append(self.AUTH_URL + self.SA_SCOPES_MAP[scope]) - else: - ps.append(self.AUTH_URL + scope) - sa['scopes'] = ps - set_scopes.append(sa) - node_data['serviceAccounts'] = set_scopes - - if boot_disk and ex_disks_gce_struct: - raise ValueError("Cannot specify both 'boot_disk' and " - "'ex_disks_gce_struct'. Use one or the other.") + # build disks if not image and not boot_disk and not ex_disks_gce_struct: raise ValueError("Missing root device or image. Must specify an " "'image', existing 'boot_disk', or use the " "'ex_disks_gce_struct'.") - if boot_disk: - if not isinstance(ex_disk_auto_delete, bool): - raise ValueError("ex_disk_auto_delete field is not a bool.") - disks = [{'boot': True, - 'type': 'PERSISTENT', - 'mode': 'READ_WRITE', - 'deviceName': boot_disk.name, - 'autoDelete': ex_disk_auto_delete, - 'zone': boot_disk.extra['zone'].extra['selfLink'], - 'source': boot_disk.extra['selfLink']}] - node_data['disks'] = disks - - if ex_disks_gce_struct: - node_data['disks'] = ex_disks_gce_struct - - if image and ('disks' not in node_data or not node_data['disks']): - if not hasattr(image, 'name'): - image = self.ex_get_image(image) - if not ex_disk_type: - ex_disk_type = 'pd-standard' - if not hasattr(ex_disk_type, 'name'): - ex_disk_type = self.ex_get_disktype(ex_disk_type) - disks = [{'boot': True, - 'type': 'PERSISTENT', - 'mode': 'READ_WRITE', - 'deviceName': name, - 'autoDelete': ex_disk_auto_delete, - 'zone': location.name, - 'initializeParams': { - 'diskName': name, - 'diskType': ex_disk_type.extra['selfLink'], - 'sourceImage': image.extra['selfLink'], - }}] - node_data['disks'] = disks - - if ex_nic_gce_struct is not None: - if hasattr(external_ip, 'address'): - raise ValueError("Cannot specify both a static IP address " - "and 'ex_nic_gce_struct'. Use one or the " - "other.") - if hasattr(network, 'name'): - if network.name == 'default': - # assume this is just the default value from create_node() - # and since the user specified ex_nic_gce_struct, the - # struct should take precedence - network = None - else: - raise ValueError("Cannot specify both 'network' and " - "'ex_nic_gce_struct'. Use one or the " - "other.") - - ni = [] - if network: - ni = [{'kind': 'compute#instanceNetworkInterface', - 'network': network.extra['selfLink']}] - if ex_subnetwork: - ni[0]['subnetwork'] = ex_subnetwork.extra['selfLink'] - if external_ip: - access_configs = [{'name': 'External NAT', - 'type': 'ONE_TO_ONE_NAT'}] - if hasattr(external_ip, 'address'): - access_configs[0]['natIP'] = external_ip.address - ni[0]['accessConfigs'] = access_configs - else: - ni = ex_nic_gce_struct - node_data['networkInterfaces'] = ni + if boot_disk and ex_disks_gce_struct: + raise ValueError("Cannot specify both 'boot_disk' and " + "'ex_disks_gce_struct'. Use one or the other.") - if description: - node_data['description'] = str(description) - if ex_can_ip_forward: - node_data['canIpForward'] = True - scheduling = {} - if ex_on_host_maintenance: - if isinstance(ex_on_host_maintenance, str) and \ - ex_on_host_maintenance in ['MIGRATE', 'TERMINATE']: - scheduling['onHostMaintenance'] = ex_on_host_maintenance - else: - scheduling['onHostMaintenance'] = 'MIGRATE' - if ex_automatic_restart is not None: - scheduling['automaticRestart'] = ex_automatic_restart - if ex_preemptible is not None: - scheduling['preemptible'] = ex_preemptible - if scheduling: - node_data['scheduling'] = scheduling + use_selflinks = True + source = None + if boot_disk: + source = boot_disk + + node_data = self._create_instance_properties( + name, node_size=size, image=image, source=source, + disk_type=ex_disk_type, disk_auto_delete=ex_disk_auto_delete, + external_ip=external_ip, network=network, subnetwork=ex_subnetwork, + can_ip_forward=ex_can_ip_forward, + service_accounts=ex_service_accounts, + on_host_maintenance=ex_on_host_maintenance, + automatic_restart=ex_automatic_restart, preemptible=ex_preemptible, + tags=tags, metadata=metadata, description=description, + disks_gce_struct=ex_disks_gce_struct, + nic_gce_struct=ex_nic_gce_struct, use_selflinks=use_selflinks) + node_data['name'] = name request = '/zones/%s/instances' % (location.name) return request, node_data @@ -6315,6 +7896,26 @@ def _to_forwarding_rule(self, forwarding_rule): protocol=forwarding_rule.get('IPProtocol'), targetpool=target, driver=self, extra=extra) + def _to_sslcertificate(self, sslcertificate): + """ + Return the SslCertificate object from the JSON-response. + + :param sslcertificate: Dictionary describing SslCertificate + :type sslcertificate: ``dict`` + + :return: Return SslCertificate object. + :rtype: :class:`GCESslCertificate` + """ + extra = {} + if 'description' in sslcertificate: + extra['description'] = sslcertificate['description'] + extra['selfLink'] = sslcertificate['selfLink'] + + return GCESslCertificate(id=sslcertificate['id'], + name=sslcertificate['name'], + certificate=sslcertificate['certificate'], + driver=self, extra=extra) + def _to_subnetwork(self, subnetwork): """ Return a Subnetwork object from the JSON-response dictionary. @@ -6702,6 +8303,34 @@ def _to_targethttpproxy(self, targethttpproxy): name=targethttpproxy['name'], urlmap=urlmap, driver=self, extra=extra) + def _to_targethttpsproxy(self, targethttpsproxy): + """ + Return the TargetHttpsProxy object from the JSON-response. + + :param targethttpsproxy: Dictionary describing TargetHttpsProxy + :type targethttpsproxy: ``dict`` + + :return: Return TargetHttpsProxy object. + :rtype: :class:`GCETargetHttpsProxy` + """ + extra = {} + if 'description' in targethttpsproxy: + extra['description'] = targethttpsproxy['description'] + extra['selfLink'] = targethttpsproxy['selfLink'] + + sslcertificates = [ + self._get_object_by_kind(x) + for x in targethttpsproxy.get('sslCertificates', []) + ] + obj_name = self._get_components_from_path(targethttpsproxy['urlMap'])[ + 'name'] + urlmap = self.ex_get_urlmap(obj_name) + + return GCETargetHttpsProxy(id=targethttpsproxy['id'], + name=targethttpsproxy['name'], + sslcertificates=sslcertificates, + urlmap=urlmap, driver=self, extra=extra) + def _to_targetinstance(self, targetinstance): """ Return a Target Instance object from the JSON-response dictionary. @@ -6780,28 +8409,28 @@ def _to_instancegroup(self, instancegroup): :rtype: :class:`GCEInstanceGroup` """ extra = {} - extra['description'] = instancegroup['description'] + extra['description'] = instancegroup.get('description', None) extra['selfLink'] = instancegroup['selfLink'] extra['namedPorts'] = instancegroup.get('namedPorts', []) + extra['fingerprint'] = instancegroup.get('fingerprint', None) zone = self.ex_get_zone(instancegroup['zone']) - obj_name = self._get_components_from_path(instancegroup['network'])[ - 'name'] - network = self.ex_get_network(obj_name) - # TODO(supertom): Investigate further. Subnetwork seems optional, - # but docs say otherwise. In the meantime, be defensive. + # Note: network/subnetwork will not be available if the Instance Group + # does not contain instances. + network = instancegroup.get('network', None) + if network: + obj_name = self._get_components_from_path(network)['name'] + network = self.ex_get_network(obj_name) + subnetwork = instancegroup.get('subnetwork', None) if subnetwork: - obj_name = self._get_components_from_path(subnetwork)['name'] - subnetwork = self.ex_get_subnetwork(obj_name) - else: - subnetwork = None + parts = self._get_components_from_path(subnetwork) + subnetwork = self.ex_get_subnetwork(parts['name'], parts['region']) return GCEInstanceGroup( id=instancegroup['id'], name=instancegroup['name'], zone=zone, network=network, subnetwork=subnetwork, - description=instancegroup['description'], named_ports=instancegroup.get('namedPorts', []), driver=self, extra=extra) @@ -7090,7 +8719,9 @@ def _licenses_from_urls(self, licenses): 'compute#project': _to_project, 'compute#region': _to_region, 'compute#snapshot': _to_snapshot, + 'compute#sslCertificate': _to_sslcertificate, 'compute#targetHttpProxy': _to_targethttpproxy, + 'compute#targetHttpsProxy': _to_targethttpsproxy, 'compute#targetInstance': _to_targetinstance, 'compute#targetPool': _to_targetpool, 'compute#urlMap': _to_urlmap, diff --git a/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json b/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json index f42644e919..0f42511d29 100644 --- a/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json +++ b/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json @@ -85,7 +85,7 @@ "name": "myinstancegroup", "description": "my description for myinstancegroup", "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1", - "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup", + "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup", "baseInstanceName": "base-foo", "fingerprint": "Q21hYveq9do=", "currentActions": { @@ -98,7 +98,7 @@ "refreshing": 0 }, "targetSize": 4, - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup" } ] }, @@ -232,7 +232,7 @@ "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b", "name": "myinstancegroup", "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1", - "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup", + "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup", "baseInstanceName": "myinstancegroup", "fingerprint": "5bKcxzAnGOg=", "currentActions": { @@ -245,7 +245,7 @@ "refreshing": 0 }, "targetSize": 2, - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup" } ] }, diff --git a/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json b/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json index e9af3b4776..4e0e5b1b61 100644 --- a/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json +++ b/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json @@ -7,14 +7,14 @@ "backends": [ { "description": "", - "group": "https://www.googleapis.com/resourceviews/v1beta1/projects/project_name/zones/us-central1-b/resourceViews/us-resources", + "group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", "balancingMode": "RATE", "maxRate": 100, "capacityScaler": 1.0 }, { "description": "", - "group": "https://www.googleapis.com/resourceviews/v1beta1/projects/project_name/zones/europe-west1-b/resourceViews/eu-resources", + "group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", "balancingMode": "RATE", "maxRate": 150, "capacityScaler": 1.0 diff --git a/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json b/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json index d2b022531c..1614824f12 100644 --- a/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json +++ b/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json @@ -1,5 +1,5 @@ { - "id": "projects/supertom-graphite/global/instanceTemplates", + "id": "projects/project_name/global/instanceTemplates", "items": [ { "creationTimestamp": "2016-07-18T09:53:22.323-07:00", @@ -17,7 +17,7 @@ "initializeParams": { "diskSizeGb": "10", "diskType": "pd-standard", - "sourceImage": "projects/supertom-graphite/global/images/my-new-image1" + "sourceImage": "projects/project_name/global/images/my-new-image1" }, "kind": "compute#attachedDisk", "mode": "READ_WRITE", @@ -38,7 +38,7 @@ "type": "ONE_TO_ONE_NAT" } ], - "network": "https://content.googleapis.com/compute/v1/projects/supertom-graphite/global/networks/default" + "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default" } ], "scheduling": { @@ -59,9 +59,9 @@ } ] }, - "selfLink": "https://content.googleapis.com/compute/v1/projects/supertom-graphite/global/instanceTemplates/my-instance-template1" + "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1" } ], "kind": "compute#instanceTemplateList", - "selfLink": "https://www.googleapis.com/compute/v1/projects/supertom-graphite/global/instanceTemplates" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates" } diff --git a/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json b/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json new file mode 100644 index 0000000000..fe35dcb787 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json @@ -0,0 +1,12 @@ +{ + "status": "PENDING", + "kind": "compute#operation", + "name": "my_instance_template1", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my_instance_template1", + "operationType": "compute.instanceTemplates.insert", + "progress": 50, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_instanceTemplates_my_instance_template1_insert", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} diff --git a/libcloud/test/compute/fixtures/gce/global_sslcertificates.json b/libcloud/test/compute/fixtures/gce/global_sslcertificates.json new file mode 100644 index 0000000000..170c1cbbfa --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_sslcertificates.json @@ -0,0 +1,16 @@ +{ + "kind": "compute#sslCertificateList", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates", + "id": "projects/project_name/global/sslCertificates", + "items": [ + { + "kind": "compute#sslCertificate", + "id": "2064539516762881220", + "creationTimestamp": "2016-08-30T10:28:11.926-07:00", + "name": "example", + "description": "my example ssl cert.", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example-cert", + "certificate": "-----BEGIN CERTIFICATE-----\nfoobar==\n-----END CERTIFICATE-----\n" + } + ] +} diff --git a/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json b/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json new file mode 100644 index 0000000000..c6a2985e47 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json @@ -0,0 +1,10 @@ +{ + "kind": "compute#sslCertificate", + "id": "2064539516762881220", + "creationTimestamp": "2016-08-30T10:28:11.926-07:00", + "name": "example", + "description": "my example ssl cert.", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example", + "certificate": "-----BEGIN CERTIFICATE-----\nfoobar==\n-----END CERTIFICATE-----\n" + +} diff --git a/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json b/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json new file mode 100644 index 0000000000..23a9542461 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json @@ -0,0 +1,13 @@ +{ + "kind": "compute#operation", + "id": "5564862567931215044", + "name": "operation-1472578091714-53b4d4e0f85d1-cf587a68-9d7a9200", + "operationType": "insert", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example", + "targetId": "2064539516762881220", + "status": "PENDING", + "user": "1294195755358-compute@developer.gserviceaccount.com", + "progress": 0, + "insertTime": "2016-08-30T10:28:11.948-07:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation_global_sslcertificates_post" +} diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json new file mode 100644 index 0000000000..274cc4f496 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json @@ -0,0 +1,12 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "my_instance_template1", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my_instance_template1", + "operationType": "compute.instanceTemplates.insert", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_instanceTemplates_my_instance_template1_insert", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json new file mode 100644 index 0000000000..73e5fbd3b3 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json @@ -0,0 +1,13 @@ +{ + "kind": "compute#operation", + "id": "5564862567931215044", + "name": "operation-1472578091714-53b4d4e0f85d1-cf587a68-9d7a9200", + "operationType": "insert", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example", + "targetId": "2064539516762881220", + "status": "DONE", + "user": "1294195755358-compute@developer.gserviceaccount.com", + "progress": 100, + "insertTime": "2016-08-30T10:28:11.948-07:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation_global_sslcertificates_post" +} diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json new file mode 100644 index 0000000000..2b6c12f85f --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones_us_central1_a/instanceGroups/myname", + "operationType": "compute.instanceGroups.insert", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_insert", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json new file mode 100644 index 0000000000..9e21253b3a --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.addInstances", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_addInstances", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json new file mode 100644 index 0000000000..e13d01791a --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.delete", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_delete", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json new file mode 100644 index 0000000000..a994784255 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.removeInstances", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_removeInstances", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json new file mode 100644 index 0000000000..e7b869e0a7 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.setNamedPorts", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_setNamedPorts", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json b/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json new file mode 100644 index 0000000000..79ac9e4138 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json @@ -0,0 +1,11 @@ +{ + "kind": "compute#subnetwork", + "id": "4297043163355844284", + "creationTimestamp": "2016-10-01T05:34:27.209-07:00", + "gatewayAddress": "10.128.0.1", + "name": "cf-972cf02e6ad49113", + "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf", + "ipCidrRange": "10.128.0.0/20", + "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49113" +} diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json index 8f6c569863..c451bd6500 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json @@ -11,7 +11,7 @@ "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a", "name": "myinstancegroup", "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1", - "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", + "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", "baseInstanceName": "myinstancegroup", "fingerprint": "5bKcxzAnGOg=", "currentActions": { @@ -24,7 +24,7 @@ "refreshing": 0 }, "targetSize": 2, - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup" } ] } diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json index be0f0e723a..823b5b38c2 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json @@ -7,7 +7,7 @@ "name": "myinstancegroup", "description": "my description for myinstancegroup", "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1", - "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", + "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", "baseInstanceName": "base-foo", "fingerprint": "Q21hYveq9do=", "currentActions": { @@ -20,5 +20,5 @@ "refreshing": 0 }, "targetSize": 4, - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup" } diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json index ee36755ee2..68f06e6fe0 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json @@ -4,11 +4,11 @@ "id": "1968709502073089769", "creationTimestamp": "2016-08-11T16:53:42.413-07:00", "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a", - "name": "managed-instance-group-1", + "name": "myinstancegroup", "description": "This instance group is controlled by Instance Group Manager 'myinstancegroup'. To modify instances in this group, use the Instance Group Manager API: https://cloud.google.com/compute/docs/reference/latest/instanceGroupManagers", "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default", "fingerprint": "42WmSpB8rSM=", - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup", "size": 4, "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112" } diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json new file mode 100644 index 0000000000..f853bb778f --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json @@ -0,0 +1,14 @@ +{ + + "kind": "compute#instanceGroup", + "id": "1968709502073089768", + "creationTimestamp": "2016-08-26T16:53:42.413-07:00", + "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a", + "name": "myinstancegroup2", + "description": "myinstancegroup2", + "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default", + "fingerprint": "42WmSpB8rSN=", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup2", + "size": 4, + "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112" +} diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json index 2385aa3697..863f664afc 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json @@ -7,7 +7,7 @@ "name": "myinstancegroup", "description": "my description for myinstancegroup", "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1", - "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup", + "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup", "baseInstanceName": "base-foo", "fingerprint": "Q21hYveq9do=", "currentActions": { @@ -20,5 +20,5 @@ "refreshing": 0 }, "targetSize": 4, - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup" } diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json index c4330a823d..94815c4daa 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json @@ -10,5 +10,5 @@ "fingerprint": "42WmSpB8rSM=", "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup", "size": 4, - "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112" + "subnetwork": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112" } diff --git a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json index 1bf7014a30..1ac5d9f650 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json @@ -11,7 +11,7 @@ "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b", "name": "myinstancegroup", "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1", - "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup", + "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup", "baseInstanceName": "myinstancegroup", "fingerprint": "5bKcxzAnGOg=", "currentActions": { @@ -24,7 +24,7 @@ "refreshing": 0 }, "targetSize": 2, - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup" + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup" } ] } diff --git a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json index 37263738fb..55e9ff53fd 100644 --- a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json +++ b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json @@ -8,7 +8,7 @@ "description": "This instance group is controlled by Instance Group Manager 'myinstancegroup'. To modify instances in this group, use the Instance Group Manager API: https://cloud.google.com/compute/docs/reference/latest/instanceGroupManagers", "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default", "fingerprint": "42WmSpB8rSM=", - "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup", "size": 4, - "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-972cf02e6ad49112" + "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-972cf02e6ad49113" } diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json new file mode 100644 index 0000000000..33a837babd --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json @@ -0,0 +1,29 @@ +{ + "id": "projects/project_name/zones/us-central1-a/instanceGroups", + "items": [ + { + "creationTimestamp": "2016-09-09T13:48:39.700-07:00", + "description": "", + "fingerprint": "42WmSpB8rSM=", + "id": "5837905299775594184", + "kind": "compute#instanceGroup", + "name": "myname", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "size": 0, + "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a" + }, + { + "creationTimestamp": "2016-09-09T13:54:30.857-07:00", + "description": "", + "fingerprint": "42WmSpB8rSM=", + "id": "6825641674983513961", + "kind": "compute#instanceGroup", + "name": "myname2", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname2", + "size": 0, + "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a" + } + ], + "kind": "compute#instanceGroupList", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups" +} diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json new file mode 100644 index 0000000000..2b6c12f85f --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones_us_central1_a/instanceGroups/myname", + "operationType": "compute.instanceGroups.insert", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_insert", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json new file mode 100644 index 0000000000..7114faca30 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json @@ -0,0 +1,12 @@ +{ + + "kind": "compute#instanceGroup", + "id": "5837905299775594184", + "creationTimestamp": "2016-09-09T13:48:39.700-07:00", + "name": "myname", + "description": "", + "fingerprint": "42WmSpB8rSM=", + "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "size": 0 +} diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json new file mode 100644 index 0000000000..9e21253b3a --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.addInstances", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_addInstances", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json new file mode 100644 index 0000000000..e13d01791a --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.delete", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_delete", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json new file mode 100644 index 0000000000..94c6cbff3b --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json @@ -0,0 +1,15 @@ +{ + "id": "projects/project_name/zones/us-central1-a/instanceGroups/myname/listInstances", + "items": [ + { + "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name", + "status": "RUNNING" + }, + { + "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/lcnode-001", + "status": "RUNNING" + } + ], + "kind": "compute#instanceGroupsListInstances", + "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname/listInstances" +} diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json new file mode 100644 index 0000000000..a994784255 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.removeInstances", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_removeInstances", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json new file mode 100644 index 0000000000..e7b869e0a7 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json @@ -0,0 +1,13 @@ +{ + "status": "DONE", + "kind": "compute#operation", + "name": "myname", + "zone": "us-central1-a", + "insertTime": "2016-09-02T09:31:52.285-07:00", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", + "operationType": "compute.instanceGroups.setNamedPorts", + "progress": 100, + "id": 123456, + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_setNamedPorts", + "user": "1264195755357-compute@developer.gserviceaccount.com" +} \ No newline at end of file diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py index 75a53f1e61..9ec97e085f 100644 --- a/libcloud/test/compute/test_gce.py +++ b/libcloud/test/compute/test_gce.py @@ -22,7 +22,7 @@ from libcloud.utils.py3 import httplib from libcloud.compute.drivers.gce import ( - GCENodeDriver, API_VERSION, timestamp_to_datetime, GCEAddress, + GCENodeDriver, API_VERSION, timestamp_to_datetime, GCEAddress, GCEBackend, GCEBackendService, GCEFirewall, GCEForwardingRule, GCEHealthCheck, GCENetwork, GCENodeImage, GCERoute, GCERegion, GCETargetHttpProxy, GCEUrlMap, GCEZone, GCESubnetwork) @@ -108,6 +108,125 @@ def test_match_images(self): image = self.driver._match_images(project, 'backports') self.assertEqual(image.name, 'backports-debian-7-wheezy-v20131127') + def test_build_disk_gce_struct(self): + device_name = 'disk_name' + disk_name = None + source = self.driver.ex_get_volume('lcdisk') + is_boot = True + # source as input + d = self.driver._build_disk_gce_struct( + device_name=device_name, source=source, disk_name=disk_name, + is_boot=is_boot) + self.assertEqual(source.extra['selfLink'], d['source']) + self.assertTrue(d['boot']) + self.assertTrue(d['autoDelete']) + self.assertEqual('READ_WRITE', d['mode']) + self.assertFalse('initializeParams' in d) + + # image as input + device_name = 'disk_name' + disk_type = self.driver.ex_get_disktype('pd-ssd', 'us-central1-a') + image = self.driver.ex_get_image('debian-7') + source = None + is_boot = True + d = self.driver._build_disk_gce_struct(device_name=device_name, + disk_type=disk_type, + image=image, is_boot=is_boot) + self.assertEqual('READ_WRITE', d['mode']) + self.assertEqual('PERSISTENT', d['type']) + self.assertTrue('initializeParams' in d and + isinstance(d['initializeParams'], dict)) + self.assertTrue( + all(k in d['initializeParams'] + for k in ['sourceImage', 'diskType', 'diskName'])) + self.assertTrue(d['initializeParams']['sourceImage'].startswith( + 'https://')) + self.assertTrue(d['autoDelete']) + self.assertTrue(d['boot']) + + def test_build_network_gce_struct(self): + network = self.driver.ex_get_network('lcnetwork') + address = self.driver.ex_get_address('lcaddress') + subnetwork_name = 'cf-972cf02e6ad49112' + subnetwork = self.driver.ex_get_subnetwork(subnetwork_name) + d = self.driver._build_network_gce_struct(network, subnetwork, address) + self.assertTrue('network' in d) + self.assertTrue('subnetwork' in d) + self.assertTrue('kind' in d and + d['kind'] == 'compute#instanceNetworkInterface') + + network = self.driver.ex_get_network('default') + d = self.driver._build_network_gce_struct(network) + self.assertTrue('network' in d) + self.assertFalse('subnetwork' in d) + self.assertTrue('kind' in d and + d['kind'] == 'compute#instanceNetworkInterface') + + def test_build_scheduling_gce_struct(self): + self.assertFalse( + self.driver._build_scheduling_gce_struct(None, None, None)) + # on_host_maintenance bad value should raise a Valueerror + self.assertRaises(ValueError, + self.driver._build_service_account_gce_struct, + 'on_host_maintenance="foobar"') + # on_host_maintenance is 'MIGRATE' and prempt is True + self.assertRaises(ValueError, + self.driver._build_service_account_gce_struct, + 'on_host_maintenance="MIGRATE"', 'preemptible=True') + # automatic_restart is True and prempt is True + self.assertRaises(ValueError, + self.driver._build_service_account_gce_struct, + 'automatic_restart="True"', 'preemptible=True') + + actual = self.driver._build_scheduling_gce_struct('TERMINATE', True, + False) + self.assertTrue('automaticRestart' in actual and + actual['automaticRestart'] is True) + self.assertTrue('onHostMaintenance' in actual and + actual['onHostMaintenance'] == 'TERMINATE') + self.assertTrue('preemptible' in actual) + self.assertFalse(actual['preemptible']) + + def test_build_service_account_gce_struct(self): + self.assertRaises(ValueError, + self.driver._build_service_account_gce_struct, None) + input = {'scopes': ['compute-ro']} + actual = self.driver._build_service_account_gce_struct(input) + self.assertTrue('email' in actual) + self.assertTrue('scopes' in actual) + + def test_build_service_account_gce_list(self): + # ensure we have a list + self.assertRaises(ValueError, + self.driver._build_service_accounts_gce_list, 'foo') + # no input + actual = self.driver._build_service_accounts_gce_list() + self.assertTrue(len(actual) == 1) + self.assertTrue('email' in actual[0]) + self.assertTrue('scopes' in actual[0]) + + def test_get_selflink_or_name(self): + network = self.driver.ex_get_network('lcnetwork') + + # object as input + actual = self.driver._get_selflink_or_name(network, False, 'network') + self.assertEqual('lcnetwork', actual) + actual = self.driver._get_selflink_or_name(network, True, 'network') + self.assertTrue(actual.startswith('https://')) + + # name-only as input + actual = self.driver._get_selflink_or_name('lcnetwork', True, + 'network') + self.assertTrue(actual.startswith('https://')) + + actual = self.driver._get_selflink_or_name('lcnetwork', False, + 'network') + self.assertTrue('lcnetwork', actual) + + # if selflinks is true, we need objname + self.assertRaises(ValueError, self.driver._get_selflink_or_name, + 'lcnetwork', True) + def test_ex_get_serial_output(self): self.assertRaises(ValueError, self.driver.ex_get_serial_output, 'foo') node = self.driver.ex_get_node('node-name', 'us-central1-a') @@ -213,6 +332,86 @@ def test_list_images(self): self.assertEqual(local_images[0].name, 'aws-ubuntu') self.assertEqual(debian_images[1].name, 'debian-7-wheezy-v20131120') + def test_ex_destroy_instancegroup(self): + name = 'myname' + zone = 'us-central1-a' + uig = self.driver.ex_get_instancegroup(name, zone) + self.assertTrue(self.driver.ex_destroy_instancegroup(uig)) + + def test_ex_get_instancegroup(self): + name = 'myname' + loc = 'us-central1-a' + actual = self.driver.ex_get_instancegroup(name, loc) + self.assertEqual(actual.name, name) + self.assertEqual(actual.zone.name, loc) + + def test_ex_create_instancegroup(self): + name = 'myname' + loc = 'us-central1-a' + actual = self.driver.ex_create_instancegroup(name, loc) + self.assertEqual(actual.name, name) + self.assertEqual(actual.zone.name, loc) + + def test_ex_list_instancegroups(self): + loc = 'us-central1-a' + actual = self.driver.ex_list_instancegroups(loc) + self.assertTrue(len(actual) == 2) + self.assertEqual(actual[0].name, 'myname') + self.assertEqual(actual[1].name, 'myname2') + + def test_ex_instancegroup_list_instances(self): + name = 'myname' + loc = 'us-central1-a' + gceobj = self.driver.ex_get_instancegroup(name, loc) + actual = self.driver.ex_instancegroup_list_instances(gceobj) + self.assertTrue(len(actual) == 2) + for node in actual: + self.assertTrue(isinstance(node, Node)) + self.assertEqual(loc, node.extra['zone'].name) + + def test_ex_instancegroup_add_instances(self): + name = 'myname' + loc = 'us-central1-a' + gceobj = self.driver.ex_get_instancegroup(name, loc) + node_name = self.driver.ex_get_node('node-name', loc) + lcnode = self.driver.ex_get_node('lcnode-001', loc) + node_list = [node_name, lcnode] + self.assertTrue( + self.driver.ex_instancegroup_add_instances(gceobj, node_list)) + + def test_ex_instancegroup_remove_instances(self): + name = 'myname' + loc = 'us-central1-a' + gceobj = self.driver.ex_get_instancegroup(name, loc) + node_name = self.driver.ex_get_node('node-name', loc) + lcnode = self.driver.ex_get_node('lcnode-001', loc) + node_list = [node_name, lcnode] + self.assertTrue( + self.driver.ex_instancegroup_remove_instances(gceobj, node_list)) + + def test_ex_instancegroup_set_named_ports(self): + name = 'myname' + loc = 'us-central1-a' + gceobj = self.driver.ex_get_instancegroup(name, loc) + named_ports = [{'name': 'foo', 'port': 4444}] + # base case + self.assertTrue( + self.driver.ex_instancegroup_set_named_ports(gceobj, named_ports)) + # specify nothing, default is empty list + self.assertTrue(self.driver.ex_instancegroup_set_named_ports(gceobj)) + # specify empty list + self.assertTrue( + self.driver.ex_instancegroup_set_named_ports(gceobj, [])) + # raise valueerror if string is passed in + self.assertRaises(ValueError, + self.driver.ex_instancegroup_set_named_ports, gceobj, + 'foobar') + # raise valueerror if dictionary is passed in + self.assertRaises(ValueError, + self.driver.ex_instancegroup_set_named_ports, gceobj, + {'name': 'foo', + 'port': 4444}) + def test_ex_create_instancegroupmanager(self): name = 'myinstancegroup' zone = 'us-central1-a' @@ -226,6 +425,14 @@ def test_ex_create_instancegroupmanager(self): self.assertEqual(mig.size, size) self.assertEqual(mig.zone.name, zone) + def test_ex_create_instancetemplate(self): + name = 'my-instance-template1' + actual = self.driver.ex_create_instancetemplate( + name, size='n1-standard-1', image='debian-7', network='default') + self.assertEqual(actual.name, name) + self.assertEqual(actual.extra['properties']['machineType'], + 'n1-standard-1') + def test_list_locations(self): locations = self.driver.list_locations() self.assertEqual(len(locations), 6) @@ -236,6 +443,12 @@ def test_ex_list_routes(self): self.assertEqual(len(routes), 3) self.assertTrue('lcdemoroute' in [route.name for route in routes]) + def test_ex_list_sslcertificate(self): + ssl_name = 'example' + certs = self.driver.ex_list_sslcertificates() + self.assertEqual(certs[0].name, ssl_name) + self.assertTrue(len(certs) == 1) + def test_ex_list_subnetworks(self): subnetworks = self.driver.ex_list_subnetworks() self.assertEqual(len(subnetworks), 1) @@ -244,6 +457,15 @@ def test_ex_list_subnetworks(self): subnetworks = self.driver.ex_list_subnetworks('all') self.assertEqual(len(subnetworks), 4) + def test_ex_create_sslcertificate(self): + ssl_name = 'example' + private_key = '-----BEGIN RSA PRIVATE KEY-----\nfoobar==\n-----END RSA PRIVATE KEY-----\n' + certificate = '-----BEGIN CERTIFICATE-----\nfoobar==\n-----END CERTIFICATE-----\n' + ssl = self.driver.ex_create_sslcertificate( + ssl_name, certificate=certificate, private_key=private_key) + self.assertEqual(ssl_name, ssl.name) + self.assertEqual(certificate, ssl.certificate) + def test_ex_create_subnetwork(self): name = 'cf-972cf02e6ad49112' cidr = '10.128.0.0/20' @@ -278,6 +500,13 @@ def test_ex_destroy_subnetwork(self): # delete with region object self.assertTrue(self.driver.ex_destroy_subnetwork(name, region)) + def test_ex_get_sslcertificate(self): + ssl_name = 'example' + ssl = self.driver.ex_get_sslcertificate(ssl_name) + self.assertEqual(ssl.name, ssl_name) + self.assertTrue(hasattr(ssl, 'certificate')) + self.assertTrue(len(ssl.certificate)) + def test_ex_get_subnetwork(self): name = 'cf-972cf02e6ad49112' region_name = 'us-central1' @@ -403,7 +632,7 @@ def test_ex_instancegroupmanager_list_managed_instances(self): instances = mig.list_managed_instances() self.assertTrue(all([x['currentAction'] == 'NONE' for x in instances])) self.assertTrue('base-foo-2vld' in [x['name'] for x in instances]) - self.assertEquals(len(instances), 4) + self.assertEqual(len(instances), 4) def test_ex_list_instancetemplates(self): instancetemplates = self.driver.ex_list_instancetemplates() @@ -453,12 +682,40 @@ def test_ex_create_address(self): self.assertTrue(isinstance(address, GCEAddress)) self.assertEqual(address.name, address_name) + def test_ex_create_backend(self): + # Note: this is an internal object, no API call is made + # and no fixture is needed specifically for GCEBackend, however + # it does rely on an InstanceGroup object. + ig = self.driver.ex_get_instancegroup('myinstancegroup', + 'us-central1-a') + + backend = self.driver.ex_create_backend(ig) + + self.assertTrue(isinstance(backend, GCEBackend)) + self.assertEqual(backend.name, + '%s/instanceGroups/%s' % (ig.zone.name, ig.name)) + self.assertEqual(backend.instance_group.name, ig.name) + self.assertEqual(backend.balancing_mode, 'UTILIZATION') + def test_ex_create_backendservice(self): backendservice_name = 'web-service' + + ig1 = self.driver.ex_get_instancegroup('myinstancegroup', + 'us-central1-a') + backend1 = self.driver.ex_create_backend(ig1) + ig2 = self.driver.ex_get_instancegroup('myinstancegroup2', + 'us-central1-a') + backend2 = self.driver.ex_create_backend(ig2) + backendservice = self.driver.ex_create_backendservice( - name=backendservice_name, healthchecks=['lchealthcheck']) + name=backendservice_name, healthchecks=['lchealthcheck'], + backends=[backend1, backend2]) self.assertTrue(isinstance(backendservice, GCEBackendService)) self.assertEqual(backendservice.name, backendservice_name) + self.assertEqual(len(backendservice.backends), 2) + ig_links = [ig1.extra['selfLink'], ig2.extra['selfLink']] + for be in backendservice.backends: + self.assertTrue(be['group'] in ig_links) def test_ex_create_healthcheck(self): healthcheck_name = 'lchealthcheck' @@ -486,8 +743,8 @@ def test_ex_create_image(self): family = 'coreos' guest_os_features = ['VIRTIO_SCSI_MULTIQUEUE', 'WINDOWS'] expected_features = [ - {'type': 'VIRTIO_SCSI_MULTIQUEUE'}, - {'type': 'WINDOWS'}] + {'type': 'VIRTIO_SCSI_MULTIQUEUE'}, {'type': 'WINDOWS'} + ] mock_request = mock.Mock() mock_request.side_effect = self.driver.connection.async_request self.driver.connection.async_request = mock_request @@ -516,8 +773,8 @@ def test_ex_copy_image(self): family = 'coreos' guest_os_features = ['VIRTIO_SCSI_MULTIQUEUE', 'WINDOWS'] expected_features = [ - {'type': 'VIRTIO_SCSI_MULTIQUEUE'}, - {'type': 'WINDOWS'}] + {'type': 'VIRTIO_SCSI_MULTIQUEUE'}, {'type': 'WINDOWS'} + ] image = self.driver.ex_copy_image(name, url, description=description, family=family, guest_os_features=guest_os_features) @@ -676,6 +933,9 @@ def test_create_node_req(self): self.assertEqual(node_data['serviceAccounts'][0]['email'], 'default') self.assertIsInstance(node_data['serviceAccounts'][0]['scopes'], list) self.assertEqual(len(node_data['serviceAccounts'][0]['scopes']), 1) + self.assertEqual(len(node_data['networkInterfaces']), 1) + self.assertTrue(node_data['networkInterfaces'][0][ + 'network'].startswith('https://')) def test_create_node_network_opts(self): node_name = 'node-name' @@ -2206,6 +2466,12 @@ def _global_urlMaps_web_map(self, method, url, body, headers): body = self.fixtures.load('global_urlMaps_web_map.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _regions_us_east1_subnetworks_cf_972cf02e6ad49113(self, method, url, + body, headers): + body = self.fixtures.load( + 'regions_us-east1_subnetworks_cf_972cf02e6ad49113.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _regions_us_central1_subnetworks_cf_972cf02e6ad49112(self, method, url, body, headers): body = self.fixtures.load( @@ -2939,6 +3205,12 @@ def _zones_us_central1_a_instanceGroups_myinstancegroup(self, method, url, 'zones_us-central1-a_instanceGroup_myinstancegroup.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _zones_us_central1_a_instanceGroups_myinstancegroup2(self, method, url, + body, headers): + body = self.fixtures.load( + 'zones_us-central1-a_instanceGroup_myinstancegroup2.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _zones_us_central1_b_instanceGroups_myinstancegroup(self, method, url, body, headers): body = self.fixtures.load( @@ -2975,7 +3247,6 @@ def _zones_us_east1_b_instanceGroupManagers(self, method, url, body, 'zones_us-east1-b_instanceGroupManagers.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) - # TODO(supertom): this one def _zones_us_central1_a_instanceGroupManagers(self, method, url, body, headers): # do an insert. Returns an operations link, which then @@ -2995,7 +3266,19 @@ def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroupM return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) def _global_instanceTemplates(self, method, url, body, headers): - body = self.fixtures.load('global_instanceTemplates.json') + if method == 'POST': + # insert + body = self.fixtures.load('global_instanceTemplates_insert.json') + else: + # get or list call + body = self.fixtures.load('global_instanceTemplates.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _global_operations_operation_global_instanceTemplates_my_instance_template1_insert( + self, method, url, body, headers): + """ Redirects from _global_instanceTemplates """ + body = self.fixtures.load( + 'operations_operation_global_instanceTemplates_insert.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) def _global_instanceTemplates_my_instance_template1(self, method, url, @@ -3008,6 +3291,109 @@ def _aggregated_autoscalers(self, method, url, body, headers): body = self.fixtures.load('aggregated_autoscalers.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_sslCertificates(self, method, url, body, headers): + if method == 'POST': + body = self.fixtures.load('global_sslcertificates_post.json') + else: + body = self.fixtures.load('global_sslcertificates.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _global_sslCertificates_example(self, method, url, body, headers): + body = self.fixtures.load('global_sslcertificates_example.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _global_operations_operation_global_sslcertificates_post( + self, method, url, body, headers): + body = self.fixtures.load( + 'operations_operation_global_sslcertificates_post.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_instanceGroups_myname(self, method, url, body, + headers): + if method == 'DELETE': + # delete + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_myname_delete.json') + else: + # get or list call + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_myname.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_delete( + self, method, url, body, headers): + """ Redirects from _zones_us_central1_a_instanceGroups_myname """ + body = self.fixtures.load( + 'operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_instanceGroups(self, method, url, body, headers): + if method == 'POST': + # insert + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_insert.json') + else: + # get or list call + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_insert( + self, method, url, body, headers): + """ Redirects from _zones_us_central1_a_instanceGroups """ + body = self.fixtures.load( + 'operations_operation_zones_us_central1_a_instanceGroups_insert.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_instanceGroups_myname_listInstances( + self, method, url, body, headers): + # POST + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_myname_listInstances.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_instanceGroups_myname_addInstances( + self, method, url, body, headers): + # POST + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_myname_addInstances.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances( + self, method, url, body, headers): + """ Redirects from _zones_us_central1_a_instanceGroups_myname_addInstances """ + body = self.fixtures.load( + 'operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_instanceGroups_myname_removeInstances( + self, method, url, body, headers): + # POST + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_myname_removeInstances.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances( + self, method, url, body, headers): + """ Redirects from _zones_us_central1_a_instanceGroups_myname_removeInstances """ + body = self.fixtures.load( + 'operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_instanceGroups_myname_setNamedPorts( + self, method, url, body, headers): + # POST + body = self.fixtures.load( + 'zones_us_central1_a_instanceGroups_myname_setNamedPorts.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts( + self, method, url, body, headers): + """ Redirects from _zones_us_central1_a_instanceGroups_myname_setNamedPorts """ + body = self.fixtures.load( + 'operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + if __name__ == '__main__': sys.exit(unittest.main())