Skip to content

Commit 50c46f6

Browse files
author
Yong Sheng Gong
committed
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
1 parent dd803f8 commit 50c46f6

File tree

9 files changed

+146
-43
lines changed

9 files changed

+146
-43
lines changed

quantumclient/client.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
except ImportError:
2121
import simplejson as json
2222
import logging
23+
import os
2324
import urlparse
2425
# Python 2.5 compat fix
2526
if not hasattr(urlparse, 'parse_qsl'):
@@ -33,6 +34,11 @@
3334

3435
_logger = logging.getLogger(__name__)
3536

37+
if 'QUANTUMCLIENT_DEBUG' in os.environ and os.environ['QUANTUMCLIENT_DEBUG']:
38+
ch = logging.StreamHandler()
39+
_logger.setLevel(logging.DEBUG)
40+
_logger.addHandler(ch)
41+
3642

3743
class ServiceCatalog(object):
3844
"""Helper methods for dealing with a Keystone Service Catalog."""
@@ -86,7 +92,8 @@ class HTTPClient(httplib2.Http):
8692
def __init__(self, username=None, tenant_name=None,
8793
password=None, auth_url=None,
8894
token=None, region_name=None, timeout=None,
89-
endpoint_url=None, insecure=False, **kwargs):
95+
endpoint_url=None, insecure=False,
96+
auth_strategy='keystone', **kwargs):
9097
super(HTTPClient, self).__init__(timeout=timeout)
9198
self.username = username
9299
self.tenant_name = tenant_name
@@ -96,6 +103,7 @@ def __init__(self, username=None, tenant_name=None,
96103
self.auth_token = token
97104
self.content_type = 'application/json'
98105
self.endpoint_url = endpoint_url
106+
self.auth_strategy = auth_strategy
99107
# httplib2 overrides
100108
self.force_exception_to_status_code = True
101109
self.disable_ssl_certificate_validation = insecure
@@ -126,19 +134,21 @@ def _cs_request(self, *args, **kwargs):
126134
return resp, body
127135

128136
def do_request(self, url, method, **kwargs):
129-
if not self.endpoint_url or not self.auth_token:
137+
if not self.endpoint_url:
130138
self.authenticate()
131139

132140
# Perform the request once. If we get a 401 back then it
133141
# might be because the auth token expired, so try to
134142
# re-authenticate and try again. If it still fails, bail.
135143
try:
136-
kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
144+
if self.auth_token:
145+
kwargs.setdefault('headers', {})
146+
kwargs['headers']['X-Auth-Token'] = self.auth_token
137147
resp, body = self._cs_request(self.endpoint_url + url, method,
138148
**kwargs)
139149
return resp, body
140150
except exceptions.Unauthorized as ex:
141-
if not self.endpoint_url or not self.auth_token:
151+
if not self.endpoint_url:
142152
self.authenticate()
143153
resp, body = self._cs_request(
144154
self.management_url + url, method, **kwargs)
@@ -161,6 +171,8 @@ def _extract_service_catalog(self, body):
161171
endpoint_type='adminURL')
162172

163173
def authenticate(self):
174+
if self.auth_strategy != 'keystone':
175+
raise exceptions.Unauthorized(message='unknown auth strategy')
164176
body = {'auth': {'passwordCredentials':
165177
{'username': self.username,
166178
'password': self.password, },

quantumclient/common/clientmanager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, token=None, url=None,
5353
username=None, password=None,
5454
region_name=None,
5555
api_version=None,
56+
auth_strategy=None
5657
):
5758
self._token = token
5859
self._url = url
@@ -64,6 +65,7 @@ def __init__(self, token=None, url=None,
6465
self._region_name = region_name
6566
self._api_version = api_version
6667
self._service_catalog = None
68+
self._auth_strategy = auth_strategy
6769
return
6870

6971
def initialize(self):

quantumclient/common/command.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ def run(self, parsed_args):
3333
return
3434
else:
3535
return super(OpenStackCommand, self).run(parsed_args)
36+
37+
def get_data(self, parsed_args):
38+
pass
39+
40+
def take_action(self, parsed_args):
41+
return self.get_data(parsed_args)

quantumclient/common/utils.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ def to_primitive(value):
6262
return value
6363

6464

65-
def dumps(value):
65+
def dumps(value, indent=None):
6666
try:
67-
return json.dumps(value)
67+
return json.dumps(value, indent=indent)
6868
except TypeError:
6969
pass
7070
return json.dumps(to_primitive(value))
@@ -126,17 +126,28 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
126126
data = item[field_name]
127127
else:
128128
data = getattr(item, field_name, '')
129+
if data is None:
130+
data = ''
129131
row.append(data)
130132
return tuple(row)
131133

132134

133-
def __str2bool(strbool):
135+
def str2bool(strbool):
134136
if strbool is None:
135137
return None
136138
else:
137139
return strbool.lower() == 'true'
138140

139141

142+
def str2dict(strdict):
143+
'''@param strdict: key1=value1,key2=value2'''
144+
_info = {}
145+
for kv_str in strdict.split(","):
146+
k, v = kv_str.split("=", 1)
147+
_info.update({k: v})
148+
return _info
149+
150+
140151
def http_log(_logger, args, kwargs, resp, body):
141152
if not _logger.isEnabledFor(logging.DEBUG):
142153
return

quantumclient/quantum/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,6 @@ def make_client(instance):
6464
region_name=instance._region_name,
6565
auth_url=instance._auth_url,
6666
endpoint_url=url,
67-
token=instance._token)
67+
token=instance._token,
68+
auth_strategy=instance._auth_strategy)
6869
return client

