diff --git a/quantumclient/client.py b/quantumclient/client.py index b892d303e..03c590a21 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -20,6 +20,7 @@ try: except ImportError: import simplejson as json import logging +import os import urlparse # Python 2.5 compat fix if not hasattr(urlparse, 'parse_qsl'): @@ -33,6 +34,11 @@ from quantumclient.common import utils _logger = logging.getLogger(__name__) +if 'QUANTUMCLIENT_DEBUG' in os.environ and os.environ['QUANTUMCLIENT_DEBUG']: + ch = logging.StreamHandler() + _logger.setLevel(logging.DEBUG) + _logger.addHandler(ch) + class ServiceCatalog(object): """Helper methods for dealing with a Keystone Service Catalog.""" @@ -86,7 +92,8 @@ class HTTPClient(httplib2.Http): 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): + endpoint_url=None, insecure=False, + auth_strategy='keystone', **kwargs): super(HTTPClient, self).__init__(timeout=timeout) self.username = username self.tenant_name = tenant_name @@ -96,6 +103,7 @@ class HTTPClient(httplib2.Http): self.auth_token = token self.content_type = 'application/json' self.endpoint_url = endpoint_url + self.auth_strategy = auth_strategy # httplib2 overrides self.force_exception_to_status_code = True self.disable_ssl_certificate_validation = insecure @@ -126,19 +134,21 @@ class HTTPClient(httplib2.Http): return resp, body def do_request(self, url, method, **kwargs): - if not self.endpoint_url or not self.auth_token: + if not self.endpoint_url: 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 + if self.auth_token: + kwargs.setdefault('headers', {}) + kwargs['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: + if not self.endpoint_url: self.authenticate() resp, body = self._cs_request( self.management_url + url, method, **kwargs) @@ -161,6 +171,8 @@ class HTTPClient(httplib2.Http): endpoint_type='adminURL') def authenticate(self): + if self.auth_strategy != 'keystone': + raise exceptions.Unauthorized(message='unknown auth strategy') body = {'auth': {'passwordCredentials': {'username': self.username, 'password': self.password, }, diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index ee2fcf3d2..1c1f1acd0 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -53,6 +53,7 @@ class ClientManager(object): username=None, password=None, region_name=None, api_version=None, + auth_strategy=None ): self._token = token self._url = url @@ -64,6 +65,7 @@ class ClientManager(object): self._region_name = region_name self._api_version = api_version self._service_catalog = None + self._auth_strategy = auth_strategy return def initialize(self): diff --git a/quantumclient/common/command.py b/quantumclient/common/command.py index 405f65556..f2706d019 100644 --- a/quantumclient/common/command.py +++ b/quantumclient/common/command.py @@ -33,3 +33,9 @@ class OpenStackCommand(Command): return else: return super(OpenStackCommand, self).run(parsed_args) + + def get_data(self, parsed_args): + pass + + def take_action(self, parsed_args): + return self.get_data(parsed_args) diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index 4044ae8af..bed02e010 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -62,9 +62,9 @@ def to_primitive(value): return value -def dumps(value): +def dumps(value, indent=None): try: - return json.dumps(value) + return json.dumps(value, indent=indent) except TypeError: pass return json.dumps(to_primitive(value)) @@ -126,17 +126,28 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): data = item[field_name] else: data = getattr(item, field_name, '') + if data is None: + data = '' row.append(data) return tuple(row) -def __str2bool(strbool): +def str2bool(strbool): if strbool is None: return None else: return strbool.lower() == 'true' +def str2dict(strdict): + '''@param strdict: key1=value1,key2=value2''' + _info = {} + for kv_str in strdict.split(","): + k, v = kv_str.split("=", 1) + _info.update({k: v}) + return _info + + def http_log(_logger, args, kwargs, resp, body): if not _logger.isEnabledFor(logging.DEBUG): return diff --git a/quantumclient/quantum/client.py b/quantumclient/quantum/client.py index e2c059fc0..7aa1f1589 100644 --- a/quantumclient/quantum/client.py +++ b/quantumclient/quantum/client.py @@ -64,5 +64,6 @@ def make_client(instance): region_name=instance._region_name, auth_url=instance._auth_url, endpoint_url=url, - token=instance._token) + token=instance._token, + auth_strategy=instance._auth_strategy) return client diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 89c1c7a63..e1402af85 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -70,11 +70,12 @@ def parse_args_to_dict(values_specs): current_arg = None _values_specs = [] _value_number = 0 + _list_flag = False current_item = None for _item in values_specs: if _item.startswith('--'): if current_arg is not None: - if _value_number > 1: + if _value_number > 1 or _list_flag: current_arg.update({'nargs': '+'}) elif _value_number == 0: current_arg.update({'action': 'store_true'}) @@ -93,12 +94,16 @@ def parse_args_to_dict(values_specs): _type_str = _item.split('=', 2)[1] current_arg.update({'type': eval(_type_str)}) if _type_str == 'bool': - current_arg.update({'type': utils.__str2bool}) + current_arg.update({'type': utils.str2bool}) + elif _type_str == 'dict': + current_arg.update({'type': utils.str2dict}) continue else: raise exceptions.CommandError( "invalid values_specs %s" % ' '.join(values_specs)) - + elif _item == 'list=true': + _list_flag = True + continue if not _item.startswith('--'): if not current_item or '=' in current_item: raise exceptions.CommandError( @@ -110,9 +115,10 @@ def parse_args_to_dict(values_specs): _value_number = 1 else: _value_number = 0 + _list_flag = False _values_specs.append(_item) if current_arg is not None: - if _value_number > 1: + if _value_number > 1 or _list_flag: current_arg.update({'nargs': '+'}) elif _value_number == 0: current_arg.update({'action': 'store_true'}) @@ -188,6 +194,19 @@ class CreateCommand(QuantumCommand, show.ShowOne): print >>self.app.stdout, _('Created a new %s:' % self.resource) else: info = {'': ''} + for k, v in info.iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + info[k] = value + elif v is None: + info[k] = '' return zip(*sorted(info.iteritems())) @@ -334,6 +353,19 @@ class ShowCommand(QuantumCommand, show.ShowOne): "show_%s" % self.resource) data = obj_showor(parsed_args.id, **params) if self.resource in data: + for k, v in data[self.resource].iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' return zip(*sorted(data[self.resource].iteritems())) else: return None diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 9b8af129e..bd213f8ae 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -17,6 +17,7 @@ import logging +from quantumclient import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -26,7 +27,7 @@ from quantumclient.quantum.v2_0 import UpdateCommand def _format_fixed_ips(port): try: - return '\n'.join(port['fixed_ips']) + return '\n'.join([utils.dumps(ip) for ip in port['fixed_ips']]) except Exception: return '' @@ -74,6 +75,12 @@ class CreatePort(CreateCommand): parser.add_argument( '--device-id', help='device id of this port') + parser.add_argument( + '--fixed-ip', + action='append', + help='desired Ip for this port: ' + 'subnet_id=<id>,ip_address=<ip>, ' + 'can be repeated') parser.add_argument( 'network_id', help='Network id of this port belongs to') @@ -87,6 +94,12 @@ class CreatePort(CreateCommand): body['port'].update({'device_id': parsed_args.device_id}) if parsed_args.tenant_id: body['port'].update({'tenant_id': parsed_args.tenant_id}) + ips = [] + if parsed_args.fixed_ip: + for ip_spec in parsed_args.fixed_ip: + ips.append(utils.str2dict(ip_spec)) + if ips: + body['port'].update({'fixed_ips': ips}) return body diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 975a1459f..a737b4b66 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -197,6 +197,13 @@ class QuantumShell(App): action='store_true', help='show tracebacks on errors', ) # Global arguments + parser.add_argument( + '--os-auth-strategy', metavar='<auth-strategy>', + default=env('OS_AUTH_STRATEGY', default='keystone'), + help='Authentication strategy (Env: OS_AUTH_STRATEGY' + ', default keystone). For now, any other value will' + ' disable the authentication') + parser.add_argument( '--os-auth-url', metavar='<auth-url>', default=env('OS_AUTH_URL'), @@ -272,41 +279,46 @@ class QuantumShell(App): """Make sure the user has provided all of the authentication info we need. """ + if self.options.os_auth_strategy == 'keystone': + 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 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]") + else: # not keystone 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, @@ -315,7 +327,8 @@ class QuantumShell(App): username=self.options.os_username, password=self.options.os_password, region_name=self.options.os_region_name, - api_version=self.api_version, ) + api_version=self.api_version, + auth_strategy=self.options.os_auth_strategy, ) return def initialize_app(self, argv): diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index 695aa0aff..4192ff81b 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -57,3 +57,16 @@ class CLITestArgs(unittest.TestCase): _specs = ['--tag=t', '--arg1', 'value1'] self.assertEqual('value1', quantumV20.parse_args_to_dict(_specs)['arg1']) + + def test_dict_arg(self): + _specs = ['--tag=t', '--arg1', 'type=dict', 'key1=value1,key2=value2'] + arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + self.assertEqual('value1', arg1['key1']) + self.assertEqual('value2', arg1['key2']) + + def test_list_of_dict_arg(self): + _specs = ['--tag=t', '--arg1', 'type=dict', + 'list=true', 'key1=value1,key2=value2'] + arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + self.assertEqual('value1', arg1[0]['key1']) + self.assertEqual('value2', arg1[0]['key2'])