From e92be92837ff640e410b1fe91a438bf14c07594e Mon Sep 17 00:00:00 2001
From: Philipp Strube <pst@cloudcontrol.de>
Date: Mon, 24 Jun 2013 15:03:04 -0700
Subject: [PATCH] Implementation of list_images and list_network for Cloudstack
 driver.

---
 libcloud/compute/drivers/cloudstack.py             | 48 ++++++++++++++++++----
 .../fixtures/cloudstack/listNetworks_default.json  |  2 +-
 libcloud/test/compute/test_cloudstack.py           | 47 ++++++++++++++++++++-
 3 files changed, 87 insertions(+), 10 deletions(-)

diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py
index 74799c6..d8f28de 100644
--- a/libcloud/compute/drivers/cloudstack.py
+++ b/libcloud/compute/drivers/cloudstack.py
@@ -99,6 +99,23 @@ class CloudStackDiskOffering(object):
         return self.__class__ is other.__class__ and self.id == other.id
 
 
+class CloudStackNetwork(object):
+    """Class representing a CloudStack Network"""
+
+    def __init__(self, displaytext, name, networkofferingid, id, zoneid):
+        self.displaytext = displaytext
+        self.name = name
+        self.networkofferingid = networkofferingid
+        self.id = id
+        self.zoneid = zoneid
+
+    def __repr__(self):
+        return (('<CloudStackNetwork: id=%s, displaytext=%s, name=%s, '
+                 'networkofferingid=%s, zoneid=%s, dirver=%s>')
+                % (self.id, self.displaytext, self.name,
+                   self.networkofferingid, self.zoneid, self.driver.name))
+
+
 class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
     """Driver for the CloudStack API.
 
@@ -157,13 +174,14 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         imgs = self._sync_request('listTemplates', **args)
         images = []
         for img in imgs.get('template', []):
-            images.append(NodeImage(img['id'], img['name'], self,
-                                    {'hypervisor': img['hypervisor'],
-                                     'format': img['format'],
-                                     'os': img['ostypename'],
-                                     }
-                                    )
-                          )
+            images.append(NodeImage(
+                id=img['id'],
+                name=img['name'],
+                driver=self.connection.driver,
+                extra={
+                    'hypervisor': img['hypervisor'],
+                    'format': img['format'],
+                    'os': img['ostypename']}))
         return images
 
     def list_locations(self):
@@ -365,6 +383,22 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
 
         return diskOfferings
 
+    def ex_list_networks(self):
+        """List the available networks"""
+
+        nets = self._sync_request('listNetworks')['network']
+
+        networks = []
+        for net in nets:
+            networks.append(CloudStackNetwork(
+                net['displaytext'],
+                net['name'],
+                net['networkofferingid'],
+                net['id'],
+                net['zoneid']))
+
+        return networks
+
     def create_volume(self, size, name, location, snapshot=None):
         # TODO Add snapshot handling
         for diskOffering in self.ex_list_disk_offerings():
diff --git a/libcloud/test/compute/fixtures/cloudstack/listNetworks_default.json b/libcloud/test/compute/fixtures/cloudstack/listNetworks_default.json
index 2701347..7f696f0 100644
--- a/libcloud/test/compute/fixtures/cloudstack/listNetworks_default.json
+++ b/libcloud/test/compute/fixtures/cloudstack/listNetworks_default.json
@@ -1 +1 @@
-{ "listnetworksresponse" : { "network" : [  {"id":860,"name":"Virtual Network","displaytext":"A dedicated virtualized network for your account.  The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router.","broadcastdomaintype":"Vlan","traffictype":"Guest","zoneid":1,"networkofferingid":6,"networkofferingname":"DefaultVirtualizedNetworkOffering","networkofferingdisplaytext":"Virtual Vlan","networkofferingavailability":"Required","isshared":false,"issystem":false,"state":"Implemented","related":860,"broadcasturi":"vlan://1459","dns1":"1.1.1.1","dns2":"1.1.1.2","type":"Virtual","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","isdefault":true,"service":[{"name":"Gateway"},{"name":"Firewall","capability":[{"name":"MultipleIps","value":"true"},{"name":"TrafficStatistics","value":"per public ip"},{"name":"StaticNat","value":"true"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedSourceNatTypes","value":"per account"}]},{"name":"UserData"},{"name":"Dns"},{"name":"Dhcp"},{"name":"Lb","capability":[{"name":"TrafficStatistics","value":"per public ip"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedLbAlgorithms","value":"roundrobin,leastconn"}]}],"networkdomain":"cs363local","securitygroupenabled":false} ] } }
+{ "listnetworksresponse" : {"count": 1, "network": [{"domain": "ROOT", "acltype": "Domain", "specifyipranges": true, "related": "00304a04-c7ea-4e77-a786-18bc64347bf7", "zoneid": "1128bd56-b4d9-4ac6-a7b9-c715b187ce11", "domainid": "4a8857b8-7235-4e31-a7ef-b8b44d180850", "displaytext": "guestNetworkForBasicZone", "id": "00304a04-c7ea-4e77-a786-18bc64347bf7", "canusefordeploy": true, "physicalnetworkid": "07f747f5-b445-487f-b2d7-81a5a512989e", "networkdomain": "cs1cloud.internal", "service": [{"name": "SecurityGroup"}, {"name": "UserData"}, {"name": "Dhcp"}], "state": "Setup", "type": "Shared", "zonename": "CH-GV2", "networkofferingavailability": "Optional", "networkofferingid": "45964a3a-8a1c-4438-a377-0ff1e264047a", "tags": [], "networkofferingdisplaytext": "Exoscale Offering for Shared Security group enabled networks", "subdomainaccess": true, "traffictype": "Guest", "restartrequired": false, "broadcastdomaintype": "Vlan", "name": "guestNetworkForBasicZone", "dns2": "80.245.17.230", "dns1": "80.245.17.229", "networkofferingname": "ExoscaleSharedNetworkOfferingWithSGService", "issystem": false}]} }
\ No newline at end of file
diff --git a/libcloud/test/compute/test_cloudstack.py b/libcloud/test/compute/test_cloudstack.py
index 864cff4..d97a316 100644
--- a/libcloud/test/compute/test_cloudstack.py
+++ b/libcloud/test/compute/test_cloudstack.py
@@ -20,6 +20,8 @@ from libcloud.utils.py3 import httplib
 from libcloud.utils.py3 import urlparse
 from libcloud.utils.py3 import parse_qsl
 
+from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver
+
 try:
     import simplejson as json
 except ImportError:
@@ -90,6 +92,23 @@ class CloudStackNodeDriverTest(unittest.TestCase, TestCaseMixin):
         images = self.driver.list_images()
         self.assertEquals(0, len(images))
 
+    def test_list_images(self):
+        _, fixture = CloudStackMockHttp()._load_fixture(
+            'listTemplates_default.json')
+        templates = fixture['listtemplatesresponse']['template']
+
+        images = self.driver.list_images()
+        for i, image in enumerate(images):
+            # NodeImage expects id to be a string,
+            # the CloudStack fixture has an int
+            tid = str(templates[i]['id'])
+            tname = templates[i]['name']
+            self.assertIsInstance(image.id, basestring)
+            self.assertIsInstance(image.name, basestring)
+            self.assertIsInstance(image.driver, CloudStackNodeDriver)
+            self.assertEquals(image.id, tid)
+            self.assertEquals(image.name, tname)
+
     def test_ex_list_disk_offerings(self):
         diskOfferings = self.driver.ex_list_disk_offerings()
         self.assertEquals(1, len(diskOfferings))
@@ -99,6 +118,28 @@ class CloudStackNodeDriverTest(unittest.TestCase, TestCaseMixin):
         self.assertEquals('Disk offer 1', diskOffering.name)
         self.assertEquals(10, diskOffering.size)
 
+    def test_ex_list_networks(self):
+        _, fixture = CloudStackMockHttp()._load_fixture(
+            'listNetworks_default.json')
+        fixture_networks = fixture['listnetworksresponse']['network']
+
+        networks = self.driver.ex_list_networks()
+
+        for i, network in enumerate(networks):
+            self.assertIsInstance(network.id, basestring)
+            self.assertIsInstance(network.displaytext, basestring)
+            self.assertIsInstance(network.name, basestring)
+            self.assertIsInstance(network.networkofferingid, basestring)
+            self.assertIsInstance(network.zoneid, basestring)
+            self.assertEquals(network.id, fixture_networks[i]['id'])
+            self.assertEquals(
+                network.displaytext, fixture_networks[i]['displaytext'])
+            self.assertEquals(network.name, fixture_networks[i]['name'])
+            self.assertEquals(
+                network.networkofferingid,
+                fixture_networks[i]['networkofferingid'])
+            self.assertEquals(network.zoneid, fixture_networks[i]['zoneid'])
+
     def test_create_volume(self):
         volumeName = 'vol-0'
         location = self.driver.list_locations()[0]
@@ -167,8 +208,10 @@ class CloudStackNodeDriverTest(unittest.TestCase, TestCaseMixin):
         self.assertEqual(keypairs[0]['fingerprint'], fingerprint)
 
     def test_create_keypair(self):
-        self.assertRaises(LibcloudError, self.driver.ex_create_keypair,
-                'cs-keypair')
+        self.assertRaises(
+            LibcloudError,
+            self.driver.ex_create_keypair,
+            'cs-keypair')
 
     def test_delete_keypair(self):
         res = self.driver.ex_delete_keypair('cs-keypair')
-- 
1.8.1.4

