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:
Yong Sheng Gong 2012-06-24 16:11:11 +08:00
parent dd803f8e26
commit 50c46f61d1
9 changed files with 145 additions and 42 deletions

View File

@ -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, },

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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'])