Added nttcis modules to backup and loadbalancer packages and edited providers and types modules in packages at needed
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/cdc27a66 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/cdc27a66 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/cdc27a66 Branch: refs/heads/trunk Commit: cdc27a6687ced8bae58c4fd801924c1e897a68bc Parents: b80bf32 Author: mitch <[email protected]> Authored: Fri Jul 13 09:52:49 2018 -0400 Committer: mitch <[email protected]> Committed: Fri Jul 13 09:52:49 2018 -0400 ---------------------------------------------------------------------- libcloud/backup/drivers/nttcis.py | 689 +++++++++++++++ libcloud/backup/providers.py | 4 +- libcloud/backup/types.py | 1 + libcloud/common/nttcis.py | 2 +- libcloud/compute/drivers/nttcis.py | 8 +- libcloud/http.py | 1 + libcloud/loadbalancer/drivers/nttcis.py | 1162 ++++++++++++++++++++++++++ libcloud/loadbalancer/providers.py | 2 + libcloud/loadbalancer/types.py | 1 + tests/conftest.py | 10 + tests/libtest.py | 33 +- 11 files changed, 1903 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/backup/drivers/nttcis.py ---------------------------------------------------------------------- diff --git a/libcloud/backup/drivers/nttcis.py b/libcloud/backup/drivers/nttcis.py new file mode 100644 index 0000000..cd0bbda --- /dev/null +++ b/libcloud/backup/drivers/nttcis.py @@ -0,0 +1,689 @@ +# 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. + +from libcloud.utils.py3 import ET +from libcloud.backup.base import BackupDriver, BackupTarget, BackupTargetJob +from libcloud.backup.types import BackupTargetType +from libcloud.backup.types import Provider +from libcloud.common.nttcis import dd_object_to_id +from libcloud.common.nttcis import NttCisConnection +from libcloud.common.nttcis import NttCisBackupClient +from libcloud.common.nttcis import NttCisBackupClientAlert +from libcloud.common.nttcis import NttCisBackupClientType +from libcloud.common.nttcis import NttCisBackupDetails +from libcloud.common.nttcis import NttCisBackupSchedulePolicy +from libcloud.common.nttcis import NttCisBackupStoragePolicy +from libcloud.common.nttcis import API_ENDPOINTS, DEFAULT_REGION +from libcloud.common.nttcis import TYPES_URN +from libcloud.common.nttcis import GENERAL_NS, BACKUP_NS +from libcloud.utils.xml import fixxpath, findtext, findall + +# pylint: disable=no-member + +DEFAULT_BACKUP_PLAN = 'Advanced' + + +class NttCisBackupDriver(BackupDriver): + """ + NttCis backup driver. + """ + + selected_region = None + connectionCls = NttCisConnection + name = 'Dimension Data Backup' + website = 'https://cloud.nttcis.com/' + type = Provider.DIMENSIONDATA + api_version = 1.0 + + network_domain_id = None + + def __init__(self, key, secret=None, secure=True, host=None, port=None, + api_version=None, region=DEFAULT_REGION, **kwargs): + + if region not in API_ENDPOINTS and host is None: + raise ValueError( + 'Invalid region: %s, no host specified' % (region)) + if region is not None: + self.selected_region = API_ENDPOINTS[region] + + super(NttCisBackupDriver, self).__init__( + key=key, secret=secret, + secure=secure, host=host, + port=port, + api_version=api_version, + region=region, + **kwargs) + + def _ex_connection_class_kwargs(self): + """ + Add the region to the kwargs before the connection is instantiated + """ + + kwargs = super(NttCisBackupDriver, + self)._ex_connection_class_kwargs() + kwargs['region'] = self.selected_region + return kwargs + + def get_supported_target_types(self): + """ + Get a list of backup target types this driver supports + + :return: ``list`` of :class:``BackupTargetType`` + """ + return [BackupTargetType.VIRTUAL] + + def list_targets(self): + """ + List all backuptargets + + :rtype: ``list`` of :class:`BackupTarget` + """ + targets = self._to_targets( + self.connection.request_with_orgId_api_2('server/server').object) + return targets + + def create_target(self, name, address, + type=BackupTargetType.VIRTUAL, extra=None): + """ + Creates a new backup target + + :param name: Name of the target (not used) + :type name: ``str`` + + :param address: The ID of the node in Dimension Data Cloud + :type address: ``str`` + + :param type: Backup target type, only Virtual supported + :type type: :class:`BackupTargetType` + + :param extra: (optional) Extra attributes (driver specific). + :type extra: ``dict`` + + :rtype: Instance of :class:`BackupTarget` + """ + if extra is not None: + service_plan = extra.get('servicePlan', DEFAULT_BACKUP_PLAN) + else: + service_plan = DEFAULT_BACKUP_PLAN + extra = {'servicePlan': service_plan} + + create_node = ET.Element('NewBackup', + {'xmlns': BACKUP_NS}) + create_node.set('servicePlan', service_plan) + + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup' % (address), + method='POST', + data=ET.tostring(create_node)).object + + asset_id = None + for info in findall(response, + 'additionalInformation', + GENERAL_NS): + if info.get('name') == 'assetId': + asset_id = findtext(info, 'value', GENERAL_NS) + + return BackupTarget( + id=asset_id, + name=name, + address=address, + type=type, + extra=extra, + driver=self + ) + + def create_target_from_node(self, node, type=BackupTargetType.VIRTUAL, + extra=None): + """ + Creates a new backup target from an existing node + + :param node: The Node to backup + :type node: ``Node`` + + :param type: Backup target type (Physical, Virtual, ...). + :type type: :class:`BackupTargetType` + + :param extra: (optional) Extra attributes (driver specific). + :type extra: ``dict`` + + :rtype: Instance of :class:`BackupTarget` + """ + return self.create_target(name=node.name, + address=node.id, + type=BackupTargetType.VIRTUAL, + extra=extra) + + def create_target_from_container(self, container, + type=BackupTargetType.OBJECT, + extra=None): + """ + Creates a new backup target from an existing storage container + + :param node: The Container to backup + :type node: ``Container`` + + :param type: Backup target type (Physical, Virtual, ...). + :type type: :class:`BackupTargetType` + + :param extra: (optional) Extra attributes (driver specific). + :type extra: ``dict`` + + :rtype: Instance of :class:`BackupTarget` + """ + return NotImplementedError( + 'create_target_from_container not supported for this driver') + + def update_target(self, target, name=None, address=None, extra=None): + """ + Update the properties of a backup target, only changing the serviceplan + is supported. + + :param target: Backup target to update + :type target: Instance of :class:`BackupTarget` or ``str`` + + :param name: Name of the target + :type name: ``str`` + + :param address: Hostname, FQDN, IP, file path etc. + :type address: ``str`` + + :param extra: (optional) Extra attributes (driver specific). + :type extra: ``dict`` + + :rtype: Instance of :class:`BackupTarget` + """ + if extra is not None: + service_plan = extra.get('servicePlan', DEFAULT_BACKUP_PLAN) + else: + service_plan = DEFAULT_BACKUP_PLAN + request = ET.Element('ModifyBackup', + {'xmlns': BACKUP_NS}) + request.set('servicePlan', service_plan) + server_id = self._target_to_target_address(target) + self.connection.request_with_orgId_api_1( + 'server/%s/backup/modify' % (server_id), + method='POST', + data=ET.tostring(request)).object + if isinstance(target, BackupTarget): + target.extra = extra + else: + target = self.ex_get_target_by_id(server_id) + return target + + def delete_target(self, target): + """ + Delete a backup target + + :param target: Backup target to delete + :type target: Instance of :class:`BackupTarget` or ``str`` + + :rtype: ``bool`` + """ + server_id = self._target_to_target_address(target) + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup?disable' % (server_id), + method='GET').object + response_code = findtext(response, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def list_recovery_points(self, target, start_date=None, end_date=None): + """ + List the recovery points available for a target + + :param target: Backup target to delete + :type target: Instance of :class:`BackupTarget` + + :param start_date: The start date to show jobs between (optional) + :type start_date: :class:`datetime.datetime` + + :param end_date: The end date to show jobs between (optional) + :type end_date: :class:`datetime.datetime`` + + :rtype: ``list`` of :class:`BackupTargetRecoveryPoint` + """ + raise NotImplementedError( + 'list_recovery_points not implemented for this driver') + + def recover_target(self, target, recovery_point, path=None): + """ + Recover a backup target to a recovery point + + :param target: Backup target to delete + :type target: Instance of :class:`BackupTarget` + + :param recovery_point: Backup target with the backup data + :type recovery_point: Instance of :class:`BackupTarget` + + :param path: The part of the recovery point to recover (optional) + :type path: ``str`` + + :rtype: Instance of :class:`BackupTargetJob` + """ + raise NotImplementedError( + 'recover_target not implemented for this driver') + + def recover_target_out_of_place(self, target, recovery_point, + recovery_target, path=None): + """ + Recover a backup target to a recovery point out-of-place + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` + + :param recovery_point: Backup target with the backup data + :type recovery_point: Instance of :class:`BackupTarget` + + :param recovery_target: Backup target with to recover the data to + :type recovery_target: Instance of :class:`BackupTarget` + + :param path: The part of the recovery point to recover (optional) + :type path: ``str`` + + :rtype: Instance of :class:`BackupTargetJob` + """ + raise NotImplementedError( + 'recover_target_out_of_place not implemented for this driver') + + def get_target_job(self, target, id): + """ + Get a specific backup job by ID + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` + + :param id: Backup target with the backup data + :type id: Instance of :class:`BackupTarget` + + :rtype: :class:`BackupTargetJob` + """ + jobs = self.list_target_jobs(target) + return list(filter(lambda x: x.id == id, jobs))[0] + + def list_target_jobs(self, target): + """ + List the backup jobs on a target + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` + + :rtype: ``list`` of :class:`BackupTargetJob` + """ + raise NotImplementedError( + 'list_target_jobs not implemented for this driver') + + def create_target_job(self, target, extra=None): + """ + Create a new backup job on a target + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` + + :param extra: (optional) Extra attributes (driver specific). + :type extra: ``dict`` + + :rtype: Instance of :class:`BackupTargetJob` + """ + raise NotImplementedError( + 'create_target_job not implemented for this driver') + + def resume_target_job(self, target, job): + """ + Resume a suspended backup job on a target + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` + + :param job: Backup target job to resume + :type job: Instance of :class:`BackupTargetJob` + + :rtype: ``bool`` + """ + raise NotImplementedError( + 'resume_target_job not implemented for this driver') + + def suspend_target_job(self, target, job): + """ + Suspend a running backup job on a target + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` + + :param job: Backup target job to suspend + :type job: Instance of :class:`BackupTargetJob` + + :rtype: ``bool`` + """ + raise NotImplementedError( + 'suspend_target_job not implemented for this driver') + + def cancel_target_job(self, job, ex_client=None, ex_target=None): + """ + Cancel a backup job on a target + + :param job: Backup target job to cancel. If it is ``None`` + ex_client and ex_target must be set + :type job: Instance of :class:`BackupTargetJob` or ``None`` + + :param ex_client: Client of the job to cancel. + Not necessary if job is specified. + NttCis only has 1 job per client + :type ex_client: Instance of :class:`NttCisBackupClient` + or ``str`` + + :param ex_target: Target to cancel a job from. + Not necessary if job is specified. + :type ex_target: Instance of :class:`BackupTarget` or ``str`` + + :rtype: ``bool`` + """ + if job is None: + if ex_client is None or ex_target is None: + raise ValueError("Either job or ex_client and " + "ex_target have to be set") + server_id = self._target_to_target_address(ex_target) + client_id = self._client_to_client_id(ex_client) + else: + server_id = job.target.address + client_id = job.extra['clientId'] + + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup/client/%s?cancelJob' % (server_id, + client_id), + method='GET').object + response_code = findtext(response, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def ex_get_target_by_id(self, id): + """ + Get a target by server id + + :param id: The id of the target you want to get + :type id: ``str`` + + :rtype: :class:`BackupTarget` + """ + node = self.connection.request_with_orgId_api_2( + 'server/server/%s' % id).object + return self._to_target(node) + + def ex_add_client_to_target(self, target, client_type, storage_policy, + schedule_policy, trigger, email): + """ + Add a client to a target + + :param target: Backup target with the backup data + :type target: Instance of :class:`BackupTarget` or ``str`` + + :param client: Client to add to the target + :type client: Instance of :class:`NttCisBackupClientType` + or ``str`` + + :param storage_policy: The storage policy for the client + :type storage_policy: Instance of + :class:`NttCisBackupStoragePolicy` + or ``str`` + + :param schedule_policy: The schedule policy for the client + :type schedule_policy: Instance of + :class:`NttCisBackupSchedulePolicy` + or ``str`` + + :param trigger: The notify trigger for the client + :type trigger: ``str`` + + :param email: The notify email for the client + :type email: ``str`` + + :rtype: ``bool`` + """ + server_id = self._target_to_target_address(target) + + backup_elm = ET.Element('NewBackupClient', + {'xmlns': BACKUP_NS}) + if isinstance(client_type, NttCisBackupClientType): + ET.SubElement(backup_elm, "type").text = client_type.type + else: + ET.SubElement(backup_elm, "type").text = client_type + + if isinstance(storage_policy, NttCisBackupStoragePolicy): + ET.SubElement(backup_elm, + "storagePolicyName").text = storage_policy.name + else: + ET.SubElement(backup_elm, + "storagePolicyName").text = storage_policy + + if isinstance(schedule_policy, NttCisBackupSchedulePolicy): + ET.SubElement(backup_elm, + "schedulePolicyName").text = schedule_policy.name + else: + ET.SubElement(backup_elm, + "schedulePolicyName").text = schedule_policy + + alerting_elm = ET.SubElement(backup_elm, "alerting") + alerting_elm.set('trigger', trigger) + ET.SubElement(alerting_elm, "emailAddress").text = email + + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup/client' % (server_id), + method='POST', + data=ET.tostring(backup_elm)).object + response_code = findtext(response, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def ex_remove_client_from_target(self, target, backup_client): + """ + Removes a client from a backup target + + :param target: The backup target to remove the client from + :type target: :class:`BackupTarget` or ``str`` + + :param backup_client: The backup client to remove + :type backup_client: :class:`NttCisBackupClient` or ``str`` + + :rtype: ``bool`` + """ + server_id = self._target_to_target_address(target) + client_id = self._client_to_client_id(backup_client) + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup/client/%s?disable' % (server_id, client_id), + method='GET').object + response_code = findtext(response, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def ex_get_backup_details_for_target(self, target): + """ + Returns a backup details object for a target + + :param target: The backup target to get details for + :type target: :class:`BackupTarget` or ``str`` + + :rtype: :class:`NttCisBackupDetails` + """ + if not isinstance(target, BackupTarget): + target = self.ex_get_target_by_id(target) + if target is None: + return + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup' % (target.address), + method='GET').object + return self._to_backup_details(response, target) + + def ex_list_available_client_types(self, target): + """ + Returns a list of available backup client types + + :param target: The backup target to list available types for + :type target: :class:`BackupTarget` or ``str`` + + :rtype: ``list`` of :class:`NttCisBackupClientType` + """ + server_id = self._target_to_target_address(target) + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup/client/type' % (server_id), + method='GET').object + return self._to_client_types(response) + + def ex_list_available_storage_policies(self, target): + """ + Returns a list of available backup storage policies + + :param target: The backup target to list available policies for + :type target: :class:`BackupTarget` or ``str`` + + :rtype: ``list`` of :class:`NttCisBackupStoragePolicy` + """ + server_id = self._target_to_target_address(target) + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup/client/storagePolicy' % (server_id), + method='GET').object + return self._to_storage_policies(response) + + def ex_list_available_schedule_policies(self, target): + """ + Returns a list of available backup schedule policies + + :param target: The backup target to list available policies for + :type target: :class:`BackupTarget` or ``str`` + + :rtype: ``list`` of :class:`NttCisBackupSchedulePolicy` + """ + server_id = self._target_to_target_address(target) + response = self.connection.request_with_orgId_api_1( + 'server/%s/backup/client/schedulePolicy' % (server_id), + method='GET').object + return self._to_schedule_policies(response) + + def _to_storage_policies(self, object): + elements = object.findall(fixxpath('storagePolicy', BACKUP_NS)) + + return [self._to_storage_policy(el) for el in elements] + + def _to_storage_policy(self, element): + return NttCisBackupStoragePolicy( + retention_period=int(element.get('retentionPeriodInDays')), + name=element.get('name'), + secondary_location=element.get('secondaryLocation') + ) + + def _to_schedule_policies(self, object): + elements = object.findall(fixxpath('schedulePolicy', BACKUP_NS)) + + return [self._to_schedule_policy(el) for el in elements] + + def _to_schedule_policy(self, element): + return NttCisBackupSchedulePolicy( + name=element.get('name'), + description=element.get('description') + ) + + def _to_client_types(self, object): + elements = object.findall(fixxpath('backupClientType', BACKUP_NS)) + + return [self._to_client_type(el) for el in elements] + + def _to_client_type(self, element): + description = element.get('description') + if description is None: + description = findtext(element, 'description', BACKUP_NS) + return NttCisBackupClientType( + type=element.get('type'), + description=description, + is_file_system=bool(element.get('isFileSystem') == 'true') + ) + + def _to_backup_details(self, object, target): + return NttCisBackupDetails( + asset_id=object.get('assetId'), + service_plan=object.get('servicePlan'), + status=object.get('state'), + clients=self._to_clients(object, target) + ) + + def _to_clients(self, object, target): + elements = object.findall(fixxpath('backupClient', BACKUP_NS)) + + return [self._to_client(el, target) for el in elements] + + def _to_client(self, element, target): + client_id = element.get('id') + return NttCisBackupClient( + id=client_id, + type=self._to_client_type(element), + status=element.get('status'), + schedule_policy=findtext(element, 'schedulePolicyName', BACKUP_NS), + storage_policy=findtext(element, 'storagePolicyName', BACKUP_NS), + download_url=findtext(element, 'downloadUrl', BACKUP_NS), + running_job=self._to_backup_job(element, target, client_id), + alert=self._to_alert(element) + ) + + def _to_alert(self, element): + alert = element.find(fixxpath('alerting', BACKUP_NS)) + if alert is not None: + notify_list = [ + email_addr.text for email_addr + in alert.findall(fixxpath('emailAddress', BACKUP_NS)) + ] + return NttCisBackupClientAlert( + trigger=element.get('trigger'), + notify_list=notify_list + ) + return None + + def _to_backup_job(self, element, target, client_id): + running_job = element.find(fixxpath('runningJob', BACKUP_NS)) + if running_job is not None: + return BackupTargetJob( + id=running_job.get('id'), + status=running_job.get('status'), + progress=int(running_job.get('percentageComplete')), + driver=self.connection.driver, + target=target, + extra={'clientId': client_id} + ) + return None + + def _to_targets(self, object): + node_elements = object.findall(fixxpath('server', TYPES_URN)) + + return [self._to_target(el) for el in node_elements] + + def _to_target(self, element): + backup = findall(element, 'backup', TYPES_URN) + if len(backup) == 0: + return + extra = { + 'description': findtext(element, 'description', TYPES_URN), + 'sourceImageId': findtext(element, 'sourceImageId', TYPES_URN), + 'datacenterId': element.get('datacenterId'), + 'deployedTime': findtext(element, 'createTime', TYPES_URN), + 'servicePlan': backup[0].get('servicePlan') + } + + n = BackupTarget(id=backup[0].get('assetId'), + name=findtext(element, 'name', TYPES_URN), + address=element.get('id'), + driver=self.connection.driver, + type=BackupTargetType.VIRTUAL, + extra=extra) + return n + + @staticmethod + def _client_to_client_id(backup_client): + return dd_object_to_id(backup_client, NttCisBackupClient) + + @staticmethod + def _target_to_target_address(target): + return dd_object_to_id(target, BackupTarget, id_value='address') http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/backup/providers.py ---------------------------------------------------------------------- diff --git a/libcloud/backup/providers.py b/libcloud/backup/providers.py index 16cd610..fbe9021 100644 --- a/libcloud/backup/providers.py +++ b/libcloud/backup/providers.py @@ -25,7 +25,9 @@ DRIVERS = { Provider.GCE: ('libcloud.backup.drivers.gce', 'GCEBackupDriver'), Provider.DIMENSIONDATA: - ('libcloud.backup.drivers.dimensiondata', 'DimensionDataBackupDriver') + ('libcloud.backup.drivers.dimensiondata', 'DimensionDataBackupDriver'), + Provider.NTTCIS: + ('libcloud.backup.drivers.nttcis', 'NttCisNodeDriver'), } http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/backup/types.py ---------------------------------------------------------------------- diff --git a/libcloud/backup/types.py b/libcloud/backup/types.py index 8a4f987..b9039ad 100644 --- a/libcloud/backup/types.py +++ b/libcloud/backup/types.py @@ -31,6 +31,7 @@ class Provider(object): DIMENSIONDATA = 'dimensiondata' EBS = 'ebs' GCE = 'gce' + NTTCIS = 'nttcis' class BackupTargetType(object): http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/common/nttcis.py ---------------------------------------------------------------------- diff --git a/libcloud/common/nttcis.py b/libcloud/common/nttcis.py index 12138d2..7215139 100644 --- a/libcloud/common/nttcis.py +++ b/libcloud/common/nttcis.py @@ -1412,7 +1412,7 @@ class NttCisBackupClientRunningJob(object): % (self.id)) -class DNttCisBackupClientType(object): +class NttCisBackupClientType(object): """ A client type object for backups """ http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/compute/drivers/nttcis.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/nttcis.py b/libcloud/compute/drivers/nttcis.py index 937cd67..8a2fa38 100644 --- a/libcloud/compute/drivers/nttcis.py +++ b/libcloud/compute/drivers/nttcis.py @@ -102,15 +102,15 @@ OBJECT_TO_TAGGING_ASSET_TYPE_MAP = { class NttCisNodeDriver(NodeDriver): """ - DimensionData node driver. + NttCis node driver. Default api_version is used unless specified. """ selected_region = None connectionCls = NttCisConnection - name = 'DimensionData' + name = 'NTTC-CIS' website = 'http://www.dimensiondata.com/' - type = Provider.DIMENSIONDATA + type = Provider.NTTCIS features = {'create_node': ['password']} api_version = 1.0 @@ -1387,7 +1387,7 @@ class NttCisNodeDriver(NodeDriver): :param location: The target location :type location: :class:`NodeLocation` or ``str`` - :return: a list of NttCisaNetwork objects + :return: a list of NttCisNetwork objects :rtype: ``list`` of :class:`NttCisNetwork` """ return self.list_networks(location=location) http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/http.py ---------------------------------------------------------------------- diff --git a/libcloud/http.py b/libcloud/http.py index e691937..c8d7102 100644 --- a/libcloud/http.py +++ b/libcloud/http.py @@ -221,6 +221,7 @@ class LibcloudConnection(LibcloudBaseConnection): verify=self.verification ) + def prepared_request(self, method, url, body=None, headers=None, raw=False, stream=False): headers = self._normalize_headers(headers=headers) http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/loadbalancer/drivers/nttcis.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/drivers/nttcis.py b/libcloud/loadbalancer/drivers/nttcis.py new file mode 100644 index 0000000..8cb723b --- /dev/null +++ b/libcloud/loadbalancer/drivers/nttcis.py @@ -0,0 +1,1162 @@ +# 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 withv +# 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. + +from libcloud.utils.py3 import ET +from libcloud.common.nttcis import NttCisConnection +from libcloud.common.nttcis import NttCisPool +from libcloud.common.nttcis import NttCisPoolMember +from libcloud.common.nttcis import NttCisVirtualListener +from libcloud.common.nttcis import NttCisVIPNode +from libcloud.common.nttcis import NttCisDefaultHealthMonitor +from libcloud.common.nttcis import NttCisPersistenceProfile +from libcloud.common.nttcis import \ + NttCisVirtualListenerCompatibility +from libcloud.common.nttcis import NttCisDefaultiRule +from libcloud.common.nttcis import API_ENDPOINTS +from libcloud.common.nttcis import DEFAULT_REGION +from libcloud.common.nttcis import TYPES_URN +from libcloud.utils.misc import reverse_dict +from libcloud.utils.xml import fixxpath, findtext, findall +from libcloud.loadbalancer.types import State +from libcloud.loadbalancer.base import Algorithm, Driver, LoadBalancer +from libcloud.loadbalancer.base import Member +from libcloud.loadbalancer.types import Provider + + +class NttCisLBDriver(Driver): + """ + NttCis node driver. + """ + + selected_region = None + connectionCls = NttCisConnection + name = 'Dimension Data Load Balancer' + website = 'https://cloud.nttcis.com/' + type = Provider.DIMENSIONDATA + api_version = 1.0 + + network_domain_id = None + + _VALUE_TO_ALGORITHM_MAP = { + 'ROUND_ROBIN': Algorithm.ROUND_ROBIN, + 'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS, + 'SHORTEST_RESPONSE': Algorithm.SHORTEST_RESPONSE, + 'PERSISTENT_IP': Algorithm.PERSISTENT_IP + } + _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP) + + _VALUE_TO_STATE_MAP = { + 'NORMAL': State.RUNNING, + 'PENDING_ADD': State.PENDING, + 'PENDING_CHANGE': State.PENDING, + 'PENDING_DELETE': State.PENDING, + 'FAILED_ADD': State.ERROR, + 'FAILED_CHANGE': State.ERROR, + 'FAILED_DELETE': State.ERROR, + 'REQUIRES_SUPPORT': State.ERROR + } + + def __init__(self, key, secret=None, secure=True, host=None, port=None, + api_version=None, region=DEFAULT_REGION, **kwargs): + + if region not in API_ENDPOINTS and host is None: + raise ValueError( + 'Invalid region: %s, no host specified' % (region)) + if region is not None: + self.selected_region = API_ENDPOINTS[region] + + super(NttCisLBDriver, self).__init__(key=key, secret=secret, + secure=secure, host=host, + port=port, + api_version=api_version, + region=region, + **kwargs) + + def _ex_connection_class_kwargs(self): + """ + Add the region to the kwargs before the connection is instantiated + """ + + kwargs = super(NttCisLBDriver, + self)._ex_connection_class_kwargs() + kwargs['region'] = self.selected_region + return kwargs + + def create_balancer(self, name, port=None, protocol=None, + algorithm=None, members=None, + ex_listener_ip_address=None): + """ + Create a new load balancer instance + + :param name: Name of the new load balancer (required) + :type name: ``str`` + + :param port: An integer in the range of 1-65535. If not supplied, + it will be taken to mean 'Any Port' + :type port: ``int`` + + :param protocol: Loadbalancer protocol, defaults to http. + :type protocol: ``str`` + + :param members: list of Members to attach to balancer (optional) + :type members: ``list`` of :class:`Member` + + :param algorithm: Load balancing algorithm, defaults to ROUND_ROBIN. + :type algorithm: :class:`.Algorithm` + + :param ex_listener_ip_address: Must be a valid IPv4 in dot-decimal + notation (x.x.x.x). + :type ex_listener_ip_address: ``str`` + + :rtype: :class:`LoadBalancer` + """ + network_domain_id = self.network_domain_id + if protocol is None: + protocol = 'http' + if algorithm is None: + algorithm = Algorithm.ROUND_ROBIN + + # Create a pool first + pool = self.ex_create_pool( + network_domain_id=network_domain_id, + name=name, + ex_description=None, + balancer_method=self._ALGORITHM_TO_VALUE_MAP[algorithm]) + + # Attach the members to the pool as nodes + if members is not None: + for member in members: + node = self.ex_create_node( + network_domain_id=network_domain_id, + name=member.ip, + ip=member.ip, + ex_description=None) + self.ex_create_pool_member( + pool=pool, + node=node, + port=port) + + # Create the virtual listener (balancer) + listener = self.ex_create_virtual_listener( + network_domain_id=network_domain_id, + name=name, + ex_description=name, + port=port, + pool=pool, + listener_ip_address=ex_listener_ip_address) + + return LoadBalancer( + id=listener.id, + name=listener.name, + state=State.RUNNING, + ip=listener.ip, + port=port, + driver=self, + extra={'pool_id': pool.id, + 'network_domain_id': network_domain_id, + 'listener_ip_address': ex_listener_ip_address} + ) + + def list_balancers(self, ex_network_domain_id=None): + """ + List all loadbalancers inside a geography or in given network. + + In Dimension Data terminology these are known as virtual listeners + + :param ex_network_domain_id: UUID of Network Domain + if not None returns only balancers in the given network + if None then returns all pools for the organization + :type ex_network_domain_id: ``str`` + + :rtype: ``list`` of :class:`LoadBalancer` + """ + params = None + if ex_network_domain_id is not None: + params = {"networkDomainId": ex_network_domain_id} + + return self._to_balancers( + self.connection + .request_with_orgId_api_2('networkDomainVip/virtualListener', + params=params).object) + + def get_balancer(self, balancer_id): + """ + Return a :class:`LoadBalancer` object. + + :param balancer_id: id of a load balancer you want to fetch + :type balancer_id: ``str`` + + :rtype: :class:`LoadBalancer` + """ + + bal = self.connection \ + .request_with_orgId_api_2('networkDomainVip/virtualListener/%s' + % balancer_id).object + return self._to_balancer(bal) + + def list_protocols(self): + """ + Return a list of supported protocols. + + Since all protocols are support by Dimension Data, this is a list + of common protocols. + + :rtype: ``list`` of ``str`` + """ + return ['http', 'https', 'tcp', 'udp', 'ftp', 'smtp'] + + def balancer_list_members(self, balancer): + """ + Return list of members attached to balancer. + + In Dimension Data terminology these are the members of the pools + within a virtual listener. + + :param balancer: LoadBalancer which should be used + :type balancer: :class:`LoadBalancer` + + :rtype: ``list`` of :class:`Member` + """ + pool_members = self.ex_get_pool_members(balancer.extra['pool_id']) + members = [] + for pool_member in pool_members: + members.append(Member( + id=pool_member.id, + ip=pool_member.ip, + port=pool_member.port, + balancer=balancer, + extra=None + )) + return members + + def balancer_attach_member(self, balancer, member): + """ + Attach a member to balancer + + :param balancer: LoadBalancer which should be used + :type balancer: :class:`LoadBalancer` + + :param member: Member to join to the balancer + :type member: :class:`Member` + + :return: Member after joining the balancer. + :rtype: :class:`Member` + """ + node = self.ex_create_node( + network_domain_id=balancer.extra['network_domain_id'], + name='Member.' + member.ip, + ip=member.ip, + ex_description='' + ) + if node is False: + return False + pool = self.ex_get_pool(balancer.extra['pool_id']) + pool_member = self.ex_create_pool_member( + pool=pool, + node=node, + port=member.port) + member.id = pool_member.id + return member + + def balancer_detach_member(self, balancer, member): + """ + Detach member from balancer + + :param balancer: LoadBalancer which should be used + :type balancer: :class:`LoadBalancer` + + :param member: Member which should be used + :type member: :class:`Member` + + :return: ``True`` if member detach was successful, otherwise ``False``. + :rtype: ``bool`` + """ + create_pool_m = ET.Element('removePoolMember', {'xmlns': TYPES_URN, + 'id': member.id}) + + result = self.connection.request_with_orgId_api_2( + 'networkDomainVip/removePoolMember', + method='POST', + data=ET.tostring(create_pool_m)).object + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def destroy_balancer(self, balancer): + """ + Destroy a load balancer (virtual listener) + + :param balancer: LoadBalancer which should be used + :type balancer: :class:`LoadBalancer` + + :return: ``True`` if the destroy was successful, otherwise ``False``. + :rtype: ``bool`` + """ + delete_listener = ET.Element('deleteVirtualListener', + {'xmlns': TYPES_URN, + 'id': balancer.id}) + + result = self.connection.request_with_orgId_api_2( + 'networkDomainVip/deleteVirtualListener', + method='POST', + data=ET.tostring(delete_listener)).object + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_set_current_network_domain(self, network_domain_id): + """ + Set the network domain (part of the network) of the driver + + :param network_domain_id: ID of the pool (required) + :type network_domain_id: ``str`` + """ + self.network_domain_id = network_domain_id + + def ex_get_current_network_domain(self): + """ + Get the current network domain ID of the driver. + + :return: ID of the network domain + :rtype: ``str`` + """ + return self.network_domain_id + + def ex_create_pool_member(self, pool, node, port=None): + """ + Create a new member in an existing pool from an existing node + + :param pool: Instance of ``NttCisPool`` (required) + :type pool: ``NttCisPool`` + + :param node: Instance of ``NttCisVIPNode`` (required) + :type node: ``NttCisVIPNode`` + + :param port: Port the the service will listen on + :type port: ``str`` + + :return: The node member, instance of ``NttCisPoolMember`` + :rtype: ``NttCisPoolMember`` + """ + create_pool_m = ET.Element('addPoolMember', {'xmlns': TYPES_URN}) + ET.SubElement(create_pool_m, "poolId").text = pool.id + ET.SubElement(create_pool_m, "nodeId").text = node.id + if port is not None: + ET.SubElement(create_pool_m, "port").text = str(port) + ET.SubElement(create_pool_m, "status").text = 'ENABLED' + + response = self.connection.request_with_orgId_api_2( + 'networkDomainVip/addPoolMember', + method='POST', + data=ET.tostring(create_pool_m)).object + + member_id = None + node_name = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'poolMemberId': + member_id = info.get('value') + if info.get('name') == 'nodeName': + node_name = info.get('value') + + return NttCisPoolMember( + id=member_id, + name=node_name, + status=State.RUNNING, + ip=node.ip, + port=port, + node_id=node.id + ) + + def ex_create_node(self, + network_domain_id, + name, + ip, + ex_description, + connection_limit=25000, + connection_rate_limit=2000): + """ + Create a new node + + :param network_domain_id: Network Domain ID (required) + :type name: ``str`` + + :param name: name of the node (required) + :type name: ``str`` + + :param ip: IPv4 address of the node (required) + :type ip: ``str`` + + :param ex_description: Description of the node (required) + :type ex_description: ``str`` + + :param connection_limit: Maximum number + of concurrent connections per sec + :type connection_limit: ``int`` + + :param connection_rate_limit: Maximum number of concurrent sessions + :type connection_rate_limit: ``int`` + + :return: Instance of ``NttCisVIPNode`` + :rtype: ``NttCisVIPNode`` + """ + create_node_elm = ET.Element('createNode', {'xmlns': TYPES_URN}) + ET.SubElement(create_node_elm, "networkDomainId") \ + .text = network_domain_id + ET.SubElement(create_node_elm, "name").text = name + ET.SubElement(create_node_elm, "description").text \ + = str(ex_description) + ET.SubElement(create_node_elm, "ipv4Address").text = ip + ET.SubElement(create_node_elm, "status").text = 'ENABLED' + ET.SubElement(create_node_elm, "connectionLimit") \ + .text = str(connection_limit) + ET.SubElement(create_node_elm, "connectionRateLimit") \ + .text = str(connection_rate_limit) + + response = self.connection.request_with_orgId_api_2( + action='networkDomainVip/createNode', + method='POST', + data=ET.tostring(create_node_elm)).object + + node_id = None + node_name = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'nodeId': + node_id = info.get('value') + if info.get('name') == 'name': + node_name = info.get('value') + return NttCisVIPNode( + id=node_id, + name=node_name, + status=State.RUNNING, + ip=ip + ) + + def ex_update_node(self, node): + """ + Update the properties of a node + + :param pool: The instance of ``NttCisNode`` to update + :type pool: ``NttCisNode`` + + :return: The instance of ``NttCisNode`` + :rtype: ``NttCisNode`` + """ + create_node_elm = ET.Element('editNode', {'xmlns': TYPES_URN}) + ET.SubElement(create_node_elm, "connectionLimit") \ + .text = str(node.connection_limit) + ET.SubElement(create_node_elm, "connectionRateLimit") \ + .text = str(node.connection_rate_limit) + + self.connection.request_with_orgId_api_2( + action='networkDomainVip/createNode', + method='POST', + data=ET.tostring(create_node_elm)).object + return node + + def ex_set_node_state(self, node, enabled): + """ + Change the state of a node (enable/disable) + + :param pool: The instance of ``NttCisNode`` to update + :type pool: ``NttCisNode`` + + :param enabled: The target state of the node + :type enabled: ``bool`` + + :return: The instance of ``NttCisNode`` + :rtype: ``NttCisNode`` + """ + create_node_elm = ET.Element('editNode', {'xmlns': TYPES_URN}) + ET.SubElement(create_node_elm, "status") \ + .text = "ENABLED" if enabled is True else "DISABLED" + + self.connection.request_with_orgId_api_2( + action='networkDomainVip/editNode', + method='POST', + data=ET.tostring(create_node_elm)).object + return node + + def ex_create_pool(self, + network_domain_id, + name, + balancer_method, + ex_description, + health_monitors=None, + service_down_action='NONE', + slow_ramp_time=30): + """ + Create a new pool + + :param network_domain_id: Network Domain ID (required) + :type name: ``str`` + + :param name: name of the node (required) + :type name: ``str`` + + :param balancer_method: The load balancer algorithm (required) + :type balancer_method: ``str`` + + :param ex_description: Description of the node (required) + :type ex_description: ``str`` + + :param health_monitors: A list of health monitors to use for the pool. + :type health_monitors: ``list`` of + :class:`NttCisDefaultHealthMonitor` + + :param service_down_action: What to do when node + is unavailable NONE, DROP or RESELECT + :type service_down_action: ``str`` + + :param slow_ramp_time: Number of seconds to stagger ramp up of nodes + :type slow_ramp_time: ``int`` + + :return: Instance of ``NttCisPool`` + :rtype: ``NttCisPool`` + """ + # Names cannot contain spaces. + name.replace(' ', '_') + create_node_elm = ET.Element('createPool', {'xmlns': TYPES_URN}) + ET.SubElement(create_node_elm, "networkDomainId") \ + .text = network_domain_id + ET.SubElement(create_node_elm, "name").text = name + ET.SubElement(create_node_elm, "description").text \ + = str(ex_description) + ET.SubElement(create_node_elm, "loadBalanceMethod") \ + .text = str(balancer_method) + + if health_monitors is not None: + for monitor in health_monitors: + ET.SubElement(create_node_elm, "healthMonitorId") \ + .text = str(monitor.id) + + ET.SubElement(create_node_elm, "serviceDownAction") \ + .text = service_down_action + ET.SubElement(create_node_elm, "slowRampTime").text \ + = str(slow_ramp_time) + + response = self.connection.request_with_orgId_api_2( + action='networkDomainVip/createPool', + method='POST', + data=ET.tostring(create_node_elm)).object + + pool_id = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'poolId': + pool_id = info.get('value') + + return NttCisPool( + id=pool_id, + name=name, + description=ex_description, + status=State.RUNNING, + load_balance_method=str(balancer_method), + health_monitor_id=None, + service_down_action=service_down_action, + slow_ramp_time=str(slow_ramp_time) + ) + + def ex_create_virtual_listener(self, + network_domain_id, + name, + ex_description, + port=None, + pool=None, + listener_ip_address=None, + persistence_profile=None, + fallback_persistence_profile=None, + irule=None, + protocol='TCP', + connection_limit=25000, + connection_rate_limit=2000, + source_port_preservation='PRESERVE'): + """ + Create a new virtual listener (load balancer) + + :param network_domain_id: Network Domain ID (required) + :type name: ``str`` + + :param name: name of the listener (required) + :type name: ``str`` + + :param ex_description: Description of the node (required) + :type ex_description: ``str`` + + :param port: An integer in the range of 1-65535. If not supplied, + it will be taken to mean 'Any Port' + :type port: ``int`` + + :param pool: The pool to use for the listener + :type pool: :class:`NttCisPool` + + :param listener_ip_address: The IPv4 Address of the virtual listener + :type listener_ip_address: ``str`` + + :param persistence_profile: Persistence profile + :type persistence_profile: :class:`NttCisPersistenceProfile` + + :param fallback_persistence_profile: Fallback persistence profile + :type fallback_persistence_profile: + :class:`NttCisPersistenceProfile` + + :param irule: The iRule to apply + :type irule: :class:`NttCisDefaultiRule` + + :param protocol: For STANDARD type, ANY, TCP or UDP + for PERFORMANCE_LAYER_4 choice of ANY, TCP, UDP, HTTP + :type protcol: ``str`` + + :param connection_limit: Maximum number + of concurrent connections per sec + :type connection_limit: ``int`` + + :param connection_rate_limit: Maximum number of concurrent sessions + :type connection_rate_limit: ``int`` + + :param source_port_preservation: Choice of PRESERVE, + PRESERVE_STRICT or CHANGE + :type source_port_preservation: ``str`` + + :return: Instance of the listener + :rtype: ``NttCisVirtualListener`` + """ + if (port == 80) or (port == 443): + listener_type = 'PERFORMANCE_LAYER_4' + else: + listener_type = 'STANDARD' + + create_node_elm = ET.Element('createVirtualListener', + {'xmlns': TYPES_URN}) + ET.SubElement(create_node_elm, "networkDomainId") \ + .text = network_domain_id + ET.SubElement(create_node_elm, "name").text = name + ET.SubElement(create_node_elm, "description").text = \ + str(ex_description) + ET.SubElement(create_node_elm, "type").text = listener_type + ET.SubElement(create_node_elm, "protocol") \ + .text = protocol + if listener_ip_address is not None: + ET.SubElement(create_node_elm, "listenerIpAddress").text = \ + str(listener_ip_address) + if port is not None: + ET.SubElement(create_node_elm, "port").text = str(port) + ET.SubElement(create_node_elm, "enabled").text = 'true' + ET.SubElement(create_node_elm, "connectionLimit") \ + .text = str(connection_limit) + ET.SubElement(create_node_elm, "connectionRateLimit") \ + .text = str(connection_rate_limit) + ET.SubElement(create_node_elm, "sourcePortPreservation") \ + .text = source_port_preservation + if pool is not None: + ET.SubElement(create_node_elm, "poolId") \ + .text = pool.id + if persistence_profile is not None: + ET.SubElement(create_node_elm, "persistenceProfileId") \ + .text = persistence_profile.id + if fallback_persistence_profile is not None: + ET.SubElement(create_node_elm, "fallbackPersistenceProfileId") \ + .text = fallback_persistence_profile.id + if irule is not None: + ET.SubElement(create_node_elm, "iruleId") \ + .text = irule.id + + response = self.connection.request_with_orgId_api_2( + action='networkDomainVip/createVirtualListener', + method='POST', + data=ET.tostring(create_node_elm)).object + + virtual_listener_id = None + virtual_listener_ip = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'virtualListenerId': + virtual_listener_id = info.get('value') + if info.get('name') == 'listenerIpAddress': + virtual_listener_ip = info.get('value') + + return NttCisVirtualListener( + id=virtual_listener_id, + name=name, + ip=virtual_listener_ip, + status=State.RUNNING + ) + + def ex_get_pools(self, ex_network_domain_id=None): + """ + Get all of the pools inside the current geography or + in given network. + + :param ex_network_domain_id: UUID of Network Domain + if not None returns only balancers in the given network + if None then returns all pools for the organization + :type ex_network_domain_id: ``str`` + + :return: Returns a ``list`` of type ``NttCisPool`` + :rtype: ``list`` of ``NttCisPool`` + """ + params = None + + if ex_network_domain_id is not None: + params = {"networkDomainId": ex_network_domain_id} + + pools = self.connection \ + .request_with_orgId_api_2('networkDomainVip/pool', + params=params).object + + return self._to_pools(pools) + + def ex_get_pool(self, pool_id): + """ + Get a specific pool inside the current geography + + :param pool_id: The identifier of the pool + :type pool_id: ``str`` + + :return: Returns an instance of ``NttCisPool`` + :rtype: ``NttCisPool`` + """ + pool = self.connection \ + .request_with_orgId_api_2('networkDomainVip/pool/%s' + % pool_id).object + return self._to_pool(pool) + + def ex_update_pool(self, pool): + """ + Update the properties of an existing pool + only method, serviceDownAction and slowRampTime are updated + + :param pool: The instance of ``NttCisPool`` to update + :type pool: ``NttCisPool`` + + :return: ``True`` for success, ``False`` for failure + :rtype: ``bool`` + """ + create_node_elm = ET.Element('editPool', {'xmlns': TYPES_URN}) + + ET.SubElement(create_node_elm, "loadBalanceMethod") \ + .text = str(pool.load_balance_method) + ET.SubElement(create_node_elm, "serviceDownAction") \ + .text = pool.service_down_action + ET.SubElement(create_node_elm, "slowRampTime").text \ + = str(pool.slow_ramp_time) + + response = self.connection.request_with_orgId_api_2( + action='networkDomainVip/editPool', + method='POST', + data=ET.tostring(create_node_elm)).object + response_code = findtext(response, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_destroy_pool(self, pool): + """ + Destroy an existing pool + + :param pool: The instance of ``NttCisPool`` to destroy + :type pool: ``NttCisPool`` + + :return: ``True`` for success, ``False`` for failure + :rtype: ``bool`` + """ + destroy_request = ET.Element('deletePool', + {'xmlns': TYPES_URN, + 'id': pool.id}) + + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/deletePool', + method='POST', + data=ET.tostring(destroy_request)).object + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_get_pool_members(self, pool_id): + """ + Get the members of a pool + + :param pool: The instance of a pool + :type pool: ``NttCisPool`` + + :return: Returns an ``list`` of ``NttCisPoolMember`` + :rtype: ``list`` of ``NttCisPoolMember`` + """ + members = self.connection \ + .request_with_orgId_api_2('networkDomainVip/poolMember?poolId=%s' + % pool_id).object + return self._to_members(members) + + def ex_get_pool_member(self, pool_member_id): + """ + Get a specific member of a pool + + :param pool: The id of a pool member + :type pool: ``str`` + + :return: Returns an instance of ``NttCisPoolMember`` + :rtype: ``NttCisPoolMember`` + """ + member = self.connection \ + .request_with_orgId_api_2('networkDomainVip/poolMember/%s' + % pool_member_id).object + return self._to_member(member) + + def ex_set_pool_member_state(self, member, enabled=True): + request = ET.Element('editPoolMember', + {'xmlns': TYPES_URN, + 'id': member.id}) + state = "ENABLED" if enabled is True else "DISABLED" + ET.SubElement(request, 'status').text = state + + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/editPoolMember', + method='POST', + data=ET.tostring(request)).object + + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_destroy_pool_member(self, member, destroy_node=False): + """ + Destroy a specific member of a pool + + :param pool: The instance of a pool member + :type pool: ``NttCisPoolMember`` + + :param destroy_node: Also destroy the associated node + :type destroy_node: ``bool`` + + :return: ``True`` for success, ``False`` for failure + :rtype: ``bool`` + """ + # remove the pool member + destroy_request = ET.Element('removePoolMember', + {'xmlns': TYPES_URN, + 'id': member.id}) + + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/removePoolMember', + method='POST', + data=ET.tostring(destroy_request)).object + + if member.node_id is not None and destroy_node is True: + return self.ex_destroy_node(member.node_id) + else: + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_get_nodes(self, ex_network_domain_id=None): + """ + Get the nodes within this geography or in given network. + + :param ex_network_domain_id: UUID of Network Domain + if not None returns only balancers in the given network + if None then returns all pools for the organization + :type ex_network_domain_id: ``str`` + + :return: Returns an ``list`` of ``NttCisVIPNode`` + :rtype: ``list`` of ``NttCisVIPNode`` + """ + params = None + if ex_network_domain_id is not None: + params = {"networkDomainId": ex_network_domain_id} + + nodes = self.connection \ + .request_with_orgId_api_2('networkDomainVip/node', + params=params).object + return self._to_nodes(nodes) + + def ex_get_node(self, node_id): + """ + Get the node specified by node_id + + :return: Returns an instance of ``NttCisVIPNode`` + :rtype: Instance of ``NttCisVIPNode`` + """ + nodes = self.connection \ + .request_with_orgId_api_2('networkDomainVip/node/%s' + % node_id).object + return self._to_node(nodes) + + def ex_destroy_node(self, node_id): + """ + Destroy a specific node + + :param node_id: The ID of of a ``NttCisVIPNode`` + :type node_id: ``str`` + + :return: ``True`` for success, ``False`` for failure + :rtype: ``bool`` + """ + # Destroy the node + destroy_request = ET.Element('deleteNode', + {'xmlns': TYPES_URN, + 'id': node_id}) + + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/deleteNode', + method='POST', + data=ET.tostring(destroy_request)).object + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_wait_for_state(self, state, func, poll_interval=2, + timeout=60, *args, **kwargs): + """ + Wait for the function which returns a instance + with field status to match + + Keep polling func until one of the desired states is matched + + :param state: Either the desired state (`str`) or a `list` of states + :type state: ``str`` or ``list`` + + :param func: The function to call, e.g. ex_get_vlan + :type func: ``function`` + + :param poll_interval: The number of seconds to wait between checks + :type poll_interval: `int` + + :param timeout: The total number of seconds to wait to reach a state + :type timeout: `int` + + :param args: The arguments for func + :type args: Positional arguments + + :param kwargs: The arguments for func + :type kwargs: Keyword arguments + """ + return self.connection.wait_for_state(state, func, poll_interval, + timeout, *args, **kwargs) + + def ex_get_default_health_monitors(self, network_domain_id): + """ + Get the default health monitors available for a network domain + + :param network_domain_id: The ID of of a ``NttCisNetworkDomain`` + :type network_domain_id: ``str`` + + :rtype: `list` of :class:`NttCisDefaultHealthMonitor` + """ + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/defaultHealthMonitor', + params={'networkDomainId': network_domain_id}, + method='GET').object + return self._to_health_monitors(result) + + def ex_get_default_persistence_profiles(self, network_domain_id): + """ + Get the default persistence profiles available for a network domain + + :param network_domain_id: The ID of of a ``NttCisNetworkDomain`` + :type network_domain_id: ``str`` + + :rtype: `list` of :class:`NttCisPersistenceProfile` + """ + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/defaultPersistenceProfile', + params={'networkDomainId': network_domain_id}, + method='GET').object + return self._to_persistence_profiles(result) + + def ex_get_default_irules(self, network_domain_id): + """ + Get the default iRules available for a network domain + + :param network_domain_id: The ID of of a ``NttCisNetworkDomain`` + :type network_domain_id: ``str`` + + :rtype: `list` of :class:`NttCisDefaultiRule` + """ + result = self.connection.request_with_orgId_api_2( + action='networkDomainVip/defaultIrule', + params={'networkDomainId': network_domain_id}, + method='GET').object + return self._to_irules(result) + + def _to_irules(self, object): + irules = [] + matches = object.findall( + fixxpath('defaultIrule', TYPES_URN)) + for element in matches: + irules.append(self._to_irule(element)) + return irules + + def _to_irule(self, element): + compatible = [] + matches = element.findall( + fixxpath('virtualListenerCompatibility', TYPES_URN)) + for match_element in matches: + compatible.append( + NttCisVirtualListenerCompatibility( + type=match_element.get('type'), + protocol=match_element.get('protocol', None))) + irule_element = element.find(fixxpath('irule', TYPES_URN)) + return NttCisDefaultiRule( + id=irule_element.get('id'), + name=irule_element.get('name'), + compatible_listeners=compatible + ) + + def _to_persistence_profiles(self, object): + profiles = [] + matches = object.findall( + fixxpath('defaultPersistenceProfile', TYPES_URN)) + for element in matches: + profiles.append(self._to_persistence_profile(element)) + return profiles + + def _to_persistence_profile(self, element): + compatible = [] + matches = element.findall( + fixxpath('virtualListenerCompatibility', TYPES_URN)) + for match_element in matches: + compatible.append( + NttCisVirtualListenerCompatibility( + type=match_element.get('type'), + protocol=match_element.get('protocol', None))) + + return NttCisPersistenceProfile( + id=element.get('id'), + fallback_compatible=bool( + element.get('fallbackCompatible') == "true"), + name=findtext(element, 'name', TYPES_URN), + compatible_listeners=compatible + ) + + def _to_health_monitors(self, object): + monitors = [] + matches = object.findall(fixxpath('defaultHealthMonitor', TYPES_URN)) + for element in matches: + monitors.append(self._to_health_monitor(element)) + return monitors + + def _to_health_monitor(self, element): + return NttCisDefaultHealthMonitor( + id=element.get('id'), + name=findtext(element, 'name', TYPES_URN), + node_compatible=bool( + findtext(element, 'nodeCompatible', TYPES_URN) == "true"), + pool_compatible=bool( + findtext(element, 'poolCompatible', TYPES_URN) == "true"), + ) + + def _to_nodes(self, object): + nodes = [] + for element in object.findall(fixxpath("node", TYPES_URN)): + nodes.append(self._to_node(element)) + + return nodes + + def _to_node(self, element): + ipaddress = findtext(element, 'ipv4Address', TYPES_URN) + if ipaddress is None: + ipaddress = findtext(element, 'ipv6Address', TYPES_URN) + + name = findtext(element, 'name', TYPES_URN) + + node = NttCisVIPNode( + id=element.get('id'), + name=name, + status=self._VALUE_TO_STATE_MAP.get( + findtext(element, 'state', TYPES_URN), + State.UNKNOWN), + connection_rate_limit=findtext(element, + 'connectionRateLimit', TYPES_URN), + connection_limit=findtext(element, 'connectionLimit', TYPES_URN), + ip=ipaddress) + + return node + + def _to_balancers(self, object): + loadbalancers = [] + for element in object.findall(fixxpath("virtualListener", TYPES_URN)): + loadbalancers.append(self._to_balancer(element)) + + return loadbalancers + + def _to_balancer(self, element): + ipaddress = findtext(element, 'listenerIpAddress', TYPES_URN) + name = findtext(element, 'name', TYPES_URN) + port = findtext(element, 'port', TYPES_URN) + extra = {} + + pool_element = element.find(fixxpath( + 'pool', + TYPES_URN)) + if pool_element is None: + extra['pool_id'] = None + + else: + extra['pool_id'] = pool_element.get('id') + + extra['network_domain_id'] = findtext(element, 'networkDomainId', + TYPES_URN) + + balancer = LoadBalancer( + id=element.get('id'), + name=name, + state=self._VALUE_TO_STATE_MAP.get( + findtext(element, 'state', TYPES_URN), + State.UNKNOWN), + ip=ipaddress, + port=port, + driver=self.connection.driver, + extra=extra + ) + + return balancer + + def _to_members(self, object): + members = [] + for element in object.findall(fixxpath("poolMember", TYPES_URN)): + members.append(self._to_member(element)) + + return members + + def _to_member(self, element): + port = findtext(element, 'port', TYPES_URN) + if port is not None: + port = int(port) + pool_member = NttCisPoolMember( + id=element.get('id'), + name=element.find(fixxpath( + 'node', + TYPES_URN)).get('name'), + status=findtext(element, 'state', TYPES_URN), + node_id=element.find(fixxpath( + 'node', + TYPES_URN)).get('id'), + ip=element.find(fixxpath( + 'node', + TYPES_URN)).get('ipAddress'), + port=port + ) + return pool_member + + def _to_pools(self, object): + pools = [] + for element in object.findall(fixxpath("pool", TYPES_URN)): + pools.append(self._to_pool(element)) + + return pools + + def _to_pool(self, element): + pool = NttCisPool( + id=element.get('id'), + name=findtext(element, 'name', TYPES_URN), + status=findtext(element, 'state', TYPES_URN), + description=findtext(element, 'description', TYPES_URN), + load_balance_method=findtext(element, 'loadBalanceMethod', + TYPES_URN), + health_monitor_id=findtext(element, 'healthMonitorId', TYPES_URN), + service_down_action=findtext(element, 'serviceDownAction', + TYPES_URN), + slow_ramp_time=findtext(element, 'slowRampTime', TYPES_URN), + ) + return pool http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/loadbalancer/providers.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/providers.py b/libcloud/loadbalancer/providers.py index a778304..967cc62 100644 --- a/libcloud/loadbalancer/providers.py +++ b/libcloud/loadbalancer/providers.py @@ -47,6 +47,8 @@ DRIVERS = { ('libcloud.loadbalancer.drivers.dimensiondata', 'DimensionDataLBDriver'), Provider.ALIYUN_SLB: ('libcloud.loadbalancer.drivers.slb', 'SLBDriver'), + Provider.NTTCIS: + ('libcloud.loadbalancer.drivers.nttcis', 'NttCisNodeDriver'), } http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/libcloud/loadbalancer/types.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/types.py b/libcloud/loadbalancer/types.py index 5752622..bfb5430 100644 --- a/libcloud/loadbalancer/types.py +++ b/libcloud/loadbalancer/types.py @@ -51,6 +51,7 @@ class Provider(object): GCE = 'gce' GOGRID = 'gogrid' NINEFOLD = 'ninefold' + NTTCIS = 'nttcis' RACKSPACE = 'rackspace' SOFTLAYER = 'softlayer' http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/tests/conftest.py ---------------------------------------------------------------------- diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e6bbeb7 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,10 @@ +import pytest +import libcloud + + [email protected](scope="module") +def driver(): + cls = libcloud.get_driver(libcloud.DriverType.COMPUTE, libcloud.DriverType.COMPUTE.NTTCIS) + driver = cls('mitchgeo-test', 'Snmpv2c!', region='dd-eu') + return driver + http://git-wip-us.apache.org/repos/asf/libcloud/blob/cdc27a66/tests/libtest.py ---------------------------------------------------------------------- diff --git a/tests/libtest.py b/tests/libtest.py index f235c46..cba785c 100644 --- a/tests/libtest.py +++ b/tests/libtest.py @@ -2,10 +2,35 @@ import pytest import libcloud -def test_connection(): - cls = libcloud.get_driver(libcloud.DriverType.COMPUTE, libcloud.DriverType.COMPUTE.NTTCIS) - driver = cls('mitchgeo-test', 'Snmpv2c!', region='dd-eu') +def test_list_node_all(driver): nodes = driver.list_nodes() - assert isinstance(nodes, list) + for node in nodes: + print(node.extra['networkDomainId'], node.extra['datacenterId'], node.uuid, node.state, node.name, node.extra['cpu'], + [disk for disk in node.extra['disks']], node.extra['memoryMb'], node.extra['OS_displayName'], node.extra['ipv6']) + assert isinstance(nodes, list) and len(nodes) > 0 +def test_list_node_location(driver): + nodes = driver.list_nodes(ex_location='EU7') + print() + for node in nodes: + print(node.extra['networkDomainId'], node.extra['datacenterId'], node.uuid, node.state, node.name, node.extra['cpu'], + [disk for disk in node.extra['disks']], node.extra['memoryMb'], node.extra['OS_displayName'], node.extra['ipv6']) + assert isinstance(nodes, list) and len(nodes) > 0 + + +def test_list_node_started(driver): + nodes = driver.list_nodes(ex_started='true') + print() + for node in nodes: + print(node.extra['networkDomainId'], node.extra['datacenterId'], node.uuid, node.state, node.name, node.extra['cpu'], + [disk for disk in node.extra['disks']], node.extra['memoryMb'], node.extra['OS_displayName'], node.extra['ipv6']) + assert isinstance(nodes, list) and len(nodes) > 0 + + +def test_images(driver): + images = driver.list_images() + print() + print(images) + assert isinstance(images, list) and len(images) > 0 +
