Files
nova/nova/cmd/common.py
Stephen Finucane 35b304661d Handle deprecation of inspect.getargspec
getargspec has been deprecated in py3 with plans to remove it in py3.6.
The recommendation is to move to inspect.signature, but the results of
that call are different than the existing one.

There is also getfullargspec available under py3 that was originally
deprecated, but for the sake of handling 2/3 code, it has been
un-deprecated. This call uses inspect internally, but returns a mostly
compatible result with what getargspec did. This handles getargspec
deprecation by just using getfullargspec instead if it is available

Closes-Bug: #1766919

Change-Id: I154b1ef14dbace803841cb3eed3ff02dd0020588
2018-04-26 11:51:10 +00:00

213 lines
6.8 KiB
Python

# Copyright 2016 Cloudbase Solutions Srl
# All Rights Reserved.
#
# 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.
"""
Common functions used by different CLI interfaces.
"""
from __future__ import print_function
import argparse
import traceback
from oslo_log import log as logging
import six
import nova.conf
import nova.db.api
from nova import exception
from nova.i18n import _
from nova import utils
CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)
def block_db_access(service_name):
"""Blocks Nova DB access."""
class NoDB(object):
def __getattr__(self, attr):
return self
def __call__(self, *args, **kwargs):
stacktrace = "".join(traceback.format_stack())
LOG.error('No db access allowed in %(service_name)s: '
'%(stacktrace)s',
dict(service_name=service_name, stacktrace=stacktrace))
raise exception.DBNotAllowed(service_name)
nova.db.api.IMPL = NoDB()
def validate_args(fn, *args, **kwargs):
"""Check that the supplied args are sufficient for calling a function.
>>> validate_args(lambda a: None)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): a
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): b, d
:param fn: the function to check
:param arg: the positional arguments supplied
:param kwargs: the keyword arguments supplied
"""
argspec = utils.getargspec(fn)
num_defaults = len(argspec.defaults or [])
required_args = argspec.args[:len(argspec.args) - num_defaults]
if six.get_method_self(fn) is not None:
required_args.pop(0)
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
return missing
# Decorators for actions
def args(*args, **kwargs):
"""Decorator which adds the given args and kwargs to the args list of
the desired func's __dict__.
"""
def _decorator(func):
func.__dict__.setdefault('args', []).insert(0, (args, kwargs))
return func
return _decorator
def methods_of(obj):
"""Get all callable methods of an object that don't start with underscore
returns a list of tuples of the form (method_name, method)
"""
result = []
for i in dir(obj):
if callable(getattr(obj, i)) and not i.startswith('_'):
result.append((i, getattr(obj, i)))
return result
def add_command_parsers(subparsers, categories):
"""Adds command parsers to the given subparsers.
Adds version and bash-completion parsers.
Adds a parser with subparsers for each category in the categories dict
given.
"""
parser = subparsers.add_parser('version')
parser = subparsers.add_parser('bash-completion')
parser.add_argument('query_category', nargs='?')
for category in categories:
command_object = categories[category]()
desc = getattr(command_object, 'description', None)
parser = subparsers.add_parser(category, description=desc)
parser.set_defaults(command_object=command_object)
category_subparsers = parser.add_subparsers(dest='action')
for (action, action_fn) in methods_of(command_object):
parser = category_subparsers.add_parser(
action, description=getattr(action_fn, 'description', desc))
action_kwargs = []
for args, kwargs in getattr(action_fn, 'args', []):
# we must handle positional parameters (ARG) separately from
# positional parameters (--opt). Detect this by checking for
# the presence of leading '--'
if args[0] != args[0].lstrip('-'):
kwargs.setdefault('dest', args[0].lstrip('-'))
if kwargs['dest'].startswith('action_kwarg_'):
action_kwargs.append(
kwargs['dest'][len('action_kwarg_'):])
else:
action_kwargs.append(kwargs['dest'])
kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
else:
action_kwargs.append(args[0])
args = ['action_kwarg_' + arg for arg in args]
parser.add_argument(*args, **kwargs)
parser.set_defaults(action_fn=action_fn)
parser.set_defaults(action_kwargs=action_kwargs)
parser.add_argument('action_args', nargs='*',
help=argparse.SUPPRESS)
def print_bash_completion(categories):
if not CONF.category.query_category:
print(" ".join(categories.keys()))
elif CONF.category.query_category in categories:
fn = categories[CONF.category.query_category]
command_object = fn()
actions = methods_of(command_object)
print(" ".join([k for (k, v) in actions]))
def get_action_fn():
fn = CONF.category.action_fn
fn_args = []
for arg in CONF.category.action_args:
if isinstance(arg, six.binary_type):
arg = arg.decode('utf-8')
fn_args.append(arg)
fn_kwargs = {}
for k in CONF.category.action_kwargs:
v = getattr(CONF.category, 'action_kwarg_' + k)
if v is None:
continue
if isinstance(v, six.binary_type):
v = v.decode('utf-8')
fn_kwargs[k] = v
# call the action with the remaining arguments
# check arguments
missing = validate_args(fn, *fn_args, **fn_kwargs)
if missing:
# NOTE(mikal): this isn't the most helpful error message ever. It is
# long, and tells you a lot of things you probably don't want to know
# if you just got a single arg wrong.
print(fn.__doc__)
CONF.print_help()
raise exception.Invalid(
_("Missing arguments: %s") % ", ".join(missing))
return fn, fn_args, fn_kwargs
def action_description(text):
"""Decorator for adding a description to command action.
To display help text on action call instead of common category help text
action function can be decorated.
command <category> <action> -h will show description and arguments.
"""
def _decorator(func):
func.description = text
return func
return _decorator