Repository: libcloud
Updated Branches:
  refs/heads/trunk c2edb008e -> e62eb2bcc


update create_node function for  MCP2 and implement backward compatibility for 
MCP1 using kwarg


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/95d10015
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/95d10015
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/95d10015

Branch: refs/heads/trunk
Commit: 95d1001592013af05198e76ce5f6cc1a3960515e
Parents: 4d11ec2
Author: Samuel Chong <[email protected]>
Authored: Tue Oct 11 11:47:55 2016 +1100
Committer: Samuel Chong <[email protected]>
Committed: Tue Oct 11 11:47:55 2016 +1100

----------------------------------------------------------------------
 libcloud/common/dimensiondata.py            |   3 +-
 libcloud/compute/drivers/dimensiondata.py   | 579 ++++++++++++++++++-----
 libcloud/test/compute/test_dimensiondata.py | 324 ++++++++++++-
 3 files changed, 782 insertions(+), 124 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/95d10015/libcloud/common/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/common/dimensiondata.py b/libcloud/common/dimensiondata.py
index 84bca0d..e0ab33e 100644
--- a/libcloud/common/dimensiondata.py
+++ b/libcloud/common/dimensiondata.py
@@ -732,7 +732,8 @@ class DimensionDataServerDisk(object):
     """
     A class that represents the disk on a server
     """
