]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Adding refactored API Client
authorTyler Smith <tylesmit@cisco.com>
Thu, 28 Jul 2011 17:41:06 +0000 (10:41 -0700)
committerTyler Smith <tylesmit@cisco.com>
Thu, 28 Jul 2011 17:41:06 +0000 (10:41 -0700)
quantum/client.py [new file with mode: 0644]

diff --git a/quantum/client.py b/quantum/client.py
new file mode 100644 (file)
index 0000000..a9d57e9
--- /dev/null
@@ -0,0 +1,266 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Citrix Systems
+# All Rights Reserved.
+#
+#    Licensed 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.
+#    @author: Tyler Smith, Cisco Systems
+
+import httplib
+import socket
+import urllib
+from quantum.common.wsgi import Serializer
+
+class api_call(object):
+    """A Decorator to add support for format and tenant overriding"""
+    def __init__(self, f):
+        self.f = f
+
+    def __get__(self, instance, owner):
+        def with_params(*args, **kwargs):
+            # Backup the format and tenant, then temporarily change them if needed
+            (format, tenant) = (instance.format, instance.tenant)
+
+            if 'format' in kwargs:
+                instance.format = kwargs['format']
+            if 'tenant' in kwargs:
+                instance.tenant = kwargs['tenant']
+
+            ret = self.f(instance, *args)
+            (instance.format, instance.tenant) = (format, tenant)
+            return ret
+        return with_params
+
+class Client(object):
+
+    """A base client class - derived from Glance.BaseClient"""
+
+    action_prefix = '/v0.1/tenants/{tenant_id}'
+    
+    """Action query strings"""
+    networks_path = "/networks"
+    network_path = "/networks/%s"
+    ports_path = "/networks/%s/ports"
+    port_path = "/networks/%s/ports/%s"
+    attachment_path = "/networks/%s/ports/%s/attachment"
+
+    def __init__(self, host = "127.0.0.1", port = 9696, use_ssl = False,
+        tenant=None, format="xml", testingStub=None, key_file=None, cert_file=None):
+        """
+        Creates a new client to some service.
+
+        :param host: The host where service resides
+        :param port: The port where service resides
+        :param use_ssl: True to use SSL, False to use HTTP
+        :param tenant: The tenant ID to make requests with
+        :param format: The format to query the server with
+        :param testingStub: A class that stubs basic server attributes for tests
+        :param key_file: The SSL key file to use if use_ssl is true
+        :param cert_file: The SSL cert file to use if use_ssl is true
+        """
+        self.host = host
+        self.port = port
+        self.use_ssl = use_ssl
+        self.tenant = tenant
+        self.format = format
+        self.connection = None
+        self.testingStub = testingStub
+        self.key_file = key_file
+        self.cert_file = cert_file
+
+    def get_connection_type(self):
+        """
+        Returns the proper connection type
+        """
+        if self.testingStub:
+            return self.testingStub
+        if self.use_ssl:
+            return httplib.HTTPSConnection
+        else:
+            return httplib.HTTPConnection
+
+    def do_request(self, method, action, body=None,
+                   headers=None, params=None):
+        """
+        Connects to the server and issues a request.  
+        Returns the result data, or raises an appropriate exception if
+        HTTP status code is not 2xx
+
+        :param method: HTTP method ("GET", "POST", "PUT", etc...)
+        :param body: string of data to send, or None (default)
+        :param headers: mapping of key/value pairs to add as headers
+        :param params: dictionary of key/value pairs to add to append
+                             to action
+
+        """
+        
+        # Ensure we have a tenant id
+        if not self.tenant:
+            raise Exception("Tenant ID not set")
+
+        # Add format and tenant_id
+        action += ".%s" % self.format
+        action = Client.action_prefix + action
+        action = action.replace('{tenant_id}',self.tenant)
+
+        if type(params) is dict:
+            action += '?' + urllib.urlencode(params)
+
+        try:
+            connection_type = self.get_connection_type()
+            headers = headers or {}
+            
+            # Open connection and send request, handling SSL certs
+            certs = {'key_file':self.key_file, 'cert_file':self.cert_file}
+
+            # Only use ssl certs if the cert dict has 1 or more file strings
+            if len([x for x in certs if x != None]):
+                c = connection_type(self.host, self.port, **certs)
+            else:
+                c = connection_type(self.host, self.port)
+
+            c.request(method, action, body, headers)
+            res = c.getresponse()
+            status_code = self.get_status_code(res)
+            if status_code in (httplib.OK,
+                               httplib.CREATED,
+                               httplib.ACCEPTED,
+                               httplib.NO_CONTENT):
+                return self.deserialize(res)
+            else:
+                raise Exception("Server returned error: %s" % res.read())
+
+        except (socket.error, IOError), e:
+            raise Exception("Unable to connect to "
+                            "server. Got error: %s" % e)
+
+    def get_status_code(self, response):
+        """
+        Returns the integer status code from the response, which
+        can be either a Webob.Response (used in testing) or httplib.Response
+        """
+        if hasattr(response, 'status_int'):
+            return response.status_int
+        else:
+            return response.status
+
+    def serialize(self, data):
+        if type(data) is dict:
+            return Serializer().serialize(data, self.content_type())
+
+    def deserialize(self, data):
+        if self.get_status_code(data) == 202:
+            return data.read()
+        return Serializer().deserialize(data.read(), self.content_type())
+
+    def content_type(self, format=None):
+        if not format:
+            format = self.format
+        return "application/%s" % (format)
+
+    @api_call
+    def list_networks(self):
+        """
+        Queries the server for a list of networks
+        """
+        return self.do_request("GET", self.networks_path)
+
+    @api_call
+    def list_network_details(self, network):
+        """
+        Queries the server for the details of a certain network
+        """
+        return self.do_request("GET", (self.network_path%network))
+
+    @api_call
+    def create_network(self, body=None):
+        """
+        Creates a new network on the server
+        """
+        body = self.serialize(body)
+        return self.do_request("POST", self.networks_path, body=body)
+
+    @api_call
+    def update_network(self, network, body=None):
+        """
+        Updates a network on the server
+        """
+        body = self.serialize(body)
+        return self.do_request("PUT", self.network_path % (network),body=body)
+
+    @api_call
+    def delete_network(self, network):
+        """
+        Deletes a network on the server
+        """
+        return self.do_request("DELETE", self.network_path % (network))
+
+    @api_call
+    def list_ports(self, network):
+        """
+        Queries the server for a list of ports on a given network
+        """
+        return self.do_request("GET", self.ports_path % (network))
+
+    @api_call
+    def list_port_details(self, network, port):
+        """
+        Queries the server for a list of ports on a given network
+        """
+        return self.do_request("GET", self.port_path % (network,port))
+
+    @api_call
+    def create_port(self, network):
+        """
+        Creates a new port on a network on the server
+        """
+        return self.do_request("POST", self.ports_path % (network))
+
+    @api_call
+    def delete_port(self, network, port):
+        """
+        Deletes a port from a network on the server
+        """
+        return self.do_request("DELETE", self.port_path % (network,port))
+
+    @api_call
+    def set_port_state(self, network, port, body=None):
+        """
+        Sets the state of a port on the server
+        """
+        body = self.serialize(body)
+        return self.do_request("PUT",
+            self.port_path % (network,port), body=body)
+
+    @api_call
+    def list_port_attachments(self, network, port):
+        """
+        Deletes a port from a network on the server
+        """
+        return self.do_request("GET", self.attachment_path % (network,port))
+    
+    @api_call
+    def attach_resource(self, network, port, body=None):
+        """
+        Deletes a port from a network on the server
+        """
+        body = self.serialize(body)
+        return self.do_request("PUT",
+            self.attachment_path % (network,port), body=body)
+
+    @api_call
+    def detach_resource(self, network, port):
+        """
+        Deletes a port from a network on the server
+        """
+        return self.do_request("DELETE", self.attachment_path % (network,port))