add keystone support, new command interface, API v2.0
blueprint new-cli Bug #1001053 Implement new commands interface, ready for v2.0. adopt cliff arch. new client binary is quantumv2. After it is stable, we will remove quantum binary. Httplibs2 is used. usage: https://docs.google.com/document/d/1e_4UtnhFfgtnsB8EVB31BZKldaVzl_BlsGnGBrKmcDk/edit Patch 2: add license header Patch 3: add v1.0 support, fix show net details Patch 4: quantumclient network api v2.0 Patch 5: subnet and port commands for api v2.0, add fields selector Patch 6: add test cases Patch 7: fix interactive mode, modify according to comments and https://review.openstack.org/#/c/8366/, add two tasks to BP: noauth and openstack common Patch 8: fix log problem Patch 9: modify according to the comments by dan on patch 5 Patch 10: just trigger jenkins Patch 11: pep 1.3 fix Patch 12: cliff and prettytable to more than 0.6.0 Patch 13: change setup.py to include more packages Patch 14: pep check on jenkins Patch 15: add license text to empty __init__.py files Patch 16: fix v1.1 test cases after server changes Change-Id: Ibbbdd834371c6a023b31e4797718fc0fe9786d89
This commit is contained in:
		| @@ -30,11 +30,46 @@ gettext.install('quantumclient', unicode=1) | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common.serializer import Serializer | ||||
| from quantumclient.common import utils | ||||
|  | ||||
| net_filters_v11_opt = [] | ||||
| net_filters_v11_opt.append({'--name': | ||||
|                             {'help': _('name filter'), }, }) | ||||
| net_filters_v11_opt.append({'--op-status': | ||||
|                             {'help': _('op-status filter'), }, }) | ||||
| net_filters_v11_opt.append({'--port-op-status': | ||||
|                             {'help': _('port-op-status filter'), }, }) | ||||
| net_filters_v11_opt.append({'--port-state': | ||||
|                             {'help': _('port-state filter'), }, }) | ||||
| net_filters_v11_opt.append({'--has-attachment': | ||||
|                             {'help': _('has-attachment filter'), }, }) | ||||
| net_filters_v11_opt.append({'--attachment': | ||||
|                             {'help': _('attachment filter'), }, }) | ||||
| net_filters_v11_opt.append({'--port': | ||||
|                             {'help': _('port filter'), }, }) | ||||
|  | ||||
| port_filters_v11_opt = [] | ||||
| port_filters_v11_opt.append({'--name': | ||||
|                              {'help': _('name filter'), }, }) | ||||
| port_filters_v11_opt.append({'--op-status': | ||||
|                              {'help': _('op-status filter'), }, }) | ||||
| port_filters_v11_opt.append({'--has-attachment': | ||||
|                              {'help': _('has-attachment filter'), }, }) | ||||
| port_filters_v11_opt.append({'--attachement': | ||||
|                              {'help': _('attachment filter'), }, }) | ||||
|  | ||||
| net_filters_v11 = [] | ||||
| for arg in net_filters_v11_opt: | ||||
|     for key in arg.iterkeys(): | ||||
|         net_filters_v11.append(key.split('--', 2)[1]) | ||||
|  | ||||
| port_filters_v11 = [] | ||||
| for arg in port_filters_v11_opt: | ||||
|     for key in arg.iterkeys(): | ||||
|         port_filters_v11.append(key.split('--', 2)[1]) | ||||
|  | ||||
| LOG = logging.getLogger('quantumclient') | ||||
|  | ||||
|  | ||||
| AUTH_TOKEN_HEADER = "X-Auth-Token" | ||||
|  | ||||
|  | ||||
| @@ -55,12 +90,11 @@ def exception_handler_v10(status_code, error_content): | ||||
|         430: 'portNotFound', | ||||
|         431: 'requestedStateInvalid', | ||||
|         432: 'portInUse', | ||||
|         440: 'alreadyAttached', | ||||
|         } | ||||
|         440: 'alreadyAttached', } | ||||
|  | ||||
|     quantum_errors = { | ||||
|         400: exceptions.BadInputError, | ||||
|         401: exceptions.NotAuthorized, | ||||
|         401: exceptions.Unauthorized, | ||||
|         404: exceptions.NotFound, | ||||
|         420: exceptions.NetworkNotFoundClient, | ||||
|         421: exceptions.NetworkInUseClient, | ||||
| @@ -68,8 +102,7 @@ def exception_handler_v10(status_code, error_content): | ||||
|         431: exceptions.StateInvalidClient, | ||||
|         432: exceptions.PortInUseClient, | ||||
|         440: exceptions.AlreadyAttachedClient, | ||||
|         501: NotImplementedError, | ||||
|         } | ||||
|         501: NotImplementedError, } | ||||
|  | ||||
|     # Find real error type | ||||
|     error_type = None | ||||
| @@ -105,7 +138,7 @@ def exception_handler_v11(status_code, error_content): | ||||
|         'RequestedStateInvalid': exceptions.StateInvalidClient, | ||||
|         'PortInUse': exceptions.PortInUseClient, | ||||
|         'AlreadyAttached': exceptions.AlreadyAttachedClient, | ||||
|         } | ||||
|         'QuantumServiceFault': exceptions.QuantumClientException, } | ||||
|  | ||||
|     error_dict = None | ||||
|     if isinstance(error_content, dict): | ||||
| @@ -156,6 +189,33 @@ class ApiCall(object): | ||||
|         return with_params | ||||
|  | ||||
|  | ||||
| class APIFilterCall(object): | ||||
|  | ||||
|     def __init__(self, filters): | ||||
|         self.filters = filters | ||||
|  | ||||
|     def __call__(self, f): | ||||
|         def wrapped_f(*args, **kwargs): | ||||
|             """ | ||||
|             Temporarily sets the format and tenant for this request | ||||
|             """ | ||||
|             instance = args[0] | ||||
|             (format, tenant) = (instance.format, instance.tenant) | ||||
|  | ||||
|             if 'format' in kwargs: | ||||
|                 instance.format = kwargs['format'] | ||||
|                 if 'format' not in self.filters: | ||||
|                     del kwargs['format'] | ||||
|             if 'tenant' in kwargs: | ||||
|                 instance.tenant = kwargs['tenant'] | ||||
|                 if 'tenant' not in self.filters: | ||||
|                     del kwargs['tenant'] | ||||
|             ret = f(*args, **kwargs) | ||||
|             (instance.format, instance.tenant) = (format, tenant) | ||||
|             return ret | ||||
|         return wrapped_f | ||||
|  | ||||
|  | ||||
| class Client(object): | ||||
|  | ||||
|     """A base client class - derived from Glance.BaseClient""" | ||||
| @@ -166,13 +226,10 @@ class Client(object): | ||||
|             "attributes": { | ||||
|                 "network": ["id", "name"], | ||||
|                 "port": ["id", "state"], | ||||
|                 "attachment": ["id"]}, | ||||
|                 "attachment": ["id"], }, | ||||
|             "plurals": { | ||||
|                 "networks": "network", | ||||
|                 "ports": "port", | ||||
|                 }, | ||||
|             }, | ||||
|         } | ||||
|                 "ports": "port", }, }, } | ||||
|  | ||||
|     # Action query strings | ||||
|     networks_path = "/networks" | ||||
| @@ -298,19 +355,18 @@ class Client(object): | ||||
|                 headers[AUTH_TOKEN_HEADER] = self.auth_token | ||||
|             # Open connection and send request, handling SSL certs | ||||
|             certs = {'key_file': self.key_file, 'cert_file': self.cert_file} | ||||
|             certs = dict((x, certs[x]) for x in certs if certs[x] != None) | ||||
|             certs = dict((x, certs[x]) for x in certs if certs[x] is not None) | ||||
|             if self.use_ssl and len(certs): | ||||
|                 conn = connection_type(self.host, self.port, **certs) | ||||
|             else: | ||||
|                 conn = connection_type(self.host, self.port) | ||||
|             # besides HTTP(s)Connection, we still have testConnection | ||||
|             if (LOG.isEnabledFor(logging.DEBUG) and | ||||
|                 isinstance(conn, httplib.HTTPConnection)): | ||||
|                 conn.set_debuglevel(1) | ||||
|  | ||||
|             res = self._send_request(conn, method, action, body, headers) | ||||
|             status_code = self.get_status_code(res) | ||||
|             data = res.read() | ||||
|             utils.http_log(LOG, [method, action], | ||||
|                            {'body': body, | ||||
|                             'headers': headers, | ||||
|                             }, res, data) | ||||
|             if self.logger: | ||||
|                 self.logger.debug("Quantum Client Reply (code = %s) :\n %s" % | ||||
|                                   (str(status_code), data)) | ||||
| @@ -531,7 +587,7 @@ class ClientV11(Client): | ||||
|     features specific to API v1.1 such as filters | ||||
|     """ | ||||
|  | ||||
|     @ApiCall | ||||
|     @APIFilterCall(net_filters_v11) | ||||
|     def list_networks(self, **filters): | ||||
|         """ | ||||
|         Fetches a list of all networks for a tenant | ||||
| @@ -539,14 +595,14 @@ class ClientV11(Client): | ||||
|         # Pass filters in "params" argument to do_request | ||||
|         return self.get(self.networks_path, params=filters) | ||||
|  | ||||
|     @ApiCall | ||||
|     @APIFilterCall(net_filters_v11) | ||||
|     def list_networks_details(self, **filters): | ||||
|         """ | ||||
|         Fetches a detailed list of all networks for a tenant | ||||
|         """ | ||||
|         return self.get(self.networks_path + self.detail_path, params=filters) | ||||
|  | ||||
|     @ApiCall | ||||
|     @APIFilterCall(port_filters_v11) | ||||
|     def list_ports(self, network, **filters): | ||||
|         """ | ||||
|         Fetches a list of ports on a given network | ||||
| @@ -554,7 +610,7 @@ class ClientV11(Client): | ||||
|         # Pass filters in "params" argument to do_request | ||||
|         return self.get(self.ports_path % (network), params=filters) | ||||
|  | ||||
|     @ApiCall | ||||
|     @APIFilterCall(port_filters_v11) | ||||
|     def list_ports_details(self, network, **filters): | ||||
|         """ | ||||
|         Fetches a detailed list of ports on a given network | ||||
|   | ||||
| @@ -29,7 +29,8 @@ from quantumclient import Client | ||||
| from quantumclient import ClientV11 | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common import utils | ||||
|  | ||||
| from quantumclient import net_filters_v11 | ||||
| from quantumclient import port_filters_v11 | ||||
|  | ||||
| # Configure logger for client - cli logger is a child of it | ||||
| # NOTE(salvatore-orlando): logger name does not map to package | ||||
| @@ -43,107 +44,80 @@ FORMAT = 'json' | ||||
| commands_v10 = { | ||||
|     "list_nets": { | ||||
|         "func": cli_lib.list_nets, | ||||
|         "args": ["tenant-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id"], }, | ||||
|     "list_nets_detail": { | ||||
|         "func": cli_lib.list_nets_detail, | ||||
|         "args": ["tenant-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id"], }, | ||||
|     "create_net": { | ||||
|         "func": cli_lib.create_net, | ||||
|         "args": ["tenant-id", "net-name"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-name"], }, | ||||
|     "delete_net": { | ||||
|         "func": cli_lib.delete_net, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id"], }, | ||||
|     "show_net": { | ||||
|         "func": cli_lib.show_net, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id"], }, | ||||
|     "show_net_detail": { | ||||
|         "func": cli_lib.show_net_detail, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id"], }, | ||||
|     "update_net": { | ||||
|         "func": cli_lib.update_net, | ||||
|         "args": ["tenant-id", "net-id", "new-name"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "new-name"], }, | ||||
|     "list_ports": { | ||||
|         "func": cli_lib.list_ports, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id"], }, | ||||
|     "list_ports_detail": { | ||||
|         "func": cli_lib.list_ports_detail, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id"], }, | ||||
|     "create_port": { | ||||
|         "func": cli_lib.create_port, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id"], }, | ||||
|     "delete_port": { | ||||
|         "func": cli_lib.delete_port, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], }, | ||||
|     "update_port": { | ||||
|         "func": cli_lib.update_port, | ||||
|         "args": ["tenant-id", "net-id", "port-id", "params"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "port-id", "params"], }, | ||||
|     "show_port": { | ||||
|         "func": cli_lib.show_port, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], }, | ||||
|     "show_port_detail": { | ||||
|         "func": cli_lib.show_port_detail, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], }, | ||||
|     "plug_iface": { | ||||
|         "func": cli_lib.plug_iface, | ||||
|         "args": ["tenant-id", "net-id", "port-id", "iface-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "port-id", "iface-id"], }, | ||||
|     "unplug_iface": { | ||||
|         "func": cli_lib.unplug_iface, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], | ||||
|         }, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], }, | ||||
|     "show_iface": { | ||||
|         "func": cli_lib.show_iface, | ||||
|         "args": ["tenant-id", "net-id", "port-id"], | ||||
|         }, | ||||
|     } | ||||
|         "args": ["tenant-id", "net-id", "port-id"], }, } | ||||
|  | ||||
| commands_v11 = commands_v10.copy() | ||||
| commands_v11.update({ | ||||
|     "list_nets": { | ||||
|         "func": cli_lib.list_nets_v11, | ||||
|         "args": ["tenant-id"], | ||||
|         "filters": ["name", "op-status", "port-op-status", "port-state", | ||||
|                     "has-attachment", "attachment", "port"], | ||||
|         }, | ||||
|         "filters": net_filters_v11, }, | ||||
|     "list_nets_detail": { | ||||
|         "func": cli_lib.list_nets_detail_v11, | ||||
|         "args": ["tenant-id"], | ||||
|         "filters": ["name", "op-status", "port-op-status", "port-state", | ||||
|                     "has-attachment", "attachment", "port"], | ||||
|         }, | ||||
|         "filters": net_filters_v11, }, | ||||
|     "list_ports": { | ||||
|         "func": cli_lib.list_ports_v11, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         "filters": ["name", "op-status", "has-attachment", "attachment"], | ||||
|         }, | ||||
|         "filters": port_filters_v11, }, | ||||
|     "list_ports_detail": { | ||||
|         "func": cli_lib.list_ports_detail_v11, | ||||
|         "args": ["tenant-id", "net-id"], | ||||
|         "filters": ["name", "op-status", "has-attachment", "attachment"], | ||||
|         }, | ||||
|     }) | ||||
|         "filters": port_filters_v11, }, }) | ||||
| commands = { | ||||
|     '1.0': commands_v10, | ||||
|     '1.1': commands_v11, | ||||
|     } | ||||
|     '1.1': commands_v11, } | ||||
| clients = { | ||||
|     '1.0': Client, | ||||
|     '1.1': ClientV11, | ||||
|     } | ||||
|     '1.1': ClientV11, } | ||||
|  | ||||
|  | ||||
| def help(version): | ||||
|   | ||||
| @@ -201,8 +201,7 @@ interface: %(iface.id)s | ||||
| plugged in Logical Port ID: %(port_id)s | ||||
| on Virtual Network: %(network_id)s | ||||
| for Tenant: %(tenant_id)s | ||||
| """.strip(), | ||||
|         ) | ||||
| """.strip(), ) | ||||
|  | ||||
|     _templates_v11 = _templates_v10.copy() | ||||
|     _templates_v11.update(dict( | ||||
| @@ -236,13 +235,11 @@ operational status: %(port.op-status)s | ||||
| interface: %(port.attachment.id)s | ||||
| on Virtual Network: %(network_id)s | ||||
| for Tenant: %(tenant_id)s | ||||
| """.strip(), | ||||
|         )) | ||||
| """.strip(), )) | ||||
|  | ||||
|     _templates = { | ||||
|         '1.0': _templates_v10, | ||||
|         '1.1': _templates_v11 | ||||
|         } | ||||
|         '1.1': _templates_v11, } | ||||
|  | ||||
|     def __init__(self, cmd, data, version): | ||||
|         super(CmdOutputTemplate, self).__init__( | ||||
|   | ||||
							
								
								
									
										200
									
								
								quantumclient/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								quantumclient/client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| try: | ||||
|     import json | ||||
| except ImportError: | ||||
|     import simplejson as json | ||||
| import logging | ||||
| import urlparse | ||||
| # Python 2.5 compat fix | ||||
| if not hasattr(urlparse, 'parse_qsl'): | ||||
|     import cgi | ||||
|     urlparse.parse_qsl = cgi.parse_qsl | ||||
|  | ||||
| import httplib2 | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common import utils | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class ServiceCatalog(object): | ||||
|     """Helper methods for dealing with a Keystone Service Catalog.""" | ||||
|  | ||||
|     def __init__(self, resource_dict): | ||||
|         self.catalog = resource_dict | ||||
|  | ||||
|     def get_token(self): | ||||
|         """Fetch token details fron service catalog""" | ||||
|         token = {'id': self.catalog['access']['token']['id'], | ||||
|                  'expires': self.catalog['access']['token']['expires'], } | ||||
|         try: | ||||
|             token['user_id'] = self.catalog['access']['user']['id'] | ||||
|             token['tenant_id'] = ( | ||||
|                 self.catalog['access']['token']['tenant']['id']) | ||||
|         except: | ||||
|             # just leave the tenant and user out if it doesn't exist | ||||
|             pass | ||||
|         return token | ||||
|  | ||||
|     def url_for(self, attr=None, filter_value=None, | ||||
|                 service_type='network', endpoint_type='adminURL'): | ||||
|         """Fetch the admin URL from the Quantum service for | ||||
|         a particular endpoint attribute. If none given, return | ||||
|         the first. See tests for sample service catalog.""" | ||||
|  | ||||
|         catalog = self.catalog['access'].get('serviceCatalog', []) | ||||
|         matching_endpoints = [] | ||||
|         for service in catalog: | ||||
|             if service['type'] != service_type: | ||||
|                 continue | ||||
|  | ||||
|             endpoints = service['endpoints'] | ||||
|             for endpoint in endpoints: | ||||
|                 if not filter_value or endpoint.get(attr) == filter_value: | ||||
|                     matching_endpoints.append(endpoint) | ||||
|  | ||||
|         if not matching_endpoints: | ||||
|             raise exceptions.EndpointNotFound() | ||||
|         elif len(matching_endpoints) > 1: | ||||
|             raise exceptions.AmbiguousEndpoints(message=matching_endpoints) | ||||
|         else: | ||||
|             return matching_endpoints[0][endpoint_type] | ||||
|  | ||||
|  | ||||
| class HTTPClient(httplib2.Http): | ||||
|     """Handles the REST calls and responses, include authn""" | ||||
|  | ||||
|     USER_AGENT = 'python-quantumclient' | ||||
|  | ||||
|     def __init__(self, username=None, tenant_name=None, | ||||
|                  password=None, auth_url=None, | ||||
|                  token=None, region_name=None, timeout=None, | ||||
|                  endpoint_url=None, insecure=False, **kwargs): | ||||
|         super(HTTPClient, self).__init__(timeout=timeout) | ||||
|         self.username = username | ||||
|         self.tenant_name = tenant_name | ||||
|         self.password = password | ||||
|         self.auth_url = auth_url.rstrip('/') if auth_url else None | ||||
|         self.region_name = region_name | ||||
|         self.auth_token = token | ||||
|         self.content_type = 'application/json' | ||||
|         self.endpoint_url = endpoint_url | ||||
|         # httplib2 overrides | ||||
|         self.force_exception_to_status_code = True | ||||
|         self.disable_ssl_certificate_validation = insecure | ||||
|  | ||||
|     def _cs_request(self, *args, **kwargs): | ||||
|         kargs = {} | ||||
|         kargs.setdefault('headers', kwargs.get('headers', {})) | ||||
|         kargs['headers']['User-Agent'] = self.USER_AGENT | ||||
|  | ||||
|         if 'content_type' in kwargs: | ||||
|             kargs['headers']['Content-Type'] = kwargs['content_type'] | ||||
|             kargs['headers']['Accept'] = kwargs['content_type'] | ||||
|         else: | ||||
|             kargs['headers']['Content-Type'] = self.content_type | ||||
|             kargs['headers']['Accept'] = self.content_type | ||||
|  | ||||
|         if 'body' in kwargs: | ||||
|             kargs['body'] = kwargs['body'] | ||||
|  | ||||
|         resp, body = self.request(*args, **kargs) | ||||
|  | ||||
|         utils.http_log(_logger, args, kargs, resp, body) | ||||
|         status_code = self.get_status_code(resp) | ||||
|         if status_code == 401: | ||||
|             raise exceptions.Unauthorized(message=body) | ||||
|         elif status_code == 403: | ||||
|             raise exceptions.Forbidden(message=body) | ||||
|         return resp, body | ||||
|  | ||||
|     def do_request(self, url, method, **kwargs): | ||||
|         if not self.endpoint_url or not self.auth_token: | ||||
|             self.authenticate() | ||||
|  | ||||
|         # Perform the request once. If we get a 401 back then it | ||||
|         # might be because the auth token expired, so try to | ||||
|         # re-authenticate and try again. If it still fails, bail. | ||||
|         try: | ||||
|             kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token | ||||
|             resp, body = self._cs_request(self.endpoint_url + url, method, | ||||
|                                           **kwargs) | ||||
|             return resp, body | ||||
|         except exceptions.Unauthorized as ex: | ||||
|             if not self.endpoint_url or not self.auth_token: | ||||
|                 self.authenticate() | ||||
|                 resp, body = self._cs_request( | ||||
|                     self.management_url + url, method, **kwargs) | ||||
|                 return resp, body | ||||
|             else: | ||||
|                 raise ex | ||||
|  | ||||
|     def _extract_service_catalog(self, body): | ||||
|         """ Set the client's service catalog from the response data. """ | ||||
|         self.service_catalog = ServiceCatalog(body) | ||||
|         try: | ||||
|             sc = self.service_catalog.get_token() | ||||
|             self.auth_token = sc['id'] | ||||
|             self.auth_tenant_id = sc.get('tenant_id') | ||||
|             self.auth_user_id = sc.get('user_id') | ||||
|         except KeyError: | ||||
|             raise exceptions.Unauthorized() | ||||
|         self.endpoint_url = self.service_catalog.url_for( | ||||
|             attr='region', filter_value=self.region_name, | ||||
|             endpoint_type='adminURL') | ||||
|  | ||||
|     def authenticate(self): | ||||
|         body = {'auth': {'passwordCredentials': | ||||
|                          {'username': self.username, | ||||
|                           'password': self.password, }, | ||||
|                          'tenantName': self.tenant_name, }, } | ||||
|  | ||||
|         token_url = self.auth_url + "/tokens" | ||||
|  | ||||
|         # Make sure we follow redirects when trying to reach Keystone | ||||
|         tmp_follow_all_redirects = self.follow_all_redirects | ||||
|         self.follow_all_redirects = True | ||||
|         try: | ||||
|             resp, body = self._cs_request(token_url, "POST", | ||||
|                                           body=json.dumps(body), | ||||
|                                           content_type="application/json") | ||||
|         finally: | ||||
|             self.follow_all_redirects = tmp_follow_all_redirects | ||||
|         status_code = self.get_status_code(resp) | ||||
|         if status_code != 200: | ||||
|             raise exceptions.Unauthorized(message=body) | ||||
|         if body: | ||||
|             try: | ||||
|                 body = json.loads(body) | ||||
|             except ValueError: | ||||
|                 pass | ||||
|         else: | ||||
|             body = None | ||||
|         self._extract_service_catalog(body) | ||||
|  | ||||
|     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 | ||||
							
								
								
									
										79
									
								
								quantumclient/common/clientmanager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								quantumclient/common/clientmanager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| """Manage access to the clients, including authenticating when needed. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from quantumclient.common import exceptions as exc | ||||
|  | ||||
| from quantumclient.quantum import client as quantum_client | ||||
| from quantumclient.client import HTTPClient | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class ClientCache(object): | ||||
|     """Descriptor class for caching created client handles. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, factory): | ||||
|         self.factory = factory | ||||
|         self._handle = None | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         # Tell the ClientManager to login to keystone | ||||
|         if self._handle is None: | ||||
|             self._handle = self.factory(instance) | ||||
|         return self._handle | ||||
|  | ||||
|  | ||||
| class ClientManager(object): | ||||
|     """Manages access to API clients, including authentication. | ||||
|     """ | ||||
|     quantum = ClientCache(quantum_client.make_client) | ||||
|  | ||||
|     def __init__(self, token=None, url=None, | ||||
|                  auth_url=None, | ||||
|                  tenant_name=None, tenant_id=None, | ||||
|                  username=None, password=None, | ||||
|                  region_name=None, | ||||
|                  api_version=None, | ||||
|                  ): | ||||
|         self._token = token | ||||
|         self._url = url | ||||
|         self._auth_url = auth_url | ||||
|         self._tenant_name = tenant_name | ||||
|         self._tenant_id = tenant_id | ||||
|         self._username = username | ||||
|         self._password = password | ||||
|         self._region_name = region_name | ||||
|         self._api_version = api_version | ||||
|         self._service_catalog = None | ||||
|         return | ||||
|  | ||||
|     def initialize(self): | ||||
|         if not self._url: | ||||
|             httpclient = HTTPClient(username=self._username, | ||||
|                                     tenant_name=self._tenant_name, | ||||
|                                     password=self._password, | ||||
|                                     region_name=self._region_name, | ||||
|                                     auth_url=self._auth_url) | ||||
|             httpclient.authenticate() | ||||
|             # Populate other password flow attributes | ||||
|             self._token = httpclient.auth_token | ||||
|             self._url = httpclient.endpoint_url | ||||
							
								
								
									
										35
									
								
								quantumclient/common/command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								quantumclient/common/command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| """ | ||||
| OpenStack base command | ||||
| """ | ||||
|  | ||||
| from cliff.command import Command | ||||
|  | ||||
|  | ||||
| class OpenStackCommand(Command): | ||||
|     """Base class for OpenStack commands | ||||
|     """ | ||||
|  | ||||
|     api = None | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         if not self.api: | ||||
|             return | ||||
|         else: | ||||
|             return super(OpenStackCommand, self).run(parsed_args) | ||||
| @@ -87,10 +87,33 @@ class AlreadyAttachedClient(QuantumClientException): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class NotAuthorized(QuantumClientException): | ||||
| class Unauthorized(QuantumClientException): | ||||
|     """ | ||||
|     HTTP 401 - Unauthorized: bad credentials. | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class Forbidden(QuantumClientException): | ||||
|     """ | ||||
|     HTTP 403 - Forbidden: your credentials don't give you access to this | ||||
|     resource. | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class EndpointNotFound(QuantumClientException): | ||||
|     """Could not find Service or Region in Service Catalog.""" | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class AmbiguousEndpoints(QuantumClientException): | ||||
|     """Found more than one matching endpoint in Service Catalog.""" | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "AmbiguousEndpoints: %s" % repr(self.message) | ||||
|  | ||||
|  | ||||
| class QuantumCLIError(QuantumClientException): | ||||
|     """ Exception raised when command line parsing fails """ | ||||
|     pass | ||||
| @@ -120,3 +143,13 @@ class Invalid(Error): | ||||
|  | ||||
| class InvalidContentType(Invalid): | ||||
|     message = _("Invalid content type %(content_type)s.") | ||||
|  | ||||
|  | ||||
| class UnsupportedVersion(Exception): | ||||
|     """Indicates that the user is trying to use an unsupported | ||||
|        version of the API""" | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class CommandError(Exception): | ||||
|     pass | ||||
|   | ||||
| @@ -22,7 +22,11 @@ | ||||
|  | ||||
| import datetime | ||||
| import json | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
|  | ||||
|  | ||||
| def env(*vars, **kwargs): | ||||
| @@ -38,12 +42,12 @@ def env(*vars, **kwargs): | ||||
|  | ||||
|  | ||||
| def to_primitive(value): | ||||
|     if type(value) is type([]) or type(value) is type((None,)): | ||||
|     if isinstance(value, list) or isinstance(value, tuple): | ||||
|         o = [] | ||||
|         for v in value: | ||||
|             o.append(to_primitive(v)) | ||||
|         return o | ||||
|     elif type(value) is type({}): | ||||
|     elif isinstance(value, dict): | ||||
|         o = {} | ||||
|         for k, v in value.iteritems(): | ||||
|             o[k] = to_primitive(v) | ||||
| @@ -68,3 +72,88 @@ def dumps(value): | ||||
|  | ||||
| def loads(s): | ||||
|     return json.loads(s) | ||||
|  | ||||
|  | ||||
| def import_class(import_str): | ||||
|     """Returns a class from a string including module and class | ||||
|  | ||||
|     :param import_str: a string representation of the class name | ||||
|     :rtype: the requested class | ||||
|     """ | ||||
|     mod_str, _sep, class_str = import_str.rpartition('.') | ||||
|     __import__(mod_str) | ||||
|     return getattr(sys.modules[mod_str], class_str) | ||||
|  | ||||
|  | ||||
| def get_client_class(api_name, version, version_map): | ||||
|     """Returns the client class for the requested API version | ||||
|  | ||||
|     :param api_name: the name of the API, e.g. 'compute', 'image', etc | ||||
|     :param version: the requested API version | ||||
|     :param version_map: a dict of client classes keyed by version | ||||
|     :rtype: a client class for the requested API version | ||||
|     """ | ||||
|     try: | ||||
|         client_path = version_map[str(version)] | ||||
|     except (KeyError, ValueError): | ||||
|         msg = "Invalid %s client version '%s'. must be one of: %s" % ( | ||||
|               (api_name, version, ', '.join(version_map.keys()))) | ||||
|         raise exceptions.UnsupportedVersion(msg) | ||||
|  | ||||
|     return import_class(client_path) | ||||
|  | ||||
|  | ||||
| def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): | ||||
|     """Return a tuple containing the item properties. | ||||
|  | ||||
|     :param item: a single item resource (e.g. Server, Tenant, etc) | ||||
|     :param fields: tuple of strings with the desired field names | ||||
|     :param mixed_case_fields: tuple of field names to preserve case | ||||
|     :param formatters: dictionary mapping field names to callables | ||||
|        to format the values | ||||
|     """ | ||||
|     row = [] | ||||
|  | ||||
|     for field in fields: | ||||
|         if field in formatters: | ||||
|             row.append(formatters[field](item)) | ||||
|         else: | ||||
|             if field in mixed_case_fields: | ||||
|                 field_name = field.replace(' ', '_') | ||||
|             else: | ||||
|                 field_name = field.lower().replace(' ', '_') | ||||
|             if not hasattr(item, field_name) and isinstance(item, dict): | ||||
|                 data = item[field_name] | ||||
|             else: | ||||
|                 data = getattr(item, field_name, '') | ||||
|             row.append(data) | ||||
|     return tuple(row) | ||||
|  | ||||
|  | ||||
| def __str2bool(strbool): | ||||
|     if strbool is None: | ||||
|         return None | ||||
|     else: | ||||
|         return strbool.lower() == 'true' | ||||
|  | ||||
|  | ||||
| def http_log(_logger, args, kwargs, resp, body): | ||||
|         if not _logger.isEnabledFor(logging.DEBUG): | ||||
|             return | ||||
|  | ||||
|         string_parts = ['curl -i'] | ||||
|         for element in args: | ||||
|             if element in ('GET', 'POST'): | ||||
|                 string_parts.append(' -X %s' % element) | ||||
|             else: | ||||
|                 string_parts.append(' %s' % element) | ||||
|  | ||||
|         for element in kwargs['headers']: | ||||
|             header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) | ||||
|             string_parts.append(header) | ||||
|  | ||||
|         _logger.debug("REQ: %s\n" % "".join(string_parts)) | ||||
|         if 'body' in kwargs and kwargs['body']: | ||||
|             _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) | ||||
|         _logger.debug("RESP:%s\n", resp) | ||||
|         _logger.debug("RESP BODY:%s\n", body) | ||||
|   | ||||
							
								
								
									
										14
									
								
								quantumclient/quantum/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								quantumclient/quantum/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
							
								
								
									
										68
									
								
								quantumclient/quantum/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								quantumclient/quantum/client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
