summaryrefslogtreecommitdiff
path: root/openstackclient/common
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient/common')
-rw-r--r--openstackclient/common/availability_zone.py5
-rw-r--r--openstackclient/common/clientmanager.py39
-rw-r--r--openstackclient/common/command.py1
-rw-r--r--openstackclient/common/exceptions.py5
-rw-r--r--openstackclient/common/logs.py48
-rw-r--r--openstackclient/common/quota.py4
-rw-r--r--openstackclient/common/timing.py3
-rw-r--r--openstackclient/common/utils.py14
8 files changed, 89 insertions, 30 deletions
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py
index a941418b..a6d11b78 100644
--- a/openstackclient/common/availability_zone.py
+++ b/openstackclient/common/availability_zone.py
@@ -146,21 +146,20 @@ class ListAvailabilityZone(command.Lister):
def _get_network_availability_zones(self, parsed_args):
network_client = self.app.client_manager.network
- data = []
try:
# Verify that the extension exists.
network_client.find_extension('Availability Zone',
ignore_missing=False)
- data = network_client.availability_zones()
except Exception as e:
self.log.debug('Network availability zone exception: ' + str(e))
if parsed_args.network:
message = "Availability zones list not supported by " \
"Network API"
self.log.warning(message)
+ return []
result = []
- for zone in data:
+ for zone in network_client.availability_zones():
result += _xform_network_availability_zone(zone)
return result
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index dce19725..938dd05c 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -37,6 +37,7 @@ USER_AGENT = 'python-openstackclient'
class ClientCache(object):
"""Descriptor class for caching created client handles."""
+
def __init__(self, factory):
self.factory = factory
self._handle = None
@@ -90,7 +91,7 @@ class ClientManager(object):
self._cli_options = cli_options
self._api_version = api_version
self._pw_callback = pw_func
- self._url = self._cli_options.auth.get('url', None)
+ self._url = self._cli_options.auth.get('url')
self._region_name = self._cli_options.region_name
self._interface = self._cli_options.interface
@@ -113,24 +114,40 @@ class ClientManager(object):
root_logger = logging.getLogger('')
LOG.setLevel(root_logger.getEffectiveLevel())
- def setup_auth(self):
+ # NOTE(gyee): use this flag to indicate whether auth setup has already
+ # been completed. If so, do not perform auth setup again. The reason
+ # we need this flag is that we want to be able to perform auth setup
+ # outside of auth_ref as auth_ref itself is a property. We can not
+ # retrofit auth_ref to optionally skip scope check. Some operations
+ # do not require a scoped token. In those cases, we call setup_auth
+ # prior to dereferrencing auth_ref.
+ self._auth_setup_completed = False
+
+ def setup_auth(self, required_scope=True):
"""Set up authentication
+ :param required_scope: indicate whether a scoped token is required
+
This is deferred until authentication is actually attempted because
it gets in the way of things that do not require auth.
"""
+ if self._auth_setup_completed:
+ return
+
# If no auth type is named by the user, select one based on
# the supplied options
self.auth_plugin_name = auth.select_auth_plugin(self._cli_options)
# Basic option checking to avoid unhelpful error messages
- auth.check_valid_auth_options(self._cli_options, self.auth_plugin_name)
+ auth.check_valid_auth_options(self._cli_options,
+ self.auth_plugin_name,
+ required_scope=required_scope)
# Horrible hack alert...must handle prompt for null password if
# password auth is requested.
if (self.auth_plugin_name.endswith('password') and
- not self._cli_options.auth.get('password', None)):
+ not self._cli_options.auth.get('password')):
self._cli_options.auth['password'] = self._pw_callback()
(auth_plugin, self._auth_params) = auth.build_auth_params(
@@ -146,9 +163,9 @@ class ClientManager(object):
# PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
if (self._api_version.get('identity') == '3' and
self.auth_plugin_name.endswith('password') and
- not self._auth_params.get('project_domain_id', None) and
+ not self._auth_params.get('project_domain_id') and
not self.auth_plugin_name.startswith('v2') and
- not self._auth_params.get('project_domain_name', None)):
+ not self._auth_params.get('project_domain_name')):
self._auth_params['project_domain_id'] = default_domain
# NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
@@ -157,8 +174,8 @@ class ClientManager(object):
if (self._api_version.get('identity') == '3' and
self.auth_plugin_name.endswith('password') and
not self.auth_plugin_name.startswith('v2') and
- not self._auth_params.get('user_domain_id', None) and
- not self._auth_params.get('user_domain_name', None)):
+ not self._auth_params.get('user_domain_id') and
+ not self._auth_params.get('user_domain_name')):
self._auth_params['user_domain_id'] = default_domain
# For compatibility until all clients can be updated
@@ -167,8 +184,8 @@ class ClientManager(object):
elif 'tenant_name' in self._auth_params:
self._project_name = self._auth_params['tenant_name']
- LOG.info('Using auth plugin: %s' % self.auth_plugin_name)
- LOG.debug('Using parameters %s' %
+ LOG.info('Using auth plugin: %s', self.auth_plugin_name)
+ LOG.debug('Using parameters %s',
strutils.mask_password(self._auth_params))
self.auth = auth_plugin.load_from_options(**self._auth_params)
# needed by SAML authentication
@@ -180,6 +197,8 @@ class ClientManager(object):
user_agent=USER_AGENT,
)
+ self._auth_setup_completed = True
+
return
@property
diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py
index 13b0bcc2..fee4559e 100644
--- a/openstackclient/common/command.py
+++ b/openstackclient/common/command.py
@@ -22,6 +22,7 @@ import six
class CommandMeta(abc.ABCMeta):
+
def __new__(mcs, name, bases, cls_dict):
if 'log' not in cls_dict:
cls_dict['log'] = logging.getLogger(
diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py
index ab043db0..5f5f5ab1 100644
--- a/openstackclient/common/exceptions.py
+++ b/openstackclient/common/exceptions.py
@@ -41,6 +41,7 @@ class UnsupportedVersion(Exception):
class ClientException(Exception):
"""The base exception class for all exceptions this library raises."""
+
def __init__(self, code, message=None, details=None):
self.code = code
self.message = message or self.__class__.message
@@ -122,8 +123,8 @@ def from_response(response, body):
if body:
if hasattr(body, 'keys'):
error = body[body.keys()[0]]
- message = error.get('message', None)
- details = error.get('details', None)
+ message = error.get('message')
+ details = error.get('details')
else:
# If we didn't get back a properly formed error message we
# probably couldn't communicate with Keystone at all.
diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py
index 6d1aec13..221c5997 100644
--- a/openstackclient/common/logs.py
+++ b/openstackclient/common/logs.py
@@ -18,6 +18,13 @@ import sys
import warnings
+def get_loggers():
+ loggers = {}
+ for logkey in logging.Logger.manager.loggerDict.keys():
+ loggers[logkey] = logging.getLevelName(logging.getLogger(logkey).level)
+ return loggers
+
+
def log_level_from_options(options):
# if --debug, --quiet or --verbose is not specified,
# the default logging level is warning
@@ -34,6 +41,17 @@ def log_level_from_options(options):
return log_level
+def log_level_from_string(level_string):
+ log_level = {
+ 'critical': logging.CRITICAL,
+ 'error': logging.ERROR,
+ 'warning': logging.WARNING,
+ 'info': logging.INFO,
+ 'debug': logging.DEBUG,
+ }.get(level_string, logging.WARNING)
+ return log_level
+
+
def log_level_from_config(config):
# Check the command line option
verbose_level = config.get('verbose_level')
@@ -49,15 +67,7 @@ def log_level_from_config(config):
verbose_level = 'info'
else:
verbose_level = 'debug'
-
- log_level = {
- 'critical': logging.CRITICAL,
- 'error': logging.ERROR,
- 'warning': logging.WARNING,
- 'info': logging.INFO,
- 'debug': logging.DEBUG,
- }.get(verbose_level, logging.WARNING)
- return log_level
+ return log_level_from_string(verbose_level)
def set_warning_filter(log_level):
@@ -159,7 +169,7 @@ class LogConfigurator(object):
self.dump_trace = cloud_config.config.get('debug', self.dump_trace)
self.console_logger.setLevel(log_level)
- log_file = cloud_config.config.get('log_file', None)
+ log_file = cloud_config.config.get('log_file')
if log_file:
if not self.file_logger:
self.file_logger = logging.FileHandler(filename=log_file)
@@ -168,3 +178,21 @@ class LogConfigurator(object):
self.file_logger.setFormatter(_FileFormatter(config=cloud_config))
self.file_logger.setLevel(log_level)
self.root_logger.addHandler(self.file_logger)
+
+ logconfig = cloud_config.config.get('logging')
+ if logconfig:
+ highest_level = logging.NOTSET
+ for k in logconfig.keys():
+ level = log_level_from_string(logconfig[k])
+ logging.getLogger(k).setLevel(level)
+ if (highest_level < level):
+ highest_level = level
+ self.console_logger.setLevel(highest_level)
+ if self.file_logger:
+ self.file_logger.setLevel(highest_level)
+ # loggers that are not set will use the handler level, so we
+ # need to set the global level for all the loggers
+ for logkey in logging.Logger.manager.loggerDict.keys():
+ logger = logging.getLogger(logkey)
+ if logger.level == logging.NOTSET:
+ logger.setLevel(log_level)
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index f208948e..b3d4c3b6 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -169,7 +169,7 @@ class ShowQuota(command.ShowOne):
project = utils.find_resource(
identity_client.projects,
parsed_args.project,
- ).id
+ ).id
try:
if parsed_args.quota_class:
@@ -193,7 +193,7 @@ class ShowQuota(command.ShowOne):
project = utils.find_resource(
identity_client.projects,
parsed_args.project,
- ).id
+ ).id
return self.app.client_manager.network.get_quota(project)
else:
return {}
diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py
index 5f628759..71c2fec7 100644
--- a/openstackclient/common/timing.py
+++ b/openstackclient/common/timing.py
@@ -30,7 +30,8 @@ class Timing(command.Lister):
for url, td in self.app.timing_data:
# NOTE(dtroyer): Take the long way here because total_seconds()
# was added in py27.
- sec = (td.microseconds + (td.seconds + td.days*86400) * 1e6) / 1e6
+ sec = (td.microseconds + (td.seconds + td.days *
+ 86400) * 1e6) / 1e6
total += sec
results.append((url, sec))
results.append(('Total', total))
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index 4142f830..840da402 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -163,7 +163,7 @@ def get_field(item, field):
raise exceptions.CommandError(msg)
-def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
+def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
"""Return a tuple containing the item properties.
:param item: a single item resource (e.g. Server, Project, etc)
@@ -172,6 +172,11 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
:param formatters: dictionary mapping field names to callables
to format the values
"""
+ if mixed_case_fields is None:
+ mixed_case_fields = []
+ if formatters is None:
+ formatters = {}
+
row = []
for field in fields:
@@ -187,7 +192,7 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
return tuple(row)
-def get_dict_properties(item, fields, mixed_case_fields=[], formatters={}):
+def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
"""Return a tuple containing the item properties.
:param item: a single dict resource
@@ -196,6 +201,11 @@ def get_dict_properties(item, fields, mixed_case_fields=[], formatters={}):
:param formatters: dictionary mapping field names to callables
to format the values
"""
+ if mixed_case_fields is None:
+ mixed_case_fields = []
+ if formatters is None:
+ formatters = {}
+
row = []
for field in fields: