LIBCLOUD-392: Add support for keypairs management in the OpenStack driver.

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/aadbd4c3
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/aadbd4c3
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/aadbd4c3

Branch: refs/heads/trunk
Commit: aadbd4c3eeadd391262cda664303e2f3d5564eed
Parents: 63a5b8d
Author: schaubl <[email protected]>
Authored: Tue Sep 10 11:03:26 2013 +0200
Committer: Tomaz Muraus <[email protected]>
Committed: Wed Sep 25 21:10:21 2013 +0200

----------------------------------------------------------------------
 libcloud/compute/drivers/openstack.py           | 117 +++++++++++++++++++
 .../fixtures/openstack_v1.1/_os_keypairs.json   |  18 +++
 .../openstack_v1.1/_os_keypairs_create.json     |   9 ++
 .../_os_keypairs_create_import.json             |   8 ++
 libcloud/test/compute/test_openstack.py         |  65 ++++++++++-
 5 files changed, 216 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/aadbd4c3/libcloud/compute/drivers/openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/openstack.py 
b/libcloud/compute/drivers/openstack.py
index 9cbaa5e..875094d 100644
--- a/libcloud/compute/drivers/openstack.py
+++ b/libcloud/compute/drivers/openstack.py
@@ -28,6 +28,7 @@ from libcloud.utils.py3 import b
 from libcloud.utils.py3 import next
 from libcloud.utils.py3 import urlparse
 
+import os
 import base64
 
 from xml.etree import ElementTree as ET
@@ -1121,6 +1122,43 @@ class OpenStackSecurityGroupRule(object):
                 self.to_port))
 
 
+class OpenStackKeyPair(object):
+    """
+    A KeyPair.
+    """
+
+    def __init__(self, name, fingerprint, public_key, driver, private_key=None,
+                 extra=None):
+        """
+        Constructor.
+
+        @keyword    name: Name of the KeyPair.
+        @type       name: C{str}
+
+        @keyword    fingerprint: Fingerprint of the KeyPair
+        @type       fingerprint: C{str}
+
+        @keyword    public_key: Public key in OpenSSH format.
+        @type       public_key: C{str}
+
+        @keyword    private_key: Private key in PEM format.
+        @type       private_key: C{str}
+
+        @keyword    extra: Extra attributes associated with this KeyPair.
+        @type       extra: C{dict}
+        """
+        self.name = name
+        self.fingerprint = fingerprint
+        self.public_key = public_key
+        self.private_key = private_key
+        self.driver = driver
+        self.extra = extra or {}
+
+    def __repr__(self):
+        return ('<OpenStackKeyPair name=%s fingerprint=%s public_key=%s ...>'
+                % (self.name, self.fingerprint, self.public_key))
+
+
 class OpenStack_1_1_Connection(OpenStackComputeConnection):
     responseCls = OpenStack_1_1_Response
     accept_format = 'application/json'
@@ -1636,6 +1674,85 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
                                        (rule.id), method='DELETE')
         return resp.status == httplib.NO_CONTENT
 
