http://git-wip-us.apache.org/repos/asf/libcloud/blob/b80bf325/libcloud/compute/drivers/nttcis.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/nttcis.py b/libcloud/compute/drivers/nttcis.py new file mode 100644 index 0000000..937cd67 --- /dev/null +++ b/libcloud/compute/drivers/nttcis.py @@ -0,0 +1,4754 @@ +# 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. +""" +NTT CIS Driver +""" + +from libcloud.utils.py3 import ET +from libcloud.common.nttcis import LooseVersion +from libcloud.common.exceptions import BaseHTTPError +from libcloud.compute.base import NodeDriver, Node, NodeAuthPassword +from libcloud.compute.base import NodeSize, NodeImage, NodeLocation +from libcloud.common.nttcis import dd_object_to_id +from libcloud.common.nttcis import NttCisAPIException +from libcloud.common.nttcis import (NttCisConnection, + NttCisStatus) +from libcloud.common.nttcis import NttCisNetwork +from libcloud.common.nttcis import NttCisNetworkDomain +from libcloud.common.nttcis import NttCisVlan +from libcloud.common.nttcis import NttCisServerCpuSpecification +from libcloud.common.nttcis import NttCisServerDisk +from libcloud.common.nttcis import NttCisServerVMWareTools +from libcloud.common.nttcis import NttCisPublicIpBlock +from libcloud.common.nttcis import NttCisFirewallRule +from libcloud.common.nttcis import NttCisFirewallAddress +from libcloud.common.nttcis import NttCisNatRule +from libcloud.common.nttcis import NttCisAntiAffinityRule +from libcloud.common.nttcis import NttCisIpAddressList +from libcloud.common.nttcis import NttCisChildIpAddressList +from libcloud.common.nttcis import NttCisIpAddress +from libcloud.common.nttcis import NttCisPortList +from libcloud.common.nttcis import NttCisPort +from libcloud.common.nttcis import NttCisChildPortList +from libcloud.common.nttcis import NttCisNic +from libcloud.common.nttcis import NetworkDomainServicePlan +from libcloud.common.nttcis import NttCisTagKey +from libcloud.common.nttcis import NttCisTag +from libcloud.common.nttcis import API_ENDPOINTS, DEFAULT_REGION +from libcloud.common.nttcis import TYPES_URN +from libcloud.common.nttcis import SERVER_NS, NETWORK_NS, GENERAL_NS +from libcloud.utils.py3 import urlencode, ensure_string +from libcloud.utils.xml import fixxpath, findtext, findall +from libcloud.utils.py3 import basestring +from libcloud.compute.types import NodeState, Provider +import sys + +# Node state map is a dictionary with the keys as tuples +# These tuples represent: +# (<state_of_node_from_didata>, <is node started?>, <action happening>) +NODE_STATE_MAP = { + ('NORMAL', 'false', None): + NodeState.STOPPED, + ('PENDING_CHANGE', 'false', None): + NodeState.PENDING, + ('PENDING_CHANGE', 'false', 'CHANGE_NETWORK_ADAPTER'): + NodeState.PENDING, + ('PENDING_CHANGE', 'true', 'CHANGE_NETWORK_ADAPTER'): + NodeState.PENDING, + ('PENDING_CHANGE', 'false', 'EXCHANGE_NIC_VLANS'): + NodeState.PENDING, + ('PENDING_CHANGE', 'true', 'EXCHANGE_NIC_VLANS'): + NodeState.PENDING, + ('NORMAL', 'true', None): + NodeState.RUNNING, + ('PENDING_CHANGE', 'true', 'START_SERVER'): + NodeState.STARTING, + ('PENDING_ADD', 'true', 'DEPLOY_SERVER'): + NodeState.STARTING, + ('PENDING_ADD', 'true', 'DEPLOY_SERVER_WITH_DISK_SPEED'): + NodeState.STARTING, + ('PENDING_CHANGE', 'true', 'SHUTDOWN_SERVER'): + NodeState.STOPPING, + ('PENDING_CHANGE', 'true', 'POWER_OFF_SERVER'): + NodeState.STOPPING, + ('PENDING_CHANGE', 'true', 'REBOOT_SERVER'): + NodeState.REBOOTING, + ('PENDING_CHANGE', 'true', 'RESET_SERVER'): + NodeState.REBOOTING, + ('PENDING_CHANGE', 'true', 'RECONFIGURE_SERVER'): + NodeState.RECONFIGURING, +} + +OBJECT_TO_TAGGING_ASSET_TYPE_MAP = { + 'Node': 'SERVER', + 'NodeImage': 'CUSTOMER_IMAGE', + 'NttCisNetworkDomain': 'NETWORK_DOMAIN', + 'NttCisVlan': 'VLAN', + 'NttCisPublicIpBlock': 'PUBLIC_IP_BLOCK' +} + + +class NttCisNodeDriver(NodeDriver): + """ + DimensionData node driver. + Default api_version is used unless specified. + """ + + selected_region = None + connectionCls = NttCisConnection + name = 'DimensionData' + website = 'http://www.dimensiondata.com/' + type = Provider.DIMENSIONDATA + features = {'create_node': ['password']} + api_version = 1.0 + + 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] + + if api_version is not None: + self.api_version = api_version + + super(NttCisNodeDriver, 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(NttCisNodeDriver, + self)._ex_connection_class_kwargs() + kwargs['region'] = self.selected_region + kwargs['api_version'] = self.api_version + return kwargs + + def _create_node_mcp1(self, name, image, auth, ex_description, + ex_network=None, + ex_memory_gb=None, + ex_cpu_specification=None, + ex_is_started=True, + ex_primary_dns=None, + ex_secondary_dns=None, **kwargs): + """ + Create a new NTTCIS node + + :keyword name: String with a name for this new node (required) + :type name: ``str`` + + :keyword image: OS Image to boot on node. (required) + :type image: :class:`NodeImage` or ``str`` + + :keyword auth: Initial authentication information for the + node. (If this is a customer LINUX + image auth will be ignored) + :type auth: :class:`NodeAuthPassword` or ``str`` or + ``None`` + + :keyword ex_description: description for this node (required) + :type ex_description: ``str`` + + :keyword ex_network: Network to create the node within + (required unless using ex_network_domain + or ex_primary_ipv4) + + :type ex_network: :class:`NttCisNetwork` or ``str`` + + :keyword ex_memory_gb: The amount of memory in GB for the + server + :type ex_memory_gb: ``int`` + + :keyword ex_cpu_specification: The spec of CPU to deploy ( + optional) + :type ex_cpu_specification: + :class:`DimensionDataServerCpuSpecification` + + :keyword ex_is_started: Start server after creation? default + true (required) + :type ex_is_started: ``bool`` + + :keyword ex_primary_dns: The node's primary DNS + + :type ex_primary_dns: ``str`` + + :keyword ex_secondary_dns: The node's secondary DNS + + :type ex_secondary_dns: ``str`` + + :return: The newly created :class:`Node`. + :rtype: :class:`Node` + """ + password = None + image_needs_auth = self._image_needs_auth(image) + if image_needs_auth: + if isinstance(auth, basestring): + auth_obj = NodeAuthPassword(password=auth) + password = auth + else: + auth_obj = self._get_and_check_auth(auth) + password = auth_obj.password + + server_elm = ET.Element('deployServer', {'xmlns': TYPES_URN}) + ET.SubElement(server_elm, "name").text = name + ET.SubElement(server_elm, "description").text = ex_description + image_id = self._image_to_image_id(image) + ET.SubElement(server_elm, "imageId").text = image_id + ET.SubElement(server_elm, "start").text = str( + ex_is_started).lower() + if password is not None: + ET.SubElement(server_elm, + "administratorPassword").text = password + + if ex_cpu_specification is not None: + cpu = ET.SubElement(server_elm, "cpu") + cpu.set('speed', ex_cpu_specification.performance) + cpu.set('count', str(ex_cpu_specification.cpu_count)) + cpu.set('coresPerSocket', + str(ex_cpu_specification.cores_per_socket)) + + if ex_memory_gb is not None: + ET.SubElement(server_elm, "memoryGb").text = str(ex_memory_gb) + + if ex_network is not None: + network_elm = ET.SubElement(server_elm, "network") + network_id = self._network_to_network_id(ex_network) + ET.SubElement(network_elm, "networkId").text = network_id + + if ex_primary_dns: + dns_elm = ET.SubElement(server_elm, "primaryDns") + dns_elm.text = ex_primary_dns + + if ex_secondary_dns: + dns_elm = ET.SubElement(server_elm, "secondaryDns") + dns_elm.text = ex_secondary_dns + + response = self.connection.request_with_orgId_api_2( + 'server/deployServer', + method='POST', + data=ET.tostring(server_elm)).object + + node_id = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'serverId': + node_id = info.get('value') + + node = self.ex_get_node_by_id(node_id) + + if image_needs_auth: + if getattr(auth_obj, "generated", False): + node.extra['password'] = auth_obj.password + + return node + + def create_node(self, name, + image, + auth, + ex_network_domain=None, + ex_primary_nic_private_ipv4=None, + ex_primary_nic_vlan=None, + ex_primary_nic_network_adapter=None, + ex_additional_nics=None, + ex_description=None, + ex_disks=None, + ex_cpu_specification=None, + ex_memory_gb=None, + ex_is_started=True, + ex_primary_dns=None, + ex_secondary_dns=None, + ex_ipv4_gateway=None, + ex_microsoft_time_zone=None, + **kwargs + ): + """ + Create a new DimensionData node in MCP2. However, it is still + backward compatible for MCP1 for a limited time. Please consider + using MCP2 datacenter as MCP1 will phase out soon. + + Legacy Create Node for MCP1 datacenter + + >>> from pprint import pprint + >>> from libcloud.compute.types import Provider + >>> from libcloud.compute.base import NodeAuthPassword + >>> from libcloud.compute.providers import get_driver + >>> import libcloud.security + >>> + >>> # Get dimension data driver + >>> libcloud.security.VERIFY_SSL_CERT = False + >>> DimensionData = get_driver(Provider.DIMENSIONDATA) + >>> driver = cls('myusername','mypassword', region='dd-au') + >>> + >>> # Password + >>> root_pw = NodeAuthPassword('password123') + >>> + >>> # Get location + >>> location = driver.ex_get_location_by_id(id='AU1') + >>> + >>> # Get network by location + >>> my_network = driver.list_networks(location=location)[0] + >>> pprint(my_network) + >>> + >>> # Get Image + >>> images = driver.list_images(location=location) + >>> image = images[0] + >>> + >>> node = driver.create_node(name='test_blah_2', image=image, + >>> auth=root_pw, + >>> ex_description='test3 node', + >>> ex_network=my_network, + >>> ex_is_started=False) + >>> pprint(node) + + + Create Node in MCP2 Data Center + + >>> from pprint import pprint + >>> from libcloud.compute.types import Provider + >>> from libcloud.compute.base import NodeAuthPassword + >>> from libcloud.compute.providers import get_driver + >>> import libcloud.security + >>> + >>> # Get dimension data driver + >>> libcloud.security.VERIFY_SSL_CERT = True + >>> cls = get_driver(Provider.DIMENSIONDATA) + >>> driver = cls('myusername','mypassword', region='dd-au') + >>> + >>> # Password + >>> root_pw = NodeAuthPassword('password123') + >>> + >>> # Get location + >>> location = driver.ex_get_location_by_id(id='AU9') + >>> + >>> # Get network domain by location + >>> networkDomainName = "Baas QA" + >>> network_domains = driver.ex_list_network_domains(location=location) + >>> my_network_domain = [d for d in network_domains if d.name == + networkDomainName][0] + >>> + >>> vlan = driver.ex_list_vlans(location=location, + >>> network_domain=my_network_domain)[0] + >>> pprint(vlan) + >>> + >>> # Get Image + >>> images = driver.list_images(location=location) + >>> image = images[0] + >>> + >>> # Create node using vlan instead of private IPv4 + >>> node = driver.create_node(name='test_server_01', image=image, + >>> auth=root_pw, + >>> ex_description='test2 node', + >>> ex_network_domain=my_network_domain, + >>> ex_primary_nic_vlan=vlan, + >>> ex_is_started=False) + >>> + >>> # Option: Create node using private IPv4 instead of vlan + >>> # node = driver.create_node(name='test_server_02', image=image, + >>> # auth=root_pw, + >>> # ex_description='test2 node', + >>> # ex_network_domain=my_network_domain, + >>> # ex_primary_nic_private_ipv4='10.1.1.7', + >>> # ex_is_started=False) + >>> + >>> # Option: Create node using by specifying Network Adapter + >>> # node = driver.create_node(name='test_server_03', image=image, + >>> # auth=root_pw, + >>> # ex_description='test2 node', + >>> # ex_network_domain=my_network_domain, + >>> # ex_primary_nic_vlan=vlan, + >>> # ex_primary_nic_network_adapter='E1000', + >>> # ex_is_started=False) + >>> + + :keyword name: (required) String with a name for this new node + :type name: ``str`` + + :keyword image: (required) OS Image to boot on node. + :type image: :class:`NodeImage` or ``str`` + + :keyword auth: Initial authentication information for the + node. (If this is a customer LINUX + image auth will be ignored) + :type auth: :class:`NodeAuthPassword` or ``str`` or ``None`` + + :keyword ex_description: (optional) description for this node + :type ex_description: ``str`` + + + :keyword ex_network_domain: (required) Network Domain or Network + Domain ID to create the node + :type ex_network_domain: :class:`DimensionDataNetworkDomain` + or ``str`` + + :keyword ex_primary_nic_private_ipv4: Provide private IPv4. Ignore + if ex_primary_nic_vlan is + provided. Use one or the + other. Not both. + :type ex_primary_nic_private_ipv4: :``str`` + + :keyword ex_primary_nic_vlan: Provide VLAN for the node if + ex_primary_nic_private_ipv4 NOT + provided. One or the other. Not both. + :type ex_primary_nic_vlan: :class: DimensionDataVlan or ``str`` + + :keyword ex_primary_nic_network_adapter: (Optional) Default value + for the Operating System + will be used if leave + empty. Example: "E1000". + :type ex_primary_nic_network_adapter: :``str`` + + :keyword ex_additional_nics: (optional) List + :class:'NttCisNic' or None + :type ex_additional_nics: ``list`` of :class:'NttCisNic' + or ``str`` + + :keyword ex_memory_gb: (optional) The amount of memory in GB for + the server Can be used to override the + memory value inherited from the source + Server Image. + :type ex_memory_gb: ``int`` + + :keyword ex_cpu_specification: (optional) The spec of CPU to deploy + :type ex_cpu_specification: + :class:`DimensionDataServerCpuSpecification` + + :keyword ex_is_started: (required) Start server after creation. + Default is set to true. + :type ex_is_started: ``bool`` + + :keyword ex_primary_dns: (Optional) The node's primary DNS + :type ex_primary_dns: ``str`` + + :keyword ex_secondary_dns: (Optional) The node's secondary DNS + :type ex_secondary_dns: ``str`` + + :keyword ex_ipv4_gateway: (Optional) IPv4 address in dot-decimal + notation, which will be used as the + Primary NIC gateway instead of the default + gateway assigned by the system. If + ipv4Gateway is provided it does not have + to be on the VLAN of the Primary NIC + but MUST be reachable or the Guest OS + will not be configured correctly. + :type ex_ipv4_gateway: ``str`` + + :keyword ex_disks: (optional) Dimensiondata disks. Optional disk + elements can be used to define the disk speed + that each disk on the Server; inherited from the + source Server Image will be deployed to. It is + not necessary to include a diskelement for every + disk; only those that you wish to set a disk + speed value for. Note that scsiId 7 cannot be + used.Up to 13 disks can be present in addition to + the required OS disk on SCSI ID 0. Refer to + https://docs.mcp-services.net/x/UwIu for disk + + :type ex_disks: List or tuple of :class:'DimensionDataServerDisk` + + :keyword ex_microsoft_time_zone: (optional) For use with + Microsoft Windows source Server Images only. For the exact + value to use please refer to the table of time zone + indexes in the following Microsoft Technet + documentation. If none is supplied, the default time + zone for the data center geographic region will be used. + :type ex_microsoft_time_zone: `str`` + + + :return: The newly created :class:`Node`. + :rtype: :class:`Node` + """ + + # Neither legacy MCP1 network nor MCP2 network domain provided + if ex_network_domain is None and 'ex_network' not in kwargs: + raise ValueError('You must provide either ex_network_domain ' + 'for MCP2 or ex_network for legacy MCP1') + + # Ambiguous parameter provided. Can't determine if it is MCP 1 or 2. + if ex_network_domain is not None and 'ex_network' in kwargs: + raise ValueError('You can only supply either ' + 'ex_network_domain ' + 'for MCP2 or ex_network for legacy MCP1') + + # Set ex_is_started to False by default if none bool data type provided + if not isinstance(ex_is_started, bool): + ex_is_started = True + + # Handle MCP1 legacy + if 'ex_network' in kwargs: + new_node = self._create_node_mcp1( + name=name, image=image, auth=auth, + ex_network=kwargs.get("ex_network"), + ex_description=ex_description, + ex_memory_gb=ex_memory_gb, + ex_cpu_specification=ex_cpu_specification, + ex_is_started=ex_is_started, + ex_primary_ipv4=ex_primary_nic_private_ipv4, + ex_disks=ex_disks, + ex_additional_nics_vlan=kwargs.get("ex_additional_nics_vlan"), + ex_additional_nics_ipv4=kwargs.get("ex_additional_nics_ipv4"), + ex_primary_dns=ex_primary_dns, + ex_secondary_dns=ex_secondary_dns + ) + else: + # Handle MCP2 legacy. CaaS api 2.2 or earlier + if 'ex_vlan' in kwargs: + ex_primary_nic_vlan = kwargs.get('ex_vlan') + + if 'ex_primary_ipv4' in kwargs: + ex_primary_nic_private_ipv4 = kwargs.get( + 'ex_primary_ipv4') + + additional_nics = [] + + if 'ex_additional_nics_vlan' in kwargs: + vlans = kwargs.get('ex_additional_nics_vlan') + if isinstance(vlans, (list, tuple)): + for v in vlans: + add_nic =NttCisNic(vlan=v) + additional_nics.append(add_nic) + else: + raise TypeError("ex_additional_nics_vlan must " + "be None or a tuple/list") + + if 'ex_additional_nics_ipv4' in kwargs: + ips = kwargs.get('ex_additional_nics_ipv4') + + if isinstance(ips, (list, tuple)): + for ip in ips: + add_nic = NttCisNic(private_ip_v4=ip) + additional_nics.append(add_nic) + else: + if ips is not None: + raise TypeError("ex_additional_nics_ipv4 must " + "be None or a tuple/list") + + if ('ex_additional_nics_vlan' in kwargs or + 'ex_additional_nics_ipv4' in kwargs): + ex_additional_nics = additional_nics + + # Handle MCP2 latest. CaaS API 2.3 onwards + if ex_network_domain is None: + raise ValueError("ex_network_domain must be specified") + + password = None + image_needs_auth = self._image_needs_auth(image) + if image_needs_auth: + if isinstance(auth, basestring): + auth_obj = NodeAuthPassword(password=auth) + password = auth + else: + auth_obj = self._get_and_check_auth(auth) + password = auth_obj.password + + server_elm = ET.Element('deployServer', {'xmlns': TYPES_URN}) + ET.SubElement(server_elm, "name").text = name + ET.SubElement(server_elm, "description").text = ex_description + image_id = self._image_to_image_id(image) + ET.SubElement(server_elm, "imageId").text = image_id + ET.SubElement(server_elm, "start").text = str( + ex_is_started).lower() + if password is not None: + ET.SubElement(server_elm, + "administratorPassword").text = password + + if ex_cpu_specification is not None: + cpu = ET.SubElement(server_elm, "cpu") + cpu.set('speed', ex_cpu_specification.performance) + cpu.set('count', str(ex_cpu_specification.cpu_count)) + cpu.set('coresPerSocket', + str(ex_cpu_specification.cores_per_socket)) + + if ex_memory_gb is not None: + ET.SubElement(server_elm, "memoryGb").text = str(ex_memory_gb) + + if (ex_primary_nic_private_ipv4 is None and + ex_primary_nic_vlan is None): + raise ValueError("Missing argument. Either " + "ex_primary_nic_private_ipv4 or " + "ex_primary_nic_vlan " + "must be specified.") + + if (ex_primary_nic_private_ipv4 is not None and + ex_primary_nic_vlan is not None): + raise ValueError("Either ex_primary_nic_private_ipv4 or " + "ex_primary_nic_vlan " + "be specified. Not both.") + + network_elm = ET.SubElement(server_elm, "networkInfo") + + net_domain_id = self._network_domain_to_network_domain_id( + ex_network_domain) + + network_elm.set('networkDomainId', net_domain_id) + + pri_nic = ET.SubElement(network_elm, 'primaryNic') + + if ex_primary_nic_private_ipv4 is not None: + ET.SubElement(pri_nic, + 'privateIpv4').text = ex_primary_nic_private_ipv4 + + if ex_primary_nic_vlan is not None: + vlan_id = self._vlan_to_vlan_id(ex_primary_nic_vlan) + ET.SubElement(pri_nic, 'vlanId').text = vlan_id + + if ex_primary_nic_network_adapter is not None: + ET.SubElement(pri_nic, + "networkAdapter").text = \ + ex_primary_nic_network_adapter + + if isinstance(ex_additional_nics, (list, tuple)): + for nic in ex_additional_nics: + additional_nic = ET.SubElement(network_elm, + 'additionalNic') + + if (nic.private_ip_v4 is None and + nic.vlan is None): + raise ValueError("Either a vlan or private_ip_v4 " + "must be specified for each " + "additional nic.") + + if (nic.private_ip_v4 is not None and + nic.vlan is not None): + raise ValueError("Either a vlan or private_ip_v4 " + "must be specified for each " + "additional nic. Not both.") + + if nic.private_ip_v4 is not None: + ET.SubElement(additional_nic, + 'privateIpv4').text = nic.private_ip_v4 + + if nic.vlan is not None: + vlan_id = self._vlan_to_vlan_id(nic.vlan) + ET.SubElement(additional_nic, 'vlanId').text = vlan_id + + if nic.network_adapter_name is not None: + ET.SubElement(additional_nic, + "networkAdapter").text = \ + nic.network_adapter_name + elif ex_additional_nics is not None: + raise TypeError( + "ex_additional_NICs must be None or tuple/list") + + if ex_primary_dns: + dns_elm = ET.SubElement(server_elm, "primaryDns") + dns_elm.text = ex_primary_dns + + if ex_secondary_dns: + dns_elm = ET.SubElement(server_elm, "secondaryDns") + dns_elm.text = ex_secondary_dns + + if ex_ipv4_gateway: + ET.SubElement(server_elm, "ipv4Gateway").text = ex_ipv4_gateway + + if isinstance(ex_disks, (list, tuple)): + for disk in ex_disks: + disk_elm = ET.SubElement(server_elm, 'disk') + disk_elm.set('scsiId', disk.scsi_id) + disk_elm.set('speed', disk.speed) + elif ex_disks is not None: + raise TypeError("ex_disks must be None or tuple/list") + + if ex_microsoft_time_zone: + ET.SubElement(server_elm, + "microsoftTimeZone").text = \ + ex_microsoft_time_zone + + response = self.connection.request_with_orgId_api_2( + 'server/deployServer', + method='POST', + data=ET.tostring(server_elm)).object + + node_id = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'serverId': + node_id = info.get('value') + + new_node = self.ex_get_node_by_id(node_id) + + if image_needs_auth: + if getattr(auth_obj, "generated", False): + new_node.extra['password'] = auth_obj.password + + return new_node + + def destroy_node(self, node): + """ + Deletes a node, node must be stopped before deletion + + + :keyword node: The node to delete + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('deleteServer', + {'xmlns': TYPES_URN, 'id': node.id}) + body = self.connection.request_with_orgId_api_2( + 'server/deleteServer', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def reboot_node(self, node): + """ + Reboots a node by requesting the OS restart via the hypervisor + + + :keyword node: The node to reboot + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('rebootServer', + {'xmlns': TYPES_URN, 'id': node.id}) + body = self.connection.request_with_orgId_api_2( + 'server/rebootServer', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def list_nodes(self, ex_location=None, ex_name=None, + ex_ipv6=None, ex_ipv4=None, ex_vlan=None, + ex_image=None, ex_deployed=None, + ex_started=None, ex_state=None, + ex_network=None, ex_network_domain=None): + """ + List nodes deployed for your organization. + + :keyword ex_location: Filters the node list to nodes that are + located in this location + :type ex_location: :class:`NodeLocation` or ``str`` + + :keyword ex_name: Filters the node list to nodes that have this name + :type ex_name ``str`` + + :keyword ex_ipv6: Filters the node list to nodes that have this + ipv6 address + :type ex_ipv6: ``str`` + + :keyword ex_ipv4: Filters the node list to nodes that have this + ipv4 address + :type ex_ipv4: ``str`` + + :keyword ex_vlan: Filters the node list to nodes that are in this VLAN + :type ex_vlan: :class:`DimensionDataVlan` or ``str`` + + :keyword ex_image: Filters the node list to nodes that have this image + :type ex_image: :class:`NodeImage` or ``str`` + + :keyword ex_deployed: Filters the node list to nodes that are + deployed or not + :type ex_deployed: ``bool`` + + :keyword ex_started: Filters the node list to nodes that are + started or not + :type ex_started: ``bool`` + + :keyword ex_state: Filters the node list by nodes that are in + this state + :type ex_state: ``str`` + + :keyword ex_network: Filters the node list to nodes in this network + :type ex_network: :class:`NttCisNetwork` or ``str`` + + :keyword ex_network_domain: Filters the node list to nodes in this + network domain + :type ex_network_domain: :class:`NttCisNetworkDomain` + or ``str`` + + :return: a list of `Node` objects + :rtype: ``list`` of :class:`Node` + """ + node_list = [] + for nodes in self.ex_list_nodes_paginated( + location=ex_location, + name=ex_name, ipv6=ex_ipv6, + ipv4=ex_ipv4, vlan=ex_vlan, + image=ex_image, deployed=ex_deployed, + started=ex_started, state=ex_state, + network=ex_network, + network_domain=ex_network_domain): + node_list.extend(nodes) + + return node_list + + def list_images(self, location=None): + """ + List images available + + Note: Currently only returns the default 'base OS images' + provided by NTTCIS. Customer images (snapshots) + use ex_list_customer_images + + :keyword ex_location: Filters the node list to nodes that are + located in this location + :type ex_location: :class:`NodeLocation` or ``str`` + + :return: List of images available + :rtype: ``list`` of :class:`NodeImage` + """ + params = {} + if location is not None: + params['datacenterId'] = self._location_to_location_id(location) + + return self._to_images( + self.connection.request_with_orgId_api_2( + 'image/osImage', + params=params) + .object) + + def list_sizes(self, location=None): + """ + return a list of available sizes + Currently, the size of the node is dictated by the chosen OS base + image, they cannot be set explicitly. + + @inherits: :class:`NodeDriver.list_sizes` + """ + return [ + NodeSize(id=1, + name="default", + ram=0, + disk=0, + bandwidth=0, + price=0, + driver=self.connection.driver), + ] + + def list_locations(self, ex_id=None): + """ + List locations (datacenters) available for instantiating servers and + networks. + + :keyword ex_id: Filters the location list to this id + :type ex_id: ``str`` + + :return: List of locations + :rtype: ``list`` of :class:`NodeLocation` + """ + params = {} + if ex_id is not None: + params['id'] = ex_id + + return self._to_locations( + self.connection + .request_with_orgId_api_2( + 'infrastructure/datacenter', + params=params + ).object + ) + + def list_networks(self, location=None): + """ + List networks deployed across all data center locations for your + organization. The response includes the location of each network. + + + :keyword location: The location + :type location: :class:`NodeLocation` or ``str`` + + :return: a list of NttCisNetwork objects + :rtype: ``list`` of :class:`NttCisNetwork` + """ + url_ext = '' + if location is not None: + url_ext = '/' + self._location_to_location_id(location) + + return self._to_networks( + self.connection + .request_with_orgId_api_1('networkWithLocation%s' % url_ext) + .object) + + def import_image(self, ovf_package_name, name, + cluster_id=None, datacenter_id=None, description=None, + is_guest_os_customization=None, + tagkey_name_value_dictionaries=None): + """ + Import image + + :param ovf_package_name: Image OVF package name + :type ovf_package_name: ``str`` + + :param name: Image name + :type name: ``str`` + + :param cluster_id: Provide either cluster_id or datacenter_id + :type cluster_id: ``str`` + + :param datacenter_id: Provide either cluster_id or datacenter_id + :type datacenter_id: ``str`` + + :param description: Optional. Description of image + :type description: ``str`` + + :param is_guest_os_customization: Optional. true for NGOC image + :type is_guest_os_customization: ``bool`` + + :param tagkey_name_value_dictionaries: Optional tagkey name value dict + :type tagkey_name_value_dictionaries: dictionaries + + :return: Return true if successful + :rtype: ``bool`` + """ + + # Unsupported for version lower than 2.4 + if LooseVersion(self.connection.active_api_version) < LooseVersion( + '2.4'): + raise Exception("import image is feature is NOT supported in " + "api version earlier than 2.4") + elif cluster_id is None and datacenter_id is None: + raise ValueError("Either cluster_id or datacenter_id must be " + "provided") + elif cluster_id is not None and datacenter_id is not None: + raise ValueError("Cannot accept both cluster_id and " + "datacenter_id. Please provide either one") + else: + import_image_elem = ET.Element( + 'urn:importImage', + { + 'xmlns:urn': TYPES_URN, + }) + + ET.SubElement( + import_image_elem, + 'urn:ovfPackage' + ).text = ovf_package_name + + ET.SubElement( + import_image_elem, + 'urn:name' + ).text = name + + if description is not None: + ET.SubElement( + import_image_elem, + 'urn:description' + ).text = description + + if cluster_id is not None: + ET.SubElement( + import_image_elem, + 'urn:clusterId' + ).text = cluster_id + else: + ET.SubElement( + import_image_elem, + 'urn:datacenterId' + ).text = datacenter_id + + if is_guest_os_customization is not None: + ET.SubElement( + import_image_elem, + 'urn:guestOsCustomization' + ).text = is_guest_os_customization + + if len(tagkey_name_value_dictionaries) > 0: + for k, v in tagkey_name_value_dictionaries.items(): + tag_elem = ET.SubElement( + import_image_elem, + 'urn:tag') + ET.SubElement(tag_elem, + 'urn:tagKeyName').text = k + + if v is not None: + ET.SubElement(tag_elem, + 'urn:value').text = v + + response = self.connection.request_with_orgId_api_2( + 'image/importImage', + method='POST', + data=ET.tostring(import_image_elem)).object + + response_code = findtext(response, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_list_nodes_paginated(self, name=None, location=None, + ipv6=None, ipv4=None, vlan=None, + image=None, deployed=None, started=None, + state=None, network=None, network_domain=None): + """ + Return a generator which yields node lists in pages + + :keyword location: Filters the node list to nodes that are + located in this location + :type location: :class:`NodeLocation` or ``str`` + + :keyword name: Filters the node list to nodes that have this name + :type name ``str`` + + :keyword ipv6: Filters the node list to nodes that have this + ipv6 address + :type ipv6: ``str`` + + :keyword ipv4: Filters the node list to nodes that have this + ipv4 address + :type ipv4: ``str`` + + :keyword vlan: Filters the node list to nodes that are in this VLAN + :type vlan: :class:`NttCisVlan` or ``str`` + + :keyword image: Filters the node list to nodes that have this image + :type image: :class:`NodeImage` or ``str`` + + :keyword deployed: Filters the node list to nodes that are + deployed or not + :type deployed: ``bool`` + + :keyword started: Filters the node list to nodes that are + started or not + :type started: ``bool`` + + :keyword state: Filters the node list to nodes that are in + this state + :type state: ``str`` + + :keyword network: Filters the node list to nodes in this network + :type network: :class:`NttCisNetwork` or ``str`` + + :keyword network_domain: Filters the node list to nodes in this + network domain + :type network_domain: :class:`NttCisNetworkDomain` + or ``str`` + + :return: a list of `Node` objects + :rtype: ``generator`` of `list` of :class:`Node` + """ + + params = {} + if location is not None: + params['datacenterId'] = self._location_to_location_id(location) + if ipv6 is not None: + params['ipv6'] = ipv6 + if ipv4 is not None: + params['privateIpv4'] = ipv4 + if state is not None: + params['state'] = state + if started is not None: + params['started'] = started + if deployed is not None: + params['deployed'] = deployed + if name is not None: + params['name'] = name + if network_domain is not None: + params['networkDomainId'] = \ + self._network_domain_to_network_domain_id(network_domain) + if network is not None: + params['networkId'] = self._network_to_network_id(network) + if vlan is not None: + params['vlanId'] = self._vlan_to_vlan_id(vlan) + if image is not None: + params['sourceImageId'] = self._image_to_image_id(image) + + nodes_obj = self._list_nodes_single_page(params) + yield self._to_nodes(nodes_obj) + + while nodes_obj.get('pageCount') >= nodes_obj.get('pageSize'): + params['pageNumber'] = int(nodes_obj.get('pageNumber')) + 1 + nodes_obj = self._list_nodes_single_page(params) + yield self._to_nodes(nodes_obj) + + def ex_start_node(self, node): + """ + Powers on an existing deployed server + + :param node: Node which should be used + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('startServer', + {'xmlns': TYPES_URN, 'id': node.id}) + body = self.connection.request_with_orgId_api_2( + 'server/startServer', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_shutdown_graceful(self, node): + """ + This function will attempt to "gracefully" stop a server by + initiating a shutdown sequence within the guest operating system. + A successful response on this function means the system has + successfully passed the request into the operating system. + + :param node: Node which should be used + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('shutdownServer', + {'xmlns': TYPES_URN, 'id': node.id}) + body = self.connection.request_with_orgId_api_2( + 'server/shutdownServer', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_power_off(self, node): + """ + This function will abruptly power-off a server. Unlike + ex_shutdown_graceful, success ensures the node will stop but some OS + and application configurations may be adversely affected by the + equivalent of pulling the power plug out of the machine. + + :param node: Node which should be used + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('powerOffServer', + {'xmlns': TYPES_URN, 'id': node.id}) + + try: + body = self.connection.request_with_orgId_api_2( + 'server/powerOffServer', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + except (NttCisAPIException, NameError, BaseHTTPError): + r = self.ex_get_node_by_id(node.id) + response_code = r.state.upper() + + return response_code in ['IN_PROGRESS', 'OK', 'STOPPED', 'STOPPING'] + + def ex_reset(self, node): + """ + This function will abruptly reset a server. Unlike + reboot_node, success ensures the node will restart but some OS + and application configurations may be adversely affected by the + equivalent of pulling the power plug out of the machine. + + :param node: Node which should be used + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('resetServer', + {'xmlns': TYPES_URN, 'id': node.id}) + body = self.connection.request_with_orgId_api_2( + 'server/resetServer', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_update_vm_tools(self, node): + """ + This function triggers an update of the VMware Tools + software running on the guest OS of a Server. + + :param node: Node which should be used + :type node: :class:`Node` + + :rtype: ``bool`` + """ + request_elm = ET.Element('updateVmwareTools', + {'xmlns': TYPES_URN, 'id': node.id}) + body = self.connection.request_with_orgId_api_2( + 'server/updateVmwareTools', + method='POST', + data=ET.tostring(request_elm)).object + response_code = findtext(body, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_update_node(self, node, name=None, description=None, + cpu_count=None, ram_mb=None): + """ + Update the node, the name, CPU or RAM + + :param node: Node which should be used + :type node: :class:`Node` + + :param name: The new name (optional) + :type name: ``str`` + + :param description: The new description (optional) + :type description: ``str`` + + :param cpu_count: The new CPU count (optional) + :type cpu_count: ``int`` + + :param ram_mb: The new Memory in MB (optional) + :type ram_mb: ``int`` + + :rtype: ``bool`` + """ + data = {} + if name is not None: + data['name'] = name + if description is not None: + data['description'] = description + if cpu_count is not None: + data['cpuCount'] = str(cpu_count) + if ram_mb is not None: + data['memory'] = str(ram_mb) + body = self.connection.request_with_orgId_api_1( + 'server/%s' % (node.id), + method='POST', + data=urlencode(data, True)).object + response_code = findtext(body, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def ex_create_anti_affinity_rule(self, node_list): + """ + Create an anti affinity rule given a list of nodes + Anti affinity rules ensure that servers will not reside + on the same VMware ESX host + + :param node_list: The list of nodes to create a rule for + :type node_list: ``list`` of :class:`Node` or + ``list`` of ``str`` + + :rtype: ``bool`` + """ + if not isinstance(node_list, (list, tuple)): + raise TypeError("Node list must be a list or a tuple.") + anti_affinity_xml_request = ET.Element('NewAntiAffinityRule', + {'xmlns': SERVER_NS}) + for node in node_list: + ET.SubElement(anti_affinity_xml_request, 'serverId').text = \ + self._node_to_node_id(node) + result = self.connection.request_with_orgId_api_1( + 'antiAffinityRule', + method='POST', + data=ET.tostring(anti_affinity_xml_request)).object + response_code = findtext(result, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def ex_delete_anti_affinity_rule(self, anti_affinity_rule): + """ + Remove anti affinity rule + + :param anti_affinity_rule: The anti affinity rule to delete + :type anti_affinity_rule: :class:`NttCisAntiAffinityRule` or + ``str`` + + :rtype: ``bool`` + """ + rule_id = self._anti_affinity_rule_to_anti_affinity_rule_id( + anti_affinity_rule) + result = self.connection.request_with_orgId_api_1( + 'antiAffinityRule/%s?delete' % (rule_id), + method='GET').object + response_code = findtext(result, 'result', GENERAL_NS) + return response_code in ['IN_PROGRESS', 'SUCCESS'] + + def ex_list_anti_affinity_rules(self, network=None, network_domain=None, + node=None, filter_id=None, + filter_state=None): + """ + List anti affinity rules for a network, network domain, or node + + :param network: The network to list anti affinity rules for + One of network, network_domain, or node is required + :type network: :class:`NttCisNetwork` or ``str`` + + :param network_domain: The network domain to list anti affinity rules + One of network, network_domain, + or node is required + :type network_domain: :class:`NttCisNetworkDomain` or ``str`` + + :param node: The node to list anti affinity rules for + One of network, netwok_domain, or node is required + :type node: :class:`Node` or ``str`` + + :param filter_id: This will allow you to filter the rules + by this node id + :type filter_id: ``str`` + + :type filter_state: This will allow you to filter rules by + node state (i.e. NORMAL) + :type filter_state: ``str`` + + :rtype: ``list`` of :class:NttCisAntiAffinityRule` + """ + not_none_arguments = [key + for key in (network, network_domain, node) + if key is not None] + if len(not_none_arguments) != 1: + raise ValueError("One and ONLY one of network, " + "network_domain, or node must be set") + + params = {} + if network_domain is not None: + params['networkDomainId'] = \ + self._network_domain_to_network_domain_id(network_domain) + if network is not None: + params['networkId'] = \ + self._network_to_network_id(network) + if node is not None: + params['serverId'] = \ + self._node_to_node_id(node) + if filter_id is not None: + params['id'] = filter_id + if filter_state is not None: + params['state'] = filter_state + + paged_result = self.connection.paginated_request_with_orgId_api_2( + 'server/antiAffinityRule', + method='GET', + params=params + ) + + rules = [] + for result in paged_result: + rules.extend(self._to_anti_affinity_rules(result)) + return rules + + def ex_attach_node_to_vlan(self, node, vlan=None, private_ipv4=None): + """ + Attach a node to a VLAN by adding an additional NIC to + the node on the target VLAN. The IP will be automatically + assigned based on the VLAN IP network space. Alternatively, provide + a private IPv4 address instead of VLAN information, and this will + be assigned to the node on corresponding NIC. + + :param node: Node which should be used + :type node: :class:`Node` + + :param vlan: VLAN to attach the node to + (required unless private_ipv4) + :type vlan: :class:`NttCisVlan` + + :keyword private_ipv4: Private nic IPv4 Address + (required unless vlan) + :type private_ipv4: ``str`` + + :rtype: ``bool`` + """ + request = ET.Element('addNic', + {'xmlns': TYPES_URN}) + ET.SubElement(request, 'serverId').text = node.id + nic = ET.SubElement(request, 'nic') + + if vlan is not None: + ET.SubElement(nic, 'vlanId').text = vlan.id + elif private_ipv4 is not None: + ET.SubElement(nic, 'privateIpv4').text = private_ipv4 + else: + raise ValueError("One of vlan or primary_ipv4 " + "must be specified") + + response = self.connection.request_with_orgId_api_2( + 'server/addNic', + method='POST', + data=ET.tostring(request)).object + response_code = findtext(response, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_destroy_nic(self, nic_id): + """ + Remove a NIC on a node, removing the node from a VLAN + + :param nic_id: The identifier of the NIC to remove + :type nic_id: ``str`` + + :rtype: ``bool`` + """ + request = ET.Element('removeNic', + {'xmlns': TYPES_URN, + 'id': nic_id}) + + response = self.connection.request_with_orgId_api_2( + 'server/removeNic', + method='POST', + data=ET.tostring(request)).object + response_code = findtext(response, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_list_networks(self, location=None): + """ + List networks deployed across all data center locations for your + organization. The response includes the location of each network. + + :param location: The target location + :type location: :class:`NodeLocation` or ``str`` + + :return: a list of NttCisaNetwork objects + :rtype: ``list`` of :class:`NttCisNetwork` + """ + return self.list_networks(location=location) + + def ex_create_network(self, location, name, description=None): + """ + Create a new network in an MCP 1.0 location + + :param location: The target location (MCP1) + :type location: :class:`NodeLocation` or ``str`` + + :param name: The name of the network + :type name: ``str`` + + :param description: Additional description of the network + :type description: ``str`` + + :return: A new instance of `NttCisNetwork` + :rtype: Instance of :class:`NttCisNetwork` + """ + network_location = self._location_to_location_id(location) + + create_node = ET.Element('NewNetworkWithLocation', + {'xmlns': NETWORK_NS}) + ET.SubElement(create_node, "name").text = name + if description is not None: + ET.SubElement(create_node, "description").text = description + ET.SubElement(create_node, "location").text = network_location + + self.connection.request_with_orgId_api_1( + 'networkWithLocation', + method='POST', + data=ET.tostring(create_node)) + + # MCP1 API does not return the ID, but name is unique for location + network = list( + filter(lambda x: x.name == name, + self.ex_list_networks(location)))[0] + + return network + + def ex_delete_network(self, network): + """ + Delete a network from an MCP 1 data center + + :param network: The network to delete + :type network: :class:`NttCisNetwork` + + :rtype: ``bool`` + """ + response = self.connection.request_with_orgId_api_1( + 'network/%s?delete' % network.id, + method='GET').object + response_code = findtext(response, 'result', GENERAL_NS) + return response_code == "SUCCESS" + + def ex_rename_network(self, network, new_name): + """ + Rename a network in MCP 1 data center + + :param network: The network to rename + :type network: :class:`NttCisNetwork` + + :param new_name: The new name of the network + :type new_name: ``str`` + + :rtype: ``bool`` + """ + response = self.connection.request_with_orgId_api_1( + 'network/%s' % network.id, + method='POST', + data='name=%s' % new_name).object + response_code = findtext(response, 'result', GENERAL_NS) + return response_code == "SUCCESS" + + def ex_get_network_domain(self, network_domain_id): + """ + Get an individual Network Domain, by identifier + + :param network_domain_id: The identifier of the network domain + :type network_domain_id: ``str`` + + :rtype: :class:`NttCisNetworkDomain` + """ + locations = self.list_locations() + net = self.connection.request_with_orgId_api_2( + 'network/networkDomain/%s' % network_domain_id).object + return self._to_network_domain(net, locations) + + def ex_list_network_domains(self, location=None, name=None, + service_plan=None, state=None): + """ + List networks domains deployed across all data center locations domain. + + for your organization. + The response includes the location of each network + :param location: Only network domains in the location (optional) + :type location: :class:`NodeLocation` or ``str`` + + :param name: Only network domains of this name (optional) + :type name: ``str`` + + :param service_plan: Only network domains of this type (optional) + :type service_plan: ``str`` + + :param state: Only network domains in this state (optional) + :type state: ``str`` + + :return: a list of `NttCisNetwork` objects + :rtype: ``list`` of :class:`NttCisNetwork` + """ + params = {} + if location is not None: + params['datacenterId'] = self._location_to_location_id(location) + if name is not None: + params['name'] = name + if service_plan is not None: + params['type'] = service_plan + if state is not None: + params['state'] = state + + response = self.connection \ + .request_with_orgId_api_2('network/networkDomain', + params=params).object + return self._to_network_domains(response) + + def ex_create_network_domain(self, location, name, service_plan, + description=None): + """ + Deploy a new network domain to a data center + + :param location: The data center to list + :type location: :class:`NodeLocation` or ``str`` + + :param name: The name of the network domain to create + :type name: ``str`` + + :param service_plan: The service plan, either "ESSENTIALS" + or "ADVANCED" + :type service_plan: ``str`` + + :param description: An additional description of + the network domain + :type description: ``str`` + + :return: an instance of `NttCisNetworkDomain` + :rtype: :class:`NttCisNetworkDomain` + """ + create_node = ET.Element('deployNetworkDomain', {'xmlns': TYPES_URN}) + ET.SubElement( + create_node, + "datacenterId" + ).text = self._location_to_location_id(location) + + ET.SubElement(create_node, "name").text = name + if description is not None: + ET.SubElement(create_node, "description").text = description + ET.SubElement(create_node, "type").text = service_plan + + response = self.connection.request_with_orgId_api_2( + 'network/deployNetworkDomain', + method='POST', + data=ET.tostring(create_node)).object + + network_domain_id = None + + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'networkDomainId': + network_domain_id = info.get('value') + + return NttCisNetworkDomain( + id=network_domain_id, + name=name, + description=description, + location=location, + status=NodeState.RUNNING, + plan=service_plan + ) + + def ex_update_network_domain(self, network_domain): + """ + Update the properties of a network domain + + :param network_domain: The network domain with updated properties + :type network_domain: :class:`NttCisNetworkDomain` + + :return: an instance of `NttCisNetworkDomain` + :rtype: :class:`NttCisNetworkDomain` + """ + edit_node = ET.Element('editNetworkDomain', {'xmlns': TYPES_URN}) + edit_node.set('id', network_domain.id) + ET.SubElement(edit_node, "name").text = network_domain.name + if network_domain.description is not None: + ET.SubElement(edit_node, "description").text \ + = network_domain.description + ET.SubElement(edit_node, "type").text = network_domain.plan + + self.connection.request_with_orgId_api_2( + 'network/editNetworkDomain', + method='POST', + data=ET.tostring(edit_node)).object + + return network_domain + + def ex_delete_network_domain(self, network_domain): + """ + Delete a network domain + + :param network_domain: The network domain to delete + :type network_domain: :class:`NttCisNetworkDomain` + + :rtype: ``bool`` + """ + delete_node = ET.Element('deleteNetworkDomain', {'xmlns': TYPES_URN}) + delete_node.set('id', network_domain.id) + result = self.connection.request_with_orgId_api_2( + 'network/deleteNetworkDomain', + method='POST', + data=ET.tostring(delete_node)).object + + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_create_vlan(self, + network_domain, + name, + private_ipv4_base_address, + description=None, + private_ipv4_prefix_size=24): + """ + Deploy a new VLAN to a network domain + + :param network_domain: The network domain to add the VLAN to + :type network_domain: :class:`NttCisNetworkDomain` + + :param name: The name of the VLAN to create + :type name: ``str`` + + :param private_ipv4_base_address: The base IPv4 address + e.g. 192.168.1.0 + :type private_ipv4_base_address: ``str`` + + :param description: An additional description of the VLAN + :type description: ``str`` + + :param private_ipv4_prefix_size: The size of the IPv4 + address space, e.g 24 + :type private_ipv4_prefix_size: ``int`` + + :return: an instance of `NttCisVlan` + :rtype: :class:`NttCisVlan` + """ + create_node = ET.Element('deployVlan', {'xmlns': TYPES_URN}) + ET.SubElement(create_node, "networkDomainId").text = network_domain.id + ET.SubElement(create_node, "name").text = name + if description is not None: + ET.SubElement(create_node, "description").text = description + ET.SubElement(create_node, "privateIpv4BaseAddress").text = \ + private_ipv4_base_address + ET.SubElement(create_node, "privateIpv4PrefixSize").text = \ + str(private_ipv4_prefix_size) + + response = self.connection.request_with_orgId_api_2( + 'network/deployVlan', + method='POST', + data=ET.tostring(create_node)).object + + vlan_id = None + + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'vlanId': + vlan_id = info.get('value') + + return self.ex_get_vlan(vlan_id) + + def ex_get_vlan(self, vlan_id): + """ + Get a single VLAN, by it's identifier + + :param vlan_id: The identifier of the VLAN + :type vlan_id: ``str`` + + :return: an instance of `NttCisVlan` + :rtype: :class:`NttCisVlan` + """ + locations = self.list_locations() + vlan = self.connection.request_with_orgId_api_2( + 'network/vlan/%s' % vlan_id).object + return self._to_vlan(vlan, locations) + + def ex_update_vlan(self, vlan): + """ + Updates the properties of the given VLAN + Only name and description are updated + + :param vlan: The VLAN to update + :type vlan: :class:`NttCisetworkDomain` + + :return: an instance of `NttCisVlan` + :rtype: :class:`NttCisVlan` + """ + edit_node = ET.Element('editVlan', {'xmlns': TYPES_URN}) + edit_node.set('id', vlan.id) + ET.SubElement(edit_node, "name").text = vlan.name + if vlan.description is not None: + ET.SubElement(edit_node, "description").text \ + = vlan.description + + self.connection.request_with_orgId_api_2( + 'network/editVlan', + method='POST', + data=ET.tostring(edit_node)).object + + return vlan + + def ex_expand_vlan(self, vlan): + """ + Expands the VLAN to the prefix size in private_ipv4_range_size + The expansion will + not be permitted if the proposed IP space overlaps with an + already deployed VLANs IP space. + + :param vlan: The VLAN to update + :type vlan: :class:`NttCisNetworkDomain` + + :return: an instance of `NttCisVlan` + :rtype: :class:`NttCisVlan` + """ + edit_node = ET.Element('expandVlan', {'xmlns': TYPES_URN}) + edit_node.set('id', vlan.id) + ET.SubElement(edit_node, "privateIpv4PrefixSize").text =\ + vlan.private_ipv4_range_size + + self.connection.request_with_orgId_api_2( + 'network/expandVlan', + method='POST', + data=ET.tostring(edit_node)).object + + return vlan + + def ex_delete_vlan(self, vlan): + """ + Deletes an existing VLAN + + :param vlan: The VLAN to delete + :type vlan: :class:`DNttCisNetworkDomain` + + :rtype: ``bool`` + """ + delete_node = ET.Element('deleteVlan', {'xmlns': TYPES_URN}) + delete_node.set('id', vlan.id) + result = self.connection.request_with_orgId_api_2( + 'network/deleteVlan', + method='POST', + data=ET.tostring(delete_node)).object + + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_list_vlans(self, location=None, network_domain=None, name=None, + ipv4_address=None, ipv6_address=None, state=None): + """ + List VLANs available, can filter by location and/or network domain + + :param location: Only VLANs in this location (optional) + :type location: :class:`NodeLocation` or ``str`` + + :param network_domain: Only VLANs in this domain (optional) + :type network_domain: :class:`NttCisNetworkDomain` + + :param name: Only VLANs with this name (optional) + :type name: ``str`` + + :param ipv4_address: Only VLANs with this ipv4 address (optional) + :type ipv4_address: ``str`` + + :param ipv6_address: Only VLANs with this ipv6 address (optional) + :type ipv6_address: ``str`` + + :param state: Only VLANs with this state (optional) + :type state: ``str`` + + :return: a list of NttCisVlan objects + :rtype: ``list`` of :class:`NttCisVlan` + """ + params = {} + if location is not None: + params['datacenterId'] = self._location_to_location_id(location) + if network_domain is not None: + params['networkDomainId'] = \ + self._network_domain_to_network_domain_id(network_domain) + if name is not None: + params['name'] = name + if ipv4_address is not None: + params['privateIpv4Address'] = ipv4_address + if ipv6_address is not None: + params['ipv6Address'] = ipv6_address + if state is not None: + params['state'] = state + response = self.connection.request_with_orgId_api_2('network/vlan', + params=params) \ + .object + return self._to_vlans(response) + + def ex_add_public_ip_block_to_network_domain(self, network_domain): + add_node = ET.Element('addPublicIpBlock', {'xmlns': TYPES_URN}) + ET.SubElement(add_node, "networkDomainId").text =\ + network_domain.id + + response = self.connection.request_with_orgId_api_2( + 'network/addPublicIpBlock', + method='POST', + data=ET.tostring(add_node)).object + + block_id = None + + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'ipBlockId': + block_id = info.get('value') + return self.ex_get_public_ip_block(block_id) + + def ex_list_public_ip_blocks(self, network_domain): + params = {} + params['networkDomainId'] = network_domain.id + + response = self.connection \ + .request_with_orgId_api_2('network/publicIpBlock', + params=params).object + return self._to_ip_blocks(response) + + def ex_get_public_ip_block(self, block_id): + locations = self.list_locations() + block = self.connection.request_with_orgId_api_2( + 'network/publicIpBlock/%s' % block_id).object + return self._to_ip_block(block, locations) + + def ex_delete_public_ip_block(self, block): + delete_node = ET.Element('removePublicIpBlock', {'xmlns': TYPES_URN}) + delete_node.set('id', block.id) + result = self.connection.request_with_orgId_api_2( + 'network/removePublicIpBlock', + method='POST', + data=ET.tostring(delete_node)).object + + response_code = findtext(result, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_get_node_by_id(self, id): + node = self.connection.request_with_orgId_api_2( + 'server/server/%s' % id).object + return self._to_node(node) + + def ex_list_firewall_rules(self, network_domain, page_size=50, + page_number=1): + params = {'pageSize': page_size, 'pageNumber': page_number} + params['networkDomainId'] = self._network_domain_to_network_domain_id( + network_domain) + + response = self.connection \ + .request_with_orgId_api_2('network/firewallRule', + params=params).object + return self._to_firewall_rules(response, network_domain) + + def ex_create_firewall_rule(self, network_domain, rule, position, + position_relative_to_rule=None): + """ + Creates a firewall rule + + :param network_domain: The network domain in which to create + the firewall rule + :type network_domain: :class:`NttCisNetworkDomain` or ``str`` + + :param rule: The rule in which to create + :type rule: :class:`NttCisFirewallRule` + + :param position: The position in which to create the rule + There are two types of positions + with position_relative_to_rule arg and without it + With: 'BEFORE' or 'AFTER' + Without: 'FIRST' or 'LAST' + :type position: ``str`` + + :param position_relative_to_rule: The rule or rule name in + which to decide positioning by + :type position_relative_to_rule: + :class:`NttCisFirewallRule` or ``str`` + + :rtype: ``bool`` + """ + positions_without_rule = ('FIRST', 'LAST') + positions_with_rule = ('BEFORE', 'AFTER') + + create_node = ET.Element('createFirewallRule', {'xmlns': TYPES_URN}) + ET.SubElement(create_node, "networkDomainId").text = \ + self._network_domain_to_network_domain_id(network_domain) + ET.SubElement(create_node, "name").text = rule.name + ET.SubElement(create_node, "action").text = rule.action + ET.SubElement(create_node, "ipVersion").text = rule.ip_version + ET.SubElement(create_node, "protocol").text = rule.protocol + # Setup source port rule + source = ET.SubElement(create_node, "source") + if rule.source.address_list_id is not None: + source_ip = ET.SubElement(source, 'ipAddressListId') + source_ip.text = rule.source.address_list_id + else: + source_ip = ET.SubElement(source, 'ip') + if rule.source.any_ip: + source_ip.set('address', 'ANY') + else: + source_ip.set('address', rule.source.ip_address) + if rule.source.ip_prefix_size is not None: + source_ip.set('prefixSize', + str(rule.source.ip_prefix_size)) + if rule.source.port_list_id is not None: + source_port = ET.SubElement(source, 'portListId') + source_port.text = rule.source.port_list_id + else: + if rule.source.port_begin is not None: + source_port = ET.SubElement(source, 'port') + source_port.set('begin', rule.source.port_begin) + if rule.source.port_end is not None: + source_port.set('end', rule.source.port_end) + # Setup destination port rule + dest = ET.SubElement(create_node, "destination") + if rule.destination.address_list_id is not None: + dest_ip = ET.SubElement(dest, 'ipAddressListId') + dest_ip.text = rule.destination.address_list_id + else: + dest_ip = ET.SubElement(dest, 'ip') + if rule.destination.any_ip: + dest_ip.set('address', 'ANY') + else: + dest_ip.set('address', rule.destination.ip_address) + if rule.destination.ip_prefix_size is not None: + dest_ip.set('prefixSize', rule.destination.ip_prefix_size) + if rule.destination.port_list_id is not None: + dest_port = ET.SubElement(dest, 'portListId') + dest_port.text = rule.destination.port_list_id + else: + if rule.destination.port_begin is not None: + dest_port = ET.SubElement(dest, 'port') + dest_port.set('begin', rule.destination.port_begin) + if rule.destination.port_end is not None: + dest_port.set('end', rule.destination.port_end) + # Set up positioning of rule + ET.SubElement(create_node, "enabled").text = str(rule.enabled).lower() + placement = ET.SubElement(create_node, "placement") + if position_relative_to_rule is not None: + if position not in positions_with_rule: + raise ValueError("When position_relative_to_rule is specified" + " position must be %s" + % ', '.join(positions_with_rule)) + if isinstance(position_relative_to_rule, + NttCisFirewallRule): + rule_name = position_relative_to_rule.name + else: + rule_name = position_relative_to_rule + placement.set('relativeToRule', rule_name) + else: + if position not in positions_without_rule: + raise ValueError("When position_relative_to_rule is not" + " specified position must be %s" + % ', '.join(positions_without_rule)) + placement.set('position', position) + + response = self.connection.request_with_orgId_api_2( + 'network/createFirewallRule', + method='POST', + data=ET.tostring(create_node)).object + + rule_id = None + for info in findall(response, 'info', TYPES_URN): + if info.get('name') == 'firewallRuleId': + rule_id = info.get('value') + rule.id = rule_id + return rule + + def ex_edit_firewall_rule(self, rule, position, + relative_rule_for_position=None): + """ + Edit a firewall rule + + >>> from pprint import pprint + >>> from libcloud.compute.types import Provider + >>> from libcloud.compute.providers import get_driver + >>> import libcloud.security + >>> + >>> # Get dimension data driver + >>> libcloud.security.VERIFY_SSL_CERT = True + >>> cls = get_driver(Provider.DIMENSIONDATA) + >>> driver = cls('myusername','mypassword', region='dd-au') + >>> + >>> # Get location + >>> location = driver.ex_get_location_by_id(id='AU9') + >>> + >>> # Get network domain by location + >>> networkDomainName = "Baas QA" + >>> network_domains = driver.ex_list_network_domains(location=location) + >>> my_network_domain = [d for d in network_domains if d.name == + networkDomainName][0] + >>> + >>> + >>> # List firewall rules + >>> firewall_rules = driver.ex_list_firewall_rules(my_network_domain) + >>> + >>> # Get Firewall Rule by name + >>> pprint("List specific firewall rule by name") + >>> fire_rule_under_test = (list(filter(lambda x: x.name == + 'My_New_Firewall_Rule', firewall_rules))[0]) + >>> pprint(fire_rule_under_test.source) + >>> pprint(fire_rule_under_test.destination) + >>> + >>> # Edit Firewall + >>> fire_rule_under_test.destination.address_list_id = + '5e7c323f-c885-4e4b-9a27-94c44217dbd3' + >>> fire_rule_under_test.destination.port_list_id = + 'b6557c5a-45fa-4138-89bd-8fe68392691b' + >>> result = driver.ex_edit_firewall_rule(fire_rule_under_test, 'LAST') + >>> pprint(result) + + :param rule: (required) The rule in which to create + :type rule: :class:`DNttCisFirewallRule` + + :param position: (required) There are two types of positions + with position_relative_to_rule arg and without it + With: 'BEFORE' or 'AFTER' + Without: 'FIRST' or 'LAST' + :type position: ``str`` + + :param relative_rule_for_position: (optional) The rule or rule name in + which to decide the relative rule + for positioning. + :type relative_rule_for_position: + :class:`NttCisFirewallRule` or ``str`` + + :rtype: ``bool`` + """ + + positions_without_rule = ('FIRST', 'LAST') + positions_with_rule = ('BEFORE', 'AFTER') + + edit_node = ET.Element('editFirewallRule', + {'xmlns': TYPES_URN, 'id': rule.id}) + ET.SubElement(edit_node, "action").text = rule.action + ET.SubElement(edit_node, "protocol").text = rule.protocol + + # Source address + source = ET.SubElement(edit_node, "source") + if rule.source.address_list_id is not None: + source_ip = ET.SubElement(source, 'ipAddressListId') + source_ip.text = rule.source.address_list_id + else: + source_ip = ET.SubElement(source, 'ip') + if rule.source.any_ip: + source_ip.set('address', 'ANY') + else: + source_ip.set('address', rule.source.ip_address) + if rule.source.ip_prefix_size is not None: + source_ip.set('prefixSize', + str(rule.source.ip_prefix_size)) + + # Setup source port rule + if rule.source.port_list_id is not None: + source_port = ET.SubElement(source, 'portListId') + source_port.text = rule.source.port_list_id + else: + if rule.source.port_begin is not None: + source_port = ET.SubElement(source, 'port') + source_port.set('begin', rule.source.port_begin) + if rule.source.port_end is not None: + source_port.set('end', rule.source.port_end) + # Setup destination port rule + dest = ET.SubElement(edit_node, "destination") + if rule.destination.address_list_id is not None: + dest_ip = ET.SubElement(dest, 'ipAddressListId') + dest_ip.text = rule.destination.address_list_id + else: + dest_ip = ET.SubElement(dest, 'ip') + if rule.destination.any_ip: + dest_ip.set('address', 'ANY') + else: + dest_ip.set('address', rule.destination.ip_address) + if rule.destination.ip_prefix_size is not None: + dest_ip.set('prefixSize', rule.destination.ip_prefix_size) + if rule.destination.port_list_id is not None: + dest_port = ET.SubElement(dest, 'portListId') + dest_port.text = rule.destination.port_list_id + else: + if rule.destination.port_begin is not None: + dest_port = ET.SubElement(dest, 'port') + dest_port.set('begin', rule.destination.port_begin) + if rule.destination.port_end is not None: + dest_port.set('end', rule.destination.port_end) + # Set up positioning of rule + ET.SubElement(edit_node, "enabled").text = str(rule.enabled).lower() + placement = ET.SubElement(edit_node, "placement") + if relative_rule_for_position is not None: + if position not in positions_with_rule: + raise ValueError("When position_relative_to_rule is specified" + " position must be %s" + % ', '.join(positions_with_rule)) + if isinstance(relative_rule_for_position, + NttCisFirewallRule): + rule_name = relative_rule_for_position.name + else: + rule_name = relative_rule_for_position + placement.set('relativeToRule', rule_name) + else: + if position not in positions_without_rule: + raise ValueError("When position_relative_to_rule is not" + " specified position must be %s" + % ', '.join(positions_without_rule)) + placement.set('position', position) + + response = self.connection.request_with_orgId_api_2( + 'network/editFirewallRule', + method='POST', + data=ET.tostring(edit_node)).object + + response_code = findtext(response, 'responseCode', TYPES_URN) + return response_code in ['IN_PROGRESS', 'OK'] + + def ex_get_firewall_rule(self, network_domain, rule_id): + locations = self.list_locations() + rule = self.connection.request_with_orgId_api_2( + 'network/firewallRule/%s' % rule_id).object + return self._to_firewall_rule(rule, locations, network_domain) + + def ex_set_firewall_rule_state(self, rule, state): + """ + Change the state (enabled or disabled) of a rule + + :param rule: The rule to delete + :type rule: :class:`NttCisFirewallRule` + + :param state: The desired state enabled (True) or disabled (False) + :type state: ``bool`` + + :rtype: ``bool`` + """ + update_node = ET.Element('editFirewallRule', {'xmlns': TYPES_URN}) + update_node.set('id', rule.id) + ET.SubElement(update_node, 'enabled').text = str(state).lower() + result = self.connection.request_with_orgId_api_2( + 'network/editFirewallRule', + method='POST', + data=ET.tostring(update_node)).object + + response_code = findtext(result, 'responseCode', TYPES_URN) + return
<TRUNCATED>
