You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by se...@apache.org on 2015/02/27 10:12:53 UTC

libcloud git commit: Add Affinity Group support to CloudStack

Repository: libcloud
Updated Branches:
  refs/heads/trunk 62864f52e -> 52d96c7f5


Add Affinity Group support to CloudStack

Signed-off-by: Sebastien Goasguen <ru...@gmail.com>

This closes #468


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

Branch: refs/heads/trunk
Commit: 52d96c7f5d4284f6574d1dd9bf7c14ba6102fa95
Parents: 62864f5
Author: Mateusz Korszun <mk...@gmail.com>
Authored: Tue Feb 17 12:20:39 2015 +0100
Committer: Sebastien Goasguen <ru...@gmail.com>
Committed: Fri Feb 27 04:12:15 2015 -0500

----------------------------------------------------------------------
 CHANGES.rst                                     |   4 +
 libcloud/compute/drivers/cloudstack.py          | 213 +++++++++++++++++++
 .../cloudstack/createAffinityGroup_default.json |   1 +
 .../cloudstack/deleteAffinityGroup_default.json |   1 +
 .../listAffinityGroupTypes_default.json         |   1 +
 .../cloudstack/listAffinityGroups_default.json  |   1 +
 .../cloudstack/queryAsyncJobResult_1300004.json |   1 +
 .../cloudstack/queryAsyncJobResult_1300005.json |   1 +
 .../cloudstack/queryAsyncJobResult_1300006.json |  62 ++++++
 .../updateVMAffinityGroup_default.json          |   1 +
 libcloud/test/compute/test_cloudstack.py        |  43 +++-
 11 files changed, 328 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 7c410c2..094af05 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -42,6 +42,10 @@ Compute
   (GITHUB-465)
   [Avi Nanhkoesingh]
 
+- Add affinity group support to CloudStack driver
+  (LIBCLOUD-671, GITHUB-468)
+  [Mateusz Korszun]
+
 DNS
 ~~~
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/compute/drivers/cloudstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py
index ef3b4d3..fb1961e 100644
--- a/libcloud/compute/drivers/cloudstack.py
+++ b/libcloud/compute/drivers/cloudstack.py
@@ -1063,6 +1063,85 @@ class CloudStackProject(object):
                    self.driver.name))
 
 
+class CloudStackAffinityGroup(object):
+    """
+    Class representing a CloudStack AffinityGroup.
+    """
+
+    def __init__(self, id, account, description, domain, domainid, name,
+                 group_type, virtualmachine_ids):
+        """
+        A CloudStack Affinity Group.
+
+        @note: This is a non-standard extension API, and only works for
+               CloudStack.
+
+        :param      id: CloudStack Affinity Group ID
+        :type       id: ``str``
+
+        :param      account: An account for the affinity group. Must be used
+                             with domainId.
+        :type       account: ``str``
+
+        :param      description: optional description of the affinity group
+        :type       description: ``str``
+
+        :param      domain: the domain name of the affinity group
+        :type       domain: ``str``
+
+        :param      domainid: domain ID of the account owning the affinity
+                              group
+        :type       domainid: ``str``
+
+        :param      name: name of the affinity group
+        :type       name: ``str``
+
+        :param      group_type: the type of the affinity group
+        :type       group_type: :class:`CloudStackAffinityGroupType`
+
+        :param      virtualmachine_ids: virtual machine Ids associated with
+                                        this affinity group
+        :type       virtualmachine_ids: ``str``
+
+        :rtype:     :class:`CloudStackAffinityGroup`
+        """
+        self.id = id
+        self.account = account
+        self.description = description
+        self.domain = domain
+        self.domainid = domainid
+        self.name = name
+        self.type = group_type
+        self.virtualmachine_ids = virtualmachine_ids
+
+    def __repr__(self):
+        return (('<CloudStackAffinityGroup: id=%s, name=%s, type=%s>')
+                % (self.id, self.name, self.type))
+
+
+class CloudStackAffinityGroupType(object):
+    """
+    Class representing a CloudStack AffinityGroupType.
+    """
+
+    def __init__(self, type_name):
+        """
+        A CloudStack Affinity Group Type.
+
+        @note: This is a non-standard extension API, and only works for
+               CloudStack.
+
+        :param      type_name: the type of the affinity group
+        :type       type_name: ``str``
+
+        :rtype: :class:`CloudStackAffinityGroupType`
+        """
+        self.type = type_name
+
+    def __repr__(self):
+        return (('<CloudStackAffinityGroupType: type=%s>') % self.type)
+
+
 class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
     """
     Driver for the CloudStack API.
@@ -1313,6 +1392,11 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         :keyword    ex_rootdisksize: String with rootdisksize for the template
         :type       ex_rootdisksize: ``str``
 
+        :keyword    ex_affinity_groups: List of affinity groups to assign to
+                                        the node
+        :type       ex_affinity_groups: ``list`` of
+                                        :class:`.CloudStackAffinityGroup`
+
         :rtype:     :class:`.CloudStackNode`
         """
 