+    def _to_keypairs(self, obj):
+        keypairs = obj['keypairs']
+        return [self._to_keypair(keypair['keypair']) for keypair in keypairs]
+
+    def _to_keypair(self, obj):
+        return OpenStackKeyPair(name=obj['name'],
+                                fingerprint=obj['fingerprint'],
+                                public_key=obj['public_key'],
+                                private_key=obj.get('private_key', None),
+                                driver=self)
+
+    def ex_list_keypairs(self):
+        """
+        Get a list of KeyPairs that are available.
+
+        @rtype: C{list} of L{OpenStackKeyPair}
+        """
+        return self._to_keypairs(
+            self.connection.request('/os-keypairs').object)
+
+    def ex_create_keypair(self, name):
+        """
+        Create a new KeyPair
+
+        @param name: Name of the new KeyPair
+        @type  name: C{str}
+
+        @rtype: L{OpenStackKeyPair}
+        """
+        return self._to_keypair(self.connection.request(
+            '/os-keypairs', method='POST',
+            data={'keypair': {'name': name}}
+        ).object['keypair'])
+
+    def ex_import_keypair(self, name, public_key_file):
+        """
+        Import a KeyPair from a file
+
+        @param name: Name of the new KeyPair
+        @type  name: C{str}
+
+        @param public_key_file: Path to the public key file (in OpenSSH format)
+        @type  public_key_file: C{str}
+
+        @rtype: L{OpenStackKeyPair}
+        """
+        public_key = open(os.path.expanduser(public_key_file), 'r').read()
+        return self.ex_import_keypair_from_string(name, public_key)
+
+    def ex_import_keypair_from_string(self, name, public_key):
+        """
+        Import a KeyPair from a string
+
+        @param name: Name of the new KeyPair
+        @type  name: C{str}
+
+        @param public_key: Public key (in OpenSSH format)
+        @type  public_key: C{str}
+
+        @rtype: L{OpenStackKeyPair}
+        """
+        return self._to_keypair(self.connection.request(
+            '/os-keypairs', method='POST',
+            data={'keypair': {'name': name, 'public_key': public_key}}
+        ).object['keypair'])
+
+    def ex_delete_keypair(self, keypair):
+        """
+        Delete a KeyPair.
+
+        @param keypair: KeyPair to delete
+        @type  keypair: L{OpenStackKeyPair}
+
+        @rtype: C{bool}
+        """
+        resp = self.connection.request('/os-keypairs/%s' % (keypair.name),
+                                       method='DELETE')
+        return resp.status == httplib.ACCEPTED
+
     def ex_get_size(self, size_id):
         """
         Get a NodeSize

http://git-wip-us.apache.org/repos/asf/libcloud/blob/aadbd4c3/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs.json 
b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs.json
new file mode 100644
index 0000000..dff7164
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs.json
@@ -0,0 +1,18 @@
+{
+    "keypairs": [
+        {
+            "keypair": {
+                "fingerprint": 
"22:0e:d6:f7:bd:5e:ee:49:cf:1f:10:d5:9c:a8:35:64",
+                "name": "key1",
+                "public_key": "ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAAAgQC/ePvJuMEOc90gidxWN+8lYekv+S6j8SJhcQRBjE5DVs/M+3VXyJTQc6fguUS9c7o8GZXpP/0dwbVa9y76HeZs6In+XE1egoUyz4zLHQ5jUepFeekChpSlo6yQWI2SHUxJOshqPLOEU1XlrwvN0h5FcXGVV0x6DJgLZuCRS7oIxQ==
 Generated by Nova\n"
+            }
+        },
+        {
+            "keypair": {
+                "fingerprint": 
"5d:66:33:ae:99:0f:fb:cb:86:f2:bc:ae:53:99:b6:ed",
+                "name": "key2",
+                "public_key": "ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAAAgQCz5sy4u8KwAPAMPr+4bEMlU6BwpSD6eZVokwMclojqIz9nKAvQD9AEw/6ok9Xsn0oixBrCoW2HYsXIiUziufzheoGsZIzuj3D7Rpbtrft53FtICe5UtQrOo3WJb8bvbzpDDd7xYlb9PpQTXoxInzjgBW+Ox6OODx2NazTk7PHZDQ==
 Generated by Nova\n"
+            }
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/aadbd4c3/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create.json
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create.json 
b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create.json
new file mode 100644
index 0000000..fbfdbb9
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create.json
@@ -0,0 +1,9 @@
+{
+    "keypair": {
+        "fingerprint": "80:f8:03:a7:8e:c1:c3:b1:7e:c5:8c:50:04:5e:1c:5b",
+        "name": "key0",
+        "private_key": "-----BEGIN RSA PRIVATE 
KEY-----\nMIICWwIBAAKBgQDPC4MDHBbUjeGZ4pK5svGxkFHJFdDatpMAYcW/fyDxsMbyiHnu\nUOxB0WJupUQd4tc7B8+MNOLzcZVQkUjIhhkb5qCbjcoOqzb59owtNCSi7TleaC6w\n15j1LJb3zdHVxEhGJ19I95DhOtiFRHp2Ik3bYV6p+uv0sQxfaqw3q5M3+QIDAQAB\nAoGAW2LqZfH9Bb7GSEUgnESmt8hKwSYW9KLHidCeFyNG6Ect2RlyMEWZsod4Gfxq\nb4KTm6Ob8XfagLeuv0wRQyklZUbyb4aurfn4hX0cpkxSPAVar8uG/0TJY1wswxfo\nkReZCq7CQFlt7w3Y1RHZyXo/inyAxohi393trVhIGAqdXp0CQQDt7/GeI5QWKjYj\nwe5kFTRowVJ+y61MP237Bz+YF5+pq28ikdLAMzdDOyd3LJTnBGJ/DK1ksfJDCSue\nEgdifYJrAkEA3sM1fRQB/PyyyCR1DcZGlOfc/OBCSG4aTMYOK+g0PnibKPj5wS6q\nuK8w1q+0CztpgKsmEtQ+H7H8Fva81S7wKwJANY7tNEuN6e9WgHYG00Byq6HYj/II\n8EDW4Mqg5ftrVSXhvkZUyi69IcUO/SRr4BR8l1yjKydjAPPvfYVRZDocQQJASHXr\nQkJt2yM/7IafZNmoP+ukIMW6CeF2wJ50IagoxmFo500FwOczNVwXYN5KjJTI3sfN\nXLaZdqnovHeKOTZJfQJAZ2HBnmgsLoFE6ONF492TXIs7JxJr8z4QUp1AXGUXcZmy\njuL3b9XW6K908Ev8uTSNzRo6TyGuYKGllp10K6A3bA==\n-----END
 RSA PRIVATE KEY-----\n",
+        "public_key": "ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAAAgQDPC4MDHBbUjeGZ4pK5svGxkFHJFdDatpMAYcW/fyDxsMbyiHnuUOxB0WJupUQd4tc7B8+MNOLzcZVQkUjIhhkb5qCbjcoOqzb59owtNCSi7TleaC6w15j1LJb3zdHVxEhGJ19I95DhOtiFRHp2Ik3bYV6p+uv0sQxfaqw3q5M3+Q==
 Generated by Nova\n",
+        "user_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/aadbd4c3/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create_import.json
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create_import.json 
b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create_import.json
new file mode 100644
index 0000000..56f0958
--- /dev/null
+++ 
b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_create_import.json
@@ -0,0 +1,8 @@
+{
+    "keypair": {
+        "fingerprint": "97:10:a6:e7:92:65:7e:69:fe:e6:81:8f:39:3c:8f:5a",
+        "name": "key3",
+        "public_key": "ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAABAQCzTJr5BNSlTIDFsVY3zUJtbbcPsWbw7XDE/eXRQ+704790ARKKvE3FsERqdMZvwcx1osR0sGVdpgAiV/z5iEb5z2juQp7yQJHePiEnfHTH99NVJN+Y1BztchRoz224IaP987bN+fd8Pl/O1YDCyw+bX5zI/ekCC9z8fTdI2l1AbTnKVn7UjZBjKZi1uPMaH016fp039pIOtkjvIgDWjeGwOiJjY1vzaX3nxQje4kprEZ4FKk4yyG61qveBZr+/0Xq6ocNOYUSpB29AZ0IcfJa7P3yMxVRzSS9aN0fmrlf3kIFkVAy45A83GfZpiMxo/ulTaO9+tTSwulZP+0bxkCkn
 dummycomment\n",
+        "user_id": "dbdf4c6cab0c4ae78bef0bcdb03c2440"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/aadbd4c3/libcloud/test/compute/test_openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_openstack.py 
b/libcloud/test/compute/test_openstack.py
index 5512167..5de21fb 100644
--- a/libcloud/test/compute/test_openstack.py
+++ b/libcloud/test/compute/test_openstack.py
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import sys
 import unittest
 import datetime
@@ -39,7 +40,7 @@ from libcloud.compute.drivers.openstack import (
     OpenStack_1_0_NodeDriver, OpenStack_1_0_Response,
     OpenStack_1_1_NodeDriver, OpenStackSecurityGroup,
     OpenStackSecurityGroupRule, OpenStack_1_1_FloatingIpPool,
-    OpenStack_1_1_FloatingIpAddress
+    OpenStack_1_1_FloatingIpAddress, OpenStackKeyPair
 )
 from libcloud.compute.base import Node, NodeImage, NodeSize, StorageVolume
 from libcloud.pricing import set_pricing, clear_pricing_data
@@ -1142,6 +1143,49 @@ class OpenStack_1_1_Tests(unittest.TestCase, 
TestCaseMixin):
         result = self.driver.ex_delete_security_group_rule(security_group_rule)
         self.assertTrue(result)
 
+    def test_ex_list_keypairs(self):
+        keypairs = self.driver.ex_list_keypairs()
+        self.assertEqual(len(keypairs), 2, 'Wrong keypairs count')
+        keypair = keypairs[1]
+        self.assertEqual(keypair.name, 'key2')
+        self.assertEqual(keypair.fingerprint, 
'5d:66:33:ae:99:0f:fb:cb:86:f2:bc:ae:53:99:b6:ed')
+        self.assertEqual(keypair.public_key, 'ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAAAgQCz5sy4u8KwAPAMPr+4bEMlU6BwpSD6eZVokwMclojqIz9nKAvQD9AEw/6ok9Xsn0oixBrCoW2HYsXIiUziufzheoGsZIzuj3D7Rpbtrft53FtICe5UtQrOo3WJb8bvbzpDDd7xYlb9PpQTXoxInzjgBW+Ox6OODx2NazTk7PHZDQ==
 Generated by Nova\n')
+        self.assertEqual(keypair.private_key, None)
+
+    def test_ex_create_keypair(self):
+        name = 'key0'
+        keypair = self.driver.ex_create_keypair(name)
+        self.assertEqual(keypair.name, name)
+        self.assertEqual(keypair.fingerprint, 
'80:f8:03:a7:8e:c1:c3:b1:7e:c5:8c:50:04:5e:1c:5b')
+        self.assertEqual(keypair.public_key, 'ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAAAgQDPC4MDHBbUjeGZ4pK5svGxkFHJFdDatpMAYcW/fyDxsMbyiHnuUOxB0WJupUQd4tc7B8+MNOLzcZVQkUjIhhkb5qCbjcoOqzb59owtNCSi7TleaC6w15j1LJb3zdHVxEhGJ19I95DhOtiFRHp2Ik3bYV6p+uv0sQxfaqw3q5M3+Q==
 Generated by Nova\n')
+        self.assertEqual(keypair.private_key, '-----BEGIN RSA PRIVATE 
KEY-----\nMIICWwIBAAKBgQDPC4MDHBbUjeGZ4pK5svGxkFHJFdDatpMAYcW/fyDxsMbyiHnu\nUOxB0WJupUQd4tc7B8+MNOLzcZVQkUjIhhkb5qCbjcoOqzb59owtNCSi7TleaC6w\n15j1LJb3zdHVxEhGJ19I95DhOtiFRHp2Ik3bYV6p+uv0sQxfaqw3q5M3+QIDAQAB\nAoGAW2LqZfH9Bb7GSEUgnESmt8hKwSYW9KLHidCeFyNG6Ect2RlyMEWZsod4Gfxq\nb4KTm6Ob8XfagLeuv0wRQyklZUbyb4aurfn4hX0cpkxSPAVar8uG/0TJY1wswxfo\nkReZCq7CQFlt7w3Y1RHZyXo/inyAxohi393trVhIGAqdXp0CQQDt7/GeI5QWKjYj\nwe5kFTRowVJ+y61MP237Bz+YF5+pq28ikdLAMzdDOyd3LJTnBGJ/DK1ksfJDCSue\nEgdifYJrAkEA3sM1fRQB/PyyyCR1DcZGlOfc/OBCSG4aTMYOK+g0PnibKPj5wS6q\nuK8w1q+0CztpgKsmEtQ+H7H8Fva81S7wKwJANY7tNEuN6e9WgHYG00Byq6HYj/II\n8EDW4Mqg5ftrVSXhvkZUyi69IcUO/SRr4BR8l1yjKydjAPPvfYVRZDocQQJASHXr\nQkJt2yM/7IafZNmoP+ukIMW6CeF2wJ50IagoxmFo500FwOczNVwXYN5KjJTI3sfN\nXLaZdqnovHeKOTZJfQJAZ2HBnmgsLoFE6ONF492TXIs7JxJr8z4QUp1AXGUXcZmy\njuL3b9XW6K908Ev8uTSNzRo6TyGuYKGllp10K6A3bA==\n-----END
 RSA PRIVATE KEY-----\n')
+
+    def test_ex_import_keypair(self):
+        name = 'key3'
+        path = os.path.join(os.path.dirname(__file__), "fixtures", "misc", 
"dummy_rsa.pub")
+        pub_key = open(path, 'r').read()
+        keypair = self.driver.ex_import_keypair(name, path)
+        self.assertEqual(keypair.name, name)
+        self.assertEqual(keypair.fingerprint, 
'97:10:a6:e7:92:65:7e:69:fe:e6:81:8f:39:3c:8f:5a')
+        self.assertEqual(keypair.public_key, pub_key)
+        self.assertEqual(keypair.private_key, None)
+
+
+    def test_ex_import_keypair_from_string(self):
+        name = 'key3'
+        path = os.path.join(os.path.dirname(__file__), "fixtures", "misc", 
"dummy_rsa.pub")
+        pub_key = open(path, 'r').read()
+        keypair = self.driver.ex_import_keypair_from_string(name, pub_key)
+        self.assertEqual(keypair.name, name)
+        self.assertEqual(keypair.fingerprint, 
'97:10:a6:e7:92:65:7e:69:fe:e6:81:8f:39:3c:8f:5a')
+        self.assertEqual(keypair.public_key, pub_key)
+        self.assertEqual(keypair.private_key, None)
+
+    def test_ex_delete_keypair(self):
+        keypair = OpenStackKeyPair(name='key1', fingerprint=None, 
public_key=None, driver=self.driver)
+        result = self.driver.ex_delete_keypair(keypair)
+        self.assertTrue(result)
+
     def test_ex_list_floating_ip_pools(self):
         ret = self.driver.ex_list_floating_ip_pools()
         self.assertEqual(ret[0].name, 'public')
@@ -1404,6 +1448,25 @@ class OpenStack_1_1_MockHttp(MockHttpTestCase):
         else:
             raise NotImplementedError()
 
+    def _v1_1_slug_os_keypairs(self, method, url, body, headers):
+        if method == "GET":
+            body = self.fixtures.load('_os_keypairs.json')
+        elif method == "POST":
+            if 'public_key' in body:
+                body = self.fixtures.load('_os_keypairs_create_import.json')
+            else:
+                body = self.fixtures.load('_os_keypairs_create.json')
+        else:
+            raise NotImplementedError()
+
+        return (httplib.OK, body, self.json_content_headers, 
httplib.responses[httplib.OK])
+
+    def _v1_1_slug_os_keypairs_key1(self, method, url, body, headers):
+        if method == "DELETE":
+            return (httplib.ACCEPTED, "", {}, 
httplib.responses[httplib.ACCEPTED])
+        else:
+            raise NotImplementedError()
+
     def _v1_1_slug_os_volumes(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_os_volumes.json')

Reply via email to