Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 145 additions & 14 deletions libcloud/compute/drivers/cloudstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@
'hypervisor': {
'key_name': 'hypervisor',
'transform_func': str
},
'project': {
'key_name': 'project',
'transform_func': str
},
'project_id': {
'key_name': 'projectid',
'transform_func': str
}
},
'volume': {
Expand Down Expand Up @@ -188,6 +196,58 @@
'key_name': 'zonename',
'transform_func': str
}
},
'project': {
'account': {'key_name': 'account', 'transform_func': str},
'cpuavailable': {'key_name': 'cpuavailable', 'transform_func': int},
'cpulimit': {'key_name': 'cpulimit', 'transform_func': int},
'cputotal': {'key_name': 'cputotal', 'transform_func': int},
'domain': {'key_name': 'domain', 'transform_func': str},
'domainid': {'key_name': 'domainid', 'transform_func': str},
'ipavailable': {'key_name': 'ipavailable', 'transform_func': int},
'iplimit': {'key_name': 'iplimit', 'transform_func': int},
'iptotal': {'key_name': 'iptotal', 'transform_func': int},
'memoryavailable': {'key_name': 'memoryavailable',
'transform_func': int},
'memorylimit': {'key_name': 'memorylimit', 'transform_func': int},
'memorytotal': {'key_name': 'memorytotal', 'transform_func': int},
'networkavailable': {'key_name': 'networkavailable',
'transform_func': int},
'networklimit': {'key_name': 'networklimit', 'transform_func': int},
'networktotal': {'key_name': 'networktotal', 'transform_func': int},
'primarystorageavailable': {'key_name': 'primarystorageavailable',
'transform_func': int},
'primarystoragelimit': {'key_name': 'primarystoragelimit',
'transform_func': int},
'primarystoragetotal': {'key_name': 'primarystoragetotal',
'transform_func': int},
'secondarystorageavailable': {'key_name': 'secondarystorageavailable',
'transform_func': int},
'secondarystoragelimit': {'key_name': 'secondarystoragelimit',
'transform_func': int},
'secondarystoragetotal': {'key_name': 'secondarystoragetotal',
'transform_func': int},
'snapshotavailable': {'key_name': 'snapshotavailable',
'transform_func': int},
'snapshotlimit': {'key_name': 'snapshotlimit', 'transform_func': int},
'snapshottotal': {'key_name': 'snapshottotal', 'transform_func': int},
'state': {'key_name': 'state', 'transform_func': str},
'tags': {'key_name': 'tags', 'transform_func': str},
'templateavailable': {'key_name': 'templateavailable',
'transform_func': int},
'templatelimit': {'key_name': 'templatelimit', 'transform_func': int},
'templatetotal': {'key_name': 'templatetotal', 'transform_func': int},
'vmavailable': {'key_name': 'vmavailable', 'transform_func': int},
'vmlimit': {'key_name': 'vmlimit', 'transform_func': int},
'vmrunning': {'key_name': 'vmrunning', 'transform_func': int},
'vmtotal': {'key_name': 'vmtotal', 'transform_func': int},
'volumeavailable': {'key_name': 'volumeavailable',
'transform_func': int},
'volumelimit': {'key_name': 'volumelimit', 'transform_func': int},
'volumetotal': {'key_name': 'volumetotal', 'transform_func': int},
'vpcavailable': {'key_name': 'vpcavailable', 'transform_func': int},
'vpclimit': {'key_name': 'vpclimit', 'transform_func': int},
'vpctotal': {'key_name': 'vpctotal', 'transform_func': int}
}
}

Expand Down Expand Up @@ -402,6 +462,25 @@ def __repr__(self):
self.networkofferingid, self.zoneid, self.driver.name))


class CloudStackProject(object):
"""
Class representing a CloudStack Project.
"""

def __init__(self, id, name, display_text, driver, extra=None):
self.id = id
self.name = name
self.display_text = display_text
self.driver = driver
self.extra = extra or {}

