Retire senlin command line tool

With upstream osc-lib bug fixed, the --profile argument now works.
We have no need of the senlin command line. This patch retires the
command line.

Change-Id: I6bfc8f6f97ad7a77624ba5da1004a7a8f0924740
This commit is contained in:
tengqm 2017-12-13 04:49:57 -05:00
parent 5bd4827289
commit 0717dfb97f
17 changed files with 5 additions and 6964 deletions

View File

@ -7,12 +7,11 @@ Team and repository tags
.. Change things from this point on .. Change things from this point on
Python bindings to the Senlin Clustering API OpenStackClient Plugin for Senlin Clustering Service
============================================ ====================================================
This is a client library for Senlin built on the Senlin clustering API. It This is a client library for Senlin built on the Senlin clustering API. It
provides a Python API (the ``senlinclient`` module) and a command-line tool provides a plugin for the openstackclient command-line tool.
(``senlin``).
Development takes place via the usual OpenStack processes as outlined in the Development takes place via the usual OpenStack processes as outlined in the
`developer guide <https://docs.openstack.org/infra/manual/developers.html>`_. `developer guide <https://docs.openstack.org/infra/manual/developers.html>`_.

File diff suppressed because it is too large Load Diff

View File

@ -247,11 +247,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ # man_pages = []
('man/senlin', 'senlin',
u'Command line reference for Senlin',
[u'Senlin Developers'], 1)
]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
# man_show_urls = False # man_show_urls = False

View File

@ -8,7 +8,6 @@ Contents:
:maxdepth: 2 :maxdepth: 2
install/index install/index
cli/index
Indices and tables Indices and tables

View File

@ -1,82 +0,0 @@
:orphan:
======
senlin
======
.. program:: senlin
SYNOPSIS
========
`senlin` [options] <command> [command-options]
`senlin help`
`senlin help` <command>
DESCRIPTION
===========
`senlin` is a command line client for controlling OpenStack Senlin.
Before the `senlin` command is issued, ensure the environment contains
the necessary variables so that the CLI can pass user credentials to
the server.
See `Getting Credentials for a CLI` section of `OpenStack CLI Guide`
for more info.
OPTIONS
=======
To get a list of available commands and options run::
senlin help
To get usage and options of a command run::
senlin help <command>
EXAMPLES
========
Get information about profile-create command::
senlin help profile-create
List available profiles::
senlin profile-list
List available clusters::
senlin cluster-list
Create a profile::
senlin profile-create -s profile.spec myprofile
View profile information::
senlin profile-show myprofile
Create a cluster::
senlin cluster-create -p myprofile -n 2 mycluster
List events::
senlin event-list
Delete a cluster::
senlin cluster-delete mycluster
BUGS
====
Senlin client is hosted in Launchpad so you can view current bugs
at https://bugs.launchpad.net/python-senlinclient/.

View File

