Repository: libcloud Updated Branches: refs/heads/trunk 62864f52e -> 52d96c7f5
Add Affinity Group support to CloudStack Signed-off-by: Sebastien Goasguen <run...@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 <mkors...@gmail.com> Authored: Tue Feb 17 12:20:39 2015 +0100 Committer: Sebastien Goasguen <run...@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')