def __repr__(self):
return (('<CloudStackProject: id=%s, display_text=%s, name=%s, '
'driver=%s>')
% (self.id, self.display_text, self.name,
self.driver.name))


class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
"""
Driver for the CloudStack API.
Expand Down Expand Up @@ -516,13 +595,22 @@ def list_locations(self):

return locations

def list_nodes(self):
def list_nodes(self, project=None):
"""
@inherits: :class:`NodeDriver.list_nodes`

:keyword project: Limit nodes returned to those configured under
the defined project.
:type project: :class:`.CloudStackProject`

:rtype: ``list`` of :class:`CloudStackNode`
"""
vms = self._sync_request('listVirtualMachines')
addrs = self._sync_request('listPublicIpAddresses')

args = {}
if project:
args['projectid'] = project.id
vms = self._sync_request('listVirtualMachines', params=args)
addrs = self._sync_request('listPublicIpAddresses', params=args)

public_ips_map = {}
for addr in addrs.get('publicipaddress', []):
Expand Down Expand Up @@ -604,6 +692,13 @@ def create_node(self, **kwargs):
into.
:type networks: ``list`` of :class:`.CloudStackNetwork`

:keyword project: Optional project to create the new node under.
:type project: :class:`.CloudStackProject`

:keyword diskoffering: Optional disk offering to add to the new
node.
:type diskoffering: :class:`.CloudStackDiskOffering`

:keyword ex_keyname: Name of existing keypair
:type ex_keyname: ``str``

Expand Down Expand Up @@ -636,6 +731,8 @@ def _create_args_to_params(self, node, **kwargs):
image = kwargs.get('image', None)
location = kwargs.get('location', None)
networks = kwargs.get('networks', None)
project = kwargs.get('project', None)
diskoffering = kwargs.get('diskoffering', None)
ex_key_name = kwargs.get('ex_keyname', None)
ex_user_data = kwargs.get('ex_userdata', None)
ex_security_groups = kwargs.get('ex_security_groups', None)
Expand All @@ -659,6 +756,12 @@ def _create_args_to_params(self, node, **kwargs):
networks = ','.join([network.id for network in networks])
server_params['networkids'] = networks

if project:
server_params['projectid'] = project.id

if diskoffering:
server_params['diskofferingid'] = diskoffering.id

if ex_key_name:
server_params['keypair'] = ex_key_name

Expand Down Expand Up @@ -771,16 +874,44 @@ def ex_list_networks(self):
extra['tags'] = self._get_resource_tags(net['tags'])

networks.append(CloudStackNetwork(
net['displaytext'],
net['name'],
net['networkofferingid'],
net['id'],
net['zoneid'],
self,
extra=extra))
net['displaytext'],
net['name'],
net['networkofferingid'],
net['id'],
net['zoneid'],
self,
extra=extra))

return networks

def ex_list_projects(self):
"""
List the available projects

:rtype ``list`` of :class:`CloudStackProject`
"""

res = self._sync_request(command='listProjects',
method='GET')
projs = res.get('project', [])

projects = []
extra_map = RESOURCE_EXTRA_ATTRIBUTES_MAP['project']
for proj in projs:
extra = self._get_extra_dict(proj, extra_map)

if 'tags' in proj:
extra['tags'] = self._get_resource_tags(proj['tags'])

projects.append(CloudStackProject(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor style thing - please prefer keyword over regular arguments. So:

CloudStackProject(id=proj['id'], ...)

id=proj['id'],
name=proj['name'],
display_text=proj['displaytext'],
driver=self,
extra=extra))

return projects

def create_volume(self, size, name, location=None, snapshot=None):
"""
Creates a data volume
Expand Down Expand Up @@ -873,10 +1004,10 @@ def list_volumes(self, node=None):
extra['tags'] = self._get_resource_tags(vol['tags'])

list_volumes.append(StorageVolume(id=vol['id'],
name=vol['name'],
size=vol['size'],
driver=self,
extra=extra))
name=vol['name'],
size=vol['size'],
driver=self,
extra=extra))
return list_volumes