@ -1,180 +0,0 @@
# 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.
import argparse
from senlinclient.common.i18n import _
from senlinclient.common import utils
def add_global_identity_args(parser):
parser.add_argument(
'--os-auth-plugin', dest='auth_plugin', metavar='AUTH_PLUGIN',
default=utils.env('OS_AUTH_PLUGIN', default=None),
help=_('Authentication plugin, default to env[OS_AUTH_PLUGIN]'))
parser.add_argument(
'--os-auth-url', dest='auth_url', metavar='AUTH_URL',
default=utils.env('OS_AUTH_URL'),
help=_('Defaults to env[OS_AUTH_URL]'))
parser.add_argument(
'--os-project-id', dest='project_id', metavar='PROJECT_ID',
default=utils.env('OS_PROJECT_ID'),
help=_('Defaults to env[OS_PROJECT_ID].'))
parser.add_argument(
'--os-project-name', dest='project_name', metavar='PROJECT_NAME',
default=utils.env('OS_PROJECT_NAME'),
help=_('Defaults to env[OS_PROJECT_NAME].'))
parser.add_argument(
'--os-tenant-id', dest='tenant_id', metavar='TENANT_ID',
default=utils.env('OS_TENANT_ID'),
help=_('Defaults to env[OS_TENANT_ID].'))
parser.add_argument(
'--os-tenant-name', dest='tenant_name', metavar='TENANT_NAME',
default=utils.env('OS_TENANT_NAME'),
help=_('Defaults to env[OS_TENANT_NAME].'))
parser.add_argument(
'--os-domain-id', dest='domain_id', metavar='DOMAIN_ID',
default=utils.env('OS_DOMAIN_ID'),
help=_('Domain ID for scope of authorization, defaults to '
'env[OS_DOMAIN_ID].'))
parser.add_argument(
'--os-domain-name', dest='domain_name', metavar='DOMAIN_NAME',
default=utils.env('OS_DOMAIN_NAME'),
help=_('Domain name for scope of authorization, defaults to '
'env[OS_DOMAIN_NAME].'))
parser.add_argument(
'--os-project-domain-id', dest='project_domain_id',
metavar='PROJECT_DOMAIN_ID',
default=utils.env('OS_PROJECT_DOMAIN_ID'),
help=_('Project domain ID for scope of authorization, defaults to '
'env[OS_PROJECT_DOMAIN_ID].'))
parser.add_argument(
'--os-project-domain-name', dest='project_domain_name',
metavar='PROJECT_DOMAIN_NAME',
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
help=_('Project domain name for scope of authorization, defaults to '
'env[OS_PROJECT_DOMAIN_NAME].'))
parser.add_argument(
'--os-user-domain-id', dest='user_domain_id',
metavar='USER_DOMAIN_ID',
default=utils.env('OS_USER_DOMAIN_ID'),
help=_('User domain ID for scope of authorization, defaults to '
'env[OS_USER_DOMAIN_ID].'))
parser.add_argument(
'--os-user-domain-name', dest='user_domain_name',
metavar='USER_DOMAIN_NAME',
default=utils.env('OS_USER_DOMAIN_NAME'),
help=_('User domain name for scope of authorization, defaults to '
'env[OS_USER_DOMAIN_NAME].'))
parser.add_argument(
'--os-username', dest='username', metavar='USERNAME',
default=utils.env('OS_USERNAME'),
help=_('Defaults to env[OS_USERNAME].'))
parser.add_argument(
'--os-user-id', dest='user_id', metavar='USER_ID',
default=utils.env('OS_USER_ID'),
help=_('Defaults to env[OS_USER_ID].'))
parser.add_argument(
'--os-password', dest='password', metavar='PASSWORD',
default=utils.env('OS_PASSWORD'),
help=_('Defaults to env[OS_PASSWORD]'))
parser.add_argument(
'--os-trust-id', dest='trust_id', metavar='TRUST_ID',
default=utils.env('OS_TRUST_ID'),
help=_('Defaults to env[OS_TRUST_ID]'))
verify_group = parser.add_mutually_exclusive_group()
verify_group.add_argument(
'--os-cacert', dest='verify', metavar='CA_BUNDLE_FILE',
default=utils.env('OS_CACERT', default=True),
help=_('Path of CA TLS certificate(s) used to verify the remote '
'server\'s certificate. Without this option senlin looks '
'for the default system CA certificates.'))
verify_group.add_argument(
'--verify',
action='store_true',
help=_('Verify server certificate (default)'))
verify_group.add_argument(
'--insecure', dest='verify', action='store_false',
help=_('Explicitly allow senlinclient to perform "insecure SSL" '
'(HTTPS) requests. The server\'s certificate will not be '
'verified against any certificate authorities. This '
'option should be used with caution.'))
parser.add_argument(
'--os-token', dest='token', metavar='TOKEN',
default=utils.env('OS_TOKEN', default=None),
help=_('A string token to bootstrap the Keystone database, defaults '
'to env[OS_TOKEN]'))
parser.add_argument(
'--os-access-info', dest='access_info', metavar='ACCESS_INFO',
default=utils.env('OS_ACCESS_INFO'),
help=_('Access info, defaults to env[OS_ACCESS_INFO]'))
# parser.add_argument(
# '--os-cert',
# help=_('Path of certificate file to use in SSL connection. This '
# 'file can optionally be prepended with the private key.'))
#
# parser.add_argument(
# '--os-key',
# help=_('Path of client key to use in SSL connection. This option is '
# 'not necessary if your key is prepended to your cert file.'))
def add_global_args(parser, version):
# GLOBAL ARGUMENTS
parser.add_argument(
'-h', '--help', action='store_true',
help=argparse.SUPPRESS)
parser.add_argument(
'--version', action='version', version=version,
help=_("Shows the client version and exits."))
parser.add_argument(
'-d', '--debug', action='store_true',
default=bool(utils.env('SENLINCLIENT_DEBUG')),
help=_('Defaults to env[SENLINCLIENT_DEBUG].'))
parser.add_argument(
'-v', '--verbose', action="store_true", default=False,
help=_("Print more verbose output."))
parser.add_argument(
'--api-timeout',
help=_('Number of seconds to wait for an API response, '
'defaults to system socket timeout'))
parser.add_argument(
'--senlin-api-version',
default=utils.env('SENLIN_API_VERSION', default='1'),
help=_('Version number for Senlin API to use, Default to "1".'))

View File

@ -1,23 +0,0 @@
# 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.
from senlinclient.common import utils
def Client(api_ver, *args, **kwargs):
"""Import versioned client module.
:param api_ver: API version required.
"""
module = utils.import_versioned_module(api_ver, 'client')
cls = getattr(module, 'Client')
return cls(*args, **kwargs)

View File