quantumclient/quantum/v2_0/__init__.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ def parse_args_to_dict(values_specs):
7070
current_arg = None
7171
_values_specs = []
7272
_value_number = 0
73+
_list_flag = False
7374
current_item = None
7475
for _item in values_specs:
7576
if _item.startswith('--'):
7677
if current_arg is not None:
77-
if _value_number > 1:
78+
if _value_number > 1 or _list_flag:
7879
current_arg.update({'nargs': '+'})
7980
elif _value_number == 0:
8081
current_arg.update({'action': 'store_true'})
@@ -93,12 +94,16 @@ def parse_args_to_dict(values_specs):
9394
_type_str = _item.split('=', 2)[1]
9495
current_arg.update({'type': eval(_type_str)})
9596
if _type_str == 'bool':
96-
current_arg.update({'type': utils.__str2bool})
97+
current_arg.update({'type': utils.str2bool})
98+
elif _type_str == 'dict':
99+
current_arg.update({'type': utils.str2dict})
97100
continue
98101
else:
99102
raise exceptions.CommandError(
100103
"invalid values_specs %s" % ' '.join(values_specs))
101-
104+
elif _item == 'list=true':
105+
_list_flag = True
106+
continue
102107
if not _item.startswith('--'):
103108
if not current_item or '=' in current_item:
104109
raise exceptions.CommandError(
@@ -110,9 +115,10 @@ def parse_args_to_dict(values_specs):
110115
_value_number = 1
111116
else:
112117
_value_number = 0
118+
_list_flag = False
113119
_values_specs.append(_item)
114120
if current_arg is not None:
115-
if _value_number > 1:
121+
if _value_number > 1 or _list_flag:
116122
current_arg.update({'nargs': '+'})
117123
elif _value_number == 0:
118124
current_arg.update({'action': 'store_true'})
@@ -188,6 +194,19 @@ def get_data(self, parsed_args):
188194
print >>self.app.stdout, _('Created a new %s:' % self.resource)
189195
else:
190196
info = {'': ''}
197+
for k, v in info.iteritems():
198+
if isinstance(v, list):
199+
value = ""
200+
for _item in v:
201+
if value:
202+
value += "\n"
203+
if isinstance(_item, dict):
204+
value += utils.dumps(_item)
205+
else:
206+
value += str(_item)
207+
info[k] = value
208+
elif v is None:
209+
info[k] = ''
191210
return zip(*sorted(info.iteritems()))
192211

193212

@@ -334,6 +353,19 @@ def get_data(self, parsed_args):
334353
"show_%s" % self.resource)
335354
data = obj_showor(parsed_args.id, **params)
336355
if self.resource in data:
356+
for k, v in data[self.resource].iteritems():
357+
if isinstance(v, list):
358+
value = ""
359+
for _item in v:
360+
if value:
361+
value += "\n"
362+
if isinstance(_item, dict):
363+
value += utils.dumps(_item)
364+
else:
365+
value += str(_item)
366+
data[self.resource][k] = value
367+
elif v is None:
368+
data[self.resource][k] = ''
337369
return zip(*sorted(data[self.resource].iteritems()))
338370
else:
339371
return None

quantumclient/quantum/v2_0/port.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import logging
1919

20+
from quantumclient import utils
2021
from quantumclient.quantum.v2_0 import CreateCommand
2122
from quantumclient.quantum.v2_0 import DeleteCommand
2223
from quantumclient.quantum.v2_0 import ListCommand
@@ -26,7 +27,7 @@
2627

2728
def _format_fixed_ips(port):
2829
try:
29-
return '\n'.join(port['fixed_ips'])
30+
return '\n'.join([utils.dumps(ip) for ip in port['fixed_ips']])
3031
except Exception:
3132
return ''
3233

@@ -74,6 +75,12 @@ def add_known_arguments(self, parser):
7475
parser.add_argument(
7576
'--device-id',
7677
help='device id of this port')
78+
parser.add_argument(
79+
'--fixed-ip',
80+
action='append',
81+
help='desired Ip for this port: '
82+
'subnet_id=<id>,ip_address=<ip>, '
83+
'can be repeated')
7784
parser.add_argument(
7885
'network_id',
7986
help='Network id of this port belongs to')
@@ -87,6 +94,12 @@ def args2body(self, parsed_args):
8794
body['port'].update({'device_id': parsed_args.device_id})
8895
if parsed_args.tenant_id:
8996
body['port'].update({'tenant_id': parsed_args.tenant_id})
97+
ips = []
98+
if parsed_args.fixed_ip:
99+
for ip_spec in parsed_args.fixed_ip:
100+
ips.append(utils.str2dict(ip_spec))
101+
if ips:
102+
body['port'].update({'fixed_ips': ips})
90103
return body
91104