@@ -1342,6 +1426,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         ex_ip_address = kwargs.get('ex_ip_address', None)
         ex_start_vm = kwargs.get('ex_start_vm', None)
         ex_rootdisksize = kwargs.get('ex_rootdisksize', None)
+        ex_affinity_groups = kwargs.get('ex_affinity_groups', None)
 
         if name:
             server_params['name'] = name
@@ -1391,6 +1476,10 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         if ex_start_vm is not None:
             server_params['startvm'] = ex_start_vm
 
+        if ex_affinity_groups:
+            affinity_group_ids = ','.join(ag.id for ag in ex_affinity_groups)
+            server_params['affinitygroupids'] = affinity_group_ids
+
         return server_params
 
     def destroy_node(self, node, ex_expunge=False):
@@ -3087,6 +3176,111 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
                             method='GET')
         return True
 
+    def ex_create_affinity_group(self, name, group_type):
+        """
+        Creates a new Affinity Group
+
+        :param name: Name of the affinity group
+        :type  name: ``str``
+
+        :param group_type: Type of the affinity group from the available
+                           affinity/anti-affinity group types
+        :type  group_type: :class:`CloudStackAffinityGroupType`
+
+        :param description: Optional description of the affinity group
+        :type  description: ``str``
+
+        :param domainid: domain ID of the account owning the affinity group
+        :type  domainid: ``str``
+
+        :rtype: :class:`CloudStackAffinityGroup`
+        """
+
+        for ag in self.ex_list_affinity_groups():
+            if name == ag.name:
+                raise LibcloudError('This Affinity Group name already exists')
+
+        params = {'name': name, 'type': group_type.type}
+
+        result = self._async_request(command='createAffinityGroup',
+                                     params=params,
+                                     method='GET')
+
+        return self._to_affinity_group(result['affinitygroup'])
+
+    def ex_delete_affinity_group(self, affinity_group):
+        """
+        Delete an Affinity Group
+
+        :param affinity_group: Instance of affinity group
+        :type  affinity_group: :class:`CloudStackAffinityGroup`
+
+        :rtype ``bool``
+        """
+        return self._async_request(command='deleteAffinityGroup',
+                                   params={'id': affinity_group.id},
+                                   method='GET')['success']
+
+    def ex_update_node_affinity_group(self, node, affinity_group_list):
+        """
+        Updates the affinity/anti-affinity group associations of a virtual
+        machine. The VM has to be stopped and restarted for the new properties
+        to take effect.
+
+        :param node: Node to update.
+        :type node: :class:`CloudStackNode`
+
+        :param affinity_group_list: List of CloudStackAffinityGroup to
+                                    associate
+        :type affinity_group_list: ``list`` of :class:`CloudStackAffinityGroup`
+
+        :rtype :class:`CloudStackNode`
+        """
+        affinity_groups = ','.join(ag.id for ag in affinity_group_list)
+
+        result = self._async_request(command='updateVMAffinityGroup',
+                                     params={
+                                         'id': node.id,
+                                         'affinitygroupids': affinity_groups},
+                                     method='GET')
+        return self._to_node(data=result['virtualmachine'])
+
+    def ex_list_affinity_groups(self):
+        """
+        List Affinity Groups
+
+        :rtype ``list`` of :class:`CloudStackAffinityGroup`
+        """
+        result = self._sync_request(command='listAffinityGroups', method='GET')
+
+        if not result.get('count'):
+            return []
+
+        affinity_groups = []
+        for ag in result['affinitygroup']:
+            affinity_groups.append(self._to_affinity_group(ag))
+
+        return affinity_groups
+
+    def ex_list_affinity_group_types(self):
+        """
+        List Affinity Group Types
+
+        :rtype ``list`` of :class:`CloudStackAffinityGroupTypes`
+        """
+        result = self._sync_request(command='listAffinityGroupTypes',
+                                    method='GET')
+
+        if not result.get('count'):
+            return []
+
+        affinity_group_types = []
+        for agt in result['affinityGroupType']:
+            affinity_group_types.append(
+                CloudStackAffinityGroupType(agt['type']))
+
+        return affinity_group_types
+
     def ex_register_iso(self, name, url, location=None, **kwargs):
         """
         Registers an existing ISO by URL.
@@ -3984,6 +4178,11 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         if security_groups:
             security_groups = [sg['name'] for sg in security_groups]
 
+        affinity_groups = data.get('affinitygroup', [])
+
+        if affinity_groups:
+            affinity_groups = [ag['id'] for ag in affinity_groups]
+
         created = data.get('created', False)
 
         extra = self._get_extra_dict(data,
@@ -3991,6 +4190,7 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
 
         # Add additional parameters to extra
         extra['security_group'] = security_groups
+        extra['affinity_group'] = affinity_groups
         extra['ip_addresses'] = []
         extra['ip_forwarding_rules'] = []
         extra['port_forwarding_rules'] = []
@@ -4016,6 +4216,19 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
                            driver=self)
         return key_pair
 
+    def _to_affinity_group(self, data):
+        affinity_group = CloudStackAffinityGroup(
+            id=data['id'],
+            name=data['name'],
+            group_type=CloudStackAffinityGroupType(data['type']),
+            account=data.get('account', ''),
+            domain=data.get('domain', ''),
+            domainid=data.get('domainid', ''),
+            description=data.get('description', ''),
+            virtualmachine_ids=data.get('virtualmachineIds', ''))
+
+        return affinity_group
+
     def _get_resource_tags(self, tag_set):
         """
         Parse tags from the provided element and return a dictionary with

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/createAffinityGroup_default.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/createAffinityGroup_default.json b/libcloud/test/compute/fixtures/cloudstack/createAffinityGroup_default.json
new file mode 100644
index 0000000..e853e2a
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/createAffinityGroup_default.json
@@ -0,0 +1 @@
+{"createaffinitygroupresponse": { "jobid" : 1300004}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/deleteAffinityGroup_default.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/deleteAffinityGroup_default.json b/libcloud/test/compute/fixtures/cloudstack/deleteAffinityGroup_default.json
new file mode 100644
index 0000000..1328cf0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/deleteAffinityGroup_default.json
@@ -0,0 +1 @@
+{"deleteaffinitygroupresponse": { "jobid" : 1300005}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/listAffinityGroupTypes_default.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/listAffinityGroupTypes_default.json b/libcloud/test/compute/fixtures/cloudstack/listAffinityGroupTypes_default.json
new file mode 100644
index 0000000..1366745
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/listAffinityGroupTypes_default.json
@@ -0,0 +1 @@
+{ "listaffinitygrouptypesresponse" : {"count": 1, "affinityGroupType": [{"type": "MyAGType"}] }}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/listAffinityGroups_default.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/listAffinityGroups_default.json b/libcloud/test/compute/fixtures/cloudstack/listAffinityGroups_default.json
new file mode 100644
index 0000000..2955f68
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/listAffinityGroups_default.json
@@ -0,0 +1 @@
+{ "listaffinitygroupsresponse" : {"count": 1, "affinitygroup" : [{"id":"11112", "name": "MyAG", "type": "MyAGType"}] }}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300004.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300004.json b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300004.json
new file mode 100644
index 0000000..cd4ece0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300004.json
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":1300004,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"affinitygroup" : {"id":"11113", "name": "MyAG2", "type": "MyAGType"} }} }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300005.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300005.json b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300005.json
new file mode 100644
index 0000000..8c19147
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300005.json
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":1300005,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"success" : true }} }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300006.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300006.json b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300006.json
new file mode 100644
index 0000000..4d022d0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_1300006.json
@@ -0,0 +1,62 @@
+{ "queryasyncjobresultresponse": {
+    "accountid": "86d47ca2-726b-4b85-a18a-77d6b0d79829",
+    "userid": "20cd68f5-0633-48a5-826e-e4e2a00dd6b8",
+    "jobstatus": 1,
+    "jobprocstatus": 0,
+    "jobresultcode": 0,
+    "jobresulttype": "object",
+    "jobresult": {
+        "virtualmachine": {
+            "id": "19253fbf-abb7-4013-a8a1-97df3b93f206",
+            "name": "TestNode",
+            "projectid": "b90442d1-079b-4066-ab7d-41f8f3a5078b",
+            "project": "Test Project",
+            "domainid": "dc0314d4-09aa-4e8f-8a54-419ecf344635",
+            "domain": "Test Domain",
+            "created": "2014-03-06T15:39:44-0600",
+            "state": "Running",
+            "haenable": false,
+            "zoneid": "d630b15a-a9e1-4641-bee8-355005b7a14d",
+            "zonename": "TestZone",
+            "templateid": "a032e8a0-3411-48b7-9e78-ff66823e6561",
+            "templatename": "OL-6.3.1-64-13.11.01",
+            "templatedisplaytext": "OL-6.3.1-64-13.11.01",
+            "passwordenabled": true,
+            "serviceofferingid": "519f8667-26d0-40e5-a1cd-da04be1fd9b5",
+            "serviceofferingname": "Test Service Offering",
+            "cpunumber": 1,
+            "cpuspeed": 2000,
+            "memory": 2000,
+            "guestosid": "b8506c91-6d8e-4086-8659-f6296a7b71ac",
+            "rootdeviceid": 0,
+            "rootdevicetype": "ROOT",
+            "securitygroup": [],
+            "password": "mW6crjxag",
+            "nic": [
+                {
+                    "id": "1c144283-979a-4359-b695-3334dc403457",
+                    "networkid": "1bf4acce-19a5-4830-ab1d-444f8acb9986",
+                    "networkname": "Public",
+                    "netmask": "255.255.252.0",
+                    "gateway": "10.1.2.2",
+                    "ipaddress": "10.2.2.8",
+                    "isolationuri": "vlan://2950",
+                    "broadcasturi": "vlan://2950",
+                    "traffictype": "Guest",
+                    "type": "Shared",
+                    "isdefault": true,
+                    "macaddress": "06:ef:30:00:04:22"
+                }
+            ],
+            "hypervisor": "VMware",
+            "tags": [],
+            "affinitygroup" : [{"id":"11112", "name": "MyAG", "type": "MyAGType"}],
+            "displayvm": true,
+            "isdynamicallyscalable": false,
+            "jobid": "e23b9f0c-b7ae-4ffe-aea0-c9cf436cc315",
+            "jobstatus": 0
+        }
+    },
+    "created": "2014-03-06T15:39:44-0600",
+    "jobid": "e23b9f5c-b7ae-4ffe-aea0-c9cf436dc315"
+} }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/fixtures/cloudstack/updateVMAffinityGroup_default.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/updateVMAffinityGroup_default.json b/libcloud/test/compute/fixtures/cloudstack/updateVMAffinityGroup_default.json
new file mode 100644
index 0000000..825364b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/updateVMAffinityGroup_default.json
@@ -0,0 +1 @@
+{"updatevmaffinitygroupresponse": { "jobid" : 1300006}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/52d96c7f/libcloud/test/compute/test_cloudstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_cloudstack.py b/libcloud/test/compute/test_cloudstack.py
index 5726a75..8eff8a9 100644
--- a/libcloud/test/compute/test_cloudstack.py
+++ b/libcloud/test/compute/test_cloudstack.py
@@ -26,7 +26,8 @@ except ImportError:
     import json
 
 from libcloud.common.types import ProviderError
-from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver
+from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver, \
+    CloudStackAffinityGroupType
 from libcloud.compute.types import LibcloudError, Provider, InvalidCredsError
 from libcloud.compute.types import KeyPairDoesNotExistError
 from libcloud.compute.types import NodeState
@@ -736,6 +737,46 @@ class CloudStackCommonTestCase(TestCaseMixin):
                                                               '0.0.0.0/0')
         self.assertTrue(res)
 
+    def test_ex_create_affinity_group(self):
+        res = self.driver.ex_create_affinity_group('MyAG2',
+                                                   CloudStackAffinityGroupType('MyAGType'))
+        self.assertEqual(res.name, 'MyAG2')
+        self.assertIsInstance(res.type, CloudStackAffinityGroupType)
+        self.assertEqual(res.type.type, 'MyAGType')
+
+    def test_ex_create_affinity_group_already_exists(self):
+        self.assertRaises(LibcloudError,
+                          self.driver.ex_create_affinity_group,
+                          'MyAG', CloudStackAffinityGroupType('MyAGType'))
+
+    def test_delete_ex_affinity_group(self):
+        afg = self.driver.ex_create_affinity_group('MyAG3',
+                                                   CloudStackAffinityGroupType('MyAGType'))
+        res = self.driver.ex_delete_affinity_group(afg)
+        self.assertTrue(res)
+
+    def test_ex_update_node_affinity_group(self):
+        affinity_group_list = self.driver.ex_list_affinity_groups()
+        nodes = self.driver.list_nodes()
+        node = self.driver.ex_update_node_affinity_group(nodes[0],
+                                                         affinity_group_list)
+        self.assertEqual(node.extra['affinity_group'][0],
+                         affinity_group_list[0].id)
+
+    def test_ex_list_affinity_groups(self):
+        res = self.driver.ex_list_affinity_groups()
+        self.assertEqual(len(res), 1)
+        self.assertEqual(res[0].id, '11112')
+        self.assertEqual(res[0].name, 'MyAG')
+        self.assertIsInstance(res[0].type, CloudStackAffinityGroupType)
+        self.assertEqual(res[0].type.type, 'MyAGType')
+
+    def test_ex_list_affinity_group_types(self):
+        res = self.driver.ex_list_affinity_group_types()
+        self.assertEqual(len(res), 1)
+        self.assertIsInstance(res[0], CloudStackAffinityGroupType)
+        self.assertEqual(res[0].type, 'MyAGType')
+
     def test_ex_list_public_ips(self):
         ips = self.driver.ex_list_public_ips()
         self.assertEqual(ips[0].address, '1.1.1.116')