| import urlparse | ||||
|  | ||||
|  | ||||
| from quantumclient.common import utils | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
| API_NAME = 'network' | ||||
| API_VERSIONS = { | ||||
|     '1.0': 'quantumclient.Client', | ||||
|     '1.1': 'quantumclient.ClientV11', | ||||
|     '2.0': 'quantumclient.v2_0.client.Client', | ||||
| } | ||||
|  | ||||
|  | ||||
| def make_client(instance): | ||||
|     """Returns an identity service client. | ||||
|     """ | ||||
|     quantum_client = utils.get_client_class( | ||||
|         API_NAME, | ||||
|         instance._api_version[API_NAME], | ||||
|         API_VERSIONS, | ||||
|     ) | ||||
|     instance.initialize() | ||||
|     url = instance._url | ||||
|     url = url.rstrip("/") | ||||
|     client_full_name = (quantum_client.__module__ + "." + | ||||
|                         quantum_client.__name__) | ||||
|     LOG.debug("we are using client: %s", client_full_name) | ||||
|     v1x = (client_full_name == API_VERSIONS['1.1'] or | ||||
|            client_full_name == API_VERSIONS['1.0']) | ||||
|     if v1x: | ||||
|         magic_tuple = urlparse.urlsplit(url) | ||||
|         scheme, netloc, path, query, frag = magic_tuple | ||||
|         host = magic_tuple.hostname | ||||
|         port = magic_tuple.port | ||||
|         use_ssl = scheme.lower().startswith('https') | ||||
|         client = quantum_client(host=host, port=port, use_ssl=use_ssl) | ||||
|         client.auth_token = instance._token | ||||
|         client.logger = LOG | ||||
|         return client | ||||
|     else: | ||||
|         client = quantum_client(username=instance._username, | ||||
|                                 tenant_name=instance._tenant_name, | ||||
|                                 password=instance._password, | ||||
|                                 region_name=instance._region_name, | ||||
|                                 auth_url=instance._auth_url, | ||||
|                                 endpoint_url=url, | ||||
|                                 token=instance._token) | ||||
|         return client | ||||
							
								
								
									
										62
									
								
								quantumclient/quantum/v1_1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								quantumclient/quantum/v1_1/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from quantumclient.common import command | ||||
