Author: tomaz
Date: Thu Jun  6 20:16:15 2013
New Revision: 1490422

URL: http://svn.apache.org/r1490422
Log:
Backport changes from trunk.

Added:
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/authorizeSecurityGroupIngress_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/authorizeSecurityGroupIngress_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/createSecurityGroup_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/createSecurityGroup_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/deleteSSHKeyPair_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/deleteSSHKeyPair_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/deleteSecurityGroup_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/deleteSecurityGroup_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/listSecurityGroups_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/listSecurityGroups_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_17188.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_17188.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_17199.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_17199.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_17200.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_17200.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/startVirtualMachine_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/startVirtualMachine_default.json
    
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/cloudstack/stopVirtualMachine_default.json
      - copied unchanged from r1490413, 
libcloud/trunk/libcloud/test/compute/fixtures/cloudstack/stopVirtualMachine_default.json
Modified:
    libcloud/branches/0.12.x/   (props changed)
    libcloud/branches/0.12.x/CHANGES
    libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py
    libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py

Propchange: libcloud/branches/0.12.x/
------------------------------------------------------------------------------
  Merged /libcloud/trunk:r1490082-1490416

Modified: libcloud/branches/0.12.x/CHANGES
URL: 
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/CHANGES?rev=1490422&r1=1490421&r2=1490422&view=diff
==============================================================================
--- libcloud/branches/0.12.x/CHANGES (original)
+++ libcloud/branches/0.12.x/CHANGES Thu Jun  6 20:16:15 2013
@@ -66,6 +66,18 @@ Changes with Apache Libcloud in deveplom
       when generating a random root password in create_node. (LIBCLOUD-334)
       [Juan Carlos Moreno]
 
+    - Add extension methods for managing keypairs to the CloudStack driver.
+      (LIBCLOUD-333)
+      [sebastien goasguen]
+
+    - Add extension methods for managing security groups to the CloudStack 
+      driver. (LIBCLOUD-332)
+      [sebastien goasguen]
+
+    - Add extension methods for starting and stoping the node to the 
+      CloudStack driver. (LIBCLOUD-338)
+      [sebastien goasguen]
+
  *) Storage
 
     - Fix an issue with double encoding the container name in the CloudFiles

Modified: libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py
URL: 
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py?rev=1490422&r1=1490421&r2=1490422&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py (original)
+++ libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py Thu Jun  6 
20:16:15 2013
@@ -41,6 +41,14 @@ class CloudStackNode(Node):
         "Delete a NAT/firewall rule."
         return self.driver.ex_delete_ip_forwarding_rule(self, rule)
 
+    def ex_start(self):
+        "Starts a stopped virtual machine"
+        return self.driver.ex_start(self)
+
+    def ex_stop(self):
+        "Stops a running virtual machine"
+        return self.driver.ex_stop(self)
+
 
 class CloudStackAddress(object):
     "A public IP address."
