You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2013/08/01 20:30:33 UTC

[21/22] git commit: Fix create_node feature metadata (LIBCLOUD-367)

Fix create_node feature metadata (LIBCLOUD-367)

Signed-off-by: Tomaz Muraus <to...@apache.org>

Conflicts:
	libcloud/compute/drivers/ec2.py


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

Branch: refs/heads/0.13.x
Commit: 506c0fc07aaeb5c7477c3ab920d5e1a48777bb12
Parents: 57e6f4e
Author: John Carr <jo...@isotoma.com>
Authored: Thu Aug 1 18:11:42 2013 +0100
Committer: Tomaz Muraus <to...@apache.org>
Committed: Thu Aug 1 20:29:28 2013 +0200

----------------------------------------------------------------------
 libcloud/compute/base.py                 | 83 +++++++++++++++++++--------
 libcloud/compute/drivers/abiquo.py       |  7 +--
 libcloud/compute/drivers/bluebox.py      | 10 ++--
 libcloud/compute/drivers/brightbox.py    |  1 -
 libcloud/compute/drivers/digitalocean.py |  1 -
 libcloud/compute/drivers/ec2.py          |  9 +--
 libcloud/compute/drivers/hostvirtual.py  | 10 +++-
 libcloud/compute/drivers/linode.py       |  7 ++-
 libcloud/compute/drivers/opsource.py     | 15 ++---
 libcloud/compute/drivers/rimuhosting.py  | 10 +---
 libcloud/compute/drivers/vcloud.py       | 11 ++--
 libcloud/test/compute/test_base.py       | 56 ++++++++++++++++++
 12 files changed, 157 insertions(+), 63 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/base.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py
index 53a2231..545ca00 100644
--- a/libcloud/compute/base.py
+++ b/libcloud/compute/base.py
@@ -358,8 +358,9 @@ class NodeAuthPassword(object):
     """
     A password to be used for authentication to a node.
     """
-    def __init__(self, password):
+    def __init__(self, password, generated=False):
         self.password = password
+        self.generated = generated
 
     def __repr__(self):
         return '<NodeAuthPassword>'
@@ -478,6 +479,41 @@ class NodeDriver(BaseDriver):
                                          host=host, port=port,
                                          api_version=api_version, **kwargs)
 
+    def _get_and_check_auth(self, auth):
+        """
+        Helper function for providers supporting L{NodeAuthPassword} or
+        L{NodeAuthSSHKey}
+
+        Validates that only a supported object type is passed to the auth
+        parameter and raises an exception if it is not.
+
+        If no L{NodeAuthPassword} object is provided but one is expected then a
+        password is automatically generated.
+        """
+
+        if isinstance(auth, NodeAuthPassword):
+            if 'password' in self.features['create_node']:
+                return auth
+            raise LibcloudError(
+                'Password provided as authentication information, but password'
+                'not supported', driver=self)
+
+        if isinstance(auth, NodeAuthSSHKey):
+            if 'ssh_key' in self.features['create_node']:
+                return auth
+            raise LibcloudError(
+                'SSH Key provided as authentication information, but SSH Key'
+                'not supported', driver=self)
+
+        if 'password' in self.features['create_node']:
+            value = os.urandom(16)
+            return NodeAuthPassword(binascii.hexlify(value), generated=True)
+
+        if auth:
+            raise LibcloudError(
+                '"auth" argument provided, but it was not a NodeAuthPassword'
+                'or NodeAuthSSHKey object', driver=self)
+
     def create_node(self, **kwargs):
         """Create a new node instance.
 
@@ -582,8 +618,8 @@ class NodeDriver(BaseDriver):
         """
         Create a new node, and start deployment.
 
