diff --git a/docs/_static/images/provider_logos/zerigo.png b/docs/_static/images/provider_logos/zerigo.png deleted file mode 100644 index ddfbd9d7e1..0000000000 Binary files a/docs/_static/images/provider_logos/zerigo.png and /dev/null differ diff --git a/docs/dns/_supported_methods.rst b/docs/dns/_supported_methods.rst index 168a30fdfb..df6ff08fe2 100644 --- a/docs/dns/_supported_methods.rst +++ b/docs/dns/_supported_methods.rst @@ -26,7 +26,6 @@ Provider list zones list records create zone update zone create record `Route53 DNS`_ yes yes yes no yes yes yes yes `Vultr DNS`_ yes yes yes no yes yes yes yes `World Wide DNS`_ yes yes yes yes yes yes yes yes -`Zerigo DNS`_ yes yes yes yes yes yes yes yes `Zonomi DNS`_ yes yes yes no yes no yes yes ================= ========== ============ =========== =========== ============= ============= =========== ============= @@ -53,5 +52,4 @@ Provider list zones list records create zone update zone create record .. _`Route53 DNS`: http://aws.amazon.com/route53/ .. _`Vultr DNS`: https://www.vultr.com .. _`World Wide DNS`: https://www.worldwidedns.net/ -.. _`Zerigo DNS`: http://www.zerigo.com/ .. _`Zonomi DNS`: https://zonomi.com diff --git a/docs/dns/_supported_providers.rst b/docs/dns/_supported_providers.rst index 39da462b90..820b7e3efb 100644 --- a/docs/dns/_supported_providers.rst +++ b/docs/dns/_supported_providers.rst @@ -26,7 +26,6 @@ Provider Documentation Provider Constant Su `Route53 DNS`_ ROUTE53 single region driver :mod:`libcloud.dns.drivers.route53` :class:`Route53DNSDriver` `Vultr DNS`_ :doc:`Click ` VULTR single region driver :mod:`libcloud.dns.drivers.vultr` :class:`VultrDNSDriver` `World Wide DNS`_ :doc:`Click ` WORLDWIDEDNS single region driver :mod:`libcloud.dns.drivers.worldwidedns` :class:`WorldWideDNSDriver` -`Zerigo DNS`_ ZERIGO single region driver :mod:`libcloud.dns.drivers.zerigo` :class:`ZerigoDNSDriver` `Zonomi DNS`_ :doc:`Click ` ZONOMI single region driver :mod:`libcloud.dns.drivers.zonomi` :class:`ZonomiDNSDriver` ================= ========================================= ================= ==================== ======================================== ============================== @@ -53,5 +52,4 @@ Provider Documentation Provider Constant Su .. _`Route53 DNS`: http://aws.amazon.com/route53/ .. _`Vultr DNS`: https://www.vultr.com .. _`World Wide DNS`: https://www.worldwidedns.net/ -.. _`Zerigo DNS`: http://www.zerigo.com/ .. _`Zonomi DNS`: https://zonomi.com diff --git a/libcloud/common/nsone.py b/libcloud/common/nsone.py index f54d2edc7a..07e4e14c58 100644 --- a/libcloud/common/nsone.py +++ b/libcloud/common/nsone.py @@ -24,10 +24,10 @@ class NsOneResponse(JsonResponse): - errors = [] # type: List[Dict] - objects = [] # type: List[Dict] - def __init__(self, response, connection): + self.errors = [] # type: List[Dict] + self.objects = [] # type: List[Dict] + super().__init__(response=response, connection=connection) self.errors, self.objects = self.parse_body_and_errors() if not self.success(): diff --git a/libcloud/common/vultr.py b/libcloud/common/vultr.py index 0415715217..242c572ecf 100644 --- a/libcloud/common/vultr.py +++ b/libcloud/common/vultr.py @@ -21,9 +21,7 @@ __all__ = [ "API_HOST", - "VultrConnection", "VultrException", - "VultrResponse", "DEFAULT_API_VERSION", "VultrResponseV2", "VultrConnectionV2", @@ -37,93 +35,6 @@ DEFAULT_API_VERSION = "2" -class VultrResponse(JsonResponse): - objects = None - error_dict = {} # type: Dict[str, str] - errors = None - ERROR_CODE_MAP = { - 400: "Invalid API location. Check the URL that you are using.", - 403: "Invalid or missing API key. Check that your API key is present" - + " and matches your assigned key.", - 405: "Invalid HTTP method. Check that the method (POST|GET) matches" - + " what the documentation indicates.", - 412: "Request failed. Check the response body for a more detailed" + " description.", - 500: "Internal server error. Try again at a later time.", - 503: "Rate limit hit. API requests are limited to an average of 1/s." - + " Try your request again later.", - } - - def __init__(self, response, connection): - self.errors = [] - super().__init__(response=response, connection=connection) - self.objects, self.errors = self.parse_body_and_errors() - if not self.success(): - raise self._make_excp(self.errors[0]) - - def parse_body_and_errors(self): - """ - Returns JSON data in a python list. - """ - json_objects = [] - errors = [] - - if self.status in self.ERROR_CODE_MAP: - self.error_dict["ERRORCODE"] = self.status - self.error_dict["ERRORMESSAGE"] = self.ERROR_CODE_MAP[self.status] - errors.append(self.error_dict) - - js = super().parse_body() - if isinstance(js, dict): - js = [js] - - json_objects.append(js) - - return (json_objects, errors) - - def _make_excp(self, error): - """ - Convert API error to a VultrException instance - """ - - return VultrException(error["ERRORCODE"], error["ERRORMESSAGE"]) - - def success(self): - return len(self.errors) == 0 - - -class VultrConnection(ConnectionKey): - """ - A connection to the Vultr API - """ - - host = API_HOST - responseCls = VultrResponse - - def add_default_params(self, params): - """ - Returns default params such as api_key which is - needed to perform an action.Returns a dictionary. - Example:/v1/server/upgrade_plan?api_key=self.key - """ - params["api_key"] = self.key - - return params - - def add_default_headers(self, headers): - """ - Returns default headers such as content-type. - Returns a dictionary. - """ - headers["Content-Type"] = "application/x-www-form-urlencoded" - headers["Accept"] = "text/plain" - - return headers - - def set_path(self): - self.path = "/v/" - return self.path - - class VultrResponseV2(JsonResponse): valid_response_codes = [ httplib.OK, diff --git a/libcloud/compute/drivers/linode.py b/libcloud/compute/drivers/linode.py index 5d395cbf71..d6a35cd5ae 100644 --- a/libcloud/compute/drivers/linode.py +++ b/libcloud/compute/drivers/linode.py @@ -27,11 +27,8 @@ """ -import os import re import binascii -import itertools -from copy import copy from datetime import datetime from libcloud.utils.py3 import httplib @@ -43,19 +40,12 @@ NodeDriver, NodeLocation, StorageVolume, - NodeAuthSSHKey, - NodeAuthPassword, ) from libcloud.common.linode import ( - API_ROOT, - LINODE_PLAN_IDS, DEFAULT_API_VERSION, - LINODE_DISK_FILESYSTEMS, LINODE_DISK_FILESYSTEMS_V4, LinodeDisk, - LinodeException, LinodeIPAddress, - LinodeConnection, LinodeExceptionV4, LinodeConnectionV4, ) @@ -85,9 +75,7 @@ def __new__( **kwargs, ): if cls is LinodeNodeDriver: - if api_version == "3.0": - cls = LinodeNodeDriverV3 - elif api_version == "4.0": + if api_version == "4.0": cls = LinodeNodeDriverV4 else: raise NotImplementedError( @@ -96,709 +84,6 @@ def __new__( return super().__new__(cls) -class LinodeNodeDriverV3(LinodeNodeDriver): - """libcloud driver for the Linode API - - Rough mapping of which is which: - - - list_nodes linode.list - - reboot_node linode.reboot - - destroy_node linode.delete - - create_node linode.create, linode.update, - linode.disk.createfromdistribution, - linode.disk.create, linode.config.create, - linode.ip.addprivate, linode.boot - - list_sizes avail.linodeplans - - list_images avail.distributions - - list_locations avail.datacenters - - list_volumes linode.disk.list - - destroy_volume linode.disk.delete - - For more information on the Linode API, be sure to read the reference: - - http://www.linode.com/api/ - """ - - connectionCls = LinodeConnection - _linode_plan_ids = LINODE_PLAN_IDS - _linode_disk_filesystems = LINODE_DISK_FILESYSTEMS - features = {"create_node": ["ssh_key", "password"]} - - def __init__( - self, - key, - secret=None, - secure=True, - host=None, - port=None, - api_version=None, - region=None, - **kwargs, - ): - """Instantiate the driver with the given API key - - :param key: the API key to use (required) - :type key: ``str`` - - :rtype: ``None`` - """ - self.datacenter = None - NodeDriver.__init__(self, key) - - # Converts Linode's state from DB to a NodeState constant. - LINODE_STATES = { - (-2): NodeState.UNKNOWN, # Boot Failed - (-1): NodeState.PENDING, # Being Created - 0: NodeState.PENDING, # Brand New - 1: NodeState.RUNNING, # Running - 2: NodeState.STOPPED, # Powered Off - 3: NodeState.REBOOTING, # Shutting Down - 4: NodeState.UNKNOWN, # Reserved - } - - def list_nodes(self): - """ - List all Linodes that the API key can access - - This call will return all Linodes that the API key in use has access - to. - If a node is in this list, rebooting will work; however, creation and - destruction are a separate grant. - - :return: List of node objects that the API key can access - :rtype: ``list`` of :class:`Node` - """ - params = {"api_action": "linode.list"} - data = self.connection.request(API_ROOT, params=params).objects[0] - return self._to_nodes(data) - - def start_node(self, node): - """ - Boot the given Linode - - """ - params = {"api_action": "linode.boot", "LinodeID": node.id} - self.connection.request(API_ROOT, params=params) - return True - - def stop_node(self, node): - """ - Shutdown the given Linode - - """ - params = {"api_action": "linode.shutdown", "LinodeID": node.id} - self.connection.request(API_ROOT, params=params) - return True - - def reboot_node(self, node): - """ - Reboot the given Linode - - Will issue a shutdown job followed by a boot job, using the last booted - configuration. In most cases, this will be the only configuration. - - :param node: the Linode to reboot - :type node: :class:`Node` - - :rtype: ``bool`` - """ - params = {"api_action": "linode.reboot", "LinodeID": node.id} - self.connection.request(API_ROOT, params=params) - return True - - def destroy_node(self, node): - """Destroy the given Linode - - Will remove the Linode from the account and issue a prorated credit. A - grant for removing Linodes from the account is required, otherwise this - method will fail. - - In most cases, all disk images must be removed from a Linode before the - Linode can be removed; however, this call explicitly skips those - safeguards. There is no going back from this method. - - :param node: the Linode to destroy - :type node: :class:`Node` - - :rtype: ``bool`` - """ - params = { - "api_action": "linode.delete", - "LinodeID": node.id, - "skipChecks": True, - } - self.connection.request(API_ROOT, params=params) - return True - - def create_node( - self, - name, - image, - size, - auth, - location=None, - ex_swap=None, - ex_rsize=None, - ex_kernel=None, - ex_payment=None, - ex_comment=None, - ex_private=False, - lconfig=None, - lroot=None, - lswap=None, - ): - """Create a new Linode, deploy a Linux distribution, and boot - - This call abstracts much of the functionality of provisioning a Linode - and getting it booted. A global grant to add Linodes to the account is - required, as this call will result in a billing charge. - - Note that there is a safety valve of 5 Linodes per hour, in order to - prevent a runaway script from ruining your day. - - :keyword name: the name to assign the Linode (mandatory) - :type name: ``str`` - - :keyword image: which distribution to deploy on the Linode (mandatory) - :type image: :class:`NodeImage` - - :keyword size: the plan size to create (mandatory) - :type size: :class:`NodeSize` - - :keyword auth: an SSH key or root password (mandatory) - :type auth: :class:`NodeAuthSSHKey` or :class:`NodeAuthPassword` - - :keyword location: which datacenter to create the Linode in - :type location: :class:`NodeLocation` - - :keyword ex_swap: size of the swap partition in MB (128) - :type ex_swap: ``int`` - - :keyword ex_rsize: size of the root partition in MB (plan size - swap). - :type ex_rsize: ``int`` - - :keyword ex_kernel: a kernel ID from avail.kernels (Latest 2.6 Stable). - :type ex_kernel: ``str`` - - :keyword ex_payment: one of 1, 12, or 24; subscription length (1) - :type ex_payment: ``int`` - - :keyword ex_comment: a small comment for the configuration (libcloud) - :type ex_comment: ``str`` - - :keyword ex_private: whether or not to request a private IP (False) - :type ex_private: ``bool`` - - :keyword lconfig: what to call the configuration (generated) - :type lconfig: ``str`` - - :keyword lroot: what to call the root image (generated) - :type lroot: ``str`` - - :keyword lswap: what to call the swap space (generated) - :type lswap: ``str`` - - :return: Node representing the newly-created Linode - :rtype: :class:`Node` - """ - auth = self._get_and_check_auth(auth) - - # Pick a location (resolves LIBCLOUD-41 in JIRA) - if location: - chosen = location.id - elif self.datacenter: - chosen = self.datacenter - else: - raise LinodeException(0xFB, "Need to select a datacenter first") - - # Step 0: Parameter validation before we purchase - # We're especially careful here so we don't fail after purchase, rather - # than getting halfway through the process and having the API fail. - - # Plan ID - plans = self.list_sizes() - if size.id not in [p.id for p in plans]: - raise LinodeException(0xFB, "Invalid plan ID -- avail.plans") - - # Payment schedule - payment = "1" if not ex_payment else str(ex_payment) - if payment not in ["1", "12", "24"]: - raise LinodeException(0xFB, "Invalid subscription (1, 12, 24)") - - ssh = None - root = None - # SSH key and/or root password - if isinstance(auth, NodeAuthSSHKey): - ssh = auth.pubkey # pylint: disable=no-member - elif isinstance(auth, NodeAuthPassword): - root = auth.password - - if not ssh and not root: - raise LinodeException(0xFB, "Need SSH key or root password") - if root is not None and len(root) < 6: - raise LinodeException(0xFB, "Root password is too short") - - # Swap size - try: - swap = 128 if not ex_swap else int(ex_swap) - except Exception: - raise LinodeException(0xFB, "Need an integer swap size") - - # Root partition size - imagesize = (size.disk - swap) if not ex_rsize else int(ex_rsize) - if (imagesize + swap) > size.disk: - raise LinodeException(0xFB, "Total disk images are too big") - - # Distribution ID - distros = self.list_images() - if image.id not in [d.id for d in distros]: - raise LinodeException(0xFB, "Invalid distro -- avail.distributions") - - # Kernel - if ex_kernel: - kernel = ex_kernel - else: - if image.extra["64bit"]: - # For a list of available kernel ids, see - # https://www.linode.com/kernels/ - kernel = 138 - else: - kernel = 137 - params = {"api_action": "avail.kernels"} - kernels = self.connection.request(API_ROOT, params=params).objects[0] - if kernel not in [z["KERNELID"] for z in kernels]: - raise LinodeException(0xFB, "Invalid kernel -- avail.kernels") - - # Comments - comments = ( - "Created by Apache libcloud " - if not ex_comment - else ex_comment - ) - - # Step 1: linode.create - params = { - "api_action": "linode.create", - "DatacenterID": chosen, - "PlanID": size.id, - "PaymentTerm": payment, - } - data = self.connection.request(API_ROOT, params=params).objects[0] - linode = {"id": data["LinodeID"]} - - # Step 1b. linode.update to rename the Linode - params = { - "api_action": "linode.update", - "LinodeID": linode["id"], - "Label": name, - } - self.connection.request(API_ROOT, params=params) - - # Step 1c. linode.ip.addprivate if it was requested - if ex_private: - params = {"api_action": "linode.ip.addprivate", "LinodeID": linode["id"]} - self.connection.request(API_ROOT, params=params) - - # Step 1d. Labels - # use the linode id as the name can be up to 63 chars and the labels - # are limited to 48 chars - label = { - "lconfig": "[%s] Configuration Profile" % linode["id"], - "lroot": "[{}] {} Disk Image".format(linode["id"], image.name), - "lswap": "[%s] Swap Space" % linode["id"], - } - - if lconfig: - label["lconfig"] = lconfig - - if lroot: - label["lroot"] = lroot - - if lswap: - label["lswap"] = lswap - - # Step 2: linode.disk.createfromdistribution - if not root: - root = binascii.b2a_base64(os.urandom(8)).decode("ascii").strip() - - params = { - "api_action": "linode.disk.createfromdistribution", - "LinodeID": linode["id"], - "DistributionID": image.id, - "Label": label["lroot"], - "Size": imagesize, - "rootPass": root, - } - if ssh: - params["rootSSHKey"] = ssh - data = self.connection.request(API_ROOT, params=params).objects[0] - linode["rootimage"] = data["DiskID"] - - # Step 3: linode.disk.create for swap - params = { - "api_action": "linode.disk.create", - "LinodeID": linode["id"], - "Label": label["lswap"], - "Type": "swap", - "Size": swap, - } - data = self.connection.request(API_ROOT, params=params).objects[0] - linode["swapimage"] = data["DiskID"] - - # Step 4: linode.config.create for main profile - disks = "{},{},,,,,,,".format(linode["rootimage"], linode["swapimage"]) - params = { - "api_action": "linode.config.create", - "LinodeID": linode["id"], - "KernelID": kernel, - "Label": label["lconfig"], - "Comments": comments, - "DiskList": disks, - } - if ex_private: - params["helper_network"] = True - params["helper_distro"] = True - - data = self.connection.request(API_ROOT, params=params).objects[0] - linode["config"] = data["ConfigID"] - - # Step 5: linode.boot - params = { - "api_action": "linode.boot", - "LinodeID": linode["id"], - "ConfigID": linode["config"], - } - self.connection.request(API_ROOT, params=params) - - # Make a node out of it and hand it back - params = {"api_action": "linode.list", "LinodeID": linode["id"]} - data = self.connection.request(API_ROOT, params=params).objects[0] - nodes = self._to_nodes(data) - - if len(nodes) == 1: - node = nodes[0] - if getattr(auth, "generated", False): - node.extra["password"] = auth.password - return node - - return None - - def ex_resize_node(self, node, size): - """Resizes a Linode from one plan to another - - Immediately shuts the Linode down, charges/credits the account, - and issue a migration to another host server. - Requires a size (numeric), which is the desired PlanID available from - avail.LinodePlans() - After resize is complete the node needs to be booted - """ - - params = {"api_action": "linode.resize", "LinodeID": node.id, "PlanID": size} - self.connection.request(API_ROOT, params=params) - return True - - def ex_start_node(self, node): - # NOTE: This method is here for backward compatibility reasons after - # this method was promoted to be part of the standard compute API in - # Libcloud v2.7.0 - return self.start_node(node=node) - - def ex_stop_node(self, node): - # NOTE: This method is here for backward compatibility reasons after - # this method was promoted to be part of the standard compute API in - # Libcloud v2.7.0 - return self.stop_node(node=node) - - def ex_rename_node(self, node, name): - """Renames a node""" - - params = {"api_action": "linode.update", "LinodeID": node.id, "Label": name} - self.connection.request(API_ROOT, params=params) - return True - - def list_sizes(self, location=None): - """ - List available Linode plans - - Gets the sizes that can be used for creating a Linode. Since available - Linode plans vary per-location, this method can also be passed a - location to filter the availability. - - :keyword location: the facility to retrieve plans in - :type location: :class:`NodeLocation` - - :rtype: ``list`` of :class:`NodeSize` - """ - params = {"api_action": "avail.linodeplans"} - data = self.connection.request(API_ROOT, params=params).objects[0] - sizes = [] - for obj in data: - n = NodeSize( - id=obj["PLANID"], - name=obj["LABEL"], - ram=obj["RAM"], - disk=(obj["DISK"] * 1024), - bandwidth=obj["XFER"], - price=obj["PRICE"], - driver=self.connection.driver, - ) - sizes.append(n) - return sizes - - def list_images(self): - """ - List available Linux distributions - - Retrieve all Linux distributions that can be deployed to a Linode. - - :rtype: ``list`` of :class:`NodeImage` - """ - params = {"api_action": "avail.distributions"} - data = self.connection.request(API_ROOT, params=params).objects[0] - distros = [] - for obj in data: - i = NodeImage( - id=obj["DISTRIBUTIONID"], - name=obj["LABEL"], - driver=self.connection.driver, - extra={"pvops": obj["REQUIRESPVOPSKERNEL"], "64bit": obj["IS64BIT"]}, - ) - distros.append(i) - return distros - - def list_locations(self): - """ - List available facilities for deployment - - Retrieve all facilities that a Linode can be deployed in. - - :rtype: ``list`` of :class:`NodeLocation` - """ - params = {"api_action": "avail.datacenters"} - data = self.connection.request(API_ROOT, params=params).objects[0] - nl = [] - for dc in data: - country = None - if "USA" in dc["LOCATION"]: - country = "US" - elif "UK" in dc["LOCATION"]: - country = "GB" - elif "JP" in dc["LOCATION"]: - country = "JP" - else: - country = "??" - nl.append(NodeLocation(dc["DATACENTERID"], dc["LOCATION"], country, self)) - return nl - - def linode_set_datacenter(self, dc): - """ - Set the default datacenter for Linode creation - - Since Linodes must be created in a facility, this function sets the - default that :class:`create_node` will use. If a location keyword is - not passed to :class:`create_node`, this method must have already been - used. - - :keyword dc: the datacenter to create Linodes in unless specified - :type dc: :class:`NodeLocation` - - :rtype: ``bool`` - """ - did = dc.id - params = {"api_action": "avail.datacenters"} - data = self.connection.request(API_ROOT, params=params).objects[0] - for datacenter in data: - if did == dc["DATACENTERID"]: - self.datacenter = did - return - - dcs = ", ".join([d["DATACENTERID"] for d in data]) - self.datacenter = None - raise LinodeException(0xFD, "Invalid datacenter (use one of %s)" % dcs) - - def destroy_volume(self, volume): - """ - Destroys disk volume for the Linode. Linode id is to be provided as - extra["LinodeId"] within :class:`StorageVolume`. It can be retrieved - by :meth:`libcloud.compute.drivers.linode.LinodeNodeDriver\ - .ex_list_volumes`. - - :param volume: Volume to be destroyed - :type volume: :class:`StorageVolume` - - :rtype: ``bool`` - """ - if not isinstance(volume, StorageVolume): - raise LinodeException(0xFD, "Invalid volume instance") - - if volume.extra["LINODEID"] is None: - raise LinodeException(0xFD, "Missing LinodeID") - - params = { - "api_action": "linode.disk.delete", - "LinodeID": volume.extra["LINODEID"], - "DiskID": volume.id, - } - self.connection.request(API_ROOT, params=params) - - return True - - def ex_create_volume(self, size, name, node, fs_type): - """ - Create disk for the Linode. - - :keyword size: Size of volume in megabytes (required) - :type size: ``int`` - - :keyword name: Name of the volume to be created - :type name: ``str`` - - :keyword node: Node to attach volume to. - :type node: :class:`Node` - - :keyword fs_type: The formatted type of this disk. Valid types are: - ext3, ext4, swap, raw - :type fs_type: ``str`` - - - :return: StorageVolume representing the newly-created volume - :rtype: :class:`StorageVolume` - """ - # check node - if not isinstance(node, Node): - raise LinodeException(0xFD, "Invalid node instance") - - # check space available - total_space = node.extra["TOTALHD"] - existing_volumes = self.ex_list_volumes(node) - used_space = 0 - for volume in existing_volumes: - used_space = used_space + volume.size - - available_space = total_space - used_space - if available_space < size: - raise LinodeException( - 0xFD, - "Volume size too big. Available space\ - %d" - % available_space, - ) - - # check filesystem type - if fs_type not in self._linode_disk_filesystems: - raise LinodeException(0xFD, "Not valid filesystem type") - - params = { - "api_action": "linode.disk.create", - "LinodeID": node.id, - "Label": name, - "Type": fs_type, - "Size": size, - } - data = self.connection.request(API_ROOT, params=params).objects[0] - volume = data["DiskID"] - # Make a volume out of it and hand it back - params = { - "api_action": "linode.disk.list", - "LinodeID": node.id, - "DiskID": volume, - } - data = self.connection.request(API_ROOT, params=params).objects[0] - return self._to_volumes(data)[0] - - def ex_list_volumes(self, node, disk_id=None): - """ - List existing disk volumes for for given Linode. - - :keyword node: Node to list disk volumes for. (required) - :type node: :class:`Node` - - :keyword disk_id: Id for specific disk volume. (optional) - :type disk_id: ``int`` - - :rtype: ``list`` of :class:`StorageVolume` - """ - if not isinstance(node, Node): - raise LinodeException(0xFD, "Invalid node instance") - - params = {"api_action": "linode.disk.list", "LinodeID": node.id} - # Add param if disk_id was specified - if disk_id is not None: - params["DiskID"] = disk_id - - data = self.connection.request(API_ROOT, params=params).objects[0] - return self._to_volumes(data) - - def _to_volumes(self, objs): - """ - Convert returned JSON volumes into StorageVolume instances - - :keyword objs: ``list`` of JSON dictionaries representing the - StorageVolumes - :type objs: ``list`` - - :return: ``list`` of :class:`StorageVolume`s - """ - volumes = {} - for o in objs: - vid = o["DISKID"] - volumes[vid] = vol = StorageVolume( - id=vid, - name=o["LABEL"], - size=int(o["SIZE"]), - driver=self.connection.driver, - ) - vol.extra = copy(o) - return list(volumes.values()) - - def _to_nodes(self, objs): - """Convert returned JSON Linodes into Node instances - - :keyword objs: ``list`` of JSON dictionaries representing the Linodes - :type objs: ``list`` - :return: ``list`` of :class:`Node`s""" - - # Get the IP addresses for the Linodes - nodes = {} - batch = [] - for o in objs: - lid = o["LINODEID"] - nodes[lid] = n = Node( - id=lid, - name=o["LABEL"], - public_ips=[], - private_ips=[], - state=self.LINODE_STATES[o["STATUS"]], - driver=self.connection.driver, - ) - n.extra = copy(o) - n.extra["PLANID"] = self._linode_plan_ids.get(o.get("TOTALRAM")) - batch.append({"api_action": "linode.ip.list", "LinodeID": lid}) - - # Avoid batch limitation - ip_answers = [] - args = [iter(batch)] * 25 - - for twenty_five in itertools.zip_longest(*args): - twenty_five = [q for q in twenty_five if q] - params = { - "api_action": "batch", - "api_requestArray": json.dumps(twenty_five), - } - req = self.connection.request(API_ROOT, params=params) - if not req.success() or len(req.objects) == 0: - return None - ip_answers.extend(req.objects) - - # Add the returned IPs to the nodes and return them - for ip_list in ip_answers: - for ip in ip_list: - lid = ip["LINODEID"] - which = nodes[lid].public_ips if ip["ISPUBLIC"] == 1 else nodes[lid].private_ips - which.append(ip["IPADDRESS"]) - return list(nodes.values()) - - class LinodeNodeDriverV4(LinodeNodeDriver): connectionCls = LinodeConnectionV4 _linode_disk_filesystems = LINODE_DISK_FILESYSTEMS_V4 diff --git a/libcloud/compute/drivers/vultr.py b/libcloud/compute/drivers/vultr.py index f07f0d2d02..db8ea02414 100644 --- a/libcloud/compute/drivers/vultr.py +++ b/libcloud/compute/drivers/vultr.py @@ -40,7 +40,6 @@ StorageVolume, ) from libcloud.compute.types import Provider, NodeState, StorageVolumeState, VolumeSnapshotState -from libcloud.utils.iso8601 import parse_date from libcloud.utils.publickey import get_pubkey_openssh_fingerprint # For matching region by id @@ -750,9 +749,7 @@ def __new__( **kwargs, ): if cls is VultrNodeDriver: - if api_version == "1": - cls = VultrNodeDriverV1 - elif api_version == "2": + if api_version == "2": cls = VultrNodeDriverV2 else: raise NotImplementedError( @@ -761,333 +758,6 @@ def __new__( return super().__new__(cls) -class VultrNodeDriverV1(VultrNodeDriver): - """ - VultrNode node driver. - """ - - connectionCls = VultrConnection - - NODE_STATE_MAP = {"pending": NodeState.PENDING, "active": NodeState.RUNNING} - - EX_CREATE_YES_NO_ATTRIBUTES = [ - "enable_ipv6", - "enable_private_network", - "auto_backups", - "notify_activate", - "ddos_protection", - ] - - EX_CREATE_ID_ATTRIBUTES = { - "iso_id": "ISOID", - "script_id": "SCRIPTID", - "snapshot_id": "SNAPSHOTID", - "app_id": "APPID", - } - - EX_CREATE_ATTRIBUTES = [ - "ipxe_chain_url", - "label", - "userdata", - "reserved_ip_v4", - "hostname", - "tag", - ] - EX_CREATE_ATTRIBUTES.extend(EX_CREATE_YES_NO_ATTRIBUTES) - EX_CREATE_ATTRIBUTES.extend(EX_CREATE_ID_ATTRIBUTES.keys()) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._helper = VultrNodeDriverHelper() - - def list_nodes(self): - return self._list_resources("/v1/server/list", self._to_node) - - def list_key_pairs(self): - """ - List all the available SSH keys. - :return: Available SSH keys. - :rtype: ``list`` of :class:`SSHKey` - """ - return self._list_resources("/v1/sshkey/list", self._to_ssh_key) - - def create_key_pair(self, name, public_key=""): - """ - Create a new SSH key. - :param name: Name of the new SSH key - :type name: ``str`` - - :key public_key: Public part of the new SSH key - :type name: ``str`` - - :return: True on success - :rtype: ``bool`` - """ - params = {"name": name, "ssh_key": public_key} - res = self.connection.post("/v1/sshkey/create", params) - return res.status == httplib.OK - - def delete_key_pair(self, key_pair): - """ - Delete an SSH key. - :param key_pair: The SSH key to delete - :type key_pair: :class:`SSHKey` - - :return: True on success - :rtype: ``bool`` - """ - params = {"SSHKEYID": key_pair.id} - res = self.connection.post("/v1/sshkey/destroy", params) - return res.status == httplib.OK - - def list_locations(self): - return self._list_resources("/v1/regions/list", self._to_location) - - def list_sizes(self): - return self._list_resources("/v1/plans/list", self._to_size) - - def list_images(self): - 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): - """ - Create a node - - :param name: Name for the new node - :type name: ``str`` - - :param size: Size of the new node - :type size: :class:`NodeSize` - - :param image: Image for the new node - :type image: :class:`NodeImage` - - :param location: Location of the new node - :type location: :class:`NodeLocation` - - :param ex_ssh_key_ids: IDs of the SSH keys to initialize - :type ex_sshkeyid: ``list`` of ``str`` - - :param ex_create_attr: Extra attributes for node creation - :type ex_create_attr: ``dict`` - - The `ex_create_attr` parameter can include the following dictionary - key and value pairs: - - * `ipxe_chain_url`: ``str`` for specifying URL to boot via IPXE - * `iso_id`: ``str`` the ID of a specific ISO to mount, - only meaningful with the `Custom` `NodeImage` - * `script_id`: ``int`` ID of a startup script to execute on boot, - only meaningful when the `NodeImage` is not `Custom` - * 'snapshot_id`: ``str`` Snapshot ID to restore for the initial - installation, only meaningful with the `Snapshot` `NodeImage` - * `enable_ipv6`: ``bool`` Whether an IPv6 subnet should be assigned - * `enable_private_network`: ``bool`` Whether private networking - support should be added - * `label`: ``str`` Text label to be shown in the control panel - * `auto_backups`: ``bool`` Whether automatic backups should be enabled - * `app_id`: ``int`` App ID to launch if launching an application, - only meaningful when the `NodeImage` is `Application` - * `userdata`: ``str`` Base64 encoded cloud-init user-data - * `notify_activate`: ``bool`` Whether an activation email should be - sent when the server is ready - * `ddos_protection`: ``bool`` Whether DDOS protection should be enabled - * `reserved_ip_v4`: ``str`` IP address of the floating IP to use as - the main IP of this server - * `hostname`: ``str`` The hostname to assign to this server - * `tag`: ``str`` The tag to assign to this server - - :return: The newly created node. - :rtype: :class:`Node` - - """ - params = { - "DCID": location.id, - "VPSPLANID": size.id, - "OSID": image.id, - "label": name, - } - - if ex_ssh_key_ids is not None: - params["SSHKEYID"] = ",".join(ex_ssh_key_ids) - - ex_create_attr = ex_create_attr or {} - for key, value in ex_create_attr.items(): - if key in self.EX_CREATE_ATTRIBUTES: - if key in self.EX_CREATE_YES_NO_ATTRIBUTES: - params[key] = "yes" if value else "no" - else: - if key in self.EX_CREATE_ID_ATTRIBUTES: - key = self.EX_CREATE_ID_ATTRIBUTES[key] - params[key] = value - - result = self.connection.post("/v1/server/create", params) - if result.status != httplib.OK: - return False - - subid = result.object["SUBID"] - - retry_count = 3 - created_node = None - - for _ in range(retry_count): - try: - nodes = self.list_nodes() - created_node = [n for n in nodes if n.id == subid][0] - except IndexError: - time.sleep(1) - else: - break - - return created_node - - def reboot_node(self, node): - params = {"SUBID": node.id} - res = self.connection.post("/v1/server/reboot", params) - - return res.status == httplib.OK - - def destroy_node(self, node): - params = {"SUBID": node.id} - res = self.connection.post("/v1/server/destroy", params) - - return res.status == httplib.OK - - def _list_resources(self, url, tranform_func): - data = self.connection.get(url).object - sorted_key = sorted(data) - return [tranform_func(data[key]) for key in sorted_key] - - def _to_node(self, data): - if "status" in data: - state = self.NODE_STATE_MAP.get(data["status"], NodeState.UNKNOWN) - if state == NodeState.RUNNING and data["power_status"] != "running": - state = NodeState.STOPPED - else: - state = NodeState.UNKNOWN - - if "main_ip" in data and data["main_ip"] is not None: - public_ips = [data["main_ip"]] - else: - public_ips = [] - # simple check that we have ip address in value - if len(data["internal_ip"]) > 0: - private_ips = [data["internal_ip"]] - else: - private_ips = [] - created_at = parse_date(data["date_created"]) - - # response ordering - extra_keys = [ - "location", # Location name - "default_password", - "pending_charges", - "cost_per_month", - "current_bandwidth_gb", - "allowed_bandwidth_gb", - "netmask_v4", - "gateway_v4", - "power_status", - "server_state", - "v6_networks", - # TODO: Does we really need kvm_url? - "kvm_url", - "auto_backups", - "tag", - # "OSID", # Operating system to use. See v1/os/list. - "APPID", - "FIREWALLGROUPID", - ] - extra = self._helper.handle_extra(extra_keys, data) - - resolve_data = VULTR_COMPUTE_INSTANCE_IMAGES.get(data["OSID"]) - if resolve_data: - image = self._to_image(resolve_data) - else: - image = None - - resolve_data = VULTR_COMPUTE_INSTANCE_SIZES.get(data["VPSPLANID"]) - if resolve_data: - size = self._to_size(resolve_data) - else: - size = None - - # resolve_data = VULTR_COMPUTE_INSTANCE_LOCATIONS.get(data['DCID']) - # if resolve_data: - # location = self._to_location(resolve_data) - # extra['location'] = location - - node = Node( - id=data["SUBID"], - name=data["label"], - state=state, - public_ips=public_ips, - private_ips=private_ips, - image=image, - size=size, - extra=extra, - created_at=created_at, - driver=self, - ) - - return node - - def _to_location(self, data): - extra_keys = [ - "continent", - "state", - "ddos_protection", - "block_storage", - "regioncode", - ] - extra = self._helper.handle_extra(extra_keys, data) - - return NodeLocation( - id=data["DCID"], - name=data["name"], - country=data["country"], - extra=extra, - driver=self, - ) - - def _to_size(self, data): - extra_keys = [ - "vcpu_count", - "plan_type", - "available_locations", - ] - extra = self._helper.handle_extra(extra_keys, data) - - # backward compatibility - if extra.get("vcpu_count").isdigit(): - extra["vcpu_count"] = int(extra["vcpu_count"]) - - ram = int(data["ram"]) - disk = int(data["disk"]) - # NodeSize accepted int instead float - bandwidth = int(float(data["bandwidth"])) - price = float(data["price_per_month"]) - return NodeSize( - id=data["VPSPLANID"], - name=data["name"], - ram=ram, - disk=disk, - bandwidth=bandwidth, - price=price, - extra=extra, - driver=self, - ) - - def _to_image(self, data): - extra_keys = ["arch", "family"] - extra = self._helper.handle_extra(extra_keys, data) - return NodeImage(id=data["OSID"], name=data["name"], extra=extra, driver=self) - - def _to_ssh_key(self, data): - return SSHKey(id=data["SSHKEYID"], name=data["name"], pub_key=data["ssh_key"]) - - class VultrNodeDriverV2(VultrNodeDriver): """ Vultr API v2 NodeDriver. diff --git a/libcloud/dns/base.py b/libcloud/dns/base.py index f878447449..245acbd1c0 100644 --- a/libcloud/dns/base.py +++ b/libcloud/dns/base.py @@ -14,11 +14,13 @@ # limitations under the License. +import re import datetime from typing import Any, Dict, List, Type, Union, Iterator, Optional +from collections import namedtuple from libcloud import __version__ -from libcloud.dns.types import RecordType +from libcloud.dns.types import RecordType, RecordDoesNotExistError from libcloud.common.base import BaseDriver, Connection, ConnectionUserAndKey __all__ = ["Zone", "Record", "DNSDriver"] @@ -58,7 +60,7 @@ def __init__( :type extra: ``dict`` """ self.id = str(id) if id else None - self.domain = domain + self.domain = self.unrooted(domain) self.type = type self.ttl = ttl or None self.driver = driver @@ -102,6 +104,80 @@ def __repr__(self): self.driver.name, ) + @staticmethod + def unrooted(domain): + """Return the provided domain as an unrooted domain""" + return domain.rstrip(".") + + @staticmethod + def rooted(domain): + """Return the provided domain as an unrooted domain""" + return domain if domain.endswith(".") else f"{domain}." + + def prefix(self, subname): + """ + Accept subordinate (or identity) names in multiple convenience formats. + + In the following examples, "www" is returned: + + | Format | subname | self.domain | + |-----------------|--------------------|---------------| + | Bare host name | "www" | "example.com" | + | FQDN (unrooted) | "www.example.com" | "example.com" | + | FQDN (rooted) | "www.example.com." | "example.com" | + + :param subname: Hostname or FQDN. + :type subname: ``str`` + + :return: Bare record name, without domain part. + :rtype: ``str`` + """ + + return subname.rstrip(".").removesuffix(self.domain).rstrip(".") + + def hostname(self, subname): + """ + Accept subordinate (or identity) names in multiple convenience formats. + + In the following examples, "www.example.com" is returned: + + | Format | subname | self.domain | + |-----------------|--------------------|---------------| + | Bare host name | "www" | "example.com" | + | FQDN (unrooted) | "www.example.com" | "example.com" | + | FQDN (rooted) | "www.example.com." | "example.com" | + + :param subname: Hostname or FQDN. + :type subname: ``str`` + + :return: Complete hostname, including domain part. + :rtype: ``str`` + """ + + prefix = self.prefix(subname) + return f"{prefix}.{self.domain}" if prefix else self.domain + + def fqdn(self, subname): + """ + Accept subordinate (or identity) names in multiple convenience formats. + + In the following examples, "www.example.com." is returned: + + | Format | subname | self.domain | + |-----------------|--------------------|---------------| + | Bare host name | "www" | "example.com" | + | FQDN (unrooted) | "www.example.com" | "example.com" | + | FQDN (rooted) | "www.example.com." | "example.com" | + + :param subname: Hostname or FQDN. + :type subname: ``str`` + + :return: Complete hostname, including domain part. + :rtype: ``str`` + """ + + return f"{self.hostname(subname)}." + class Record: """ @@ -145,7 +221,17 @@ def __init__( :type extra: ``dict`` """ self.id = str(id) if id else None - self.name = name + + # Support callers that have: + # 1. used None, rather than the empty string to indicate apex/naked + # records, while consistently setting the empty string. + # 2. used the full hostname (i.e. including domain), rather than the + # bare prefix (i.e. excluding domain) + # 3. used the fully qualified domain name (i.e. including domain and + # trailing dot), rather than the bare prefix. + # + self.name = "" if name is None else zone.prefix(name) + self.type = type self.data = data self.zone = zone @@ -186,6 +272,19 @@ def _get_numeric_id(self): return record_id + @property + def hostname(self): + """Return the complete hostname, including domain, for this record.""" + return self.zone.hostname(self.name) + + @property + def fqdn(self): + """ + Return the traditional fully qualified domain name, including full-stop + trailing dot, for this record. + """ + return self.zone.fqdn(self.name) + def __repr__(self): # type: () -> str zone = self.zone.domain if self.zone.domain else self.zone.id @@ -213,6 +312,11 @@ class DNSDriver(BaseDriver): # Map libcloud record type enum to provider record type name RECORD_TYPE_MAP = {} # type: Dict[RecordType, str] + # "A" -> type: "A", prefix: None + # "A:www" -> type: "A", prefix: "www" + DEFAULT_ID_RE = re.compile(r"(?P[A-Z]+)(\:(?P.+))?") + DefaultID = namedtuple("DefaultID", "type,name") + def __init__( self, key, # type: str @@ -576,3 +680,19 @@ def _string_to_record_type(self, string): string = string.upper() record_type = getattr(RecordType, string) return record_type + + @staticmethod + def to_default_id(zone, name, type): + prefix = zone.prefix(name) + return f"{type}:{prefix}" if prefix else str(type) + + @classmethod + def from_default_id(cls, zone, record_id): + match = cls.DEFAULT_ID_RE.match(record_id) + if match is None: + raise RecordDoesNotExistError( + value="malformed record ID", driver=cls, record_id=record_id + ) + + name = match.group("name") + return cls.DefaultID(match.group("type"), "" if name is None else zone.prefix(name)) diff --git a/libcloud/dns/drivers/dnsimple.py b/libcloud/dns/drivers/dnsimple.py index 11bb5d5848..68c8208923 100644 --- a/libcloud/dns/drivers/dnsimple.py +++ b/libcloud/dns/drivers/dnsimple.py @@ -16,6 +16,8 @@ DNSimple DNS Driver """ +# FIXME: v1 API was deprecated on May 31, 2018 + __all__ = ["DNSimpleDNSDriver"] try: diff --git a/libcloud/dns/drivers/durabledns.py b/libcloud/dns/drivers/durabledns.py index 23dfd7563c..9c4ef73a6f 100644 --- a/libcloud/dns/drivers/durabledns.py +++ b/libcloud/dns/drivers/durabledns.py @@ -293,7 +293,7 @@ def create_zone(self, domain, type="master", ttl=None, extra=None): params = { "apiuser": self.key, "apikey": self.secret, - "zonename": domain, + "zonename": Zone.rooted(domain), "ttl": ttl or DEFAULT_TTL, } params.update(extra) @@ -312,7 +312,7 @@ def create_zone(self, domain, type="master", ttl=None, extra=None): req_data = skel % ( self.key, self.secret, - domain, + Zone.rooted(domain), extra.get("ns"), extra.get("mbox"), extra.get("refresh"), @@ -366,6 +366,7 @@ def create_record(self, name, zone, type, data, extra=None): :rtype: :class:`Record` """ + name = zone.prefix(name) if extra is None: extra = RECORD_EXTRA_PARAMS_DEFAULT_VALUES else: @@ -476,7 +477,7 @@ def update_zone(self, zone, domain, type="master", ttl=None, extra=None): params = { "apiuser": self.key, "apikey": self.secret, - "zonename": domain, + "zonename": Zone.rooted(domain), "ttl": ttl, } params.update(extra) @@ -495,7 +496,7 @@ def update_zone(self, zone, domain, type="master", ttl=None, extra=None): req_data = skel % ( self.key, self.secret, - domain, + Zone.rooted(domain), extra["ns"], extra["mbox"], extra["refresh"], @@ -545,6 +546,7 @@ def update_record(self, record, name, type, data, extra=None): :rtype: :class:`Record` """ zone = record.zone + name = zone.prefix(name) if extra is None: extra = record.extra else: diff --git a/libcloud/dns/drivers/gandi.py b/libcloud/dns/drivers/gandi.py index 675bec0858..68fbd7b3b4 100644 --- a/libcloud/dns/drivers/gandi.py +++ b/libcloud/dns/drivers/gandi.py @@ -157,7 +157,7 @@ def _to_record(self, record, zone): extra["priority"] = int(split[0]) value = split[1] return Record( - id="{}:{}".format(record["type"], record["name"]), + id=self.to_default_id(zone, record["name"], record["type"]), name=record["name"], type=self._string_to_record_type(record["type"]), data=value, @@ -181,15 +181,16 @@ def list_records(self, zone): def get_record(self, zone_id, record_id): zid = int(zone_id) - record_type, name = record_id.split(":", 1) - filter_opts = {"name": name, "type": record_type} + zone = self.get_zone(zone_id) + rparts = self.from_default_id(zone, record_id) + filter_opts = {"name": rparts.name, "type": rparts.type} self.connection.set_context({"zone_id": zone_id}) records = self.connection.request("domain.zone.record.list", zid, 0, filter_opts).object if len(records) == 0: raise RecordDoesNotExistError(value="", driver=self, record_id=record_id) - return self._to_record(records[0], self.get_zone(zone_id)) + return self._to_record(records[0], zone) def _validate_record(self, record_id, name, record_type, data, extra): if len(data) > 1024: diff --git a/libcloud/dns/drivers/gandi_live.py b/libcloud/dns/drivers/gandi_live.py index 09df141a5d..76631bc956 100644 --- a/libcloud/dns/drivers/gandi_live.py +++ b/libcloud/dns/drivers/gandi_live.py @@ -146,15 +146,16 @@ def list_records(self, zone): """ def get_record(self, zone_id, record_id): - record_type, name = record_id.split(":", 1) - action = "{}/domains/{}/records/{}/{}".format(API_BASE, zone_id, name, record_type) + zone = self.get_zone(zone_id) + rparts = self.from_default_id(zone, record_id) + action = "{}/domains/{}/records/{}/{}".format(API_BASE, zone_id, rparts.name, rparts.type) try: record = self.connection.request(action=action, method="GET") except ResourceNotFoundError: raise RecordDoesNotExistError( value="", driver=self.connection.driver, record_id=record_id ) - return self._to_record(record.object, self.get_zone(zone_id))[0] + return self._to_record(record.object, zone)[0] def create_record(self, name, zone, type, data, extra=None): self._validate_record(None, name, type, data, extra) @@ -420,7 +421,7 @@ def _to_record_sub(self, data, zone, value): priority, value = value.split() extra["priority"] = priority return Record( - id="{}:{}".format(data["rrset_type"], data["rrset_name"]), + id=self.to_default_id(zone, data["rrset_name"], data["rrset_type"]), name=data["rrset_name"], type=self._string_to_record_type(data["rrset_type"]), data=value, diff --git a/libcloud/dns/drivers/google.py b/libcloud/dns/drivers/google.py index 376186debc..3ecacab552 100644 --- a/libcloud/dns/drivers/google.py +++ b/libcloud/dns/drivers/google.py @@ -134,11 +134,12 @@ def get_record(self, zone_id, record_id): :rtype: :class:`Record` """ - (record_type, record_name) = record_id.split(":", 1) + zone = self.get_zone(zone_id) + parts = self.from_default_id(zone, record_id) params = { - "name": record_name, - "type": record_type, + "name": parts.name, + "type": parts.type, } request = "/managedZones/%s/rrsets" % (zone_id) @@ -149,8 +150,6 @@ def get_record(self, zone_id, record_id): raise ZoneDoesNotExistError(value="", driver=self.connection.driver, zone_id=zone_id) if len(response["rrsets"]) > 0: - zone = self.get_zone(zone_id) - return self._to_record(response["rrsets"][0], zone) raise RecordDoesNotExistError(value="", driver=self.connection.driver, record_id=record_id) @@ -185,7 +184,7 @@ def create_zone(self, domain, type="master", ttl=None, extra=None): name = self._cleanup_domain(domain) data = { - "dnsName": domain, + "dnsName": Zone.rooted(domain), "name": name, "description": description, } @@ -216,6 +215,7 @@ def create_record(self, name, zone, type, data, extra=None): :rtype: :class:`Record` """ + name = zone.fqdn(name) ttl = data.get("ttl", 0) rrdatas = data.get("rrdatas", []) @@ -253,7 +253,7 @@ def delete_record(self, record): data = { "deletions": [ { - "name": record.name, + "name": record.fqdn, "type": record.type, "rrdatas": record.data["rrdatas"], "ttl": record.data["ttl"], @@ -385,10 +385,8 @@ def _to_records(self, response, zone): return records def _to_record(self, r, zone): - record_id = "{}:{}".format(r["type"], r["name"]) - return Record( - id=record_id, + id=self.to_default_id(zone, r["name"], r["type"]), name=r["name"], type=r["type"], data=r, diff --git a/libcloud/dns/drivers/linode.py b/libcloud/dns/drivers/linode.py index b2f609f254..b93e743b88 100644 --- a/libcloud/dns/drivers/linode.py +++ b/libcloud/dns/drivers/linode.py @@ -20,13 +20,11 @@ from libcloud.dns.base import Zone, Record, DNSDriver from libcloud.dns.types import Provider, RecordType, ZoneDoesNotExistError, RecordDoesNotExistError from libcloud.utils.py3 import httplib -from libcloud.utils.misc import get_new_obj, merge_valid_keys +from libcloud.utils.misc import merge_valid_keys from libcloud.common.linode import ( - API_ROOT, DEFAULT_API_VERSION, LinodeResponse, LinodeException, - LinodeConnection, LinodeResponseV4, LinodeExceptionV4, LinodeConnectionV4, @@ -88,9 +86,7 @@ def __new__( **kwargs, ): if cls is LinodeDNSDriver: - if api_version == "3.0": - cls = LinodeDNSDriverV3 - elif api_version == "4.0": + if api_version == "4.0": cls = LinodeDNSDriverV4 else: raise NotImplementedError( @@ -117,258 +113,6 @@ def _make_excp(self, error): return result -class LinodeDNSConnection(LinodeConnection): - responseCls = LinodeDNSResponse - - -class LinodeDNSDriverV3(LinodeDNSDriver): - connectionCls = LinodeDNSConnection - - RECORD_TYPE_MAP = { - RecordType.NS: "NS", - RecordType.MX: "MX", - RecordType.A: "A", - RecordType.AAAA: "AAAA", - RecordType.CNAME: "CNAME", - RecordType.TXT: "TXT", - RecordType.SRV: "SRV", - } - - def list_zones(self): - params = {"api_action": "domain.list"} - data = self.connection.request(API_ROOT, params=params).objects[0] - zones = self._to_zones(data) - return zones - - def list_records(self, zone): - params = {"api_action": "domain.resource.list", "DOMAINID": zone.id} - - self.connection.set_context(context={"resource": "zone", "id": zone.id}) - data = self.connection.request(API_ROOT, params=params).objects[0] - records = self._to_records(items=data, zone=zone) - return records - - def get_zone(self, zone_id): - params = {"api_action": "domain.list", "DomainID": zone_id} - self.connection.set_context(context={"resource": "zone", "id": zone_id}) - data = self.connection.request(API_ROOT, params=params).objects[0] - zones = self._to_zones(data) - - if len(zones) != 1: - raise ZoneDoesNotExistError(value="", driver=self, zone_id=zone_id) - - return zones[0] - - def get_record(self, zone_id, record_id): - zone = self.get_zone(zone_id=zone_id) - params = { - "api_action": "domain.resource.list", - "DomainID": zone_id, - "ResourceID": record_id, - } - self.connection.set_context(context={"resource": "record", "id": record_id}) - data = self.connection.request(API_ROOT, params=params).objects[0] - records = self._to_records(items=data, zone=zone) - - if len(records) != 1: - raise RecordDoesNotExistError(value="", driver=self, record_id=record_id) - - return records[0] - - def create_zone(self, domain, type="master", ttl=None, extra=None): - """ - Create a new zone. - - API docs: http://www.linode.com/api/dns/domain.create - """ - params = {"api_action": "domain.create", "Type": type, "Domain": domain} - - if ttl: - params["TTL_sec"] = ttl - - merged = merge_valid_keys(params=params, valid_keys=VALID_ZONE_EXTRA_PARAMS, extra=extra) - data = self.connection.request(API_ROOT, params=params).objects[0] - zone = Zone( - id=data["DomainID"], - domain=domain, - type=type, - ttl=ttl, - extra=merged, - driver=self, - ) - return zone - - def update_zone(self, zone, domain=None, type=None, ttl=None, extra=None): - """ - Update an existing zone. - - API docs: http://www.linode.com/api/dns/domain.update - """ - params = {"api_action": "domain.update", "DomainID": zone.id} - - if type: - params["Type"] = type - - if domain: - params["Domain"] = domain - - if ttl: - params["TTL_sec"] = ttl - - merged = merge_valid_keys(params=params, valid_keys=VALID_ZONE_EXTRA_PARAMS, extra=extra) - self.connection.request(API_ROOT, params=params).objects[0] - updated_zone = get_new_obj( - obj=zone, - klass=Zone, - attributes={"domain": domain, "type": type, "ttl": ttl, "extra": merged}, - ) - return updated_zone - - def create_record(self, name, zone, type, data, extra=None): - """ - Create a new record. - - API docs: http://www.linode.com/api/dns/domain.resource.create - """ - params = { - "api_action": "domain.resource.create", - "DomainID": zone.id, - "Name": name, - "Target": data, - "Type": self.RECORD_TYPE_MAP[type], - } - merged = merge_valid_keys(params=params, valid_keys=VALID_RECORD_EXTRA_PARAMS, extra=extra) - - result = self.connection.request(API_ROOT, params=params).objects[0] - record = Record( - id=result["ResourceID"], - name=name, - type=type, - data=data, - extra=merged, - zone=zone, - driver=self, - ttl=merged.get("TTL_sec", None), - ) - return record - - def update_record(self, record, name=None, type=None, data=None, extra=None): - """ - Update an existing record. - - API docs: http://www.linode.com/api/dns/domain.resource.update - """ - params = { - "api_action": "domain.resource.update", - "ResourceID": record.id, - "DomainID": record.zone.id, - } - - if name: - params["Name"] = name - - if data: - params["Target"] = data - - if type is not None: - params["Type"] = self.RECORD_TYPE_MAP[type] - - merged = merge_valid_keys(params=params, valid_keys=VALID_RECORD_EXTRA_PARAMS, extra=extra) - - self.connection.request(API_ROOT, params=params).objects[0] - updated_record = get_new_obj( - obj=record, - klass=Record, - attributes={"name": name, "data": data, "type": type, "extra": merged}, - ) - return updated_record - - def delete_zone(self, zone): - params = {"api_action": "domain.delete", "DomainID": zone.id} - - self.connection.set_context(context={"resource": "zone", "id": zone.id}) - data = self.connection.request(API_ROOT, params=params).objects[0] - - return "DomainID" in data - - def delete_record(self, record): - params = { - "api_action": "domain.resource.delete", - "DomainID": record.zone.id, - "ResourceID": record.id, - } - - self.connection.set_context(context={"resource": "record", "id": record.id}) - data = self.connection.request(API_ROOT, params=params).objects[0] - - return "ResourceID" in data - - def _to_zones(self, items): - """ - Convert a list of items to the Zone objects. - """ - zones = [] - - for item in items: - zones.append(self._to_zone(item)) - - return zones - - def _to_zone(self, item): - """ - Build an Zone object from the item dictionary. - """ - extra = { - "SOA_Email": item["SOA_EMAIL"], - "status": item["STATUS"], - "description": item["DESCRIPTION"], - } - zone = Zone( - id=item["DOMAINID"], - domain=item["DOMAIN"], - type=item["TYPE"], - ttl=item["TTL_SEC"], - driver=self, - extra=extra, - ) - return zone - - def _to_records(self, items, zone=None): - """ - Convert a list of items to the Record objects. - """ - records = [] - - for item in items: - records.append(self._to_record(item=item, zone=zone)) - - return records - - def _to_record(self, item, zone=None): - """ - Build a Record object from the item dictionary. - """ - extra = { - "protocol": item["PROTOCOL"], - "ttl_sec": item["TTL_SEC"], - "port": item["PORT"], - "weight": item["WEIGHT"], - "priority": item["PRIORITY"], - } - type = self._string_to_record_type(item["TYPE"]) - record = Record( - id=item["RESOURCEID"], - name=item["NAME"], - type=type, - data=item["TARGET"], - zone=zone, - driver=self, - ttl=item["TTL_SEC"], - extra=extra, - ) - return record - - class LinodeDNSResponseV4(LinodeResponseV4): pass diff --git a/libcloud/dns/drivers/luadns.py b/libcloud/dns/drivers/luadns.py index ccb1cdfb58..9d5f26e34b 100644 --- a/libcloud/dns/drivers/luadns.py +++ b/libcloud/dns/drivers/luadns.py @@ -233,7 +233,7 @@ def create_record(self, name, zone, type, data, extra=None): :rtype: :class:`Record` """ action = "/v1/zones/%s/records" % zone.id - to_post = {"name": name, "content": data, "type": type, "zone_id": int(zone.id)} + to_post = {"name": zone.fqdn(name), "content": data, "type": type, "zone_id": int(zone.id)} # ttl is required to create a record for luadns # pass it through extra like this: extra={'ttl':ttl} if extra is not None: diff --git a/libcloud/dns/drivers/nsone.py b/libcloud/dns/drivers/nsone.py index 58c012197d..28b03d3843 100644 --- a/libcloud/dns/drivers/nsone.py +++ b/libcloud/dns/drivers/nsone.py @@ -163,12 +163,15 @@ def get_record(self, zone_id, record_id): :param zone_id: The id of the zone where to search for the record (e.g. example.com) :type zone_id: ``str`` - :param record_id: The type of record to search for - (e.g. A, AAA, MX etc) + :param record_id: The record type joined with the record + name by a colon, if non-empty (e.g. A, AAAA:www, MX:mail, etc) :return: :class:`Record` """ - action = "/v1/zones/{}/{}/{}".format(zone_id, zone_id, record_id) + zone = self.get_zone(zone_id=zone_id) + parts = self.from_default_id(zone, record_id) + rdomain = zone.hostname(parts.name) + action = f"/v1/zones/{zone.domain}/{rdomain}/{parts.type}" try: response = self.connection.request(action=action, method="GET") except NsOneException as e: @@ -176,7 +179,6 @@ def get_record(self, zone_id, record_id): raise RecordDoesNotExistError(value=e.message, driver=self, record_id=record_id) else: raise e - zone = self.get_zone(zone_id=zone_id) record = self._to_record(item=response.parse_body(), zone=zone) return record @@ -188,7 +190,7 @@ def delete_record(self, record): :return: Boolean """ - action = "/v1/zones/{}/{}/{}".format(record.zone.domain, record.name, record.type) + action = "/v1/zones/{}/{}/{}".format(record.zone.domain, record.hostname, record.type) try: response = self.connection.request(action=action, method="DELETE") except NsOneException as e: @@ -213,7 +215,7 @@ def create_record(self, name, zone, type, data, extra=None): :type extra: ``dict`` :return: :class:`Record` """ - record_name = "{}.{}".format(name, zone.domain) if name != "" else zone.domain # noqa + record_name = zone.hostname(name) action = "/v1/zones/{}/{}/{}".format(zone.domain, record_name, type) if type == RecordType.MX: @@ -258,7 +260,7 @@ def update_record(self, record, name, type, data, extra=None): zone = record.zone action = "/v1/zones/{}/{}/{}".format( zone.domain, - "{}.{}".format(name, zone.domain), + zone.hostname(name), type, ) raw_data = {"answers": [{"answer": [data]}]} @@ -300,9 +302,10 @@ def _to_zone(self, item): if key not in common_attr: extra[key] = item.get(key) + zdomain = item["zone"] zone = Zone( - domain=item["zone"], - id=item["id"], + domain=zdomain, + id=zdomain, type=item.get("type"), extra=extra, ttl=extra.get("ttl"), @@ -328,10 +331,12 @@ def _to_record(self, item, zone): data = item.get("answers")[0]["answer"] else: data = item.get("short_answers") + rdomain = item["domain"] + rtype = item["type"] record = Record( - id=item["id"], - name=item["domain"], - type=item["type"], + id=self.to_default_id(zone, rdomain, rtype), + name=rdomain, + type=rtype, data=data, zone=zone, driver=self, diff --git a/libcloud/dns/drivers/pointdns.py b/libcloud/dns/drivers/pointdns.py index 98dbfb8aa5..26d4d2d8b8 100644 --- a/libcloud/dns/drivers/pointdns.py +++ b/libcloud/dns/drivers/pointdns.py @@ -280,7 +280,7 @@ def create_record(self, name, zone, type, data, extra=None): :rtype: :class:`Record` """ - r_json = {"name": name, "data": data, "record_type": type} + r_json = {"name": zone.hostname(name), "data": data, "record_type": type} if extra is not None: r_json.update(extra) r_data = json.dumps({"zone_record": r_json}) @@ -354,7 +354,7 @@ def update_record(self, record, name, type, data, extra=None): :rtype: :class:`Record` """ zone = record.zone - r_json = {"name": name, "data": data, "record_type": type} + r_json = {"name": zone.hostname(name), "data": data, "record_type": type} if extra is not None: r_json.update(extra) r_data = json.dumps({"zone_record": r_json}) diff --git a/libcloud/dns/drivers/powerdns.py b/libcloud/dns/drivers/powerdns.py index fd9a2dc743..92101cec74 100644 --- a/libcloud/dns/drivers/powerdns.py +++ b/libcloud/dns/drivers/powerdns.py @@ -185,18 +185,19 @@ def create_record(self, name, zone, type, data, extra=None): if extra is None or extra.get("ttl", None) is None: raise ValueError("PowerDNS requires a ttl value for every record") + hostname = zone.hostname(name) if self._pdns_version() == 3: record = { "content": data, "disabled": False, - "name": name, + "name": hostname, "ttl": extra["ttl"], "type": type, } payload = { "rrsets": [ { - "name": name, + "name": hostname, "type": type, "changetype": "REPLACE", "records": [record], @@ -212,7 +213,7 @@ def create_record(self, name, zone, type, data, extra=None): payload = { "rrsets": [ { - "name": name, + "name": hostname, "type": type, "changetype": "REPLACE", "ttl": extra["ttl"], @@ -426,19 +427,20 @@ def update_record(self, record, name, type, data, extra=None): if extra is None or extra.get("ttl", None) is None: raise ValueError("PowerDNS requires a ttl value for every record") + hostname = record.zone.hostname(name) if self._pdns_version() == 3: updated_record = { "content": data, "disabled": False, - "name": name, + "name": hostname, "ttl": extra["ttl"], "type": type, } payload = { "rrsets": [ - {"name": record.name, "type": record.type, "changetype": "DELETE"}, + {"name": record.hostname, "type": record.type, "changetype": "DELETE"}, { - "name": name, + "name": hostname, "type": type, "changetype": "REPLACE", "records": [updated_record], @@ -457,7 +459,7 @@ def update_record(self, record, name, type, data, extra=None): payload = { "rrsets": [ { - "name": name, + "name": hostname, "type": type, "changetype": "REPLACE", "ttl": extra["ttl"], diff --git a/libcloud/dns/drivers/rackspace.py b/libcloud/dns/drivers/rackspace.py index e0edd9c53d..479712e0bf 100644 --- a/libcloud/dns/drivers/rackspace.py +++ b/libcloud/dns/drivers/rackspace.py @@ -292,7 +292,7 @@ def create_record(self, name, zone, type, data, extra=None): # name is "bar.foo.com" extra = extra if extra else {} - name = self._to_full_record_name(domain=zone.domain, name=name) + name = zone.hostname(name) data = {"name": name, "type": self.RECORD_TYPE_MAP[type], "data": data} if "ttl" in extra: @@ -314,8 +314,7 @@ def update_record(self, record, name=None, type=None, data=None, extra=None): # attribute must always be present. extra = extra if extra else {} - name = self._to_full_record_name(domain=record.zone.domain, name=record.name) - payload = {"name": name} + payload = {"name": record.hostname} if data: payload["data"] = data @@ -552,7 +551,6 @@ def _to_zone(self, data): def _to_record(self, data, zone): id = data["id"] fqdn = data["name"] - name = self._to_partial_record_name(domain=zone.domain, name=fqdn) type = self._string_to_record_type(data["type"]) record_data = data["data"] extra = {"fqdn": fqdn} @@ -563,7 +561,7 @@ def _to_record(self, data, zone): record = Record( id=str(id), - name=name, + name=fqdn, type=type, data=record_data, zone=zone, @@ -586,42 +584,6 @@ def _to_ptr_record(self, data, link): record = RackspacePTRRecord(id=str(id), ip=ip, domain=domain, driver=self, extra=extra) return record - def _to_full_record_name(self, domain, name): - """ - Build a FQDN from a domain and record name. - - :param domain: Domain name. - :type domain: ``str`` - - :param name: Record name. - :type name: ``str`` - """ - if name: - name = "{}.{}".format(name, domain) - else: - name = domain - - return name - - def _to_partial_record_name(self, domain, name): - """ - Remove domain portion from the record name. - - :param domain: Domain name. - :type domain: ``str`` - - :param name: Full record name (fqdn). - :type name: ``str`` - """ - if name == domain: - # Map "root" record names to None to be consistent with other - # drivers - return None - - # Strip domain portion - name = name.replace(".%s" % (domain), "") - return name - def _ex_connection_class_kwargs(self): kwargs = self.openstack_connection_kwargs() kwargs["region"] = self.region diff --git a/libcloud/dns/drivers/rcodezero.py b/libcloud/dns/drivers/rcodezero.py index 21a79a87ee..8f5829e60c 100644 --- a/libcloud/dns/drivers/rcodezero.py +++ b/libcloud/dns/drivers/rcodezero.py @@ -31,6 +31,8 @@ "RcodeZeroDNSDriver", ] +# FIXME: v1 API is deprecated + class RcodeZeroResponse(JsonResponse): def success(self): diff --git a/libcloud/dns/drivers/route53.py b/libcloud/dns/drivers/route53.py index 6140cc6cb6..79be93da2d 100644 --- a/libcloud/dns/drivers/route53.py +++ b/libcloud/dns/drivers/route53.py @@ -129,14 +129,11 @@ def get_zone(self, zone_id): def get_record(self, zone_id, record_id): zone = self.get_zone(zone_id=zone_id) - record_type, name = record_id.split(":", 1) - - if name: - full_name = ".".join((name, zone.domain)) - else: - full_name = zone.domain + rparts = self.from_default_id(zone, record_id) self.connection.set_context({"zone_id": zone_id}) - params = urlencode({"name": full_name, "type": record_type, "maxitems": "1"}) + params = urlencode( + {"name": zone.hostname(rparts.name), "type": rparts.type, "maxitems": "1"} + ) uri = API_ROOT + "hostedzone/" + zone_id + "/rrset?" + params data = self.connection.request(uri).object @@ -145,9 +142,9 @@ def get_record(self, zone_id, record_id): # A cute aspect of the /rrset filters is that they are more pagination # hints than filters!! # So will return a result even if its not what you asked for. - record_type_num = self._string_to_record_type(record_type) + record_type_num = self._string_to_record_type(rparts.type) - if record.name != name or record.type != record_type_num: + if record.name != rparts.name or record.type != record_type_num: raise RecordDoesNotExistError(value="", driver=self, record_id=record_id) return record @@ -186,10 +183,9 @@ def create_record(self, name, zone, type, data, extra=None): extra = extra or {} batch = [("CREATE", name, type, data, extra)] self._post_changeset(zone, batch) - id = ":".join((self.RECORD_TYPE_MAP[type], name)) return Record( - id=id, + id=self.to_default_id(zone, name, type), name=name, type=type, data=data, @@ -221,10 +217,8 @@ def update_record(self, record, name=None, type=None, data=None, extra=None): record=record, name=name, type=type, data=data, extra=extra ) - id = ":".join((self.RECORD_TYPE_MAP[type], name)) - return Record( - id=id, + id=self.to_default_id(record.zone, name, type), name=name, type=type, data=data, @@ -262,7 +256,7 @@ def ex_create_multi_value_record(self, name, zone, type, data, extra=None): ET.SubElement(change, "Action").text = "CREATE" rrs = ET.SubElement(change, "ResourceRecordSet") - ET.SubElement(rrs, "Name").text = name + "." + zone.domain + ET.SubElement(rrs, "Name").text = zone.hostname(name) ET.SubElement(rrs, "Type").text = self.RECORD_TYPE_MAP[type] ET.SubElement(rrs, "TTL").text = str(extra.get("ttl", "0")) @@ -280,13 +274,11 @@ def ex_create_multi_value_record(self, name, zone, type, data, extra=None): self.connection.set_context({"zone_id": zone.id}) self.connection.request(uri, method="POST", data=data) - id = ":".join((self.RECORD_TYPE_MAP[type], name)) - records = [] for value in values: record = Record( - id=id, + id=self.to_default_id(zone, name, type), name=name, type=type, data=value, @@ -338,12 +330,7 @@ def _update_multi_value_record(self, record, name=None, type=None, data=None, ex rrs = ET.SubElement(change, "ResourceRecordSet") - if record.name: - record_name = record.name + "." + record.zone.domain - else: - record_name = record.zone.domain - - ET.SubElement(rrs, "Name").text = record_name + ET.SubElement(rrs, "Name").text = record.hostname ET.SubElement(rrs, "Type").text = self.RECORD_TYPE_MAP[record.type] ET.SubElement(rrs, "TTL").text = str(record.extra.get("ttl", "0")) @@ -363,12 +350,7 @@ def _update_multi_value_record(self, record, name=None, type=None, data=None, ex rrs = ET.SubElement(change, "ResourceRecordSet") - if name: - record_name = name + "." + record.zone.domain - else: - record_name = record.zone.domain - - ET.SubElement(rrs, "Name").text = record_name + ET.SubElement(rrs, "Name").text = record.zone.hostname(name) ET.SubElement(rrs, "Type").text = self.RECORD_TYPE_MAP[type] ET.SubElement(rrs, "TTL").text = str(extra.get("ttl", "0")) @@ -399,12 +381,7 @@ def _post_changeset(self, zone, changes_list): rrs = ET.SubElement(change, "ResourceRecordSet") - if name: - record_name = name + "." + zone.domain - else: - record_name = zone.domain - - ET.SubElement(rrs, "Name").text = record_name + ET.SubElement(rrs, "Name").text = zone.hostname(name) ET.SubElement(rrs, "Type").text = self.RECORD_TYPE_MAP[type_] ET.SubElement(rrs, "TTL").text = str(extra.get("ttl", "0")) @@ -528,9 +505,8 @@ def _to_record(self, elem, zone, index=0): extra["weight"] = int(weight) extra["port"] = int(port) - id = ":".join((self.RECORD_TYPE_MAP[type], name)) record = Record( - id=id, + id=self.to_default_id(zone, name, type), name=name, type=type, data=data, diff --git a/libcloud/dns/drivers/vultr.py b/libcloud/dns/drivers/vultr.py index e0fae847f6..d7b330ce89 100644 --- a/libcloud/dns/drivers/vultr.py +++ b/libcloud/dns/drivers/vultr.py @@ -22,24 +22,17 @@ from libcloud.dns.types import ( Provider, RecordType, - ZoneDoesNotExistError, - ZoneAlreadyExistsError, - RecordDoesNotExistError, - RecordAlreadyExistsError, ) -from libcloud.utils.py3 import urlencode from libcloud.common.vultr import ( DEFAULT_API_VERSION, - VultrResponse, - VultrConnection, VultrResponseV2, VultrConnectionV2, ) __all__ = [ "ZoneRequiredException", - "VultrDNSResponse", - "VultrDNSConnection", + "VultrDNSResponseV2", + "VultrDNSConnectionV2", "VultrDNSDriver", ] @@ -48,14 +41,6 @@ class ZoneRequiredException(Exception): pass -class VultrDNSResponse(VultrResponse): - pass - - -class VultrDNSConnection(VultrConnection): - responseCls = VultrDNSResponse - - class VultrDNSResponseV2(VultrResponseV2): pass @@ -81,9 +66,7 @@ def __new__( **kwargs, ): if cls is VultrDNSDriver: - if api_version == "1": - cls = VultrDNSDriverV1 - elif api_version == "2": + if api_version == "2": cls = VultrDNSDriverV2 else: raise NotImplementedError( @@ -93,362 +76,6 @@ def __new__( return super().__new__(cls) -class VultrDNSDriverV1(VultrDNSDriver): - connectionCls = VultrDNSConnection - - RECORD_TYPE_MAP = { - RecordType.A: "A", - RecordType.AAAA: "AAAA", - RecordType.TXT: "TXT", - RecordType.CNAME: "CNAME", - RecordType.MX: "MX", - RecordType.NS: "NS", - RecordType.SRV: "SRV", - } - - def list_zones(self): - """ - Return a list of records for the provided zone. - - :param zone: Zone to list records for. - :type zone: :class:`Zone` - - :return: ``list`` of :class:`Record` - """ - action = "/v1/dns/list" - params = {"api_key": self.key} - response = self.connection.request(action=action, params=params) - zones = self._to_zones(response.objects[0]) - - return zones - - def list_records(self, zone): - """ - Returns a list of records for the provided zone. - - :param zone: zone to list records for - :type zone: `Zone` - - :rtype: list of :class: `Record` - """ - - if not isinstance(zone, Zone): - raise ZoneRequiredException("zone should be of type Zone") - - zones = self.list_zones() - - if not self.ex_zone_exists(zone.domain, zones): - raise ZoneDoesNotExistError(value="", driver=self, zone_id=zone.domain) - - action = "/v1/dns/records" - params = {"domain": zone.domain} - response = self.connection.request(action=action, params=params) - records = self._to_records(response.objects[0], zone=zone) - - return records - - def get_zone(self, zone_id): - """ - Returns a `Zone` instance. - - :param zone_id: name of the zone user wants to get. - :type zone_id: ``str`` - - :rtype: :class:`Zone` - """ - ret_zone = None - - action = "/v1/dns/list" - params = {"api_key": self.key} - response = self.connection.request(action=action, params=params) - zones = self._to_zones(response.objects[0]) - - if not self.ex_zone_exists(zone_id, zones): - raise ZoneDoesNotExistError(value=None, zone_id=zone_id, driver=self) - - for zone in zones: - if zone_id == zone.domain: - ret_zone = zone - - return ret_zone - - def get_record(self, zone_id, record_id): - """ - Returns a Record instance. - - :param zone_id: name of the required zone - :type zone_id: ``str`` - - :param record_id: ID of the required record - :type record_id: ``str`` - - :rtype: :class: `Record` - """ - ret_record = None - zone = self.get_zone(zone_id=zone_id) - records = self.list_records(zone=zone) - - if not self.ex_record_exists(record_id, records): - raise RecordDoesNotExistError(value="", driver=self, record_id=record_id) - - for record in records: - if record_id == record.id: - ret_record = record - - return ret_record - - def create_zone(self, domain, type="master", ttl=None, extra=None): - """ - Returns a `Zone` object. - - :param domain: Zone domain name, (e.g. example.com). - :type domain: ``str`` - - :param type: Zone type (master / slave). - :type type: ``str`` - - :param ttl: TTL for new records. (optional) - :type ttl: ``int`` - - :param extra: (optional) Extra attributes (driver specific). - (e.g. {'serverip':'127.0.0.1'}) - """ - extra = extra or {} - - if extra and extra.get("serverip"): - serverip = extra["serverip"] - else: - raise ValueError("Missing servertip key in extra") - - params = {"api_key": self.key} - data = urlencode({"domain": domain, "serverip": serverip}) - action = "/v1/dns/create_domain" - zones = self.list_zones() - - if self.ex_zone_exists(domain, zones): - raise ZoneAlreadyExistsError(value="", driver=self, zone_id=domain) - - self.connection.request(params=params, action=action, data=data, method="POST") - zone = Zone(id=domain, domain=domain, type=type, ttl=ttl, driver=self, extra=extra) - - return zone - - def create_record(self, name, zone, type, data, extra=None): - """ - Create a new record. - - :param name: Record name without the domain name (e.g. www). - Note: If you want to create a record for a base domain - name, you should specify empty string ('') for this - argument. - :type name: ``str`` - - :param zone: Zone where the requested record is created. - :type zone: :class:`Zone` - - :param type: DNS record type (A, AAAA, ...). - :type type: :class:`RecordType` - - :param data: Data for the record (depends on the record type). - :type data: ``str`` - - :param extra: Extra attributes (driver specific). (optional) - :type extra: ``dict`` - - :rtype: :class:`Record` - """ - extra = extra or {} - - ret_record = None - old_records_list = self.list_records(zone=zone) - # check if record already exists - # if exists raise RecordAlreadyExistsError - - for record in old_records_list: - if record.name == name and record.data == data: - raise RecordAlreadyExistsError(value="", driver=self, record_id=record.id) - - MX = self.RECORD_TYPE_MAP.get("MX") - SRV = self.RECORD_TYPE_MAP.get("SRV") - - if extra and extra.get("priority"): - priority = int(extra["priority"]) - else: - priority = None - - post_data = { - "domain": zone.domain, - "name": name, - "type": self.RECORD_TYPE_MAP.get(type), - "data": data, - } - - if type == MX or type == SRV: - if priority is None: - raise ValueError("Missing priority argument for MX record type") - post_data["priority"] = priority - - encoded_data = urlencode(post_data) - params = {"api_key": self.key} - action = "/v1/dns/create_record" - - self.connection.request(action=action, params=params, data=encoded_data, method="POST") - updated_zone_records = zone.list_records() - - for record in updated_zone_records: - if record.name == name and record.data == data: - ret_record = record - - return ret_record - - def delete_zone(self, zone): - """ - Delete a zone. - - Note: This will delete all the records belonging to this zone. - - :param zone: Zone to delete. - :type zone: :class:`Zone` - - :rtype: ``bool`` - """ - action = "/v1/dns/delete_domain" - params = {"api_key": self.key} - data = urlencode({"domain": zone.domain}) - zones = self.list_zones() - - if not self.ex_zone_exists(zone.domain, zones): - raise ZoneDoesNotExistError(value="", driver=self, zone_id=zone.domain) - - response = self.connection.request(params=params, action=action, data=data, method="POST") - - return response.status == 200 - - def delete_record(self, record): - """ - Delete a record. - - :param record: Record to delete. - :type record: :class:`Record` - - :rtype: ``bool`` - """ - action = "/v1/dns/delete_record" - params = {"api_key": self.key} - data = urlencode({"RECORDID": record.id, "domain": record.zone.domain}) - - zone_records = self.list_records(record.zone) - - if not self.ex_record_exists(record.id, zone_records): - raise RecordDoesNotExistError(value="", driver=self, record_id=record.id) - - response = self.connection.request(action=action, params=params, data=data, method="POST") - - return response.status == 200 - - def ex_zone_exists(self, zone_id, zones_list): - """ - Function to check if a `Zone` object exists. - - :param zone_id: Name of the `Zone` object. - :type zone_id: ``str`` - - :param zones_list: A list containing `Zone` objects - :type zones_list: ``list`` - - :rtype: Returns `True` or `False` - """ - - zone_ids = [] - - for zone in zones_list: - zone_ids.append(zone.domain) - - return zone_id in zone_ids - - def ex_record_exists(self, record_id, records_list): - """ - :param record_id: Name of the `Record` object. - :type record_id: ``str`` - - :param records_list: A list containing `Record` objects - :type records_list: ``list`` - - :rtype: ``bool`` - """ - record_ids = [] - - for record in records_list: - record_ids.append(record.id) - - return record_id in record_ids - - def _to_zone(self, item): - """ - Build an object `Zone` from the item dictionary - - :param item: item to build the zone from - :type item: `dictionary` - - :rtype: :instance: `Zone` - """ - type = "master" - extra = {"date_created": item["date_created"]} - - zone = Zone( - id=item["domain"], - domain=item["domain"], - driver=self, - type=type, - ttl=None, - extra=extra, - ) - - return zone - - def _to_zones(self, items): - """ - Returns a list of `Zone` objects. - - :param: items: a list that contains dictionary objects to be passed - to the _to_zone function. - :type items: ``list`` - """ - zones = [] - - for item in items: - zones.append(self._to_zone(item)) - - return zones - - def _to_record(self, item, zone): - extra = {} - - if item.get("priority"): - extra["priority"] = item["priority"] - - type = self._string_to_record_type(item["type"]) - record = Record( - id=item["RECORDID"], - name=item["name"], - type=type, - data=item["data"], - zone=zone, - driver=self, - extra=extra, - ) - - return record - - def _to_records(self, items, zone): - records = [] - - for item in items: - records.append(self._to_record(item, zone=zone)) - - return records - - class VultrDNSDriverV2(VultrDNSDriver): connectionCls = VultrDNSConnectionV2 diff --git a/libcloud/dns/drivers/worldwidedns.py b/libcloud/dns/drivers/worldwidedns.py index 80593b499c..70a4155f45 100644 --- a/libcloud/dns/drivers/worldwidedns.py +++ b/libcloud/dns/drivers/worldwidedns.py @@ -226,18 +226,18 @@ def update_zone(self, zone, domain, type="master", ttl=None, extra=None, ex_raw= if extra is not None: params.update(extra) + method = "GET" if ex_raw: action = "/api_dns_modify_raw.asp" if self.reseller_id is not None: action = "/api_dns_modify_raw_reseller.asp" method = "POST" + elif self.reseller_id is not None: + params["ID"] = self.reseller_id + action = "/api_dns_modify_reseller.asp" else: action = "/api_dns_modify.asp" - - if self.reseller_id is not None: - action = "/api_dns_modify_reseller.asp" - method = "GET" response = self.connection.request(action, params=params, method=method) # noqa zone = self.get_zone(zone.id) @@ -262,15 +262,15 @@ def update_record(self, record, name, type, data, extra=None): :param data: Data for the record (depends on the record type). :type data: ``str`` - :param extra: Contains 'entry' Entry position (1 thru 40) + :param extra: Extra attributes (driver specific). (optional). :type extra: ``dict`` :rtype: :class:`Record` """ - if (extra is None) or ("entry" not in extra): - raise WorldWideDNSError(value="You must enter 'entry' parameter", driver=self) - record_id = extra.get("entry") + record_id = record.id + if "entry" in extra and str(extra["entry"]) != record_id: + raise WorldWideDNSError(value="Inconsistent 'entry' parameter", driver=self) if name == "": name = "@" @@ -330,10 +330,12 @@ def create_zone(self, domain, type="master", ttl=None, extra=None): else: dyn = 1 params = {"DOMAIN": domain, "TYPE": _type} - action = "/api_dns_new_domain.asp" - if self.reseller_id is not None: + if self.reseller_id is None: params["DYN"] = dyn + action = "/api_dns_new_domain.asp" + else: + params["ID"] = self.reseller_id action = "/api_dns_new_domain_reseller.asp" self.connection.request(action, params=params) zone = self.get_zone(domain) @@ -435,13 +437,7 @@ def delete_record(self, record): :rtype: ``bool`` """ zone = record.zone - - for index in range(MAX_RECORD_ENTRIES): - if record.name == zone.extra["S%s" % (index + 1)]: - entry = index + 1 - - break - extra = {"S%s" % entry: "", "T%s" % entry: "NONE", "D%s" % entry: ""} + extra = {"S%s" % record.id: "", "T%s" % record.id: "NONE", "D%s" % record.id: ""} self.update_zone(zone, zone.domain, extra=extra) return True diff --git a/libcloud/dns/drivers/zerigo.py b/libcloud/dns/drivers/zerigo.py deleted file mode 100644 index 0f37e0f357..0000000000 --- a/libcloud/dns/drivers/zerigo.py +++ /dev/null @@ -1,503 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__all__ = ["ZerigoDNSDriver"] - - -import copy -import base64 - -from libcloud.dns.base import Zone, Record, DNSDriver -from libcloud.dns.types import Provider, RecordType, ZoneDoesNotExistError, RecordDoesNotExistError -from libcloud.utils.py3 import ET, b, httplib -from libcloud.utils.xml import findall, findtext -from libcloud.utils.misc import get_new_obj, merge_valid_keys -from libcloud.common.base import XmlResponse, ConnectionUserAndKey -from libcloud.common.types import LibcloudError, InvalidCredsError, MalformedResponseError - -API_HOST = "ns.zerigo.com" -API_VERSION = "1.1" -API_ROOT = "/api/%s/" % (API_VERSION) - -VALID_ZONE_EXTRA_PARAMS = ["notes", "tag-list", "ns1", "slave-nameservers"] -VALID_RECORD_EXTRA_PARAMS = ["notes", "ttl", "priority"] - -# Number of items per page (maximum limit is 1000) -ITEMS_PER_PAGE = 100 - - -class ZerigoError(LibcloudError): - def __init__(self, code, errors): - self.code = code - self.errors = errors or [] - - def __str__(self): - return "Errors: %s" % (", ".join(self.errors)) - - def __repr__(self): - return "".format( - self.code, - len(self.errors), - ) - - -class ZerigoDNSResponse(XmlResponse): - def success(self): - return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] - - def parse_error(self): - status = int(self.status) - - if status == 401: - if not self.body: - raise InvalidCredsError(str(self.status) + ": " + self.error) - else: - raise InvalidCredsError(self.body) - elif status == 404: - context = self.connection.context - - if context["resource"] == "zone": - raise ZoneDoesNotExistError(value="", driver=self, zone_id=context["id"]) - elif context["resource"] == "record": - raise RecordDoesNotExistError(value="", driver=self, record_id=context["id"]) - elif status != 503: - try: - body = ET.XML(self.body) - except Exception: - raise MalformedResponseError("Failed to parse XML", body=self.body) - - errors = [] - - for error in findall(element=body, xpath="error"): - errors.append(error.text) - - raise ZerigoError(code=status, errors=errors) - - return self.body - - -class ZerigoDNSConnection(ConnectionUserAndKey): - host = API_HOST - secure = True - responseCls = ZerigoDNSResponse - - def add_default_headers(self, headers): - auth_b64 = base64.b64encode(b("{}:{}".format(self.user_id, self.key))) - headers["Authorization"] = "Basic %s" % (auth_b64.decode("utf-8")) - - return headers - - def request(self, action, params=None, data="", headers=None, method="GET"): - if not headers: - headers = {} - - if not params: - params = {} - - if method in ("POST", "PUT"): - headers = {"Content-Type": "application/xml; charset=UTF-8"} - - return super().request( - action=action, params=params, data=data, method=method, headers=headers - ) - - -class ZerigoDNSDriver(DNSDriver): - type = Provider.ZERIGO - name = "Zerigo DNS" - website = "http://www.zerigo.com/" - connectionCls = ZerigoDNSConnection - - RECORD_TYPE_MAP = { - RecordType.A: "A", - RecordType.AAAA: "AAAA", - RecordType.CNAME: "CNAME", - RecordType.GEO: "GEO", - RecordType.MX: "MX", - RecordType.NAPTR: "NAPTR", - RecordType.NS: "NS", - RecordType.PTR: "PTR", - RecordType.REDIRECT: "REDIRECT", - RecordType.SPF: "SPF", - RecordType.SRV: "SRV", - RecordType.TXT: "TXT", - RecordType.URL: "URL", - } - - def iterate_zones(self): - return self._get_more("zones") - - def iterate_records(self, zone): - return self._get_more("records", zone=zone) - - def get_zone(self, zone_id): - path = API_ROOT + "zones/%s.xml" % (zone_id) - self.connection.set_context({"resource": "zone", "id": zone_id}) - data = self.connection.request(path).object - zone = self._to_zone(elem=data) - - return zone - - def get_record(self, zone_id, record_id): - zone = self.get_zone(zone_id=zone_id) - self.connection.set_context({"resource": "record", "id": record_id}) - path = API_ROOT + "hosts/%s.xml" % (record_id) - data = self.connection.request(path).object - record = self._to_record(elem=data, zone=zone) - - return record - - def create_zone(self, domain, type="master", ttl=None, extra=None): - """ - Create a new zone. - - Provider API docs: - https://www.zerigo.com/docs/apis/dns/1.1/zones/create - - @inherits: :class:`DNSDriver.create_zone` - """ - path = API_ROOT + "zones.xml" - zone_elem = self._to_zone_elem(domain=domain, type=type, ttl=ttl, extra=extra) - data = self.connection.request( - action=path, data=ET.tostring(zone_elem), method="POST" - ).object - zone = self._to_zone(elem=data) - - return zone - - def update_zone(self, zone, domain=None, type=None, ttl=None, extra=None): - """ - Update an existing zone. - - Provider API docs: - https://www.zerigo.com/docs/apis/dns/1.1/zones/update - - @inherits: :class:`DNSDriver.update_zone` - """ - - if domain: - raise LibcloudError("Domain cannot be changed", driver=self) - - path = API_ROOT + "zones/%s.xml" % (zone.id) - zone_elem = self._to_zone_elem(domain=domain, type=type, ttl=ttl, extra=extra) - response = self.connection.request(action=path, data=ET.tostring(zone_elem), method="PUT") - assert response.status == httplib.OK - - merged = merge_valid_keys( - params=copy.deepcopy(zone.extra), - valid_keys=VALID_ZONE_EXTRA_PARAMS, - extra=extra, - ) - updated_zone = get_new_obj( - obj=zone, klass=Zone, attributes={"type": type, "ttl": ttl, "extra": merged} - ) - - return updated_zone - - def create_record(self, name, zone, type, data, extra=None): - """ - Create a new record. - - Provider API docs: - https://www.zerigo.com/docs/apis/dns/1.1/hosts/create - - @inherits: :class:`DNSDriver.create_record` - """ - path = API_ROOT + "zones/%s/hosts.xml" % (zone.id) - record_elem = self._to_record_elem(name=name, type=type, data=data, extra=extra) - response = self.connection.request( - action=path, data=ET.tostring(record_elem), method="POST" - ) - assert response.status == httplib.CREATED - record = self._to_record(elem=response.object, zone=zone) - - return record - - def update_record(self, record, name=None, type=None, data=None, extra=None): - path = API_ROOT + "hosts/%s.xml" % (record.id) - record_elem = self._to_record_elem(name=name, type=type, data=data, extra=extra) - response = self.connection.request(action=path, data=ET.tostring(record_elem), method="PUT") - assert response.status == httplib.OK - - merged = merge_valid_keys( - params=copy.deepcopy(record.extra), - valid_keys=VALID_RECORD_EXTRA_PARAMS, - extra=extra, - ) - updated_record = get_new_obj( - obj=record, - klass=Record, - attributes={"type": type, "data": data, "extra": merged}, - ) - - return updated_record - - def delete_zone(self, zone): - path = API_ROOT + "zones/%s.xml" % (zone.id) - self.connection.set_context({"resource": "zone", "id": zone.id}) - response = self.connection.request(action=path, method="DELETE") - - return response.status == httplib.OK - - def delete_record(self, record): - path = API_ROOT + "hosts/%s.xml" % (record.id) - self.connection.set_context({"resource": "record", "id": record.id}) - response = self.connection.request(action=path, method="DELETE") - - return response.status == httplib.OK - - def ex_get_zone_by_domain(self, domain): - """ - Retrieve a zone object by the domain name. - - :param domain: The domain which should be used - :type domain: ``str`` - - :rtype: :class:`Zone` - """ - path = API_ROOT + "zones/%s.xml" % (domain) - self.connection.set_context({"resource": "zone", "id": domain}) - data = self.connection.request(path).object - zone = self._to_zone(elem=data) - - return zone - - def ex_force_slave_axfr(self, zone): - """ - Force a zone transfer. - - :param zone: Zone which should be used. - :type zone: :class:`Zone` - - :rtype: :class:`Zone` - """ - path = API_ROOT + "zones/%s/force_slave_axfr.xml" % (zone.id) - self.connection.set_context({"resource": "zone", "id": zone.id}) - response = self.connection.request(path, method="POST") - assert response.status == httplib.ACCEPTED - - return zone - - def _to_zone_elem(self, domain=None, type=None, ttl=None, extra=None): - zone_elem = ET.Element("zone", {}) - - if domain: - domain_elem = ET.SubElement(zone_elem, "domain") - domain_elem.text = domain - - if type: - ns_type_elem = ET.SubElement(zone_elem, "ns-type") - - if type == "master": - ns_type_elem.text = "pri_sec" - elif type == "slave": - if not extra or "ns1" not in extra: - raise LibcloudError( - "ns1 extra attribute is required " + "when zone type is slave", - driver=self, - ) - - ns_type_elem.text = "sec" - ns1_elem = ET.SubElement(zone_elem, "ns1") - ns1_elem.text = extra["ns1"] - elif type == "std_master": - # TODO: Each driver should provide supported zone types - # Slave name servers are elsewhere - - if not extra or "slave-nameservers" not in extra: - raise LibcloudError( - "slave-nameservers extra " - + "attribute is required whenzone " - + "type is std_master", - driver=self, - ) - - ns_type_elem.text = "pri" - slave_nameservers_elem = ET.SubElement(zone_elem, "slave-nameservers") - slave_nameservers_elem.text = extra["slave-nameservers"] - - if ttl: - default_ttl_elem = ET.SubElement(zone_elem, "default-ttl") - default_ttl_elem.text = str(ttl) - - if extra and "tag-list" in extra: - tags = extra["tag-list"] - - tags_elem = ET.SubElement(zone_elem, "tag-list") - tags_elem.text = " ".join(tags) - - return zone_elem - - def _to_record_elem(self, name=None, type=None, data=None, extra=None): - record_elem = ET.Element("host", {}) - - if name: - name_elem = ET.SubElement(record_elem, "hostname") - name_elem.text = name - - if type is not None: - type_elem = ET.SubElement(record_elem, "host-type") - type_elem.text = self.RECORD_TYPE_MAP[type] - - if data: - data_elem = ET.SubElement(record_elem, "data") - data_elem.text = data - - if extra: - if "ttl" in extra: - ttl_elem = ET.SubElement(record_elem, "ttl", {"type": "integer"}) - ttl_elem.text = str(extra["ttl"]) - - if "priority" in extra: - # Only MX and SRV records support priority - priority_elem = ET.SubElement(record_elem, "priority", {"type": "integer"}) - - priority_elem.text = str(extra["priority"]) - - if "notes" in extra: - notes_elem = ET.SubElement(record_elem, "notes") - notes_elem.text = extra["notes"] - - return record_elem - - def _to_zones(self, elem): - zones = [] - - for item in findall(element=elem, xpath="zone"): - zone = self._to_zone(elem=item) - zones.append(zone) - - return zones - - def _to_zone(self, elem): - id = findtext(element=elem, xpath="id") - domain = findtext(element=elem, xpath="domain") - type = findtext(element=elem, xpath="ns-type") - type = "master" if type.find("pri") == 0 else "slave" - ttl = findtext(element=elem, xpath="default-ttl") - - hostmaster = findtext(element=elem, xpath="hostmaster") - custom_ns = findtext(element=elem, xpath="custom-ns") - custom_nameservers = findtext(element=elem, xpath="custom-nameservers") - notes = findtext(element=elem, xpath="notes") - nx_ttl = findtext(element=elem, xpath="nx-ttl") - slave_nameservers = findtext(element=elem, xpath="slave-nameservers") - tags = findtext(element=elem, xpath="tag-list") - tags = tags.split(" ") if tags else [] - - extra = { - "hostmaster": hostmaster, - "custom-ns": custom_ns, - "custom-nameservers": custom_nameservers, - "notes": notes, - "nx-ttl": nx_ttl, - "slave-nameservers": slave_nameservers, - "tags": tags, - } - zone = Zone(id=str(id), domain=domain, type=type, ttl=int(ttl), driver=self, extra=extra) - - return zone - - def _to_records(self, elem, zone): - records = [] - - for item in findall(element=elem, xpath="host"): - record = self._to_record(elem=item, zone=zone) - records.append(record) - - return records - - def _to_record(self, elem, zone): - id = findtext(element=elem, xpath="id") - name = findtext(element=elem, xpath="hostname") - type = findtext(element=elem, xpath="host-type") - type = self._string_to_record_type(type) - data = findtext(element=elem, xpath="data") - - notes = findtext(element=elem, xpath="notes", no_text_value=None) - state = findtext(element=elem, xpath="state", no_text_value=None) - fqdn = findtext(element=elem, xpath="fqdn", no_text_value=None) - priority = findtext(element=elem, xpath="priority", no_text_value=None) - ttl = findtext(element=elem, xpath="ttl", no_text_value=None) - - if not name: - name = None - - if ttl: - ttl = int(ttl) - - extra = { - "notes": notes, - "state": state, - "fqdn": fqdn, - "priority": priority, - "ttl": ttl, - } - - record = Record( - id=id, - name=name, - type=type, - data=data, - zone=zone, - driver=self, - ttl=ttl, - extra=extra, - ) - - return record - - def _get_more(self, rtype, **kwargs): - exhausted = False - last_key = None - - while not exhausted: - items, last_key, exhausted = self._get_data(rtype, last_key, **kwargs) - - yield from items - - def _get_data(self, rtype, last_key, **kwargs): - # Note: last_key in this case really is a "last_page". - # TODO: Update base driver and change last_key to something more - # generic - e.g. marker - params = {} - params["per_page"] = ITEMS_PER_PAGE - params["page"] = last_key + 1 if last_key else 1 - - if rtype == "zones": - path = API_ROOT + "zones.xml" - response = self.connection.request(path) - transform_func = self._to_zones - elif rtype == "records": - zone = kwargs["zone"] - path = API_ROOT + "zones/%s/hosts.xml" % (zone.id) - self.connection.set_context({"resource": "zone", "id": zone.id}) - response = self.connection.request(path, params=params) - transform_func = self._to_records - else: - raise ValueError(f"Unsupported rtype: {rtype}") - - exhausted = False - result_count = int(response.headers.get("x-query-count", 0)) - - if (params["page"] * ITEMS_PER_PAGE) >= result_count: - exhausted = True - - if response.status == httplib.OK: - items = transform_func(elem=response.object, **kwargs) - - return items, params["page"], exhausted - else: - return [], None, True diff --git a/libcloud/dns/drivers/zonomi.py b/libcloud/dns/drivers/zonomi.py index ca347367fe..7b42500f9f 100644 --- a/libcloud/dns/drivers/zonomi.py +++ b/libcloud/dns/drivers/zonomi.py @@ -54,7 +54,7 @@ def list_zones(self): :return: ``list`` of :class:`Zone` """ action = "/app/dns/dyndns.jsp?" - params = {"action": "QUERYZONES", "api_key": self.key} + params = {"action": "QUERYZONES"} response = self.connection.request(action=action, params=params) zones = self._to_zones(response.objects) @@ -118,9 +118,10 @@ def get_record(self, zone_id, record_id): record = None zone = self.get_zone(zone_id=zone_id) records = self.list_records(zone=zone) + parts = self.from_default_id(zone, record_id) for r in records: - if r.id == record_id: + if r.name == parts.name and r.type == parts.type: record = r if record is None: @@ -175,10 +176,7 @@ def create_record(self, name, zone, type, data, extra=None): :rtype: :class:`Record` """ action = "/app/dns/dyndns.jsp?" - if name: - record_name = name + "." + zone.domain - else: - record_name = zone.domain + record_name = zone.hostname(name) params = {"action": "SET", "name": record_name, "value": data, "type": type} if type == "MX" and extra is not None: @@ -236,7 +234,7 @@ def delete_record(self, record): :rtype: Bool """ action = "/app/dns/dyndns.jsp?" - params = {"action": "DELETE", "name": record.name, "type": record.type} + params = {"action": "DELETE", "name": record.hostname, "type": record.type} try: response = self.connection.request(action=action, params=params) except ZonomiException as e: @@ -314,17 +312,13 @@ def _to_record(self, item, zone): else: ttl = None extra = {"ttl": ttl, "prio": item.get("prio")} - if len(item["name"]) > len(zone.domain): - full_domain = item["name"] - index = full_domain.index("." + zone.domain) - record_name = full_domain[:index] - else: - record_name = zone.domain + rname = item["name"] + rtype = item["type"] record = Record( - id=record_name, - name=record_name, + id=self.to_default_id(zone, rname, rtype), + name=rname, data=item["content"], - type=item["type"], + type=rtype, zone=zone, driver=self, ttl=ttl, diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py index 7469275897..1a33020250 100644 --- a/libcloud/dns/providers.py +++ b/libcloud/dns/providers.py @@ -30,7 +30,6 @@ DRIVERS = { Provider.DUMMY: ("libcloud.dns.drivers.dummy", "DummyDNSDriver"), Provider.LINODE: ("libcloud.dns.drivers.linode", "LinodeDNSDriver"), - Provider.ZERIGO: ("libcloud.dns.drivers.zerigo", "ZerigoDNSDriver"), Provider.RACKSPACE: ("libcloud.dns.drivers.rackspace", "RackspaceDNSDriver"), Provider.ROUTE53: ("libcloud.dns.drivers.route53", "Route53DNSDriver"), Provider.GANDI: ("libcloud.dns.drivers.gandi", "GandiDNSDriver"), @@ -41,7 +40,6 @@ "DigitalOceanDNSDriver", ), Provider.WORLDWIDEDNS: ("libcloud.dns.drivers.worldwidedns", "WorldWideDNSDriver"), - Provider.DNSIMPLE: ("libcloud.dns.drivers.dnsimple", "DNSimpleDNSDriver"), Provider.POINTDNS: ("libcloud.dns.drivers.pointdns", "PointDNSDriver"), Provider.VULTR: ("libcloud.dns.drivers.vultr", "VultrDNSDriver"), Provider.LIQUIDWEB: ("libcloud.dns.drivers.liquidweb", "LiquidWebDNSDriver"), @@ -56,10 +54,11 @@ Provider.BUDDYNS: ("libcloud.dns.drivers.buddyns", "BuddyNSDNSDriver"), Provider.POWERDNS: ("libcloud.dns.drivers.powerdns", "PowerDNSDriver"), Provider.ONAPP: ("libcloud.dns.drivers.onapp", "OnAppDNSDriver"), - Provider.RCODEZERO: ("libcloud.dns.drivers.rcodezero", "RcodeZeroDNSDriver"), # Deprecated + Provider.DNSIMPLE: ("libcloud.dns.drivers.dnsimple", "DNSimpleDNSDriver"), Provider.RACKSPACE_US: ("libcloud.dns.drivers.rackspace", "RackspaceUSDNSDriver"), Provider.RACKSPACE_UK: ("libcloud.dns.drivers.rackspace", "RackspaceUKDNSDriver"), + Provider.RCODEZERO: ("libcloud.dns.drivers.rcodezero", "RcodeZeroDNSDriver"), } diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py index ec2cb7b45f..32972d3976 100644 --- a/libcloud/dns/types.py +++ b/libcloud/dns/types.py @@ -60,7 +60,6 @@ class Provider: ROUTE53 = "route53" VULTR = "vultr" WORLDWIDEDNS = "worldwidedns" - ZERIGO = "zerigo" ZONOMI = "zonomi" DNSPOD = "dnspod" # Deprecated diff --git a/libcloud/test/__init__.py b/libcloud/test/__init__.py index 1cc595f685..21e4c28304 100644 --- a/libcloud/test/__init__.py +++ b/libcloud/test/__init__.py @@ -14,11 +14,13 @@ # limitations under the License. import os +import json import random import unittest import requests import requests_mock +from requests.structures import CaseInsensitiveDict from libcloud.http import LibcloudConnection from libcloud.utils.py3 import PY2, httplib, parse_qs, urlparse, urlquote, parse_qsl @@ -87,6 +89,19 @@ def read(self, chunk_size=None): return StringIO.read(self) +class MockRequest: + def __init__(self, method, url, query, body, headers): + self.method = method + self.url = url + self.query = parse_qs(query) + self.headers = CaseInsensitiveDict(headers) + self.body = body + + @property + def json(self): + return json.loads(self.body) + + class MockHttp(LibcloudConnection, unittest.TestCase): """ A mock HTTP client/server suitable for testing purposes. This replaces @@ -102,6 +117,8 @@ class MockHttp(LibcloudConnection, unittest.TestCase): use_param = None # will use this param to namespace the request function test = None # TestCase instance which is using this mock proxy_url = None + keep_history = False + history = [] def __init__(self, *args, **kwargs): # Load assertion methods into the class, in case people want to assert @@ -118,6 +135,10 @@ def _get_request(self, method, url, body=None, headers=None): # Find a method we can use for this request parsed = urlparse.urlparse(url) _, _, path, _, query, _ = parsed + + if self.keep_history: + self.history.append(MockRequest(method, path, query, body, headers)) + qs = parse_qs(query) if path.endswith("/"): path = path[:-1] diff --git a/libcloud/test/compute/fixtures/linode/_avail_datacenters.json b/libcloud/test/compute/fixtures/linode/_avail_datacenters.json deleted file mode 100644 index eb169623b1..0000000000 --- a/libcloud/test/compute/fixtures/linode/_avail_datacenters.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [ - { - "LOCATION": "Dallas, TX, USA", - "DATACENTERID": 2, - "ABBR": "dallas" - }, - { - "LOCATION": "Fremont, CA, USA", - "DATACENTERID": 3, - "ABBR": "fremont" - }, - { - "LOCATION": "Atlanta, GA, USA", - "DATACENTERID": 4, - "ABBR": "atlanta" - }, - { - "LOCATION": "Newark, NJ, USA", - "DATACENTERID": 6, - "ABBR": "newark" - }, - { - "LOCATION": "London, England, UK", - "DATACENTERID": 7, - "ABBR": "london" - }, - { - "LOCATION": "Tokyo, JP", - "DATACENTERID": 8, - "ABBR": "tokyo" - } - ], - "ACTION": "avail.datacenters" -} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/linode/_avail_distributions.json b/libcloud/test/compute/fixtures/linode/_avail_distributions.json deleted file mode 100644 index f1bdee61b9..0000000000 --- a/libcloud/test/compute/fixtures/linode/_avail_distributions.json +++ /dev/null @@ -1,246 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [ - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 112, - "IS64BIT": 1, - "LABEL": "Arch Linux 2013.06", - "MINIMAGESIZE": 500, - "CREATE_DT": "2013-06-06 02:45:11.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 89, - "IS64BIT": 1, - "LABEL": "CentOS 6.2", - "MINIMAGESIZE": 800, - "CREATE_DT": "2011-07-19 11:38:20.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 78, - "IS64BIT": 1, - "LABEL": "Debian 6", - "MINIMAGESIZE": 550, - "CREATE_DT": "2011-02-08 16:54:31.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 109, - "IS64BIT": 1, - "LABEL": "Debian 7", - "MINIMAGESIZE": 660, - "CREATE_DT": "2013-05-08 11:31:32.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 114, - "IS64BIT": 1, - "LABEL": "Fedora 19", - "MINIMAGESIZE": 750, - "CREATE_DT": "2013-08-26 15:29:21.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 53, - "IS64BIT": 1, - "LABEL": "Gentoo", - "MINIMAGESIZE": 1000, - "CREATE_DT": "2009-04-04 00:00:00.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 115, - "IS64BIT": 1, - "LABEL": "openSUSE 12.3", - "MINIMAGESIZE": 1024, - "CREATE_DT": "2013-09-19 10:49:09.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 87, - "IS64BIT": 1, - "LABEL": "Slackware 13.37", - "MINIMAGESIZE": 600, - "CREATE_DT": "2011-06-05 15:11:59.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 65, - "IS64BIT": 1, - "LABEL": "Ubuntu 10.04 LTS", - "MINIMAGESIZE": 450, - "CREATE_DT": "2010-04-29 00:00:00.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 99, - "IS64BIT": 1, - "LABEL": "Ubuntu 12.04 LTS", - "MINIMAGESIZE": 600, - "CREATE_DT": "2012-04-26 17:25:16.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 111, - "IS64BIT": 1, - "LABEL": "Ubuntu 13.04", - "MINIMAGESIZE": 770, - "CREATE_DT": "2013-05-08 11:31:32.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 113, - "IS64BIT": 0, - "LABEL": "Arch Linux 2013.06 32bit", - "MINIMAGESIZE": 500, - "CREATE_DT": "2013-06-06 02:45:11.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 88, - "IS64BIT": 0, - "LABEL": "CentOS 6.2 32bit", - "MINIMAGESIZE": 800, - "CREATE_DT": "2011-07-19 11:38:20.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 77, - "IS64BIT": 0, - "LABEL": "Debian 6 32bit", - "MINIMAGESIZE": 550, - "CREATE_DT": "2011-02-08 16:54:31.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 108, - "IS64BIT": 0, - "LABEL": "Debian 7 32bit", - "MINIMAGESIZE": 660, - "CREATE_DT": "2013-05-08 11:31:32.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 72, - "IS64BIT": 0, - "LABEL": "Gentoo 32bit", - "MINIMAGESIZE": 1000, - "CREATE_DT": "2010-09-13 00:00:00.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 86, - "IS64BIT": 0, - "LABEL": "Slackware 13.37 32bit", - "MINIMAGESIZE": 600, - "CREATE_DT": "2011-06-05 15:11:59.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 64, - "IS64BIT": 0, - "LABEL": "Ubuntu 10.04 LTS 32bit", - "MINIMAGESIZE": 450, - "CREATE_DT": "2010-04-29 00:00:00.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 98, - "IS64BIT": 0, - "LABEL": "Ubuntu 12.04 LTS 32bit", - "MINIMAGESIZE": 600, - "CREATE_DT": "2012-04-26 17:25:16.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 110, - "IS64BIT": 0, - "LABEL": "Ubuntu 13.04 32bit", - "MINIMAGESIZE": 770, - "CREATE_DT": "2013-05-08 11:31:32.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 105, - "IS64BIT": 1, - "LABEL": "Arch Linux 2012.10", - "MINIMAGESIZE": 500, - "CREATE_DT": "2012-10-22 15:00:49.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 60, - "IS64BIT": 1, - "LABEL": "CentOS 5.6 64bit", - "MINIMAGESIZE": 950, - "CREATE_DT": "2009-08-17 00:00:00.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 100, - "IS64BIT": 1, - "LABEL": "Fedora 17", - "MINIMAGESIZE": 800, - "CREATE_DT": "2012-05-31 16:03:49.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 97, - "IS64BIT": 1, - "LABEL": "openSUSE 12.1", - "MINIMAGESIZE": 1000, - "CREATE_DT": "2012-04-13 11:43:30.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 107, - "IS64BIT": 1, - "LABEL": "Ubuntu 12.10", - "MINIMAGESIZE": 660, - "CREATE_DT": "2012-11-06 11:51:25.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 104, - "IS64BIT": 0, - "LABEL": "Arch Linux 2012.10 32bit", - "MINIMAGESIZE": 500, - "CREATE_DT": "2012-10-22 15:00:49.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 59, - "IS64BIT": 0, - "LABEL": "CentOS 5.6 32bit", - "MINIMAGESIZE": 950, - "CREATE_DT": "2009-08-17 00:00:00.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 101, - "IS64BIT": 0, - "LABEL": "Fedora 17 32bit", - "MINIMAGESIZE": 800, - "CREATE_DT": "2012-05-31 16:03:49.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 96, - "IS64BIT": 0, - "LABEL": "openSUSE 12.1 32bit", - "MINIMAGESIZE": 1000, - "CREATE_DT": "2012-04-13 11:43:30.0" - }, - { - "REQUIRESPVOPSKERNEL": 1, - "DISTRIBUTIONID": 106, - "IS64BIT": 0, - "LABEL": "Ubuntu 12.10 32bit", - "MINIMAGESIZE": 660, - "CREATE_DT": "2012-11-06 11:51:25.0" - } - ], - "ACTION": "avail.distributions" -} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/linode/_avail_kernels.json b/libcloud/test/compute/fixtures/linode/_avail_kernels.json deleted file mode 100644 index 9552c46c5f..0000000000 --- a/libcloud/test/compute/fixtures/linode/_avail_kernels.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "ERRORARRAY": [], - "ACTION": "avail.kernels", - "DATA": [ - { - "LABEL": "Latest 2.6 Stable (2.6.18.8-linode19)", - "ISXEN": 1, - "KERNELID": 60 - }, - { - "LABEL": "2.6.18.8-linode19", - "ISXEN": 1, - "KERNELID": 103 - }, - { - "LABEL": "2.6.30.5-linode20", - "ISXEN": 1, - "KERNELID": 105 - }, - { - "LABEL": "Latest 2.6 Stable (2.6.18.8-x86_64-linode7)", - "ISXEN": 1, - "KERNELID": 107 - }, - { - "LABEL": "2.6.18.8-x86_64-linode7", - "ISXEN": 1, - "KERNELID": 104 - }, - { - "LABEL": "2.6.30.5-x86_64-linode8", - "ISXEN": 1, - "KERNELID": 106 - }, - { - "LABEL": "pv-grub-x86_32", - "ISXEN": 1, - "KERNELID": 92 - }, - { - "LABEL": "pv-grub-x86_64", - "ISXEN": 1, - "KERNELID": 95 - }, - { - "LABEL": "Recovery - Finnix (kernel)", - "ISXEN": 1, - "KERNELID": 61 - }, - { - "LABEL": "2.6.18.8-domU-linode7", - "ISXEN": 1, - "KERNELID": 81 - }, - { - "LABEL": "2.6.18.8-linode10", - "ISXEN": 1, - "KERNELID": 89 - }, - { - "LABEL": "2.6.18.8-linode16", - "ISXEN": 1, - "KERNELID": 98 - }, - { - "LABEL": "2.6.24.4-linode8", - "ISXEN": 1, - "KERNELID": 84 - }, - { - "LABEL": "2.6.25-linode9", - "ISXEN": 1, - "KERNELID": 88 - }, - { - "LABEL": "2.6.25.10-linode12", - "ISXEN": 1, - "KERNELID": 90 - }, - { - "LABEL": "2.6.26-linode13", - "ISXEN": 1, - "KERNELID": 91 - }, - { - "LABEL": "2.6.27.4-linode14", - "ISXEN": 1, - "KERNELID": 93 - }, - { - "LABEL": "2.6.28-linode15", - "ISXEN": 1, - "KERNELID": 96 - }, - { - "LABEL": "2.6.28.3-linode17", - "ISXEN": 1, - "KERNELID": 99 - }, - { - "LABEL": "2.6.29-linode18", - "ISXEN": 1, - "KERNELID": 101 - }, - { - "LABEL": "2.6.16.38-x86_64-linode2", - "ISXEN": 1, - "KERNELID": 85 - }, - { - "LABEL": "2.6.18.8-x86_64-linode1", - "ISXEN": 1, - "KERNELID": 86 - }, - { - "LABEL": "2.6.27.4-x86_64-linode3", - "ISXEN": 1, - "KERNELID": 94 - }, - { - "LABEL": "2.6.28-x86_64-linode4", - "ISXEN": 1, - "KERNELID": 97 - }, - { - "LABEL": "2.6.28.3-x86_64-linode5", - "ISXEN": 1, - "KERNELID": 100 - }, - { - "LABEL": "2.6.29-x86_64-linode6", - "ISXEN": 1, - "KERNELID": 102 - }, - { - "LABEL": "3.9.3-x86-linode52", - "ISXEN": 1, - "KERNELID": 137 - }, - { - "LABEL": "3.9.3-x86_64-linode33", - "ISXEN": 1, - "KERNELID": 138 - } - ] -} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/linode/_avail_linodeplans.json b/libcloud/test/compute/fixtures/linode/_avail_linodeplans.json deleted file mode 100644 index ac2488934f..0000000000 --- a/libcloud/test/compute/fixtures/linode/_avail_linodeplans.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [{ - "CORES": 1, - "PRICE": 10.00, - "RAM": 1024, - "XFER": 2000, - "PLANID": 1, - "LABEL": "Linode 1024", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 24, - "HOURLY": 0.0150 - }, { - "CORES": 2, - "PRICE": 20.00, - "RAM": 2048, - "XFER": 3000, - "PLANID": 2, - "LABEL": "Linode 2048", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 48, - "HOURLY": 0.0300 - }, { - "CORES": 4, - "PRICE": 40.00, - "RAM": 4096, - "XFER": 4000, - "PLANID": 4, - "LABEL": "Linode 4096", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 96, - "HOURLY": 0.0600 - }, { - "CORES": 6, - "PRICE": 80.00, - "RAM": 8192, - "XFER": 8000, - "PLANID": 6, - "LABEL": "Linode 8192", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 192, - "HOURLY": 0.1200 - }, { - "CORES": 8, - "PRICE": 160.00, - "RAM": 16384, - "XFER": 16000, - "PLANID": 7, - "LABEL": "Linode 16384", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 384, - "HOURLY": 0.2400 - }, { - "CORES": 12, - "PRICE": 320.00, - "RAM": 32768, - "XFER": 20000, - "PLANID": 8, - "LABEL": "Linode 32768", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 768, - "HOURLY": 0.4800 - }, { - "CORES": 16, - "PRICE": 480.00, - "RAM": 49152, - "XFER": 20000, - "PLANID": 9, - "LABEL": "Linode 49152", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 1152, - "HOURLY": 0.7200 - }, { - "CORES": 20, - "PRICE": 640.00, - "RAM": 65536, - "XFER": 20000, - "PLANID": 10, - "LABEL": "Linode 65536", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 1536, - "HOURLY": 0.9600 - }, { - "CORES": 20, - "PRICE": 960.00, - "RAM": 98304, - "XFER": 20000, - "PLANID": 12, - "LABEL": "Linode 98304", - "AVAIL": { - "3": 500, - "2": 500, - "7": 500, - "6": 500, - "4": 500, - "8": 500 - }, - "DISK": 1920, - "HOURLY": 1.4400 - }], - "ACTION": "avail.linodeplans" -} diff --git a/libcloud/test/compute/fixtures/linode/_batch.json b/libcloud/test/compute/fixtures/linode/_batch.json deleted file mode 100644 index 36e28f9050..0000000000 --- a/libcloud/test/compute/fixtures/linode/_batch.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "ERRORARRAY": [], - "DATA": [ - { - "IPADDRESSID": 5384, - "RDNS_NAME": "li22-54.members.linode.com", - "LINODEID": 8098, - "ISPUBLIC": 1, - "IPADDRESS": "66.228.43.47" - }, - { - "IPADDRESSID": 5575, - "RDNS_NAME": "li22-245.members.linode.com", - "LINODEID": 8098, - "ISPUBLIC": 1, - "IPADDRESS": "75.127.96.245" - } - ], - "ACTION": "linode.ip.list" - } -] diff --git a/libcloud/test/compute/fixtures/linode/_linode_disk_list.json b/libcloud/test/compute/fixtures/linode/_linode_disk_list.json deleted file mode 100644 index 8ec05a9f1d..0000000000 --- a/libcloud/test/compute/fixtures/linode/_linode_disk_list.json +++ /dev/null @@ -1,28 +0,0 @@ -{ -"ERRORARRAY":[], -"ACTION":"linode.disk.list", -"DATA":[ - { - "UPDATE_DT":"2009-06-30 13:19:00.0", - "DISKID":55319, - "LABEL":"test label", - "TYPE":"ext3", - "LINODEID":8098, - "ISREADONLY":0, - "STATUS":1, - "CREATE_DT":"2008-04-04 10:08:06.0", - "SIZE":4096 - }, - { - "UPDATE_DT":"2009-07-18 12:53:043.0", - "DISKID":55320, - "LABEL":"256M Swap Image", - "TYPE":"swap", - "LINODEID":8098, - "ISREADONLY":0, - "STATUS":1, - "CREATE_DT":"2008-04-04 10:08:06.0", - "SIZE":256 - } - ] -} diff --git a/libcloud/test/compute/fixtures/linode/_linode_ip_list.json b/libcloud/test/compute/fixtures/linode/_linode_ip_list.json deleted file mode 100644 index 33a969ca68..0000000000 --- a/libcloud/test/compute/fixtures/linode/_linode_ip_list.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "ACTION": "linode.ip.list", - "DATA": [ - { - "IPADDRESS": "66.228.43.47", - "IPADDRESSID": 5384, - "ISPUBLIC": 1, - "LINODEID": 8098, - "RDNS_NAME": "li22-54.members.linode.com" - }, - { - "IPADDRESS": "75.127.96.245", - "IPADDRESSID": 5575, - "ISPUBLIC": 1, - "LINODEID": 8098, - "RDNS_NAME": "li22-245.members.linode.com" - } - ], - "ERRORARRAY": [] -} diff --git a/libcloud/test/compute/fixtures/linode/_linode_list.json b/libcloud/test/compute/fixtures/linode/_linode_list.json deleted file mode 100644 index 345f7cadbb..0000000000 --- a/libcloud/test/compute/fixtures/linode/_linode_list.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [ - { - "ALERT_CPU_ENABLED": 1, - "ALERT_BWIN_ENABLED": 1, - "ALERT_BWQUOTA_ENABLED": 1, - "BACKUPWINDOW": 0, - "ALERT_DISKIO_THRESHOLD": 1000, - "DISTRIBUTIONVENDOR": "Debian", - "WATCHDOG": 1, - "DATACENTERID": 6, - "STATUS": 1, - "ALERT_DISKIO_ENABLED": 1, - "CREATE_DT": "2012-05-04 19:31:30.0", - "TOTALHD": 49152, - "ALERT_BWQUOTA_THRESHOLD": 80, - "TOTALRAM": 2048, - "ALERT_BWIN_THRESHOLD": 5, - "LINODEID": 8098, - "ALERT_BWOUT_THRESHOLD": 5, - "ALERT_BWOUT_ENABLED": 1, - "BACKUPSENABLED": 1, - "ALERT_CPU_THRESHOLD": 90, - "PLANID": "2", - "BACKUPWEEKLYDAY": 0, - "LABEL": "api-node3", - "LPM_DISPLAYGROUP": "test", - "TOTALXFER": 3000 - } - ], - "ACTION": "linode.list" -} diff --git a/libcloud/test/compute/fixtures/vultr/create_key_pair.json b/libcloud/test/compute/fixtures/vultr/create_key_pair.json deleted file mode 100644 index f43bc0ae7b..0000000000 --- a/libcloud/test/compute/fixtures/vultr/create_key_pair.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "SSHKEYID": "5806ab4970aba" -} diff --git a/libcloud/test/compute/fixtures/vultr/create_node.json b/libcloud/test/compute/fixtures/vultr/create_node.json deleted file mode 100644 index 2a58f23a5d..0000000000 --- a/libcloud/test/compute/fixtures/vultr/create_node.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "SUBID": "41326859" -} diff --git a/libcloud/test/compute/fixtures/vultr/error_rate_limit.txt b/libcloud/test/compute/fixtures/vultr/error_rate_limit.txt deleted file mode 100644 index 27def76b8a..0000000000 --- a/libcloud/test/compute/fixtures/vultr/error_rate_limit.txt +++ /dev/null @@ -1 +0,0 @@ -Rate limit reached - please try your request again later. Current rate limit: 2 requests/sec \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/vultr/list_images.json b/libcloud/test/compute/fixtures/vultr/list_images.json deleted file mode 100644 index 45a35961ce..0000000000 --- a/libcloud/test/compute/fixtures/vultr/list_images.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "127": { - "OSID": 127, - "name": "CentOS 6 x64", - "arch": "x64", - "family": "centos", - "windows": false - }, - "147": { - "OSID": 147, - "name": "CentOS 6 i386", - "arch": "i386", - "family": "centos", - "windows": false - }, - "167": { - "OSID": 167, - "name": "CentOS 7 x64", - "arch": "x64", - "family": "centos", - "windows": false - }, - "381": { - "OSID": 381, - "name": "CentOS 7 SELinux x64", - "arch": "x64", - "family": "centos", - "windows": false - }, - "362": { - "OSID": 362, - "name": "CentOS 8 x64", - "arch": "x64", - "family": "centos", - "windows": false - }, - "401": { - "OSID": 401, - "name": "CentOS 8 Stream x64", - "arch": "x64", - "family": "centos", - "windows": false - }, - "215": { - "OSID": 215, - "name": "Ubuntu 16.04 x64", - "arch": "x64", - "family": "ubuntu", - "windows": false - }, - "216": { - "OSID": 216, - "name": "Ubuntu 16.04 i386", - "arch": "i386", - "family": "ubuntu", - "windows": false - }, - "270": { - "OSID": 270, - "name": "Ubuntu 18.04 x64", - "arch": "x64", - "family": "ubuntu", - "windows": false - }, - "387": { - "OSID": 387, - "name": "Ubuntu 20.04 x64", - "arch": "x64", - "family": "ubuntu", - "windows": false - }, - "194": { - "OSID": 194, - "name": "Debian 8 i386 (jessie)", - "arch": "i386", - "family": "debian", - "windows": false - }, - "244": { - "OSID": 244, - "name": "Debian 9 x64 (stretch)", - "arch": "x64", - "family": "debian", - "windows": false - }, - "352": { - "OSID": 352, - "name": "Debian 10 x64 (buster)", - "arch": "x64", - "family": "debian", - "windows": false - }, - "230": { - "OSID": 230, - "name": "FreeBSD 11 x64", - "arch": "x64", - "family": "freebsd", - "windows": false - }, - "327": { - "OSID": 327, - "name": "FreeBSD 12 x64", - "arch": "x64", - "family": "freebsd", - "windows": false - }, - "366": { - "OSID": 366, - "name": "OpenBSD 6.6 x64", - "arch": "x64", - "family": "openbsd", - "windows": false - }, - "394": { - "OSID": 394, - "name": "OpenBSD 6.7 x64", - "arch": "x64", - "family": "openbsd", - "windows": false - }, - "391": { - "OSID": 391, - "name": "Fedora CoreOS", - "arch": "x64", - "family": "fedora-coreos", - "windows": false - }, - "367": { - "OSID": 367, - "name": "Fedora 31 x64", - "arch": "x64", - "family": "fedora", - "windows": false - }, - "389": { - "OSID": 389, - "name": "Fedora 32 x64", - "arch": "x64", - "family": "fedora", - "windows": false - }, - "124": { - "OSID": 124, - "name": "Windows 2012 R2 x64", - "arch": "x64", - "family": "windows", - "windows": true - }, - "240": { - "OSID": 240, - "name": "Windows 2016 x64", - "arch": "x64", - "family": "windows", - "windows": true - }, - "159": { - "OSID": 159, - "name": "Custom", - "arch": "x64", - "family": "iso", - "windows": false - }, - "164": { - "OSID": 164, - "name": "Snapshot", - "arch": "x64", - "family": "snapshot", - "windows": false - }, - "180": { - "OSID": 180, - "name": "Backup", - "arch": "x64", - "family": "backup", - "windows": false - }, - "186": { - "OSID": 186, - "name": "Application", - "arch": "x64", - "family": "application", - "windows": false - } -} diff --git a/libcloud/test/compute/fixtures/vultr/list_key_pairs.json b/libcloud/test/compute/fixtures/vultr/list_key_pairs.json deleted file mode 100644 index b8d5cad19e..0000000000 --- a/libcloud/test/compute/fixtures/vultr/list_key_pairs.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "5806a8ef2a0c6": { - "SSHKEYID": "5806a8ef2a0c6", - "date_created": "2016-10-18 18:57:51", - "name": "test-key-pair", - "ssh_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZYMivN4KqJZ3dNEWeH20PUeB2ZnZRkk91K5SgxWrEotgpX4pMVM/9oxkh4bKw5CBzT6KAOghzLcBViFpNVjDyyet9wwVcy6cjuUynx63UtbTLB+r4D+bD/+/9rQTeckvGYg9Y8xIKL/oaVeCcdBM8JhSQZbZ/aARi2K79FWGH61azAqc/JCHT63f3FhspjdVpcVoVOjsZG3WG6Vymys2cXH1PM5qMgBbmp+5LkSv0LvUULyxcrtKkUyntPr1BvIFSNbo2lhXLwnM4DXONP6U/yMFte+ZwiajF7pUCdB9HqvXVU+IfswYSDuhHzL9j8+ZLQ2enF/lkkYxpMHE2t215 tester@test" - } -} diff --git a/libcloud/test/compute/fixtures/vultr/list_locations.json b/libcloud/test/compute/fixtures/vultr/list_locations.json deleted file mode 100644 index 63a6194a3b..0000000000 --- a/libcloud/test/compute/fixtures/vultr/list_locations.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "6": { - "DCID": "6", - "name": "Atlanta", - "country": "US", - "continent": "North America", - "state": "GA", - "ddos_protection": false, - "block_storage": false, - "regioncode": "ATL" - }, - "2": { - "DCID": "2", - "name": "Chicago", - "country": "US", - "continent": "North America", - "state": "IL", - "ddos_protection": true, - "block_storage": false, - "regioncode": "ORD" - }, - "3": { - "DCID": "3", - "name": "Dallas", - "country": "US", - "continent": "North America", - "state": "TX", - "ddos_protection": true, - "block_storage": false, - "regioncode": "DFW" - }, - "5": { - "DCID": "5", - "name": "Los Angeles", - "country": "US", - "continent": "North America", - "state": "CA", - "ddos_protection": true, - "block_storage": false, - "regioncode": "LAX" - }, - "39": { - "DCID": "39", - "name": "Miami", - "country": "US", - "continent": "North America", - "state": "FL", - "ddos_protection": true, - "block_storage": false, - "regioncode": "MIA" - }, - "1": { - "DCID": "1", - "name": "New Jersey", - "country": "US", - "continent": "North America", - "state": "NJ", - "ddos_protection": true, - "block_storage": true, - "regioncode": "EWR" - }, - "4": { - "DCID": "4", - "name": "Seattle", - "country": "US", - "continent": "North America", - "state": "WA", - "ddos_protection": true, - "block_storage": false, - "regioncode": "SEA" - }, - "12": { - "DCID": "12", - "name": "Silicon Valley", - "country": "US", - "continent": "North America", - "state": "CA", - "ddos_protection": true, - "block_storage": false, - "regioncode": "SJC" - }, - "40": { - "DCID": "40", - "name": "Singapore", - "country": "SG", - "continent": "Asia", - "state": "", - "ddos_protection": false, - "block_storage": false, - "regioncode": "SGP" - }, - "7": { - "DCID": "7", - "name": "Amsterdam", - "country": "NL", - "continent": "Europe", - "state": "", - "ddos_protection": true, - "block_storage": false, - "regioncode": "AMS" - }, - "34": { - "DCID": "34", - "name": "Seoul", - "country": "KR", - "continent": "Asia", - "state": "", - "ddos_protection": false, - "block_storage": false, - "regioncode": "ICN" - }, - "25": { - "DCID": "25", - "name": "Tokyo", - "country": "JP", - "continent": "Asia", - "state": "", - "ddos_protection": false, - "block_storage": false, - "regioncode": "NRT" - }, - "8": { - "DCID": "8", - "name": "London", - "country": "GB", - "continent": "Europe", - "state": "", - "ddos_protection": true, - "block_storage": false, - "regioncode": "LHR" - }, - "24": { - "DCID": "24", - "name": "Paris", - "country": "FR", - "continent": "Europe", - "state": "", - "ddos_protection": true, - "block_storage": false, - "regioncode": "CDG" - }, - "9": { - "DCID": "9", - "name": "Frankfurt", - "country": "DE", - "continent": "Europe", - "state": "", - "ddos_protection": true, - "block_storage": false, - "regioncode": "FRA" - }, - "22": { - "DCID": "22", - "name": "Toronto", - "country": "CA", - "continent": "North America", - "state": "", - "ddos_protection": false, - "block_storage": false, - "regioncode": "YTO" - }, - "19": { - "DCID": "19", - "name": "Sydney", - "country": "AU", - "continent": "Australia", - "state": "", - "ddos_protection": false, - "block_storage": false, - "regioncode": "SYD" - } -} diff --git a/libcloud/test/compute/fixtures/vultr/list_nodes.json b/libcloud/test/compute/fixtures/vultr/list_nodes.json deleted file mode 100644 index 2648f5e404..0000000000 --- a/libcloud/test/compute/fixtures/vultr/list_nodes.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "41326859": { - "SUBID": "41326859", - "os": "CentOS SELinux 8 x64", - "ram": "1024 MB", - "disk": "Virtual 25 GB", - "main_ip": "217.69.11.158", - "vcpu_count": "1", - "location": "Paris", - "DCID": "24", - "default_password": "*7j6j6[#q", - "date_created": "2020-10-15 03:17:22", - "pending_charges": "0.04", - "status": "active", - "cost_per_month": "5.00", - "current_bandwidth_gb": 0, - "allowed_bandwidth_gb": "1000", - "netmask_v4": "255.255.254.0", - "gateway_v4": "217.69.10.1", - "power_status": "running", - "server_state": "locked", - "VPSPLANID": "201", - "v6_main_ip": "2001:19f0:6801:1cc8:5400:03ff:fe03:5478", - "v6_network_size": "64", - "v6_network": "2001:19f0:6801:1cc8::", - "v6_networks": [ - { - "v6_main_ip": "2001:19f0:6801:1cc8:5400:03ff:fe03:5478", - "v6_network_size": "64", - "v6_network": "2001:19f0:6801:1cc8::" - } - ], - "label": "labelname", - "internal_ip": "10.24.96.3", - "kvm_url": "https://my.vultr.com/subs/novnc/api.php?data=eawxFVZw2mXnhGUV", - "auto_backups": "yes", - "tag": "", - "OSID": "362", - "APPID": "0", - "FIREWALLGROUPID": "0" - }, - "41306569": { - "SUBID": "41306569", - "os": "Ubuntu 20.04 x64", - "ram": "1024 MB", - "disk": "Virtual 25 GB", - "main_ip": "45.76.43.87", - "vcpu_count": "1", - "location": "Amsterdam", - "DCID": "7", - "default_password": "h6*hrte6tg", - "date_created": "2020-10-14 09:37:50", - "pending_charges": "0.18", - "status": "active", - "cost_per_month": "5.00", - "current_bandwidth_gb": 0.026, - "allowed_bandwidth_gb": "1000", - "netmask_v4": "255.255.254.0", - "gateway_v4": "45.76.42.1", - "power_status": "running", - "server_state": "installingbooting", - "VPSPLANID": "201", - "v6_main_ip": "2001:19f0:5001:2b9c:5400:03ff:fe03:9568", - "v6_network_size": "64", - "v6_network": "2001:19f0:5001:2b9c::", - "v6_networks": [ - { - "v6_main_ip": "2001:19f0:5001:2b9c:5400:03ff:fe03:9568", - "v6_network_size": "64", - "v6_network": "2001:19f0:5001:2b9c::" - } - ], - "label": "libcloud-label", - "internal_ip": "10.7.96.85", - "kvm_url": "https://my.vultr.com/subs/novnc/api.php?data=erewawxFVZw2mXnhGUV", - "auto_backups": "yes", - "tag": "Web", - "OSID": "387", - "APPID": "0", - "FIREWALLGROUPID": "0" - }, - "41326895": { - "SUBID": "41326895", - "os": "Ubuntu 18.04 x64", - "ram": "2048 MB", - "disk": "Virtual 55 GB", - "main_ip": "136.244.113.89", - "vcpu_count": "1", - "location": "Paris", - "DCID": "24", - "default_password": "Dy4@K_Z1gvb!!8zw", - "date_created": "2020-10-15 03:17:54", - "pending_charges": "0.00", - "status": "pending", - "cost_per_month": "10.00", - "current_bandwidth_gb": 0, - "allowed_bandwidth_gb": "2000", - "netmask_v4": "255.255.254.0", - "gateway_v4": "136.244.112.1", - "power_status": "running", - "server_state": "none", - "VPSPLANID": "202", - "v6_main_ip": false, - "v6_network_size": "", - "v6_network": "", - "v6_networks": [], - "label": "servlabel", - "internal_ip": "", - "kvm_url": "", - "auto_backups": "no", - "tag": "", - "OSID": "270", - "APPID": "0", - "FIREWALLGROUPID": "4e83489b" - } -} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/vultr/list_sizes.json b/libcloud/test/compute/fixtures/vultr/list_sizes.json deleted file mode 100644 index cb786dab0b..0000000000 --- a/libcloud/test/compute/fixtures/vultr/list_sizes.json +++ /dev/null @@ -1,405 +0,0 @@ -{ - "201": { - "VPSPLANID": "201", - "name": "1024 MB RAM,25 GB SSD,1.00 TB BW", - "vcpu_count": "1", - "ram": "1024", - "disk": "25", - "bandwidth": "1.00", - "bandwidth_gb": "1024", - "price_per_month": "5.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "202": { - "VPSPLANID": "202", - "name": "2048 MB RAM,55 GB SSD,2.00 TB BW", - "vcpu_count": "1", - "ram": "2048", - "disk": "55", - "bandwidth": "2.00", - "bandwidth_gb": "2048", - "price_per_month": "10.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "203": { - "VPSPLANID": "203", - "name": "4096 MB RAM,80 GB SSD,3.00 TB BW", - "vcpu_count": "2", - "ram": "4096", - "disk": "80", - "bandwidth": "3.00", - "bandwidth_gb": "3072", - "price_per_month": "20.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "204": { - "VPSPLANID": "204", - "name": "8192 MB RAM,160 GB SSD,4.00 TB BW", - "vcpu_count": "4", - "ram": "8192", - "disk": "160", - "bandwidth": "4.00", - "bandwidth_gb": "4096", - "price_per_month": "40.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "205": { - "VPSPLANID": "205", - "name": "16384 MB RAM,320 GB SSD,5.00 TB BW", - "vcpu_count": "6", - "ram": "16384", - "disk": "320", - "bandwidth": "5.00", - "bandwidth_gb": "5120", - "price_per_month": "80.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "206": { - "VPSPLANID": "206", - "name": "32768 MB RAM,640 GB SSD,6.00 TB BW", - "vcpu_count": "8", - "ram": "32768", - "disk": "640", - "bandwidth": "6.00", - "bandwidth_gb": "6144", - "price_per_month": "160.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 39 - ] - }, - "207": { - "VPSPLANID": "207", - "name": "65536 MB RAM,1280 GB SSD,10.00 TB BW", - "vcpu_count": "16", - "ram": "65536", - "disk": "1280", - "bandwidth": "10.00", - "bandwidth_gb": "10240", - "price_per_month": "320.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 2, - 3, - 5, - 7, - 12, - 19, - 24, - 39 - ] - }, - "208": { - "VPSPLANID": "208", - "name": "98304 MB RAM,1600 GB SSD,15.00 TB BW", - "vcpu_count": "24", - "ram": "98304", - "disk": "1600", - "bandwidth": "15.00", - "bandwidth_gb": "15360", - "price_per_month": "640.00", - "plan_type": "SSD", - "windows": false, - "available_locations": [ - 12 - ] - }, - "115": { - "VPSPLANID": "115", - "name": "8192 MB RAM,110 GB SSD,10.00 TB BW", - "vcpu_count": "2", - "ram": "8192", - "disk": "110", - "bandwidth": "10.00", - "bandwidth_gb": "10240", - "price_per_month": "60.00", - "plan_type": "DEDICATED", - "windows": false, - "available_locations": [ - 1, - 12, - 25 - ] - }, - "116": { - "VPSPLANID": "116", - "name": "16384 MB RAM,2x110 GB SSD,20.00 TB BW", - "vcpu_count": "4", - "ram": "16384", - "disk": "110", - "bandwidth": "20.00", - "bandwidth_gb": "20480", - "price_per_month": "120.00", - "plan_type": "DEDICATED", - "windows": false, - "available_locations": [ - 1 - ] - }, - "117": { - "VPSPLANID": "117", - "name": "24576 MB RAM,3x110 GB SSD,30.00 TB BW", - "vcpu_count": "6", - "ram": "24576", - "disk": "110", - "bandwidth": "30.00", - "bandwidth_gb": "30720", - "price_per_month": "180.00", - "plan_type": "DEDICATED", - "windows": false, - "available_locations": [ - 1 - ] - }, - "118": { - "VPSPLANID": "118", - "name": "32768 MB RAM,4x110 GB SSD,40.00 TB BW", - "vcpu_count": "8", - "ram": "32768", - "disk": "110", - "bandwidth": "40.00", - "bandwidth_gb": "40960", - "price_per_month": "240.00", - "plan_type": "DEDICATED", - "windows": false, - "available_locations": [ - 1 - ] - }, - "400": { - "VPSPLANID": "400", - "name": "1024 MB RAM,32 GB SSD,1.00 TB BW", - "vcpu_count": "1", - "ram": "1024", - "disk": "32", - "bandwidth": "1.00", - "bandwidth_gb": "1024", - "price_per_month": "6.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "401": { - "VPSPLANID": "401", - "name": "2048 MB RAM,64 GB SSD,2.00 TB BW", - "vcpu_count": "1", - "ram": "2048", - "disk": "64", - "bandwidth": "2.00", - "bandwidth_gb": "2048", - "price_per_month": "12.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 12, - 19, - 22, - 24, - 25, - 39, - 40 - ] - }, - "402": { - "VPSPLANID": "402", - "name": "4096 MB RAM,128 GB SSD,3.00 TB BW", - "vcpu_count": "2", - "ram": "4096", - "disk": "128", - "bandwidth": "3.00", - "bandwidth_gb": "3072", - "price_per_month": "24.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [ - 1, - 4, - 5, - 7, - 12, - 19, - 22, - 24 - ] - }, - "403": { - "VPSPLANID": "403", - "name": "8192 MB RAM,256 GB SSD,4.00 TB BW", - "vcpu_count": "3", - "ram": "8192", - "disk": "256", - "bandwidth": "4.00", - "bandwidth_gb": "4096", - "price_per_month": "48.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [ - 1, - 4, - 22 - ] - }, - "404": { - "VPSPLANID": "404", - "name": "16384 MB RAM,384 GB SSD,5.00 TB BW", - "vcpu_count": "4", - "ram": "16384", - "disk": "384", - "bandwidth": "5.00", - "bandwidth_gb": "5120", - "price_per_month": "96.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [ - 22 - ] - }, - "405": { - "VPSPLANID": "405", - "name": "32768 MB RAM,512 GB SSD,6.00 TB BW", - "vcpu_count": "8", - "ram": "32768", - "disk": "512", - "bandwidth": "6.00", - "bandwidth_gb": "6144", - "price_per_month": "192.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [] - }, - "406": { - "VPSPLANID": "406", - "name": "49152 MB RAM,768 GB SSD,8.00 TB BW", - "vcpu_count": "12", - "ram": "49152", - "disk": "768", - "bandwidth": "8.00", - "bandwidth_gb": "8192", - "price_per_month": "256.00", - "plan_type": "HIGHFREQUENCY", - "windows": false, - "available_locations": [] - } -} diff --git a/libcloud/test/compute/test_linode.py b/libcloud/test/compute/test_linode.py deleted file mode 100644 index bbe6354488..0000000000 --- a/libcloud/test/compute/test_linode.py +++ /dev/null @@ -1,208 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Maintainer: Jed Smith -# Based upon code written by Alex Polvi -# - -import sys -import unittest - -from libcloud.test import MockHttp -from libcloud.utils.py3 import httplib -from libcloud.compute.base import Node, StorageVolume, NodeAuthSSHKey, NodeAuthPassword -from libcloud.test.compute import TestCaseMixin -from libcloud.test.file_fixtures import ComputeFileFixtures -from libcloud.compute.drivers.linode import LinodeNodeDriver - - -class LinodeTest(unittest.TestCase, TestCaseMixin): - # The Linode test suite - - def setUp(self): - LinodeNodeDriver.connectionCls.conn_class = LinodeMockHttp - LinodeMockHttp.use_param = "api_action" - self.driver = LinodeNodeDriver("foo", api_version="3.0") - - def test_list_nodes(self): - nodes = self.driver.list_nodes() - self.assertEqual(len(nodes), 1) - node = nodes[0] - self.assertEqual(node.id, "8098") - self.assertEqual(node.name, "api-node3") - self.assertEqual(node.extra["PLANID"], "2") - self.assertTrue("75.127.96.245" in node.public_ips) - self.assertEqual(node.private_ips, []) - - def test_reboot_node(self): - # An exception would indicate failure - node = self.driver.list_nodes()[0] - self.driver.reboot_node(node) - - def test_destroy_node(self): - # An exception would indicate failure - node = self.driver.list_nodes()[0] - self.driver.destroy_node(node) - - def test_create_node_password_auth(self): - # Will exception on failure - self.driver.create_node( - name="Test", - location=self.driver.list_locations()[0], - size=self.driver.list_sizes()[0], - image=self.driver.list_images()[6], - auth=NodeAuthPassword("test123"), - ) - - def test_create_node_ssh_key_auth(self): - # Will exception on failure - node = self.driver.create_node( - name="Test", - location=self.driver.list_locations()[0], - size=self.driver.list_sizes()[0], - image=self.driver.list_images()[6], - auth=NodeAuthSSHKey("foo"), - ) - self.assertTrue(isinstance(node, Node)) - - def test_list_sizes(self): - sizes = self.driver.list_sizes() - self.assertEqual(len(sizes), 9) - for size in sizes: - self.assertEqual(size.ram, int(size.name.split(" ")[1])) - - def test_list_images(self): - images = self.driver.list_images() - self.assertEqual(len(images), 30) - - def test_create_node_response(self): - # should return a node object - node = self.driver.create_node( - name="node-name", - location=self.driver.list_locations()[0], - size=self.driver.list_sizes()[0], - image=self.driver.list_images()[0], - auth=NodeAuthPassword("foobar"), - ) - self.assertTrue(isinstance(node, Node)) - - def test_destroy_volume(self): - # Will exception on failure - node = self.driver.list_nodes()[0] - volume = StorageVolume( - id=55648, - name="test", - size=1024, - driver=self.driver, - extra={"LINODEID": node.id}, - ) - self.driver.destroy_volume(volume) - - def test_ex_create_volume(self): - # should return a StorageVolume object - node = self.driver.list_nodes()[0] - volume = self.driver.ex_create_volume( - size=4096, name="Another test image", node=node, fs_type="ext4" - ) - self.assertTrue(isinstance(volume, StorageVolume)) - - def test_ex_list_volumes(self): - # should return list of StorageVolume objects - node = self.driver.list_nodes()[0] - volumes = self.driver.ex_list_volumes(node=node) - - self.assertTrue(isinstance(volumes, list)) - self.assertTrue(isinstance(volumes[0], StorageVolume)) - self.assertEqual(len(volumes), 2) - - -class LinodeMockHttp(MockHttp): - fixtures = ComputeFileFixtures("linode") - - def _avail_datacenters(self, method, url, body, headers): - body = self.fixtures.load("_avail_datacenters.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _avail_linodeplans(self, method, url, body, headers): - body = self.fixtures.load("_avail_linodeplans.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _avail_distributions(self, method, url, body, headers): - body = self.fixtures.load("_avail_distributions.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_create(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.create","DATA":{"LinodeID":8098}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_disk_create(self, method, url, body, headers): - body = ( - '{"ERRORARRAY":[],"ACTION":"linode.disk.create","DATA":{"JobID":1298,"DiskID":55647}}' - ) - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_disk_delete(self, method, url, body, headers): - body = ( - '{"ERRORARRAY":[],"ACTION":"linode.disk.delete","DATA":{"JobID":1298,"DiskID":55648}}' - ) - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_disk_createfromdistribution(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.disk.createFromDistribution","DATA":{"JobID":1298,"DiskID":55647}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_disk_list(self, method, url, body, headers): - body = self.fixtures.load("_linode_disk_list.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_delete(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.delete","DATA":{"LinodeID":8098}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_update(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.update","DATA":{"LinodeID":8098}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_reboot(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.reboot","DATA":{"JobID":1305}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _avail_kernels(self, method, url, body, headers): - body = self.fixtures.load("_avail_kernels.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_boot(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.boot","DATA":{"JobID":1300}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_config_create(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.config.create","DATA":{"ConfigID":31239}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_list(self, method, url, body, headers): - body = self.fixtures.load("_linode_list.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _linode_ip_list(self, method, url, body, headers): - body = self.fixtures.load("_linode_ip_list.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _batch(self, method, url, body, headers): - body = self.fixtures.load("_batch.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - -if __name__ == "__main__": - sys.exit(unittest.main()) diff --git a/libcloud/test/compute/test_vultr.py b/libcloud/test/compute/test_vultr.py deleted file mode 100644 index e2ac3f3751..0000000000 --- a/libcloud/test/compute/test_vultr.py +++ /dev/null @@ -1,236 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import sys -import unittest - -from libcloud.test import MockHttp, LibcloudTestCase -from libcloud.utils.py3 import httplib -from libcloud.common.types import ServiceUnavailableError -from libcloud.compute.base import NodeSize, NodeImage -from libcloud.test.secrets import VULTR_PARAMS -from libcloud.test.file_fixtures import ComputeFileFixtures -from libcloud.compute.drivers.vultr import VultrNodeDriver, VultrNodeDriverV1 - -try: - import simplejson as json # pylint: disable=unused-import -except ImportError: - # pylint: disable=unused-import - import json # NOQA - - -# class VultrTests(unittest.TestCase, TestCaseMixin): -class VultrTests(LibcloudTestCase): - def setUp(self): - VultrNodeDriver.connectionCls.conn_class = VultrMockHttp - VultrMockHttp.type = None - self.driver = VultrNodeDriver(*VULTR_PARAMS, api_version="1") - - def test_correct_class_is_used(self): - self.assertIsInstance(self.driver, VultrNodeDriverV1) - - def test_list_images_dont_require_api_key(self): - self.driver.list_images() - self.assertFalse(self.driver.connection.require_api_key()) - - def test_list_images_success(self): - images = self.driver.list_images() - self.assertTrue(len(images) >= 1) - - image = images[0] - self.assertTrue(image.id is not None) - self.assertTrue(image.name is not None) - - def test_list_sizes_dont_require_api_key(self): - self.driver.list_sizes() - self.assertFalse(self.driver.connection.require_api_key()) - - def test_list_sizes_success(self): - """count of current plans""" - sizes = self.driver.list_sizes() - self.assertTrue(len(sizes) == 19) - - size = sizes[0] - self.assertTrue(size.id.isdigit()) - self.assertEqual(size.name, "8192 MB RAM,110 GB SSD,10.00 TB BW") - self.assertEqual(size.ram, 8192) - - size = sizes[16] - self.assertTrue(size.id.isdigit()) - self.assertEqual(size.name, "16384 MB RAM,384 GB SSD,5.00 TB BW") - self.assertEqual(size.ram, 16384) - - def test_list_locations_dont_require_api_key(self): - self.driver.list_locations() - self.assertFalse(self.driver.connection.require_api_key()) - - def test_list_locations_success(self): - locations = self.driver.list_locations() - self.assertTrue(len(locations) >= 1) - - location = locations[0] - self.assertEqual(location.id, "1") - self.assertEqual(location.name, "New Jersey") - self.assertEqual(location.extra["continent"], "North America") - - def test_list_locations_extra_success(self): - locations = self.driver.list_locations() - self.assertTrue(len(locations) >= 1) - extra_keys = [ - "continent", - "state", - "ddos_protection", - "block_storage", - "regioncode", - ] - for location in locations: - self.assertTrue(len(location.extra.keys()) >= 5) - self.assertTrue(all(item in location.extra.keys() for item in extra_keys)) - - def test_list_nodes_require_api_key(self): - self.driver.list_nodes() - self.assertTrue(self.driver.connection.require_api_key()) - - def test_list_nodes_success(self): - nodes = self.driver.list_nodes() - self.assertEqual(len(nodes), 3) - self.assertTrue(nodes[0].id.isdigit()) - self.assertEqual(nodes[0].id, "41306569") - self.assertEqual(nodes[0].public_ips, ["45.76.43.87"]) - self.assertEqual(nodes[0].private_ips, ["10.7.96.85"]) - self.assertEqual(nodes[2].private_ips, []) - - def test_list_nodes_image_success(self): - nodes = self.driver.list_nodes() - node = nodes[0] - self.assertTrue(isinstance(node.image, NodeImage)) - - def test_list_nodes_size_success(self): - nodes = self.driver.list_nodes() - node = nodes[0] - self.assertTrue(isinstance(node.size, NodeSize)) - - def test_list_nodes_success_extra(self): - extra_keys = [ - "default_password", - "pending_charges", - "cost_per_month", - ] - nodes = self.driver.list_nodes() - for node in nodes: - self.assertTrue(len(node.extra.keys()) > 5) - self.assertTrue(all(item in node.extra.keys() for item in extra_keys)) - - def test_reboot_node_success(self): - node = self.driver.list_nodes()[0] - result = self.driver.reboot_node(node) - self.assertTrue(result) - - def test_create_node_success(self): - test_size = self.driver.list_sizes()[0] - test_image = self.driver.list_images()[0] - test_location = self.driver.list_locations()[0] - created_node = self.driver.create_node("test-node", test_size, test_image, test_location) - self.assertEqual(created_node.id, "41326859") - - def test_destroy_node_success(self): - node = self.driver.list_nodes()[0] - result = self.driver.destroy_node(node) - self.assertTrue(result) - - def test_list_key_pairs_success(self): - key_pairs = self.driver.list_key_pairs() - self.assertEqual(len(key_pairs), 1) - key_pair = key_pairs[0] - self.assertEqual(key_pair.id, "5806a8ef2a0c6") - self.assertEqual(key_pair.name, "test-key-pair") - - def test_create_key_pair_success(self): - res = self.driver.create_key_pair("test-key-pair") - self.assertTrue(res) - - def test_delete_key_pair_success(self): - key_pairs = self.driver.list_key_pairs() - key_pair = key_pairs[0] - res = self.driver.delete_key_pair(key_pair) - self.assertTrue(res) - - def test_rate_limit(self): - VultrMockHttp.type = "SERVICE_UNAVAILABLE" - self.assertRaises(ServiceUnavailableError, self.driver.list_nodes) - - -class VultrMockHttp(MockHttp): - fixtures = ComputeFileFixtures("vultr") - - # pylint: disable=unused-argument - def _v1_regions_list(self, method, url, body, headers): - body = self.fixtures.load("list_locations.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_os_list(self, method, url, body, headers): - body = self.fixtures.load("list_images.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_plans_list(self, method, url, body, headers): - body = self.fixtures.load("list_sizes.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_server_list(self, method, url, body, headers): - body = self.fixtures.load("list_nodes.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_server_list_SERVICE_UNAVAILABLE(self, method, url, body, headers): - body = self.fixtures.load("error_rate_limit.txt") - return ( - httplib.SERVICE_UNAVAILABLE, - body, - {}, - httplib.responses[httplib.SERVICE_UNAVAILABLE], - ) - - # pylint: disable=unused-argument - def _v1_server_create(self, method, url, body, headers): - body = self.fixtures.load("create_node.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_server_destroy(self, method, url, body, headers): - return (httplib.OK, "", {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_server_reboot(self, method, url, body, headers): - return (httplib.OK, "", {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_sshkey_list(self, method, url, body, headers): - body = self.fixtures.load("list_key_pairs.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_sshkey_create(self, method, url, body, headers): - body = self.fixtures.load("create_key_pair.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - # pylint: disable=unused-argument - def _v1_sshkey_destroy(self, method, url, body, headers): - return (httplib.OK, "", {}, httplib.responses[httplib.OK]) - - -if __name__ == "__main__": - sys.exit(unittest.main()) diff --git a/libcloud/test/dns/fixtures/gandi/update_zone.xml b/libcloud/test/dns/fixtures/gandi/update_zone.xml new file mode 100644 index 0000000000..da7588b6fc --- /dev/null +++ b/libcloud/test/dns/fixtures/gandi/update_zone.xml @@ -0,0 +1,43 @@ + + + + + + + + date_updated + 20101028T12:38:17 + + + domains + 0 + + + id + 47234 + + + name + other.com + + + owner + AB3917-GANDI + + + public + 0 + + + version + 1 + + + versions + + + + + + + diff --git a/libcloud/test/dns/fixtures/linode/create_domain.json b/libcloud/test/dns/fixtures/linode/create_domain.json deleted file mode 100644 index a9eef97a03..0000000000 --- a/libcloud/test/dns/fixtures/linode/create_domain.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ERRORARRAY": [], - "ACTION": "domain.create", - "DATA": { - "DomainID": 5094 - } -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/create_domain_validation_error.json b/libcloud/test/dns/fixtures/linode/create_domain_validation_error.json deleted file mode 100644 index 3c7059724f..0000000000 --- a/libcloud/test/dns/fixtures/linode/create_domain_validation_error.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ERRORARRAY": [ - { - "ERRORCODE": 8, - "ERRORMESSAGE": "The domain 'linode.com' already exists in our database. Please open a ticket if you think this is in error." - } - ], - "DATA": {}, - "ACTION": "domain.create" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/create_resource.json b/libcloud/test/dns/fixtures/linode/create_resource.json deleted file mode 100644 index 0fa3738e35..0000000000 --- a/libcloud/test/dns/fixtures/linode/create_resource.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": { - "ResourceID": 3585100 - }, - "ACTION": "domain.resource.create" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/delete_domain.json b/libcloud/test/dns/fixtures/linode/delete_domain.json deleted file mode 100644 index ff39a38ed7..0000000000 --- a/libcloud/test/dns/fixtures/linode/delete_domain.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ERRORARRAY": [], - "ACTION": "domain.delete", - "DATA": { - "DomainID": 5123 - } -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/delete_domain_does_not_exist.json b/libcloud/test/dns/fixtures/linode/delete_domain_does_not_exist.json deleted file mode 100644 index 8965baa6ee..0000000000 --- a/libcloud/test/dns/fixtures/linode/delete_domain_does_not_exist.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ERRORARRAY": [ - { - "ERRORCODE": 5, - "ERRORMESSAGE": "Object not found" - } - ], - "DATA": {}, - "ACTION": "domain.delete" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/delete_resource.json b/libcloud/test/dns/fixtures/linode/delete_resource.json deleted file mode 100644 index 7f7af2adbf..0000000000 --- a/libcloud/test/dns/fixtures/linode/delete_resource.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": { - "ResourceID": 3585141 - }, - "ACTION": "domain.resource.delete" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/delete_resource_does_not_exist.json b/libcloud/test/dns/fixtures/linode/delete_resource_does_not_exist.json deleted file mode 100644 index b6969aa64c..0000000000 --- a/libcloud/test/dns/fixtures/linode/delete_resource_does_not_exist.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ERRORARRAY": [ - { - "ERRORCODE": 5, - "ERRORMESSAGE": "Object not found" - } - ], - "DATA": {}, - "ACTION": "domain.resource.delete" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/domain_list.json b/libcloud/test/dns/fixtures/linode/domain_list.json deleted file mode 100644 index ac88b9b5ef..0000000000 --- a/libcloud/test/dns/fixtures/linode/domain_list.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "ERRORARRAY": [], - "ACTION": "domain.list", - "DATA": [ - { - "DOMAINID": 5093, - "DESCRIPTION": "", - "EXPIRE_SEC": 0, - "RETRY_SEC": 0, - "STATUS": 1, - "LPM_DISPLAYGROUP": "thing", - "MASTER_IPS": "", - "REFRESH_SEC": 0, - "SOA_EMAIL": "dns@example.com", - "TTL_SEC": 0, - "DOMAIN": "linode.com", - "AXFR_IPS": "none", - "TYPE": "master" - }, - { - "DOMAINID": 5094, - "DESCRIPTION": "", - "EXPIRE_SEC": 0, - "RETRY_SEC": 0, - "STATUS": 1, - "LPM_DISPLAYGROUP": "", - "MASTER_IPS": "2600:3c03::f03c:91ff:feae:e071;66.228.43.47;", - "REFRESH_SEC": 0, - "SOA_EMAIL": "", - "TTL_SEC": 0, - "DOMAIN": "0.c.d.7.0.6.0.f.1.0.7.4.0.1.0.0.2.ip6.arpa", - "AXFR_IPS": "2600:3c03::f03c:91ff:feae:e071;66.228.43.47;", - "TYPE": "slave" - } - ] -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/get_record.json b/libcloud/test/dns/fixtures/linode/get_record.json deleted file mode 100644 index 4d5b0eb597..0000000000 --- a/libcloud/test/dns/fixtures/linode/get_record.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [ - { - "DOMAINID": 5093, - "PORT": 80, - "RESOURCEID": 3585100, - "NAME": "www", - "WEIGHT": 5, - "TTL_SEC": 0, - "TARGET": "127.0.0.1", - "PRIORITY": 10, - "PROTOCOL": "", - "TYPE": "a" - } - ], - "ACTION": "domain.resource.list" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/get_record_does_not_exist.json b/libcloud/test/dns/fixtures/linode/get_record_does_not_exist.json deleted file mode 100644 index c2c1fb499d..0000000000 --- a/libcloud/test/dns/fixtures/linode/get_record_does_not_exist.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ERRORARRAY": [ - { - "ERRORCODE": 5, - "ERRORMESSAGE": "Object not found" - } - ], - "DATA": {}, - "ACTION": "domain.resource.list" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/get_zone.json b/libcloud/test/dns/fixtures/linode/get_zone.json deleted file mode 100644 index e9387122f6..0000000000 --- a/libcloud/test/dns/fixtures/linode/get_zone.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [ - { - "DOMAINID": 5093, - "DESCRIPTION": "", - "EXPIRE_SEC": 0, - "RETRY_SEC": 0, - "STATUS": 1, - "LPM_DISPLAYGROUP": "thing", - "MASTER_IPS": "", - "REFRESH_SEC": 0, - "SOA_EMAIL": "dns@example.com", - "TTL_SEC": 0, - "DOMAIN": "linode.com", - "AXFR_IPS": "none", - "TYPE": "master" - } - ], - "ACTION": "domain.list" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/get_zone_does_not_exist.json b/libcloud/test/dns/fixtures/linode/get_zone_does_not_exist.json deleted file mode 100644 index ea18547f51..0000000000 --- a/libcloud/test/dns/fixtures/linode/get_zone_does_not_exist.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ERRORARRAY": [ - { - "ERRORCODE": 5, - "ERRORMESSAGE": "Object not found" - } - ], - "DATA": {}, - "ACTION": "domain.list" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/resource_list.json b/libcloud/test/dns/fixtures/linode/resource_list.json deleted file mode 100644 index 2ed18cd821..0000000000 --- a/libcloud/test/dns/fixtures/linode/resource_list.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": [ - { - "DOMAINID": 5093, - "PORT": 80, - "RESOURCEID": 3585100, - "NAME": "mc", - "WEIGHT": 5, - "TTL_SEC": 0, - "TARGET": "127.0.0.1", - "PRIORITY": 10, - "PROTOCOL": "", - "TYPE": "a" - }, - { - "DOMAINID": 5093, - "PORT": 25565, - "RESOURCEID": 3585141, - "NAME": "_minecraft._udp", - "WEIGHT": 5, - "TTL_SEC": 0, - "TARGET": "mc.linode.com", - "PRIORITY": 10, - "PROTOCOL": "udp", - "TYPE": "srv" - } - ], - "ACTION": "domain.resource.list" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/resource_list_does_not_exist.json b/libcloud/test/dns/fixtures/linode/resource_list_does_not_exist.json deleted file mode 100644 index c2c1fb499d..0000000000 --- a/libcloud/test/dns/fixtures/linode/resource_list_does_not_exist.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ERRORARRAY": [ - { - "ERRORCODE": 5, - "ERRORMESSAGE": "Object not found" - } - ], - "DATA": {}, - "ACTION": "domain.resource.list" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/update_domain.json b/libcloud/test/dns/fixtures/linode/update_domain.json deleted file mode 100644 index f695f4c103..0000000000 --- a/libcloud/test/dns/fixtures/linode/update_domain.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": { - "DomainID": 5093 - }, - "ACTION": "domain.update" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/linode/update_resource.json b/libcloud/test/dns/fixtures/linode/update_resource.json deleted file mode 100644 index 7f88aabe6f..0000000000 --- a/libcloud/test/dns/fixtures/linode/update_resource.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ERRORARRAY": [], - "DATA": { - "ResourceID": 3585100 - }, - "ACTION": "domain.resource.update" -} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/luadns/create_record_success.json b/libcloud/test/dns/fixtures/luadns/create_record_success.json index c72c2b9708..6e79aaa3ec 100644 --- a/libcloud/test/dns/fixtures/luadns/create_record_success.json +++ b/libcloud/test/dns/fixtures/luadns/create_record_success.json @@ -1,6 +1,6 @@ { "id": 31, - "name": "test.com.", + "name": "example.com.", "type": "A", "content": "127.0.0.1", "ttl": 13, diff --git a/libcloud/test/dns/fixtures/luadns/get_record.json b/libcloud/test/dns/fixtures/luadns/get_record.json index 29dc374e15..c42441e606 100644 --- a/libcloud/test/dns/fixtures/luadns/get_record.json +++ b/libcloud/test/dns/fixtures/luadns/get_record.json @@ -1,6 +1,6 @@ { "id": 31, - "name": "example.com.", + "name": "example.org.", "type": "MX", "content": "10 mail.example.com.", "ttl": 300, diff --git a/libcloud/test/dns/fixtures/luadns/records_list.json b/libcloud/test/dns/fixtures/luadns/records_list.json index d4cd30a686..826073394e 100644 --- a/libcloud/test/dns/fixtures/luadns/records_list.json +++ b/libcloud/test/dns/fixtures/luadns/records_list.json @@ -1,7 +1,7 @@ [ { "id": 6683, - "name": "example.org.", + "name": "example.com.", "type": "NS", "content": "b.ns.luadns.net.", "ttl": 86400, @@ -11,7 +11,7 @@ }, { "id": 6684, - "name": "example.org.", + "name": "example.com.", "type": "NS", "content": "a.ns.luadns.net.", "ttl": 86400, diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json index f13b76a1c3..2e3f5f8a0b 100644 --- a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json +++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json @@ -1,6 +1,6 @@ { "zone_record": { - "name": "updated.com", + "name": "updated.example.com", "data": "1.2.3.5", "id": 141, "aux": null, diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json index 607a68d047..a2a1468b79 100644 --- a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json +++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json @@ -11,7 +11,7 @@ }, { "zone_record": { - "name": "site.example1.com", + "name": "site1.example.com", "data": "1.2.3.6", "id": 150, "aux": null, diff --git a/libcloud/test/dns/fixtures/vultr/delete_zone.json b/libcloud/test/dns/fixtures/vultr/delete_zone.json deleted file mode 100644 index fe51488c70..0000000000 --- a/libcloud/test/dns/fixtures/vultr/delete_zone.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/libcloud/test/dns/fixtures/vultr/empty_records_list.json b/libcloud/test/dns/fixtures/vultr/empty_records_list.json deleted file mode 100644 index fe51488c70..0000000000 --- a/libcloud/test/dns/fixtures/vultr/empty_records_list.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/libcloud/test/dns/fixtures/vultr/empty_zones_list.json b/libcloud/test/dns/fixtures/vultr/empty_zones_list.json deleted file mode 100644 index 41b42e677b..0000000000 --- a/libcloud/test/dns/fixtures/vultr/empty_zones_list.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - -] diff --git a/libcloud/test/dns/fixtures/vultr/get_record.json b/libcloud/test/dns/fixtures/vultr/get_record.json deleted file mode 100644 index 2c7145145a..0000000000 --- a/libcloud/test/dns/fixtures/vultr/get_record.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "type":"A", - "name":"zupo", - "data":"127.0.0.1", - "priority":0, - "RECORDID":1300 - - } - -] diff --git a/libcloud/test/dns/fixtures/vultr/get_zone.json b/libcloud/test/dns/fixtures/vultr/get_zone.json deleted file mode 100644 index 7639145980..0000000000 --- a/libcloud/test/dns/fixtures/vultr/get_zone.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "domain":"zupo.com", - "date_created":"2015-11-12 16:58:59" - } - -] diff --git a/libcloud/test/dns/fixtures/vultr/list_domains.json b/libcloud/test/dns/fixtures/vultr/list_domains.json deleted file mode 100644 index a2730cb2d1..0000000000 --- a/libcloud/test/dns/fixtures/vultr/list_domains.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "domain": "example.com", - "date_created": "2014-12-11 16:20:59" - }, - { - "domain": "zupo.com", - "date_created": "2014-12-11 16:21:50" - }, - - { - "domain":"oltjano.com", - "date_created": "2014-12-11 16:21:40" - }, - - { - "domain":"13.com", - "date_created":"2015-12-11 16:21:50" - } - -] diff --git a/libcloud/test/dns/fixtures/vultr/list_records.json b/libcloud/test/dns/fixtures/vultr/list_records.json deleted file mode 100644 index e6430831f1..0000000000 --- a/libcloud/test/dns/fixtures/vultr/list_records.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "type":"A", - "name":"arecord", - "data":"127.0.0.1", - "RECORDID":13 - }, - - { - "type":"CNAME", - "name":"*", - "data":"example.com", - "priority":0, - "RECORDID":1265277 - } - - - -] diff --git a/libcloud/test/dns/fixtures/vultr/test_zone.json b/libcloud/test/dns/fixtures/vultr/test_zone.json deleted file mode 100644 index 0cf5519bb7..0000000000 --- a/libcloud/test/dns/fixtures/vultr/test_zone.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - - { - "domain":"test.com", - "date_created":"1-1-2015 18:31:31" - - } - -] diff --git a/libcloud/test/dns/fixtures/zerigo/create_record.xml b/libcloud/test/dns/fixtures/zerigo/create_record.xml deleted file mode 100644 index 2a44e00e22..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/create_record.xml +++ /dev/null @@ -1,13 +0,0 @@ - - 2008-12-07T02:51:13Z - 127.0.0.1 - www.example.com - A - www - 23456780 - - - - 2008-12-07T02:51:13Z - 12345678 - diff --git a/libcloud/test/dns/fixtures/zerigo/create_zone.xml b/libcloud/test/dns/fixtures/zerigo/create_zone.xml deleted file mode 100644 index a48156c667..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/create_zone.xml +++ /dev/null @@ -1,18 +0,0 @@ - - 2008-12-07T02:40:02Z - ns1.example.com,ns2.example.com - true - 600 - foo.bar.com - dnsadmin@example.com - 12345679 - - - pri_sec - - - - 2008-12-07T02:40:02Z - 0 - - diff --git a/libcloud/test/dns/fixtures/zerigo/create_zone_validation_error.xml b/libcloud/test/dns/fixtures/zerigo/create_zone_validation_error.xml deleted file mode 100644 index 664b9719ed..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/create_zone_validation_error.xml +++ /dev/null @@ -1,4 +0,0 @@ - - Ns type is not included in the list - Default ttl must be greater than or equal to 60 - diff --git a/libcloud/test/dns/fixtures/zerigo/get_record.xml b/libcloud/test/dns/fixtures/zerigo/get_record.xml deleted file mode 100644 index 31619a8935..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/get_record.xml +++ /dev/null @@ -1,13 +0,0 @@ - - 2008-12-07T02:51:13Z - 172.16.16.1 - example.com - A - www - 23456789 - - - - 2008-12-07T02:51:13Z - 12345678 - diff --git a/libcloud/test/dns/fixtures/zerigo/get_zone.xml b/libcloud/test/dns/fixtures/zerigo/get_zone.xml deleted file mode 100644 index f94522c457..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/get_zone.xml +++ /dev/null @@ -1,32 +0,0 @@ - - 2008-12-07T02:40:02Z - ns1.example.com,ns2.example.com - true - 600 - example.com - dnsadmin@example.com - 12345678 - - - pri_sec - - - one two - 2008-12-07T02:40:02Z - 1 - - - 2008-12-07T02:51:13Z - 172.16.16.1 - example.com - A - - 23456789 - - - - 2008-12-07T02:51:13Z - 12345678 - - - diff --git a/libcloud/test/dns/fixtures/zerigo/list_records.xml b/libcloud/test/dns/fixtures/zerigo/list_records.xml deleted file mode 100644 index f226162c79..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/list_records.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 2008-12-07T02:51:13Z - 172.16.16.1 - www.example.com - A - www - 23456789 - - - - 2008-12-07T02:51:13Z - 12345678 - - - 2008-12-07T02:51:13Z - 172.16.16.2 - test.example.com - A - test - 23456789 - - - 3600 - 2008-12-07T02:51:13Z - 12345678 - - - 2008-12-07T02:51:13Z - 172.16.16.3 - test2.example.com - A - - 23456789 - - - 3600 - 2008-12-07T02:51:13Z - 12345678 - - - 2008-12-07T02:51:13Z - 172.16.16.4 - test4.example.com - A - 23456789 - - - 3600 - 2008-12-07T02:51:13Z - 12345678 - - - diff --git a/libcloud/test/dns/fixtures/zerigo/list_records_no_results.xml b/libcloud/test/dns/fixtures/zerigo/list_records_no_results.xml deleted file mode 100644 index 7020c67a6e..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/list_records_no_results.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libcloud/test/dns/fixtures/zerigo/list_zones.xml b/libcloud/test/dns/fixtures/zerigo/list_zones.xml deleted file mode 100644 index 3870926e05..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/list_zones.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - 2008-12-07T02:40:02Z - - false - 600 - example.com - - 12345678 - test foo bar - - pri_sec - - - 2008-12-07T02:40:02Z - - diff --git a/libcloud/test/dns/fixtures/zerigo/list_zones_no_results.xml b/libcloud/test/dns/fixtures/zerigo/list_zones_no_results.xml deleted file mode 100644 index 0572fec45c..0000000000 --- a/libcloud/test/dns/fixtures/zerigo/list_zones_no_results.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libcloud/test/dns/test_base.py b/libcloud/test/dns/test_base.py index 006bb8cd92..717a4af48b 100644 --- a/libcloud/test/dns/test_base.py +++ b/libcloud/test/dns/test_base.py @@ -63,6 +63,50 @@ def setUp(self): self.driver = DNSDriver("none", "none") self.tmp_file = tempfile.mkstemp() self.tmp_path = self.tmp_file[1] + self.master_zone = Zone( + id=1, domain="example.com", type="master", ttl=900, driver=self.driver + ) + + def test_zone_helpers(self): + zone = self.master_zone + + for func in (zone.prefix, zone.hostname, zone.fqdn): + with self.assertRaises(AttributeError): + self.assertEqual(func(None)) + + for apex in ("", "example.com", "example.com."): + self.assertEqual(zone.prefix(apex), "") + self.assertEqual(zone.hostname(apex), "example.com") + self.assertEqual(zone.fqdn(apex), "example.com.") + + for sub in ("sub", "sub.example.com", "sub.example.com."): + self.assertEqual(zone.prefix(sub), "sub") + self.assertEqual(zone.hostname(sub), "sub.example.com") + self.assertEqual(zone.fqdn(sub), "sub.example.com.") + + def test_record_init(self): + common = { + "id": None, + "name": None, + "type": RecordType.A, + "data": "0.0.0.0", + "zone": self.master_zone, + "driver": self.master_zone, + } + + for apex in (None, "", "example.com", "example.com."): + common["name"] = apex + r1 = Record(**common) + self.assertEqual(r1.name, "") + self.assertEqual(r1.hostname, "example.com") + self.assertEqual(r1.fqdn, "example.com.") + + for sub in ("sub", "sub.example.com", "sub.example.com."): + common["name"] = sub + r2 = Record(**common) + self.assertEqual(r2.name, "sub") + self.assertEqual(r2.hostname, "sub.example.com") + self.assertEqual(r2.fqdn, "sub.example.com.") def test_export_zone_to_bind_format_slave_should_throw(self): zone = Zone(id=1, domain="example.com", type="slave", ttl=900, driver=self.driver) @@ -139,7 +183,7 @@ def test_export_zone_to_bind_format_success(self): def test_get_numeric_id(self): values = MOCK_RECORDS_VALUES[0].copy() values["driver"] = self.driver - values["zone"] = None + values["zone"] = self.master_zone record = Record(**values) record.id = "abcd" @@ -162,6 +206,36 @@ def test_get_numeric_id(self): result = record._get_numeric_id() self.assertEqual(result, "") + def test_driver_to_default_id(self): + data = [ + # name, type, id (expected) + ("", "A", "A"), + ("example.com", "A", "A"), + ("example.com.", "A", "A"), + ("mail", "MX", "MX:mail"), + ("mail.example.com", "MX", "MX:mail"), + ("mail.example.com.", "MX", "MX:mail"), + ] + for rname, rtype, rexpect in data: + rid = self.driver.to_default_id(self.master_zone, rname, rtype) + self.assertEqual(rid, rexpect) + + def test_driver_from_default_id(self): + data = [ + # id, name (expected), type (expected) + ("A", "", "A"), # without trailing colon + ("A:", "", "A"), # with trailing colon + ("A:example.com", "", "A"), + ("A:example.com.", "", "A"), + ("MX:mail", "mail", "MX"), + ("MX:mail.example.com", "mail", "MX"), + ("MX:mail.example.com.", "mail", "MX"), + ] + for rid, rname, rtype in data: + rparts = self.driver.from_default_id(self.master_zone, rid) + self.assertEqual(rparts.name, rname) + self.assertEqual(rparts.type, rtype) + def zero_pad(value: int) -> str: if value < 10: diff --git a/libcloud/test/dns/test_buddyns.py b/libcloud/test/dns/test_buddyns.py index 4506e99965..55e2e8c950 100644 --- a/libcloud/test/dns/test_buddyns.py +++ b/libcloud/test/dns/test_buddyns.py @@ -28,6 +28,7 @@ class BuddyNSDNSTests(unittest.TestCase): def setUp(self): BuddyNSMockHttp.type = None + BuddyNSMockHttp.history.clear() BuddyNSDNSDriver.connectionCls.conn_class = BuddyNSMockHttp self.driver = BuddyNSDNSDriver(*DNS_PARAMS_BUDDYNS) self.test_zone = Zone( @@ -43,12 +44,24 @@ def test_list_zones_empty(self): BuddyNSMockHttp.type = "EMPTY_ZONES_LIST" zones = self.driver.list_zones() + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api/v2/zone/") + self.assertEqual(zones, []) def test_list_zones_success(self): BuddyNSMockHttp.type = "LIST_ZONES" zones = self.driver.list_zones() + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api/v2/zone/") + self.assertEqual(len(zones), 2) zone = zones[0] @@ -73,12 +86,24 @@ def test_delete_zone_zone_does_not_exist(self): else: self.fail("Exception was not thrown") + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, f"/api/v2/zone/{self.test_zone.domain}") + def test_delete_zone_success(self): BuddyNSMockHttp.type = "DELETE_ZONE_SUCCESS" status = self.driver.delete_zone(zone=self.test_zone) self.assertTrue(status) + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, f"/api/v2/zone/{self.test_zone.domain}") + def test_get_zone_zone_does_not_exist(self): BuddyNSMockHttp.type = "GET_ZONE_ZONE_DOES_NOT_EXIST" try: @@ -88,10 +113,22 @@ def test_get_zone_zone_does_not_exist(self): else: self.fail("Exception was not thrown") + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api/v2/zone/zonedoesnotexist.com") + def test_get_zone_success(self): BuddyNSMockHttp.type = "GET_ZONE_SUCCESS" zone = self.driver.get_zone(zone_id="myexample.com") + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api/v2/zone/myexample.com") + self.assertEqual(zone.id, "myexample.com") self.assertEqual(zone.domain, "myexample.com") self.assertIsNone(zone.type) @@ -102,6 +139,13 @@ def test_create_zone_success(self): BuddyNSMockHttp.type = "CREATE_ZONE_SUCCESS" zone = self.driver.create_zone(domain="microsoft.com") + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/api/v2/zone/") + self.assertEqual(sent.json["name"], "microsoft.com") + self.assertEqual(zone.id, "microsoft.com") self.assertEqual(zone.domain, "microsoft.com") self.assertIsNone(zone.type), @@ -117,9 +161,18 @@ def test_create_zone_zone_already_exists(self): else: self.fail("Exception was not thrown") + reqs = BuddyNSMockHttp.history + self.assertEqual(len(reqs), 1) + sent = reqs.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/api/v2/zone/") + self.assertEqual(sent.json["name"], "newzone.com") + self.assertEqual(sent.json["master"], "13.0.0.1") + class BuddyNSMockHttp(MockHttp): fixtures = DNSFileFixtures("buddyns") + keep_history = True def _api_v2_zone_EMPTY_ZONES_LIST(self, method, url, body, headers): body = self.fixtures.load("empty_zones_list.json") diff --git a/libcloud/test/dns/test_cloudflare.py b/libcloud/test/dns/test_cloudflare.py index 87da31ff9a..acded3c693 100644 --- a/libcloud/test/dns/test_cloudflare.py +++ b/libcloud/test/dns/test_cloudflare.py @@ -39,6 +39,7 @@ def setUp(self): CloudFlareDNSDriver.MEMBERSHIPS_PAGE_SIZE = 5 CloudFlareMockHttp.type = None CloudFlareMockHttp.use_param = "a" + CloudFlareMockHttp.history.clear() self.driver = CloudFlareDNSDriver(*DNS_PARAMS_CLOUDFLARE) def test_auth_key(self): @@ -56,6 +57,11 @@ def test_list_record_types(self): def test_list_zones(self): zones = self.driver.list_zones() + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url.rsplit("?")[0], "/client/v4/zones") + self.assertEqual(len(zones), 1) zone = zones[0] @@ -69,8 +75,12 @@ def test_list_zones(self): def test_get_record(self): record = self.driver.get_record("1234", "364797364") + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/client/v4/zones/1234/dns_records/364797364") + self.assertEqual(record.id, "364797364") - self.assertIsNone(record.name) + self.assertEqual(record.name, "") self.assertEqual(record.type, "A") self.assertEqual(record.data, "192.30.252.153") @@ -85,11 +95,16 @@ def test_get_record_record_is_invalid(self): def test_list_records(self): zone = self.driver.list_zones()[0] records = self.driver.list_records(zone=zone) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url.rsplit("?")[0], f"/client/v4/zones/{zone.id}/dns_records") + self.assertEqual(len(records), 11) record = records[0] self.assertEqual(record.id, "364797364") - self.assertIsNone(record.name) + self.assertEqual(record.name, "") self.assertEqual(record.type, "A") self.assertEqual(record.data, "192.30.252.153") self.assertEqual(record.extra["priority"], None) @@ -108,7 +123,7 @@ def test_list_records(self): record = [r for r in records if r.type == "MX"][0] self.assertEqual(record.id, "78526") - self.assertIsNone(record.name) + self.assertEqual(record.name, "") self.assertEqual(record.type, "MX") self.assertEqual(record.data, "aspmx3.googlemail.com") self.assertEqual(record.extra["priority"], 30) @@ -121,6 +136,11 @@ def test_list_records(self): def test_get_zone(self): zone = self.driver.get_zone(zone_id="1234") + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/client/v4/zones/1234") + self.assertEqual(zone.id, "1234") self.assertEqual(zone.domain, "example.com") self.assertEqual(zone.type, "master") @@ -142,6 +162,15 @@ def test_create_record(self): data="127.0.0.3", extra={"proxied": True}, ) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}/dns_records") + self.assertEqual(sent.json["type"], "A") + self.assertEqual(sent.json["name"], "test5") + self.assertEqual(sent.json["content"], "127.0.0.3") + self.assertEqual(sent.json["proxied"], True) + self.assertEqual(record.id, "412561327") self.assertEqual(record.name, "test5") self.assertEqual(record.type, "A") @@ -154,6 +183,17 @@ def test_create_record_SSHFP_record_type(self): record = self.driver.create_record( name="test_sshfp", zone=zone, type=RecordType.SSHFP, data="2 1 ABCDEF12345" ) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}/dns_records") + self.assertEqual(sent.json["type"], "SSHFP") + self.assertEqual(sent.json["name"], "test_sshfp") + self.assertEqual(sent.json["content"], None) + self.assertEqual( + sent.json["data"], {"algorithm": "2", "type": "1", "fingerprint": "ABCDEF12345"} + ) + self.assertEqual(record.id, "200") self.assertEqual(record.name, "test_sshfp") self.assertEqual(record.type, "SSHFP") @@ -166,6 +206,14 @@ def test_create_record_CAA_record_type(self): record = self.driver.create_record( name="test5", zone=zone, type=RecordType.CAA, data="0 issue caa.example.com" ) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}/dns_records") + self.assertEqual(sent.json["type"], "CAA") + self.assertEqual(sent.json["name"], "test5") + self.assertEqual(sent.json["content"], "0\tissue\tcaa.example.com") + self.assertEqual(record.id, "412561327") self.assertEqual(record.name, "test5") self.assertEqual(record.type, "A") @@ -214,6 +262,14 @@ def test_update_record(self): extra={"proxied": True}, ) + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "PUT") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}/dns_records/{record.id}") + self.assertEqual(sent.json["type"], "A") + self.assertEqual(sent.json["name"], "test6") + self.assertEqual(sent.json["content"], "127.0.0.4") + self.assertEqual(sent.json["extra"]["proxied"], True) + self.assertEqual(updated_record.name, "test6") self.assertEqual(updated_record.type, "A") self.assertEqual(updated_record.data, "127.0.0.4") @@ -233,15 +289,33 @@ def test_delete_record(self): zone = self.driver.list_zones()[0] record = zone.list_records()[0] result = self.driver.delete_record(record=record) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}/dns_records/{record.id}") + self.assertTrue(result) def test_delete_zone(self): zone = self.driver.list_zones()[0] result = self.driver.delete_zone(zone=zone) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}") + self.assertTrue(result) def test_create_zone(self): zone = self.driver.create_zone(domain="example2.com", extra={"jump_start": False}) + + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/client/v4/zones") + self.assertEqual(sent.json["type"], "full") + self.assertEqual(sent.json["name"], "example2.com") + self.assertEqual(sent.json["jump_start"], False) + self.assertEqual(zone.id, "6789") self.assertEqual(zone.domain, "example2.com") @@ -257,6 +331,11 @@ def test_update_zone(self): updated_zone = self.driver.update_zone(zone=zone, domain="", extra={"paused": True}) + sent = CloudFlareMockHttp.history.pop() + self.assertEqual(sent.method, "PATCH") + self.assertEqual(sent.url, f"/client/v4/zones/{zone.id}") + self.assertEqual(sent.json["paused"], True) + self.assertEqual(zone.id, updated_zone.id) self.assertEqual(zone.domain, updated_zone.domain) self.assertEqual(zone.type, updated_zone.type) @@ -312,6 +391,7 @@ def test_normalize_record_data_from_apu(self): class CloudFlareMockHttp(MockHttp, unittest.TestCase): fixtures = DNSFileFixtures("cloudflare") + keep_history = True def _client_v4_memberships(self, method, url, body, headers): if method not in {"GET"}: diff --git a/libcloud/test/dns/test_digitalocean.py b/libcloud/test/dns/test_digitalocean.py index 2b2118b385..3a4c943943 100644 --- a/libcloud/test/dns/test_digitalocean.py +++ b/libcloud/test/dns/test_digitalocean.py @@ -31,18 +31,30 @@ def setUp(self): DigitalOcean_v2_BaseDriver.connectionCls.conn_class = DigitalOceanDNSMockHttp DigitalOceanDNSDriver.connectionCls.conn_class = DigitalOceanDNSMockHttp DigitalOceanDNSMockHttp.type = None + DigitalOceanDNSMockHttp.history.clear() self.driver = DigitalOceanDNSDriver(*DIGITALOCEAN_v2_PARAMS) def tearDown(self): LibcloudConnection.type = None DigitalOceanDNSMockHttp.type = None + DigitalOceanDNSMockHttp.history.clear() def test_list_zones(self): zones = self.driver.list_zones() + + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains") + self.assertTrue(len(zones) >= 1) def test_get_zone(self): zone = self.driver.get_zone("testdomain") + + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains/testdomain") + self.assertEqual(zone.id, "testdomain") def test_get_zone_not_found(self): @@ -52,12 +64,24 @@ def test_get_zone_not_found(self): def test_list_records(self): zone = self.driver.get_zone("testdomain") records = self.driver.list_records(zone) + + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains/testdomain/records") + self.assertTrue(len(records) >= 1) self.assertEqual(records[1].ttl, 1800) self.assertEqual(records[4].ttl, None) def test_get_record(self): record = self.driver.get_record("testdomain", "1234564") + + # [0] '/v2/domains/testdomain/records/1234564' + # [1] '/v2/domains/testdomain' + sent = DigitalOceanDNSMockHttp.history[0] + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains/testdomain/records/1234564") + self.assertEqual(record.id, "1234564") self.assertEqual(record.type, RecordType.A) self.assertEqual(record.data, "123.45.67.89") @@ -70,6 +94,12 @@ def test_get_record_not_found(self): def test_create_zone(self): DigitalOceanDNSMockHttp.type = "CREATE" zone = self.driver.create_zone("testdomain") + + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/v2/domains") + self.assertEqual(sent.json["name"], "testdomain") + self.assertEqual(zone.id, "testdomain") def test_create_record(self): @@ -79,6 +109,15 @@ def test_create_record(self): record = self.driver.create_record( "sub", zone, RecordType.A, "234.56.78.90", extra={"ttl": 60} ) + + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, f"/v2/domains/{zone.domain}/records") + self.assertEqual(sent.json["type"], "A") + self.assertEqual(sent.json["name"], "sub") + self.assertEqual(sent.json["data"], "234.56.78.90") + self.assertEqual(sent.json["ttl"], 60) + self.assertEqual(record.id, "1234565") self.assertEqual(record.type, RecordType.A) self.assertEqual(record.data, "234.56.78.90") @@ -89,6 +128,15 @@ def test_update_record(self): DigitalOceanDNSMockHttp.type = "UPDATE" record = self.driver.update_record(record, data="234.56.78.90", extra={"ttl": 60}) + + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertIn(sent.method, {"PATCH", "PUT"}) + self.assertEqual(sent.url, f"/v2/domains/testdomain/records/{record.id}") + self.assertEqual(sent.json["type"], "A") + self.assertEqual(sent.json["name"], "@") + self.assertEqual(sent.json["data"], "234.56.78.90") + self.assertEqual(sent.json["ttl"], 60) + self.assertEqual(record.id, "1234564") self.assertEqual(record.data, "234.56.78.90") self.assertEqual(record.ttl, 60) @@ -99,15 +147,24 @@ def test_delete_zone(self): DigitalOceanDNSMockHttp.type = "DELETE" self.assertTrue(self.driver.delete_zone(zone)) + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, f"/v2/domains/{zone.domain}") + def test_delete_record(self): record = self.driver.get_record("testdomain", "1234564") DigitalOceanDNSMockHttp.type = "DELETE" self.assertTrue(self.driver.delete_record(record)) + sent = DigitalOceanDNSMockHttp.history.pop() + self.assertIn(sent.method, "DELETE") + self.assertEqual(sent.url, f"/v2/domains/testdomain/records/{record.id}") + class DigitalOceanDNSMockHttp(MockHttp): fixtures = DNSFileFixtures("digitalocean") + keep_history = True response_map = { None: httplib.OK, diff --git a/libcloud/test/dns/test_dnsimple.py b/libcloud/test/dns/test_dnsimple.py index 09d1d5c9fc..e483ebfacf 100644 --- a/libcloud/test/dns/test_dnsimple.py +++ b/libcloud/test/dns/test_dnsimple.py @@ -24,6 +24,7 @@ from libcloud.dns.drivers.dnsimple import DNSimpleDNSDriver +@unittest.skip("v1 API is deprecated") class DNSimpleDNSTests(unittest.TestCase): def setUp(self): DNSimpleDNSDriver.connectionCls.conn_class = DNSimpleDNSMockHttp diff --git a/libcloud/test/dns/test_dnspod.py b/libcloud/test/dns/test_dnspod.py index bbbf6f07f4..f7ad2c1c2c 100644 --- a/libcloud/test/dns/test_dnspod.py +++ b/libcloud/test/dns/test_dnspod.py @@ -25,7 +25,7 @@ RecordDoesNotExistError, RecordAlreadyExistsError, ) -from libcloud.utils.py3 import httplib +from libcloud.utils.py3 import httplib, parse_qs from libcloud.test.secrets import DNS_PARAMS_DNSPOD from libcloud.dns.drivers.dnspod import DNSPodDNSDriver from libcloud.test.file_fixtures import DNSFileFixtures @@ -34,6 +34,7 @@ class DNSPodDNSTests(unittest.TestCase): def setUp(self): DNSPodMockHttp.type = None + DNSPodMockHttp.history.clear() DNSPodDNSDriver.connectionCls.conn_class = DNSPodMockHttp self.driver = DNSPodDNSDriver(*DNS_PARAMS_DNSPOD) self.test_zone = Zone( @@ -67,6 +68,10 @@ def test_list_zones_success(self): DNSPodMockHttp.type = "LIST_ZONES" zones = self.driver.list_zones() + sent = DNSPodMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Domain.List") + self.assertEqual(len(zones), 1) zone = zones[0] @@ -89,6 +94,12 @@ def test_get_zone_success(self): DNSPodMockHttp.type = "GET_ZONE_SUCCESS" zone = self.driver.get_zone(zone_id="6") + sent = DNSPodMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Domain.Info") + data = parse_qs(sent.body) + self.assertIn("6", data["domain_id"]) + self.assertEqual(zone.id, "6") self.assertEqual(zone.domain, "dnspod.com") self.assertIsNone(zone.type) @@ -100,6 +111,12 @@ def test_delete_zone_success(self): zone = self.test_zone status = self.driver.delete_zone(zone=zone) + sent = DNSPodMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Domain.Remove") + data = parse_qs(sent.body) + self.assertIn(zone.id, data["domain_id"]) + self.assertEqual(status, True) def test_delete_zone_zone_does_not_exist(self): @@ -114,7 +131,13 @@ def test_delete_zone_zone_does_not_exist(self): def test_create_zone_success(self): DNSPodMockHttp.type = "CREATE_ZONE_SUCCESS" - zone = self.driver.create_zone(domain="example.org") + zone = self.driver.create_zone(domain="api2.com") + + sent = DNSPodMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Domain.Create") + data = parse_qs(sent.body) + self.assertIn("api2.com", data["domain"]) self.assertEqual(zone.id, "3") self.assertEqual(zone.domain, "api2.com") @@ -135,6 +158,13 @@ def test_list_records_success(self): DNSPodMockHttp.type = "LIST_RECORDS_SUCCESS" zone = self.test_zone records = self.driver.list_records(zone=zone) + + sent = DNSPodMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Record.List") + data = parse_qs(sent.body) + self.assertIn(zone.id, data["domain_id"]) + first_record = records[0] self.assertEqual(len(records), 5) @@ -145,7 +175,14 @@ def test_list_records_success(self): def test_get_record_success(self): DNSPodMockHttp.type = "GET_RECORD_SUCCESS" - record = self.driver.get_record(zone_id="31", record_id="31") + record = self.driver.get_record(zone_id="6", record_id="50") + + sent = DNSPodMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Record.Info") + body = parse_qs(sent.body) + self.assertIn("6", body["domain_id"]) + self.assertIn("50", body["record_id"]) self.assertEqual(record.id, "50") self.assertEqual(record.type, "A") @@ -178,6 +215,20 @@ def test_create_record_success(self): data="96.126.115.73", extra={"ttl": 13, "record_line": "default"}, ) + + # [0] /Record.Create + # [1] /Record.Info + sent = DNSPodMockHttp.history[0] + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/Record.Create") + data = parse_qs(sent.body) + self.assertIn(self.test_zone.id, data["domain_id"]) + self.assertIn("A", data["record_type"]) + self.assertIn("@", data["sub_domain"]) + self.assertIn("96.126.115.73", data["value"]) + self.assertIn("13", data["ttl"]) + self.assertIn("default", data["record_line"]) + self.assertEqual(record.id, "50") self.assertEqual(record.name, "@") self.assertEqual(record.data, "96.126.115.73") @@ -201,6 +252,7 @@ def test_create_record_already_exists_error(self): class DNSPodMockHttp(MockHttp): fixtures = DNSFileFixtures("dnspod") + keep_history = True def _Domain_List_EMPTY_ZONES_LIST(self, method, url, body, headers): body = self.fixtures.load("empty_zones_list.json") diff --git a/libcloud/test/dns/test_durabledns.py b/libcloud/test/dns/test_durabledns.py index c98768dfe4..dac7320899 100644 --- a/libcloud/test/dns/test_durabledns.py +++ b/libcloud/test/dns/test_durabledns.py @@ -39,6 +39,7 @@ class DurableDNSTests(LibcloudTestCase): def setUp(self): DurableDNSDriver.connectionCls.conn_class = DurableDNSMockHttp DurableDNSMockHttp.type = None + DurableDNSMockHttp.history.clear() self.driver = DurableDNSDriver(*DNS_PARAMS_DURABLEDNS) def assertHasKeys(self, dictionary, keys): @@ -81,10 +82,15 @@ def test_list_zones(self): ) self.driver.get_zone = MagicMock(return_value=zone) zones = self.driver.list_zones() + + sent = DurableDNSMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/services/dns/listZones.php") + self.assertEqual(len(zones), 2) zone = zones[0] self.assertEqual(zone.id, "myzone.com.") - self.assertEqual(zone.domain, "myzone.com.") + self.assertEqual(zone.domain, "myzone.com") self.assertEqual(zone.ttl, 1300) self.assertEqual(zone.extra["ns"], "ns1.durabledns.com.") self.assertEqual(zone.extra["mbox"], "mail.myzone.com") @@ -128,6 +134,13 @@ def test_list_records(self): ) self.driver.get_record = MagicMock(return_value=record) records = self.driver.list_records(zone=zone) + + sent = DurableDNSMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/services/dns/listRecords.php") + data = sent.body.decode() + self.assertIn(":zonename>myzone.com.myzone.com.myzone.com.deletedzone.com.myzone.com.record14500myzone.com.myzone.com.353286987domain.zone.list/n<", "><") + self.assertIn("domain.zone.record.list47234/n<", "><") + self.assertIn("domain.zone.info47234/n<", "><") + self.assertIn("domain.zone.createt.com/n<", "><") + self.assertIn("domain.zone.update47234other.com/n<", "><") + self.assertIn("domain.zone.record.add{zone.id}www127.0.0.130/n<", "><") + self.assertIn("domain.zone.record.add{zone.id}wwwA127.0.0.130/n<", "><") + self.assertIn("domain.zone.delete{zone.id}/n<", "><") + self.assertIn("domain.zone.record.delete{zone.id}1{record.name}t.com", xml) + self.assertEqual(zone.id, "47234") self.assertEqual(zone.domain, "t.com") @@ -139,6 +164,16 @@ def test_create_record(self): name="www", zone=zone, type=RecordType.A, data="127.0.0.1", extra={"ttl": 0} ) + sent = Route53MockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/2012-02-29/hostedzone/47234/rrset") + xml = sent.body.decode() + self.assertIn("CREATE", xml) + self.assertIn("www.t.com", xml) + self.assertIn("A", xml) + self.assertIn("127.0.0.1", xml) + self.assertIn("0", xml) + self.assertEqual(record.id, "A:www") self.assertEqual(record.name, "www") self.assertEqual(record.zone, zone) @@ -151,7 +186,17 @@ def test_create_record_zone_name(self): name="", zone=zone, type=RecordType.A, data="127.0.0.1", extra={"ttl": 0} ) - self.assertEqual(record.id, "A:") + sent = Route53MockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/2012-02-29/hostedzone/47234/rrset") + xml = sent.body.decode() + self.assertIn("CREATE", xml) + self.assertIn("t.com", xml) + self.assertIn("A", xml) + self.assertIn("127.0.0.1", xml) + self.assertIn("0", xml) + + self.assertEqual(record.id, "A") self.assertEqual(record.name, "") self.assertEqual(record.zone, zone) self.assertEqual(record.type, RecordType.A) @@ -163,7 +208,7 @@ def test_create_TXT_record(self): """ zone = self.driver.list_zones()[0] record = self.driver.create_record(name="", zone=zone, type=RecordType.TXT, data="test") - self.assertEqual(record.id, "TXT:") + self.assertEqual(record.id, "TXT") self.assertEqual(record.name, "") self.assertEqual(record.zone, zone) self.assertEqual(record.type, RecordType.TXT) @@ -175,7 +220,7 @@ def test_create_TXT_record_quoted(self): """ zone = self.driver.list_zones()[0] record = self.driver.create_record(name="", zone=zone, type=RecordType.TXT, data='"test"') - self.assertEqual(record.id, "TXT:") + self.assertEqual(record.id, "TXT") self.assertEqual(record.name, "") self.assertEqual(record.zone, zone) self.assertEqual(record.type, RecordType.TXT) @@ -187,7 +232,7 @@ def test_create_SPF_record(self): """ zone = self.driver.list_zones()[0] record = self.driver.create_record(name="", zone=zone, type=RecordType.SPF, data="test") - self.assertEqual(record.id, "SPF:") + self.assertEqual(record.id, "SPF") self.assertEqual(record.name, "") self.assertEqual(record.zone, zone) self.assertEqual(record.type, RecordType.SPF) @@ -199,7 +244,7 @@ def test_create_SPF_record_quoted(self): """ zone = self.driver.list_zones()[0] record = self.driver.create_record(name="", zone=zone, type=RecordType.SPF, data='"test"') - self.assertEqual(record.id, "SPF:") + self.assertEqual(record.id, "SPF") self.assertEqual(record.name, "") self.assertEqual(record.zone, zone) self.assertEqual(record.type, RecordType.SPF) @@ -213,7 +258,7 @@ def test_create_TXT_record_escaped(self): record = self.driver.create_record( name="", zone=zone, type=RecordType.TXT, data='test "with"' ) - self.assertEqual(record.id, "TXT:") + self.assertEqual(record.id, "TXT") self.assertEqual(record.name, "") self.assertEqual(record.zone, zone) self.assertEqual(record.type, RecordType.TXT) @@ -247,23 +292,41 @@ def test_update_record(self): params = { "record": record, "name": "www", - "type": RecordType.A, + "type": RecordType.AAAA, "data": "::1", - "extra": {"ttle": 0}, + "extra": {"ttl": 0}, } updated_record = self.driver.update_record(**params) + sent = Route53MockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/2012-02-29/hostedzone/47234/rrset") + xml = sent.body.decode() + self.assertIn("www.t.com", xml) + self.assertIn("DELETE", xml) + self.assertIn(f"{record.type}", xml) + self.assertIn(f"{record.data}", xml) + self.assertIn("CREATE", xml) + self.assertIn("AAAA", xml) + self.assertIn("::1", xml) + self.assertIn("0", xml) + self.assertEqual(record.data, "208.111.35.173") - self.assertEqual(updated_record.id, "A:www") + self.assertEqual(updated_record.id, "AAAA:www") self.assertEqual(updated_record.name, "www") self.assertEqual(updated_record.zone, record.zone) - self.assertEqual(updated_record.type, RecordType.A) + self.assertEqual(updated_record.type, RecordType.AAAA) self.assertEqual(updated_record.data, "::1") def test_delete_zone(self): zone = self.driver.list_zones()[0] status = self.driver.delete_zone(zone=zone) + + sent = Route53MockHttp.history.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, "/2012-02-29/hostedzone/47234") + self.assertTrue(status) def test_delete_zone_does_not_exist(self): @@ -282,6 +345,15 @@ def test_delete_record(self): zone = self.driver.list_zones()[0] record = self.driver.list_records(zone=zone)[0] status = self.driver.delete_record(record=record) + + sent = Route53MockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/2012-02-29/hostedzone/47234/rrset") + xml = sent.body.decode() + self.assertIn("DELETE", xml) + self.assertIn("wibble.t.com", xml) + self.assertIn("CNAME", xml) + self.assertTrue(status) def test_delete_record_does_not_exist(self): @@ -298,6 +370,7 @@ def test_delete_record_does_not_exist(self): class Route53MockHttp(MockHttp): fixtures = DNSFileFixtures("route53") + keep_history = True def _2012_02_29_hostedzone_47234(self, method, url, body, headers): body = self.fixtures.load("get_zone.xml") diff --git a/libcloud/test/dns/test_vultr.py b/libcloud/test/dns/test_vultr.py deleted file mode 100644 index a3409219b9..0000000000 --- a/libcloud/test/dns/test_vultr.py +++ /dev/null @@ -1,310 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import unittest - -from libcloud.test import MockHttp -from libcloud.dns.base import Zone, Record -from libcloud.dns.types import ( - RecordType, - ZoneDoesNotExistError, - ZoneAlreadyExistsError, - RecordDoesNotExistError, -) -from libcloud.utils.py3 import httplib -from libcloud.test.secrets import VULTR_PARAMS -from libcloud.dns.drivers.vultr import VultrDNSDriver, VultrDNSDriverV1 -from libcloud.test.file_fixtures import DNSFileFixtures - - -class VultrTests(unittest.TestCase): - def setUp(self): - VultrMockHttp.type = None - VultrDNSDriverV1.connectionCls.conn_class = VultrMockHttp - self.driver = VultrDNSDriver(*VULTR_PARAMS, api_version="1") - self.test_zone = Zone( - id="test.com", - type="master", - ttl=None, - domain="test.com", - extra={}, - driver=self, - ) - self.test_record = Record( - id="31", - type=RecordType.A, - name="test", - zone=self.test_zone, - data="127.0.0.1", - driver=self, - extra={}, - ) - - def test_correct_class_is_used(self): - self.assertIsInstance(self.driver, VultrDNSDriverV1) - - def test_list_zones_empty(self): - VultrMockHttp.type = "EMPTY_ZONES_LIST" - zones = self.driver.list_zones() - - self.assertEqual(zones, []) - - def test_list_zones_success(self): - zones = self.driver.list_zones() - self.assertEqual(len(zones), 4) - - zone = zones[0] - self.assertEqual(zone.id, "example.com") - self.assertEqual(zone.type, "master") - self.assertEqual(zone.domain, "example.com") - self.assertIsNone(zone.ttl) - - zone = zones[1] - self.assertEqual(zone.id, "zupo.com") - self.assertEqual(zone.type, "master") - self.assertEqual(zone.domain, "zupo.com") - self.assertIsNone(zone.ttl) - - zone = zones[2] - self.assertEqual(zone.id, "oltjano.com") - self.assertEqual(zone.type, "master") - self.assertEqual(zone.domain, "oltjano.com") - self.assertIsNone(zone.ttl) - - zone = zones[3] - self.assertEqual(zone.id, "13.com") - self.assertEqual(zone.type, "master") - self.assertEqual(zone.domain, "13.com") - self.assertIsNone(zone.ttl) - - def test_get_zone_zone_does_not_exist(self): - VultrMockHttp.type = "GET_ZONE_ZONE_DOES_NOT_EXIST" - try: - self.driver.get_zone(zone_id="test.com") - except ZoneDoesNotExistError as e: - self.assertEqual(e.zone_id, "test.com") - else: - self.fail("Exception was not thrown") - - def test_get_zone_success(self): - VultrMockHttp.type = "GET_ZONE_SUCCESS" - zone = self.driver.get_zone(zone_id="zupo.com") - - self.assertEqual(zone.id, "zupo.com") - self.assertEqual(zone.domain, "zupo.com") - self.assertEqual(zone.type, "master") - self.assertIsNone(zone.ttl) - - def test_delete_zone_zone_does_not_exist(self): - VultrMockHttp.type = "DELETE_ZONE_ZONE_DOES_NOT_EXIST" - - try: - self.driver.delete_zone(zone=self.test_zone) - except ZoneDoesNotExistError as e: - self.assertEqual(e.zone_id, self.test_zone.id) - else: - self.fail("Exception was not thrown") - - def test_delete_zone_success(self): - zone = self.driver.list_zones()[0] - status = self.driver.delete_zone(zone=zone) - - self.assertTrue(status) - - def test_create_zone_success(self): - zone = self.driver.create_zone(domain="test.com", extra={"serverip": "127.0.0.1"}) - - self.assertEqual(zone.id, "test.com") - self.assertEqual(zone.domain, "test.com") - self.assertEqual(zone.type, "master"), - self.assertIsNone(zone.ttl) - - def test_create_zone_zone_already_exists(self): - VultrMockHttp.type = "CREATE_ZONE_ZONE_ALREADY_EXISTS" - - try: - self.driver.create_zone(domain="example.com", extra={"serverip": "127.0.0.1"}) - except ZoneAlreadyExistsError as e: - self.assertEqual(e.zone_id, "example.com") - else: - self.fail("Exception was not thrown") - - def test_get_record_record_does_not_exist(self): - VultrMockHttp.type = "GET_RECORD_RECORD_DOES_NOT_EXIST" - - try: - self.driver.get_record(zone_id="zupo.com", record_id="1300") - except RecordDoesNotExistError as e: - self.assertEqual(e.record_id, "1300") - else: - self.fail("Exception was not thrown") - - def test_list_records_zone_does_not_exist(self): - VultrMockHttp.type = "LIST_RECORDS_ZONE_DOES_NOT_EXIST" - - try: - self.driver.list_records(zone=self.test_zone) - except ZoneDoesNotExistError as e: - self.assertEqual(e.zone_id, self.test_zone.id) - else: - self.fail("Exception was not thrown") - - def test_list_records_empty(self): - VultrMockHttp.type = "EMPTY_RECORDS_LIST" - zone = self.driver.list_zones()[0] - records = self.driver.list_records(zone=zone) - - self.assertEqual(records, []) - - def test_list_records_success(self): - zone = self.driver.get_zone(zone_id="zupo.com") - records = self.driver.list_records(zone=zone) - self.assertEqual(len(records), 2) - - arecord = records[0] - self.assertEqual(arecord.id, "13") - self.assertEqual(arecord.name, "arecord") - self.assertEqual(arecord.type, RecordType.A) - self.assertEqual(arecord.data, "127.0.0.1") - - def test_get_record_success(self): - VultrMockHttp.type = "GET_RECORD" - record = self.driver.get_record(zone_id="zupo.com", record_id="1300") - - self.assertEqual(record.id, "1300") - self.assertEqual(record.name, "zupo") - self.assertEqual(record.data, "127.0.0.1") - self.assertEqual(record.type, RecordType.A) - - def test_delete_record_record_does_not_exist(self): - VultrMockHttp.type = "DELETE_RECORD_RECORD_DOES_NOT_EXIST" - - try: - self.driver.delete_record(record=self.test_record) - except RecordDoesNotExistError as e: - self.assertEqual(e.record_id, self.test_record.id) - else: - self.fail("Exception was not thrown") - - def test_delete_record_success(self): - zone = self.driver.list_zones()[0] - record = self.driver.list_records(zone=zone)[0] - status = self.driver.delete_record(record=record) - - self.assertTrue(status) - - -class VultrMockHttp(MockHttp): - fixtures = DNSFileFixtures("vultr") - - def _v1_dns_list(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_records(self, method, url, body, headers): - body = self.fixtures.load("list_records.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_EMPTY_ZONES_LIST(self, method, url, body, headers): - body = self.fixtures.load("empty_zones_list.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_GET_ZONE_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_GET_ZONE_SUCCESS(self, method, url, body, headers): - body = self.fixtures.load("get_zone.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_EMPTY_RECORDS_LIST(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_records_EMPTY_RECORDS_LIST(self, method, url, body, headers): - body = self.fixtures.load("empty_records_list.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_GET_RECORD(self, method, url, body, headers): - body = self.fixtures.load("get_zone.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_records_GET_RECORD(self, method, url, body, headers): - body = self.fixtures.load("get_record.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_GET_RECORD_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("get_zone.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_records_GET_RECORD_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("list_records.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_delete_domain(self, method, url, body, headers): - body = "" - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_delete_record(self, method, url, body, headers): - body = "" - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_create_domain(self, method, url, body, headers): - body = "" - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_CREATE_ZONE_ZONE_ALREADY_EXISTS(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_create_domain_CREATE_ZONE_ZONE_ALREADY_EXISTS(self, method, url, body, headers): - body = "" - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_DELETE_ZONE_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_delete_domain_DELETE_ZONE_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_records_DELETE_RECORD_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("list_records.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_delete_record_DELETE_RECORD_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_DELETE_RECORD_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("test_zone.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_list_LIST_RECORDS_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("list_domains.json") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _v1_dns_records_LIST_RECORDS_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - -if __name__ == "__main__": - sys.exit(unittest.main()) diff --git a/libcloud/test/dns/test_vultr_v2.py b/libcloud/test/dns/test_vultr_v2.py index baa804509c..9792410bd7 100644 --- a/libcloud/test/dns/test_vultr_v2.py +++ b/libcloud/test/dns/test_vultr_v2.py @@ -25,6 +25,7 @@ class VultrTests(unittest.TestCase): def setUp(self): VultrMockHttp.type = None + VultrMockHttp.history.clear() VultrDNSDriverV2.connectionCls.conn_class = VultrMockHttp self.driver = VultrDNSDriver("foo") @@ -36,6 +37,11 @@ def test_unknown_api_version(self): def test_list_zones(self): zones = self.driver.list_zones() + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains") + self.assertEqual(len(zones), 2) zone = zones[0] self.assertEqual(zone.id, "example.com") @@ -44,12 +50,23 @@ def test_list_zones(self): def test_create_zone(self): zone = self.driver.create_zone("example.com") + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/v2/domains") + self.assertEqual(sent.json["domain"], "example.com") + self.assertEqual(zone.id, "example.com") self.assertEqual(zone.domain, "example.com") self.assertEqual(zone.extra["date_created"], "2021-09-07T10:28:34+00:00") def test_get_zone(self): zone = self.driver.get_zone("example.com") + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains/example.com") + self.assertEqual(zone.id, "example.com") self.assertEqual(zone.domain, "example.com") self.assertEqual(zone.extra["date_created"], "2021-09-07T09:52:18+00:00") @@ -57,11 +74,21 @@ def test_get_zone(self): def test_delete_zone(self): zone = self.driver.get_zone("example.com") response = self.driver.delete_zone(zone) + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, "/v2/domains/example.com") + self.assertTrue(response) def test_list_records(self): zone = self.driver.list_zones()[0] records = self.driver.list_records(zone) + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains/example.com/records") + self.assertEqual(len(records), 5) record = records[0] self.assertEqual(record.id, "123") @@ -72,6 +99,14 @@ def test_list_records(self): def test_create_record(self): zone = self.driver.list_zones()[0] record = self.driver.create_record("test1", zone, "A", "192.168.0.11") + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "POST") + self.assertEqual(sent.url, "/v2/domains/example.com/records") + self.assertEqual(sent.json["name"], "test1") + self.assertEqual(sent.json["type"], "A") + self.assertEqual(sent.json["data"], "192.168.0.11") + self.assertEqual(record.id, "123") self.assertEqual(record.zone.domain, zone.domain) self.assertEqual(record.type, "A") @@ -84,12 +119,26 @@ def test_update_record(self): response = self.driver.update_record( record, name="test", data="192.168.0.0", extra=dict(ttl=300, priority=1) ) + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "PATCH") + self.assertEqual(sent.url, "/v2/domains/example.com/records/123") + self.assertEqual(sent.json["name"], "test") + self.assertEqual(sent.json["data"], "192.168.0.0") + self.assertEqual(sent.json["ttl"], 300) + self.assertEqual(sent.json["priority"], 1) + self.assertTrue(response) def test_get_record(self): zone = self.driver.list_zones()[0] temp = self.driver.list_records(zone)[0] record = self.driver.get_record(zone.domain, temp.id) + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/v2/domains/example.com/records/123") + self.assertEqual(record.id, "123") self.assertEqual(record.zone.domain, zone.domain) self.assertEqual(record.type, "NS") @@ -101,11 +150,17 @@ def test_delete_record(self): zone = self.driver.list_zones()[0] record = self.driver.list_records(zone)[0] response = self.driver.delete_record(record) + + sent = VultrMockHttp.history.pop() + self.assertEqual(sent.method, "DELETE") + self.assertEqual(sent.url, "/v2/domains/example.com/records/123") + self.assertTrue(response) class VultrMockHttp(MockHttp): fixtures = DNSFileFixtures("vultr_v2") + keep_history = True def _v2_domains(self, method, url, body, headers): if method == "GET": diff --git a/libcloud/test/dns/test_worldwidedns.py b/libcloud/test/dns/test_worldwidedns.py index 35655be47f..868f7f2964 100644 --- a/libcloud/test/dns/test_worldwidedns.py +++ b/libcloud/test/dns/test_worldwidedns.py @@ -28,6 +28,7 @@ class WorldWideDNSTests(unittest.TestCase): def setUp(self): WorldWideDNSDriver.connectionCls.conn_class = WorldWideDNSMockHttp WorldWideDNSMockHttp.type = None + WorldWideDNSMockHttp.history.clear() self.driver = WorldWideDNSDriver(*DNS_PARAMS_WORLDWIDEDNS) def assertHasKeys(self, dictionary, keys): @@ -46,6 +47,11 @@ def test_list_record_types(self): def test_list_zones_success(self): zones = self.driver.list_zones() + + sent = WorldWideDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_list_domain.asp") + self.assertEqual(len(zones), 1) zone = zones[0] @@ -76,6 +82,12 @@ def test_list_zones_success(self): def test_list_records_success(self): zone = self.driver.list_zones()[0] records = self.driver.list_records(zone=zone) + + sent = WorldWideDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_list_domain.asp") + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertEqual(len(records), 3) www = records[0] @@ -97,6 +109,12 @@ def test_list_records_zone_does_not_exist(self): def test_get_zone_success(self): zone = self.driver.get_zone(zone_id="niteowebsponsoredthisone.com") + + sent = WorldWideDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_list_domain.asp") + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertEqual(zone.id, "niteowebsponsoredthisone.com") self.assertEqual(zone.type, "master") self.assertEqual(zone.domain, "niteowebsponsoredthisone.com") @@ -156,6 +174,17 @@ def test_get_record_record_does_not_exist(self): def test_create_zone_success(self): zone = self.driver.create_zone(domain="niteowebsponsoredthisone.com", type="master") + + # [0] /api_dns_new_domain.asp + # [1] /api_dns_list.asp + # [2] /api_dns_list_domain.asp + sent = WorldWideDNSMockHttp.history.pop(0) + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_new_domain.asp") + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertIn("1", sent.query["DYN"]) + self.assertIn("0", sent.query["TYPE"]) + self.assertEqual(zone.id, "niteowebsponsoredthisone.com") self.assertEqual(zone.domain, "niteowebsponsoredthisone.com") self.assertEqual(zone.ttl, "43200") @@ -173,6 +202,8 @@ def test_create_zone_validaton_error(self): def test_update_zone_success(self): zone = self.driver.list_zones()[0] + WorldWideDNSMockHttp.history.clear() + WorldWideDNSMockHttp.type = "UPDATE_ZONE" updated_zone = self.driver.update_zone( zone=zone, @@ -181,6 +212,14 @@ def test_update_zone_success(self): extra={"HOSTMASTER": "mail.niteowebsponsoredthisone.com"}, ) # noqa + sent = WorldWideDNSMockHttp.history.pop(0) + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_modify.asp") + self.assertNotIn("ID", sent.query) + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertIn("3800", sent.query["TTL"]) + self.assertIn("mail.niteowebsponsoredthisone.com", sent.query["HOSTMASTER"]) + self.assertEqual(zone.extra["HOSTMASTER"], "hostmaster.niteowebsponsoredthisone.com") self.assertEqual(updated_zone.id, zone.id) @@ -204,6 +243,8 @@ def test_update_zone_success(self): def test_create_record_success(self): zone = self.driver.list_zones()[0] + WorldWideDNSMockHttp.history.clear() + WorldWideDNSMockHttp.type = "CREATE_RECORD" record = self.driver.create_record( name="domain4", @@ -213,6 +254,18 @@ def test_create_record_success(self): extra={"entry": 4}, ) + # [0] /api_dns_modify.asp + # [1] /api_dns_list_domain.asp + sent = WorldWideDNSMockHttp.history.pop(0) + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_modify.asp") + self.assertNotIn("ID", sent.query) + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertIn("domain4", sent.query["S4"]) + self.assertIn("A", sent.query["T4"]) + self.assertIn("0.0.0.4", sent.query["D4"]) + self.assertIn("43200", sent.query["TTL"]) + self.assertEqual(record.id, "4") self.assertEqual(record.name, "domain4") self.assertNotEqual(record.zone.extra.get("S4"), zone.extra.get("S4")) @@ -279,6 +332,8 @@ def test_create_record_max_entry_reached_give_entry(self): def test_update_record_success(self): zone = self.driver.list_zones()[0] record = self.driver.get_record(zone.id, "1") + WorldWideDNSMockHttp.history.clear() + WorldWideDNSMockHttp.type = "UPDATE_RECORD" record = self.driver.update_record( record=record, @@ -288,6 +343,18 @@ def test_update_record_success(self): extra={"entry": 1}, ) + # [0] /api_dns_modify.asp + # [1] /api_dns_list_domain.asp + sent = WorldWideDNSMockHttp.history.pop(0) + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_modify.asp") + self.assertNotIn("ID", sent.query) + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertIn("domain1", sent.query["S1"]) + self.assertIn("A", sent.query["T1"]) + self.assertIn("0.0.0.1", sent.query["D1"]) + self.assertIn("43200", sent.query["TTL"]) + self.assertEqual(record.id, "1") self.assertEqual(record.name, "domain1") self.assertNotEqual(record.zone.extra.get("S1"), zone.extra.get("S1")) @@ -298,6 +365,12 @@ def test_update_record_success(self): def test_delete_zone_success(self): zone = self.driver.list_zones()[0] status = self.driver.delete_zone(zone=zone) + + sent = WorldWideDNSMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_delete_domain.asp") + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertTrue(status) def test_delete_zone_does_not_exist(self): @@ -315,10 +388,25 @@ def test_delete_zone_does_not_exist(self): def test_delete_record_success(self): zone = self.driver.list_zones()[0] records = self.driver.list_records(zone=zone) + WorldWideDNSMockHttp.history.clear() + self.assertEqual(len(records), 3) record = records[1] + WorldWideDNSMockHttp.type = "DELETE_RECORD" status = self.driver.delete_record(record=record) + + # [0] /api_dns_modify.asp + # [1] /api_dns_list.asp + # [2] /api_dns_list_domain.asp + sent = WorldWideDNSMockHttp.history.pop(0) + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/api_dns_modify.asp") + self.assertIn("niteowebsponsoredthisone.com", sent.query["DOMAIN"]) + self.assertIn("www", sent.query["S1"]) + self.assertIn("NONE", sent.query["T2"]) + self.assertIn("@", sent.query["S3"]) + self.assertTrue(status) zone = self.driver.list_zones()[0] records = self.driver.list_records(zone=zone) @@ -327,6 +415,7 @@ def test_delete_record_success(self): class WorldWideDNSMockHttp(MockHttp): fixtures = DNSFileFixtures("worldwidedns") + keep_history = True def _api_dns_list_asp(self, method, url, body, headers): body = self.fixtures.load("api_dns_list") diff --git a/libcloud/test/dns/test_zerigo.py b/libcloud/test/dns/test_zerigo.py deleted file mode 100644 index 1fc1af8842..0000000000 --- a/libcloud/test/dns/test_zerigo.py +++ /dev/null @@ -1,344 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and - -import sys -import unittest - -from libcloud.test import MockHttp -from libcloud.dns.types import RecordType, ZoneDoesNotExistError, RecordDoesNotExistError -from libcloud.utils.py3 import httplib -from libcloud.common.types import LibcloudError, InvalidCredsError -from libcloud.test.secrets import DNS_PARAMS_ZERIGO -from libcloud.dns.drivers.zerigo import ZerigoError, ZerigoDNSDriver -from libcloud.test.file_fixtures import DNSFileFixtures - - -class ZerigoTests(unittest.TestCase): - def setUp(self): - ZerigoDNSDriver.connectionCls.conn_class = ZerigoMockHttp - ZerigoMockHttp.type = None - self.driver = ZerigoDNSDriver(*DNS_PARAMS_ZERIGO) - - def test_invalid_credentials(self): - ZerigoMockHttp.type = "INVALID_CREDS" - - try: - list(self.driver.list_zones()) - except InvalidCredsError: - pass - else: - self.fail("Exception was not thrown") - - def test_list_record_types(self): - record_types = self.driver.list_record_types() - self.assertEqual(len(record_types), 13) - self.assertTrue(RecordType.A in record_types) - - def test_list_zones_success(self): - zones = self.driver.list_zones() - self.assertEqual(len(zones), 1) - self.assertEqual(zones[0].domain, "example.com") - self.assertEqual(zones[0].type, "master") - self.assertEqual(zones[0].extra["notes"], "test foo bar") - - def test_list_zones_no_results(self): - ZerigoMockHttp.type = "NO_RESULTS" - zones = self.driver.list_zones() - self.assertEqual(len(zones), 0) - - def test_list_records_success(self): - zone = self.driver.list_zones()[0] - records = list(self.driver.list_records(zone=zone)) - - self.assertEqual(len(records), 4) - self.assertEqual(records[0].name, "www") - self.assertEqual(records[0].type, RecordType.A) - self.assertEqual(records[0].data, "172.16.16.1") - self.assertEqual(records[0].extra["fqdn"], "www.example.com") - self.assertIsNone(records[0].extra["notes"]) - self.assertIsNone(records[0].extra["priority"]) - - self.assertEqual(records[1].name, "test") - self.assertEqual(records[1].extra["ttl"], 3600) - - def test_record_with_empty_name(self): - zone = self.driver.list_zones()[0] - record1 = list(self.driver.list_records(zone=zone))[-1] - record2 = list(self.driver.list_records(zone=zone))[-2] - - self.assertIsNone(record1.name) - self.assertIsNone(record2.name) - - def test_list_records_no_results(self): - zone = self.driver.list_zones()[0] - ZerigoMockHttp.type = "NO_RESULTS" - records = list(self.driver.list_records(zone=zone)) - self.assertEqual(len(records), 0) - - def test_list_records_zone_does_not_exist(self): - zone = self.driver.list_zones()[0] - - ZerigoMockHttp.type = "ZONE_DOES_NOT_EXIST" - try: - list(self.driver.list_records(zone=zone)) - except ZoneDoesNotExistError as e: - self.assertEqual(e.zone_id, zone.id) - else: - self.fail("Exception was not thrown") - pass - - def test_get_zone_success(self): - zone = self.driver.get_zone(zone_id=12345678) - - self.assertEqual(zone.id, "12345678") - self.assertEqual(zone.domain, "example.com") - self.assertEqual(zone.extra["hostmaster"], "dnsadmin@example.com") - self.assertEqual(zone.type, "master") - - def test_get_zone_does_not_exist(self): - ZerigoMockHttp.type = "DOES_NOT_EXIST" - - try: - self.driver.get_zone(zone_id="4444") - except ZoneDoesNotExistError as e: - self.assertEqual(e.zone_id, "4444") - else: - self.fail("Exception was not thrown") - - def test_get_record_success(self): - record = self.driver.get_record(zone_id="12345678", record_id="23456789") - self.assertEqual(record.id, "23456789") - self.assertEqual(record.name, "www") - self.assertEqual(record.type, RecordType.A) - - def test_get_record_zone_does_not_exist(self): - ZerigoMockHttp.type = "ZONE_DOES_NOT_EXIST" - - try: - self.driver.get_record(zone_id="444", record_id="28536") - except ZoneDoesNotExistError: - pass - else: - self.fail("Exception was not thrown") - - def test_get_record_record_does_not_exist(self): - ZerigoMockHttp.type = "RECORD_DOES_NOT_EXIST" - - try: - self.driver.get_record(zone_id="12345678", record_id="28536") - except RecordDoesNotExistError: - pass - else: - self.fail("Exception was not thrown") - - def test_create_zone_success(self): - ZerigoMockHttp.type = "CREATE_ZONE" - - zone = self.driver.create_zone(domain="foo.bar.com", type="master", ttl=None, extra=None) - self.assertEqual(zone.id, "12345679") - self.assertEqual(zone.domain, "foo.bar.com") - - def test_create_zone_validaton_error(self): - ZerigoMockHttp.type = "CREATE_ZONE_VALIDATION_ERROR" - - try: - self.driver.create_zone(domain="foo.bar.com", type="master", ttl=10, extra=None) - except ZerigoError as e: - self.assertEqual(len(e.errors), 2) - else: - self.fail("Exception was not thrown") - - def test_update_zone_success(self): - zone = self.driver.list_zones()[0] - updated_zone = self.driver.update_zone(zone=zone, ttl=10, extra={"notes": "bar foo"}) - - self.assertEqual(zone.extra["notes"], "test foo bar") - - self.assertEqual(updated_zone.id, zone.id) - self.assertEqual(updated_zone.domain, "example.com") - self.assertEqual(updated_zone.type, zone.type) - self.assertEqual(updated_zone.ttl, 10) - self.assertEqual(updated_zone.extra["notes"], "bar foo") - - def test_update_zone_domain_cannot_be_changed(self): - zone = self.driver.list_zones()[0] - - try: - self.driver.update_zone(zone=zone, domain="libcloud.org") - except LibcloudError: - pass - else: - self.fail("Exception was not thrown") - - def test_create_record_success(self): - zone = self.driver.list_zones()[0] - - ZerigoMockHttp.type = "CREATE_RECORD" - record = self.driver.create_record( - name="www", zone=zone, type=RecordType.A, data="127.0.0.1" - ) - - self.assertEqual(record.id, "23456780") - self.assertEqual(record.name, "www") - self.assertEqual(record.zone, zone) - self.assertEqual(record.type, RecordType.A) - self.assertEqual(record.data, "127.0.0.1") - - def test_update_record_success(self): - zone = self.driver.list_zones()[0] - record = self.driver.list_records(zone=zone)[0] - updated_record = self.driver.update_record( - record=record, name="www", type=RecordType.AAAA, data="::1" - ) - - self.assertEqual(record.data, "172.16.16.1") - - self.assertEqual(updated_record.id, record.id) - self.assertEqual(updated_record.name, "www") - self.assertEqual(updated_record.zone, record.zone) - self.assertEqual(updated_record.type, RecordType.AAAA) - self.assertEqual(updated_record.data, "::1") - - def test_delete_zone_success(self): - zone = self.driver.list_zones()[0] - status = self.driver.delete_zone(zone=zone) - self.assertTrue(status) - - def test_delete_zone_does_not_exist(self): - zone = self.driver.list_zones()[0] - - ZerigoMockHttp.type = "ZONE_DOES_NOT_EXIST" - - try: - self.driver.delete_zone(zone=zone) - except ZoneDoesNotExistError as e: - self.assertEqual(e.zone_id, zone.id) - else: - self.fail("Exception was not thrown") - - def test_delete_record_success(self): - zone = self.driver.list_zones()[0] - record = self.driver.list_records(zone=zone)[0] - status = self.driver.delete_record(record=record) - self.assertTrue(status) - - def test_delete_record_does_not_exist(self): - zone = self.driver.list_zones()[0] - record = self.driver.list_records(zone=zone)[0] - - ZerigoMockHttp.type = "RECORD_DOES_NOT_EXIST" - - try: - self.driver.delete_record(record=record) - except RecordDoesNotExistError as e: - self.assertEqual(e.record_id, record.id) - else: - self.fail("Exception was not thrown") - - -class ZerigoMockHttp(MockHttp): - fixtures = DNSFileFixtures("zerigo") - - def _api_1_1_zones_xml_INVALID_CREDS(self, method, url, body, headers): - body = "HTTP Basic: Access denied.\n" - return (httplib.UNAUTHORIZED, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_xml(self, method, url, body, headers): - body = self.fixtures.load("list_zones.xml") - return (httplib.OK, body, {"x-query-count": "1"}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_xml_NO_RESULTS(self, method, url, body, headers): - body = self.fixtures.load("list_zones_no_results.xml") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_hosts_xml(self, method, url, body, headers): - body = self.fixtures.load("list_records.xml") - return (httplib.OK, body, {"x-query-count": "1"}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_hosts_xml_NO_RESULTS(self, method, url, body, headers): - body = self.fixtures.load("list_records_no_results.xml") - return (httplib.OK, body, {"x-query-count": "0"}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_hosts_xml_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_xml(self, method, url, body, headers): - body = self.fixtures.load("get_zone.xml") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_4444_xml_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_hosts_23456789_xml(self, method, url, body, headers): - body = self.fixtures.load("get_record.xml") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_444_xml_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_xml_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = self.fixtures.load("get_zone.xml") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_hosts_28536_xml_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_xml_CREATE_ZONE(self, method, url, body, headers): - body = self.fixtures.load("create_zone.xml") - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_xml_CREATE_ZONE_VALIDATION_ERROR(self, method, url, body, headers): - body = self.fixtures.load("create_zone_validation_error.xml") - return (httplib.UNPROCESSABLE_ENTITY, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_hosts_xml_CREATE_RECORD(self, method, url, body, headers): - body = self.fixtures.load("create_record.xml") - return (httplib.CREATED, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_zones_12345678_xml_ZONE_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.OK]) - - def _api_1_1_hosts_23456789_xml_RECORD_DOES_NOT_EXIST(self, method, url, body, headers): - body = "" - return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.OK]) - - """ - def (self, method, url, body, headers): - body = self.fixtures.load('.xml') - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def (self, method, url, body, headers): - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def (self, method, url, body, headers): - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def (self, method, url, body, headers): - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def (self, method, url, body, headers): - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - - def (self, method, url, body, headers): - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - """ - - -if __name__ == "__main__": - sys.exit(unittest.main()) diff --git a/libcloud/test/dns/test_zonomi.py b/libcloud/test/dns/test_zonomi.py index e03e691753..6bc1a6d89b 100644 --- a/libcloud/test/dns/test_zonomi.py +++ b/libcloud/test/dns/test_zonomi.py @@ -34,6 +34,7 @@ class ZonomiTests(unittest.TestCase): def setUp(self): ZonomiDNSDriver.connectionCls.conn_class = ZonomiMockHttp ZonomiMockHttp.type = None + ZonomiMockHttp.history.clear() self.driver = ZonomiDNSDriver(*DNS_PARAMS_ZONOMI) self.test_zone = Zone( id="zone.com", @@ -44,7 +45,7 @@ def setUp(self): extra={}, ) self.test_record = Record( - id="record.zone.com", + id="A:record", name="record.zone.com", data="127.0.0.1", type="A", @@ -69,6 +70,11 @@ def test_list_zones_empty(self): def test_list_zones_success(self): zones = self.driver.list_zones() + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("QUERYZONES", sent.query["action"]) + self.assertEqual(len(zones), 3) zone = zones[0] @@ -105,6 +111,11 @@ def test_get_zone_GET_ZONE_SUCCESS(self): ZonomiMockHttp.type = "GET_ZONE_SUCCESS" zone = self.driver.get_zone(zone_id="gamertest.com") + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("QUERYZONES", sent.query["action"]) + self.assertEqual(zone.id, "gamertest.com") self.assertEqual(zone.domain, "gamertest.com") self.assertEqual(zone.type, "master") @@ -124,6 +135,12 @@ def test_delete_zone_delete_zone_success(self): ZonomiMockHttp.type = "DELETE_ZONE_SUCCESS" status = self.driver.delete_zone(zone=self.test_zone) + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("DELETEZONE", sent.query["action"]) + self.assertIn("zone.com", sent.query["name"]) + self.assertEqual(status, True) def test_create_zone_already_exists(self): @@ -140,6 +157,11 @@ def test_create_zone_create_zone_success(self): zone = self.driver.create_zone(domain="myzone.com") + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/addzone.jsp") + self.assertIn("myzone.com", sent.query["name"]) + self.assertEqual(zone.id, "myzone.com") self.assertEqual(zone.domain, "myzone.com") self.assertEqual(zone.type, "master") @@ -153,32 +175,38 @@ def test_list_records_success(self): ZonomiMockHttp.type = "LIST_RECORDS_SUCCESS" records = self.driver.list_records(zone=self.test_zone) + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("QUERY", sent.query["action"]) + self.assertIn("**.zone.com", sent.query["name"]) + self.assertEqual(len(records), 4) record = records[0] - self.assertEqual(record.id, "zone.com") + self.assertEqual(record.id, "SOA") self.assertEqual(record.type, "SOA") self.assertEqual(record.data, "ns1.zonomi.com. soacontact.zonomi.com. 13") - self.assertEqual(record.name, "zone.com") + self.assertEqual(record.hostname, "zone.com") self.assertEqual(record.zone, self.test_zone) second_record = records[1] - self.assertEqual(second_record.id, "zone.com") - self.assertEqual(second_record.name, "zone.com") + self.assertEqual(second_record.id, "NS") + self.assertEqual(second_record.hostname, "zone.com") self.assertEqual(second_record.type, "NS") self.assertEqual(second_record.data, "ns1.zonomi.com") self.assertEqual(second_record.zone, self.test_zone) third_record = records[2] - self.assertEqual(third_record.id, "oltjano") + self.assertEqual(third_record.id, "A:oltjano") self.assertEqual(third_record.name, "oltjano") self.assertEqual(third_record.type, "A") self.assertEqual(third_record.data, "127.0.0.1") self.assertEqual(third_record.zone, self.test_zone) fourth_record = records[3] - self.assertEqual(fourth_record.id, "zone.com") - self.assertEqual(fourth_record.name, "zone.com") + self.assertEqual(fourth_record.id, "NS") + self.assertEqual(fourth_record.hostname, "zone.com") self.assertEqual(fourth_record.type, "NS") self.assertEqual(fourth_record.data, "ns5.zonomi.com") self.assertEqual(fourth_record.zone, self.test_zone) @@ -211,9 +239,15 @@ def test_get_record_success(self): driver=self.driver, ) self.driver.get_zone = MagicMock(return_value=zone) - record = self.driver.get_record(record_id="oltjano", zone_id="zone.com") + record = self.driver.get_record(record_id="A:oltjano", zone_id="zone.com") + + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("QUERY", sent.query["action"]) + self.assertIn("**.zone.com", sent.query["name"]) - self.assertEqual(record.id, "oltjano") + self.assertEqual(record.id, "A:oltjano") self.assertEqual(record.name, "oltjano") self.assertEqual(record.type, "A") self.assertEqual(record.data, "127.0.0.1") @@ -233,6 +267,13 @@ def test_delete_record_success(self): record = self.test_record status = self.driver.delete_record(record=record) + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("DELETE", sent.query["action"]) + self.assertIn("record.zone.com", sent.query["name"]) + self.assertIn("A", sent.query["type"]) + self.assertEqual(status, True) def test_create_record_already_exists(self): @@ -254,7 +295,15 @@ def test_create_record_success(self): name="createrecord", zone=zone, type="A", data="127.0.0.1", extra={} ) - self.assertEqual(record.id, "createrecord") + sent = ZonomiMockHttp.history.pop() + self.assertEqual(sent.method, "GET") + self.assertEqual(sent.url, "/app/dns/dyndns.jsp") + self.assertIn("SET", sent.query["action"]) + self.assertIn("createrecord.zone.com", sent.query["name"]) + self.assertIn("A", sent.query["type"]) + self.assertIn("127.0.0.1", sent.query["value"]) + + self.assertEqual(record.id, "A:createrecord") self.assertEqual(record.name, "createrecord") self.assertEqual(record.type, "A") self.assertEqual(record.data, "127.0.0.1") @@ -293,6 +342,7 @@ def test_convert_to_master_couldnt_convert(self): class ZonomiMockHttp(MockHttp): fixtures = DNSFileFixtures("zonomi") + keep_history = True def _app_dns_dyndns_jsp_EMPTY_ZONES_LIST(self, method, url, body, headers): body = self.fixtures.load("empty_zones_list.xml")