| from quantumclient.common import utils | ||||
|  | ||||
|  | ||||
| class QuantumCommand(command.OpenStackCommand): | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.QuantumCommand') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(QuantumCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--request-format', | ||||
|             help=_('the xml or json request format'), | ||||
|             default='json', | ||||
|             choices=['json', 'xml', ], ) | ||||
|         parser.add_argument( | ||||
|             'tenant_id', metavar='tenant-id', | ||||
|             help=_('the owner tenant ID'), ) | ||||
|         return parser | ||||
|  | ||||
|  | ||||
| class QuantumPortCommand(QuantumCommand): | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.QuantumPortCommand') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(QuantumPortCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'net_id', metavar='net-id', | ||||
|             help=_('the owner network ID'), ) | ||||
|         return parser | ||||
|  | ||||
|  | ||||
| class QuantumInterfaceCommand(QuantumPortCommand): | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.QuantumInterfaceCommand') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(QuantumInterfaceCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'port_id', metavar='port-id', | ||||
|             help=_('the owner Port ID'), ) | ||||
|         return parser | ||||
							
								
								
									
										97
									
								
								quantumclient/quantum/v1_1/interface.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								quantumclient/quantum/v1_1/interface.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from cliff import show | ||||
|  | ||||
| from quantumclient.quantum.v1_1 import QuantumInterfaceCommand | ||||
|  | ||||
|  | ||||
| class PlugInterface(QuantumInterfaceCommand): | ||||
|     """Plug interface to a given port""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.PlugInterface') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(PlugInterface, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'iface_id', metavar='iface-id', | ||||
|             help='_(ID of the interface to plug)', ) | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|  | ||||
|         data = {'attachment': {'id': '%s' % parsed_args.iface_id, }, } | ||||
|         quantum_client.attach_resource(parsed_args.net_id, | ||||
|                                        parsed_args.port_id, | ||||
|                                        data) | ||||
|         print >>self.app.stdout, (_('Plugged interface %(interfaceid)s' | ||||
|                                     ' into Logical Port %(portid)s') | ||||
|                                   % {'interfaceid': parsed_args.iface_id, | ||||
|                                      'portid': parsed_args.port_id, }) | ||||
|  | ||||
|         return | ||||
|  | ||||
|  | ||||
| class UnPlugInterface(QuantumInterfaceCommand): | ||||
|     """Unplug interface from a given port""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.UnPlugInterface') | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|  | ||||
|         quantum_client.detach_resource(parsed_args.net_id, parsed_args.port_id) | ||||
|         print >>self.app.stdout, ( | ||||
|             _('Unplugged interface on Logical Port %(portid)s') | ||||
|             % {'portid': parsed_args.port_id, }) | ||||
|  | ||||
|         return | ||||
|  | ||||
|  | ||||
| class ShowInterface(QuantumInterfaceCommand, show.ShowOne): | ||||
|     """Show interface on a given port""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ShowInterface') | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|  | ||||
|         iface = quantum_client.show_port_attachment( | ||||
|             parsed_args.net_id, | ||||
|             parsed_args.port_id)['attachment'] | ||||
|  | ||||
|         if iface: | ||||
|             if 'id' not in iface: | ||||
|                 iface['id'] = '<none>' | ||||
|         else: | ||||
|             iface = {'': ''} | ||||
|         return zip(*sorted(iface.iteritems())) | ||||
							
								
								
									
										268
									
								
								quantumclient/quantum/v1_1/network.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								quantumclient/quantum/v1_1/network.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
| import itertools | ||||
|  | ||||
| from cliff import lister | ||||
| from cliff import show | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common import utils | ||||
| from quantumclient import net_filters_v11_opt | ||||
| from quantumclient.quantum.v1_1 import QuantumCommand | ||||
|  | ||||
|  | ||||
| class ListNetwork(QuantumCommand, lister.Lister): | ||||
|     """List networks that belong to a given tenant""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ListNetwork') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(ListNetwork, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--show-details', | ||||
|             help='show detailed info', | ||||
|             action='store_true', | ||||
|             default=False, ) | ||||
|         for net_filter in net_filters_v11_opt: | ||||
|             option_key = net_filter.keys()[0] | ||||
|             option_defs = net_filter.get(option_key) | ||||
|             parser.add_argument(option_key, **option_defs) | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         search_opts = { | ||||
|             'tenant': parsed_args.tenant_id, } | ||||
|         for net_filter in net_filters_v11_opt: | ||||
|             option_key = net_filter.keys()[0] | ||||
|             arg = option_key[2:] | ||||
|             arg = arg.replace('-', '_') | ||||
|             arg_value = getattr(parsed_args, arg, None) | ||||
|             if arg_value is not None: | ||||
|                 search_opts.update({option_key[2:]: arg_value, }) | ||||
|  | ||||
|         self.log.debug('search options: %s', search_opts) | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         columns = ('ID', ) | ||||
|         data = None | ||||
|         if parsed_args.show_details: | ||||
|             data = quantum_client.list_networks_details(**search_opts) | ||||
|             # dict: {u'networks': [{u'op-status': u'UP', | ||||
|             #        u'id': u'7a068b68-c736-42ab-9e43-c9d83c57627e', | ||||
|             #        u'name': u'private'}]} | ||||
|             columns = ('ID', 'op-status', 'name', ) | ||||
|         else: | ||||
|             data = quantum_client.list_networks(**search_opts) | ||||
|             # {u'networks': [{u'id': | ||||
|             #  u'7a068b68-c736-42ab-9e43-c9d83c57627e'}]} | ||||
|         networks = [] | ||||
|         if 'networks' in data: | ||||
|             networks = data['networks'] | ||||
|  | ||||
|         return (columns, | ||||
|                 (utils.get_item_properties( | ||||
|                     s, columns, formatters={}, ) for s in networks), ) | ||||
|  | ||||
|  | ||||
| def _format_attachment(port): | ||||
|     # attachment {u'id': u'gw-7a068b68-c7'} | ||||
|     try: | ||||
|         return ('attachment' in port and port['attachment'] and | ||||
|                 'id' in port['attachment'] and | ||||
|                 port['attachment']['id'] or '') | ||||
|     except Exception: | ||||
|         return '' | ||||
|  | ||||
|  | ||||
| class ShowNetwork(QuantumCommand, show.ShowOne): | ||||
|     """Show information of a given network""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ShowNetwork') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(ShowNetwork, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'net_id', metavar='net-id', | ||||
|             help='ID of network to display') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--show-details', | ||||
|             help='show detailed info of networks', | ||||
|             action='store_true', | ||||
|             default=False, ) | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         data = None | ||||
|         if parsed_args.show_details: | ||||
|             data = quantum_client.show_network_details(parsed_args.net_id) | ||||
|         else: | ||||
|             data = quantum_client.show_network(parsed_args.net_id) | ||||
|         # {u'network': {u'op-status': u'UP', 'xmlns': | ||||
|         #               u'http://openstack.org/quantum/api/v1.1', u'id': | ||||
|         # u'7a068b68-c736-42ab-9e43-c9d83c57627e', u'name': u'private'}} | ||||
|         network = {} | ||||
|         ports = None | ||||
|         network = 'network' in data and data['network'] or None | ||||
|         if network: | ||||
|             ports = network.pop('ports', None) | ||||
|             column_names, data = zip(*sorted(network.iteritems())) | ||||
|             if not parsed_args.columns: | ||||
|                 columns_to_include = column_names | ||||
|             else: | ||||
|                 columns_to_include = [c for c in column_names | ||||
|                                       if c in parsed_args.columns] | ||||
|                 # Set up argument to compress() | ||||
|                 selector = [(c in columns_to_include) | ||||
|                             for c in column_names] | ||||
|                 data = list(itertools.compress(data, selector)) | ||||
|             formatter = self.formatters[parsed_args.formatter] | ||||
|             formatter.emit_one(columns_to_include, data, | ||||
|                                self.app.stdout, parsed_args) | ||||
|         if ports: | ||||
|             print >>self.app.stdout, _('Network Ports:') | ||||
|             columns = ('op-status', 'state', 'id', 'attachment', ) | ||||
|             column_names, data = (columns, (utils.get_item_properties( | ||||
|                 s, columns, formatters={'attachment': _format_attachment}, ) | ||||
|                 for s in ports), ) | ||||
|             if not parsed_args.columns: | ||||
|                 columns_to_include = column_names | ||||
|                 data_gen = data | ||||
|             else: | ||||
|                 columns_to_include = [c for c in column_names | ||||
|                                       if c in parsed_args.columns] | ||||
|                 if not columns_to_include: | ||||
|                     raise ValueError( | ||||
|                         'No recognized column names in %s' % | ||||
|                         str(parsed_args.columns)) | ||||
|                 # Set up argument to compress() | ||||
|                 selector = [(c in columns_to_include) | ||||
|                             for c in column_names] | ||||
|                 # Generator expression to only return the parts of a row | ||||
|                 # of data that the user has expressed interest in | ||||
|                 # seeing. We have to convert the compress() output to a | ||||
|                 # list so the table formatter can ask for its length. | ||||
|                 data_gen = (list(itertools.compress(row, selector)) | ||||
|                             for row in data) | ||||
|             formatter = self.formatters[parsed_args.formatter] | ||||
|             formatter.emit_list(columns_to_include, | ||||
|                                 data_gen, self.app.stdout, parsed_args) | ||||
|  | ||||
|         return ('', []) | ||||
|  | ||||
|  | ||||
| class CreateNetwork(QuantumCommand, show.ShowOne): | ||||
|     """Create a network for a given tenant""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.CreateNetwork') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(CreateNetwork, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'net_name', metavar='net-name', | ||||
|             help='Name of network to create') | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         body = {'network': {'name': parsed_args.net_name, }, } | ||||
|         network = quantum_client.create_network(body) | ||||
|         # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} | ||||
|         info = 'network' in network and network['network'] or None | ||||
|         if info: | ||||
|             print >>self.app.stdout, _('Created a new Virtual Network:') | ||||
|         else: | ||||
|             info = {'': ''} | ||||
|         return zip(*sorted(info.iteritems())) | ||||
|  | ||||
|  | ||||
| class DeleteNetwork(QuantumCommand): | ||||
|     """Delete a given network""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.DeleteNetwork') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(DeleteNetwork, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'net_id', metavar='net-id', | ||||
|             help='ID of network to delete') | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         quantum_client.delete_network(parsed_args.net_id) | ||||
|         print >>self.app.stdout, (_('Deleted Network: %(networkid)s') | ||||
|                                   % {'networkid': parsed_args.net_id}) | ||||
|         return | ||||
|  | ||||
|  | ||||
| class UpdateNetwork(QuantumCommand): | ||||
|     """Update network's information""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.UpdateNetwork') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(UpdateNetwork, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'net_id', metavar='net-id', | ||||
|             help='ID of network to update') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             'newvalues', metavar='field=newvalue[,field2=newvalue2]', | ||||
|             help='new values for the network') | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         field_values = parsed_args.newvalues | ||||
|         data = {'network': {}} | ||||
|         for kv in field_values.split(","): | ||||
|             try: | ||||
|                 k, v = kv.split("=") | ||||
|                 data['network'][k] = v | ||||
|             except ValueError: | ||||
|                 raise exceptions.CommandError( | ||||
|                     "malformed new values (field=newvalue): %s" % kv) | ||||
|  | ||||
|         data['network']['id'] = parsed_args.net_id | ||||
|         quantum_client.update_network(parsed_args.net_id, data) | ||||
|         print >>self.app.stdout, ( | ||||
|             _('Updated Network: %(networkid)s') % | ||||
|             {'networkid': parsed_args.net_id}) | ||||
|         return | ||||
							
								
								
									
										222
									
								
								quantumclient/quantum/v1_1/port.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								quantumclient/quantum/v1_1/port.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,222 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from cliff import lister | ||||
| from cliff import show | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common import utils | ||||
| from quantumclient import port_filters_v11_opt | ||||
| from quantumclient.quantum.v1_1 import QuantumPortCommand | ||||
|  | ||||
|  | ||||
| class ListPort(QuantumPortCommand, lister.Lister): | ||||
|     """List ports that belong to a given tenant's network""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ListPort') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(ListPort, self).get_parser(prog_name) | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--show-details', | ||||
|             help='show detailed info of networks', | ||||
|             action='store_true', | ||||
|             default=False, ) | ||||
|         for item in port_filters_v11_opt: | ||||
|             option_key = item.keys()[0] | ||||
|             option_defs = item.get(option_key) | ||||
|             parser.add_argument(option_key, **option_defs) | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         search_opts = { | ||||
|             'tenant': parsed_args.tenant_id, } | ||||
|         for item in port_filters_v11_opt: | ||||
|             option_key = item.keys()[0] | ||||
|             arg = option_key[2:] | ||||
|             arg = arg.replace('-', '_') | ||||
|             arg_value = getattr(parsed_args, arg, None) | ||||
|             if arg_value is not None: | ||||
|                 search_opts.update({option_key[2:]: arg_value, }) | ||||
|  | ||||
|         self.log.debug('search options: %s', search_opts) | ||||
|  | ||||
|         columns = ('ID', ) | ||||
|         data = None | ||||
|         if parsed_args.show_details: | ||||
|             data = quantum_client.list_ports_details( | ||||
|                 parsed_args.net_id, **search_opts) | ||||
|             # dict:dict: {u'ports': [{ | ||||
|             #          u'op-status': u'DOWN', | ||||
|             #          u'state': u'ACTIVE', | ||||
|             #          u'id': u'479ba2b7-042f-44b9-aefb-b1550e114454'}, ]} | ||||
|             columns = ('ID', 'op-status', 'state') | ||||
|         else: | ||||
|             data = quantum_client.list_ports(parsed_args.net_id, **search_opts) | ||||
|             # {u'ports': [{u'id': u'7a068b68-c736-42ab-9e43-c9d83c57627e'}]} | ||||
|         ports = [] | ||||
|         if 'ports' in data: | ||||
|             ports = data['ports'] | ||||
|  | ||||
|         return (columns, | ||||
|                 (utils.get_item_properties( | ||||
|                     s, columns, formatters={}, ) for s in ports), ) | ||||
|  | ||||
|  | ||||
| class ShowPort(QuantumPortCommand, show.ShowOne): | ||||
|     """Show information of a given port""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ShowPort') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(ShowPort, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'port_id', metavar='port-id', | ||||
|             help='ID of the port to show', ) | ||||
|         parser.add_argument( | ||||
|             '--show-details', | ||||
|             help='show detailed info', | ||||
|             action='store_true', | ||||
|             default=False, ) | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         data = None | ||||
|         if parsed_args.show_details: | ||||
|             data = quantum_client.show_port_details( | ||||
|                 parsed_args.net_id, parsed_args.port_id) | ||||
|             # {u'port': {u'op-status': u'DOWN', u'state': u'ACTIVE', | ||||
|             #            u'id': u'479ba2b7-042f-44b9-aefb- | ||||
|             #  b1550e114454', u'attachment': {u'id': u'gw-7a068b68-c7'}}} | ||||
|         else: | ||||
|             data = quantum_client.show_port( | ||||
|                 parsed_args.net_id, parsed_args.port_id) | ||||
|             # {u'port': {u'op-status': u'DOWN', u'state': u'ACTIVE', | ||||
|             # u'id': u'479ba2b7-042f-44b9-aefb-b1550e114454'}} | ||||
|  | ||||
|         port = 'port' in data and data['port'] or None | ||||
|         if port: | ||||
|             attachment = 'attachment' in port and port['attachment'] or None | ||||
|             if attachment: | ||||
|                 interface = attachment['id'] | ||||
|                 port.update({'attachment': interface}) | ||||
|             return zip(*sorted(port.iteritems())) | ||||
|         return ('', []) | ||||
|  | ||||
|  | ||||
| class CreatePort(QuantumPortCommand, show.ShowOne): | ||||
|     """Create port for a given network""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.CreatePort') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(CreatePort, self).get_parser(prog_name) | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         data = quantum_client.create_port(parsed_args.net_id) | ||||
|         # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} | ||||
|         info = 'port' in data and data['port'] or None | ||||
|         if info: | ||||
|             print >>self.app.stdout, _('Created a new Logical Port:') | ||||
|         else: | ||||
|             info = {'': ''} | ||||
|         return zip(*sorted(info.iteritems())) | ||||
|  | ||||
|  | ||||
| class DeletePort(QuantumPortCommand): | ||||
|     """Delete a given port""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.DeletePort') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(DeletePort, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'port_id', metavar='port-id', | ||||
|             help='ID of the port to delete', ) | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         quantum_client.delete_port(parsed_args.net_id, parsed_args.port_id) | ||||
|         print >>self.app.stdout, (_('Deleted Logical Port: %(portid)s') % | ||||
|                                   {'portid': parsed_args.port_id}) | ||||
|         return | ||||
|  | ||||
|  | ||||
| class UpdatePort(QuantumPortCommand): | ||||
|     """Update information of a given port""" | ||||
|  | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.UpdatePort') | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(UpdatePort, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'port_id', metavar='port-id', | ||||
|             help='ID of the port to update', ) | ||||
|  | ||||
|         parser.add_argument( | ||||
|             'newvalues', metavar='field=newvalue[,field2=newvalue2]', | ||||
|             help='new values for the Port') | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.app.client_manager.quantum | ||||
|         quantum_client.tenant = parsed_args.tenant_id | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         field_values = parsed_args.newvalues | ||||
|         data = {'port': {}} | ||||
|         for kv in field_values.split(","): | ||||
|             try: | ||||
|                 k, v = kv.split("=") | ||||
|                 data['port'][k] = v | ||||
|             except ValueError: | ||||
|                 raise exceptions.CommandError( | ||||
|                     "malformed new values (field=newvalue): %s" % kv) | ||||
|         data['network_id'] = parsed_args.net_id | ||||
|         data['port']['id'] = parsed_args.port_id | ||||
|  | ||||
|         quantum_client.update_port( | ||||
|             parsed_args.net_id, parsed_args.port_id, data) | ||||
|         print >>self.app.stdout, (_('Updated Logical Port: %(portid)s') % | ||||
|                                   {'portid': parsed_args.port_id}) | ||||
|         return | ||||
							
								
								
									
										339
									
								
								quantumclient/quantum/v2_0/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								quantumclient/quantum/v2_0/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,339 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import argparse | ||||