-        Depends on a Provider Driver supporting either using a specific
-        password or returning a generated password.
+        Depends on user providing authentication information or the Provider
+        Driver generating a password and returning it.
 
         This function may raise a :class:`DeploymentException`, if a create_node
         call was successful, but there is a later error (like SSH failing or
@@ -656,29 +692,33 @@ class NodeDriver(BaseDriver):
             raise RuntimeError('paramiko is not installed. You can install ' +
                                'it using pip: pip install paramiko')
 
-        password = None
-
-        if 'create_node' not in self.features:
-            raise NotImplementedError(
-                'deploy_node not implemented for this driver')
-        elif 'generates_password' not in self.features["create_node"]:
-            if 'password' not in self.features["create_node"] and \
-               'ssh_key' not in self.features["create_node"]:
+        if 'auth' in kwargs:
+            auth = kwargs['auth']
+            if not isinstance(auth, (NodeAuthSSHKey, NodeAuthPassword)):
+                raise NotImplementedError(
+                    'If providing auth, only NodeAuthSSHKey or'
+                    'NodeAuthPassword is supported')
+        elif 'ssh_key' in kwargs:
+            # If an ssh_key is provided we can try deploy_node
+            pass
+        elif 'create_node' in self.features:
+            f = self.features['create_node']
+            if not 'generates_password' in f and not "password" in f:
                 raise NotImplementedError(
                     'deploy_node not implemented for this driver')
-
-            if 'auth' not in kwargs:
-                value = os.urandom(16)
-                kwargs['auth'] = NodeAuthPassword(binascii.hexlify(value))
-
-            if 'ssh_key' not in kwargs:
-                password = kwargs['auth'].password
+        else:
+            raise NotImplementedError(
+                'deploy_node not implemented for this driver')
 
         node = self.create_node(**kwargs)
         max_tries = kwargs.get('max_tries', 3)
 
-        if 'generates_password' in self.features['create_node']:
-            password = node.extra.get('password')
+        password = None
+        if 'auth' in kwargs:
+            if isinstance(kwargs['auth'], NodeAuthPassword):
+                password = kwargs['auth'].password
+        elif 'password' in node.extra:
+            password = node.extra['password']
 
         ssh_interface = kwargs.get('ssh_interface', 'public_ips')
 
@@ -693,9 +733,6 @@ class NodeDriver(BaseDriver):
             e = sys.exc_info()[1]
             raise DeploymentError(node=node, original_exception=e, driver=self)
 
-        if password:
-            node.extra['password'] = password
-
         ssh_username = kwargs.get('ssh_username', 'root')
         ssh_alternate_usernames = kwargs.get('ssh_alternate_usernames', [])
         ssh_port = kwargs.get('ssh_port', 22)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/abiquo.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/abiquo.py b/libcloud/compute/drivers/abiquo.py
index 7a10a99..98601cc 100644
--- a/libcloud/compute/drivers/abiquo.py
+++ b/libcloud/compute/drivers/abiquo.py
@@ -40,7 +40,6 @@ class AbiquoNodeDriver(NodeDriver):
     name = 'Abiquo'
     website = 'http://www.abiquo.com/'
     connectionCls = AbiquoConnection
-    features = {'create_node': ['password']}
     timeout = 2000  # some images take a lot of time!
 
     # Media Types
@@ -104,10 +103,6 @@ class AbiquoNodeDriver(NodeDriver):
                               undefined behavoir will be selected. (optional)
         @type       location: L{NodeLocation}
 
-        @keyword    auth:   Initial authentication information for the node
-                            (optional)
-        @type       auth:   L{NodeAuthPassword}
-
         @keyword   group_name:  Which group this node belongs to. If empty,
                                  it will be created into 'libcloud' group. If
                                  it does not found any group in the target
@@ -684,7 +679,7 @@ class AbiquoNodeDriver(NodeDriver):
         for vapp in vapps_element.findall('virtualAppliance'):
             if vapp.findtext('name') == group_name:
                 uri_vapp = get_href(vapp, 'edit')
-                return  NodeGroup(self, vapp.findtext('name'), uri=uri_vapp)
+                return NodeGroup(self, vapp.findtext('name'), uri=uri_vapp)
 
         # target group not found: create it. Since it is an extension of
         # the basic 'libcloud' functionality, we try to be as flexible as

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/bluebox.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/bluebox.py b/libcloud/compute/drivers/bluebox.py
index ee9b3ce..8dc1ba2 100644
--- a/libcloud/compute/drivers/bluebox.py
+++ b/libcloud/compute/drivers/bluebox.py
@@ -135,6 +135,7 @@ class BlueboxNodeDriver(NodeDriver):
     api_name = 'bluebox'
     name = 'Bluebox Blocks'
     website = 'http://bluebox.net'
+    features = {'create_node': ['ssh_key', 'password']}
 
     def list_nodes(self):
         result = self.connection.request('/api/blocks.json')
@@ -166,10 +167,7 @@ class BlueboxNodeDriver(NodeDriver):
         image = kwargs['image']
         size = kwargs['size']
 
-        try:
-            auth = kwargs['auth']
-        except Exception:
-            raise Exception("SSH public key or password required.")
+        auth = self._get_and_check_auth(kwargs.get('auth'))
 
         data = {
             'hostname': name,
@@ -197,6 +195,10 @@ class BlueboxNodeDriver(NodeDriver):
         result = self.connection.request('/api/blocks.json', headers=headers,
                                          data=params, method='POST')
         node = self._to_node(result.object)
+
+        if getattr(auth, "generated", False):
+            node.extra['password'] = auth.password
+
         return node
 
     def destroy_node(self, node):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/brightbox.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/brightbox.py b/libcloud/compute/drivers/brightbox.py
index 1747fdb..ce307ea 100644
--- a/libcloud/compute/drivers/brightbox.py
+++ b/libcloud/compute/drivers/brightbox.py
@@ -44,7 +44,6 @@ class BrightboxNodeDriver(NodeDriver):
     type = Provider.BRIGHTBOX
     name = 'Brightbox'
     website = 'http://www.brightbox.co.uk/'
-    features = {'create_node': ['ssh_key']}
 
     NODE_STATE_MAP = {'creating': NodeState.PENDING,
                       'active': NodeState.RUNNING,

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/digitalocean.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/digitalocean.py b/libcloud/compute/drivers/digitalocean.py
index 0f4ee40..09ace5f 100644
--- a/libcloud/compute/drivers/digitalocean.py
+++ b/libcloud/compute/drivers/digitalocean.py
@@ -75,7 +75,6 @@ class DigitalOceanNodeDriver(NodeDriver):
     type = Provider.DIGITAL_OCEAN
     name = 'Digital Ocean'
     website = 'https://www.digitalocean.com'
-    features = {'create_node': ['ssh_key']}
 
     NODE_STATE_MAP = {'new': NodeState.PENDING,
                       'off': NodeState.REBOOTING,

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index aecfdb2..17702fd 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -423,7 +423,6 @@ class BaseEC2NodeDriver(NodeDriver):
 
     connectionCls = EC2Connection
     path = '/'
-    features = {'create_node': ['ssh_key']}
 
     NODE_STATE_MAP = {
         'pending': NodeState.PENDING,
@@ -1333,13 +1332,15 @@ class BaseEC2NodeDriver(NodeDriver):
 
         if 'ex_blockdevicemappings' in kwargs:
             if not isinstance(kwargs['ex_blockdevicemappings'], (list, tuple)):
-                raise AttributeError('ex_blockdevicemappings not list or tuple')
+                raise AttributeError(
+                    'ex_blockdevicemappings not list or tuple')
 
             for idx, mapping in enumerate(kwargs['ex_blockdevicemappings']):
                 idx += 1  # we want 1-based indexes
                 if not isinstance(mapping, dict):
-                    raise AttributeError('mapping %s in ex_blockdevicemappings '
-                                         'not a dict' % mapping)
+                    raise AttributeError(
+                        'mapping %s in ex_blockdevicemappings '
+                        'not a dict' % mapping)
                 for k, v in mapping.items():
                     params['BlockDeviceMapping.%d.%s' % (idx, k)] = str(v)
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/hostvirtual.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/hostvirtual.py b/libcloud/compute/drivers/hostvirtual.py
index e311224..de5e73d 100644
--- a/libcloud/compute/drivers/hostvirtual.py
+++ b/libcloud/compute/drivers/hostvirtual.py
@@ -61,6 +61,7 @@ class HostVirtualNodeDriver(NodeDriver):
     name = 'HostVirtual'
     website = 'http://www.vr.org'
     connectionCls = HostVirtualComputeConnection
+    features = {'create_node': ['ssh_key', 'password']}
 
     def __init__(self, key):
         self.location = None
@@ -164,6 +165,8 @@ class HostVirtualNodeDriver(NodeDriver):
         size = kwargs['size']
         image = kwargs['image']
 
+        auth = self._get_and_check_auth(kwargs.get('auth'))
+
         params = {'plan': size.name}
 
         dc = DEFAULT_NODE_LOCATION_ID
@@ -186,9 +189,12 @@ class HostVirtualNodeDriver(NodeDriver):
         })
 
         # provisioning a server using the stub node
-        self.ex_provision_node(node=stub_node, auth=kwargs['auth'])
-
+        self.ex_provision_node(node=stub_node, auth=auth)
         node = self._wait_for_node(stub_node.id)
+
+        if getattr(auth, 'generated', False):
+            node.extra['password'] = auth.password
+
         return node
 
     def reboot_node(self, node):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/linode.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/linode.py b/libcloud/compute/drivers/linode.py
index 86eb61c..c5cae83 100644
--- a/libcloud/compute/drivers/linode.py
+++ b/libcloud/compute/drivers/linode.py
@@ -208,7 +208,7 @@ class LinodeNodeDriver(NodeDriver):
         name = kwargs["name"]
         image = kwargs["image"]
         size = kwargs["size"]
-        auth = kwargs["auth"]
+        auth = self._get_and_check_auth(kwargs["auth"])
 
         # Pick a location (resolves LIBCLOUD-41 in JIRA)
         if "location" in kwargs:
@@ -372,7 +372,10 @@ class LinodeNodeDriver(NodeDriver):
         nodes = self._to_nodes(data)
 
         if len(nodes) == 1:
-            return nodes[0]
+            node = nodes[0]
+            if getattr(auth, "generated", False):
+                node.extra['password'] = auth.password
+            return node
 
         return None
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/opsource.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/opsource.py b/libcloud/compute/drivers/opsource.py
index 8b89ea8..5796a44 100644
--- a/libcloud/compute/drivers/opsource.py
+++ b/libcloud/compute/drivers/opsource.py
@@ -281,12 +281,8 @@ class OpsourceNodeDriver(NodeDriver):
         #       cannot be set at create time because size is part of the
         #       image definition.
         password = None
-        if 'auth' in kwargs:
-            auth = kwargs.get('auth')
-            if isinstance(auth, NodeAuthPassword):
-                password = auth.password
-            else:
-                raise ValueError('auth must be of NodeAuthPassword type')
+        auth = self._get_and_check_auth(kwargs.get('auth'))
+        password = auth.password
 
         ex_description = kwargs.get('ex_description', '')
         ex_isStarted = kwargs.get('ex_isStarted', True)
@@ -319,7 +315,12 @@ class OpsourceNodeDriver(NodeDriver):
         # XXX: return the last node in the list that has a matching name.  this
         #      is likely but not guaranteed to be the node we just created
         #      because opsource allows multiple nodes to have the same name
-        return list(filter(lambda x: x.name == name, self.list_nodes()))[-1]
+        node = list(filter(lambda x: x.name == name, self.list_nodes()))[-1]
+
+        if getattr(auth, "generated", False):
+            node.extra['password'] = auth.password
+
+        return node
 
     def destroy_node(self, node):
         body = self.connection.request_with_orgId(

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/rimuhosting.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/rimuhosting.py b/libcloud/compute/drivers/rimuhosting.py
index eb81a91..6db9b0d 100644
--- a/libcloud/compute/drivers/rimuhosting.py
+++ b/libcloud/compute/drivers/rimuhosting.py
@@ -116,6 +116,7 @@ class RimuHostingNodeDriver(NodeDriver):
     name = 'RimuHosting'
     website = 'http://rimuhosting.com/'
     connectionCls = RimuHostingConnection
+    features = {'create_node': ['password']}
 
     def __init__(self, key, host=API_HOST, port=443,
                  api_context=API_CONTEXT, secure=True):
@@ -283,11 +284,8 @@ class RimuHostingNodeDriver(NodeDriver):
             data['instantiation_options']['control_panel'] = \
                 kwargs['ex_control_panel']
 
-        if 'auth' in kwargs:
-            auth = kwargs['auth']
-            if not isinstance(auth, NodeAuthPassword):
-                raise ValueError('auth must be of NodeAuthPassword type')
-            data['instantiation_options']['password'] = auth.password
+        auth = self._get_and_check_auth(kwargs.get('auth'))
+        data['instantiation_options']['password'] = auth.password
 
         if 'ex_billing_oid' in kwargs:
             #TODO check for valid oid.
@@ -345,5 +343,3 @@ class RimuHostingNodeDriver(NodeDriver):
             NodeLocation('DCLONDON', "RimuHosting London", 'GB', self),
             NodeLocation('DCSYDNEY', "RimuHosting Sydney", 'AU', self),
         ]
-
-    features = {"create_node": ["password"]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/compute/drivers/vcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py
index e7d9a7e..02638e6 100644
--- a/libcloud/compute/drivers/vcloud.py
+++ b/libcloud/compute/drivers/vcloud.py
@@ -718,12 +718,8 @@ class VCloudNodeDriver(NodeDriver):
             network = ''
 
         password = None
-        if 'auth' in kwargs:
-            auth = kwargs['auth']
-            if isinstance(auth, NodeAuthPassword):
-                password = auth.password
-            else:
-                raise ValueError('auth must be of NodeAuthPassword type')
+        auth = self._get_and_check_auth(kwargs.get('auth'))
+        password = auth.password
 
         instantiate_xml = InstantiateVAppXML(
             name=name,
@@ -759,6 +755,9 @@ class VCloudNodeDriver(NodeDriver):
         res = self.connection.request(vapp_path)
         node = self._to_node(res.object)
 
+        if getattr(auth, "generated", False):
+            node.extra['password'] = auth.password
+
         return node
 
     features = {"create_node": ["password"]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/506c0fc0/libcloud/test/compute/test_base.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_base.py b/libcloud/test/compute/test_base.py
index 17081c1..750d527 100644
--- a/libcloud/test/compute/test_base.py
+++ b/libcloud/test/compute/test_base.py
@@ -17,7 +17,9 @@ import unittest
 
 from libcloud.common.base import Response
 from libcloud.common.base import Connection, ConnectionKey, ConnectionUserAndKey
+from libcloud.common.types import LibcloudError
 from libcloud.compute.base import Node, NodeSize, NodeImage, NodeDriver
+from libcloud.compute.base import NodeAuthSSHKey, NodeAuthPassword
 
 from libcloud.test import MockResponse           # pylint: disable-msg=E0611
 
@@ -52,5 +54,59 @@ class BaseTests(unittest.TestCase):
     def test_base_connection_timeout(self):
         Connection(timeout=10)
 
+
+class TestValidateAuth(unittest.TestCase):
+
+    def test_get_auth_ssh(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['ssh_key']}
+        auth = NodeAuthSSHKey('pubkey...')
+        self.assertEqual(auth, n._get_and_check_auth(auth))
+
+    def test_get_auth_ssh_but_given_password(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['ssh_key']}
+        auth = NodeAuthPassword('password')
+        self.assertRaises(LibcloudError, n._get_and_check_auth, auth)
+
+    def test_get_auth_password(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['password']}
+        auth = NodeAuthPassword('password')
+        self.assertEqual(auth, n._get_and_check_auth(auth))
+
+    def test_get_auth_password_but_given_ssh_key(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['password']}
+        auth = NodeAuthSSHKey('publickey')
+        self.assertRaises(LibcloudError, n._get_and_check_auth, auth)
+
+    def test_get_auth_default_ssh_key(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['ssh_key']}
+        self.assertEqual(None, n._get_and_check_auth(None))
+
+    def test_get_auth_default_password(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['password']}
+        auth = n._get_and_check_auth(None)
+        self.assertTrue(isinstance(auth, NodeAuthPassword))
+
+    def test_get_auth_default_no_feature(self):
+        n = NodeDriver('foo')
+        self.assertEqual(None, n._get_and_check_auth(None))
+
+    def test_get_auth_generates_password_but_given_nonsense(self):
+        n = NodeDriver('foo')
+        n.features = {'create_node': ['generates_password']}
+        auth = "nonsense"
+        self.assertRaises(LibcloudError, n._get_and_check_auth, auth)
+
+    def test_get_auth_no_features_but_given_nonsense(self):
+        n = NodeDriver('foo')
+        auth = "nonsense"
+        self.assertRaises(LibcloudError, n._get_and_check_auth, auth)
+
+
 if __name__ == '__main__':
     sys.exit(unittest.main())