Updated Branches: refs/heads/trunk 3921d84f7 -> 05e8559d4
Fix create_node feature metadata (LIBCLOUD-367) Signed-off-by: Tomaz Muraus <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/7771ae72 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/7771ae72 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/7771ae72 Branch: refs/heads/trunk Commit: 7771ae72d8daac273fc759cd654c58631ec0af05 Parents: 3921d84 Author: John Carr <[email protected]> Authored: Thu Aug 1 18:11:42 2013 +0100 Committer: Tomaz Muraus <[email protected]> Committed: Thu Aug 1 19:18:58 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 | 11 ++-- 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(+), 65 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/7771ae72/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/7771ae72/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/7771ae72/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/7771ae72/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/7771ae72/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/7771ae72/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index 4b02b48..e9b5577 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) @@ -1416,8 +1417,6 @@ class EC2NodeDriver(BaseEC2NodeDriver): website = 'http://aws.amazon.com/ec2/' path = '/' - features = {'create_node': ['ssh_key']} - NODE_STATE_MAP = { 'pending': NodeState.PENDING, 'running': NodeState.RUNNING, http://git-wip-us.apache.org/repos/asf/libcloud/blob/7771ae72/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/7771ae72/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/7771ae72/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/7771ae72/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/7771ae72/libcloud/compute/drivers/vcloud.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py index adae38f..4b4ac63 100644 --- a/libcloud/compute/drivers/vcloud.py +++ b/libcloud/compute/drivers/vcloud.py @@ -719,12 +719,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, @@ -760,6 +756,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/7771ae72/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())