| import logging | ||||
|  | ||||
| from cliff import lister | ||||
| from cliff import show | ||||
|  | ||||
| from quantumclient.common import command | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common import utils | ||||
|  | ||||
|  | ||||
| def add_show_list_common_argument(parser): | ||||
|     parser.add_argument( | ||||
|         '-D', '--show-details', | ||||
|         help='show detailed info', | ||||
|         action='store_true', | ||||
|         default=False, ) | ||||
|     parser.add_argument( | ||||
|         '-F', '--fields', | ||||
|         help='specify the field(s) to be returned by server,' | ||||
|         ' can be repeated', | ||||
|         action='append', | ||||
|         default=[], ) | ||||
|  | ||||
|  | ||||
| def add_extra_argument(parser, name, _help): | ||||
|     parser.add_argument( | ||||
|         name, | ||||
|         nargs=argparse.REMAINDER, | ||||
|         help=_help + ': --key1 [type=int|bool|...] value ' | ||||
|                      '[--key2 [type=int|bool|...] value ...]') | ||||
|  | ||||
|  | ||||
| def parse_args_to_dict(values_specs): | ||||
|     '''It is used to analyze the extra command options to command. | ||||
|  | ||||
|     Besides known options and arguments, our commands also support user to | ||||
|     put more options to the end of command line. For example, | ||||
|     list_nets -- --tag x y --key1 value1, where '-- --tag x y --key1 value1' | ||||
|     is extra options to our list_nets. This feature can support V2.0 API's | ||||
|     fields selection and filters. For example, to list networks which has name | ||||
|     'test4', we can have list_nets -- --name=test4. | ||||
|  | ||||
|     value spec is: --key type=int|bool|... value. Type is one of Python | ||||
|     built-in types. By default, type is string. The key without value is | ||||
|     a bool option. Key with two values will be a list option. | ||||
|  | ||||
|     ''' | ||||
|     # -- is a pseudo argument | ||||
|     if values_specs and values_specs[0] == '--': | ||||
|         del values_specs[0] | ||||
|     _options = {} | ||||
|     current_arg = None | ||||
|     _values_specs = [] | ||||
|     _value_number = 0 | ||||
|     current_item = None | ||||
|     for _item in values_specs: | ||||
|         if _item.startswith('--'): | ||||
|             if current_arg is not None: | ||||
|                 if _value_number > 1: | ||||
|                     current_arg.update({'nargs': '+'}) | ||||
|                 elif _value_number == 0: | ||||
|                     current_arg.update({'action': 'store_true'}) | ||||
|             _temp = _item | ||||
|             if "=" in _item: | ||||
|                 _item = _item.split('=')[0] | ||||
|             if _item in _options: | ||||
|                 raise exceptions.CommandError( | ||||
|                     "duplicated options %s" % ' '.join(values_specs)) | ||||
|             else: | ||||
|                 _options.update({_item: {}}) | ||||
|             current_arg = _options[_item] | ||||
|             _item = _temp | ||||
|         elif _item.startswith('type='): | ||||
|             if current_arg is not None: | ||||
|                 _type_str = _item.split('=', 2)[1] | ||||
|                 current_arg.update({'type': eval(_type_str)}) | ||||
|                 if _type_str == 'bool': | ||||
|                     current_arg.update({'type': utils.__str2bool}) | ||||
|                 continue | ||||
|             else: | ||||
|                 raise exceptions.CommandError( | ||||
|                     "invalid values_specs %s" % ' '.join(values_specs)) | ||||
|  | ||||
|         if not _item.startswith('--'): | ||||
|             if not current_item or '=' in current_item: | ||||
|                 raise exceptions.CommandError( | ||||
|                     "Invalid values_specs %s" % ' '.join(values_specs)) | ||||
|             _value_number += 1 | ||||
|         elif _item.startswith('--'): | ||||
|             current_item = _item | ||||
|             if '=' in current_item: | ||||
|                 _value_number = 1 | ||||
|             else: | ||||
|                 _value_number = 0 | ||||
|         _values_specs.append(_item) | ||||
|     if current_arg is not None: | ||||
|         if _value_number > 1: | ||||
|             current_arg.update({'nargs': '+'}) | ||||
|         elif _value_number == 0: | ||||
|             current_arg.update({'action': 'store_true'}) | ||||
|     _parser = argparse.ArgumentParser(add_help=False) | ||||
|     for opt, optspec in _options.iteritems(): | ||||
|         _parser.add_argument(opt, **optspec) | ||||
|     _args = _parser.parse_args(_values_specs) | ||||
|     result_dict = {} | ||||
|     for opt in _options.iterkeys(): | ||||
|         _opt = opt.split('--', 2)[1] | ||||
|         _value = getattr(_args, _opt.replace('-', '_')) | ||||
|         if _value is not None: | ||||
|             result_dict.update({_opt: _value}) | ||||
|     return result_dict | ||||
|  | ||||
|  | ||||
| class QuantumCommand(command.OpenStackCommand): | ||||
|     api = 'network' | ||||
|     log = logging.getLogger(__name__ + '.QuantumCommand') | ||||
|  | ||||
|     def get_client(self): | ||||
|         return self.app.client_manager.quantum | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(QuantumCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--request-format', | ||||
|             help=_('the xml or json request format'), | ||||
|             default='json', | ||||
|             choices=['json', 'xml', ], ) | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|  | ||||
| class CreateCommand(QuantumCommand, show.ShowOne): | ||||
|     """Create a resource for a given tenant | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     api = 'network' | ||||
|     resource = None | ||||
|     log = None | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(CreateCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--tenant-id', metavar='tenant-id', | ||||
|             help=_('the owner tenant ID'), ) | ||||
|         self.add_known_arguments(parser) | ||||
|         add_extra_argument(parser, 'value_specs', | ||||
|                            'new values for the %s' % self.resource) | ||||
|         return parser | ||||
|  | ||||
|     def add_known_arguments(self, parser): | ||||
|         pass | ||||
|  | ||||
|     def args2body(self, parsed_args): | ||||
|         return {} | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.get_client() | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         body = self.args2body(parsed_args) | ||||
|         _extra_values = parse_args_to_dict(parsed_args.value_specs) | ||||
|         body[self.resource].update(_extra_values) | ||||
|         obj_creator = getattr(quantum_client, | ||||
|                               "create_%s" % self.resource) | ||||
|         data = obj_creator(body) | ||||
|         # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} | ||||
|         info = self.resource in data and data[self.resource] or None | ||||
|         if info: | ||||
|             print >>self.app.stdout, _('Created a new %s:' % self.resource) | ||||
|         else: | ||||
|             info = {'': ''} | ||||
|         return zip(*sorted(info.iteritems())) | ||||
|  | ||||
|  | ||||
| class UpdateCommand(QuantumCommand): | ||||
|     """Update resource's information | ||||
|     """ | ||||
|  | ||||
|     api = 'network' | ||||
|     resource = None | ||||
|     log = None | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(UpdateCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'id', metavar='%s-id' % self.resource, | ||||
|             help='ID of %s to update' % self.resource) | ||||
|         add_extra_argument(parser, 'value_specs', | ||||
|                            'new values for the %s' % self.resource) | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.get_client() | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         value_specs = parsed_args.value_specs | ||||
|         if not value_specs: | ||||
|             raise exceptions.CommandError( | ||||
|                 "Must specify new values to update %s" % self.resource) | ||||
|         data = {self.resource: parse_args_to_dict(value_specs)} | ||||
|         obj_updator = getattr(quantum_client, | ||||
|                               "update_%s" % self.resource) | ||||
|         obj_updator(parsed_args.id, data) | ||||
|         print >>self.app.stdout, ( | ||||
|             _('Updated %(resource)s: %(id)s') % | ||||
|             {'id': parsed_args.id, 'resource': self.resource}) | ||||
|         return | ||||
|  | ||||
|  | ||||
| class DeleteCommand(QuantumCommand): | ||||
|     """Delete a given resource | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     api = 'network' | ||||
|     resource = None | ||||
|     log = None | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(DeleteCommand, self).get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'id', metavar='%s-id' % self.resource, | ||||
|             help='ID of %s to delete' % self.resource) | ||||
|         return parser | ||||
|  | ||||
|     def run(self, parsed_args): | ||||
|         self.log.debug('run(%s)' % parsed_args) | ||||
|         quantum_client = self.get_client() | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         obj_deleter = getattr(quantum_client, | ||||
|                               "delete_%s" % self.resource) | ||||
|         obj_deleter(parsed_args.id) | ||||
|         print >>self.app.stdout, (_('Deleted %(resource)s: %(id)s') | ||||
|                                   % {'id': parsed_args.id, | ||||
|                                      'resource': self.resource}) | ||||
|         return | ||||
|  | ||||
|  | ||||
| class ListCommand(QuantumCommand, lister.Lister): | ||||
|     """List resourcs that belong to a given tenant | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     api = 'network' | ||||
|     resource = None | ||||
|     log = None | ||||
|     _formatters = None | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(ListCommand, self).get_parser(prog_name) | ||||
|         add_show_list_common_argument(parser) | ||||
|         add_extra_argument(parser, 'filter_specs', 'filters options') | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.get_client() | ||||
|         search_opts = parse_args_to_dict(parsed_args.filter_specs) | ||||
|  | ||||
|         self.log.debug('search options: %s', search_opts) | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         fields = parsed_args.fields | ||||
|         extra_fields = search_opts.get('fields', []) | ||||
|         if extra_fields: | ||||
|             if isinstance(extra_fields, list): | ||||
|                 fields.extend(extra_fields) | ||||
|             else: | ||||
|                 fields.append(extra_fields) | ||||
|         if fields: | ||||
|             search_opts.update({'fields': fields}) | ||||
|         if parsed_args.show_details: | ||||
|             search_opts.update({'verbose': 'True'}) | ||||
|         obj_lister = getattr(quantum_client, | ||||
|                              "list_%ss" % self.resource) | ||||
|  | ||||
|         data = obj_lister(**search_opts) | ||||
|         info = [] | ||||
|         collection = self.resource + "s" | ||||
|         if collection in data: | ||||
|             info = data[collection] | ||||
|         _columns = len(info) > 0 and sorted(info[0].keys()) or [] | ||||
|         return (_columns, (utils.get_item_properties( | ||||
|             s, _columns, formatters=self._formatters, ) | ||||
|             for s in info), ) | ||||
|  | ||||
|  | ||||
| class ShowCommand(QuantumCommand, show.ShowOne): | ||||
|     """Show information of a given resource | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     api = 'network' | ||||
|     resource = None | ||||
|     log = None | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super(ShowCommand, self).get_parser(prog_name) | ||||
|         add_show_list_common_argument(parser) | ||||
|         parser.add_argument( | ||||
|             'id', metavar='%s-id' % self.resource, | ||||
|             help='ID of %s to look up' % self.resource) | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|     def get_data(self, parsed_args): | ||||
|         self.log.debug('get_data(%s)' % parsed_args) | ||||
|         quantum_client = self.get_client() | ||||
|         quantum_client.format = parsed_args.request_format | ||||
|         params = {} | ||||
|         if parsed_args.show_details: | ||||
|             params = {'verbose': 'True'} | ||||
|         if parsed_args.fields: | ||||
|             params = {'fields': parsed_args.fields} | ||||
|         obj_showor = getattr(quantum_client, | ||||
|                              "show_%s" % self.resource) | ||||
|         data = obj_showor(parsed_args.id, **params) | ||||
|         if self.resource in data: | ||||
|             return zip(*sorted(data[self.resource].iteritems())) | ||||
|         else: | ||||
|             return None | ||||
							
								
								
									
										99
									
								
								quantumclient/quantum/v2_0/network.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								quantumclient/quantum/v2_0/network.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from quantumclient.quantum.v2_0 import CreateCommand | ||||
| from quantumclient.quantum.v2_0 import DeleteCommand | ||||
| from quantumclient.quantum.v2_0 import ListCommand | ||||
| from quantumclient.quantum.v2_0 import UpdateCommand | ||||
| from quantumclient.quantum.v2_0 import ShowCommand | ||||
|  | ||||
|  | ||||
| def _format_subnets(network): | ||||
|     try: | ||||
|         return '\n'.join(network['subnets']) | ||||
|     except Exception: | ||||
|         return '' | ||||
|  | ||||
|  | ||||
| class ListNetwork(ListCommand): | ||||
|     """List networks that belong to a given tenant | ||||
|  | ||||
|     Sample: list_nets -D -- --name=test4 --tag a b | ||||
|     """ | ||||
|  | ||||
|     resource = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ListNetwork') | ||||
|     _formatters = {'subnets': _format_subnets, } | ||||
|  | ||||
|  | ||||
| class ShowNetwork(ShowCommand): | ||||
|     """Show information of a given network | ||||
|  | ||||
|     Sample: show_net -D <network_id> | ||||
|     """ | ||||
|  | ||||
|     resource = 'network' | ||||
|     log = logging.getLogger(__name__ + '.ShowNetwork') | ||||
|  | ||||
|  | ||||
| class CreateNetwork(CreateCommand): | ||||
|     """Create a network for a given tenant | ||||
|  | ||||
|     Sample create_net --tenant-id xxx --admin-state-down <net_name> --tag x y | ||||
|     """ | ||||
|  | ||||
|     resource = 'network' | ||||
|     log = logging.getLogger(__name__ + '.CreateNetwork') | ||||
|  | ||||
|     def add_known_arguments(self, parser): | ||||
|         parser.add_argument( | ||||
|             '--admin-state-down', | ||||
|             default=True, action='store_false', | ||||
|             help='Set Admin State Up to false') | ||||
|         parser.add_argument( | ||||
|             'name', metavar='name', | ||||
|             help='Name of network to create') | ||||
|  | ||||
|     def args2body(self, parsed_args): | ||||
|         body = {'network': { | ||||
|             'name': parsed_args.name, | ||||
|             'admin_state_up': parsed_args.admin_state_down, }, } | ||||
|         if parsed_args.tenant_id: | ||||
|             body['network'].update({'tenant_id': parsed_args.tenant_id}) | ||||
|         return body | ||||
|  | ||||
|  | ||||
| class DeleteNetwork(DeleteCommand): | ||||
|     """Delete a given network | ||||
|  | ||||
|     Sample: delete_net <network_id> | ||||
|     """ | ||||
|  | ||||
|     log = logging.getLogger(__name__ + '.DeleteNetwork') | ||||
|     resource = 'network' | ||||
|  | ||||
|  | ||||
| class UpdateNetwork(UpdateCommand): | ||||
|     """Update network's information | ||||
|  | ||||
|     Sample: update_net <network_id> --name=test --admin_state_up type=bool True | ||||
|     """ | ||||
|  | ||||
|     log = logging.getLogger(__name__ + '.UpdateNetwork') | ||||
|     resource = 'network' | ||||
							
								
								
									
										110
									
								
								quantumclient/quantum/v2_0/port.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								quantumclient/quantum/v2_0/port.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from quantumclient.quantum.v2_0 import CreateCommand | ||||
