2014-01-28 17:02:25 -08:00
|
|
|
# Copyright 2011, VMware, Inc.
|
2012-04-11 02:20:57 -07:00
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
|
|
# not use this file except in compliance with the License. You may obtain
|
|
|
|
# a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
# License for the specific language governing permissions and limitations
|
|
|
|
# under the License.
|
|
|
|
#
|
|
|
|
# Borrowed from nova code base, more utilities will be added/borrowed as and
|
|
|
|
# when needed.
|
|
|
|
|
|
|
|
"""Utilities and helper functions."""
|
|
|
|
|
Utility method for boolean argument
In the recent commit, True/False of boolean argument
became case-insensitve and this code is copy-and-paste'ed
in several places.
Now there are several number of patches which add explicit
arguments for *-update or *-list and the code of boolean
argumetns like below will be used more than now.
This commit add a utility method to register such boolean opt.
parser.add_argument(
'--enabled',
dest='enabled', metavar='{True,False}',
choices=['True', 'true', 'False', 'false'],
help=_('Whether to enable or disable this rule.'),
default=argparse.SUPPRESS)
Change-Id: I9575eeef32154a8b92589c2cc7889803216bddb2
2015-01-28 03:02:46 +09:00
|
|
|
import argparse
|
2015-12-06 16:55:24 +09:00
|
|
|
import functools
|
2016-03-29 10:11:35 +00:00
|
|
|
import hashlib
|
2012-05-18 09:02:29 +08:00
|
|
|
import logging
|
2012-04-11 02:20:57 -07:00
|
|
|
import os
|
2012-05-18 09:02:29 +08:00
|
|
|
|
2015-05-06 19:39:35 +00:00
|
|
|
from oslo_utils import encodeutils
|
|
|
|
from oslo_utils import importutils
|
2014-07-28 14:02:57 +04:00
|
|
|
import six
|
|
|
|
|
2015-11-28 09:16:42 +09:00
|
|
|
from neutronclient._i18n import _
|
2013-07-02 18:44:42 -04:00
|
|
|
from neutronclient.common import exceptions
|
2012-04-11 02:20:57 -07:00
|
|
|
|
2016-03-29 10:11:35 +00:00
|
|
|
SENSITIVE_HEADERS = ('X-Auth-Token',)
|
|
|
|
|
2012-04-11 02:20:57 -07:00
|
|
|
|
|
|
|
def env(*vars, **kwargs):
|
2013-06-10 22:04:56 +09:00
|
|
|
"""Returns the first environment variable set.
|
|
|
|
|
2014-08-21 15:45:10 +03:00
|
|
|
If none are non-empty, defaults to '' or keyword arg default.
|
2012-04-11 02:20:57 -07:00
|
|
|
"""
|
|
|
|
for v in vars:
|
|
|
|
value = os.environ.get(v)
|
|
|
|
if value:
|
|
|
|
return value
|
|
|
|
return kwargs.get('default', '')
|
|
|
|
|
|
|
|
|
2015-10-22 04:06:25 +00:00
|
|
|
def convert_to_uppercase(string):
|
|
|
|
return string.upper()
|
|
|
|
|
|
|
|
|
2016-01-05 11:45:51 +09:00
|
|
|
def convert_to_lowercase(string):
|
|
|
|
return string.lower()
|
|
|
|
|
|
|
|
|
2012-05-18 09:02:29 +08:00
|
|
|
def get_client_class(api_name, version, version_map):
|
2014-08-21 15:45:10 +03:00
|
|
|
"""Returns the client class for the requested API version.
|
2012-05-18 09:02:29 +08:00
|
|
|
|
|
|
|
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
|
|
|
:param version: the requested API version
|
|
|
|
:param version_map: a dict of client classes keyed by version
|
|
|
|
:rtype: a client class for the requested API version
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
client_path = version_map[str(version)]
|
|
|
|
except (KeyError, ValueError):
|
2013-11-15 23:18:59 -05:00
|
|
|
msg = _("Invalid %(api_name)s client version '%(version)s'. must be "
|
|
|
|
"one of: %(map_keys)s")
|
|
|
|
msg = msg % {'api_name': api_name, 'version': version,
|
|
|
|
'map_keys': ', '.join(version_map.keys())}
|
2012-05-18 09:02:29 +08:00
|
|
|
raise exceptions.UnsupportedVersion(msg)
|
|
|
|
|
2014-09-03 22:01:56 -07:00
|
|
|
return importutils.import_class(client_path)
|
2012-05-18 09:02:29 +08:00
|
|
|
|
|
|
|
|
2014-08-12 14:07:28 +10:00
|
|
|
def get_item_properties(item, fields, mixed_case_fields=(), formatters=None):
|
2012-05-18 09:02:29 +08:00
|
|
|
"""Return a tuple containing the item properties.
|
|
|
|
|
|
|
|
:param item: a single item resource (e.g. Server, Tenant, etc)
|
|
|
|
:param fields: tuple of strings with the desired field names
|
|
|
|
:param mixed_case_fields: tuple of field names to preserve case
|
|
|
|
:param formatters: dictionary mapping field names to callables
|
|
|
|
to format the values
|
|
|
|
"""
|
2014-08-12 14:07:28 +10:00
|
|
|
if formatters is None:
|
|
|
|
formatters = {}
|
|
|
|
|
2012-05-18 09:02:29 +08:00
|
|
|
row = []
|
|
|
|
|
|
|
|
for field in fields:
|
|
|
|
if field in formatters:
|
|
|
|
row.append(formatters[field](item))
|
|
|
|
else:
|
|
|
|
if field in mixed_case_fields:
|
|
|
|
field_name = field.replace(' ', '_')
|
|
|
|
else:
|
|
|
|
field_name = field.lower().replace(' ', '_')
|
|
|
|
if not hasattr(item, field_name) and isinstance(item, dict):
|
|
|
|
data = item[field_name]
|
|
|
|
else:
|
|
|
|
data = getattr(item, field_name, '')
|
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
2012-06-24 16:11:11 +08:00
|
|
|
if data is None:
|
|
|
|
data = ''
|
2012-05-18 09:02:29 +08:00
|
|
|
row.append(data)
|
|
|
|
return tuple(row)
|
|
|
|
|
|
|
|
|
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
2012-06-24 16:11:11 +08:00
|
|
|
def str2bool(strbool):
|
2012-05-18 09:02:29 +08:00
|
|
|
if strbool is None:
|
|
|
|
return None
|
2014-08-21 16:04:21 +03:00
|
|
|
return strbool.lower() == 'true'
|
2012-05-18 09:02:29 +08:00
|
|
|
|
|
|
|
|
2015-12-06 16:55:24 +09:00
|
|
|
def str2dict(strdict, required_keys=None, optional_keys=None):
|
2014-08-21 16:04:21 +03:00
|
|
|
"""Convert key1=value1,key2=value2,... string into dictionary.
|
|
|
|
|
2015-12-06 16:55:24 +09:00
|
|
|
:param strdict: string in the form of key1=value1,key2=value2
|
|
|
|
:param required_keys: list of required keys. All keys in this list must be
|
|
|
|
specified. Otherwise ArgumentTypeError will be raised.
|
|
|
|
If this parameter is unspecified, no required key check
|
|
|
|
will be done.
|
|
|
|
:param optional_keys: list of optional keys.
|
|
|
|
This parameter is used for valid key check.
|
|
|
|
When at least one of required_keys and optional_keys,
|
|
|
|
a key must be a member of either of required_keys or
|
|
|
|
optional_keys. Otherwise, ArgumentTypeError will be
|
|
|
|
raised. When both required_keys and optional_keys are
|
|
|
|
unspecified, no valid key check will be done.
|
2014-08-21 16:04:21 +03:00
|
|
|
"""
|
2015-05-31 19:42:46 +02:00
|
|
|
result = {}
|
|
|
|
if strdict:
|
|
|
|
for kv in strdict.split(','):
|
|
|
|
key, sep, value = kv.partition('=')
|
|
|
|
if not sep:
|
|
|
|
msg = _("invalid key-value '%s', expected format: key=value")
|
|
|
|
raise argparse.ArgumentTypeError(msg % kv)
|
|
|
|
result[key] = value
|
2015-12-06 16:55:24 +09:00
|
|
|
valid_keys = set(required_keys or []) | set(optional_keys or [])
|
|
|
|
if valid_keys:
|
|
|
|
invalid_keys = [k for k in result if k not in valid_keys]
|
|
|
|
if invalid_keys:
|
|
|
|
msg = _("Invalid key(s) '%(invalid_keys)s' specified. "
|
|
|
|
"Valid key(s): '%(valid_keys)s'.")
|
|
|
|
raise argparse.ArgumentTypeError(
|
|
|
|
msg % {'invalid_keys': ', '.join(sorted(invalid_keys)),
|
|
|
|
'valid_keys': ', '.join(sorted(valid_keys))})
|
|
|
|
if required_keys:
|
|
|
|
not_found_keys = [k for k in required_keys if k not in result]
|
|
|
|
if not_found_keys:
|
|
|
|
msg = _("Required key(s) '%s' not specified.")
|
|
|
|
raise argparse.ArgumentTypeError(msg % ', '.join(not_found_keys))
|
2015-05-31 19:42:46 +02:00
|
|
|
return result
|
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
2012-06-24 16:11:11 +08:00
|
|
|
|
|
|
|
|
2015-12-06 16:55:24 +09:00
|
|
|
def str2dict_type(optional_keys=None, required_keys=None):
|
|
|
|
return functools.partial(str2dict,
|
|
|
|
optional_keys=optional_keys,
|
|
|
|
required_keys=required_keys)
|
|
|
|
|
|
|
|
|
2012-11-09 19:36:30 +08:00
|
|
|
def http_log_req(_logger, args, kwargs):
|
|
|
|
if not _logger.isEnabledFor(logging.DEBUG):
|
|
|
|
return
|
|
|
|
|
|
|
|
string_parts = ['curl -i']
|
|
|
|
for element in args:
|
|
|
|
if element in ('GET', 'POST', 'DELETE', 'PUT'):
|
|
|
|
string_parts.append(' -X %s' % element)
|
|
|
|
else:
|
|
|
|
string_parts.append(' %s' % element)
|
|
|
|
|
2016-03-29 10:11:35 +00:00
|
|
|
for (key, value) in six.iteritems(kwargs['headers']):
|
|
|
|
if key in SENSITIVE_HEADERS:
|
|
|
|
v = value.encode('utf-8')
|
|
|
|
h = hashlib.sha1(v)
|
|
|
|
d = h.hexdigest()
|
|
|
|
value = "{SHA1}%s" % d
|
|
|
|
header = ' -H "%s: %s"' % (key, value)
|
2012-11-09 19:36:30 +08:00
|
|
|
string_parts.append(header)
|
|
|
|
|
|
|
|
if 'body' in kwargs and kwargs['body']:
|
|
|
|
string_parts.append(" -d '%s'" % (kwargs['body']))
|
2014-10-16 19:23:53 +09:00
|
|
|
req = encodeutils.safe_encode("".join(string_parts))
|
2015-08-10 12:59:58 +03:00
|
|
|
_logger.debug("REQ: %s", req)
|
2012-05-18 09:02:29 +08:00
|
|
|
|
|
|
|
|
2012-11-09 19:36:30 +08:00
|
|
|
def http_log_resp(_logger, resp, body):
|
|
|
|
if not _logger.isEnabledFor(logging.DEBUG):
|
|
|
|
return
|
2015-08-10 12:59:58 +03:00
|
|
|
_logger.debug("RESP: %(code)s %(headers)s %(body)s",
|
2014-04-15 13:46:12 -07:00
|
|
|
{'code': resp.status_code,
|
|
|
|
'headers': resp.headers,
|
|
|
|
'body': body})
|
2013-04-01 13:27:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
def _safe_encode_without_obj(data):
|
2014-07-28 14:02:57 +04:00
|
|
|
if isinstance(data, six.string_types):
|
2014-10-16 19:23:53 +09:00
|
|
|
return encodeutils.safe_encode(data)
|
2013-04-01 13:27:28 +08:00
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
def safe_encode_list(data):
|
2014-07-28 14:02:57 +04:00
|
|
|
return list(map(_safe_encode_without_obj, data))
|
2013-04-01 13:27:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
def safe_encode_dict(data):
|
2014-02-19 19:34:30 +01:00
|
|
|
def _encode_item(item):
|
|
|
|
k, v = item
|
2013-04-01 13:27:28 +08:00
|
|
|
if isinstance(v, list):
|
|
|
|
return (k, safe_encode_list(v))
|
|
|
|
elif isinstance(v, dict):
|
|
|
|
return (k, safe_encode_dict(v))
|
|
|
|
return (k, _safe_encode_without_obj(v))
|
|
|
|
|
2014-07-28 14:02:57 +04:00
|
|
|
return dict(list(map(_encode_item, data.items())))
|
Utility method for boolean argument
In the recent commit, True/False of boolean argument
became case-insensitve and this code is copy-and-paste'ed
in several places.
Now there are several number of patches which add explicit
arguments for *-update or *-list and the code of boolean
argumetns like below will be used more than now.
This commit add a utility method to register such boolean opt.
parser.add_argument(
'--enabled',
dest='enabled', metavar='{True,False}',
choices=['True', 'true', 'False', 'false'],
help=_('Whether to enable or disable this rule.'),
default=argparse.SUPPRESS)
Change-Id: I9575eeef32154a8b92589c2cc7889803216bddb2
2015-01-28 03:02:46 +09:00
|
|
|
|
|
|
|
|
|
|
|
def add_boolean_argument(parser, name, **kwargs):
|
|
|
|
for keyword in ('metavar', 'choices'):
|
|
|
|
kwargs.pop(keyword, None)
|
|
|
|
default = kwargs.pop('default', argparse.SUPPRESS)
|
|
|
|
parser.add_argument(
|
|
|
|
name,
|
|
|
|
metavar='{True,False}',
|
|
|
|
choices=['True', 'true', 'False', 'false'],
|
|
|
|
default=default,
|
|
|
|
**kwargs)
|