libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anthonys...@apache.org
Subject [04/51] [abbrv] libcloud git commit: Added nttcis modules to backup and loadbalancer packages and edited providers and types modules in packages at needed
Date Wed, 31 Oct 2018 03:11:15 GMT
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 <mitch.raful@itaas.dimensiondata.com>
Authored: Fri Jul 13 09:52:49 2018 -0400
Committer: mitch <mitch.raful@itaas.dimensiondata.com>
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
+
+
+@pytest.fixture(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
+


Mime
View raw message