| from quantumclient.quantum.v2_0 import DeleteCommand | ||||
| from quantumclient.quantum.v2_0 import ListCommand | ||||
| from quantumclient.quantum.v2_0 import ShowCommand | ||||
| from quantumclient.quantum.v2_0 import UpdateCommand | ||||
|  | ||||
|  | ||||
| def _format_fixed_ips(port): | ||||
|     try: | ||||
|         return '\n'.join(port['fixed_ips']) | ||||
|     except Exception: | ||||
|         return '' | ||||
|  | ||||
|  | ||||
| class ListPort(ListCommand): | ||||
|     """List networks that belong to a given tenant | ||||
|  | ||||
|     Sample: list_ports -D -- --name=test4 --tag a b | ||||
|     """ | ||||
|  | ||||
|     resource = 'port' | ||||
|     log = logging.getLogger(__name__ + '.ListPort') | ||||
|     _formatters = {'fixed_ips': _format_fixed_ips, } | ||||
|  | ||||
|  | ||||
| class ShowPort(ShowCommand): | ||||
|     """Show information of a given port | ||||
|  | ||||
|     Sample: show_port -D <port_id> | ||||
|     """ | ||||
|  | ||||
|     resource = 'port' | ||||
|     log = logging.getLogger(__name__ + '.ShowPort') | ||||
|  | ||||
|  | ||||
| class CreatePort(CreateCommand): | ||||
|     """Create a port for a given tenant | ||||
|  | ||||
|     Sample create_port --tenant-id xxx --admin-state-down \ | ||||
|       --mac_address mac --device_id deviceid <network_id> | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     resource = 'port' | ||||
|     log = logging.getLogger(__name__ + '.CreatePort') | ||||
|  | ||||
|     def add_known_arguments(self, parser): | ||||
|         parser.add_argument( | ||||
|             '--admin-state-down', | ||||
|             default=True, action='store_false', | ||||
|             help='set admin state up to false') | ||||
|         parser.add_argument( | ||||
|             '--mac-address', | ||||
|             help='mac address of port') | ||||
|         parser.add_argument( | ||||
|             '--device-id', | ||||
|             help='device id of this port') | ||||
|         parser.add_argument( | ||||
|             'network_id', | ||||
|             help='Network id of this port belongs to') | ||||
|  | ||||
|     def args2body(self, parsed_args): | ||||
|         body = {'port': {'admin_state_up': parsed_args.admin_state_down, | ||||
|                          'network_id': parsed_args.network_id, }, } | ||||
|         if parsed_args.mac_address: | ||||
|             body['port'].update({'mac_address': parsed_args.mac_address}) | ||||
|         if parsed_args.device_id: | ||||
|             body['port'].update({'device_id': parsed_args.device_id}) | ||||
|         if parsed_args.tenant_id: | ||||
|             body['port'].update({'tenant_id': parsed_args.tenant_id}) | ||||
|         return body | ||||
|  | ||||
|  | ||||
| class DeletePort(DeleteCommand): | ||||
|     """Delete a given port | ||||
|  | ||||
|     Sample: delete_port <port_id> | ||||
|     """ | ||||
|  | ||||
|     resource = 'port' | ||||
|     log = logging.getLogger(__name__ + '.DeletePort') | ||||
|  | ||||
|  | ||||
| class UpdatePort(UpdateCommand): | ||||
|     """Update port's information | ||||
|  | ||||
|     Sample: update_port <port_id> --name=test --admin_state_up type=bool True | ||||
|     """ | ||||
|  | ||||
|     resource = 'port' | ||||
|     log = logging.getLogger(__name__ + '.UpdatePort') | ||||
							
								
								
									
										101
									
								
								quantumclient/quantum/v2_0/subnet.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								quantumclient/quantum/v2_0/subnet.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from quantumclient.quantum.v2_0 import CreateCommand | ||||
| from quantumclient.quantum.v2_0 import DeleteCommand | ||||
| from quantumclient.quantum.v2_0 import ListCommand | ||||
| from quantumclient.quantum.v2_0 import ShowCommand | ||||
| from quantumclient.quantum.v2_0 import UpdateCommand | ||||
|  | ||||
|  | ||||
| class ListSubnet(ListCommand): | ||||
|     """List networks that belong to a given tenant | ||||
|  | ||||
|     Sample: list_subnets -D -- --name=test4 --tag a b | ||||
|     """ | ||||
|  | ||||
|     resource = 'subnet' | ||||
|     log = logging.getLogger(__name__ + '.ListSubnet') | ||||
|     _formatters = {} | ||||
|  | ||||
|  | ||||
| class ShowSubnet(ShowCommand): | ||||
|     """Show information of a given subnet | ||||
|  | ||||
|     Sample: show_subnet -D <subnet_id> | ||||
|     """ | ||||
|  | ||||
|     resource = 'subnet' | ||||
|     log = logging.getLogger(__name__ + '.ShowSubnet') | ||||
|  | ||||
|  | ||||
| class CreateSubnet(CreateCommand): | ||||
|     """Create a subnet for a given tenant | ||||
|  | ||||
|     Sample create_subnet --tenant-id xxx --ip-version 4\ | ||||
|     <network_id> <cidr> --tag x y --otherfield value | ||||
|     """ | ||||
|  | ||||
|     resource = 'subnet' | ||||
|     log = logging.getLogger(__name__ + '.CreateSubnet') | ||||
|  | ||||
|     def add_known_arguments(self, parser): | ||||
|         parser.add_argument('--ip-version', type=int, | ||||
|                             default=4, choices=[4, 6], | ||||
|                             help='IP version with default 4') | ||||
|         parser.add_argument( | ||||
|             '--gateway', metavar='gateway', | ||||
|             help='gateway ip of this subnet') | ||||
|         parser.add_argument( | ||||
|             'network_id', | ||||
|             help='Network id of this subnet belongs to') | ||||
|         parser.add_argument( | ||||
|             'cidr', metavar='cidr', | ||||
|             help='cidr of subnet to create') | ||||
|  | ||||
|     def args2body(self, parsed_args): | ||||
|         body = {'subnet': {'cidr': parsed_args.cidr, | ||||
|                            'network_id': parsed_args.network_id, | ||||
|                            'ip_version': parsed_args.ip_version, }, } | ||||
|         if parsed_args.gateway: | ||||
|             body['subnet'].update({'gateway_ip': parsed_args.gateway}) | ||||
|         if parsed_args.tenant_id: | ||||
|             body['subnet'].update({'tenant_id': parsed_args.tenant_id}) | ||||
|         return body | ||||
|  | ||||
|  | ||||
| class DeleteSubnet(DeleteCommand): | ||||
|     """Delete a given subnet | ||||
|  | ||||
|     Sample: delete_subnet <subnet-id> | ||||
|     """ | ||||
|  | ||||
|     resource = 'subnet' | ||||
|     log = logging.getLogger(__name__ + '.DeleteSubnet') | ||||
|  | ||||
|  | ||||
| class UpdateSubnet(UpdateCommand): | ||||
|     """Update subnet's information | ||||
|  | ||||
|     Sample: | ||||
|     update_subnet <subnet-id> --name=test --admin_state_up type=bool True | ||||
|     """ | ||||
|  | ||||
|     resource = 'subnet' | ||||
|     log = logging.getLogger(__name__ + '.UpdateSubnet') | ||||
							
								
								
									
										383
									
								
								quantumclient/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								quantumclient/shell.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,383 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| """ | ||||
