diff --git a/CHANGES.rst b/CHANGES.rst index bb38bc214e..806d96ed3b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,19 @@ Common Compute ~~~~~~~ +- [Compute] Unify ``NodeDriver`` method signatures across drivers. + + Driver implementations now preserve the standard argument order and + optionality declared by ``NodeDriver``. Provider-specific arguments follow + the standard arguments and are optional. ``list_nodes`` is excluded because + its ``*args, **kwargs`` contract is intentionally unrestricted. + + This can be a backward-incompatible change for code which passes arguments + positionally to affected methods. + + (#2170) + [Miguel Caballer - @micafer] + - [SSH] Support paramiko 4 RSA key support has been removed as of paramiko 4, so only import it diff --git a/docs/development.rst b/docs/development.rst index f21d803132..f73e5bba36 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -177,10 +177,10 @@ For example: def list_nodes(self): pass - def list_images(self): + def list_images(self, location=None): pass - def create_node(self): + def create_node(self, name, size, image, location=None, auth=None): pass def reboot_node(self): diff --git a/docs/examples/compute/nttcis/Nodes_Create_mcp2_Customized.py b/docs/examples/compute/nttcis/Nodes_Create_mcp2_Customized.py index 642b840cd8..6160963f48 100644 --- a/docs/examples/compute/nttcis/Nodes_Create_mcp2_Customized.py +++ b/docs/examples/compute/nttcis/Nodes_Create_mcp2_Customized.py @@ -17,9 +17,10 @@ vlans = driver.ex_list_vlans() vlan = [v for v in vlans if v.name == vlan_name][0] new_node = driver.create_node( - "Suse_12", - image, - psswd, + name="Suse_12", + size=None, + image=image, + auth=psswd, ex_description="Customized_Suse server", ex_network_domain=net_domain, ex_primary_nic_vlan=vlan, diff --git a/docs/examples/compute/onapp/functionality.py b/docs/examples/compute/onapp/functionality.py index 66c944d123..9e40815b8a 100644 --- a/docs/examples/compute/onapp/functionality.py +++ b/docs/examples/compute/onapp/functionality.py @@ -31,6 +31,8 @@ node = driver.create_node( name=name, + size=None, + image=None, ex_memory=memory, ex_cpus=cpus, ex_cpu_shares=cpu_shares, diff --git a/docs/examples/compute/vmware_vcloud_1.5.py b/docs/examples/compute/vmware_vcloud_1.5.py index 327044b47d..493feffb44 100644 --- a/docs/examples/compute/vmware_vcloud_1.5.py +++ b/docs/examples/compute/vmware_vcloud_1.5.py @@ -26,20 +26,23 @@ image = [i for i in images if i.name == "natty-server-cloudimg-amd64"][0] # Create node with minimum set of parameters -node = driver.create_node(name="test node 1", image=image) +node = driver.create_node(name="test node 1", size=None, image=image) # Destroy the node driver.destroy_node(node) # Create node without deploying and powering it on -node = driver.create_node(name="test node 2", image=image, ex_deploy=False) +node = driver.create_node(name="test node 2", size=None, image=image, ex_deploy=False) # Create node with custom CPU & Memory values -node = driver.create_node(name="test node 3", image=image, ex_vm_cpu=3, ex_vm_memory=1024) +node = driver.create_node( + name="test node 3", size=None, image=image, ex_vm_cpu=3, ex_vm_memory=1024 +) # Create node with customised networking parameters (eg. for OVF # imported images) node = driver.create_node( name="test node 4", + size=None, image=image, ex_vm_network="your vm net name", ex_network="your org net name", @@ -48,9 +51,12 @@ ) # Create node in a custom virtual data center -node = driver.create_node(name="test node 4", image=image, ex_vdc="your vdc name") +node = driver.create_node(name="test node 4", size=None, image=image, ex_vdc="your vdc name") # Create node with guest OS customisation script to be run at first boot node = driver.create_node( - name="test node 5", image=image, ex_vm_script="filesystem path to your script" + name="test node 5", + size=None, + image=image, + ex_vm_script="filesystem path to your script", ) diff --git a/docs/upgrade_notes.rst b/docs/upgrade_notes.rst index 097cbf0855..1214f07d44 100644 --- a/docs/upgrade_notes.rst +++ b/docs/upgrade_notes.rst @@ -5,6 +5,32 @@ This page describes how to upgrade from a previous version to a new version which contains backward incompatible or semi-incompatible changes and how to preserve the old behavior when this is possible. +Libcloud 3.9.2 +-------------- + +Compute driver implementations now use the same argument order and optionality +as the corresponding methods on ``NodeDriver``. Provider-specific arguments +are optional and appear after the standard arguments. ``list_nodes`` is not +affected because its ``*args, **kwargs`` contract is intentionally +unrestricted. + +This affects methods including ``create_node``, ``list_sizes``, +``list_images``, image management, key pair management, and block storage +operations. Code which passes arguments positionally may need to be updated. +Using keyword arguments for provider-specific options is recommended. + +For example, the standard node creation signature is: + +.. sourcecode:: python + + def create_node(self, name, size, image, location=None, auth=None, ...) + +The standard volume creation signature is: + +.. sourcecode:: python + + def create_volume(self, size, name, location=None, snapshot=None, ...) + Libcloud 3.9.0 -------------- diff --git a/libcloud/compute/drivers/abiquo.py b/libcloud/compute/drivers/abiquo.py index 73bd208c0a..305c618a10 100644 --- a/libcloud/compute/drivers/abiquo.py +++ b/libcloud/compute/drivers/abiquo.py @@ -76,7 +76,7 @@ def __init__(self, user_id, secret, endpoint, **kwargs): super().__init__(key=user_id, secret=secret, secure=False, host=None, port=None, **kwargs) self.ex_populate_cache() - def create_node(self, image, name=None, size=None, location=None, ex_group_name=None): + def create_node(self, name, size, image, location=None, auth=None, ex_group_name=None): """ Create a new node instance in Abiquo diff --git a/libcloud/compute/drivers/azure.py b/libcloud/compute/drivers/azure.py index d424f357d5..81388f14b5 100644 --- a/libcloud/compute/drivers/azure.py +++ b/libcloud/compute/drivers/azure.py @@ -334,7 +334,10 @@ def __init__(self, subscription_id=None, key_file=None, **kwargs): self.follow_redirects = kwargs.get("follow_redirects", True) super().__init__(self.subscription_id, self.key_file, secure=True, **kwargs) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ Lists all sizes @@ -481,7 +484,9 @@ def create_node( name, size, image, - ex_cloud_service_name, + location=None, + auth=None, + ex_cloud_service_name=None, ex_storage_service_name=None, ex_new_deployment=False, ex_deployment_slot="Production", @@ -490,7 +495,6 @@ def create_node( ex_custom_data=None, ex_virtual_network_name=None, ex_network_config=None, - auth=None, **kwargs, ): """ @@ -1004,19 +1008,40 @@ def ex_destroy_storage_service(self, name): Functions not implemented """ - def create_volume_snapshot(self): + def create_volume_snapshot( + self, + volume, + name=None, + ): raise NotImplementedError("You cannot create snapshots of " "Azure VMs at this time.") - def attach_volume(self): + def attach_volume( + self, + node, + volume, + device=None, + ): raise NotImplementedError("attach_volume is not supported " "at this time.") - def create_volume(self): + def create_volume( + self, + size, + name, + location=None, + snapshot=None, + ): raise NotImplementedError("create_volume is not supported " "at this time.") - def detach_volume(self): + def detach_volume( + self, + volume, + ): raise NotImplementedError("detach_volume is not supported " "at this time.") - def destroy_volume(self): + def destroy_volume( + self, + volume, + ): raise NotImplementedError("destroy_volume is not supported " "at this time.") """ diff --git a/libcloud/compute/drivers/azure_arm.py b/libcloud/compute/drivers/azure_arm.py index 852c350e5d..68868b254e 100644 --- a/libcloud/compute/drivers/azure_arm.py +++ b/libcloud/compute/drivers/azure_arm.py @@ -505,11 +505,11 @@ def create_node( name, size, image, - auth, - ex_resource_group, + location=None, + auth=None, + ex_resource_group=None, ex_storage_account=None, ex_blob_container="vhds", - location=None, ex_user_name="azureuser", ex_network=None, ex_subnet=None, @@ -1082,10 +1082,10 @@ def attach_volume( self, node, volume, + device=None, ex_lun=None, ex_vhd_uri=None, ex_vhd_create=False, - **ex_kwargs, ): """ Attach a volume to node. diff --git a/libcloud/compute/drivers/brightbox.py b/libcloud/compute/drivers/brightbox.py index 67ee9bcc26..655ee7242c 100644 --- a/libcloud/compute/drivers/brightbox.py +++ b/libcloud/compute/drivers/brightbox.py @@ -168,7 +168,9 @@ def _put(self, path, data={}): headers = {"Content-Type": "application/json"} return self.connection.request(path, data=data, headers=headers, method="PUT") - def create_node(self, name, size, image, location=None, ex_userdata=None, ex_servergroup=None): + def create_node( + self, name, size, image, location=None, auth=None, ex_userdata=None, ex_servergroup=None + ): """Create a new Brightbox node Reference: https://api.gb1.brightbox.com/1.0/#server_create_server @@ -216,7 +218,10 @@ def list_images(self, location=None): data = self.connection.request("/%s/images" % self.api_version).object return list(map(self._to_image, data)) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): data = self.connection.request("/%s/server_types" % self.api_version).object return list(map(self._to_size, data)) diff --git a/libcloud/compute/drivers/cloudscale.py b/libcloud/compute/drivers/cloudscale.py index 7698bd65d2..d16a9b3101 100644 --- a/libcloud/compute/drivers/cloudscale.py +++ b/libcloud/compute/drivers/cloudscale.py @@ -94,13 +94,19 @@ def list_nodes(self): """ return self._list_resources("/v1/servers", self._to_node) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ Lists all available sizes. On cloudscale these are known as flavors. """ return self._list_resources("/v1/flavors", self._to_size) - def list_images(self): + def list_images( + self, + location=None, + ): """ List all images. @@ -110,7 +116,7 @@ def list_images(self): """ return self._list_resources("/v1/images", self._to_image) - def create_node(self, name, size, image, location=None, ex_create_attr=None): + def create_node(self, name, size, image, location=None, auth=None, ex_create_attr=None): """ Create a node. diff --git a/libcloud/compute/drivers/cloudsigma.py b/libcloud/compute/drivers/cloudsigma.py index a012b480bf..00c2057d50 100644 --- a/libcloud/compute/drivers/cloudsigma.py +++ b/libcloud/compute/drivers/cloudsigma.py @@ -316,6 +316,8 @@ def create_node( name, size, image, + location=None, + auth=None, smp="auto", nic_model="e1000", vnc_password=None, @@ -1178,7 +1180,10 @@ def list_nodes(self, ex_tag=None): return nodes - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ List available sizes. """ @@ -1200,7 +1205,10 @@ def list_sizes(self): return sizes - def list_images(self): + def list_images( + self, + location=None, + ): """ Return a list of available pre-installed library drives. @@ -1223,6 +1231,8 @@ def create_node( name, size, image, + location=None, + auth=None, ex_metadata=None, ex_vnc_password=None, ex_avoid=None, @@ -1622,7 +1632,15 @@ def ex_create_drive(self, name, size, media="disk", ex_avoid=None): return drive - def create_volume(self, name, size, media="disk", ex_avoid=None): + def create_volume( + self, + size, + name, + location=None, + snapshot=None, + media="disk", + ex_avoid=None, + ): return self.ex_create_drive(name=name, size=size, media=media, ex_avoid=ex_avoid) def ex_clone_drive(self, drive, name=None, ex_avoid=None): @@ -1729,7 +1747,12 @@ def ex_attach_drive(self, node, drive): return response.status == 200 - def attach_volume(self, node, volume): + def attach_volume( + self, + node, + volume, + device=None, + ): return self.ex_attach_drive(node=node, drive=volume) def ex_detach_drive(self, node, drive): @@ -1740,7 +1763,11 @@ def ex_detach_drive(self, node, drive): return response.status == 200 - def detach_volume(self, node, volume): + def detach_volume( + self, + volume, + node=None, + ): return self.ex_detach_drive(node=node, drive=volume) def ex_get_drive(self, drive_id): @@ -1765,7 +1792,11 @@ def ex_destroy_drive(self, drive): return response.status == httplib.NO_CONTENT - def destroy_volume(self, drive): + def destroy_volume( + self, + volume, + ): + drive = volume return self.ex_destroy_drive(drive=drive) # Firewall policies extension methods @@ -2154,7 +2185,10 @@ def list_key_pairs(self): return keys - def get_key_pair(self, key_uuid): + def get_key_pair( + self, + name, + ): """ Retrieve a single key pair. @@ -2163,6 +2197,7 @@ def get_key_pair(self, key_uuid): :rtype: :class:`.KeyPair` """ + key_uuid = name action = "/keypairs/%s/" % (key_uuid) response = self.connection.request(action=action, method="GET").object diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py index 69605c2f1e..bec45459c1 100644 --- a/libcloud/compute/drivers/cloudstack.py +++ b/libcloud/compute/drivers/cloudstack.py @@ -1601,6 +1601,7 @@ def create_node( size, image, location=None, + auth=None, networks=None, project=None, diskoffering=None, diff --git a/libcloud/compute/drivers/digitalocean.py b/libcloud/compute/drivers/digitalocean.py index 761199dcf3..e1d75c8348 100644 --- a/libcloud/compute/drivers/digitalocean.py +++ b/libcloud/compute/drivers/digitalocean.py @@ -100,7 +100,10 @@ class DigitalOcean_v2_NodeDriver(DigitalOcean_v2_BaseDriver, DigitalOceanNodeDri EX_CREATE_ATTRIBUTES = ["backups", "ipv6", "private_networking", "tags", "ssh_keys"] - def list_images(self): + def list_images( + self, + location=None, + ): data = self._paginated_request("/v2/images", "images") return list(map(self._to_image, data)) @@ -151,7 +154,8 @@ def create_node( name, size, image, - location, + location=None, + auth=None, ex_create_attr=None, ex_ssh_key_ids=None, ex_user_data=None, @@ -230,7 +234,12 @@ def reboot_node(self, node): ) return res.status == httplib.CREATED - def create_image(self, node, name): + def create_image( + self, + node, + name, + description=None, + ): """ Create an image from a Node. @@ -239,8 +248,11 @@ def create_image(self, node, name): :param node: Node to use as base for image :type node: :class:`Node` - :param node: Name for image - :type node: ``str`` + :param name: Name for image + :type name: ``str`` + + :param description: Optional image description. + :type description: ``str`` :rtype: ``bool`` """ @@ -250,16 +262,20 @@ def create_image(self, node, name): ) return res.status == httplib.CREATED - def delete_image(self, image): + def delete_image( + self, + node_image, + ): """Delete an image for node. @inherits: :class:`NodeDriver.delete_image` - :param image: the image to be deleted - :type image: :class:`NodeImage` + :param node_image: the image to be deleted + :type node_image: :class:`NodeImage` :rtype: ``bool`` """ + image = node_image res = self.connection.request("/v2/images/%s" % (image.id), method="DELETE") return res.status == httplib.NO_CONTENT @@ -373,13 +389,17 @@ def create_key_pair(self, name, public_key=""): return self._to_key_pair(data=data) - def delete_key_pair(self, key): + def delete_key_pair( + self, + key_pair, + ): """ Delete an existing SSH key. - :param key: SSH key (required) - :type key: :class:`KeyPair` + :param key_pair: SSH key (required) + :type key_pair: :class:`KeyPair` """ + key = key_pair key_id = key.extra["id"] res = self.connection.request("/v2/account/keys/%s" % (key_id), method="DELETE") return res.status == httplib.NO_CONTENT @@ -494,7 +514,11 @@ def detach_volume(self, volume): return all([r.status == httplib.ACCEPTED for r in responses]) - def create_volume_snapshot(self, volume, name): + def create_volume_snapshot( + self, + volume, + name=None, + ): """ Create a new volume snapshot. diff --git a/libcloud/compute/drivers/dimensiondata.py b/libcloud/compute/drivers/dimensiondata.py index 31660cec35..3dfdd34dce 100644 --- a/libcloud/compute/drivers/dimensiondata.py +++ b/libcloud/compute/drivers/dimensiondata.py @@ -272,8 +272,10 @@ def _create_node_mcp1( def create_node( self, name, + size, image, - auth, + location=None, + auth=None, ex_network_domain=None, ex_primary_nic_private_ipv4=None, ex_primary_nic_vlan=None, diff --git a/libcloud/compute/drivers/dummy.py b/libcloud/compute/drivers/dummy.py index 04889789d7..a3fee5fd8a 100644 --- a/libcloud/compute/drivers/dummy.py +++ b/libcloud/compute/drivers/dummy.py @@ -287,7 +287,7 @@ def list_locations(self): NodeLocation(id=3, name="Island Datacenter", country="FJ", driver=self), ] - def create_node(self, name, size, image): + def create_node(self, name, size, image, location=None, auth=None): """ Creates a dummy node; the node id is equal to the number of nodes in the node list diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index 770a30d15a..8d62c78739 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -1876,7 +1876,12 @@ def create_volume( return volume - def attach_volume(self, node, volume, device): + def attach_volume( + self, + node, + volume, + device=None, + ): params = { "Action": "AttachVolume", "VolumeId": volume.id, @@ -2018,7 +2023,13 @@ def delete_key_pair(self, key_pair): return self._get_boolean(res) - def copy_image(self, image, source_region, name=None, description=None): + def copy_image( + self, + source_region, + node_image, + name, + description=None, + ): """ Copy an Amazon Machine Image from the specified source region to the current region. @@ -2028,8 +2039,8 @@ def copy_image(self, image, source_region, name=None, description=None): :param source_region: The region where the image resides :type source_region: ``str`` - :param image: Instance of class NodeImage - :type image: :class:`NodeImage` + :param node_image: Instance of class NodeImage + :type node_image: :class:`NodeImage` :param name: The name of the new image :type name: ``str`` @@ -2040,6 +2051,7 @@ def copy_image(self, image, source_region, name=None, description=None): :return: Instance of class ``NodeImage`` :rtype: :class:`NodeImage` """ + image = node_image params = { "Action": "CopyImage", "SourceRegion": source_region, @@ -2110,17 +2122,21 @@ def create_image(self, node, name, description=None, reboot=False, block_device_ return image - def delete_image(self, image): + def delete_image( + self, + node_image, + ): """ Deletes an image at Amazon given a NodeImage object @inherits: :class:`NodeDriver.delete_image` - :param image: Instance of ``NodeImage`` - :type image: :class: `NodeImage` + :param node_image: Instance of ``NodeImage`` + :type node_image: :class: `NodeImage` :rtype: ``bool`` """ + image = node_image params = {"Action": "DeregisterImage", "ImageId": image.id} response = self.connection.request(self.path, params=params).object @@ -5502,7 +5518,10 @@ def _to_size(self, el): extra={"cpu": int(cpu)}, ) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ Lists available nodes sizes. @@ -5641,7 +5660,7 @@ def __init__( super().__init__(key=key, secret=secret, secure=secure, host=host, port=port, **kwargs) - def create_node(self, **kwargs): + def create_node(self, name, size, image, location=None, auth=None, **kwargs): """ Creates a new Outscale node. The ex_iamprofile keyword is not supported. @@ -5680,7 +5699,14 @@ def create_node(self, **kwargs): """ if "ex_iamprofile" in kwargs: raise NotImplementedError("ex_iamprofile not implemented") - return super().create_node(**kwargs) + return super().create_node( + name=name, + size=size, + image=image, + location=location, + auth=auth, + **kwargs, + ) def ex_create_network(self, cidr_block, name=None): """ diff --git a/libcloud/compute/drivers/ecs.py b/libcloud/compute/drivers/ecs.py index 8fca7b4554..45f0501918 100644 --- a/libcloud/compute/drivers/ecs.py +++ b/libcloud/compute/drivers/ecs.py @@ -402,6 +402,7 @@ def create_node( name, size, image, + location=None, auth=None, ex_security_group_id=None, ex_description=None, diff --git a/libcloud/compute/drivers/equinixmetal.py b/libcloud/compute/drivers/equinixmetal.py index 2508e5fa6a..11e497927b 100644 --- a/libcloud/compute/drivers/equinixmetal.py +++ b/libcloud/compute/drivers/equinixmetal.py @@ -245,12 +245,19 @@ def list_locations(self): return list(map(self._to_location, data)) - def list_images(self): + def list_images( + self, + location=None, + ): data = self.connection.request("/metal/v1/operating-systems").object["operating_systems"] return list(map(self._to_image, data)) - def list_sizes(self, ex_project_id=None): + def list_sizes( + self, + location=None, + ex_project_id=None, + ): project_id = ( ex_project_id or self.project_id or (len(self.projects) and self.projects[0].id) ) @@ -269,7 +276,8 @@ def create_node( name, size, image, - location, + location=None, + auth=None, ex_project_id=None, ip_addresses=None, cloud_init=None, @@ -416,7 +424,11 @@ def list_key_pairs(self): return list(map(self._to_key_pairs, data)) - def create_key_pair(self, name, public_key): + def create_key_pair( + self, + name, + public_key=None, + ): """ Create a new SSH key. @@ -426,18 +438,25 @@ def create_key_pair(self, name, public_key): :param public_key: Valid public key string (required) :type public_key: ``str`` """ + if not public_key: + raise ValueError("public_key is required.") + params = {"label": name, "key": public_key} data = self.connection.request("/metal/v1/ssh-keys", method="POST", params=params).object return self._to_key_pairs(data) - def delete_key_pair(self, key): + def delete_key_pair( + self, + key_pair, + ): """ Delete an existing SSH key. - :param key: SSH key (required) - :type key: :class:`KeyPair` + :param key_pair: SSH key (required) + :type key_pair: :class:`KeyPair` """ + key = key_pair key_id = key.name res = self.connection.request("/metal/v1/ssh-keys/%s" % (key_id), method="DELETE") diff --git a/libcloud/compute/drivers/gandi.py b/libcloud/compute/drivers/gandi.py index 594b5e204f..b3543c85ed 100644 --- a/libcloud/compute/drivers/gandi.py +++ b/libcloud/compute/drivers/gandi.py @@ -230,7 +230,23 @@ def destroy_node(self, node): return True return False - def deploy_node(self, **kwargs): + def deploy_node( + self, + deploy, + ssh_username="root", + ssh_alternate_usernames=None, + ssh_port=22, + ssh_timeout=10, + ssh_key=None, + ssh_key_password=None, + auth=None, + timeout=300, + max_tries=3, + ssh_interface="public_ips", + at_exit_func=None, + wait_period=5, + **create_node_kwargs, + ): """ deploy_node is not implemented for gandi driver @@ -244,6 +260,7 @@ def create_node( size, image, location=None, + auth=None, login=None, password=None, inet_family=4, @@ -520,7 +537,11 @@ def attach_volume(self, node, volume, device=None): return True return False - def detach_volume(self, node, volume): + def detach_volume( + self, + volume, + node=None, + ): """ Detaches a volume from a node. diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py index bdb46dbdca..e9d77c6e3a 100644 --- a/libcloud/compute/drivers/gce.py +++ b/libcloud/compute/drivers/gce.py @@ -2678,7 +2678,12 @@ def ex_list_forwarding_rules(self, region=None, global_rules=False): return list_forwarding_rules - def list_images(self, ex_project=None, ex_include_deprecated=False): + def list_images( + self, + location=None, + ex_project=None, + ex_include_deprecated=False, + ): """ Return a list of image objects. If no project is specified, a list of all non-deprecated global and vendor images images is returned. By @@ -4441,6 +4446,7 @@ def create_node( size, image, location=None, + auth=None, ex_network="default", ex_subnetwork=None, ex_tags=None, @@ -6134,7 +6140,11 @@ def create_volume( return self.ex_get_volume(name, location) - def create_volume_snapshot(self, volume, name): + def create_volume_snapshot( + self, + volume, + name=None, + ): """ Create a snapshot of the provided Volume. diff --git a/libcloud/compute/drivers/gig_g8.py b/libcloud/compute/drivers/gig_g8.py index fa85b62eb9..3c0e80975c 100644 --- a/libcloud/compute/drivers/gig_g8.py +++ b/libcloud/compute/drivers/gig_g8.py @@ -168,11 +168,12 @@ def _location(self): def create_node( self, name, + size, image, - ex_network, - ex_description, - size=None, + location=None, auth=None, + ex_network=None, + ex_description=None, ex_create_attr=None, ex_expose_ssh=False, ): @@ -415,7 +416,10 @@ def ex_list_networks(self): networks.append(self._to_network(network)) return networks - def list_sizes(self): + def list_sizes( + self, + location=None, + ): # type () -> List[Size] """ Returns a list of node sizes as a cloud provider might have @@ -476,7 +480,10 @@ def destroy_node(self, node): self._api_request("/machines/delete", {"machineId": int(node.id)}) return True - def list_images(self): + def list_images( + self, + location=None, + ): # type () -> List[Image] """ Returns a list of images as a cloud provider might have @@ -497,7 +504,15 @@ def list_volumes(self): volumes.append(self._to_volume(disk)) return volumes - def create_volume(self, size, name, ex_description, ex_disk_type="D"): + def create_volume( + self, + size, + name, + location=None, + snapshot=None, + ex_description=None, + ex_disk_type="D", + ): # type (int, str, str, Optional[str]) -> StorageVolume """ Create volume @@ -508,12 +523,12 @@ def create_volume(self, size, name, ex_description, ex_disk_type="D"): :param name: Name of the volume :type name: ``str`` - :param description: Description of the volume - :type description: ``str`` + :param ex_description: Description of the volume + :type ex_description: ``str`` - :param disk_type: Type of the disk depending on the G8 - D for datadisk is always available - :type disk_type: ``str`` + :param ex_disk_type: Type of the disk depending on the G8. + ``D`` for a data disk is always available. + :type ex_disk_type: ``str`` :rtype: class:`StorageVolume` """ @@ -534,13 +549,22 @@ def destroy_volume(self, volume): self._api_request("/disks/delete", {"diskId": int(volume.id)}) return True - def attach_volume(self, node, volume): + def attach_volume( + self, + node, + volume, + device=None, + ): # type (Node, StorageVolume) -> bool params = {"machineId": int(node.id), "diskId": int(volume.id)} self._api_request("/machines/attachDisk", params) return True - def detach_volume(self, node, volume): + def detach_volume( + self, + volume, + node=None, + ): # type (Node, StorageVolume) -> bool params = {"machineId": int(node.id), "diskId": int(volume.id)} self._api_request("/machines/detachDisk", params) diff --git a/libcloud/compute/drivers/gridscale.py b/libcloud/compute/drivers/gridscale.py index 64d5f93e0e..263a83be82 100644 --- a/libcloud/compute/drivers/gridscale.py +++ b/libcloud/compute/drivers/gridscale.py @@ -194,7 +194,10 @@ def ex_list_ips(self): ips.append(ip) return ips - def list_images(self): + def list_images( + self, + location=None, + ): """ List images. @@ -208,7 +211,9 @@ def list_images(self): templates.append(template) return sorted(templates, key=lambda sort: sort.name) - def create_node(self, name, size, image, location, ex_ssh_key_ids=None, **kwargs): + def create_node( + self, name, size, image, location=None, auth=None, ex_ssh_key_ids=None, **kwargs + ): """ Create a simple node with a name, cores, memory at the designated location. @@ -377,7 +382,11 @@ def _create_volume_from_template(self, size, name, location=None, template=None) ) ) - def create_volume_snapshot(self, volume, name): + def create_volume_snapshot( + self, + volume, + name=None, + ): """ Creates a snapshot of the current state of your volume, you can rollback to. @@ -404,7 +413,12 @@ def create_volume_snapshot(self, volume, name): ) ) - def create_image(self, node, name): + def create_image( + self, + node, + name, + description=None, + ): """ Creates an image from a node object. @@ -723,7 +737,12 @@ def ex_link_isoimage_to_node(self, node, isoimage): ) return result - def attach_volume(self, node, volume): + def attach_volume( + self, + node, + volume, + device=None, + ): """ Attaches volume to node. diff --git a/libcloud/compute/drivers/kamatera.py b/libcloud/compute/drivers/kamatera.py index 6013ef8935..645b7ce171 100644 --- a/libcloud/compute/drivers/kamatera.py +++ b/libcloud/compute/drivers/kamatera.py @@ -88,7 +88,10 @@ def list_locations(self): for datacenter in response.object ] - def list_sizes(self, location): + def list_sizes( + self, + location=None, + ): """ List predefined sizes for the given location. @@ -111,7 +114,10 @@ def list_sizes(self, location): for size in response.object ] - def list_images(self, location): + def list_images( + self, + location=None, + ): """ List available disk images. @@ -133,7 +139,7 @@ def create_node( name, size, image, - location, + location=None, auth=None, ex_networks=None, ex_dailybackup=False, diff --git a/libcloud/compute/drivers/ktucloud.py b/libcloud/compute/drivers/ktucloud.py index 3ec56867e2..e6d4268100 100644 --- a/libcloud/compute/drivers/ktucloud.py +++ b/libcloud/compute/drivers/ktucloud.py @@ -60,7 +60,7 @@ def list_sizes(self, location=None): sizes.append(NodeSize(diskofferingid, sz["diskofferingdesc"], 0, 0, 0, 0, self)) return sizes - def create_node(self, name, size, image, location=None, ex_usageplantype="hourly"): + def create_node(self, name, size, image, location=None, auth=None, ex_usageplantype="hourly"): params = { "displayname": name, "serviceofferingid": image.id, diff --git a/libcloud/compute/drivers/kubevirt.py b/libcloud/compute/drivers/kubevirt.py index b011c3a449..c04291f6f5 100644 --- a/libcloud/compute/drivers/kubevirt.py +++ b/libcloud/compute/drivers/kubevirt.py @@ -815,8 +815,8 @@ def _create_node_image(vm, image): # type: (dict, NodeImage) -> None def create_node( self, name, # type: str - size=None, # type: Optional[NodeSize] - image=None, # type: Optional[Union[NodeImage, str]] + size, # type: NodeSize + image, # type: Union[NodeImage, str] location=None, # type: Optional[NodeLocation] auth=None, # type: Optional[Union[NodeAuthSSHKey, NodeAuthPassword]] ex_cpu=None, # type: Optional[Union[int, str]] @@ -1204,6 +1204,7 @@ def create_volume( size, name, location=None, + snapshot=None, ex_storage_class_name="", ex_volume_mode="Filesystem", ex_access_mode="ReadWriteOnce", @@ -1216,9 +1217,9 @@ def create_volume( :param size: The size in Gigabytes :type size: `int` - :param volume_type: This is the type of volume to be created that is - dependent on the underlying cloud where Kubernetes - is deployed. K8s is supporting the following types: + :param ex_volume_type: This is the type of volume to be created that is + dependent on the underlying cloud where Kubernetes + is deployed. K8s is supporting the following types: -gcePersistentDisk -awsElasticBlockStore -azureFile @@ -1243,19 +1244,20 @@ def create_volume( where type is one of the above and key1, key2... are type specific keys and their corresponding values. eg: {nsf: {server: "172.0.0.0", path: "/tmp"}} {awsElasticBlockStore: {fsType: 'ext4', volumeID: "1234"}} - :type volume_type: `str` - - :param volume_params: A dict with the key:value that the - volume_type needs. - This parameter is a dict in the form - {key1:value1, key2:value2,...}, - where type is one of the above and key1, key2... - are type specific keys and - their corresponding values. - eg: for nsf volume_type - {server: "172.0.0.0", path: "/tmp"} - for awsElasticBlockStore volume_type - {fsType: 'ext4', volumeID: "1234"} + :type ex_volume_type: `str` + + :param ex_volume_params: A dict with the key:value that the + ``ex_volume_type`` needs. + This parameter is a dict in the form + {key1:value1, key2:value2,...}, + where type is one of the above and key1, key2... + are type specific keys and + their corresponding values. + eg: for nfs ``ex_volume_type`` + {server: "172.0.0.0", path: "/tmp"} + for awsElasticBlockStore ``ex_volume_type`` + {fsType: 'ext4', volumeID: "1234"} + :type ex_volume_params: ``dict`` """ if ex_dynamic: @@ -1518,7 +1520,11 @@ def attach_volume(self, node, volume, device="disk", ex_bus="virtio", ex_name=No except Exception: raise - def detach_volume(self, volume, ex_node): + def detach_volume( + self, + volume, + ex_node=None, + ): """ Detaches a volume from a node but the node must be given since a PVC can have more than one VMI's pointing to it diff --git a/libcloud/compute/drivers/linode.py b/libcloud/compute/drivers/linode.py index b8da22ea49..ac0e390c8d 100644 --- a/libcloud/compute/drivers/linode.py +++ b/libcloud/compute/drivers/linode.py @@ -126,7 +126,10 @@ def list_nodes(self): data = self._paginated_request("/v4/linode/instances", "data") return [self._to_node(obj) for obj in data] - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ Returns a list of Linode Types @@ -135,7 +138,10 @@ def list_sizes(self): data = self._paginated_request("/v4/linode/types", "data") return [self._to_size(obj) for obj in data] - def list_images(self): + def list_images( + self, + location=None, + ): """ Returns a list of images @@ -254,7 +260,6 @@ def reboot_node(self, node): def create_node( self, - location, # Previously, the following 3 parameters did not match the rest of the libcloud # codebase drivers. They should be in the same order as other compute drivers. # Previously, it looked like this: @@ -270,6 +275,8 @@ def create_node( name, # Can be None size, # Can be None image, # Can be None + location=None, + auth=None, root_pass=None, ex_authorized_keys=None, ex_authorized_users=None, @@ -522,7 +529,15 @@ def list_volumes(self): return [self._to_volume(obj) for obj in data] - def create_volume(self, name, size, location=None, node=None, tags=None): + def create_volume( + self, + size, + name, + location=None, + snapshot=None, + node=None, + tags=None, + ): """Creates a volume and optionally attaches it to a node. :param name: The name to be given to volume (required).\ @@ -540,8 +555,8 @@ def create_volume(self, name, size, location=None, node=None, tags=None): Required if node is not given. :type location: :class:`NodeLocation` - :keyword volume: Node to attach the volume to - :type volume: :class:`Node` + :keyword node: Node to attach the volume to + :type node: :class:`Node` :keyword tags: tags to apply to volume :type tags: `list` of `str` @@ -578,7 +593,13 @@ def create_volume(self, name, size, location=None, node=None, tags=None): ).object return self._to_volume(response) - def attach_volume(self, node, volume, persist_across_boots=True): + def attach_volume( + self, + node, + volume, + device=None, + persist_across_boots=True, + ): """Attaches a volume to a node. Volume and node must be located in the same region @@ -705,24 +726,33 @@ def ex_get_volume(self, volume_id): response = self.connection.request("/v4/volumes/%s" % volume_id).object return self._to_volume(response) - def get_image(self, image): + def get_image( + self, + image_id, + ): """ Lookup a Linode image - :param image: The name to image to be looked up (required).\ - :type name: `str` + :param image_id: The ID of the image to look up (required). + :type image_id: ``str`` :rtype: :class: `NodeImage` """ + image = image_id response = self.connection.request("/v4/images/%s" % image, method="GET") return self._to_image(response.object) - def create_image(self, disk, name=None, description=None): + def create_image( + self, + node, + name, + description=None, + ): """Creates a private image from a LinodeDisk. Images are limited to three per account. - :param disk: LinodeDisk to create the image from (required) - :type disk: :class:`LinodeDisk` + :param node: LinodeDisk to create the image from (required) + :type node: :class:`LinodeDisk` :keyword name: A name for the image.\ Defaults to the name of the disk \ @@ -736,6 +766,7 @@ def create_image(self, disk, name=None, description=None): :rtype: :class:`NodeImage` """ + disk = node if not isinstance(disk, LinodeDisk): raise LinodeExceptionV4("Invalid disk instance") @@ -746,14 +777,18 @@ def create_image(self, disk, name=None, description=None): ).object return self._to_image(response) - def delete_image(self, image): + def delete_image( + self, + node_image, + ): """Deletes a private image - :param image: NodeImage to delete (required) - :type image: :class:`NodeImage` + :param node_image: NodeImage to delete (required) + :type node_image: :class:`NodeImage` :rtype: ``bool`` """ + image = node_image if not isinstance(image, NodeImage): raise LinodeExceptionV4("Invalid image instance") diff --git a/libcloud/compute/drivers/maxihost.py b/libcloud/compute/drivers/maxihost.py index c05d2efcef..6504bad577 100644 --- a/libcloud/compute/drivers/maxihost.py +++ b/libcloud/compute/drivers/maxihost.py @@ -35,13 +35,19 @@ class MaxihostNodeDriver(NodeDriver): name = "Maxihost" website = "https://www.maxihost.com/" - def create_node(self, name, size, image, location, ex_ssh_key_ids=None): + def create_node(self, name, size, image, location=None, auth=None, ex_ssh_key_ids=None): """ Create a node. + :param location: Location where the node will be created. (required) + :type location: :class:`NodeLocation` + :return: The newly created node. :rtype: :class:`Node` """ + if location is None: + raise ValueError("location is required.") + attr = { "hostname": name, "plan": size.id, @@ -156,7 +162,10 @@ def _to_location(self, data): country = data.get("location").get("country", "") return NodeLocation(id=data["slug"], name=name, country=country, driver=self) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ List sizes """ @@ -185,7 +194,10 @@ def _to_size(self, data): extra=extra, ) - def list_images(self): + def list_images( + self, + location=None, + ): """ List images """ @@ -214,7 +226,11 @@ def list_key_pairs(self): data = self.connection.request("/account/keys") return list(map(self._to_key_pair, data.object["ssh_keys"])) - def create_key_pair(self, name, public_key): + def create_key_pair( + self, + name, + public_key=None, + ): """ Create a new SSH key. @@ -224,6 +240,9 @@ def create_key_pair(self, name, public_key): :param public_key: base64 encoded public key string (required) :type public_key: ``str`` """ + if not public_key: + raise ValueError("public_key is required.") + attr = {"name": name, "public_key": public_key} res = self.connection.request("/account/keys", method="POST", data=json.dumps(attr)) diff --git a/libcloud/compute/drivers/nttcis.py b/libcloud/compute/drivers/nttcis.py index 468cfd1607..554f9b31e2 100644 --- a/libcloud/compute/drivers/nttcis.py +++ b/libcloud/compute/drivers/nttcis.py @@ -282,8 +282,10 @@ def _create_node_mcp1( def create_node( self, name, + size, image, - auth, + location=None, + auth=None, ex_network_domain=None, ex_primary_nic_private_ipv4=None, ex_primary_nic_vlan=None, diff --git a/libcloud/compute/drivers/onapp.py b/libcloud/compute/drivers/onapp.py index 307d71d1f7..76ab8fd069 100644 --- a/libcloud/compute/drivers/onapp.py +++ b/libcloud/compute/drivers/onapp.py @@ -142,13 +142,17 @@ class OnAppNodeDriver(NodeDriver): def create_node( self, name, - ex_memory, - ex_cpus, - ex_cpu_shares, - ex_hostname, - ex_template_id, - ex_primary_disk_size, - ex_swap_disk_size, + size, + image, + location=None, + auth=None, + ex_memory=None, + ex_cpus=None, + ex_cpu_shares=None, + ex_hostname=None, + ex_template_id=None, + ex_primary_disk_size=None, + ex_swap_disk_size=None, ex_required_virtual_machine_build=1, ex_required_ip_address_assignment=1, **kwargs, @@ -223,7 +227,10 @@ def list_nodes(self): nodes.append(self._to_node(vm["virtual_machine"])) return nodes - def list_images(self): + def list_images( + self, + location=None, + ): """ List all images @@ -283,7 +290,10 @@ def import_key_pair_from_string(self, name, key_material): ) return self._to_key_pair(response.object["ssh_key"]) - def delete_key_pair(self, key): + def delete_key_pair( + self, + key_pair, + ): """ Delete an existing key pair. @@ -293,6 +303,7 @@ def delete_key_pair(self, key): :return: True on success :rtype: ``bool`` """ + key = key_pair key_id = key.name response = self.connection.request("/settings/ssh_keys/%s.json" % key_id, method="DELETE") return response.status == httplib.NO_CONTENT diff --git a/libcloud/compute/drivers/opennebula.py b/libcloud/compute/drivers/opennebula.py index b67c9fa988..d4e2d16955 100644 --- a/libcloud/compute/drivers/opennebula.py +++ b/libcloud/compute/drivers/opennebula.py @@ -339,7 +339,7 @@ def __new__(cls, key, secret=None, api_version=DEFAULT_API_VERSION, **kwargs): return super().__new__(cls) - def create_node(self, name, size, image, networks=None): + def create_node(self, name, size, image, location=None, auth=None, networks=None): """ Create a new OpenNebula node. @@ -704,7 +704,7 @@ class OpenNebula_2_0_NodeDriver(OpenNebulaNodeDriver): name = "OpenNebula (v2.0 - v2.2)" - def create_node(self, name, size, image, networks=None, context=None): + def create_node(self, name, size, image, location=None, auth=None, networks=None, context=None): """ Create a new OpenNebula node. @@ -1207,7 +1207,12 @@ def destroy_volume(self, volume): return resp.status == httplib.NO_CONTENT - def attach_volume(self, node, volume, device): + def attach_volume( + self, + node, + volume, + device=None, + ): action = ET.Element("ACTION") perform = ET.SubElement(action, "PERFORM") diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index e9d5104d9b..21a613a2c0 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -663,6 +663,8 @@ def create_node( name, size, image, + location=None, + auth=None, ex_metadata=None, ex_files=None, ex_shared_ip_group=None, @@ -1126,16 +1128,20 @@ def create_image(self, node, name, description=None, reboot=True): self.connection.request("/images", method="POST", data=ET.tostring(image_elm)).object ) - def delete_image(self, image): + def delete_image( + self, + node_image, + ): """Delete an image for node. @inherits: :class:`NodeDriver.delete_image` - :param image: the image to be deleted - :type image: :class:`NodeImage` + :param node_image: the image to be deleted + :type node_image: :class:`NodeImage` :rtype: ``bool`` """ + image = node_image uri = "/images/%s" % image.id resp = self.connection.request(uri, method="DELETE") return resp.status == httplib.NO_CONTENT @@ -1425,7 +1431,9 @@ def create_node( self, name, size, - image=None, + image, + location=None, + auth=None, ex_keyname=None, ex_userdata=None, ex_config_drive=None, @@ -1778,7 +1786,13 @@ def ex_revert_resize(self, node): resp = self._node_action(node, "revertResize") return resp.status == httplib.ACCEPTED - def create_image(self, node, name, metadata=None): + def create_image( + self, + node, + name, + description=None, + metadata=None, + ): """ Creates a new image. @@ -2360,17 +2374,21 @@ def get_image(self, image_id): self.connection.request("/images/{}".format(image_id)).object["image"] ) - def delete_image(self, image): + def delete_image( + self, + node_image, + ): """ Delete a NodeImage @inherits: :class:`NodeDriver.delete_image` - :param image: image witch should be used - :type image: :class:`NodeImage` + :param node_image: image which should be used + :type node_image: :class:`NodeImage` :rtype: ``bool`` """ + image = node_image resp = self.connection.request("/images/{}".format(image.id), method="DELETE") return resp.status == httplib.NO_CONTENT diff --git a/libcloud/compute/drivers/outscale.py b/libcloud/compute/drivers/outscale.py index 1fd5a57457..1ba121eaff 100644 --- a/libcloud/compute/drivers/outscale.py +++ b/libcloud/compute/drivers/outscale.py @@ -314,8 +314,11 @@ def ex_detach_public_ip( def create_node( self, + name: str, + size, image: NodeImage, - name: str = None, + location=None, + auth=None, ex_dry_run: bool = False, ex_block_device_mapping: dict = None, ex_boot_on_creation: bool = True, @@ -833,10 +836,10 @@ def ex_update_node( def create_image( self, + node, + name, + description=None, ex_architecture: str = None, - node: Node = None, - name: str = None, - description: str = None, ex_block_device_mapping: dict = None, ex_no_reboot: bool = False, ex_root_device_name: str = None, @@ -991,6 +994,7 @@ def ex_create_image_export_task( def list_images( self, + location=None, account_aliases: List[str] = None, account_ids: List[str] = None, architectures: List[str] = None, @@ -1383,13 +1387,14 @@ def delete_key_pair(self, key_pair: KeyPair): def create_volume_snapshot( self, + volume, + name=None, ex_description: str = None, ex_dry_run: bool = False, ex_file_location: str = None, ex_snapshot_size: int = None, ex_source_region_name: str = None, ex_source_snapshot: VolumeSnapshot = None, - volume: StorageVolume = None, ): """ Create a new volume snapshot. @@ -1675,11 +1680,13 @@ def ex_update_snapshot( def create_volume( self, - ex_subregion_name: str, + size, + name, + location=None, + snapshot=None, + ex_subregion_name: str = None, ex_dry_run: bool = False, ex_iops: int = None, - size: int = None, - snapshot: VolumeSnapshot = None, ex_volume_type: str = None, ): """ @@ -1697,8 +1704,13 @@ def create_volume( the maximum allowed size for a volume is 14,901 GiB :type size: ``int`` + :param location: The location whose ID identifies the Subregion + in which to create the volume. Required when + ``ex_subregion_name`` is not provided. + :type location: :class:`NodeLocation` + :param ex_subregion_name: The Subregion in which you want to - create the volume. + create the volume. Takes precedence over ``location``. :type ex_subregion_name: ``str`` :param ex_volume_type: the type of volume you want to create (io1 @@ -1714,6 +1726,12 @@ def create_volume( :return: the created volume :rtype: ``dict`` """ + if ex_subregion_name is None: + if location is None: + raise ValueError("location or ex_subregion_name is required.") + + ex_subregion_name = location.id + data = {"DryRun": ex_dry_run, "SubregionName": ex_subregion_name} if ex_iops is not None: data.update({"Iops": ex_iops}) diff --git a/libcloud/compute/drivers/ovh.py b/libcloud/compute/drivers/ovh.py index edeff5812b..9bf73fc1cf 100644 --- a/libcloud/compute/drivers/ovh.py +++ b/libcloud/compute/drivers/ovh.py @@ -120,7 +120,7 @@ def ex_get_node(self, node_id): return self._to_node(response.object) - def create_node(self, name, image, size, location, ex_keyname=None): + def create_node(self, name, size, image, location=None, auth=None, ex_keyname=None): """ Create a new node @@ -265,7 +265,12 @@ def get_key_pair(self, name, ex_location=None): return keys[0] - def import_key_pair_from_string(self, name, key_material, ex_location): + def import_key_pair_from_string( + self, + name, + key_material, + ex_location=None, + ): """ Import a new public key from string. @@ -298,7 +303,7 @@ def create_volume( self, size, name, - location, + location=None, snapshot=None, ex_volume_type="classic", ex_description=None, diff --git a/libcloud/compute/drivers/rimuhosting.py b/libcloud/compute/drivers/rimuhosting.py index 60bce9f6f9..7808d63cf2 100644 --- a/libcloud/compute/drivers/rimuhosting.py +++ b/libcloud/compute/drivers/rimuhosting.py @@ -227,6 +227,7 @@ def create_node( name, size, image, + location=None, auth=None, ex_billing_oid=None, ex_host_server_oid=None, diff --git a/libcloud/compute/drivers/scaleway.py b/libcloud/compute/drivers/scaleway.py index 753c20e144..7a9154ac4e 100644 --- a/libcloud/compute/drivers/scaleway.py +++ b/libcloud/compute/drivers/scaleway.py @@ -187,7 +187,11 @@ def list_locations(self): for location in SCALEWAY_LOCATION_DATA ] - def list_sizes(self, region=None): + def list_sizes( + self, + location=None, + region=None, + ): """ List available VM sizes. @@ -246,7 +250,11 @@ def _to_size(self, name, size, availability): extra=extra, ) - def list_images(self, region=None): + def list_images( + self, + location=None, + region=None, + ): """ List available VM images. @@ -261,7 +269,13 @@ def list_images(self, region=None): images = response["images"] return [self._to_image(image) for image in images] - def create_image(self, node, name, region=None): + def create_image( + self, + node, + name, + description=None, + region=None, + ): """ Create a VM image from an existing node's root volume. @@ -372,7 +386,17 @@ def _to_node(self, server): created_at=parse_date(server["creation_date"]), ) - def create_node(self, name, size, image, ex_volumes=None, ex_tags=None, region=None): + def create_node( + self, + name, + size, + image, + location=None, + auth=None, + ex_volumes=None, + ex_tags=None, + region=None, + ): """ Create a new node. @@ -544,7 +568,14 @@ def _to_snapshot(self, snapshot): extra=extra, ) - def create_volume(self, size, name, region=None): + def create_volume( + self, + size, + name, + location=None, + snapshot=None, + region=None, + ): """ Create a new volume. @@ -573,7 +604,12 @@ def create_volume(self, size, name, region=None): volume = response.object["volume"] return self._to_volume(volume) - def create_volume_snapshot(self, volume, name, region=None): + def create_volume_snapshot( + self, + volume, + name=None, + region=None, + ): """ Create snapshot from volume. diff --git a/libcloud/compute/drivers/upcloud.py b/libcloud/compute/drivers/upcloud.py index 86ba8665df..4419d9cea0 100644 --- a/libcloud/compute/drivers/upcloud.py +++ b/libcloud/compute/drivers/upcloud.py @@ -173,7 +173,10 @@ def list_sizes(self, location=None): location, ) - def list_images(self): + def list_images( + self, + location=None, + ): """ List available distributions. @@ -191,7 +194,7 @@ def create_node( name, size, image, - location, + location=None, auth=None, ex_hostname="localhost", ex_username="root", diff --git a/libcloud/compute/drivers/vcl.py b/libcloud/compute/drivers/vcl.py index 299cf5ffd0..533733f75a 100644 --- a/libcloud/compute/drivers/vcl.py +++ b/libcloud/compute/drivers/vcl.py @@ -99,7 +99,16 @@ def _vcl_request(self, method, *args): raise LibcloudError(res["errormsg"], driver=self) return res - def create_node(self, image, start=None, length="60"): + def create_node( + self, + name, + size, + image, + location=None, + auth=None, + start=None, + length="60", + ): """Create a new VCL reservation size and name ignored, image is the id from list_image diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py index fc3b21cd2c..658701535e 100644 --- a/libcloud/compute/drivers/vcloud.py +++ b/libcloud/compute/drivers/vcloud.py @@ -871,6 +871,7 @@ def create_node( name, size, image, + location=None, auth=None, ex_network=None, ex_vdc=None, @@ -1605,7 +1606,7 @@ def ex_query(self, type, filter=None, page=1, page_size=100, sort_asc=None, sort return results - def create_node(self, **kwargs): + def create_node(self, name, size, image, location=None, auth=None, **kwargs): """ Creates and returns node. If the source image is: - vApp template - a new vApp is instantiated from template @@ -1691,8 +1692,6 @@ def create_node(self, **kwargs): :keyword ex_description: Set a description for the vApp. :type ex_description: ``str`` """ - name = kwargs["name"] - image = kwargs["image"] ex_vm_names = kwargs.get("ex_vm_names") ex_vm_cpu = kwargs.get("ex_vm_cpu") ex_vm_memory = kwargs.get("ex_vm_memory") diff --git a/libcloud/compute/drivers/vpsnet.py b/libcloud/compute/drivers/vpsnet.py index f92dfbecf2..1531ecae1a 100644 --- a/libcloud/compute/drivers/vpsnet.py +++ b/libcloud/compute/drivers/vpsnet.py @@ -131,7 +131,16 @@ def _get_price_per_node(self, num): single_node_price = self._get_size_price(size_id="1") return num * single_node_price - def create_node(self, name, image, size, ex_backups_enabled=False, ex_fqdn=None): + def create_node( + self, + name, + size, + image, + location=None, + auth=None, + ex_backups_enabled=False, + ex_fqdn=None, + ): """Create a new VPS.net node @inherits: :class:`NodeDriver.create_node` diff --git a/libcloud/compute/drivers/vsphere.py b/libcloud/compute/drivers/vsphere.py index 185cc6db76..728c336655 100644 --- a/libcloud/compute/drivers/vsphere.py +++ b/libcloud/compute/drivers/vsphere.py @@ -256,7 +256,10 @@ def _to_network(self, data): return VSphereNetwork(id=data.name, name=data.name, extra=extra) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): """ Returns sizes """ @@ -978,9 +981,10 @@ def wait_for_task(self, task, timeout=1800, interval=10): def create_node( self, name, - image, size, + image, location=None, + auth=None, ex_cluster=None, ex_network=None, ex_datacenter=None, @@ -1351,7 +1355,10 @@ def _get_session_token(self): self.session_token = result.object["value"] self.connection.session_token = self.session_token - def list_sizes(self): + def list_sizes( + self, + location=None, + ): return [] def list_nodes( @@ -1914,7 +1921,10 @@ def _request(self, req, method="GET", params=None, data=None): return result - def list_images(self, **kwargs): + def list_images( + self, + location=None, + ): libraries = self.ex_list_content_libraries() item_ids = [] @@ -1972,9 +1982,10 @@ def ex_list_networks(self): def create_node( self, name, + size, image, - size=None, location=None, + auth=None, ex_datastore=None, ex_disks=None, ex_folder=None, diff --git a/libcloud/compute/drivers/vultr.py b/libcloud/compute/drivers/vultr.py index abdf8b99ea..e429391cb8 100644 --- a/libcloud/compute/drivers/vultr.py +++ b/libcloud/compute/drivers/vultr.py @@ -844,14 +844,29 @@ def delete_key_pair(self, key_pair): def list_locations(self): return self._list_resources("/v1/regions/list", self._to_location) - def list_sizes(self): + def list_sizes( + self, + location=None, + ): return self._list_resources("/v1/plans/list", self._to_size) - def list_images(self): + def list_images( + self, + location=None, + ): return self._list_resources("/v1/os/list", self._to_image) # pylint: disable=too-many-locals - def create_node(self, name, size, image, location, ex_ssh_key_ids=None, ex_create_attr=None): + def create_node( + self, + name, + size, + image, + location=None, + auth=None, + ex_ssh_key_ids=None, + ex_create_attr=None, + ): """ Create a node @@ -1133,8 +1148,9 @@ def create_node( self, name: str, size: NodeSize, - location: NodeLocation, - image: Optional[NodeImage] = None, + image: NodeImage, + location: Optional[NodeLocation] = None, + auth=None, ex_ssh_key_ids: Optional[List[str]] = None, ex_private_network_ids: Optional[List[str]] = None, ex_snapshot: Union[VultrNodeSnapshot, str, None] = None, @@ -1360,7 +1376,11 @@ def destroy_node(self, node: Node) -> bool: return resp.success() - def list_sizes(self, ex_list_bare_metals: bool = True) -> List[NodeSize]: + def list_sizes( + self, + location=None, + ex_list_bare_metals: bool = True, + ): """List available node sizes. :keyword ex_list_bare_metals: Whether to fetch bare metal sizes. @@ -1375,7 +1395,10 @@ def list_sizes(self, ex_list_bare_metals: bool = True) -> List[NodeSize]: sizes += self.ex_list_bare_metal_sizes() return sizes - def list_images(self) -> List[NodeImage]: + def list_images( + self, + location=None, + ): """List available node images. :rtype: ``list`` of :class: `NodeImage` @@ -1401,10 +1424,11 @@ def list_volumes(self) -> List[StorageVolume]: def create_volume( self, - size: int, - name: str, - location: Union[NodeLocation, str], - ) -> StorageVolume: + size, + name, + location=None, + snapshot=None, + ): """Create a new volume. :param size: Size of the volume in gigabytes.\ @@ -1435,10 +1459,11 @@ def create_volume( def attach_volume( self, - node: Node, - volume: StorageVolume, + node, + volume, + device=None, ex_live: bool = True, - ) -> bool: + ): """Attaches volume to node. :param node: Node to attach volume to. @@ -1507,14 +1532,18 @@ def list_key_pairs(self) -> List[KeyPair]: data = self._paginated_request("/v2/ssh-keys", "ssh_keys") return [self._to_key_pair(item) for item in data] - def get_key_pair(self, key_id: str) -> KeyPair: + def get_key_pair( + self, + name, + ): """Retrieve a single key pair. - :param key_id: ID of the key pair to retrieve. - :type key_id: ``str`` + :param name: ID of the key pair to retrieve. + :type name: ``str`` :rtype: :class: `KeyPair` """ + key_id = name resp = self.connection.request("/v2/ssh-keys/%s" % key_id) return self._to_key_pair(resp.object["ssh_key"]) diff --git a/libcloud/test/compute/test_abiquo.py b/libcloud/test/compute/test_abiquo.py index f15aa0cfd4..7117403c3a 100644 --- a/libcloud/test/compute/test_abiquo.py +++ b/libcloud/test/compute/test_abiquo.py @@ -107,7 +107,7 @@ def test_create_node_specify_location(self): """ image = self.driver.list_images()[0] location = self.driver.list_locations()[0] - self.driver.create_node(image=image, location=location) + self.driver.create_node(name=None, size=None, image=image, location=location) def test_create_node_specify_wrong_location(self): """ @@ -115,7 +115,14 @@ def test_create_node_specify_wrong_location(self): """ image = self.driver.list_images()[0] location = NodeLocation(435, "fake-location", "Spain", self.driver) - self.assertRaises(LibcloudError, self.driver.create_node, image=image, location=location) + self.assertRaises( + LibcloudError, + self.driver.create_node, + name=None, + size=None, + image=image, + location=location, + ) def test_create_node_specify_wrong_image(self): """ @@ -128,14 +135,21 @@ def test_create_node_specify_wrong_image(self): image = NodeImage(3234, "dummy-image", self.driver) location = self.driver.list_locations()[0] # With this image, it should raise an Exception - self.assertRaises(LibcloudError, self.driver.create_node, image=image, location=location) + self.assertRaises( + LibcloudError, + self.driver.create_node, + name=None, + size=None, + image=image, + location=location, + ) def test_create_node_specify_group_name(self): """ Test 'create_node' into a concrete group. """ image = self.driver.list_images()[0] - self.driver.create_node(image=image, ex_group_name="new_group_name") + self.driver.create_node(name=None, size=None, image=image, ex_group_name="new_group_name") def test_create_group_location_does_not_exist(self): """ diff --git a/libcloud/test/compute/test_azure_arm.py b/libcloud/test/compute/test_azure_arm.py index cd0a255e5a..143ef1a2ca 100644 --- a/libcloud/test/compute/test_azure_arm.py +++ b/libcloud/test/compute/test_azure_arm.py @@ -111,8 +111,8 @@ def test_create_node(self): "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account="000000", ex_user_name="any_user", @@ -162,8 +162,8 @@ def test_create_node_storage_account_not_provided_and_not_ex_use_managed_disks( "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account=None, ex_user_name="any_user", @@ -177,8 +177,8 @@ def test_create_node_storage_account_not_provided_and_not_ex_use_managed_disks( "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account=None, ex_user_name="any_user", @@ -198,8 +198,8 @@ def test_create_node_ex_disk_size(self): "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account="000000", ex_user_name="any_user", @@ -247,8 +247,8 @@ def test_create_node_ex_customdata(self): "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account="000000", ex_user_name="any_user", @@ -268,8 +268,8 @@ def test_create_node_ex_customdata(self): "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account="000000", ex_user_name="any_user", @@ -293,8 +293,8 @@ def test_create_node_ex_os_disk_delete(self): "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account="000000", ex_user_name="any_user", @@ -340,8 +340,8 @@ def test_create_node_compute_gallery_image(self): "test-node-1", size, image, - auth, location=location, + auth=auth, ex_resource_group="000000", ex_storage_account="000000", ex_user_name="any_user", diff --git a/libcloud/test/compute/test_dimensiondata_v2_3.py b/libcloud/test/compute/test_dimensiondata_v2_3.py index 9ecfdd1ee7..56edbefbe4 100644 --- a/libcloud/test/compute/test_dimensiondata_v2_3.py +++ b/libcloud/test/compute/test_dimensiondata_v2_3.py @@ -291,6 +291,7 @@ def test_create_mcp1_node_optional_param(self): ) disks = [DimensionDataServerDisk(scsi_id="0", speed="HIGHPERFORMANCE")] node = self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -310,6 +311,7 @@ def test_create_mcp1_node_response_no_pass_random_gen(self): image = self.driver.list_images()[0] network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -325,6 +327,7 @@ def test_create_mcp1_node_response_no_pass_customer_windows(self): image = self.driver.ex_list_customer_images()[1] network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -340,6 +343,7 @@ def test_create_mcp1_node_response_no_pass_customer_windows_STR(self): image = self.driver.ex_list_customer_images()[1].id network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -355,6 +359,7 @@ def test_create_mcp1_node_response_no_pass_customer_linux(self): image = self.driver.ex_list_customer_images()[0] network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -370,6 +375,7 @@ def test_create_mcp1_node_response_no_pass_customer_linux_STR(self): image = self.driver.ex_list_customer_images()[0].id network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -386,6 +392,7 @@ def test_create_mcp1_node_response_STR(self): image = self.driver.list_images()[0].id network = self.driver.ex_list_networks()[0].id node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -406,6 +413,7 @@ def test_create_node_response_network_domain(self): cpu_count=4, cores_per_socket=1, performance="HIGHPERFORMANCE" ) node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -429,6 +437,7 @@ def test_create_node_response_network_domain_STR(self): cpu_count=4, cores_per_socket=1, performance="HIGHPERFORMANCE" ) node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -447,6 +456,7 @@ def test_create_mcp1_node_no_network(self): image = self.driver.list_images()[0] with self.assertRaises(InvalidRequestError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -459,6 +469,7 @@ def test_create_node_mcp1_ipv4(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -474,6 +485,7 @@ def test_create_node_mcp1_network(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -488,6 +500,7 @@ def test_create_node_mcp2_vlan(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -503,6 +516,7 @@ def test_create_node_mcp2_ipv4(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -519,6 +533,7 @@ def test_create_node_network_domain_no_vlan_or_ipv4(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -531,6 +546,7 @@ def test_create_node_response(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -544,6 +560,7 @@ def test_create_node_ms_time_zone(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -559,6 +576,7 @@ def test_create_node_ambigious_mcps_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -572,7 +590,7 @@ def test_create_node_no_network_domain_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( - name="test3", image=image, auth=rootPw, ex_primary_nic_vlan="fakevlan" + size=None, name="test3", image=image, auth=rootPw, ex_primary_nic_vlan="fakevlan" ) def test_create_node_no_primary_nic_fail(self): @@ -580,6 +598,7 @@ def test_create_node_no_primary_nic_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -590,6 +609,7 @@ def test_create_node_primary_vlan_nic(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -604,6 +624,7 @@ def test_create_node_primary_ipv4(self): rootPw = "pass123" image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -618,6 +639,7 @@ def test_create_node_both_primary_nic_and_vlan_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -633,6 +655,7 @@ def test_create_node_cpu_specification(self): cpu_count="4", cores_per_socket="2", performance="STANDARD" ) node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -650,6 +673,7 @@ def test_create_node_memory(self): image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -667,6 +691,7 @@ def test_create_node_disks(self): image = self.driver.list_images()[0] disks = [DimensionDataServerDisk(scsi_id="0", speed="HIGHPERFORMANCE")] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -685,6 +710,7 @@ def test_create_node_disks_fail(self): disks = "blah" with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -699,6 +725,7 @@ def test_create_node_ipv4_gateway(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -716,6 +743,7 @@ def test_create_node_network_domain_no_vlan_no_ipv4_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -730,6 +758,7 @@ def test_create_node_mcp2_additional_nics_legacy(self): additional_vlans = ["fakevlan1", "fakevlan2"] additional_ipv4 = ["10.0.0.2", "10.0.0.3"] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -748,6 +777,7 @@ def test_create_node_bad_additional_nics_ipv4(self): image = self.driver.list_images()[0] with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -766,6 +796,7 @@ def test_create_node_additional_nics(self): additional_nics = [nic1, nic2] node = self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -791,6 +822,7 @@ def test_create_node_additional_nics_vlan_ipv4_coexist_fail(self): additional_nics = [nic1, nic2] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -807,6 +839,7 @@ def test_create_node_additional_nics_invalid_input_fail(self): additional_nics = "blah" with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -825,6 +858,7 @@ def test_create_node_additional_nics_vlan_ipv4_not_exist_fail(self): additional_nics = [nic1, nic2] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -840,6 +874,7 @@ def test_create_node_bad_additional_nics_vlan(self): image = self.driver.list_images()[0] with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -854,6 +889,7 @@ def test_create_node_mcp2_indicate_dns(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, diff --git a/libcloud/test/compute/test_dimensiondata_v2_4.py b/libcloud/test/compute/test_dimensiondata_v2_4.py index b6e2c7f088..0462420da4 100644 --- a/libcloud/test/compute/test_dimensiondata_v2_4.py +++ b/libcloud/test/compute/test_dimensiondata_v2_4.py @@ -294,6 +294,7 @@ def test_create_mcp1_node_optional_param(self): ) disks = [DimensionDataServerDisk(scsi_id="0", speed="HIGHPERFORMANCE")] node = self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -313,6 +314,7 @@ def test_create_mcp1_node_response_no_pass_random_gen(self): image = self.driver.list_images()[0] network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -328,6 +330,7 @@ def test_create_mcp1_node_response_no_pass_customer_windows(self): image = self.driver.ex_list_customer_images()[1] network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -343,6 +346,7 @@ def test_create_mcp1_node_response_no_pass_customer_windows_STR(self): image = self.driver.ex_list_customer_images()[1].id network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -358,6 +362,7 @@ def test_create_mcp1_node_response_no_pass_customer_linux(self): image = self.driver.ex_list_customer_images()[0] network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -373,6 +378,7 @@ def test_create_mcp1_node_response_no_pass_customer_linux_STR(self): image = self.driver.ex_list_customer_images()[0].id network = self.driver.ex_list_networks()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -389,6 +395,7 @@ def test_create_mcp1_node_response_STR(self): image = self.driver.list_images()[0].id network = self.driver.ex_list_networks()[0].id node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -409,6 +416,7 @@ def test_create_node_response_network_domain(self): cpu_count=4, cores_per_socket=1, performance="HIGHPERFORMANCE" ) node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -432,6 +440,7 @@ def test_create_node_response_network_domain_STR(self): cpu_count=4, cores_per_socket=1, performance="HIGHPERFORMANCE" ) node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -450,6 +459,7 @@ def test_create_mcp1_node_no_network(self): image = self.driver.list_images()[0] with self.assertRaises(InvalidRequestError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -462,6 +472,7 @@ def test_create_node_mcp1_ipv4(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -477,6 +488,7 @@ def test_create_node_mcp1_network(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -491,6 +503,7 @@ def test_create_node_mcp2_vlan(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -506,6 +519,7 @@ def test_create_node_mcp2_ipv4(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -522,6 +536,7 @@ def test_create_node_network_domain_no_vlan_or_ipv4(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -534,6 +549,7 @@ def test_create_node_response(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -547,6 +563,7 @@ def test_create_node_ms_time_zone(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -562,6 +579,7 @@ def test_create_node_ambigious_mcps_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -575,7 +593,7 @@ def test_create_node_no_network_domain_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( - name="test3", image=image, auth=rootPw, ex_primary_nic_vlan="fakevlan" + size=None, name="test3", image=image, auth=rootPw, ex_primary_nic_vlan="fakevlan" ) def test_create_node_no_primary_nic_fail(self): @@ -583,6 +601,7 @@ def test_create_node_no_primary_nic_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -593,6 +612,7 @@ def test_create_node_primary_vlan_nic(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -607,6 +627,7 @@ def test_create_node_primary_ipv4(self): rootPw = "pass123" image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -621,6 +642,7 @@ def test_create_node_both_primary_nic_and_vlan_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -636,6 +658,7 @@ def test_create_node_cpu_specification(self): cpu_count="4", cores_per_socket="2", performance="STANDARD" ) node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -653,6 +676,7 @@ def test_create_node_memory(self): image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -670,6 +694,7 @@ def test_create_node_disks(self): image = self.driver.list_images()[0] disks = [DimensionDataServerDisk(scsi_id="0", speed="HIGHPERFORMANCE")] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -688,6 +713,7 @@ def test_create_node_disks_fail(self): disks = "blah" with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -702,6 +728,7 @@ def test_create_node_ipv4_gateway(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -719,6 +746,7 @@ def test_create_node_network_domain_no_vlan_no_ipv4_fail(self): image = self.driver.list_images()[0] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -733,6 +761,7 @@ def test_create_node_mcp2_additional_nics_legacy(self): additional_vlans = ["fakevlan1", "fakevlan2"] additional_ipv4 = ["10.0.0.2", "10.0.0.3"] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -751,6 +780,7 @@ def test_create_node_bad_additional_nics_ipv4(self): image = self.driver.list_images()[0] with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -769,6 +799,7 @@ def test_create_node_additional_nics(self): additional_nics = [nic1, nic2] node = self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -794,6 +825,7 @@ def test_create_node_additional_nics_vlan_ipv4_coexist_fail(self): additional_nics = [nic1, nic2] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -810,6 +842,7 @@ def test_create_node_additional_nics_invalid_input_fail(self): additional_nics = "blah" with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -828,6 +861,7 @@ def test_create_node_additional_nics_vlan_ipv4_not_exist_fail(self): additional_nics = [nic1, nic2] with self.assertRaises(ValueError): self.driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -843,6 +877,7 @@ def test_create_node_bad_additional_nics_vlan(self): image = self.driver.list_images()[0] with self.assertRaises(TypeError): self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -857,6 +892,7 @@ def test_create_node_mcp2_indicate_dns(self): rootPw = NodeAuthPassword("pass123") image = self.driver.list_images()[0] node = self.driver.create_node( + size=None, name="test2", image=image, auth=rootPw, diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index cc83bd1395..b01ef2e098 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -587,7 +587,7 @@ def test_get_image(self): def test_copy_image(self): image = self.driver.list_images()[0] resp = self.driver.copy_image( - image, "us-east-1", name="Faux Image", description="Test Image Copy" + "us-east-1", image, name="Faux Image", description="Test Image Copy" ) self.assertEqual(resp.id, "ami-4db38224") diff --git a/libcloud/test/compute/test_equinixmetal.py b/libcloud/test/compute/test_equinixmetal.py index 14b896654b..ec73014efc 100644 --- a/libcloud/test/compute/test_equinixmetal.py +++ b/libcloud/test/compute/test_equinixmetal.py @@ -150,6 +150,10 @@ def test_create_key_pair(self): ) self.assertTrue(isinstance(key, KeyPair)) + def test_create_key_pair_requires_public_key(self): + with self.assertRaisesRegex(ValueError, "public_key is required"): + self.driver.create_key_pair(name="sshkey-name") + def test_delete_key_pair(self): key = self.driver.list_key_pairs()[0] self.driver.delete_key_pair(key) diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py index 75161a0ef8..f64fe8ad61 100644 --- a/libcloud/test/compute/test_gce.py +++ b/libcloud/test/compute/test_gce.py @@ -410,7 +410,7 @@ def test_list_images(self): local_images = self.driver.list_images() all_deprecated_images = self.driver.list_images(ex_include_deprecated=True) debian_images = self.driver.list_images(ex_project="debian-cloud") - local_plus_deb = self.driver.list_images(["debian-cloud", "project_name"]) + local_plus_deb = self.driver.list_images(ex_project=["debian-cloud", "project_name"]) self.assertEqual(len(local_images), 52) self.assertEqual(len(all_deprecated_images), 180) self.assertEqual(len(debian_images), 2) diff --git a/libcloud/test/compute/test_gig_g8.py b/libcloud/test/compute/test_gig_g8.py index 386e4d7673..bd77c6737a 100644 --- a/libcloud/test/compute/test_gig_g8.py +++ b/libcloud/test/compute/test_gig_g8.py @@ -85,7 +85,9 @@ def test_create_node(self): image = self.driver.list_images()[0] size = self.driver.list_sizes()[0] network = self.driver.ex_list_networks()[0] - node = self.driver.create_node("my test", image, network, "my description", size) + node = self.driver.create_node( + "my test", size, image, ex_network=network, ex_description="my description" + ) self.assertIsInstance(node, Node) def test_stop_node(self): diff --git a/libcloud/test/compute/test_linode_v4.py b/libcloud/test/compute/test_linode_v4.py index 2bae27938a..05bdab9e4e 100644 --- a/libcloud/test/compute/test_linode_v4.py +++ b/libcloud/test/compute/test_linode_v4.py @@ -103,10 +103,10 @@ def test_create_node_response(self): image = self.driver.list_images()[0] location = self.driver.list_locations()[0] node = self.driver.create_node( - location, "node-name", size=size, image=image, + location=location, root_pass="test123456", ) self.assertTrue(isinstance(node, Node)) @@ -119,10 +119,10 @@ def test_create_node_with_ex_userdata(self): LinodeMockHttpV4.type = "EX_USERDATA" node = self.driver.create_node( - location, "node-name", size=size, image=image, + location=location, root_pass="test123456", ex_userdata=EX_USERDATA, ) @@ -155,10 +155,10 @@ def test_create_node(self): location = self.driver.list_locations()[0] node = self.driver.create_node( - location, "TestNode", size, image=image, + location=location, root_pass="test123456", ex_backups_enabled=True, ex_tags=["testing123"], @@ -176,13 +176,15 @@ def test_create_node_no_root_pass(self): location = self.driver.list_locations()[0] with self.assertRaises(LinodeExceptionV4): - self.driver.create_node(location, "TestNode", size, image=image) + self.driver.create_node("TestNode", size, image=image, location=location) def test_create_node_no_image(self): size = self.driver.list_sizes()[0] location = self.driver.list_locations()[0] LinodeMockHttpV4.type = "NO_IMAGE" - node = self.driver.create_node(location, "TestNode", size, None, ex_tags=["testing123"]) + node = self.driver.create_node( + "TestNode", size, None, location=location, ex_tags=["testing123"] + ) self.assertIsNone(node.image) self.assertEqual(node.name, "TestNode") @@ -321,7 +323,7 @@ def test_list_volumes(self): def test_create_volume(self): node = Node("22344420", None, NodeState.RUNNING, None, None, driver=self.driver) - volume = self.driver.create_volume("Volume1", 50, node=node, tags=["test123", "testing"]) + volume = self.driver.create_volume(50, "Volume1", node=node, tags=["test123", "testing"]) self.assertEqual(volume.extra["linode_id"], 22344420) self.assertEqual(volume.size, 50) @@ -332,7 +334,7 @@ def test_create_volume_unattached(self): location = self.driver.list_locations()[0] LinodeMockHttpV4.type = "UNATTACHED" volume = self.driver.create_volume( - "Volume1", 50, location=location, tags=["test123", "testing"] + 50, "Volume1", location=location, tags=["test123", "testing"] ) self.assertEqual(volume.size, 50) @@ -342,11 +344,11 @@ def test_create_volume_unattached(self): def test_create_volume_invalid_name(self): location = self.driver.list_locations()[0] with self.assertRaises(LinodeExceptionV4): - self.driver.create_volume("Volume__1", 50, location=location) + self.driver.create_volume(50, "Volume__1", location=location) with self.assertRaises(LinodeExceptionV4): - self.driver.create_volume("Volume 1", 50, location=location) + self.driver.create_volume(50, "Volume 1", location=location) with self.assertRaises(LinodeExceptionV4): - self.driver.create_volume("Volume--1", 50, location=location) + self.driver.create_volume(50, "Volume--1", location=location) def test_attach_volume_already_attached(self): volume = self.driver.list_volumes()[0] diff --git a/libcloud/test/compute/test_maxihost.py b/libcloud/test/compute/test_maxihost.py index 81f9935293..ff2d722670 100644 --- a/libcloud/test/compute/test_maxihost.py +++ b/libcloud/test/compute/test_maxihost.py @@ -50,6 +50,10 @@ def test_list_key_pairs(self): self.assertEqual(key.name, "test_key") self.assertEqual(key.fingerprint, "77:08:a7:a5:f9:8c:e1:ab:7b:c3:d8:0c:cd:ac:8b:dd") + def test_create_key_pair_requires_public_key(self): + with self.assertRaisesRegex(ValueError, "public_key is required"): + self.driver.create_key_pair(name="test-key") + def test_list_nodes(self): nodes = self.driver.list_nodes() self.assertEqual(len(nodes), 1) @@ -64,6 +68,13 @@ def test_create_node_response(self): node = self.driver.create_node(name="node-name", image=image, size=size, location=location) self.assertTrue(isinstance(node, Node)) + def test_create_node_requires_location(self): + size = self.driver.list_sizes()[0] + image = self.driver.list_images()[0] + + with self.assertRaisesRegex(ValueError, "location is required"): + self.driver.create_node(name="node-name", image=image, size=size) + def test_destroy_node_response(self): node = self.driver.list_nodes()[0] ret = self.driver.destroy_node(node) diff --git a/libcloud/test/compute/test_nttcis.py b/libcloud/test/compute/test_nttcis.py index af7975f347..f1788a0d8d 100644 --- a/libcloud/test/compute/test_nttcis.py +++ b/libcloud/test/compute/test_nttcis.py @@ -315,6 +315,7 @@ def test_create_mcp1_node_optional_param(driver): ) disks = [NttCisServerDisk(scsi_id="0", speed="HIGHPERFORMANCE")] node = driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -335,6 +336,7 @@ def test_create_mcp1_node_response_no_pass_random_gen(driver): image = driver.list_images()[0] network = driver.ex_list_networks()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -351,6 +353,7 @@ def test_create_mcp1_node_response_no_pass_customer_windows(driver): image = driver.ex_list_customer_images()[1] network = driver.ex_list_networks()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -367,6 +370,7 @@ def test_create_mcp1_node_response_no_pass_customer_windows_STR(driver): image = driver.ex_list_customer_images()[1].id network = driver.ex_list_networks()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -383,6 +387,7 @@ def test_create_mcp1_node_response_no_pass_customer_linux(driver): image = driver.ex_list_customer_images()[0] network = driver.ex_list_networks()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -399,6 +404,7 @@ def test_create_mcp1_node_response_no_pass_customer_linux_STR(driver): image = driver.ex_list_customer_images()[0].id network = driver.ex_list_networks()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=None, @@ -416,6 +422,7 @@ def test_create_mcp1_node_response_STR(driver): image = driver.list_images()[0].id network = driver.ex_list_networks()[0].id node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -432,6 +439,7 @@ def test_create_mcp1_node_no_network(driver): image = driver.list_images()[0] with pytest.raises(InvalidRequestError): driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -445,6 +453,7 @@ def test_create_node_mcp1_ipv4(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -461,6 +470,7 @@ def test_create_node_mcp1_network(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -482,6 +492,7 @@ def test_create_node_response_network_domain(driver): cpu_count=4, cores_per_socket=1, performance="HIGHPERFORMANCE" ) node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -506,6 +517,7 @@ def test_create_node_response_network_domain_STR(driver): cpu_count=4, cores_per_socket=1, performance="HIGHPERFORMANCE" ) node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -524,6 +536,7 @@ def test_create_node_mcp2_vlan(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -540,6 +553,7 @@ def test_create_node_mcp2_ipv4(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -557,6 +571,7 @@ def test_create_node_network_domain_no_vlan_or_ipv4(driver): image = driver.list_images()[0] with pytest.raises(ValueError): driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -570,6 +585,7 @@ def test_create_node_response(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -584,6 +600,7 @@ def test_create_node_ms_time_zone(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -600,6 +617,7 @@ def test_create_node_ambigious_mcps_fail(driver): image = driver.list_images()[0] with pytest.raises(ValueError): driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -613,7 +631,9 @@ def test_create_node_no_network_domain_fail(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] with pytest.raises(ValueError): - driver.create_node(name="test3", image=image, auth=rootPw, ex_primary_nic_vlan="fakevlan") + driver.create_node( + size=None, name="test3", image=image, auth=rootPw, ex_primary_nic_vlan="fakevlan" + ) def test_create_node_no_primary_nic_fail(driver): @@ -621,6 +641,7 @@ def test_create_node_no_primary_nic_fail(driver): image = driver.list_images()[0] with pytest.raises(ValueError): driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -632,6 +653,7 @@ def test_create_node_primary_vlan_nic(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -647,6 +669,7 @@ def test_create_node_primary_ipv4(driver): rootPw = "pass123" image = driver.list_images()[0] node = driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -662,6 +685,7 @@ def test_create_node_both_primary_nic_and_vlan_fail(driver): image = driver.list_images()[0] with pytest.raises(ValueError): driver.create_node( + size=None, name="test3", image=image, auth=rootPw, @@ -678,6 +702,7 @@ def test_create_node_cpu_specification(driver): cpu_count="4", cores_per_socket="2", performance="STANDARD" ) node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -696,6 +721,7 @@ def test_create_node_memory(driver): image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -714,6 +740,7 @@ def test_create_node_disks(driver): image = driver.list_images()[0] disks = [NttCisServerDisk(scsi_id="0", speed="HIGHPERFORMANCE")] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -733,6 +760,7 @@ def test_create_node_disks_fail(driver): disks = "blah" with pytest.raises(TypeError): driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -748,6 +776,7 @@ def test_create_node_ipv4_gateway(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -766,6 +795,7 @@ def test_create_node_network_domain_no_vlan_no_ipv4_fail(driver): image = driver.list_images()[0] with pytest.raises(ValueError): driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -781,6 +811,7 @@ def test_create_node_mcp2_additional_nics_legacy(driver): additional_vlans = ["fakevlan1", "fakevlan2"] additional_ipv4 = ["10.0.0.2", "10.0.0.3"] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -800,6 +831,7 @@ def test_create_node_bad_additional_nics_ipv4(driver): image = driver.list_images()[0] with pytest.raises(TypeError): driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -819,6 +851,7 @@ def test_create_node_additional_nics(driver): additional_nics = [nic1, nic2] node = driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -841,6 +874,7 @@ def test_create_node_additional_nics_vlan_ipv4_coexist_fail(driver): additional_nics = [nic1, nic2] with pytest.raises(ValueError): driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -858,6 +892,7 @@ def test_create_node_additional_nics_invalid_input_fail(driver): additional_nics = "blah" with pytest.raises(TypeError): driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -877,6 +912,7 @@ def test_create_node_additional_nics_vlan_ipv4_not_exist_fail(driver): additional_nics = [nic1, nic2] with pytest.raises(ValueError): driver.create_node( + size=None, name="test2", image=image, auth=root_pw, @@ -893,6 +929,7 @@ def test_create_node_bad_additional_nics_vlan(driver): image = driver.list_images()[0] with pytest.raises(TypeError): driver.create_node( + size=None, name="test2", image=image, auth=rootPw, @@ -908,6 +945,7 @@ def test_create_node_mcp2_indicate_dns(driver): rootPw = NodeAuthPassword("pass123") image = driver.list_images()[0] node = driver.create_node( + size=None, name="test2", image=image, auth=rootPw, diff --git a/libcloud/test/compute/test_onapp.py b/libcloud/test/compute/test_onapp.py index 27d463406e..e97f0716f1 100644 --- a/libcloud/test/compute/test_onapp.py +++ b/libcloud/test/compute/test_onapp.py @@ -35,6 +35,8 @@ def setUp(self): def test_create_node(self): node = self.driver.create_node( name="onapp-new-fred", + size=None, + image=None, ex_memory=512, ex_cpus=4, ex_cpu_shares=4, diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 0b33b6c5df..933886fcbc 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -1226,6 +1226,7 @@ def test_create_node_from_bootable_volume(self): size = NodeSize(1, "256 slice", None, None, None, None, driver=self.driver) node = self.driver.create_node( + image=None, name="racktest", size=size, ex_blockdevicemappings=[ diff --git a/libcloud/test/compute/test_outscale.py b/libcloud/test/compute/test_outscale.py new file mode 100644 index 0000000000..0f1c8322f0 --- /dev/null +++ b/libcloud/test/compute/test_outscale.py @@ -0,0 +1,348 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import inspect +import unittest +from unittest.mock import Mock + +from libcloud.compute.base import NodeDriver, NodeLocation +from libcloud.compute.drivers.outscale import OutscaleNodeDriver + + +class OutscaleNodeDriverTest(unittest.TestCase): + STANDARD_METHODS_UNDER_TEST = { + "attach_volume", + "create_image", + "create_key_pair", + "create_node", + "create_volume", + "create_volume_snapshot", + "delete_image", + "delete_key_pair", + "destroy_node", + "destroy_volume", + "destroy_volume_snapshot", + "detach_volume", + "get_image", + "get_key_pair", + "list_images", + "list_key_pairs", + "list_locations", + "list_nodes", + "list_volume_snapshots", + "list_volumes", + "reboot_node", + "start_node", + "stop_node", + } + + def setUp(self): + self.driver = OutscaleNodeDriver("key", "secret") + self._set_response({}, status_code=400) + + def _set_response(self, payload, status_code=200): + response = Mock(status_code=400) + response.status_code = status_code + response.json.return_value = payload + self.driver._call_api = Mock(return_value=response) + + def test_all_standard_methods_are_covered(self): + standard_methods = { + name + for name, method in NodeDriver.__dict__.items() + if inspect.isfunction(method) and not name.startswith("_") + } + implemented_methods = { + name + for name, method in OutscaleNodeDriver.__dict__.items() + if inspect.isfunction(method) and name in standard_methods + } + + self.assertEqual(implemented_methods, self.STANDARD_METHODS_UNDER_TEST) + + def test_list_locations(self): + expected = [Mock()] + self.driver._to_locations = Mock(return_value=expected) + self._set_response({"Locations": [{"Code": "eu-west-2a"}]}) + + result = self.driver.list_locations() + + self.assertEqual(result, expected) + self.driver._to_locations.assert_called_once_with([{"Code": "eu-west-2a"}]) + self.driver._call_api.assert_called_once_with("ReadLocations", '{"DryRun": false}') + + def test_create_node(self): + image = Mock(id="ami-1") + expected = Mock() + self.driver._to_node = Mock(return_value=expected) + self._set_response({"Vms": [{"VmId": "vm-1"}]}) + + result = self.driver.create_node(name=None, size=None, image=image) + + self.assertEqual(result, expected) + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "CreateVms") + self.assertEqual(json.loads(data)["ImageId"], image.id) + + def test_node_power_operations(self): + node = Mock(id="vm-1") + operations = { + "reboot_node": "RebootVms", + "start_node": "StartVms", + "stop_node": "StopVms", + } + + for method_name, action in operations.items(): + with self.subTest(method=method_name): + self._set_response({}) + result = getattr(self.driver, method_name)(node) + + self.assertTrue(result) + actual_action, data = self.driver._call_api.call_args.args + self.assertEqual(actual_action, action) + self.assertEqual(json.loads(data)["VmIds"], [node.id]) + + def test_list_nodes(self): + expected = [Mock()] + self.driver._to_nodes = Mock(return_value=expected) + self._set_response({"Vms": [{"VmId": "vm-1"}]}) + + result = self.driver.list_nodes() + + self.assertEqual(result, expected) + self.driver._to_nodes.assert_called_once_with([{"VmId": "vm-1"}]) + self.driver._call_api.assert_called_once_with("ReadVms", "{}") + + def test_destroy_node(self): + node = Mock(id="vm-1") + self._set_response({}) + + self.assertTrue(self.driver.destroy_node(node)) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "DeleteVms") + self.assertEqual(json.loads(data)["VmIds"], node.id) + + def test_create_image(self): + node = Mock(id="vm-1") + expected = Mock() + self.driver._to_node_image = Mock(return_value=expected) + self._set_response({"Image": {"ImageId": "ami-1"}}) + + result = self.driver.create_image(node, "image-name", "description") + + self.assertEqual(result, expected) + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "CreateImage") + self.assertEqual(json.loads(data)["VmId"], node.id) + + def test_list_images(self): + images = [{"ImageId": "ami-1"}] + self._set_response({"Images": images}) + + self.assertEqual(self.driver.list_images(), images) + self.assertEqual(self.driver._call_api.call_args.args[0], "ReadImages") + + def test_get_image(self): + expected = Mock() + self.driver._to_node_image = Mock(return_value=expected) + self._set_response({"Images": [{"ImageId": "ami-1"}]}) + + result = self.driver.get_image("ami-1") + + self.assertEqual(result, expected) + self.driver._to_node_image.assert_called_once_with({"ImageId": "ami-1"}) + + def test_delete_image(self): + image = Mock(id="ami-1") + self._set_response({}) + + self.assertTrue(self.driver.delete_image(image)) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "DeleteImage") + self.assertEqual(json.loads(data)["ImageId"], image.id) + + def test_create_key_pair(self): + expected = Mock() + self.driver._to_key_pair = Mock(return_value=expected) + self._set_response({"Keypair": {"KeypairName": "key-name"}}) + + result = self.driver.create_key_pair("key-name") + + self.assertEqual(result, expected) + self.driver._to_key_pair.assert_called_once_with({"KeypairName": "key-name"}) + + def test_list_key_pairs(self): + expected = [Mock()] + self.driver._to_key_pairs = Mock(return_value=expected) + self._set_response({"Keypairs": [{"KeypairName": "key-name"}]}) + + result = self.driver.list_key_pairs() + + self.assertEqual(result, expected) + self.driver._to_key_pairs.assert_called_once_with([{"KeypairName": "key-name"}]) + + def test_get_key_pair(self): + expected = Mock() + self.driver._to_key_pair = Mock(return_value=expected) + self._set_response({"Keypairs": [{"KeypairName": "key-name"}]}) + + result = self.driver.get_key_pair("key-name") + + self.assertEqual(result, expected) + self.driver._to_key_pair.assert_called_once_with({"KeypairName": "key-name"}) + + def test_delete_key_pair(self): + key_pair = Mock(name="key-name") + key_pair.name = "key-name" + self._set_response({}) + + self.assertTrue(self.driver.delete_key_pair(key_pair)) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "DeleteKeypair") + self.assertEqual(json.loads(data)["KeypairName"], key_pair.name) + + def test_create_volume_snapshot(self): + volume = Mock(id="vol-1") + expected = Mock() + self.driver._to_snapshot = Mock(return_value=expected) + self._set_response({"Volume": {"SnapshotId": "snap-1"}}) + + result = self.driver.create_volume_snapshot(volume, name="snapshot") + + self.assertEqual(result, expected) + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "CreateSnapshot") + self.assertEqual(json.loads(data)["VolumeId"], volume.id) + + def test_list_volume_snapshots(self): + volume = Mock(id="vol-1") + expected = [Mock()] + self.driver._to_snapshots = Mock(return_value=expected) + self._set_response({"Snapshots": [{"SnapshotId": "snap-1"}]}) + + result = self.driver.list_volume_snapshots(volume) + + self.assertEqual(result, expected) + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "ReadSnapshots") + self.assertEqual(data["Filters"]["VolumeIds"], [volume.id]) + + def test_destroy_volume_snapshot(self): + snapshot = Mock(id="snap-1") + self._set_response({}) + + self.assertTrue(self.driver.destroy_volume_snapshot(snapshot)) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "DeleteSnapshot") + self.assertEqual(json.loads(data)["SnapshotId"], snapshot.id) + + def test_create_volume_uses_location_as_subregion(self): + location = NodeLocation( + id="eu-west-2a", + name="eu-west-2a, France", + country="France", + driver=self.driver, + ) + expected = Mock() + self.driver._to_volume = Mock(return_value=expected) + self._set_response({"Volume": {"VolumeId": "vol-1"}}) + + result = self.driver.create_volume(size=10, name="volume", location=location) + + self.assertEqual(result, expected) + _, data = self.driver._call_api.call_args.args + self.assertEqual(json.loads(data)["SubregionName"], location.id) + + def test_create_volume_prefers_explicit_subregion(self): + location = NodeLocation( + id="eu-west-2a", + name="eu-west-2a, France", + country="France", + driver=self.driver, + ) + + self.driver.create_volume( + size=10, + name="volume", + location=location, + ex_subregion_name="eu-west-2b", + ) + + _, data = self.driver._call_api.call_args.args + self.assertEqual(json.loads(data)["SubregionName"], "eu-west-2b") + + def test_create_volume_requires_location_or_subregion(self): + with self.assertRaisesRegex(ValueError, "location or ex_subregion_name is required"): + self.driver.create_volume(size=10, name="volume") + + self.driver._call_api.assert_not_called() + + def test_list_volumes(self): + expected = [Mock()] + self.driver._to_volumes = Mock(return_value=expected) + self._set_response({"Volumes": [{"VolumeId": "vol-1"}]}) + + result = self.driver.list_volumes() + + self.assertEqual(result, expected) + self.driver._to_volumes.assert_called_once_with([{"VolumeId": "vol-1"}]) + + def test_destroy_volume(self): + volume = Mock(id="vol-1") + self._set_response({}) + + self.assertTrue(self.driver.destroy_volume(volume)) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "DeleteVolume") + self.assertEqual(json.loads(data)["VolumeId"], volume.id) + + def test_attach_volume(self): + node = Mock(id="vm-1") + volume = Mock(id="vol-1") + self._set_response({}) + + self.assertTrue(self.driver.attach_volume(node, volume, device="/dev/sdb")) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "LinkVolume") + self.assertEqual( + json.loads(data), + { + "VmId": node.id, + "VolumeId": volume.id, + "DeviceName": "/dev/sdb", + }, + ) + + def test_detach_volume(self): + volume = Mock(id="vol-1") + self._set_response({}) + + self.assertTrue(self.driver.detach_volume(volume, ex_force_unlink=True)) + + action, data = self.driver._call_api.call_args.args + self.assertEqual(action, "UnlinkVolume") + self.assertEqual(json.loads(data)["VolumeId"], volume.id) + self.assertTrue(json.loads(data)["ForceUnlink"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/libcloud/test/compute/test_vcl.py b/libcloud/test/compute/test_vcl.py index 76ff107455..ce3fc8fb80 100644 --- a/libcloud/test/compute/test_vcl.py +++ b/libcloud/test/compute/test_vcl.py @@ -47,7 +47,7 @@ def test_list_sizes(self): def test_create_node(self): image = self.driver.list_images()[0] - node = self.driver.create_node(image=image) + node = self.driver.create_node(name=None, size=None, image=image) self.assertEqual(node.id, "51") def test_destroy_node(self): diff --git a/libcloud/test/compute/test_vcloud.py b/libcloud/test/compute/test_vcloud.py index 34af005845..86754a5323 100644 --- a/libcloud/test/compute/test_vcloud.py +++ b/libcloud/test/compute/test_vcloud.py @@ -178,7 +178,7 @@ def test_create_node(self): def test_create_node_clone(self): image = self.driver.list_nodes()[0] - node = self.driver.create_node(name="testNode", image=image) + node = self.driver.create_node(size=None, name="testNode", image=image) self.assertTrue(isinstance(node, Node)) self.assertEqual( "https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6a",