92105

quantumclient/shell.py

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ def build_option_parser(self, description, version):
197197
action='store_true',
198198
help='show tracebacks on errors', )
199199
# Global arguments
200+
parser.add_argument(
201+
'--os-auth-strategy', metavar='<auth-strategy>',
202+
default=env('OS_AUTH_STRATEGY', default='keystone'),
203+
help='Authentication strategy (Env: OS_AUTH_STRATEGY'
204+
', default keystone). For now, any other value will'
205+
' disable the authentication')
206+
200207
parser.add_argument(
201208
'--os-auth-url', metavar='<auth-url>',
202209
default=env('OS_AUTH_URL'),
@@ -272,41 +279,46 @@ def authenticate_user(self):
272279
"""Make sure the user has provided all of the authentication
273280
info we need.
274281
"""
282+
if self.options.os_auth_strategy == 'keystone':
283+
if self.options.os_token or self.options.os_url:
284+
# Token flow auth takes priority
285+
if not self.options.os_token:
286+
raise exc.CommandError(
287+
"You must provide a token via"
288+
" either --os-token or env[OS_TOKEN]")
289+
290+
if not self.options.os_url:
291+
raise exc.CommandError(
292+
"You must provide a service URL via"
293+
" either --os-url or env[OS_URL]")
275294

276-
if self.options.os_token or self.options.os_url:
277-
# Token flow auth takes priority
278-
if not self.options.os_token:
279-
raise exc.CommandError(
280-
"You must provide a token via"
281-
" either --os-token or env[OS_TOKEN]")
282-
295+
else:
296+
# Validate password flow auth
297+
if not self.options.os_username:
298+
raise exc.CommandError(
299+
"You must provide a username via"
300+
" either --os-username or env[OS_USERNAME]")
301+
302+
if not self.options.os_password:
303+
raise exc.CommandError(
304+
"You must provide a password via"
305+
" either --os-password or env[OS_PASSWORD]")
306+
307+
if not (self.options.os_tenant_name):
308+
raise exc.CommandError(
309+
"You must provide a tenant_name via"
310+
" either --os-tenant-name or via env[OS_TENANT_NAME]")
311+
312+
if not self.options.os_auth_url:
313+
raise exc.CommandError(
314+
"You must provide an auth url via"
315+
" either --os-auth-url or via env[OS_AUTH_URL]")
316+
else: # not keystone
283317
if not self.options.os_url:
284318
raise exc.CommandError(
285319
"You must provide a service URL via"
286320
" either --os-url or env[OS_URL]")
287321

288-
else:
289-
# Validate password flow auth
290-
if not self.options.os_username:
291-
raise exc.CommandError(
292-
"You must provide a username via"
293-
" either --os-username or env[OS_USERNAME]")
294-
295-
if not self.options.os_password:
296-
raise exc.CommandError(
297-
"You must provide a password via"
298-
" either --os-password or env[OS_PASSWORD]")
299-
300-
if not (self.options.os_tenant_name):
301-
raise exc.CommandError(
302-
"You must provide a tenant_name via"
303-
" either --os-tenant-name or via env[OS_TENANT_NAME]")
304-
305-
if not self.options.os_auth_url:
306-
raise exc.CommandError(
307-
"You must provide an auth url via"
308-
" either --os-auth-url or via env[OS_AUTH_URL]")
309-
310322
self.client_manager = clientmanager.ClientManager(
311323
token=self.options.os_token,
312324
url=self.options.os_url,
@@ -315,7 +327,8 @@ def authenticate_user(self):
315327
username=self.options.os_username,
316328
password=self.options.os_password,
317329
region_name=self.options.os_region_name,
318-
api_version=self.api_version, )
330+
api_version=self.api_version,
331+
auth_strategy=self.options.os_auth_strategy, )
319332
return
320333

321334
def initialize_app(self, argv):

quantumclient/tests/unit/test_casual_args.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,16 @@ def test_arg(self):
5757
_specs = ['--tag=t', '--arg1', 'value1']
5858
self.assertEqual('value1',
5959
quantumV20.parse_args_to_dict(_specs)['arg1'])
60+
61+
def test_dict_arg(self):
62+
_specs = ['--tag=t', '--arg1', 'type=dict', 'key1=value1,key2=value2']
63+
arg1 = quantumV20.parse_args_to_dict(_specs)['arg1']
64+
self.assertEqual('value1', arg1['key1'])
65+
self.assertEqual('value2', arg1['key2'])
66+
67+
def test_list_of_dict_arg(self):
68+
_specs = ['--tag=t', '--arg1', 'type=dict',
69+
'list=true', 'key1=value1,key2=value2']
70+
arg1 = quantumV20.parse_args_to_dict(_specs)['arg1']
71+
self.assertEqual('value1', arg1[0]['key1'])
72+
self.assertEqual('value2', arg1[0]['key2'])

0 commit comments

Comments
 (0)