| Command-line interface to the Quantum APIs | ||||
| """ | ||||
| import argparse | ||||
| import gettext | ||||
| import itertools | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| from cliff.app import App | ||||
| from cliff.commandmanager import CommandManager | ||||
|  | ||||
| from quantumclient.common import clientmanager | ||||
| from quantumclient.common import exceptions as exc | ||||
| from quantumclient.common import utils | ||||
|  | ||||
|  | ||||
| gettext.install('quantum', unicode=1) | ||||
| VERSION = '2.0' | ||||
|  | ||||
|  | ||||
| def env(*vars, **kwargs): | ||||
|     """Search for the first defined of possibly many env vars | ||||
|  | ||||
|     Returns the first environment variable defined in vars, or | ||||
|     returns the default defined in kwargs. | ||||
|  | ||||
|     """ | ||||
|     for v in vars: | ||||
|         value = os.environ.get(v, None) | ||||
|         if value: | ||||
|             return value | ||||
|     return kwargs.get('default', '') | ||||
|  | ||||
| COMMAND_V1 = { | ||||
|     'list_nets': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.network.ListNetwork'), | ||||
|     'show_net': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.network.ShowNetwork'), | ||||
|     'create_net': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.network.CreateNetwork'), | ||||
|     'delete_net': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.network.DeleteNetwork'), | ||||
|     'update_net': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.network.UpdateNetwork'), | ||||
|  | ||||
|     'list_ports': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.port.ListPort'), | ||||
|     'show_port': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.port.ShowPort'), | ||||
|     'create_port': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.port.CreatePort'), | ||||
|     'delete_port': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.port.DeletePort'), | ||||
|     'update_port': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.port.UpdatePort'), | ||||
|  | ||||
|     'plug_iface': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.interface.PlugInterface'), | ||||
|     'unplug_iface': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.interface.UnPlugInterface'), | ||||
|     'show_iface': utils.import_class( | ||||
|         'quantumclient.quantum.v1_1.interface.ShowInterface'), } | ||||
| COMMAND_V2 = { | ||||
|     'list_nets': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.network.ListNetwork'), | ||||
|     'show_net': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.network.ShowNetwork'), | ||||
|     'create_net': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.network.CreateNetwork'), | ||||
|     'delete_net': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.network.DeleteNetwork'), | ||||
|     'update_net': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.network.UpdateNetwork'), | ||||
|     'list_subnets': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.subnet.ListSubnet'), | ||||
|     'show_subnet': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.subnet.ShowSubnet'), | ||||
|     'create_subnet': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.subnet.CreateSubnet'), | ||||
|     'delete_subnet': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.subnet.DeleteSubnet'), | ||||
|     'update_subnet': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.subnet.UpdateSubnet'), | ||||
|     'list_ports': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.port.ListPort'), | ||||
|     'show_port': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.port.ShowPort'), | ||||
|     'create_port': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.port.CreatePort'), | ||||
|     'delete_port': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.port.DeletePort'), | ||||
|     'update_port': utils.import_class( | ||||
|         'quantumclient.quantum.v2_0.port.UpdatePort'), } | ||||
|  | ||||
| COMMANDS = {'1.0': COMMAND_V1, | ||||
|             '1.1': COMMAND_V1, | ||||
|             '2.0': COMMAND_V2, } | ||||
|  | ||||
|  | ||||
| class HelpAction(argparse.Action): | ||||
|     """Provide a custom action so the -h and --help options | ||||
|     to the main app will print a list of the commands. | ||||
|  | ||||
|     The commands are determined by checking the CommandManager | ||||
|     instance, passed in as the "default" value for the action. | ||||
|     """ | ||||
|     def __call__(self, parser, namespace, values, option_string=None): | ||||
|         app = self.default | ||||
|         parser.print_help(app.stdout) | ||||
|         app.stdout.write('\nCommands for API v%s:\n' % app.api_version) | ||||
|         command_manager = app.command_manager | ||||
|         for name, ep in sorted(command_manager): | ||||
|             factory = ep.load() | ||||
|             cmd = factory(self, None) | ||||
|             one_liner = cmd.get_description().split('\n')[0] | ||||
|             app.stdout.write('  %-13s  %s\n' % (name, one_liner)) | ||||
|         sys.exit(0) | ||||
|  | ||||
|  | ||||
| class QuantumShell(App): | ||||
|  | ||||
|     CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' | ||||
|  | ||||
|     log = logging.getLogger(__name__) | ||||
|  | ||||
|     def __init__(self, apiversion): | ||||
|         super(QuantumShell, self).__init__( | ||||
|             description=__doc__.strip(), | ||||
|             version=VERSION, | ||||
|             command_manager=CommandManager('quantum.cli'), ) | ||||
|         for k, v in COMMANDS[apiversion].items(): | ||||
|             self.command_manager.add_command(k, v) | ||||
|  | ||||
|         # This is instantiated in initialize_app() only when using | ||||
|         # password flow auth | ||||
|         self.auth_client = None | ||||
|         self.api_version = apiversion | ||||
|  | ||||
|     def build_option_parser(self, description, version): | ||||
|         """Return an argparse option parser for this application. | ||||
|  | ||||
|         Subclasses may override this method to extend | ||||
|         the parser with more global options. | ||||
|  | ||||
|         :param description: full description of the application | ||||
|         :paramtype description: str | ||||
|         :param version: version number for the application | ||||
|         :paramtype version: str | ||||
|         """ | ||||
|         parser = argparse.ArgumentParser( | ||||
|             description=description, | ||||
|             add_help=False, ) | ||||
|         parser.add_argument( | ||||
|             '--version', | ||||
|             action='version', | ||||
|             version='%(prog)s {0}'.format(version), ) | ||||
|         parser.add_argument( | ||||
|             '-v', '--verbose', | ||||
|             action='count', | ||||
|             dest='verbose_level', | ||||
|             default=self.DEFAULT_VERBOSE_LEVEL, | ||||
|             help='Increase verbosity of output. Can be repeated.', ) | ||||
|         parser.add_argument( | ||||
|             '-q', '--quiet', | ||||
|             action='store_const', | ||||
|             dest='verbose_level', | ||||
|             const=0, | ||||
|             help='suppress output except warnings and errors', ) | ||||
|         parser.add_argument( | ||||
|             '-H', '--Help', | ||||
|             action=HelpAction, | ||||
|             nargs=0, | ||||
|             default=self,  # tricky | ||||
|             help="show this help message and exit", ) | ||||
|         parser.add_argument( | ||||
|             '--debug', | ||||
|             default=False, | ||||
|             action='store_true', | ||||
|             help='show tracebacks on errors', ) | ||||
|         # Global arguments | ||||
|         parser.add_argument( | ||||
|             '--os-auth-url', metavar='<auth-url>', | ||||
|             default=env('OS_AUTH_URL'), | ||||
|             help='Authentication URL (Env: OS_AUTH_URL)') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-tenant-name', metavar='<auth-tenant-name>', | ||||
|             default=env('OS_TENANT_NAME'), | ||||
|             help='Authentication tenant name (Env: OS_TENANT_NAME)') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-username', metavar='<auth-username>', | ||||
|             default=utils.env('OS_USERNAME'), | ||||
|             help='Authentication username (Env: OS_USERNAME)') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-password', metavar='<auth-password>', | ||||
|             default=utils.env('OS_PASSWORD'), | ||||
|             help='Authentication password (Env: OS_PASSWORD)') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-region-name', metavar='<auth-region-name>', | ||||
|             default=env('OS_REGION_NAME'), | ||||
|             help='Authentication region name (Env: OS_REGION_NAME)') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-quantum-api-version', | ||||
|             metavar='<quantum-api-version>', | ||||
|             default=env('OS_QUANTUM_API_VERSION', default='2.0'), | ||||
|             help='QAUNTUM API version, default=2.0 ' | ||||
|                  '(Env: OS_QUANTUM_API_VERSION)') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-token', metavar='<token>', | ||||
|             default=env('OS_TOKEN'), | ||||
|             help='Defaults to env[OS_TOKEN]') | ||||
|  | ||||
|         parser.add_argument( | ||||
|             '--os-url', metavar='<url>', | ||||
|             default=env('OS_URL'), | ||||
|             help='Defaults to env[OS_URL]') | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|     def run(self, argv): | ||||
|         """Equivalent to the main program for the application. | ||||
|  | ||||
|         :param argv: input arguments and options | ||||
|         :paramtype argv: list of str | ||||
|         """ | ||||
|         try: | ||||
|             self.options, remainder = self.parser.parse_known_args(argv) | ||||
|             self.configure_logging() | ||||
|             self.interactive_mode = not remainder | ||||
|             self.initialize_app(remainder) | ||||
|         except Exception as err: | ||||
|             if self.options.debug: | ||||
|                 self.log.exception(err) | ||||
|                 raise | ||||
|             else: | ||||
|                 self.log.error(err) | ||||
|             return 1 | ||||
|         result = 1 | ||||
|         if self.interactive_mode: | ||||
|             _argv = [sys.argv[0]] | ||||
|             sys.argv = _argv | ||||
|             result = self.interact() | ||||
|         else: | ||||
|             result = self.run_subcommand(remainder) | ||||
|         return result | ||||
|  | ||||
|     def authenticate_user(self): | ||||
|         """Make sure the user has provided all of the authentication | ||||
|         info we need. | ||||
|         """ | ||||
|  | ||||
|         if self.options.os_token or self.options.os_url: | ||||
|             # Token flow auth takes priority | ||||
|             if not self.options.os_token: | ||||
|                 raise exc.CommandError( | ||||
|                     "You must provide a token via" | ||||
|                     " either --os-token or env[OS_TOKEN]") | ||||
|  | ||||
|             if not self.options.os_url: | ||||
|                 raise exc.CommandError( | ||||
|                     "You must provide a service URL via" | ||||
|                     " either --os-url or env[OS_URL]") | ||||
|  | ||||
|         else: | ||||
|             # Validate password flow auth | ||||
|             if not self.options.os_username: | ||||
|                 raise exc.CommandError( | ||||
|                     "You must provide a username via" | ||||
|                     " either --os-username or env[OS_USERNAME]") | ||||
|  | ||||
|             if not self.options.os_password: | ||||
|                 raise exc.CommandError( | ||||
|                     "You must provide a password via" | ||||
|                     " either --os-password or env[OS_PASSWORD]") | ||||
|  | ||||
|             if not (self.options.os_tenant_name): | ||||
|                 raise exc.CommandError( | ||||
|                     "You must provide a tenant_name via" | ||||
|                     " either --os-tenant-name or via env[OS_TENANT_NAME]") | ||||
|  | ||||
|             if not self.options.os_auth_url: | ||||
|                 raise exc.CommandError( | ||||
|                     "You must provide an auth url via" | ||||
|                     " either --os-auth-url or via env[OS_AUTH_URL]") | ||||
|  | ||||
|         self.client_manager = clientmanager.ClientManager( | ||||
|             token=self.options.os_token, | ||||
|             url=self.options.os_url, | ||||
|             auth_url=self.options.os_auth_url, | ||||
|             tenant_name=self.options.os_tenant_name, | ||||
|             username=self.options.os_username, | ||||
|             password=self.options.os_password, | ||||
|             region_name=self.options.os_region_name, | ||||
|             api_version=self.api_version, ) | ||||
|         return | ||||
|  | ||||
|     def initialize_app(self, argv): | ||||
|         """Global app init bits: | ||||
|  | ||||
|         * set up API versions | ||||
|         * validate authentication info | ||||
|         """ | ||||
|  | ||||
|         super(QuantumShell, self).initialize_app(argv) | ||||
|  | ||||
|         self.api_version = { | ||||
|             'network': self.options.os_quantum_api_version, } | ||||
|  | ||||
|         # If the user is not asking for help, make sure they | ||||
|         # have given us auth. | ||||
|         cmd_name = None | ||||
|         if argv: | ||||
|             cmd_info = self.command_manager.find_command(argv) | ||||
|             cmd_factory, cmd_name, sub_argv = cmd_info | ||||
|         if self.interactive_mode or cmd_name != 'help': | ||||
|             self.authenticate_user() | ||||
|  | ||||
|     def clean_up(self, cmd, result, err): | ||||
|         self.log.debug('clean_up %s', cmd.__class__.__name__) | ||||
|         if err: | ||||
|             self.log.debug('got an error: %s', err) | ||||
|  | ||||
|  | ||||
| def itertools_compressdef(data, selectors): | ||||
|     # patch 2.6 compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F | ||||
|     return (d for d, s in itertools.izip(data, selectors) if s) | ||||
|  | ||||
|  | ||||
| def main(argv=sys.argv[1:]): | ||||
|     apiVersion = None | ||||
|     versionFlag = False | ||||
|     for argitem in argv: | ||||
|         if argitem.startswith('--os-quantum-api-version='): | ||||
|             apiVersion = argitem.split('=', 2)[1] | ||||
|             break | ||||
|         elif '--os-quantum-api-version' == argitem: | ||||
|             versionFlag = True | ||||
|         elif versionFlag: | ||||
|             apiVersion = argitem | ||||
|             break | ||||
|     if apiVersion and apiVersion not in COMMANDS.keys(): | ||||
|         print ("Invalid API version or API version '%s' is not supported" % | ||||
|                apiVersion) | ||||
|         sys.exit(1) | ||||
|     if not apiVersion: | ||||
|         apiVersion = env('OS_QUANTUM_API_VERSION', default='2.0') | ||||
|     try: | ||||
|         if not getattr(itertools, 'compress', None): | ||||
|             setattr(itertools, 'compress', itertools_compressdef) | ||||
|         return QuantumShell(apiVersion).run(argv) | ||||
|     except exc.QuantumClientException: | ||||
|         return 1 | ||||
|     except Exception as e: | ||||
|         print e | ||||
|         return 1 | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     sys.exit(main(sys.argv[1:])) | ||||
| @@ -17,6 +17,7 @@ | ||||
|  | ||||
|  | ||||
| from quantum import api as server | ||||
| from quantum.openstack.common import cfg | ||||
| from quantum.tests.unit import testlib_api | ||||
|  | ||||
|  | ||||
| @@ -40,9 +41,8 @@ class FakeHTTPConnection: | ||||
|     def __init__(self, _1, _2): | ||||
|         # Ignore host and port parameters | ||||
|         self._req = None | ||||
|         plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' | ||||
|         options = dict(plugin_provider=plugin) | ||||
|         self._api = server.APIRouterV11(options) | ||||
|         cfg.CONF.core_plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' | ||||
|         self._api = server.APIRouterV11() | ||||
|  | ||||
|     def request(self, method, action, body, headers): | ||||
|         # TODO: remove version prefix from action! | ||||
|   | ||||
							
								
								
									
										59
									
								
								quantumclient/tests/unit/test_casual_args.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								quantumclient/tests/unit/test_casual_args.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import unittest | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.quantum import v2_0 as quantumV20 | ||||
|  | ||||
|  | ||||
| class CLITestArgs(unittest.TestCase): | ||||
|  | ||||
|     def test_empty(self): | ||||
|         _mydict = quantumV20.parse_args_to_dict([]) | ||||
|         self.assertEqual({}, _mydict) | ||||
|  | ||||
|     def test_default_bool(self): | ||||
|         _specs = ['--my_bool', '--arg1', 'value1'] | ||||
|         _mydict = quantumV20.parse_args_to_dict(_specs) | ||||
|         self.assertTrue(_mydict['my_bool']) | ||||
|  | ||||
|     def test_bool_true(self): | ||||
|         _specs = ['--my-bool', 'type=bool', 'true',  '--arg1', 'value1'] | ||||
|         _mydict = quantumV20.parse_args_to_dict(_specs) | ||||
|         self.assertTrue(_mydict['my-bool']) | ||||
|  | ||||
|     def test_bool_false(self): | ||||
|         _specs = ['--my_bool', 'type=bool', 'false',  '--arg1', 'value1'] | ||||
|         _mydict = quantumV20.parse_args_to_dict(_specs) | ||||
|         self.assertFalse(_mydict['my_bool']) | ||||
|  | ||||
|     def test_nargs(self): | ||||
|         _specs = ['--tag', 'x', 'y', '--arg1', 'value1'] | ||||
|         _mydict = quantumV20.parse_args_to_dict(_specs) | ||||
|         self.assertTrue('x' in _mydict['tag']) | ||||
|         self.assertTrue('y' in _mydict['tag']) | ||||
|  | ||||
|     def test_badarg(self): | ||||
|         _specs = ['--tag=t', 'x', 'y', '--arg1', 'value1'] | ||||
|         self.assertRaises(exceptions.CommandError, | ||||
|                           quantumV20.parse_args_to_dict, _specs) | ||||
|  | ||||
|     def test_arg(self): | ||||
|         _specs = ['--tag=t', '--arg1', 'value1'] | ||||
|         self.assertEqual('value1', | ||||
|                          quantumV20.parse_args_to_dict(_specs)['arg1']) | ||||
| @@ -26,12 +26,12 @@ import sys | ||||
| import unittest | ||||
|  | ||||
| from quantum import api as server | ||||
| from quantum.db import api as db | ||||
| from quantumclient import ClientV11 | ||||
| from quantumclient import cli_lib as cli | ||||
| from quantumclient import Client | ||||
| from quantum.db import api as db | ||||
| from quantum.openstack.common import cfg | ||||
| from quantumclient.tests.unit import stubs as client_stubs | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger('quantumclient.tests.test_cli') | ||||
| API_VERSION = "1.1" | ||||
| FORMAT = 'json' | ||||
| @@ -41,18 +41,16 @@ class CLITest(unittest.TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         """Prepare the test environment""" | ||||
|         options = {} | ||||
|         options['plugin_provider'] = ( | ||||
|             'quantum.plugins.sample.SamplePlugin.FakePlugin') | ||||
|         #TODO: make the version of the API router configurable | ||||
|         self.api = server.APIRouterV11(options) | ||||
|         cfg.CONF.core_plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' | ||||
|         self.api = server.APIRouterV11() | ||||
|  | ||||
|         self.tenant_id = "test_tenant" | ||||
|         self.network_name_1 = "test_network_1" | ||||
|         self.network_name_2 = "test_network_2" | ||||
|         self.version = API_VERSION | ||||
|         # Prepare client and plugin manager | ||||
|         self.client = Client(tenant=self.tenant_id, | ||||
|         self.client = ClientV11(tenant=self.tenant_id, | ||||
|                                 format=FORMAT, | ||||
|                                 testingStub=client_stubs.FakeHTTPConnection, | ||||
|                                 version=self.version) | ||||
| @@ -92,6 +90,23 @@ class CLITest(unittest.TestCase): | ||||
|             # Must add newline at the end to match effect of print call | ||||
|             self.assertEquals(self.fake_stdout.make_string(), output + '\n') | ||||
|  | ||||
|     def _verify_list_networks_details_name_filter(self, name): | ||||
|             # Verification - get raw result from db | ||||
|             nw_list = db.network_list(self.tenant_id) | ||||
|             nw_filtered = [] | ||||
|             for nw in nw_list: | ||||
|                 if nw.name == name: | ||||
|                     nw_filtered.append(nw) | ||||
|             networks = [dict(id=nw.uuid, name=nw.name) for nw in nw_filtered] | ||||
|             # Fill CLI template | ||||
|             output = cli.prepare_output('list_nets_detail', | ||||
|                                         self.tenant_id, | ||||
|                                         dict(networks=networks), | ||||
|                                         self.version) | ||||
|             # Verify! | ||||
|             # Must add newline at the end to match effect of print call | ||||
|             self.assertEquals(self.fake_stdout.make_string(), output + '\n') | ||||
|  | ||||
|     def _verify_create_network(self): | ||||
|             # Verification - get raw result from db | ||||
|             nw_list = db.network_list(self.tenant_id) | ||||
| @@ -160,14 +175,10 @@ class CLITest(unittest.TestCase): | ||||
|             port_list = db.port_list(nw.uuid) | ||||
|             if not port_list: | ||||
|                 network['ports'] = [ | ||||
|                     { | ||||
|                         'id': '<none>', | ||||
|                     {'id': '<none>', | ||||
|                      'state': '<none>', | ||||
|                      'attachment': { | ||||
|                             'id': '<none>', | ||||
|                             }, | ||||
|                         }, | ||||
|                     ] | ||||
|                      'id': '<none>', }, }, ] | ||||
|             else: | ||||
|                 network['ports'] = [] | ||||
|                 for port in port_list: | ||||
| @@ -175,9 +186,7 @@ class CLITest(unittest.TestCase): | ||||
|                         'id': port.uuid, | ||||
|                         'state': port.state, | ||||
|                         'attachment': { | ||||
|                             'id': port.interface_id or '<none>', | ||||
|                             }, | ||||
|                         }) | ||||
|                             'id': port.interface_id or '<none>', }, }) | ||||
|  | ||||
|             # Fill CLI template | ||||
|             output = cli.prepare_output('show_net_detail', | ||||
| @@ -416,6 +425,25 @@ class CLITest(unittest.TestCase): | ||||
|         LOG.debug(self.fake_stdout.content) | ||||
|         self._verify_list_networks_details() | ||||
|  | ||||
|     def test_list_networks_details_v11_name_filter(self): | ||||
|         try: | ||||
|             # Pre-populate data for testing using db api | ||||
|             db.network_create(self.tenant_id, self.network_name_1) | ||||
|             db.network_create(self.tenant_id, self.network_name_2) | ||||
|             #TODO: test filters | ||||
|             cli.list_nets_detail_v11(self.client, | ||||
|                                      self.tenant_id, | ||||
|                                      self.version, | ||||
|                                      {'name': self.network_name_1, }) | ||||
|         except: | ||||
|             LOG.exception("Exception caught: %s", sys.exc_info()) | ||||
|             self.fail("test_list_networks_details_v11 failed due to " + | ||||
|                       "an exception") | ||||
|  | ||||
|         LOG.debug("Operation completed. Verifying result") | ||||
|         LOG.debug(self.fake_stdout.content) | ||||
|         self._verify_list_networks_details_name_filter(self.network_name_1) | ||||
|  | ||||
|     def test_create_network(self): | ||||
|         try: | ||||
|             cli.create_net(self.client, | ||||
|   | ||||
							
								
								
									
										290
									
								
								quantumclient/tests/unit/test_cli20.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								quantumclient/tests/unit/test_cli20.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,290 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import sys | ||||
| import unittest | ||||
|  | ||||
| import mox | ||||
| from mox import ContainsKeyValue | ||||
| from mox import Comparator | ||||
|  | ||||
| from quantumclient.v2_0.client import Client | ||||
|  | ||||
| API_VERSION = "2.0" | ||||
| FORMAT = 'json' | ||||
| TOKEN = 'testtoken' | ||||
| ENDURL = 'localurl' | ||||
|  | ||||
|  | ||||
| class FakeStdout: | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.content = [] | ||||
|  | ||||
|     def write(self, text): | ||||
|         self.content.append(text) | ||||
|  | ||||
|     def make_string(self): | ||||
|         result = '' | ||||
|         for line in self.content: | ||||
|             result = result + line | ||||
|         return result | ||||
|  | ||||
|  | ||||
| class MyResp(object): | ||||
|     def __init__(self, status): | ||||
|         self.status = status | ||||
|  | ||||
|  | ||||
| class MyApp(object): | ||||
|     def __init__(self, _stdout): | ||||
|         self.stdout = _stdout | ||||
|  | ||||
|  | ||||
| class MyComparator(Comparator): | ||||
|     def __init__(self, lhs, client): | ||||
|         self.lhs = lhs | ||||
|         self.client = client | ||||
|  | ||||
|     def _com_dict(self, lhs, rhs): | ||||
|         if len(lhs) != len(rhs): | ||||
|             return False | ||||
|         for key, value in lhs.iteritems(): | ||||
|             if key not in rhs: | ||||
|                 return False | ||||
|             rhs_value = rhs[key] | ||||
|             if not self._com(value, rhs_value): | ||||
|                 return False | ||||
|         return True | ||||
|  | ||||
|     def _com_list(self, lhs, rhs): | ||||
|         if len(lhs) != len(rhs): | ||||
|             return False | ||||
|         for lhs_value in lhs: | ||||
|             if lhs_value not in rhs: | ||||
|                 return False | ||||
|         return True | ||||
|  | ||||
|     def _com(self, lhs, rhs): | ||||
|         if lhs is None: | ||||
|             return rhs is None | ||||
|         if isinstance(lhs, dict): | ||||
|             if not isinstance(rhs, dict): | ||||
|                 return False | ||||
|             return self._com_dict(lhs, rhs) | ||||
|         if isinstance(lhs, list): | ||||
|             if not isinstance(rhs, list): | ||||
|                 return False | ||||
|             return self._com_list(lhs, rhs) | ||||
|         if isinstance(lhs, tuple): | ||||
|             if not isinstance(rhs, tuple): | ||||
|                 return False | ||||
|             return self._com_list(lhs, rhs) | ||||
|         return lhs == rhs | ||||
|  | ||||
|     def equals(self, rhs): | ||||
|         if self.client: | ||||
|             rhs = self.client.deserialize(rhs, 200) | ||||
|         return self._com(self.lhs, rhs) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return str(self.lhs) | ||||
|  | ||||
|  | ||||
| class CLITestV20Base(unittest.TestCase): | ||||
|  | ||||
|     def _url(self, path, query=None): | ||||
|         _url_str = self.endurl + "/v" + API_VERSION + path + "." + FORMAT | ||||
|         return query and _url_str + "?" + query or _url_str | ||||
|  | ||||
|     def setUp(self): | ||||
|         """Prepare the test environment""" | ||||
|         self.mox = mox.Mox() | ||||
|         self.endurl = ENDURL | ||||
|         self.client = Client(token=TOKEN, endpoint_url=self.endurl) | ||||
|         self.fake_stdout = FakeStdout() | ||||
|         sys.stdout = self.fake_stdout | ||||
|  | ||||
|     def tearDown(self): | ||||
|         """Clear the test environment""" | ||||
|         sys.stdout = sys.__stdout__ | ||||
|  | ||||
|     def _test_create_resource(self, resource, cmd, | ||||
|                               name, myid, args, | ||||
|                               position_names, position_values, tenant_id=None, | ||||
|                               tags=None, admin_state_up=True): | ||||
|         self.mox.StubOutWithMock(cmd, "get_client") | ||||
|         self.mox.StubOutWithMock(self.client.httpclient, "request") | ||||
|         cmd.get_client().MultipleTimes().AndReturn(self.client) | ||||
|         if resource == 'subnet': | ||||
|             body = {resource: {}, } | ||||
|         else: | ||||
|             body = {resource: {'admin_state_up': admin_state_up, }, } | ||||
|         if tenant_id: | ||||
|             body[resource].update({'tenant_id': tenant_id}) | ||||
|         if tags: | ||||
|             body[resource].update({'tags': tags}) | ||||
|         for i in xrange(len(position_names)): | ||||
|             body[resource].update({position_names[i]: position_values[i]}) | ||||
|         ress = {resource: | ||||
|                 {'id': myid, | ||||
|                  'name': name, }, } | ||||
|         resstr = self.client.serialize(ress) | ||||
|         # url method body | ||||
|         path = getattr(self.client, resource + "s_path") | ||||
|         self.client.httpclient.request( | ||||
|             self._url(path), 'POST', | ||||
|             body=MyComparator(body, self.client), | ||||
|             headers=ContainsKeyValue('X-Auth-Token', | ||||
|                                      TOKEN)).AndReturn((MyResp(200), | ||||
|                                                         resstr)) | ||||
|         self.mox.ReplayAll() | ||||
|         cmd_parser = cmd.get_parser('create_' + resource) | ||||
|         parsed_args = cmd_parser.parse_args(args) | ||||
|         cmd.run(parsed_args) | ||||
|         self.mox.VerifyAll() | ||||
|         self.mox.UnsetStubs() | ||||
|         _str = self.fake_stdout.make_string() | ||||
|         self.assertTrue(myid, _str) | ||||
|         self.assertTrue(name, _str) | ||||
|  | ||||
|     def _test_list_resources(self, resources, cmd, detail=False, tags=[], | ||||
|                              fields_1=[], fields_2=[]): | ||||
|         self.mox.StubOutWithMock(cmd, "get_client") | ||||
|         self.mox.StubOutWithMock(self.client.httpclient, "request") | ||||
|         cmd.get_client().MultipleTimes().AndReturn(self.client) | ||||
|         reses = {resources: [{'id': 'myid1', }, | ||||
|                              {'id': 'myid2', }, ], } | ||||
|         resstr = self.client.serialize(reses) | ||||
|         # url method body | ||||
|         query = "" | ||||
|         args = detail and ['-D', ] or [] | ||||
|         if fields_1: | ||||
|             for field in fields_1: | ||||
|                 args.append('--fields') | ||||
|                 args.append(field) | ||||
|  | ||||
|         if tags: | ||||
|             args.append('--') | ||||
|             args.append("--tag") | ||||
|         for tag in tags: | ||||
|             if query: | ||||
|                 query += "&tag=" + tag | ||||
|             else: | ||||
|                 query = "tag=" + tag | ||||
|             args.append(tag) | ||||
|         if (not tags) and fields_2: | ||||
|             args.append('--') | ||||
|         if fields_2: | ||||
|             args.append("--fields") | ||||
|             for field in fields_2: | ||||
|                 args.append(field) | ||||
|         if detail: | ||||
|             query = query and query + '&verbose=True' or 'verbose=True' | ||||
|         fields_1.extend(fields_2) | ||||
|         for field in fields_1: | ||||
|             if query: | ||||
|                 query += "&fields=" + field | ||||
|             else: | ||||
|                 query = "fields=" + field | ||||
|         path = getattr(self.client, resources + "_path") | ||||
|         self.client.httpclient.request( | ||||
|             self._url(path, query), 'GET', | ||||
|             body=None, | ||||
|             headers=ContainsKeyValue('X-Auth-Token', | ||||
|                                      TOKEN)).AndReturn((MyResp(200), resstr)) | ||||
|         self.mox.ReplayAll() | ||||
|         cmd_parser = cmd.get_parser("list_" + resources) | ||||
|  | ||||
|         parsed_args = cmd_parser.parse_args(args) | ||||
|         cmd.run(parsed_args) | ||||
|         self.mox.VerifyAll() | ||||
|         self.mox.UnsetStubs() | ||||
|         _str = self.fake_stdout.make_string() | ||||
|         self.assertTrue('myid1' in _str) | ||||
|  | ||||
|     def _test_update_resource(self, resource, cmd, myid, args, extrafields): | ||||
|         self.mox.StubOutWithMock(cmd, "get_client") | ||||
|         self.mox.StubOutWithMock(self.client.httpclient, "request") | ||||
|         cmd.get_client().MultipleTimes().AndReturn(self.client) | ||||
|         body = {resource: extrafields} | ||||
|         path = getattr(self.client, resource + "_path") | ||||
|         self.client.httpclient.request( | ||||
|             self._url(path % myid), 'PUT', | ||||
|             body=MyComparator(body, self.client), | ||||
|             headers=ContainsKeyValue('X-Auth-Token', | ||||
|                                      TOKEN)).AndReturn((MyResp(204), None)) | ||||
|         self.mox.ReplayAll() | ||||
|         cmd_parser = cmd.get_parser("update_" + resource) | ||||
|  | ||||
|         parsed_args = cmd_parser.parse_args(args) | ||||
|         cmd.run(parsed_args) | ||||
|         self.mox.VerifyAll() | ||||
|         self.mox.UnsetStubs() | ||||
|         _str = self.fake_stdout.make_string() | ||||
|         self.assertTrue(myid in _str) | ||||
|  | ||||
|     def _test_show_resource(self, resource, cmd, myid, args, fields=[]): | ||||
|         self.mox.StubOutWithMock(cmd, "get_client") | ||||
|         self.mox.StubOutWithMock(self.client.httpclient, "request") | ||||
|         cmd.get_client().MultipleTimes().AndReturn(self.client) | ||||
|         query = None | ||||
|         for field in fields: | ||||
|             if query: | ||||
|                 query += "&fields=" + field | ||||
|             else: | ||||
|                 query = "fields=" + field | ||||
|         resnetworks = {resource: | ||||
|                        {'id': myid, | ||||
|                         'name': 'myname', }, } | ||||
|         resstr = self.client.serialize(resnetworks) | ||||
|         path = getattr(self.client, resource + "_path") | ||||
|         self.client.httpclient.request( | ||||
|             self._url(path % myid, query), 'GET', | ||||
|             body=None, | ||||
|             headers=ContainsKeyValue('X-Auth-Token', | ||||
|                                      TOKEN)).AndReturn((MyResp(200), resstr)) | ||||
|         self.mox.ReplayAll() | ||||
|         cmd_parser = cmd.get_parser("show_" + resource) | ||||
|  | ||||
|         parsed_args = cmd_parser.parse_args(args) | ||||
|         cmd.run(parsed_args) | ||||
|         self.mox.VerifyAll() | ||||
|         self.mox.UnsetStubs() | ||||
|         _str = self.fake_stdout.make_string() | ||||
|         self.assertTrue(myid in _str) | ||||
|         self.assertTrue('myname' in _str) | ||||
|  | ||||
|     def _test_delete_resource(self, resource, cmd, myid, args): | ||||
|         self.mox.StubOutWithMock(cmd, "get_client") | ||||
|         self.mox.StubOutWithMock(self.client.httpclient, "request") | ||||
|         cmd.get_client().MultipleTimes().AndReturn(self.client) | ||||
|         path = getattr(self.client, resource + "_path") | ||||
|         self.client.httpclient.request( | ||||
|             self._url(path % myid), 'DELETE', | ||||
|             body=None, | ||||
|             headers=ContainsKeyValue('X-Auth-Token', | ||||
|                                      TOKEN)).AndReturn((MyResp(204), None)) | ||||
|         self.mox.ReplayAll() | ||||
|         cmd_parser = cmd.get_parser("delete_" + resource) | ||||
|  | ||||
|         parsed_args = cmd_parser.parse_args(args) | ||||
|         cmd.run(parsed_args) | ||||
|         self.mox.VerifyAll() | ||||
|         self.mox.UnsetStubs() | ||||
|         _str = self.fake_stdout.make_string() | ||||
|         self.assertTrue(myid in _str) | ||||
							
								
								
									
										140
									
								
								quantumclient/tests/unit/test_cli20_network.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								quantumclient/tests/unit/test_cli20_network.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.tests.unit.test_cli20 import CLITestV20Base | ||||
| from quantumclient.tests.unit.test_cli20 import MyApp | ||||
| from quantumclient.quantum.v2_0.network import CreateNetwork | ||||
| from quantumclient.quantum.v2_0.network import ListNetwork | ||||
| from quantumclient.quantum.v2_0.network import UpdateNetwork | ||||
| from quantumclient.quantum.v2_0.network import ShowNetwork | ||||
| from quantumclient.quantum.v2_0.network import DeleteNetwork | ||||
|  | ||||
|  | ||||
| class CLITestV20Network(CLITestV20Base): | ||||
|     def test_create_network(self): | ||||
|         """ create_net myname""" | ||||
|         resource = 'network' | ||||
|         cmd = CreateNetwork(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         args = [name, ] | ||||
|         position_names = ['name', ] | ||||
|         position_values = [name, ] | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values) | ||||
|  | ||||
|     def test_create_network_tenant(self): | ||||
|         """create_net --tenant-id tenantid myname""" | ||||
|         resource = 'network' | ||||
|         cmd = CreateNetwork(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         args = ['--tenant-id', 'tenantid', name] | ||||
|         position_names = ['name', ] | ||||
|         position_values = [name, ] | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           tenant_id='tenantid') | ||||
|  | ||||
|     def test_create_network_tags(self): | ||||
|         """ create_net myname --tags a b""" | ||||
|         resource = 'network' | ||||
|         cmd = CreateNetwork(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         args = [name, '--tags', 'a', 'b'] | ||||
|         position_names = ['name', ] | ||||
|         position_values = [name, ] | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           tags=['a', 'b']) | ||||
|  | ||||
|     def test_create_network_state(self): | ||||
|         """ create_net --admin-state-down myname""" | ||||
|         resource = 'network' | ||||
|         cmd = CreateNetwork(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         args = ['--admin-state-down', name, ] | ||||
|         position_names = ['name', ] | ||||
|         position_values = [name, ] | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           admin_state_up=False) | ||||
|  | ||||
|     def test_list_nets_detail(self): | ||||
|         """list_nets -D""" | ||||
|         resources = "networks" | ||||
|         cmd = ListNetwork(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, True) | ||||
|  | ||||
|     def test_list_nets_tags(self): | ||||
|         """list_nets -- --tags a b""" | ||||
|         resources = "networks" | ||||
|         cmd = ListNetwork(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_nets_detail_tags(self): | ||||
|         """list_nets -D -- --tags a b""" | ||||
|         resources = "networks" | ||||
|         cmd = ListNetwork(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_nets_fields(self): | ||||
|         """list_nets --fields a --fields b -- --fields c d""" | ||||
|         resources = "networks" | ||||
|         cmd = ListNetwork(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, | ||||
|                                   fields_1=['a', 'b'], fields_2=['c', 'd']) | ||||
|  | ||||
|     def test_update_network_exception(self): | ||||
|         """ update_net myid""" | ||||
|         resource = 'network' | ||||
|         cmd = UpdateNetwork(MyApp(sys.stdout), None) | ||||
|         self.assertRaises(exceptions.CommandError, self._test_update_resource, | ||||
|                           resource, cmd, 'myid', ['myid'], {}) | ||||
|  | ||||
|     def test_update_network(self): | ||||
|         """ update_net myid --name myname --tags a b""" | ||||
|         resource = 'network' | ||||
|         cmd = UpdateNetwork(MyApp(sys.stdout), None) | ||||
|         self._test_update_resource(resource, cmd, 'myid', | ||||
|                                    ['myid', '--name', 'myname', | ||||
|                                     '--tags', 'a', 'b'], | ||||
|                                    {'name': 'myname', 'tags': ['a', 'b'], } | ||||
|                                    ) | ||||
|  | ||||
|     def test_show_network(self): | ||||
|         """ show_net --fields id --fields name myid """ | ||||
|         resource = 'network' | ||||
|         cmd = ShowNetwork(MyApp(sys.stdout), None) | ||||
|         myid = 'myid' | ||||
|         args = ['--fields', 'id', '--fields', 'name', myid] | ||||
|         self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) | ||||
|  | ||||
|     def test_delete_network(self): | ||||
|         """ | ||||
|         delete_net myid | ||||
|         """ | ||||
|         resource = 'network' | ||||
|         cmd = DeleteNetwork(MyApp(sys.stdout), None) | ||||
|         myid = 'myid' | ||||
|         args = [myid] | ||||
|         self._test_delete_resource(resource, cmd, myid, args) | ||||
							
								
								
									
										139
									
								
								quantumclient/tests/unit/test_cli20_port.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								quantumclient/tests/unit/test_cli20_port.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from quantumclient.tests.unit.test_cli20 import CLITestV20Base | ||||
| from quantumclient.tests.unit.test_cli20 import MyApp | ||||
| from quantumclient.quantum.v2_0.port import CreatePort | ||||
| from quantumclient.quantum.v2_0.port import ListPort | ||||
| from quantumclient.quantum.v2_0.port import UpdatePort | ||||
| from quantumclient.quantum.v2_0.port import ShowPort | ||||
| from quantumclient.quantum.v2_0.port import DeletePort | ||||
|  | ||||
|  | ||||
| class CLITestV20Port(CLITestV20Base): | ||||
|  | ||||
|     def test_create_port(self): | ||||
|         """ create_port netid""" | ||||
|         resource = 'port' | ||||
|         cmd = CreatePort(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         args = [netid] | ||||
|         position_names = ['network_id'] | ||||
|         position_values = [] | ||||
|         position_values.extend([netid]) | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values) | ||||
|  | ||||
|     def test_create_port_full(self): | ||||
|         """ create_port --mac-address mac --device-id deviceid netid""" | ||||
|         resource = 'port' | ||||
|         cmd = CreatePort(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         args = ['--mac-address', 'mac', '--device-id', 'deviceid', netid] | ||||
|         position_names = ['network_id', 'mac_address', 'device_id'] | ||||
|         position_values = [netid, 'mac', 'deviceid'] | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values) | ||||
|  | ||||
|     def test_create_port_tenant(self): | ||||
|         """create_port --tenant-id tenantid netid""" | ||||
|         resource = 'port' | ||||
|         cmd = CreatePort(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         args = ['--tenant-id', 'tenantid', netid, ] | ||||
|         position_names = ['network_id'] | ||||
|         position_values = [] | ||||
|         position_values.extend([netid]) | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           tenant_id='tenantid') | ||||
|  | ||||
|     def test_create_port_tags(self): | ||||
|         """ create_port netid mac_address device_id --tags a b""" | ||||
|         resource = 'port' | ||||
|         cmd = CreatePort(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         args = [netid, '--tags', 'a', 'b'] | ||||
|         position_names = ['network_id'] | ||||
|         position_values = [] | ||||
|         position_values.extend([netid]) | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_ports_detail(self): | ||||
|         """list_ports -D""" | ||||
|         resources = "ports" | ||||
|         cmd = ListPort(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, True) | ||||
|  | ||||
|     def test_list_ports_tags(self): | ||||
|         """list_ports -- --tags a b""" | ||||
|         resources = "ports" | ||||
|         cmd = ListPort(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_ports_detail_tags(self): | ||||
|         """list_ports -D -- --tags a b""" | ||||
|         resources = "ports" | ||||
|         cmd = ListPort(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_ports_fields(self): | ||||
|         """list_ports --fields a --fields b -- --fields c d""" | ||||
|         resources = "ports" | ||||
|         cmd = ListPort(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, | ||||
|                                   fields_1=['a', 'b'], fields_2=['c', 'd']) | ||||
|  | ||||
|     def test_update_port(self): | ||||
|         """ update_port myid --name myname --tags a b""" | ||||
|         resource = 'port' | ||||
|         cmd = UpdatePort(MyApp(sys.stdout), None) | ||||
|         self._test_update_resource(resource, cmd, 'myid', | ||||
|                                    ['myid', '--name', 'myname', | ||||
|                                     '--tags', 'a', 'b'], | ||||
|                                    {'name': 'myname', 'tags': ['a', 'b'], } | ||||
|                                    ) | ||||
|  | ||||
|     def test_show_port(self): | ||||
|         """ show_port --fields id --fields name myid """ | ||||
|         resource = 'port' | ||||
|         cmd = ShowPort(MyApp(sys.stdout), None) | ||||
|         myid = 'myid' | ||||
|         args = ['--fields', 'id', '--fields', 'name', myid] | ||||
|         self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) | ||||
|  | ||||
|     def test_delete_port(self): | ||||
|         """ | ||||
|         delete_port myid | ||||
|         """ | ||||
|         resource = 'port' | ||||
|         cmd = DeletePort(MyApp(sys.stdout), None) | ||||
|         myid = 'myid' | ||||
|         args = [myid] | ||||
|         self._test_delete_resource(resource, cmd, myid, args) | ||||
							
								
								
									
										130
									
								
								quantumclient/tests/unit/test_cli20_subnet.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								quantumclient/tests/unit/test_cli20_subnet.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from quantumclient.tests.unit.test_cli20 import CLITestV20Base | ||||
| from quantumclient.tests.unit.test_cli20 import MyApp | ||||
| from quantumclient.quantum.v2_0.subnet import CreateSubnet | ||||
| from quantumclient.quantum.v2_0.subnet import ListSubnet | ||||
| from quantumclient.quantum.v2_0.subnet import UpdateSubnet | ||||
| from quantumclient.quantum.v2_0.subnet import ShowSubnet | ||||
| from quantumclient.quantum.v2_0.subnet import DeleteSubnet | ||||
|  | ||||
|  | ||||
| class CLITestV20Subnet(CLITestV20Base): | ||||
|  | ||||
|     def test_create_subnet(self): | ||||
|         """ create_subnet --gateway gateway netid cidr""" | ||||
|         resource = 'subnet' | ||||
|         cmd = CreateSubnet(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         cidr = 'cidrvalue' | ||||
|         gateway = 'gatewayvalue' | ||||
|         args = ['--gateway', gateway, netid, cidr] | ||||
|         position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] | ||||
|         position_values = [4, ] | ||||
|         position_values.extend([netid, cidr, gateway]) | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values) | ||||
|  | ||||
|     def test_create_subnet_tenant(self): | ||||
|         """create_subnet --tenant-id tenantid netid cidr""" | ||||
|         resource = 'subnet' | ||||
|         cmd = CreateSubnet(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         cidr = 'prefixvalue' | ||||
|         args = ['--tenant-id', 'tenantid', netid, cidr] | ||||
|         position_names = ['ip_version', 'network_id', 'cidr'] | ||||
|         position_values = [4, ] | ||||
|         position_values.extend([netid, cidr]) | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           tenant_id='tenantid') | ||||
|  | ||||
|     def test_create_subnet_tags(self): | ||||
|         """ create_subnet netid cidr --tags a b""" | ||||
|         resource = 'subnet' | ||||
|         cmd = CreateSubnet(MyApp(sys.stdout), None) | ||||
|         name = 'myname' | ||||
|         myid = 'myid' | ||||
|         netid = 'netid' | ||||
|         cidr = 'prefixvalue' | ||||
|         args = [netid, cidr, '--tags', 'a', 'b'] | ||||
|         position_names = ['ip_version', 'network_id', 'cidr'] | ||||
|         position_values = [4, ] | ||||
|         position_values.extend([netid, cidr]) | ||||
|         _str = self._test_create_resource(resource, cmd, name, myid, args, | ||||
|                                           position_names, position_values, | ||||
|                                           tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_subnets_detail(self): | ||||
|         """list_subnets -D""" | ||||
|         resources = "subnets" | ||||
|         cmd = ListSubnet(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, True) | ||||
|  | ||||
|     def test_list_subnets_tags(self): | ||||
|         """list_subnets -- --tags a b""" | ||||
|         resources = "subnets" | ||||
|         cmd = ListSubnet(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_subnets_detail_tags(self): | ||||
|         """list_subnets -D -- --tags a b""" | ||||
|         resources = "subnets" | ||||
|         cmd = ListSubnet(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) | ||||
|  | ||||
|     def test_list_subnets_fields(self): | ||||
|         """list_subnets --fields a --fields b -- --fields c d""" | ||||
|         resources = "subnets" | ||||
|         cmd = ListSubnet(MyApp(sys.stdout), None) | ||||
|         self._test_list_resources(resources, cmd, | ||||
|                                   fields_1=['a', 'b'], fields_2=['c', 'd']) | ||||
|  | ||||
|     def test_update_subnet(self): | ||||
|         """ update_subnet myid --name myname --tags a b""" | ||||
|         resource = 'subnet' | ||||
|         cmd = UpdateSubnet(MyApp(sys.stdout), None) | ||||
|         self._test_update_resource(resource, cmd, 'myid', | ||||
|                                    ['myid', '--name', 'myname', | ||||
|                                     '--tags', 'a', 'b'], | ||||
|                                    {'name': 'myname', 'tags': ['a', 'b'], } | ||||
|                                    ) | ||||
|  | ||||
|     def test_show_subnet(self): | ||||
|         """ show_subnet --fields id --fields name myid """ | ||||
|         resource = 'subnet' | ||||
|         cmd = ShowSubnet(MyApp(sys.stdout), None) | ||||
|         myid = 'myid' | ||||
|         args = ['--fields', 'id', '--fields', 'name', myid] | ||||
|         self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) | ||||
|  | ||||
|     def test_delete_subnet(self): | ||||
|         """ | ||||
|         delete_subnet myid | ||||
|         """ | ||||
|         resource = 'subnet' | ||||
|         cmd = DeleteSubnet(MyApp(sys.stdout), None) | ||||
|         myid = 'myid' | ||||
|         args = [myid] | ||||
|         self._test_delete_resource(resource, cmd, myid, args) | ||||
							
								
								
									
										14
									
								
								quantumclient/v2_0/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								quantumclient/v2_0/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
							
								
								
									
										398
									
								
								quantumclient/v2_0/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								quantumclient/v2_0/client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,398 @@ | ||||
| # Copyright 2012 OpenStack LLC. | ||||
| # 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. | ||||
| # | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| import httplib | ||||
| import logging | ||||
| import time | ||||
| import urllib | ||||
|  | ||||
| from quantumclient.client import HTTPClient | ||||
| from quantumclient.common import exceptions | ||||
| from quantumclient.common.serializer import Serializer | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def exception_handler_v20(status_code, error_content): | ||||
|     """ Exception handler for API v2.0 client | ||||
|  | ||||
|         This routine generates the appropriate | ||||
|         Quantum exception according to the contents of the | ||||
|         response body | ||||
|  | ||||
|         :param status_code: HTTP error status code | ||||
|         :param error_content: deserialized body of error response | ||||
|     """ | ||||
|  | ||||
|     quantum_errors = { | ||||
|         'NetworkNotFound': exceptions.NetworkNotFoundClient, | ||||
|         'NetworkInUse': exceptions.NetworkInUseClient, | ||||
|         'PortNotFound': exceptions.PortNotFoundClient, | ||||
|         'RequestedStateInvalid': exceptions.StateInvalidClient, | ||||
|         'PortInUse': exceptions.PortInUseClient, | ||||
|         'AlreadyAttached': exceptions.AlreadyAttachedClient, } | ||||
|  | ||||
|     error_dict = None | ||||
|     if isinstance(error_content, dict): | ||||
|         error_dict = error_content.get('QuantumError') | ||||
|     # Find real error type | ||||
|     bad_quantum_error_flag = False | ||||
|     if error_dict: | ||||
|         # If QuantumError key is found, it will definitely contain | ||||
|         # a 'message' and 'type' keys? | ||||
|         try: | ||||
|             error_type = error_dict['type'] | ||||
|             error_message = (error_dict['message'] + "\n" + | ||||
|                              error_dict['detail']) | ||||
|         except Exception: | ||||
|             bad_quantum_error_flag = True | ||||
|         if not bad_quantum_error_flag: | ||||
|             ex = None | ||||
|             try: | ||||
|                 # raise the appropriate error! | ||||
|                 ex = quantum_errors[error_type](message=error_message) | ||||
|                 ex.args = ([dict(status_code=status_code, | ||||
|                                  message=error_message)], ) | ||||
|             except Exception: | ||||
|                 pass | ||||
|             if ex: | ||||
|                 raise ex | ||||
|         else: | ||||
|             raise exceptions.QuantumClientException(message=error_dict) | ||||
|     else: | ||||
|         message = None | ||||
|         if isinstance(error_content, dict): | ||||
|             message = error_content.get('message', None) | ||||
|         if message: | ||||
|             raise exceptions.QuantumClientException(message=message) | ||||
|  | ||||
|     # If we end up here the exception was not a quantum error | ||||
|     msg = "%s-%s" % (status_code, error_content) | ||||
|     raise exceptions.QuantumClientException(message=msg) | ||||
|  | ||||
|  | ||||
| class APIParamsCall(object): | ||||
|     """A Decorator to add support for format and tenant overriding | ||||
|        and filters | ||||
|     """ | ||||
|     def __init__(self, function): | ||||
|         self.function = function | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         def with_params(*args, **kwargs): | ||||
|             _format = instance.format | ||||
|             if 'format' in kwargs: | ||||
|                 instance.format = kwargs['format'] | ||||
|             ret = self.function(instance, *args, **kwargs) | ||||
|             instance.forma = _format | ||||
|             return ret | ||||
|         return with_params | ||||
|  | ||||
|  | ||||
| class Client(object): | ||||
|     """Client for the OpenStack Quantum v2.0 API. | ||||
|  | ||||
|     :param string username: Username for authentication. (optional) | ||||
|     :param string password: Password for authentication. (optional) | ||||
|     :param string token: Token for authentication. (optional) | ||||
|     :param string tenant_name: Tenant name. (optional) | ||||
|     :param string auth_url: Keystone service endpoint for authorization. | ||||
|     :param string region_name: Name of a region to select when choosing an | ||||
|                                endpoint from the service catalog. | ||||
|     :param string endpoint_url: A user-supplied endpoint URL for the quantum | ||||
|                             service.  Lazy-authentication is possible for API | ||||
|                             service calls if endpoint is set at | ||||
|                             instantiation.(optional) | ||||
|     :param integer timeout: Allows customization of the timeout for client | ||||
|                             http requests. (optional) | ||||
|     :param insecure: ssl certificate validation. (optional) | ||||
|  | ||||
|     Example:: | ||||
|  | ||||
|         >>> from quantumclient.v2_0 import client | ||||
|         >>> quantum = client.Client(username=USER, | ||||
|                                      password=PASS, | ||||
|                                      tenant_name=TENANT_NAME, | ||||
|                                      auth_url=KEYSTONE_URL) | ||||
|  | ||||
|         >>> nets = quantum.list_nets() | ||||
|         ... | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     #Metadata for deserializing xml | ||||
|     _serialization_metadata = { | ||||
|         "application/xml": { | ||||
|             "attributes": { | ||||
|                 "network": ["id", "name"], | ||||
|                 "port": ["id", "mac_address"], | ||||
|                 "subnet": ["id", "prefix"]}, | ||||
|             "plurals": { | ||||
|                 "networks": "network", | ||||
|                 "ports": "port", | ||||
|                 "subnets": "subnet", }, }, } | ||||
|  | ||||
|     networks_path = "/networks" | ||||
|     network_path = "/networks/%s" | ||||
|     ports_path = "/ports" | ||||
|     port_path = "/ports/%s" | ||||
|     subnets_path = "/subnets" | ||||
|     subnet_path = "/subnets/%s" | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def list_ports(self, **_params): | ||||
|         """ | ||||
|         Fetches a list of all networks for a tenant | ||||
|         """ | ||||
|         # Pass filters in "params" argument to do_request | ||||
|         return self.get(self.ports_path, params=_params) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def show_port(self, port, **_params): | ||||
|         """ | ||||
|         Fetches information of a certain network | ||||
|         """ | ||||
|         return self.get(self.port_path % (port), params=_params) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def create_port(self, body=None): | ||||
|         """ | ||||
|         Creates a new port | ||||
|         """ | ||||
|         return self.post(self.ports_path, body=body) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def update_port(self, port, body=None): | ||||
|         """ | ||||
|         Updates a port | ||||
|         """ | ||||
|         return self.put(self.port_path % (port), body=body) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def delete_port(self, port): | ||||
|         """ | ||||
|         Deletes the specified port | ||||
|         """ | ||||
|         return self.delete(self.port_path % (port)) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def list_networks(self, **_params): | ||||
|         """ | ||||
|         Fetches a list of all networks for a tenant | ||||
|         """ | ||||
|         # Pass filters in "params" argument to do_request | ||||
|         return self.get(self.networks_path, params=_params) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def show_network(self, network, **_params): | ||||
|         """ | ||||
|         Fetches information of a certain network | ||||
|         """ | ||||
|         return self.get(self.network_path % (network), params=_params) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def create_network(self, body=None): | ||||
|         """ | ||||
|         Creates a new network | ||||
|         """ | ||||
|         return self.post(self.networks_path, body=body) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def update_network(self, network, body=None): | ||||
|         """ | ||||
|         Updates a network | ||||
|         """ | ||||
|         return self.put(self.network_path % (network), body=body) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def delete_network(self, network): | ||||
|         """ | ||||
|         Deletes the specified network | ||||
|         """ | ||||
|         return self.delete(self.network_path % (network)) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def list_subnets(self, **_params): | ||||
|         """ | ||||
|         Fetches a list of all networks for a tenant | ||||
|         """ | ||||
|         return self.get(self.subnets_path, params=_params) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def show_subnet(self, subnet, **_params): | ||||
|         """ | ||||
|         Fetches information of a certain subnet | ||||
|         """ | ||||
|         return self.get(self.subnet_path % (subnet), params=_params) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def create_subnet(self, body=None): | ||||
|         """ | ||||
|         Creates a new subnet | ||||
|         """ | ||||
|         return self.post(self.subnets_path, body=body) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def update_subnet(self, subnet, body=None): | ||||
|         """ | ||||
|         Updates a subnet | ||||
|         """ | ||||
|         return self.put(self.subnet_path % (subnet), body=body) | ||||
|  | ||||
|     @APIParamsCall | ||||
|     def delete_subnet(self, subnet): | ||||
|         """ | ||||
|         Deletes the specified subnet | ||||
|         """ | ||||
|         return self.delete(self.subnet_path % (subnet)) | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         """ Initialize a new client for the Quantum v2.0 API. """ | ||||
|         super(Client, self).__init__() | ||||
|         self.httpclient = HTTPClient(**kwargs) | ||||
|         self.version = '2.0' | ||||
|         self.format = 'json' | ||||
|         self.action_prefix = "/v%s" % (self.version) | ||||
|         self.retries = 0 | ||||
|         self.retry_interval = 1 | ||||
|  | ||||
|     def _handle_fault_response(self, status_code, response_body): | ||||
|         # Create exception with HTTP status code and message | ||||
|         error_message = response_body | ||||
|         _logger.debug("Error message: %s", error_message) | ||||
|         # Add deserialized error message to exception arguments | ||||
|         try: | ||||
|             des_error_body = Serializer().deserialize(error_message, | ||||
|                                                       self.content_type()) | ||||
|         except: | ||||
|             # If unable to deserialized body it is probably not a | ||||
|             # Quantum error | ||||
|             des_error_body = {'message': error_message} | ||||
|         # Raise the appropriate exception | ||||
|         exception_handler_v20(status_code, des_error_body) | ||||
|  | ||||
|     def do_request(self, method, action, body=None, headers=None, params=None): | ||||
|         # Add format and tenant_id | ||||
|         action += ".%s" % self.format | ||||
|         action = self.action_prefix + action | ||||
|         if type(params) is dict: | ||||
|             action += '?' + urllib.urlencode(params, doseq=1) | ||||
|         if body: | ||||
|             body = self.serialize(body) | ||||
|         self.httpclient.content_type = self.content_type() | ||||
|         resp, replybody = self.httpclient.do_request(action, method, body=body) | ||||
|         status_code = self.get_status_code(resp) | ||||
|         if status_code in (httplib.OK, | ||||
|                            httplib.CREATED, | ||||
|                            httplib.ACCEPTED, | ||||
|                            httplib.NO_CONTENT): | ||||
|             return self.deserialize(replybody, status_code) | ||||
|         else: | ||||
|             self._handle_fault_response(status_code, replybody) | ||||
|  | ||||
|     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): | ||||
|         """ | ||||
|         Serializes a dictionary with a single key (which can contain any | ||||
|         structure) into either xml or json | ||||
|         """ | ||||
|         if data is None: | ||||
|             return None | ||||
|         elif type(data) is dict: | ||||
|             return Serializer().serialize(data, self.content_type()) | ||||
|         else: | ||||
|             raise Exception("unable to serialize object of type = '%s'" % | ||||
|                             type(data)) | ||||
|  | ||||
|     def deserialize(self, data, status_code): | ||||
|         """ | ||||
|         Deserializes a an xml or json string into a dictionary | ||||
|         """ | ||||
|         if status_code == 204: | ||||
|             return data | ||||
|         return Serializer(self._serialization_metadata).deserialize( | ||||
|             data, self.content_type()) | ||||
|  | ||||
|     def content_type(self, format=None): | ||||
|         """ | ||||
|         Returns the mime-type for either 'xml' or 'json'.  Defaults to the | ||||
|         currently set format | ||||
|         """ | ||||
|         if not format: | ||||
|             format = self.format | ||||
|         return "application/%s" % (format) | ||||
|  | ||||
|     def retry_request(self, method, action, body=None, | ||||
|                       headers=None, params=None): | ||||
|         """ | ||||
|         Call do_request with the default retry configuration. Only | ||||
|         idempotent requests should retry failed connection attempts. | ||||
|  | ||||
|         :raises: ConnectionFailed if the maximum # of retries is exceeded | ||||
|         """ | ||||
|         max_attempts = self.retries + 1 | ||||
|         for i in xrange(max_attempts): | ||||
|             try: | ||||
|                 return self.do_request(method, action, body=body, | ||||
|                                        headers=headers, params=params) | ||||
|             except exceptions.ConnectionFailed: | ||||
|                 # Exception has already been logged by do_request() | ||||
|                 if i < self.retries: | ||||
|                     _logger.debug(_('Retrying connection to quantum service')) | ||||
|                     time.sleep(self.retry_interval) | ||||
|  | ||||
|         raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached")) | ||||
|  | ||||
|     def delete(self, action, body=None, headers=None, params=None): | ||||
|         return self.retry_request("DELETE", action, body=body, | ||||
|                                   headers=headers, params=params) | ||||
|  | ||||
|     def get(self, action, body=None, headers=None, params=None): | ||||
|         return self.retry_request("GET", action, body=body, | ||||
|                                   headers=headers, params=params) | ||||
|  | ||||
|     def post(self, action, body=None, headers=None, params=None): | ||||
|         # Do not retry POST requests to avoid the orphan objects problem. | ||||
|         return self.do_request("POST", action, body=body, | ||||
|                                headers=headers, params=params) | ||||
|  | ||||
|     def put(self, action, body=None, headers=None, params=None): | ||||
|         return self.retry_request("PUT", action, body=body, | ||||
|                                   headers=headers, params=params) | ||||
|  | ||||
| #if __name__ == '__main__': | ||||
| # | ||||
| #    client20 = Client(username='admin', | ||||
| #                      password='password', | ||||
| #                      auth_url='http://localhost:5000/v2.0', | ||||
| #                      tenant_name='admin') | ||||
| #    client20 = Client(token='ec796583fcad4aa690b723bc0b25270e', | ||||
| #                      endpoint_url='http://localhost:9696') | ||||
| # | ||||
| #    client20.tenant = 'default' | ||||
| #    client20.format = 'json' | ||||
| #    nets = client20.list_networks() | ||||
| #    print nets | ||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							| @@ -61,12 +61,13 @@ setuptools.setup( | ||||
|     tests_require=tests_require, | ||||
|     cmdclass=setup.get_cmdclass(), | ||||
|     include_package_data=False, | ||||
|     packages=setuptools.find_packages(exclude=['tests', 'tests.*']), | ||||
|     packages=setuptools.find_packages('.'), | ||||
|     package_data=PackageData, | ||||
|     eager_resources=EagerResources, | ||||
|     entry_points={ | ||||
|         'console_scripts': [ | ||||
|             'quantum = quantumclient.cli:main' | ||||
|             'quantum = quantumclient.cli:main', | ||||
|             'quantumv2 = quantumclient.shell:main', | ||||
|         ] | ||||
|     }, | ||||
| ) | ||||
|   | ||||
							
								
								
									
										7
									
								
								tools/pip-requires
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tools/pip-requires
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| cliff>=0.6.0 | ||||
| argparse | ||||
| httplib2 | ||||
| prettytable>=0.6.0 | ||||
| simplejson | ||||
|  | ||||
|  | ||||
| @@ -1,11 +1,15 @@ | ||||
| distribute>=0.6.24 | ||||
|  | ||||
| cliff>=0.6.0 | ||||
| argparse | ||||
| httplib2 | ||||
| prettytable>=0.6.0 | ||||
| simplejson | ||||
| mox | ||||
| nose | ||||
| nose-exclude | ||||
| nosexcover | ||||
| openstack.nose_plugin | ||||
| pep8==0.6.1 | ||||
| pep8 | ||||
| sphinx>=1.1.2 | ||||
|  | ||||
| https://github.com/openstack/quantum/zipball/master#egg=quantum | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yong Sheng Gong
					Yong Sheng Gong