-    def __init__(self, id, scsi_id, size_gb, speed, state):
+    def __init__(self, id=None, scsi_id=None, size_gb=None, speed=None,
+                 state=None):
         """
         Instantiate a new :class:`DimensionDataServerDisk`
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/95d10015/libcloud/compute/drivers/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/dimensiondata.py 
b/libcloud/compute/drivers/dimensiondata.py
index 8900086..8d19022 100644
--- a/libcloud/compute/drivers/dimensiondata.py
+++ b/libcloud/compute/drivers/dimensiondata.py
@@ -44,6 +44,7 @@ from libcloud.common.dimensiondata import 
DimensionDataIpAddress
 from libcloud.common.dimensiondata import DimensionDataPortList
 from libcloud.common.dimensiondata import DimensionDataPort
 from libcloud.common.dimensiondata import DimensionDataChildPortList
+from libcloud.common.dimensiondata import DimensionDataNic
 from libcloud.common.dimensiondata import NetworkDomainServicePlan
 from libcloud.common.dimensiondata import DimensionDataTagKey
 from libcloud.common.dimensiondata import DimensionDataTag
@@ -129,84 +130,61 @@ class DimensionDataNodeDriver(NodeDriver):
         kwargs['region'] = self.selected_region
         return kwargs
 
-    def create_node(self, name, image, auth, ex_description,
-                    ex_network=None, ex_network_domain=None,
-                    ex_vlan=None, ex_primary_ipv4=None,
-                    ex_memory_gb=None,
-                    ex_cpu_specification=None,
-                    ex_is_started=True, ex_additional_nics_vlan=None,
-                    ex_additional_nics_ipv4=None,
-                    ex_primary_dns=None,
-                    ex_secondary_dns=None, **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 DimensionData 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)
+            Create a new DimensionData node
 
-        :type       ex_network: :class:`DimensionDataNetwork` or ``str``
+            :keyword    name:   String with a name for this new node (required)
+            :type       name:   ``str``
 
-        :keyword    ex_network_domain:  Network Domain to create the node
-                                        (required unless using network
-                                        or ex_primary_ipv4)
-        :type       ex_network_domain: :class:`DimensionDataNetworkDomain`
-                                        or ``str``
+            :keyword    image:  OS Image to boot on node. (required)
+            :type       image:  :class:`NodeImage` or ``str``
 
-        :keyword    ex_primary_ipv4: Primary nics IPv4 Address
-                                     MCP1: (required unless ex_network)
-                                     MCP2: (required unless ex_vlan)
-        :type       ex_primary_ipv4: ``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_vlan:  VLAN to create the node within
-                              (required unless using network)
-        :type       ex_vlan: :class:`DimensionDataVlan` or ``str``
+            :keyword    ex_description:  description for this node (required)
+            :type       ex_description:  ``str``
 
-        :keyword    ex_memory_gb:  The amount of memory in GB for the server
-        :type       ex_memory_gb: ``int``
+            :keyword    ex_network:  Network to create the node within
+                                     (required unless using ex_network_domain
+                                     or ex_primary_ipv4)
 
-        :keyword    ex_cpu_specification: The spec of CPU to deploy (optional)
-        :type       ex_cpu_specification:
-            :class:`DimensionDataServerCpuSpecification`
+            :type       ex_network: :class:`DimensionDataNetwork` or ``str``
 
-        :keyword    ex_is_started:  Start server after creation? default
-                                   true (required)
-        :type       ex_is_started:  ``bool``
+            :keyword    ex_memory_gb:  The amount of memory in GB for the
+                                       server
+            :type       ex_memory_gb: ``int``
 
-        :keyword    ex_additional_nics_vlan: (MCP2 Only) List of additional
-                                              nics to add by vlan
-        :type       ex_additional_nics_vlan: ``list`` of
-            :class:`DimensionDataVlan` or ``list`` of ``str``
+            :keyword    ex_cpu_specification: The spec of CPU to deploy (
+                                              optional)
+            :type       ex_cpu_specification:
+                            :class:`DimensionDataServerCpuSpecification`
 
-        :keyword    ex_additional_nics_ipv4: (MCP2 Only) List of additional
-                                              nics to add by ipv4 address
-        :type       ex_additional_nics_ipv4: ``list`` of ``str``
+            :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
+            :keyword    ex_primary_dns: The node's primary DNS
 
-        :type       ex_primary_dns: ``str``
+            :type       ex_primary_dns: ``str``
 
-        :keyword    ex_secondary_dns: The node's secondary DNS
+            :keyword    ex_secondary_dns: The node's secondary DNS
 
-        :type       ex_secondary_dns: ``str``
+            :type       ex_secondary_dns: ``str``
 
-        :return: The newly created :class:`Node`.
-        :rtype: :class:`Node`
-        """
+            :return: The newly created :class:`Node`.
+            :rtype: :class:`Node`
+            """
         password = None
         image_needs_auth = self._image_needs_auth(image)
         if image_needs_auth:
@@ -217,20 +195,16 @@ class DimensionDataNodeDriver(NodeDriver):
                 auth_obj = self._get_and_check_auth(auth)
                 password = auth_obj.password
 
-        if (ex_network_domain is None and
-                ex_network is None and
-                ex_primary_ipv4 is None):
-            raise ValueError("One of ex_network_domain, ex_network, "
-                             "or ex_ipv6_primary must be specified")
-
         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()
+        ET.SubElement(server_elm, "start").text = str(
+            ex_is_started).lower()
         if password is not None:
-            ET.SubElement(server_elm, "administratorPassword").text = password
+            ET.SubElement(server_elm,
+                          "administratorPassword").text = password
 
         if ex_cpu_specification is not None:
             cpu = ET.SubElement(server_elm, "cpu")
@@ -246,43 +220,6 @@ class DimensionDataNodeDriver(NodeDriver):
             network_elm = ET.SubElement(server_elm, "network")
             network_id = self._network_to_network_id(ex_network)
             ET.SubElement(network_elm, "networkId").text = network_id
-        elif ex_network_domain is None and ex_primary_ipv4 is not None:
-            network_elm = ET.SubElement(server_elm, "network")
-            ET.SubElement(network_elm, "privateIpv4").text = ex_primary_ipv4
-        elif ex_network_domain is not None:
-            net_domain_id = self._network_domain_to_network_domain_id(
-                ex_network_domain)
-            network_inf_elm = ET.SubElement(
-                server_elm, "networkInfo",
-                {'networkDomainId': net_domain_id}
-            )
-
-            if ex_vlan is not None:
-                vlan_id = self._vlan_to_vlan_id(ex_vlan)
-                pri_nic = ET.SubElement(network_inf_elm, "primaryNic")
-                ET.SubElement(pri_nic, "vlanId").text = vlan_id
-            elif ex_primary_ipv4 is not None:
-                pri_nic = ET.SubElement(network_inf_elm, "primaryNic")
-                ET.SubElement(pri_nic, "privateIpv4").text = ex_primary_ipv4
-            else:
-                raise ValueError("One of ex_vlan or ex_primary_ipv4 "
-                                 "must be specified")
-
-            if isinstance(ex_additional_nics_ipv4, (list, tuple)):
-                for ipv4_nic in ex_additional_nics_ipv4:
-                    add_nic = ET.SubElement(network_inf_elm, "additionalNic")
-                    ET.SubElement(add_nic, "privateIpv4").text = ipv4_nic
-            elif ex_additional_nics_ipv4 is not None:
-                raise TypeError("ex_additional_nics_ipv4 must "
-                                "be None or a tuple/list")
-
-            if isinstance(ex_additional_nics_vlan, (list, tuple)):
-                for vlan_nic in ex_additional_nics_vlan:
-                    add_nic = ET.SubElement(network_inf_elm, "additionalNic")
-                    ET.SubElement(add_nic, "vlanId").text = vlan_nic
-            elif ex_additional_nics_vlan is not None:
-                raise TypeError("ex_additional_nics_vlan"
-                                "must be None or tuple/list")
 
         if ex_primary_dns:
             dns_elm = ET.SubElement(server_elm, "primaryDns")
@@ -310,6 +247,432 @@ class DimensionDataNodeDriver(NodeDriver):
 
         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('schong_platcaas', 'T3stst@r!', region='dd-au')
+        >>> # 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)
+        >>>
+        >>> pprint(node)
+
+        :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:'DimensionDataNic' or None
+        :type       ex_additional_nics: ``list`` of :class:'DimensionDataNic'
+                                        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')
+
+        # 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 = DimensionDataNic(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 = DimensionDataNic(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

http://git-wip-us.apache.org/repos/asf/libcloud/blob/95d10015/libcloud/test/compute/test_dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_dimensiondata.py 
b/libcloud/test/compute/test_dimensiondata.py
index 36e9a63..d6376a4 100644
--- a/libcloud/test/compute/test_dimensiondata.py
+++ b/libcloud/test/compute/test_dimensiondata.py
@@ -31,6 +31,7 @@ from libcloud.common.dimensiondata import 
DimensionDataIpAddress, \
     DimensionDataPortList, DimensionDataPort, DimensionDataChildPortList
 from libcloud.common.dimensiondata import TYPES_URN
 from libcloud.compute.drivers.dimensiondata import DimensionDataNodeDriver as 
DimensionData
+from libcloud.compute.drivers.dimensiondata import DimensionDataNic
 from libcloud.compute.base import Node, NodeAuthPassword, NodeLocation
 from libcloud.test import MockHttp, unittest, MockRawResponse, StorageMockHttp
 from libcloud.test.compute import TestCaseMixin
@@ -245,7 +246,28 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
 
-    def test_create_node_response_no_pass_random_gen(self):
+    def test_create_mcp1_node_optional_param(self):
+        root_pw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        network = self.driver.ex_list_networks()[0]
+        cpu_spec = DimensionDataServerCpuSpecification(cpu_count='4',
+                                                       cores_per_socket='2',
+                                                       performance='STANDARD')
+        disks = [DimensionDataServerDisk(scsi_id='0', speed='HIGHPERFORMANCE')]
+        node = self.driver.create_node(name='test2', image=image, auth=root_pw,
+                                       ex_description='test2 node',
+                                       ex_network=network,
+                                       ex_is_started=False,
+                                       ex_memory_gb=8,
+                                       ex_disks=disks,
+                                       ex_cpu_specification=cpu_spec,
+                                       ex_primary_dns='10.0.0.5',
+                                       ex_secondary_dns='10.0.0.6'
+                                       )
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_mcp1_node_response_no_pass_random_gen(self):
         image = self.driver.list_images()[0]
         network = self.driver.ex_list_networks()[0]
         node = self.driver.create_node(name='test2', image=image, auth=None,
@@ -255,7 +277,7 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
         self.assertTrue('password' in node.extra)
 
-    def test_create_node_response_no_pass_customer_windows(self):
+    def test_create_mcp1_node_response_no_pass_customer_windows(self):
         image = self.driver.ex_list_customer_images()[1]
         network = self.driver.ex_list_networks()[0]
         node = self.driver.create_node(name='test2', image=image, auth=None,
@@ -265,7 +287,7 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
         self.assertTrue('password' in node.extra)
 
-    def test_create_node_response_no_pass_customer_windows_STR(self):
+    def test_create_mcp1_node_response_no_pass_customer_windows_STR(self):
         image = self.driver.ex_list_customer_images()[1].id
         network = self.driver.ex_list_networks()[0]
         node = self.driver.create_node(name='test2', image=image, auth=None,
@@ -275,7 +297,7 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
         self.assertTrue('password' in node.extra)
 
-    def test_create_node_response_no_pass_customer_linux(self):
+    def test_create_mcp1_node_response_no_pass_customer_linux(self):
         image = self.driver.ex_list_customer_images()[0]
         network = self.driver.ex_list_networks()[0]
         node = self.driver.create_node(name='test2', image=image, auth=None,
@@ -285,7 +307,7 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
         self.assertTrue('password' not in node.extra)
 
-    def test_create_node_response_no_pass_customer_linux_STR(self):
+    def test_create_mcp1_node_response_no_pass_customer_linux_STR(self):
         image = self.driver.ex_list_customer_images()[0].id
         network = self.driver.ex_list_networks()[0]
         node = self.driver.create_node(name='test2', image=image, auth=None,
@@ -295,7 +317,7 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
         self.assertTrue('password' not in node.extra)
 
-    def test_create_node_response_STR(self):
+    def test_create_mcp1_node_response_STR(self):
         rootPw = 'pass123'
         image = self.driver.list_images()[0].id
         network = self.driver.ex_list_networks()[0].id
@@ -345,10 +367,10 @@ class DimensionDataTests(unittest.TestCase, 
TestCaseMixin):
         self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
 
-    def test_create_node_no_network(self):
+    def test_create_mcp1_node_no_network(self):
         rootPw = NodeAuthPassword('pass123')
         image = self.driver.list_images()[0]
-        with self.assertRaises(ValueError):
+        with self.assertRaises(InvalidRequestError):
             self.driver.create_node(name='test2',
                                     image=image,
                                     auth=rootPw,
@@ -363,6 +385,7 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
                                        image=image,
                                        auth=rootPw,
                                        ex_description='test2 node',
+                                       ex_network='fakenetwork',
                                        ex_primary_ipv4='10.0.0.1',
                                        ex_is_started=False)
         self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
@@ -417,23 +440,223 @@ class DimensionDataTests(unittest.TestCase, 
TestCaseMixin):
                                     ex_network_domain='fake_network_domain',
                                     ex_is_started=False)
 
-    def test_create_node_mcp2_additional_nics(self):
+    def test_create_node_response(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        node = self.driver.create_node(
+            name='test3',
+            image=image,
+            auth=rootPw,
+            ex_network_domain='fakenetworkdomain',
+            ex_primary_nic_vlan='fakevlan'
+        )
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_ms_time_zone(self):
         rootPw = NodeAuthPassword('pass123')
         image = self.driver.list_images()[0]
-        additional_vlans = ['fakevlan1', 'fakevlan2']
-        additional_ipv4 = ['10.0.0.2', '10.0.0.3']
+        node = self.driver.create_node(
+            name='test3',
+            image=image,
+            auth=rootPw,
+            ex_network_domain='fakenetworkdomain',
+            ex_primary_nic_vlan='fakevlan',
+            ex_microsoft_time_zone='040'
+        )
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_ambigious_mcps_fail(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(
+                name='test3',
+                image=image,
+                auth=rootPw,
+                ex_network_domain='fakenetworkdomain',
+                ex_network='fakenetwork',
+                ex_primary_nic_vlan='fakevlan'
+            )
+
+    def test_create_node_no_network_domain_fail(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(
+                name='test3',
+                image=image,
+                auth=rootPw,
+                ex_primary_nic_vlan='fakevlan'
+            )
+
+    def test_create_node_no_primary_nic_fail(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(
+                name='test3',
+                image=image,
+                auth=rootPw,
+                ex_network_domain='fakenetworkdomain'
+            )
+
+    def test_create_node_primary_vlan_nic(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        node = self.driver.create_node(
+            name='test3',
+            image=image,
+            auth=rootPw,
+            ex_network_domain='fakenetworkdomain',
+            ex_primary_nic_vlan='fakevlan',
+            ex_primary_nic_network_adapter='v1000'
+        )
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_primary_ipv4(self):
+        rootPw = 'pass123'
+        image = self.driver.list_images()[0]
+        node = self.driver.create_node(
+            name='test3',
+            image=image,
+            auth=rootPw,
+            ex_network_domain='fakenetworkdomain',
+            ex_primary_nic_private_ipv4='10.0.0.1'
+        )
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_both_primary_nic_and_vlan_fail(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(
+                name='test3',
+                image=image,
+                auth=rootPw,
+                ex_network_domain='fakenetworkdomain',
+                ex_primary_nic_private_ipv4='10.0.0.1',
+                ex_primary_nic_vlan='fakevlan'
+            )
+
+    def test_create_node_cpu_specification(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        cpu_spec = DimensionDataServerCpuSpecification(cpu_count='4',
+                                                       cores_per_socket='2',
+                                                       performance='STANDARD')
         node = self.driver.create_node(name='test2',
                                        image=image,
                                        auth=rootPw,
                                        ex_description='test2 node',
                                        ex_network_domain='fakenetworkdomain',
-                                       ex_primary_ipv4='10.0.0.1',
-                                       
ex_additional_nics_vlan=additional_vlans,
-                                       ex_additional_nics_ipv4=additional_ipv4,
-                                       ex_is_started=False)
+                                       ex_primary_nic_private_ipv4='10.0.0.1',
+                                       ex_is_started=False,
+                                       ex_cpu_specification=cpu_spec)
         self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
         self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
 
+    def test_create_node_memory(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+
+        node = self.driver.create_node(name='test2',
+                                       image=image,
+                                       auth=rootPw,
+                                       ex_description='test2 node',
+                                       ex_network_domain='fakenetworkdomain',
+                                       ex_primary_nic_private_ipv4='10.0.0.1',
+                                       ex_is_started=False,
+                                       ex_memory_gb=8)
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_disks(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        disks = [DimensionDataServerDisk(scsi_id='0', speed='HIGHPERFORMANCE')]
+        node = self.driver.create_node(name='test2',
+                                       image=image,
+                                       auth=rootPw,
+                                       ex_description='test2 node',
+                                       ex_network_domain='fakenetworkdomain',
+                                       ex_primary_nic_private_ipv4='10.0.0.1',
+                                       ex_is_started=False,
+                                       ex_disks=disks)
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_disks_fail(self):
+        root_pw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        disks = 'blah'
+        with self.assertRaises(TypeError):
+            self.driver.create_node(name='test2',
+                                    image=image,
+                                    auth=root_pw,
+                                    ex_description='test2 node',
+                                    ex_network_domain='fakenetworkdomain',
+                                    ex_primary_nic_private_ipv4='10.0.0.1',
+                                    ex_is_started=False,
+                                    ex_disks=disks)
+
+    def test_create_node_ipv4_gateway(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        node = self.driver.create_node(name='test2',
+                                       image=image,
+                                       auth=rootPw,
+                                       ex_description='test2 node',
+                                       ex_network_domain='fakenetworkdomain',
+                                       ex_primary_nic_private_ipv4='10.0.0.1',
+                                       ex_is_started=False,
+                                       ex_ipv4_gateway='10.2.2.2')
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_network_domain_no_vlan_no_ipv4_fail(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(name='test2',
+                                    image=image,
+                                    auth=rootPw,
+                                    ex_description='test2 node',
+                                    ex_network_domain='fake_network_domain',
+                                    ex_is_started=False)
+
+    def test_create_node_mcp2_additional_nics_legacy(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        additional_vlans = ['fakevlan1', 'fakevlan2']
+        additional_ipv4 = ['10.0.0.2', '10.0.0.3']
+        node = self.driver.create_node(
+            name='test2',
+            image=image,
+            auth=rootPw,
+            ex_description='test2 node',
+            ex_network_domain='fakenetworkdomain',
+            ex_primary_ipv4='10.0.0.1',
+            ex_additional_nics_vlan=additional_vlans,
+            ex_additional_nics_ipv4=additional_ipv4,
+            ex_is_started=False)
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_network_domain_no_vlan_no_ipv4_fail(self):
+        rootPw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(name='test2',
+                                    image=image,
+                                    auth=rootPw,
+                                    ex_description='test2 node',
+                                    ex_network_domain='fake_network_domain',
+                                    ex_is_started=False)
+
     def test_create_node_bad_additional_nics_ipv4(self):
         rootPw = NodeAuthPassword('pass123')
         image = self.driver.list_images()[0]
@@ -447,6 +670,77 @@ class DimensionDataTests(unittest.TestCase, TestCaseMixin):
                                     ex_additional_nics_ipv4='badstring',
                                     ex_is_started=False)
 
+    def test_create_node_additional_nics(self):
+        root_pw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        nic1 = DimensionDataNic(vlan='fake_vlan',
+                                network_adapter_name='v1000')
+        nic2 = DimensionDataNic(private_ip_v4='10.1.1.2',
+                                network_adapter_name='v1000')
+        additional_nics = [nic1, nic2]
+
+        node = self.driver.create_node(name='test2',
+                                       image=image,
+                                       auth=root_pw,
+                                       ex_description='test2 node',
+                                       ex_network_domain='fakenetworkdomain',
+                                       ex_primary_nic_private_ipv4='10.0.0.1',
+                                       ex_additional_nics=additional_nics,
+                                       ex_is_started=False)
+
+        self.assertEqual(node.id, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(node.extra['status'].action, 'DEPLOY_SERVER')
+
+    def test_create_node_additional_nics_vlan_ipv4_coexist_fail(self):
+        root_pw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        nic1 = DimensionDataNic(private_ip_v4='10.1.1.1', vlan='fake_vlan',
+                                network_adapter_name='v1000')
+        nic2 = DimensionDataNic(private_ip_v4='10.1.1.2', vlan='fake_vlan2',
+                                network_adapter_name='v1000')
+        additional_nics = [nic1, nic2]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(name='test2',
+                                    image=image,
+                                    auth=root_pw,
+                                    ex_description='test2 node',
+                                    ex_network_domain='fakenetworkdomain',
+                                    ex_primary_nic_private_ipv4='10.0.0.1',
+                                    ex_additional_nics=additional_nics,
+                                    ex_is_started=False
+                                    )
+
+    def test_create_node_additional_nics_invalid_input_fail(self):
+        root_pw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        additional_nics = 'blah'
+        with self.assertRaises(TypeError):
+            self.driver.create_node(name='test2',
+                                    image=image,
+                                    auth=root_pw,
+                                    ex_description='test2 node',
+                                    ex_network_domain='fakenetworkdomain',
+                                    ex_primary_nic_private_ipv4='10.0.0.1',
+                                    ex_additional_nics=additional_nics,
+                                    ex_is_started=False
+                                    )
+
+    def test_create_node_additional_nics_vlan_ipv4_not_exist_fail(self):
+        root_pw = NodeAuthPassword('pass123')
+        image = self.driver.list_images()[0]
+        nic1 = DimensionDataNic(network_adapter_name='v1000')
+        nic2 = DimensionDataNic(network_adapter_name='v1000')
+        additional_nics = [nic1, nic2]
+        with self.assertRaises(ValueError):
+            self.driver.create_node(name='test2',
+                                    image=image,
+                                    auth=root_pw,
+                                    ex_description='test2 node',
+                                    ex_network_domain='fakenetworkdomain',
+                                    ex_primary_nic_private_ipv4='10.0.0.1',
+                                    ex_additional_nics=additional_nics,
+                                    ex_is_started=False)
+
     def test_create_node_bad_additional_nics_vlan(self):
         rootPw = NodeAuthPassword('pass123')
         image = self.driver.list_images()[0]

Reply via email to