From 17e3483a43487d56c38cd8bf0209203947bf1707 Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Thu, 28 Jul 2011 10:41:06 -0700 Subject: [PATCH] Adding refactored API Client --- quantum/client.py | 266 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 quantum/client.py diff --git a/quantum/client.py b/quantum/client.py new file mode 100644 index 000000000..a9d57e9c9 --- /dev/null +++ b/quantum/client.py @@ -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)) -- 2.45.2