@@ -252,7 +260,7 @@ class CloudStackNodeDriver(CloudStackDri
             location = self.list_locations()[0]
 
         if 'network_id' in kwargs:
-            request_args['networkids'] = network_id
+            request_args['networkids'] = kwargs['network_id']
 
         result = self._async_request(
             'deployVirtualMachine', name=name, displayname=name,
@@ -296,8 +304,47 @@ class CloudStackNodeDriver(CloudStackDri
         self._async_request('rebootVirtualMachine', id=node.id)
         return True
 
+    def ex_start(self, node):
+        """
+        Starts/Resumes a stopped virtual machine
+
+        @type node: L{CloudStackNode}
+
+        @param id: The ID of the virtual machine (required)
+        @type  id: C{uuid}
+
+        @param hostid: destination Host ID to deploy the VM to
+                       parameter available for root admin only
+        @type  hostid: C{uuid}
+
+        @rtype C{str}
+        """
+        res = self._async_request('startVirtualMachine', id=node.id)
+        return res['virtualmachine']['state']
+
+    def ex_stop(self, node):
+        """
+        Stops/Suspends a running virtual machine
+
+        @type node: L{CloudStackNode}
+
+        @param id: The ID of the virtual machine
+        @type  id: C{uuid}
+
+        @param forced: Force stop the VM
+                       (vm is marked as Stopped even when command
+                        fails to be send to the backend).
+                       The caller knows the VM is stopped.
+        @type  forced: C{bool}
+
+        @rtype C{str}
+        """
+        res = self._async_request('stopVirtualMachine', id=node.id)
+        return res['virtualmachine']['state']
+
     def ex_list_disk_offerings(self):
-        """Fetch a list of all available disk offerings.
+        """
+        Fetch a list of all available disk offerings.
 
         @rtype: C{list} of L{CloudStackDiskOffering}
         """
@@ -362,7 +409,7 @@ class CloudStackNodeDriver(CloudStackDri
 
     def ex_allocate_public_ip(self, node):
         """
-        "Allocate a public IP and bind it to a node.
+        Allocate a public IP and bind it to a node.
 
         @param node: Node which should be used
         @type  node: L{CloudStackNode}
@@ -463,6 +510,299 @@ class CloudStackNodeDriver(CloudStackDri
         self._async_request('deleteIpForwardingRule', id=rule.id)
         return True
 
+    def ex_list_keypairs(self, **kwargs):
+        """
+        List Registered SSH Key Pairs
+
+        @param     projectid: list objects by project
+        @type      projectid: C{uuid}
+
+        @param     page: The page to list the keypairs from
+        @type      page: C{int}
+
+        @param     keyword: List by keyword
+        @type      keyword: C{str}
+
+        @param     listall: If set to false, list only resources
+                            belonging to the command's caller;
+                            if set to true - list resources that
+                            the caller is authorized to see.
+                            Default value is false
+
+        @type      listall: C{bool}
+
+        @param     pagesize: The number of results per page
+        @type      pagesize: C{int}
+
+        @param     account: List resources by account.
+                            Must be used with the domainId parameter
+        @type      account: C{str}
+
+        @param     isrecursive: Defaults to false, but if true,
+                                lists all resources from
+                                the parent specified by the
+                                domainId till leaves.
+        @type      isrecursive: C{bool}
+
+        @param     fingerprint: A public key fingerprint to look for
+        @type      fingerprint: C{str}
+
+        @param     name: A key pair name to look for
+        @type      name: C{str}
+
+        @param     domainid: List only resources belonging to
+                                     the domain specified
+        @type      domainid: C{uuid}
+
+        @return:   A list of keypair dictionaries
+        @rtype:    L{dict}
+        """
+
+        extra_args = kwargs.copy()
+        res = self._sync_request('listSSHKeyPairs', **extra_args)
+        return res['sshkeypair']
+
+    def ex_create_keypair(self, name, **kwargs):
+        """
+        Creates a SSH KeyPair, returns fingerprint and private key
+
+        @param     name: Name of the keypair (required)
+        @type      name: C{str}
+
+        @param     projectid: An optional project for the ssh key
+        @type      projectid: C{str}
+
+        @param     domainid: An optional domainId for the ssh key.
+                             If the account parameter is used,
+                             domainId must also be used.
+        @type      domainid: C{str}
+
+        @param     account: An optional account for the ssh key.
+                            Must be used with domainId.
+        @type      account: C{str}
+
+        @return:   A keypair dictionary
+        @rtype:    C{dict}
+        """
+        extra_args = kwargs.copy()
+
+        for keypair in self.ex_list_keypairs():
+            if keypair['name'] == name:
+                raise LibcloudError('SSH KeyPair with name=%s already exists'
+                                    % (name))
+
+        res = self._sync_request('createSSHKeyPair', name=name, **extra_args)
+        return res['keypair']
+
+    def ex_delete_keypair(self, name, **kwargs):
+        """
+        Deletes an existing SSH KeyPair
+
+        @param     name: Name of the keypair (required)
+        @type      name: C{str}
+
+        @param     projectid: The project associated with keypair
+        @type      projectid: C{uuid}
+
+        @param     domainid : The domain ID associated with the keypair
+        @type      domainid: C{uuid}
+
+        @param     account : The account associated with the keypair.
+                             Must be used with the domainId parameter.
+        @type      account: C{str}
+
+        @return:   True of False based on success of Keypair deletion
+        @rtype:    C{bool}
+        """
+
+        extra_args = kwargs.copy()
+
+        res = self._sync_request('deleteSSHKeyPair', name=name, **extra_args)
+        return res['success']
+
+    def ex_list_security_groups(self, **kwargs):
+        """
+        Lists Security Groups
+
+        @param domainid: List only resources belonging to the domain specified
+        @type  domainid: C{uuid}
+
+        @param account: List resources by account. Must be used with
+                                                   the domainId parameter.
+        @type  account: C{str}
+
+        @param listall: If set to false, list only resources belonging to
+                                         the command's caller; if set to true
+                                         list resources that the caller is
+                                         authorized to see.
+                                         Default value is false
+        @type  listall: C{bool}
+
+        @param pagesize: Number of entries per page
+        @type  pagesize: C{int}
+
+        @param keyword: List by keyword
+        @type  keyword: C{str}
+
+        @param tags: List resources by tags (key/value pairs)
+        @type  tags: C{dict}
+
+        @param id: list the security group by the id provided
+        @type  id: C{uuid}
+
+        @param securitygroupname: lists security groups by name
+        @type  securitygroupname: C{str}
+
+        @param virtualmachineid: lists security groups by virtual machine id
+        @type  virtualmachineid: C{uuid}
+
+        @param projectid: list objects by project
+        @type  projectid: C{uuid}
+
+        @param isrecursive: (boolean) defaults to false, but if true,
+                                      lists all resources from the parent
+                                      specified by the domainId till leaves.
+        @type  isrecursive: C{bool}
+
+        @param page: (integer)
+        @type  page: C{int}
+
+        @rtype C{list}
+        """
+        extra_args = kwargs
+        return self._sync_request('listSecurityGroups',
+                                  **extra_args)['securitygroup']
+
+    def ex_create_security_group(self, name, **kwargs):
+        """
+        Creates a new Security Group
+
+        @param name: name of the security group (required)
+        @type  name: C{str}
+
+        @param account: An optional account for the security group.
+                        Must be used with domainId.
+        @type  account: C{str}
+
+        @param domainid: An optional domainId for the security group.
+                         If the account parameter is used,
+                         domainId must also be used.
+        @type  domainid: C{uuid}
+
+        @param description: The description of the security group
+        @type  description: C{str}
+
+        @param projectid: Deploy vm for the project
+        @type  projectid: C{uuid}
+
+        @rtype: C{dict}
+        """
+
+        extra_args = kwargs.copy()
+
+        for sg in self.ex_list_security_groups():
+            if name in sg['name']:
+                raise LibcloudError('This Security Group name already exists')
+
+        return self._sync_request('createSecurityGroup',
+                                  name=name, **extra_args)['securitygroup']
+
+    def ex_delete_security_group(self, name):
+        """
+        Deletes a given Security Group
+
+        @param domainid: The domain ID of account owning
+                         the security group
+        @type  domainid: C{uuid}
+
+        @param id: The ID of the security group.
+                   Mutually exclusive with name parameter
+        @type  id: C{uuid}
+
+        @param name: The ID of the security group.
+                     Mutually exclusive with id parameter
+        @type name: C{str}
+
+        @param account: The account of the security group.
+                        Must be specified with domain ID
+        @type  account: C{str}
+
+        @param projectid:  The project of the security group
+        @type  projectid:  C{uuid}
+
+        @rtype: C{bool}
+        """
+
+        return self._sync_request('deleteSecurityGroup', name=name)['success']
+
+    def ex_authorize_security_group_ingress(self, securitygroupname,
+                                            protocol, cidrlist, startport,
+                                            endport=None):
+        """
+        Creates a new Security Group Ingress rule
+
+        @param domainid: An optional domainId for the security group.
+                         If the account parameter is used,
+                         domainId must also be used.
+        @type domainid: C{uuid}
+
+        @param startport: Start port for this ingress rule
+        @type  startport: C{int}
+
+        @param securitygroupid: The ID of the security group.
+                                Mutually exclusive with securityGroupName
+                                parameter
+        @type  securitygroupid: C{uuid}
+
+        @param cidrlist: The cidr list associated
+        @type  cidrlist: C{list}
+
+        @param usersecuritygrouplist: user to security group mapping
+        @type  usersecuritygrouplist: C{map}
+
+        @param securitygroupname: The name of the security group.
+                                  Mutually exclusive with
+                                  securityGroupName parameter
+        @type  securitygroupname: C{str}
+
+        @param account: An optional account for the security group.
+                        Must be used with domainId.
+        @type  account: C{str}
+
+        @param icmpcode: Error code for this icmp message
+        @type  icmpcode: C{int}
+
+        @param protocol: TCP is default. UDP is the other supported protocol
+        @type  protocol: C{str}
+
+        @param icmptype: type of the icmp message being sent
+        @type  icmptype: C{int}
+
+        @param projectid: An optional project of the security group
+        @type  projectid: C{uuid}
+
+        @param endport: end port for this ingress rule
+        @type  endport: C{int}
+
+        @rtype: C{list}
+        """
+
+        protocol = protocol.upper()
+        if protocol not in ('TCP', 'ICMP'):
+            raise LibcloudError('Only TCP and ICMP are allowed')
+
+        args = {
+            'securitygroupname': securitygroupname,
+            'protocol': protocol,
+            'startport': int(startport),
+            'cidrlist': cidrlist
+        }
+        if endport is None:
+            args['endport'] = int(startport)
+
+        return self._async_request('authorizeSecurityGroupIngress',
+                                   **args)['securitygroup']
+
     def ex_register_iso(self, name, url, location=None, **kwargs):
         """
         Registers an existing ISO by URL.

Modified: libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py
URL: 
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py?rev=1490422&r1=1490421&r2=1490422&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py (original)
+++ libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py Thu Jun  
6 20:16:15 2013
@@ -1,3 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 import sys
 import unittest
 
@@ -17,11 +32,13 @@ except AttributeError:
 
 from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver
 from libcloud.compute.types import DeploymentError, LibcloudError
+from libcloud.compute.base import Node, NodeImage, NodeSize, NodeLocation
 
 from libcloud.test import MockHttpTestCase
 from libcloud.test.compute import TestCaseMixin
 from libcloud.test.file_fixtures import ComputeFileFixtures
 
+
 class CloudStackNodeDriverTest(unittest.TestCase, TestCaseMixin):
     def setUp(self):
         CloudStackNodeDriver.connectionCls.conn_classes = \
@@ -104,9 +121,9 @@ class CloudStackNodeDriverTest(unittest.
         location = self.driver.list_locations()[0]
 
         self.assertRaises(
-                LibcloudError,
-                self.driver.create_volume,
-                    'vol-0', location, 11)
+            LibcloudError,
+            self.driver.create_volume,
+            'vol-0', location, 11)
 
     def test_create_volume_with_custom_disk_size_offering(self):
         CloudStackMockHttp.fixture_tag = 'withcustomdisksize'
@@ -128,6 +145,60 @@ class CloudStackNodeDriverTest(unittest.
 
         self.assertTrue(attachReturnVal)
 
+    def test_list_nodes(self):
+        node = self.driver.list_nodes()[0]
+        self.assertEquals('test', node.name)
+
+    def test_list_locations(self):
+        location = self.driver.list_locations()[0]
+        self.assertEquals('Sydney', location.name)
+
+    def test_start_node(self):
+        node = self.driver.list_nodes()[0]
+        res = node.ex_start()
+        self.assertEquals('Starting', res)
+
+    def test_stop_node(self):
+        node = self.driver.list_nodes()[0]
+        res = node.ex_stop()
+        self.assertEquals('Stopped', res)
+
+    def test_list_keypairs(self):
+        keypairs = self.driver.ex_list_keypairs()
+        fingerprint = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:' + \
+                      '00:00:00:00:00'
+
+        self.assertEqual(keypairs[0]['name'], 'cs-keypair')
+        self.assertEqual(keypairs[0]['fingerprint'], fingerprint)
+
+    def test_create_keypair(self):
+        self.assertRaises(LibcloudError, self.driver.ex_create_keypair,
+                'cs-keypair')
+
+    def test_delete_keypair(self):
+        res = self.driver.ex_delete_keypair('cs-keypair')
+        self.assertTrue(res)
+
+    def test_list_security_groups(self):
+        groups = self.driver.ex_list_security_groups()
+        self.assertEqual(groups[0]['name'], 'default')
+
+    def test_create_security_group(self):
+        group = self.driver.ex_create_security_group(name='MySG')
+        self.assertEqual(group['name'], 'MySG')
+
+    def test_delete_security_group(self):
+        res = self.driver.ex_delete_security_group(name='MySG')
+        self.assertTrue(res)
+
+    def test_authorize_security_group_ingress(self):
+        res = self.driver.ex_authorize_security_group_ingress('MySG',
+                                                              'TCP',
+                                                              '22',
+                                                              '22',
+                                                              '0.0.0.0/0')
+        self.assertTrue(res)
+
 
 class CloudStackMockHttp(MockHttpTestCase):
     fixtures = ComputeFileFixtures('cloudstack')


Reply via email to