@ -12,13 +12,9 @@
from __future__ import print_function from __future__ import print_function
import os
import sys
from heatclient.common import template_utils from heatclient.common import template_utils
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import importutils
import prettytable import prettytable
import six import six
import yaml import yaml
@ -27,46 +23,6 @@ from senlinclient.common import exc
from senlinclient.common.i18n import _ from senlinclient.common.i18n import _
supported_formats = {
"json": lambda x: jsonutils.dumps(x, indent=2),
"yaml": lambda x: yaml.safe_dump(x, default_flow_style=False)
}
def arg(*args, **kwargs):
"""Decorator for CLI args."""
def _decorator(func):
if not hasattr(func, 'arguments'):
func.arguments = []
if (args, kwargs) not in func.arguments:
func.arguments.insert(0, (args, kwargs))
return func
return _decorator
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def import_versioned_module(version, submodule=None):
module = 'senlinclient.v%s' % version
if submodule:
module = '.'.join((module, submodule))
return importutils.import_module(module)
def format_nested_dict(d, fields, column_names): def format_nested_dict(d, fields, column_names):
if d is None: if d is None:
return '' return ''
@ -99,95 +55,6 @@ def list_formatter(record):
return '\n'.join(record or []) return '\n'.join(record or [])
def _print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
"""Print a list of objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
:param fields: attributes that correspond to columns, in order
:param formatters: `dict` of callables for field formatting
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
if field in formatters:
data = formatters[field](o)
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
if data is None:
data = '-'
row.append(data)
pt.add_row(row)
if six.PY3:
return encodeutils.safe_encode(pt.get_string(**kwargs)).decode()
else:
return encodeutils.safe_encode(pt.get_string(**kwargs))
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
# This wrapper is needed because sdk may yield a generator that will
# escape the exception catching previously
if not objs:
objs = []
try:
res = _print_list(objs, fields, formatters=formatters,
sortby_index=sortby_index,
mixed_case_fields=mixed_case_fields,
field_labels=field_labels)
print(res)
except Exception as ex:
exc.parse_exception(ex)
def print_dict(d, formatters=None):
formatters = formatters or {}
pt = prettytable.PrettyTable(['Property', 'Value'],
caching=False, print_empty=False)
pt.align = 'l'
for field in d.keys():
if field in formatters:
data = formatters[field](d[field])
else:
data = d[field]
if data is None:
data = '-'
pt.add_row([field, data])
content = pt.get_string(sortby='Property')
if six.PY3:
print(encodeutils.safe_encode(content).decode())
else:
print(encodeutils.safe_encode(content))
def print_action_result(rid, res): def print_action_result(rid, res):
if res[0] == "OK": if res[0] == "OK":
output = _("accepted by action %s") % res[1] output = _("accepted by action %s") % res[1]
@ -281,18 +148,3 @@ def process_stack_spec(spec):
} }
return new_spec return new_spec
def format_output(output, format='yaml'):
fmt = format.lower()
try:
return supported_formats[fmt](output)
except KeyError:
raise exc.HTTPUnsupported(_('The format(%s) is unsupported.')
% fmt)
def exit(msg=''):
if msg:
print(msg, file=sys.stderr)
sys.exit(1)

View File

@ -1,312 +0,0 @@
# 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.
"""
Command-line interface to the Senlin clustering API.
"""
from __future__ import print_function
import argparse
import logging
import sys
from oslo_utils import encodeutils
from oslo_utils import importutils
import six
import senlinclient
from senlinclient import cliargs
from senlinclient import client as senlin_client
from senlinclient.common import exc
from senlinclient.common.i18n import _
from senlinclient.common import utils
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
USER_AGENT = 'python-senlinclient'
LOG = logging.getLogger(__name__)
class HelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(HelpFormatter, self).start_section(heading)
class SenlinShell(object):
def _setup_logging(self, debug):
log_lvl = logging.DEBUG if debug else logging.WARNING
logging.basicConfig(format="%(levelname)s (%(module)s) %(message)s",
level=log_lvl)
logging.getLogger('iso8601').setLevel(logging.WARNING)
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
def _setup_verbose(self, verbose):
if verbose:
exc.verbose = 1
def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
# get callback documentation string
desc = callback.__doc__ or ''
help = desc.strip().split('\n')[0]
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(command,
help=help,
description=desc,
add_help=False,
formatter_class=HelpFormatter)
subparser.add_argument('-h', '--help',
action='help',
help=argparse.SUPPRESS)
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback)
self.subcommands[command] = subparser
def do_bash_completion(self, args):
"""Prints all of the commands and options to stdout.
The senlin.bash_completion script doesn't have to hard code them.
"""
commands = set()
options = set()
for sc_str, sc in self.subcommands.items():
if sc_str == 'bash-completion':
continue
commands.add(sc_str)
for option in list(sc._optionals._option_string_actions):
options.add(option)
print(' '.join(commands | options))
def add_profiler_args(self, parser):
if osprofiler_profiler:
parser.add_argument(
'--os-profile',
metavar='HMAC_KEY',
default=utils.env('OS_PROFILE'),
help=_('HMAC key to use for encrypting context data for '
'performance profiling of operation. This key should '
'be the value of HMAC key configured in '
'senlin configuration (/etc/senlin/senlin.conf). '
'Without the key, profiling will not be triggered '
'even if osprofiler is enabled on server side.'))
def get_subcommand_parser(self, base_parser, version):
parser = base_parser
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
submodule = utils.import_versioned_module(version, 'shell')
self._find_actions(subparsers, submodule)
self._find_actions(subparsers, self)
return parser
@utils.arg('command', metavar='<subcommand>', nargs='?',
help=_('Display help for <subcommand>.'))
def do_help(self, args):
"""Display help about this program or one of its subcommands."""
if getattr(args, 'command', None):
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError("'%s' is not a valid subcommand" %
args.command)
else:
self.parser.print_help()
def _check_identity_arguments(self, args):
# TODO(Qiming): validate the token authentication path and the trust
# authentication path
if not args.auth_url:
msg = _('You must provide an auth url via --os-auth-url (or '
' env[OS_AUTH_URL])')
raise exc.CommandError(msg)
# username or user_id or token must be specified
if not (args.username or args.user_id or args.token):
msg = _('You must provide a user name, a user_id or a '
'token for authentication')
raise exc.CommandError(msg)
# if both username and user_id are specified, user_id takes precedence
if (args.username and args.user_id):
msg = _('Both user name and user ID are specified, Senlin will '
'use user ID for authentication')
print(_('WARNING: %s') % msg)
if 'v3' in args.auth_url:
if (args.username and not args.user_id):
if not (args.user_domain_id or args.user_domain_name):
msg = _('Either user domain ID (--user-domain-id / '
'env[OS_USER_DOMAIN_ID]) or user domain name '
'(--user-domain-name / env[OS_USER_DOMAIN_NAME]) '
'must be specified, because user name may not be '
'unique.')
raise exc.CommandError(msg)
# password is needed if username or user_id is present
if (args.username or args.user_id) and not (args.password):
msg = _('You must provide a password for user %s') % (
args.username or args.user_id)
raise exc.CommandError(msg)
# project name or ID is needed, or else sdk may find the wrong project
if (not (args.project_id or args.project_name or args.tenant_id or
args.tenant_name)):
if not (args.user_id):
msg = _('Either project/tenant ID or project/tenant name '
'must be specified, or else Senlin cannot know '
'which project to use.')
raise exc.CommandError(msg)
else:
msg = _('Neither project ID nor project name is specified. '
'Senlin will use user\'s default project which may '
'result in authentication error.')
print(_('WARNING: %s') % msg)
# both project name and ID are specified, ID takes precedence
if ((args.project_id or args.tenant_id) and
(args.project_name or args.tenant_name)):
msg = _('Both project/tenant name and project/tenant ID are '
'specified, Senlin will use project ID for '
'authentication')
print(_('WARNING: %s') % msg)
# project name may not be unique
if 'v3' in args.auth_url:
if (not (args.project_id or args.tenant_id) and
(args.project_name or args.tenant_name) and
not (args.project_domain_id or args.project_domain_name)):
msg = _('Either project domain ID (--project-domain-id / '
'env[OS_PROJECT_DOMAIN_ID]) orr project domain name '
'(--project-domain-name / '
'env[OS_PROJECT_DOMAIN_NAME]) must be specified, '
'because project/tenant name may not be unique.')
raise exc.CommandError(msg)
def _setup_senlin_client(self, api_ver, args):
"""Create senlin client using given args."""
kwargs = {
'auth_plugin': args.auth_plugin or 'password',
'auth_url': args.auth_url,
'project_name': args.project_name or args.tenant_name,
'project_id': args.project_id or args.tenant_id,
'domain_name': args.domain_name,
'domain_id': args.domain_id,
'project_domain_name': args.project_domain_name,
'project_domain_id': args.project_domain_id,
'user_domain_name': args.user_domain_name,
'user_domain_id': args.user_domain_id,
'username': args.username,
'user_id': args.user_id,
'password': args.password,
'verify': args.verify,
'token': args.token,
'trust_id': args.trust_id,
}
return senlin_client.Client('1', user_agent=USER_AGENT, **kwargs)
def main(self, argv):
# Parse args once to find version
parser = argparse.ArgumentParser(
prog='senlin',
description=__doc__.strip(),
epilog=_('Type "senlin help <COMMAND>" for help on a specific '
'command.'),
add_help=False,
formatter_class=HelpFormatter,
)
cliargs.add_global_args(parser, version=senlinclient.__version__)
cliargs.add_global_identity_args(parser)
self.add_profiler_args(parser)
base_parser = parser
(options, args) = base_parser.parse_known_args(argv)
self._setup_logging(options.debug)
self._setup_verbose(options.verbose)
# build available subcommands based on version
api_ver = options.senlin_api_version
LOG.info(api_ver)
subcommand_parser = self.get_subcommand_parser(base_parser, api_ver)
self.parser = subcommand_parser
# Handle top-level --help/-h before attempting to parse
# a command off the command line
if not args and options.help or not argv:
self.do_help(options)
return 0
# Parse args again and call whatever callback was selected
args = subcommand_parser.parse_args(argv)
# Short-circuit and deal with help command right away.
if args.func == self.do_help:
self.do_help(args)
return 0
elif args.func == self.do_bash_completion:
self.do_bash_completion(args)
return 0
# Check if identity information are sufficient
self._check_identity_arguments(args)
# Setup Senlin client connection
sc = self._setup_senlin_client(api_ver, args)
os_profile = osprofiler_profiler and options.os_profile
if os_profile:
osprofiler_profiler.init(options.os_profile)
args.func(sc.service, args)
if os_profile:
trace_id = osprofiler_profiler.get().get_base_id()
print(_("Trace ID: %s") % trace_id)
print(_("To display trace use next command:\n"
"osprofiler trace show --html %s ") % trace_id)
def main(args=None):
try:
if args is None:
args = sys.argv[1:]
SenlinShell().main(args)
except KeyboardInterrupt:
print(_("... terminating senlin client"), file=sys.stderr)
return 130
except Exception as e:
if '--debug' in args or '-d' in args:
raise
else:
print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,74 +0,0 @@
# 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.
import mock
import testtools
from senlinclient import cliargs
class TestCliArgs(testtools.TestCase):
def test_add_global_identity_args(self):
parser = mock.Mock()
cliargs.add_global_identity_args(parser)
expected = [
'--os-auth-plugin',
'--os-auth-url',
'--os-project-id',
'--os-project-name',
'--os-tenant-id',
'--os-tenant-name',
'--os-domain-id',
'--os-domain-name',
'--os-project-domain-id',
'--os-project-domain-name',
'--os-user-domain-id',
'--os-user-domain-name',
'--os-username',
'--os-user-id',
'--os-password',
'--os-trust-id',
'--os-token',
'--os-access-info',
]
options = [arg[0][0] for arg in parser.add_argument.call_args_list]
self.assertEqual(expected, options)
parser.add_mutually_exclusive_group.assert_called_once_with()
group = parser.add_mutually_exclusive_group.return_value
verify_opts = [arg[0][0] for arg in group.add_argument.call_args_list]
verify_args = [
'--os-cacert',
'--verify',
'--insecure'
]
self.assertEqual(verify_args, verify_opts)
def test_add_global_args(self):
parser = mock.Mock()
cliargs.add_global_args(parser, '1')
expected = [
'-h',
'--version',
'-d',
'-v',
'--api-timeout',
'--senlin-api-version'
]
options = [arg[0][0] for arg in parser.add_argument.call_args_list]
self.assertEqual(expected, options)

View File

@ -1,39 +0,0 @@
# 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.
import mock
import testtools
from senlinclient import client as sc
from senlinclient.common import utils
class FakeClient(object):
def __init__(self, session):
super(FakeClient, self).__init__()
self.session = session
class ClientTest(testtools.TestCase):
@mock.patch.object(utils, 'import_versioned_module')
def test_client_init(self, mock_import):
the_module = mock.Mock()
the_module.Client = FakeClient
mock_import.return_value = the_module
session = mock.Mock()
res = sc.Client('FAKE_VER', session)
mock_import.assert_called_once_with('FAKE_VER', 'client')
self.assertIsInstance(res, FakeClient)

View File

@ -1,356 +0,0 @@
# 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.
import argparse
import logging
import sys
import mock
import six
from six.moves import builtins
import testtools
from senlinclient import client as senlin_client
from senlinclient.common import exc
from senlinclient.common.i18n import _
from senlinclient.common import sdk
from senlinclient.common import utils
from senlinclient import shell
from senlinclient.tests.unit import fakes
class HelpFormatterTest(testtools.TestCase):
def test_start_section(self):
fmtr = shell.HelpFormatter('senlin')
res = fmtr.start_section(('heading', 'text1', 30))
self.assertIsNone(res)
h = fmtr._current_section.heading
self.assertEqual("HEADING('text1', 30)", h)
class TestArgs(testtools.TestCase):
def __init__(self):
self.auth_url = 'http://fakeurl/v3'
self.auth_plugin = 'test_plugin'
self.username = 'test_user_name'
self.user_id = 'test_user_id'
self.token = 'test_token'
self.project_id = 'test_project_id'
self.project_name = 'test_project_name'
self.tenant_id = 'test_tenant_id'
self.tenant_name = 'test_tenant_name'
self.password = 'test_password'
self.user_domain_id = 'test_user_domain_id'
self.user_domain_name = 'test_user_domain_name'
self.project_domain_id = 'test_project_domain_id'
self.project_domain_name = 'test_project_domain_name'
self.domain_name = 'test_domain_name'
self.domain_id = 'test_domain_id'
self.verify = 'test_verify'
self.user_preferences = 'test_preferences'
self.trust_id = 'test_trust'
class ShellTest(testtools.TestCase):
def setUp(self):
super(ShellTest, self).setUp()
def SHELL(self, func, *args, **kwargs):
orig_out = sys.stdout
sys.stdout = six.StringIO()
func(*args, **kwargs)
output = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig_out
return output
@mock.patch.object(logging, 'basicConfig')
@mock.patch.object(logging, 'getLogger')
def test_setup_logging_debug(self, x_get, x_config):
sh = shell.SenlinShell()
sh._setup_logging(True)
x_config.assert_called_once_with(
format="%(levelname)s (%(module)s) %(message)s",
level=logging.DEBUG)
mock_calls = [
mock.call('iso8601'),
mock.call().setLevel(logging.WARNING),
mock.call('urllib3.connectionpool'),
mock.call().setLevel(logging.WARNING),
]
x_get.assert_has_calls(mock_calls)
@mock.patch.object(logging, 'basicConfig')
@mock.patch.object(logging, 'getLogger')
def test_setup_logging_no_debug(self, x_get, x_config):
sh = shell.SenlinShell()
sh._setup_logging(False)
x_config.assert_called_once_with(
format="%(levelname)s (%(module)s) %(message)s",
level=logging.WARNING)
mock_calls = [
mock.call('iso8601'),
mock.call().setLevel(logging.WARNING),
mock.call('urllib3.connectionpool'),
mock.call().setLevel(logging.WARNING),
]
x_get.assert_has_calls(mock_calls)
def test_setup_verbose(self):
sh = shell.SenlinShell()
sh._setup_verbose(True)
self.assertEqual(1, exc.verbose)
sh._setup_verbose(False)
self.assertEqual(1, exc.verbose)
def test_find_actions(self):
sh = shell.SenlinShell()
sh.subcommands = {}
subparsers = mock.Mock()
x_subparser1 = mock.Mock()
x_subparser2 = mock.Mock()
x_add_parser = mock.Mock(side_effect=[x_subparser1, x_subparser2])
subparsers.add_parser = x_add_parser
# subparsers.add_parser = mock.Mock(return_value=x_subparser)
sh._find_actions(subparsers, fakes)
self.assertEqual({'command-bar': x_subparser1,
'command-foo': x_subparser2},
sh.subcommands)
add_calls = [
mock.call('command-bar', help='This is the command doc.',
description='This is the command doc.',
add_help=False,
formatter_class=shell.HelpFormatter),
mock.call('command-foo', help='Pydoc for command foo.',
description='Pydoc for command foo.',
add_help=False,
formatter_class=shell.HelpFormatter),
]
x_add_parser.assert_has_calls(add_calls)
calls_1 = [
mock.call('-h', '--help', action='help',
help=argparse.SUPPRESS),
mock.call('-F', '--flag', metavar='<FLAG>',
help='Flag desc.'),
mock.call('arg1', metavar='<ARG1>',
help='Arg1 desc')
]
x_subparser1.add_argument.assert_has_calls(calls_1)
x_subparser1.set_defaults.assert_called_once_with(
func=fakes.do_command_bar)
calls_2 = [
mock.call('-h', '--help', action='help',
help=argparse.SUPPRESS),
]
x_subparser2.add_argument.assert_has_calls(calls_2)
x_subparser2.set_defaults.assert_called_once_with(
func=fakes.do_command_foo)
def test_do_bash_completion(self):
sh = shell.SenlinShell()
sc1 = mock.Mock()
sc2 = mock.Mock()
sc1._optionals._option_string_actions = ('A1', 'A2', 'C')
sc2._optionals._option_string_actions = ('B1', 'B2', 'C')
sh.subcommands = {
'command-foo': sc1,
'command-bar': sc2,
'bash-completion': None,
}
output = self.SHELL(sh.do_bash_completion, None)
output = output.split('\n')[0]
output_list = output.split(' ')
for option in ('A1', 'A2', 'C', 'B1', 'B2',
'command-foo', 'command-bar'):
self.assertIn(option, output_list)
def test_do_add_profiler_args(self):
sh = shell.SenlinShell()
parser = mock.Mock()
sh.add_profiler_args(parser)
if shell.osprofiler_profiler:
self.assertEqual(1, parser.add_argument.call_count)
else:
self.assertEqual(0, parser.add_argument.call_count)
@mock.patch.object(utils, 'import_versioned_module')
@mock.patch.object(shell.SenlinShell, '_find_actions')
def test_get_subcommand_parser(self, x_find, x_import):
x_base = mock.Mock()
x_module = mock.Mock()
x_import.return_value = x_module
sh = shell.SenlinShell()
res = sh.get_subcommand_parser(x_base, 'v100')
self.assertEqual(x_base, res)
x_base.add_subparsers.assert_called_once_with(
metavar='<subcommand>')
x_subparsers = x_base.add_subparsers.return_value
x_import.assert_called_once_with('v100', 'shell')
find_calls = [
mock.call(x_subparsers, x_module),
mock.call(x_subparsers, sh)
]
x_find.assert_has_calls(find_calls)
@mock.patch.object(argparse.ArgumentParser, 'print_help')
def test_do_help(self, mock_print):
sh = shell.SenlinShell()
args = mock.Mock()
args.command = mock.Mock()
sh.subcommands = {args.command: argparse.ArgumentParser}
sh.do_help(args)
self.assertTrue(mock_print.called)
sh.subcommands = {}
ex = self.assertRaises(exc.CommandError,
sh.do_help, args)
msg = _("'%s' is not a valid subcommand") % args.command
self.assertEqual(msg, six.text_type(ex))
@mock.patch.object(builtins, 'print')
def test_check_identity_arguments(self, mock_print):
sh = shell.SenlinShell()
# auth_url is not specified.
args = TestArgs()
args.auth_url = None
ex = self.assertRaises(exc.CommandError,
sh._check_identity_arguments, args)
msg = _('You must provide an auth url via --os-auth-url (or '
' env[OS_AUTH_URL])')
self.assertEqual(msg, six.text_type(ex))
# username, user_id and token are not specified.
args = TestArgs()
args.username = None
args.user_id = None
args.token = None
msg = _('You must provide a user name, a user_id or a '
'token for authentication')
ex = self.assertRaises(exc.CommandError,
sh._check_identity_arguments, args)
self.assertEqual(msg, six.text_type(ex))
# Both username and user_id are specified.
args = TestArgs()
args.project_id = None
args.tenant_id = None
sh._check_identity_arguments(args)
msg = _('WARNING: Both user name and user ID are specified, '
'Senlin will use user ID for authentication')
mock_print.assert_called_with(msg)
# 'v3' in auth_url but neither user_domain_id nor user_domain_name
# is specified.
args = TestArgs()
args.user_id = None
args.user_domain_id = None
args.user_domain_name = None
msg = _('Either user domain ID (--user-domain-id / '
'env[OS_USER_DOMAIN_ID]) or user domain name '
'(--user-domain-name / env[OS_USER_DOMAIN_NAME]) '
'must be specified, because user name may not be '
'unique.')
ex = self.assertRaises(exc.CommandError,
sh._check_identity_arguments, args)
self.assertEqual(msg, six.text_type(ex))
# user_id, project_id, project_name, tenant_id and tenant_name are all
# not specified.
args = TestArgs()
args.project_id = None
args.project_name = None
args.tenant_id = None
args.tenant_name = None
args.user_id = None
msg = _('Either project/tenant ID or project/tenant name '
'must be specified, or else Senlin cannot know '
'which project to use.')
ex = self.assertRaises(exc.CommandError,
sh._check_identity_arguments, args)
self.assertEqual(msg, six.text_type(ex))
args.user_id = 'test_user_id'
sh._check_identity_arguments(args)
msg = _('Neither project ID nor project name is specified. '
'Senlin will use user\'s default project which may '
'result in authentication error.')
mock_print.assert_called_with(_('WARNING: %s') % msg)
# Both project_name and project_id are specified
args = TestArgs()
args.user_id = None
sh._check_identity_arguments(args)
msg = _('Both project/tenant name and project/tenant ID are '
'specified, Senlin will use project ID for '
'authentication')
mock_print.assert_called_with(_('WARNING: %s') % msg)
# Project name may not be unique
args = TestArgs()
args.user_id = None
args.project_id = None
args.tenant_id = None
args.project_domain_id = None
args.project_domain_name = None
msg = _('Either project domain ID (--project-domain-id / '
'env[OS_PROJECT_DOMAIN_ID]) orr project domain name '
'(--project-domain-name / '
'env[OS_PROJECT_DOMAIN_NAME]) must be specified, '
'because project/tenant name may not be unique.')
ex = self.assertRaises(exc.CommandError,
sh._check_identity_arguments, args)
self.assertEqual(msg, six.text_type(ex))
@mock.patch.object(sdk, 'create_connection')
def test_setup_senlinclient(self, mock_conn):
USER_AGENT = 'python-senlinclient'
args = TestArgs()
kwargs = {
'auth_plugin': args.auth_plugin,
'auth_url': args.auth_url,
'project_name': args.project_name or args.tenant_name,
'project_id': args.project_id or args.tenant_id,
'domain_name': args.domain_name,
'domain_id': args.domain_id,
'project_domain_name': args.project_domain_name,
'project_domain_id': args.project_domain_id,
'user_domain_name': args.user_domain_name,
'user_domain_id': args.user_domain_id,
'username': args.username,
'user_id': args.user_id,
'password': args.password,
'verify': args.verify,
'token': args.token,
'trust_id': args.trust_id,
}
sh = shell.SenlinShell()
conn = mock.Mock()
mock_conn.return_value = conn
conn.session = mock.Mock()
sh._setup_senlin_client('1', args)
mock_conn.assert_called_once_with(prof=None, user_agent=USER_AGENT,
**kwargs)
client = mock.Mock()
senlin_client.Client = mock.Mock(return_value=client)
self.assertEqual(client, sh._setup_senlin_client('1', args))

View File

@ -10,9 +10,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
import sys
from heatclient.common import template_utils from heatclient.common import template_utils
import mock import mock
import six import six
@ -23,21 +20,7 @@ from senlinclient.common.i18n import _
from senlinclient.common import utils from senlinclient.common import utils
class CaptureStdout(object): class UtilTest(testtools.TestCase):
"""Context manager for capturing stdout from statements in its block."""
def __enter__(self):
self.real_stdout = sys.stdout
self.stringio = six.StringIO()
sys.stdout = self.stringio
return self
def __exit__(self, *args):
sys.stdout = self.real_stdout
self.stringio.seek(0)
self.read = self.stringio.read
class shellTest(testtools.TestCase):
def test_format_parameter(self): def test_format_parameter(self):
params = ['status=ACTIVE;name=cluster1'] params = ['status=ACTIVE;name=cluster1']
@ -114,137 +97,3 @@ class shellTest(testtools.TestCase):
def test_list_formatter_with_empty_list(self): def test_list_formatter_with_empty_list(self):
params = [] params = []
self.assertEqual('', utils.list_formatter(params)) self.assertEqual('', utils.list_formatter(params))
class PrintListTestCase(testtools.TestCase):
def test_print_list_with_list(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
to_print = [Row(foo='fake_foo1', bar='fake_bar2'),
Row(foo='fake_foo2', bar='fake_bar1')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['foo', 'bar'])
# Output should be sorted by the first key (foo)
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo1 | fake_bar2 |
| fake_foo2 | fake_bar1 |
+-----------+-----------+
""", cso.read())
def test_print_list_with_None_string(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
to_print = [Row(foo='fake_foo1', bar='None'),
Row(foo='fake_foo2', bar='fake_bar1')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['foo', 'bar'])
# Output should be sorted by the first key (foo)
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo1 | None |
| fake_foo2 | fake_bar1 |
+-----------+-----------+
""", cso.read())
def test_print_list_with_None_data(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
to_print = [Row(foo='fake_foo1', bar=None),
Row(foo='fake_foo2', bar='fake_bar1')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['foo', 'bar'])
# Output should be sorted by the first key (foo)
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo1 | - |
| fake_foo2 | fake_bar1 |
+-----------+-----------+
""", cso.read())
def test_print_list_with_list_sortby(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
to_print = [Row(foo='fake_foo1', bar='fake_bar2'),
Row(foo='fake_foo2', bar='fake_bar1')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['foo', 'bar'], sortby_index=1)
# Output should be sorted by the first key (bar)
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo2 | fake_bar1 |
| fake_foo1 | fake_bar2 |
+-----------+-----------+
""", cso.read())
def test_print_list_with_list_no_sort(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
to_print = [Row(foo='fake_foo2', bar='fake_bar1'),
Row(foo='fake_foo1', bar='fake_bar2')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['foo', 'bar'], sortby_index=None)
# Output should be in the order given
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo2 | fake_bar1 |
| fake_foo1 | fake_bar2 |
+-----------+-----------+
""", cso.read())
def test_print_list_with_generator(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
def gen_rows():
for row in [Row(foo='fake_foo1', bar='fake_bar2'),
Row(foo='fake_foo2', bar='fake_bar1')]:
yield row
with CaptureStdout() as cso:
utils.print_list(gen_rows(), ['foo', 'bar'])
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo1 | fake_bar2 |
| fake_foo2 | fake_bar1 |
+-----------+-----------+
""", cso.read())
class PrintDictTestCase(testtools.TestCase):
def test_print_dict(self):
data = {'foo': 'fake_foo', 'bar': 'fake_bar'}
with CaptureStdout() as cso:
utils.print_dict(data)
# Output should be sorted by the Property
self.assertEqual("""\
+----------+----------+
| Property | Value |
+----------+----------+
| bar | fake_bar |
| foo | fake_foo |
+----------+----------+
""", cso.read())
def test_print_dict_with_None_data(self):
Row = collections.namedtuple('Row', ['foo', 'bar'])
to_print = [Row(foo='fake_foo1', bar=None),
Row(foo='fake_foo2', bar='fake_bar1')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['foo', 'bar'])
# Output should be sorted by the first key (foo)
self.assertEqual("""\
+-----------+-----------+
| foo | bar |
+-----------+-----------+
| fake_foo1 | - |
| fake_foo2 | fake_bar1 |
+-----------+-----------+
""", cso.read())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -23,9 +23,6 @@ packages =
senlinclient senlinclient
[entry_points] [entry_points]
console_scripts =
senlin = senlinclient.shell:main
openstack.cli.extension = openstack.cli.extension =
clustering = senlinclient.plugin clustering = senlinclient.plugin

View File

@ -1,27 +0,0 @@
# bash completion for openstack senlin
_senlin_opts="" # lazy init
_senlin_flags="" # lazy init
_senlin_opts_exp="" # lazy init
_senlin()
{
local cur prev kbc
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [ "x$_senlin_opts" == "x" ] ; then
kbc="`senlin bash-completion | sed -e "s/ -h / /"`"
_senlin_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
_senlin_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
_senlin_opts_exp="`echo $_senlin_opts | sed -e "s/[ ]/|/g"`"
fi
if [[ " ${COMP_WORDS[@]} " =~ " "($_senlin_opts_exp)" " && "$prev" != "help" ]] ; then
COMPREPLY=($(compgen -W "${_senlin_flags}" -- ${cur}))
else
COMPREPLY=($(compgen -W "${_senlin_opts}" -- ${cur}))
fi
return 0
}
complete -o default -F _senlin senlin