def list_key_pairs(self, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "deployvirtualmachineresponse" : {"jobid":11117,"id":"19253fbf-abb7-4013-a8a1-97df3b93f206"} }
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{ "listprojectsresponse": {
"count": 1,
"project": [
{
"id": "e7ce14a8-abb7-48b9-931f-5f426febed6d",
"name": "ExampleProjectName",
"displaytext": "ExampleProject",
"account": "CB0512",
"cpuavailable": 18,
"cpulimit": 32,
"cputotal": 14,
"domain": "ExampleDomain",
"domainid": "dc0314d4-09aa-4e8f-8a54-419ecf344635",
"ipavailable": 8,
"iplimit": 8,
"iptotal": 0,
"memoryavailable": 72000,
"memorylimit": 128000,
"memorytotal": 56000,
"networkavailable": 0,
"networklimit": 0,
"networktotal": 0,
"primarystorageavailable": 1204,
"primarystoragelimit": 1600,
"primarystoragetotal": 396,
"secondarystorageavailable": 3817,
"secondarystoragelimit": 4000,
"secondarystoragetotal": 183,
"snapshotavailable": 17,
"snapshotlimit": 20,
"snapshottotal": 3,
"state": "Active",
"tags": [],
"templateavailable": 17,
"templatelimit": 20,
"templatetotal": 3,
"vmavailable": 1,
"vmlimit": 8,
"vmrunning": 7,
"vmtotal": 7,
"volumeavailable": 1,
"volumelimit": 16,
"volumetotal": 15,
"vpcavailable": 20,
"vpclimit": 0,
"vpctotal": 0
}
]
} }
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{ "queryasyncjobresultresponse": {
"accountid": "86d47ca2-726b-4b85-a18a-77d6b0d79829",
"userid": "20cd68f5-0633-48a5-826e-e4e2a00dd6b8",
"cmd": "org.apache.cloudstack.api.command.user.vm.DeployVMCmd",
"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": [],
"displayvm": true,
"isdynamicallyscalable": false,
"jobid": "e23b9f0c-b7ae-4ffe-aea0-c9cf436cc315",
"jobstatus": 0
}
},
"created": "2014-03-06T15:39:44-0600",
"jobid": "e23b9f0c-b7ae-4ffe-aea0-c9cf436cc315"
} }
33 changes: 33 additions & 0 deletions libcloud/test/compute/test_cloudstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@ def test_create_node_ex_keyname(self):
self.assertEqual(node.name, 'test')
self.assertEqual(node.extra['key_name'], 'foobar')

def test_create_node_project(self):
size = self.driver.list_sizes()[0]
image = self.driver.list_images()[0]
location = self.driver.list_locations()[0]
project = self.driver.ex_list_projects()[0]
CloudStackMockHttp.fixture_tag = 'deployproject'
node = self.driver.create_node(name='test',
location=location,
image=image,
size=size,
project=project)
self.assertEqual(node.name, 'TestNode')
self.assertEqual(node.extra['project'], 'Test Project')

def test_list_images_no_images_available(self):
CloudStackMockHttp.fixture_tag = 'notemplates'

Expand Down Expand Up @@ -182,6 +196,25 @@ def test_ex_list_networks(self):
fixture_networks[i]['networkofferingid'])
self.assertEqual(network.zoneid, fixture_networks[i]['zoneid'])

def test_ex_list_projects(self):
_, fixture = CloudStackMockHttp()._load_fixture(
'listProjects_default.json')
fixture_projects = fixture['listprojectsresponse']['project']

projects = self.driver.ex_list_projects()

for i, project in enumerate(projects):
self.assertEqual(project.id, fixture_projects[i]['id'])
self.assertEqual(
project.display_text, fixture_projects[i]['displaytext'])
self.assertEqual(project.name, fixture_projects[i]['name'])
self.assertEqual(
project.extra['domainid'],
fixture_projects[i]['domainid'])
self.assertEqual(
project.extra['cpulimit'],
fixture_projects[i]['cpulimit'])

def test_create_volume(self):
volumeName = 'vol-0'
location = self.driver.list_locations()[0]
Expand Down