Repository: libcloud Updated Branches: refs/heads/trunk 91f8df844 -> 21a0d069d
Fix various bugs and add various improvements: - Fix typos in docstrings and comments - Add import_key_pair_from_string to dummy compute driver. - Fix ec2.import_key_pair_from_string for Python 3 - Fix publickey._to_md5_fingerprint for Python 3 - Fix publickey.get_pubkey_ssh2_fingerprint for Python 3 - ec2 driver: Make cidr_ips argument mandatory - Add "floating IP" functions to the OpenStack provider Closes #301 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/21a0d069 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/21a0d069 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/21a0d069 Branch: refs/heads/trunk Commit: 21a0d069d0a138ab42ce4403ab553c7fc3e4bbb6 Parents: 91f8df8 Author: Csaba Hoch <[email protected]> Authored: Thu Apr 10 11:33:33 2014 +0200 Committer: Tomaz Muraus <[email protected]> Committed: Wed May 28 23:14:41 2014 +0200 ---------------------------------------------------------------------- CHANGES.rst | 18 ++++++++ libcloud/compute/base.py | 4 +- libcloud/compute/drivers/dummy.py | 9 ++++ libcloud/compute/drivers/ec2.py | 16 +++---- libcloud/compute/drivers/openstack.py | 74 +++++++++++++++++++++++++++--- libcloud/test/test_utils.py | 16 +++++++ libcloud/utils/publickey.py | 7 ++- libcloud/utils/py3.py | 29 +++++++++++- 8 files changed, 153 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 3d97fee..51cb59f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,6 +19,14 @@ General (LIBCLOUD-552) [Tomaz Muraus] +- Fix Python 3 compatibility bugs in the following functions: + + * import_key_pair_from_string in the EC2 driver + * publickey._to_md5_fingerprint + * publickey.get_pubkey_ssh2_fingerprint + (GITHUB-301) + [Csaba Hoch] + Compute ~~~~~~~ @@ -174,6 +182,16 @@ Compute Note #2: "timeout" argument is only available in the Paramiko SSH client. +- Make "cidrs_ips" argument in the ex_authorize_security_group_egress method in + the EC2 driver mandatory. + (GITHUB-301) + [Csaba Hoch] + +- Add extension methods for manging floating IPs (ex_get_floating_ip, + ex_create_floating_ip, ex_delete_floating_ip) to the Openstack 1.1 driver. + (GITHUB-301) + [Csaba Hoch] + Storage ~~~~~~~ http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/compute/base.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py index 06a9249..d971afa 100644 --- a/libcloud/compute/base.py +++ b/libcloud/compute/base.py @@ -135,7 +135,7 @@ class Node(UuidMixin): >>> node.name 'dummy-1' - the node keeps a reference to its own driver which means that we + The node keeps a reference to its own driver which means that we can work on nodes from different providers without having to know which is which. @@ -146,7 +146,7 @@ class Node(UuidMixin): >>> node2.driver.creds 72 - Althrough Node objects can be subclassed, this isn't normally + Although Node objects can be subclassed, this isn't normally done. Instead, any driver specific information is stored in the "extra" attribute of the node. http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/compute/drivers/dummy.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/dummy.py b/libcloud/compute/drivers/dummy.py index f8e6fe1..9824335 100644 --- a/libcloud/compute/drivers/dummy.py +++ b/libcloud/compute/drivers/dummy.py @@ -24,6 +24,7 @@ import struct from libcloud.common.base import ConnectionKey from libcloud.compute.base import NodeImage, NodeSize, Node from libcloud.compute.base import NodeDriver, NodeLocation +from libcloud.compute.base import KeyPair from libcloud.compute.types import Provider, NodeState @@ -326,6 +327,14 @@ class DummyNodeDriver(NodeDriver): self.nl.append(n) return n + def import_key_pair_from_string(self, name, key_material): + key_pair = KeyPair(name=name, + public_key=key_material, + fingerprint='fingerprint', + private_key='private_key', + driver=self) + return key_pair + def _ip_to_int(ip): return socket.htonl(struct.unpack('I', socket.inet_aton(ip))[0]) http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index f50cca6..5f9fb9b 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -28,7 +28,7 @@ try: except ImportError: from xml.etree import ElementTree as ET -from libcloud.utils.py3 import b, basestring +from libcloud.utils.py3 import b, basestring, ensure_string from libcloud.utils.xml import fixxpath, findtext, findattr, findall from libcloud.utils.publickey import get_pubkey_ssh2_fingerprint @@ -1962,8 +1962,8 @@ class BaseEC2NodeDriver(NodeDriver): if 'auth' in kwargs: auth = self._get_and_check_auth(kwargs['auth']) - params['KeyName'] = \ - self.ex_find_or_import_keypair_by_key_material(auth.pubkey) + key = self.ex_find_or_import_keypair_by_key_material(auth.pubkey) + params['KeyName'] = key['keyName'] if 'ex_keyname' in kwargs: params['KeyName'] = kwargs['ex_keyname'] @@ -2173,7 +2173,7 @@ class BaseEC2NodeDriver(NodeDriver): return key_pair def import_key_pair_from_string(self, name, key_material): - base64key = base64.b64encode(b(key_material)) + base64key = ensure_string(base64.b64encode(b(key_material))) params = { 'Action': 'ImportKeyPair', @@ -2600,8 +2600,8 @@ class BaseEC2NodeDriver(NodeDriver): Group. :type description: ``str`` - :param description: Optional identifier for VPC networks - :type description: ``str`` + :param vpc_id: Optional identifier for VPC networks + :type vpc_id: ``str`` :rtype: ``dict`` """ @@ -2760,7 +2760,7 @@ class BaseEC2NodeDriver(NodeDriver): return element == 'true' def ex_authorize_security_group_egress(self, id, from_port, to_port, - cidr_ips=None, group_pairs=None, + cidr_ips, group_pairs=None, protocol='tcp'): """ Edit a Security Group to allow specific egress traffic using @@ -4590,7 +4590,7 @@ class BaseEC2NodeDriver(NodeDriver): :rtype: ``dict`` """ - params = {'GroupId': id, + params = {'GroupId': group_id, 'IpPermissions.1.IpProtocol': protocol, 'IpPermissions.1.FromPort': from_port, 'IpPermissions.1.ToPort': to_port} http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 27f1ace..5a85201 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -1758,7 +1758,7 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): resp = self.connection.request('/os-security-groups/%s' % (security_group.id), method='DELETE') - return resp.status == httplib.NO_CONTENT + return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) def ex_create_security_group_rule(self, security_group, ip_protocol, from_port, to_port, cidr=None, @@ -2168,6 +2168,63 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): return self._to_floating_ip_pools( self.connection.request('/os-floating-ip-pools').object) + def _to_floating_ips(self, obj): + ip_elements = obj['floating_ips'] + return [self._to_floating_ip(ip) for ip in ip_elements] + + def _to_floating_ip(self, obj): + return OpenStack_1_1_FloatingIpAddress(obj['id'], obj['ip'], self, + obj['instance_id']) + + def ex_list_floating_ips(self): + """ + List floating IPs + + :rtype: ``list`` of :class:`OpenStack_1_1_FloatingIpAddress` + """ + return self._to_floating_ips( + self.connection.request('/os-floating-ips').object) + + def ex_get_floating_ip(self, ip): + """ + Get specified floating IP + + :param ip: floating IP to get + :type ip: ``str`` + + :rtype: :class:`OpenStack_1_1_FloatingIpAddress` + """ + floating_ips = self.ex_list_floating_ips() + ip_obj, = [x for x in floating_ips if x.ip_address == ip] + return ip_obj + + def ex_create_floating_ip(self): + """ + Create new floating IP + + :rtype: :class:`OpenStack_1_1_FloatingIpAddress` + """ + resp = self.connection.request('/os-floating-ips', + method='POST', + data={}) + data = resp.object['floating_ip'] + id = data['id'] + ip_address = data['ip'] + return OpenStack_1_1_FloatingIpAddress(id, ip_address, self) + + def ex_delete_floating_ip(self, ip): + """ + Delete specified floating IP + + :param ip: floating IP to remove + :type ip::class:`OpenStack_1_1_FloatingIpAddress` + + :rtype: ``bool`` + """ + resp = self.connection.request('/os-floating-ips/%s' % ip.id, + method='DELETE') + return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) + def ex_attach_floating_ip_to_node(self, node, ip): """ Attach the floating IP to the node @@ -2276,7 +2333,7 @@ class OpenStack_1_1_FloatingIpPool(object): """ Get specified floating IP from the pool - :param ip: floating IP to remove + :param ip: floating IP to get :type ip: ``str`` :rtype: :class:`OpenStack_1_1_FloatingIpAddress` @@ -2320,11 +2377,12 @@ class OpenStack_1_1_FloatingIpAddress(object): Floating IP info. """ - def __init__(self, id, ip_address, pool, node_id=None): + def __init__(self, id, ip_address, pool, node_id=None, driver=None): self.id = str(id) self.ip_address = ip_address self.pool = pool self.node_id = node_id + self.driver = driver def delete(self): """ @@ -2332,8 +2390,12 @@ class OpenStack_1_1_FloatingIpAddress(object): :rtype: ``bool`` """ - return self.pool.delete_floating_ip(self) + if self.pool is not None: + return self.pool.delete_floating_ip(self) + elif self.driver is not None: + return self.driver.ex_delete_floating_ip(self) def __repr__(self): - return ('<OpenStack_1_1_FloatingIpAddress: id=%s, ip_addr=%s, pool=%s>' - % (self.id, self.ip_address, self.pool)) + return ('<OpenStack_1_1_FloatingIpAddress: id=%s, ip_addr=%s,' + ' pool=%s, driver=%s>' + % (self.id, self.ip_address, self.pool, self.driver)) http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/test/test_utils.py ---------------------------------------------------------------------- diff --git a/libcloud/test/test_utils.py b/libcloud/test/test_utils.py index 1d900d4..58b3506 100644 --- a/libcloud/test/test_utils.py +++ b/libcloud/test/test_utils.py @@ -33,6 +33,8 @@ from libcloud.utils.misc import get_driver, set_driver from libcloud.utils.py3 import PY3 from libcloud.utils.py3 import StringIO from libcloud.utils.py3 import b +from libcloud.utils.py3 import bchr +from libcloud.utils.py3 import hexadigits from libcloud.utils.py3 import urlquote from libcloud.compute.types import Provider from libcloud.compute.providers import DRIVERS @@ -262,6 +264,20 @@ class TestUtils(unittest.TestCase): value = get_secure_random_string(size=i) self.assertEqual(len(value), i) + def test_hexadigits(self): + self.assertEqual(hexadigits(b('')), []) + self.assertEqual(hexadigits(b('a')), ['61']) + self.assertEqual(hexadigits(b('AZaz09-')), + ['41', '5a', '61', '7a', '30', '39', '2d']) + + def test_bchr(self): + if PY3: + self.assertEqual(bchr(0), b'\x00') + self.assertEqual(bchr(97), b'a') + else: + self.assertEqual(bchr(0), '\x00') + self.assertEqual(bchr(97), 'a') + class NetworkingUtilsTestCase(unittest.TestCase): def test_is_public_and_is_private_subnet(self): http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/utils/publickey.py ---------------------------------------------------------------------- diff --git a/libcloud/utils/publickey.py b/libcloud/utils/publickey.py index d9e59b9..86c6ec3 100644 --- a/libcloud/utils/publickey.py +++ b/libcloud/utils/publickey.py @@ -16,6 +16,9 @@ import base64 import hashlib +from libcloud.utils.py3 import hexadigits +from libcloud.utils.py3 import bchr + __all__ = [ 'get_pubkey_openssh_fingerprint', 'get_pubkey_ssh2_fingerprint', @@ -32,7 +35,7 @@ except ImportError: def _to_md5_fingerprint(data): hashed = hashlib.md5(data).digest() - return ":".join(x.encode("hex") for x in hashed) + return ":".join(hexadigits(hashed)) def get_pubkey_openssh_fingerprint(pubkey): @@ -53,7 +56,7 @@ def get_pubkey_ssh2_fingerprint(pubkey): k = importKey(pubkey) derPK = DerSequence([k.n, k.e]) bitmap = DerObject('BIT STRING') - bitmap.payload = chr(0x00) + derPK.encode() + bitmap.payload = bchr(0x00) + derPK.encode() der = DerSequence([algorithmIdentifier, bitmap.encode()]) return _to_md5_fingerprint(der.encode()) http://git-wip-us.apache.org/repos/asf/libcloud/blob/21a0d069/libcloud/utils/py3.py ---------------------------------------------------------------------- diff --git a/libcloud/utils/py3.py b/libcloud/utils/py3.py index 797d317..1f7d229 100644 --- a/libcloud/utils/py3.py +++ b/libcloud/utils/py3.py @@ -14,7 +14,7 @@ # limitations under the License. # Libcloud Python 2.x and 3.x compatibility layer -# Some methods bellow are taken from Django PYK3 port which is licensed under 3 +# Some methods below are taken from Django PYK3 port which is licensed under 3 # clause BSD license # https://bitbucket.org/loewis/django-3k @@ -83,16 +83,33 @@ if PY3: else: raise TypeError("Invalid argument %r for b()" % (s,)) + def ensure_string(s): + if isinstance(s, str): + return s + elif isinstance(s, bytes): + return s.decode('utf-8') + else: + raise TypeError("Invalid argument %r for ensure_string()" % (s,)) + def byte(n): # assume n is a Latin-1 string of length 1 return ord(n) u = str + def bchr(s): + """Take an integer and make a 1-character byte string.""" + return bytes([s]) + def dictvalues(d): return list(d.values()) def tostring(node): return ET.tostring(node, encoding='unicode') + + def hexadigits(s): + # s needs to be a byte string. + return [format(x, "x") for x in s] + else: import httplib # NOQA from StringIO import StringIO # NOQA @@ -125,13 +142,17 @@ else: method_type = types.MethodType - b = bytes = str + b = bytes = ensure_string = str def byte(n): return n u = unicode + def bchr(s): + """Take an integer and make a 1-character byte string.""" + return chr(s) + def next(i): return i.next() @@ -146,6 +167,10 @@ else: s = s.encode('utf8') return _urlquote(s, safe) + def hexadigits(s): + # s needs to be a string. + return [x.encode("hex") for x in s] + if PY25: import posixpath
