add --fixed-ip argument to create port
add --fixed-ip argument to create port and add list and dict type for unknow option now we can use known option feature: quantumv2 create_port --fixed-ip subnet_id=<id>,ip_address=<ip> --fixed-ip subnet_id=<id>, ip_address=<ip2> network_id or unknown option feature: one ip: quantumv2 create_port network_id --fixed_ips type=dict list=true subnet_id=<id>,ip_address=<ip> two ips: quantumv2 create_port network_id --fixed_ips type=dict subnet_id=<id>,ip_address=<ip> subnet_id=<id>,ip_address=<ip2> to create port Please download: https://review.openstack.org/#/c/8794/4 and set core_plugin = quantum.db.db_base_plugin_v2.QuantumDbPluginV2 on quantum server side Patch 2: support cliff 1.0 Patch 3: support specify auth strategy, for now, any other auth strategy than keystone will disable auth, format port output Patch 4: format None as '' when outputing, deal with list of dict, add QUANTUMCLIENT_DEBUG env to enable http req/resp print, which is helpful for testing nova integration Patch 5: fix interactive mode, and initialize_app problem Change-Id: I693848c75055d1947862d55f4b538c1dfb1e86db
This commit is contained in:
		@@ -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, },
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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'])
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user