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:
parent
dd803f8e26
commit
50c46f61d1
@ -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'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user