diff --git a/requirements.txt b/requirements.txt index 5038c08..440f8f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,11 @@ # process, which may cause wedges in the gate later. Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD +cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 pbr>=1.6 # Apache-2.0 PrettyTable>=0.7,<0.8 # BSD python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 +python-openstackclient>=2.1.0 # Apache-2.0 six>=1.9.0 # MIT diff --git a/setup.cfg b/setup.cfg index 0e69921..c8b5cad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,19 @@ packages = console_scripts = watcher = watcherclient.shell:main +openstack.cli.extension = + infra_optim = watcherclient.plugin + +# Entry points for the 'openstack' command +openstack.infra_optim.v1 = + optimize_goal_show = watcherclient.v1.osc.goal_shell:ShowGoal + optimize_goal_list = watcherclient.v1.osc.goal_shell:ListGoal + +# The same as above but used by the 'watcher' command +watcherclient.v1 = + goal_show = watcherclient.v1.osc.goal_shell:ShowGoal + goal_list = watcherclient.v1.osc.goal_shell:ListGoal + [pbr] autodoc_index_modules = True @@ -49,4 +62,4 @@ input_file = watcherclient/locale/watcherclient.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC mapping_file = babel.cfg -output_file = watcherclient/locale/watcherclient.pot \ No newline at end of file +output_file = watcherclient/locale/watcherclient.pot diff --git a/watcherclient/common/command.py b/watcherclient/common/command.py new file mode 100644 index 0000000..fee4559 --- /dev/null +++ b/watcherclient/common/command.py @@ -0,0 +1,46 @@ +# Copyright 2016 NEC Corporation +# +# 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 abc +import logging + +from cliff import command +from cliff import lister +from cliff import show +import six + + +class CommandMeta(abc.ABCMeta): + + def __new__(mcs, name, bases, cls_dict): + if 'log' not in cls_dict: + cls_dict['log'] = logging.getLogger( + cls_dict['__module__'] + '.' + name) + return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict) + + +@six.add_metaclass(CommandMeta) +class Command(command.Command): + + def run(self, parsed_args): + self.log.debug('run(%s)', parsed_args) + return super(Command, self).run(parsed_args) + + +class Lister(Command, lister.Lister): + pass + + +class ShowOne(Command, show.ShowOne): + pass diff --git a/watcherclient/plugin.py b/watcherclient/plugin.py new file mode 100644 index 0000000..f45f9d9 --- /dev/null +++ b/watcherclient/plugin.py @@ -0,0 +1,64 @@ +# 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 logging + +from openstackclient.common import utils + +LOG = logging.getLogger(__name__) + +DEFAULT_API_VERSION = '1' +API_VERSION_OPTION = 'os_infra_optim_api_version' +API_NAME = 'infra-optim' +API_VERSIONS = { + '1': 'watcherclient.v1.client.Client', +} + + +def make_client(instance): + """Returns an infra-optim service client""" + watcher_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + LOG.debug('Instantiating infra-optim client: %s', watcher_client) + + endpoint = instance.get_endpoint_for_service_type( + API_NAME, + region_name=instance._region_name, + interface=instance._interface, + ) + + client = watcher_client( + endpoint=endpoint, + session=instance.session, + auth_url=instance._auth_url, + username=instance._username, + password=instance._password, + region_name=instance._region_name, + ) + + return client + + +def build_option_parser(parser): + """Hook to add global options.""" + parser.add_argument('--os-infra-optim-api-version', + metavar='', + default=utils.env( + 'OS_INFRA_OPTIM_API_VERSION', + default=DEFAULT_API_VERSION), + help=('Watcher API version, default=' + + DEFAULT_API_VERSION + + ' (Env: OS_INFRA_OPTIM_API_VERSION)')) + return parser diff --git a/watcherclient/tests/v1/osc/__init__.py b/watcherclient/tests/v1/osc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/watcherclient/tests/v1/osc/base.py b/watcherclient/tests/v1/osc/base.py new file mode 100644 index 0000000..37185b0 --- /dev/null +++ b/watcherclient/tests/v1/osc/base.py @@ -0,0 +1,67 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 json +import shlex + +import mock + +from watcherclient.tests import utils +from watcherclient.v1 import client +from watcherclient import watcher + + +class CommandTestCase(utils.BaseTestCase): + + def setUp(self): + super(CommandTestCase, self).setUp() + + self.p_build_http_client = mock.patch.object( + client.Client, 'build_http_client') + self.m_build_http_client = self.p_build_http_client.start() + + self.m_watcher_client = mock.Mock(side_effect=client.Client) + self.p_create_client = mock.patch.object( + watcher.WatcherShell, 'create_client', self.m_watcher_client) + self.p_create_client.start() + + self.addCleanup(self.p_build_http_client.stop) + self.addCleanup(self.p_create_client.stop) + + def run_cmd(self, cmd, formatting='json'): + if formatting: + formatter_arg = " -f %s" % formatting + formatter = json.loads + else: + formatter_arg = '' + formatter = str + formatted_cmd = "%(cmd)s%(formatter)s" % dict( + cmd=cmd, formatter=formatter_arg) + + exit_code = self.cmd.run(shlex.split(formatted_cmd)) + + try: + raw_data = self.stdout.getvalue() + formatted_output = formatter(self.stdout.getvalue()) + except Exception: + self.fail("Formatting error (`%s` -> '%s')" % + (raw_data, formatting)) + return exit_code, formatted_output + + def resource_as_dict(self, resource, columns=(), column_headers=()): + mapping = dict(zip(columns, column_headers)) + return {mapping[k]: v for k, v in resource.to_dict().items() + if not columns or columns and k in mapping} diff --git a/watcherclient/tests/v1/osc/test_goal_shell.py b/watcherclient/tests/v1/osc/test_goal_shell.py new file mode 100644 index 0000000..45bf74f --- /dev/null +++ b/watcherclient/tests/v1/osc/test_goal_shell.py @@ -0,0 +1,131 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 datetime + +import mock +import six + +from watcherclient.tests.v1.osc import base +from watcherclient import v1 as resource +from watcherclient.v1 import resource_fields +from watcherclient import watcher + + +GOAL_1 = { + 'uuid': "fc087747-61be-4aad-8126-b701731ae836", + 'name': "SERVER_CONSOLIDATION", + 'display_name': 'Server Consolidation', + 'created_at': datetime.datetime.now().isoformat(), + 'updated_at': None, + 'deleted_at': None, +} + +GOAL_2 = { + 'uuid': "407b03b1-63c6-49b2-adaf-4df5c0090047", + 'name': "COST_OPTIMIZATION", + 'display_name': 'Cost Optimization', + 'created_at': datetime.datetime.now().isoformat(), + 'updated_at': None, + 'deleted_at': None, +} + + +class GoalShellTest(base.CommandTestCase): + + SHORT_LIST_FIELDS = resource_fields.GOAL_SHORT_LIST_FIELDS + SHORT_LIST_FIELD_LABELS = ( + resource_fields.GOAL_SHORT_LIST_FIELD_LABELS) + FIELDS = resource_fields.GOAL_FIELDS + FIELD_LABELS = resource_fields.GOAL_FIELD_LABELS + + def setUp(self): + super(self.__class__, self).setUp() + + p_goal_manager = mock.patch.object( + resource, 'GoalManager') + self.m_goal_mgr_cls = p_goal_manager.start() + self.addCleanup(p_goal_manager.stop) + + self.m_goal_mgr = mock.Mock() + self.m_goal_mgr_cls.return_value = self.m_goal_mgr + + self.stdout = six.StringIO() + self.cmd = watcher.WatcherShell(stdout=self.stdout) + + def test_do_goal_list(self): + goal1 = resource.Goal(mock.Mock(), GOAL_1) + goal2 = resource.Goal(mock.Mock(), GOAL_2) + self.m_goal_mgr.list.return_value = [ + goal1, goal2] + + exit_code, results = self.run_cmd('goal list') + + self.assertEqual(0, exit_code) + self.assertEqual( + [self.resource_as_dict(goal1, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS), + self.resource_as_dict(goal2, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS)], + results) + + self.m_goal_mgr.list.assert_called_once_with(detail=False) + + def test_do_goal_list_detail(self): + goal1 = resource.Goal(mock.Mock(), GOAL_1) + goal2 = resource.Goal(mock.Mock(), GOAL_2) + self.m_goal_mgr.list.return_value = [ + goal1, goal2] + + exit_code, results = self.run_cmd('goal list --detail') + + self.assertEqual(0, exit_code) + self.assertEqual( + [self.resource_as_dict(goal1, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS), + self.resource_as_dict(goal2, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS)], + results) + + self.m_goal_mgr.list.assert_called_once_with(detail=True) + + def test_do_goal_show_by_name(self): + goal = resource.Goal(mock.Mock(), GOAL_1) + self.m_goal_mgr.get.return_value = goal + + exit_code, result = self.run_cmd('goal show SERVER_CONSOLIDATION') + + self.assertEqual(0, exit_code) + self.assertEqual( + self.resource_as_dict(goal, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS), + result) + self.m_goal_mgr.get.assert_called_once_with('SERVER_CONSOLIDATION') + + def test_do_goal_show_by_uuid(self): + goal = resource.Goal(mock.Mock(), GOAL_1) + self.m_goal_mgr.get.return_value = goal + + exit_code, result = self.run_cmd( + 'goal show fc087747-61be-4aad-8126-b701731ae836') + + self.assertEqual(0, exit_code) + self.assertEqual( + self.resource_as_dict(goal, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS), + result) + self.m_goal_mgr.get.assert_called_once_with( + 'fc087747-61be-4aad-8126-b701731ae836') diff --git a/watcherclient/v1/__init__.py b/watcherclient/v1/__init__.py index e69de29..7f19edd 100644 --- a/watcherclient/v1/__init__.py +++ b/watcherclient/v1/__init__.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 watcherclient.v1 import action +from watcherclient.v1 import action_plan +from watcherclient.v1 import audit +from watcherclient.v1 import audit_template +from watcherclient.v1 import goal +from watcherclient.v1 import strategy + +Action = action.Action +ActionManager = action.ActionManager +ActionPlan = action_plan.ActionPlan +ActionPlanManager = action_plan.ActionPlanManager +Audit = audit.Audit +AuditManager = audit.AuditManager +AuditTemplate = audit_template.AuditTemplate +AuditTemplateManager = audit_template.AuditTemplateManager +Goal = goal.Goal +GoalManager = goal.GoalManager +Strategy = strategy.Strategy +StrategyManager = strategy.StrategyManager + +__all__ = ( + "Action", "ActionManager", "ActionPlan", "ActionPlanManager", + "Audit", "AuditManager", "AuditTemplate", "AuditTemplateManager", + "Goal", "GoalManager", "Strategy", "StrategyManager") diff --git a/watcherclient/v1/client.py b/watcherclient/v1/client.py index 8c5a4e2..615b85d 100644 --- a/watcherclient/v1/client.py +++ b/watcherclient/v1/client.py @@ -16,13 +16,7 @@ # under the License. from watcherclient.common import http -from watcherclient.v1 import action -from watcherclient.v1 import action_plan -from watcherclient.v1 import audit -from watcherclient.v1 import audit_template -from watcherclient.v1 import goal -from watcherclient.v1 import metric_collector -from watcherclient.v1 import strategy +from watcherclient import v1 class Client(object): @@ -37,14 +31,14 @@ class Client(object): def __init__(self, *args, **kwargs): """Initialize a new client for the Watcher v1 API.""" - self.http_client = http._construct_http_client(*args, **kwargs) - self.audit = audit.AuditManager(self.http_client) - self.audit_template = audit_template.AuditTemplateManager( - self.http_client) - self.action = action.ActionManager(self.http_client) - self.action_plan = action_plan.ActionPlanManager(self.http_client) - self.goal = goal.GoalManager(self.http_client) - self.metric_collector = metric_collector.MetricCollectorManager( - self.http_client - ) - self.strategy = strategy.StrategyManager(self.http_client) + self.http_client = self.build_http_client(*args, **kwargs) + self.audit = v1.AuditManager(self.http_client) + self.audit_template = v1.AuditTemplateManager(self.http_client) + self.action = v1.ActionManager(self.http_client) + self.action_plan = v1.ActionPlanManager(self.http_client) + self.goal = v1.GoalManager(self.http_client) + self.strategy = v1.StrategyManager(self.http_client) + # self.metric_collector = v1.MetricCollectorManager(self.http_client) + + def build_http_client(self, *args, **kwargs): + return http._construct_http_client(*args, **kwargs) diff --git a/watcherclient/v1/osc/__init__.py b/watcherclient/v1/osc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/watcherclient/v1/osc/goal_shell.py b/watcherclient/v1/osc/goal_shell.py new file mode 100644 index 0000000..c97d857 --- /dev/null +++ b/watcherclient/v1/osc/goal_shell.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 Red Hat, Inc. +# 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. + +from openstackclient.common import utils + +from watcherclient._i18n import _ +from watcherclient.common import command +from watcherclient.common import utils as common_utils +from watcherclient import exceptions +from watcherclient.v1 import resource_fields as res_fields + + +class ShowGoal(command.ShowOne): + """Show detailed information about a given goal.""" + + def get_parser(self, prog_name): + parser = super(ShowGoal, self).get_parser(prog_name) + parser.add_argument( + 'goal', + metavar='', + help=_('UUID or name of the goal'), + ) + return parser + + def take_action(self, parsed_args): + client = getattr(self.app.client_manager, "infra-optim") + + try: + goal = client.goal.get(parsed_args.goal) + except exceptions.HTTPNotFound as exc: + raise exceptions.CommandError(str(exc)) + + columns = res_fields.GOAL_FIELDS + column_headers = res_fields.GOAL_FIELD_LABELS + + return column_headers, utils.get_item_properties(goal, columns) + + +class ListGoal(command.Lister): + """List information on retrieved goals.""" + + def get_parser(self, prog_name): + parser = super(ListGoal, self).get_parser(prog_name) + parser.add_argument( + '--detail', + dest='detail', + action='store_true', + default=False, + help=_("Show detailed information about metric collectors.")) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_('Maximum number of goals to return per request, ' + '0 for no limit. Default is the maximum number used ' + 'by the Watcher API Service.')) + parser.add_argument( + '--sort-key', + metavar='', + help=_('Goal field that will be used for sorting.')) + parser.add_argument( + '--sort-dir', + metavar='', + choices=['asc', 'desc'], + help=_('Sort direction: "asc" (the default) or "desc".')) + + return parser + + def take_action(self, parsed_args): + client = getattr(self.app.client_manager, "infra-optim") + + if parsed_args.detail: + fields = res_fields.GOAL_FIELDS + field_labels = res_fields.GOAL_FIELD_LABELS + else: + fields = res_fields.GOAL_SHORT_LIST_FIELDS + field_labels = res_fields.GOAL_SHORT_LIST_FIELD_LABELS + + params = {} + params.update( + common_utils.common_params_for_list( + parsed_args, fields, field_labels)) + + try: + data = client.goal.list(**params) + except exceptions.HTTPNotFound as ex: + raise exceptions.CommandError(str(ex)) + + return (field_labels, + (utils.get_item_properties(item, fields) for item in data)) diff --git a/watcherclient/watcher.py b/watcherclient/watcher.py new file mode 100644 index 0000000..f0f26b7 --- /dev/null +++ b/watcherclient/watcher.py @@ -0,0 +1,346 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# 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 Watcher API. +""" +from collections import namedtuple +import logging +import sys + +from cliff import app +from cliff import command +from cliff import commandmanager +from cliff import complete +from cliff import help as cli_help +from keystoneclient.auth.identity import v2 +from keystoneclient.auth.identity import v3 +from keystoneclient import discover +from keystoneclient import exceptions as ks_exc +from keystoneclient import session +from openstackclient.common import logs +from openstackclient.common import utils +import six.moves.urllib.parse as urlparse + +from watcherclient._i18n import _ +from watcherclient import exceptions as exc +from watcherclient import version + +LOG = logging.getLogger(__name__) + + +API_NAME = 'infra-optim' +API_VERSIONS = { + '1': 'watcherclient.v1.client.Client', +} +_DEFAULT_IDENTITY_API_VERSION = '3' +_IDENTITY_API_VERSION_2 = ['2', '2.0'] +_IDENTITY_API_VERSION_3 = ['3'] + + +class WatcherShell(app.App): + """Watcher command line interface.""" + + log = logging.getLogger(__name__) + + def __init__(self, **kwargs): + self.client = None + + # Patch command.Command to add a default auth_required = True + command.Command.auth_required = True + + # Some commands do not need authentication + cli_help.HelpCommand.auth_required = False + complete.CompleteCommand.auth_required = False + + super(WatcherShell, self).__init__( + description=__doc__.strip(), + version=version.__version__, + command_manager=commandmanager.CommandManager( + 'watcherclient.v1'), + deferred_help=True, + **kwargs + ) + + def create_client(self, args): + service_type = 'infra-optim' + project_id = args.os_project_id or args.os_tenant_id + project_name = args.os_project_name or args.os_tenant_name + + keystone_session = session.Session.load_from_cli_options(args) + + kwargs = { + 'username': args.os_username, + 'user_domain_id': args.os_user_domain_id, + 'user_domain_name': args.os_user_domain_name, + 'password': args.os_password, + 'auth_token': args.os_auth_token, + 'project_id': project_id, + 'project_name': project_name, + 'project_domain_id': args.os_project_domain_id, + 'project_domain_name': args.os_project_domain_name, + } + keystone_auth = self._get_keystone_auth(keystone_session, + args.os_auth_url, + **kwargs) + region_name = args.os_region_name + endpoint = keystone_auth.get_endpoint(keystone_session, + service_type=service_type, + region_name=region_name) + + endpoint_type = args.os_endpoint_type or 'publicURL' + kwargs = { + 'auth_url': args.os_auth_url, + 'session': keystone_session, + 'auth': keystone_auth, + 'service_type': service_type, + 'endpoint_type': endpoint_type, + 'region_name': args.os_region_name, + 'username': args.os_username, + 'password': args.os_password, + } + + watcher_client = utils.get_client_class( + API_NAME, + args.watcher_api_version or 1, + API_VERSIONS) + LOG.debug('Instantiating infra-optim client: %s', watcher_client) + + client = watcher_client(args.watcher_api_version, endpoint, **kwargs) + + return client + + def _discover_auth_versions(self, session, auth_url): + # discover the API versions the server is supporting base on the + # given URL + v2_auth_url = None + v3_auth_url = None + try: + ks_discover = discover.Discover(session=session, auth_url=auth_url) + v2_auth_url = ks_discover.url_for('2.0') + v3_auth_url = ks_discover.url_for('3.0') + except ks_exc.ClientException: + # Identity service may not support discover API version. + # Let's try to figure out the API version from the original URL. + url_parts = urlparse.urlparse(auth_url) + (scheme, netloc, path, params, query, fragment) = url_parts + path = path.lower() + if path.startswith('/v3'): + v3_auth_url = auth_url + elif path.startswith('/v2'): + v2_auth_url = auth_url + else: + # not enough information to determine the auth version + msg = _('Unable to determine the Keystone version ' + 'to authenticate with using the given ' + 'auth_url. Identity service may not support API ' + 'version discovery. Please provide a versioned ' + 'auth_url instead. %s') % auth_url + raise exc.CommandError(msg) + + return (v2_auth_url, v3_auth_url) + + def _get_keystone_v3_auth(self, v3_auth_url, **kwargs): + auth_token = kwargs.pop('auth_token', None) + if auth_token: + return v3.Token(v3_auth_url, auth_token) + else: + return v3.Password(v3_auth_url, **kwargs) + + def _get_keystone_v2_auth(self, v2_auth_url, **kwargs): + auth_token = kwargs.pop('auth_token', None) + if auth_token: + return v2.Token( + v2_auth_url, + auth_token, + tenant_id=kwargs.pop('project_id', None), + tenant_name=kwargs.pop('project_name', None)) + else: + return v2.Password( + v2_auth_url, + username=kwargs.pop('username', None), + password=kwargs.pop('password', None), + tenant_id=kwargs.pop('project_id', None), + tenant_name=kwargs.pop('project_name', None)) + + def _get_keystone_auth(self, session, auth_url, **kwargs): + # FIXME(dhu): this code should come from keystoneclient + + # discover the supported keystone versions using the given url + (v2_auth_url, v3_auth_url) = self._discover_auth_versions( + session=session, + auth_url=auth_url) + + # Determine which authentication plugin to use. First inspect the + # auth_url to see the supported version. If both v3 and v2 are + # supported, then use the highest version if possible. + auth = None + if v3_auth_url and v2_auth_url: + user_domain_name = kwargs.get('user_domain_name', None) + user_domain_id = kwargs.get('user_domain_id', None) + project_domain_name = kwargs.get('project_domain_name', None) + project_domain_id = kwargs.get('project_domain_id', None) + + # support both v2 and v3 auth. Use v3 if domain information is + # provided. + if (user_domain_name or user_domain_id or project_domain_name or + project_domain_id): + auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs) + else: + auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs) + elif v3_auth_url: + # support only v3 + auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs) + elif v2_auth_url: + # support only v2 + auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs) + else: + raise exc.CommandError( + _('Unable to determine the Keystone version ' + 'to authenticate with using the given ' + 'auth_url.')) + + return auth + + def build_option_parser(self, description, version, argparse_kwargs=None): + """Introduces global arguments for the application. + + This is inherited from the framework. + """ + parser = super(WatcherShell, self).build_option_parser( + description, version, argparse_kwargs) + parser.add_argument('--no-auth', '-N', action='store_true', + help='Do not use authentication.') + parser.add_argument('--os-identity-api-version', + metavar='', + default=utils.env('OS_IDENTITY_API_VERSION'), + help='Specify Identity API version to use. ' + 'Defaults to env[OS_IDENTITY_API_VERSION]' + ' or 3.') + parser.add_argument('--os-auth-url', '-A', + metavar='', + default=utils.env('OS_AUTH_URL'), + help='Defaults to env[OS_AUTH_URL].') + parser.add_argument('--os-region-name', '-R', + metavar='', + default=utils.env('OS_REGION_NAME'), + help='Defaults to env[OS_REGION_NAME].') + parser.add_argument('--os-username', '-U', + metavar='', + default=utils.env('OS_USERNAME'), + help='Defaults to env[OS_USERNAME].') + parser.add_argument('--os-user-id', + metavar='', + default=utils.env('OS_USER_ID'), + help='Defaults to env[OS_USER_ID].') + parser.add_argument('--os-password', '-P', + metavar='', + default=utils.env('OS_PASSWORD'), + help='Defaults to env[OS_PASSWORD].') + parser.add_argument('--os-user-domain-id', + metavar='', + default=utils.env('OS_USER_DOMAIN_ID'), + help='Defaults to env[OS_USER_DOMAIN_ID].') + parser.add_argument('--os-user-domain-name', + metavar='', + default=utils.env('OS_USER_DOMAIN_NAME'), + help='Defaults to env[OS_USER_DOMAIN_NAME].') + parser.add_argument('--os-tenant-name', '-T', + metavar='', + default=utils.env('OS_TENANT_NAME'), + help='Defaults to env[OS_TENANT_NAME].') + parser.add_argument('--os-tenant-id', '-I', + metavar='', + default=utils.env('OS_TENANT_ID'), + help='Defaults to env[OS_TENANT_ID].') + parser.add_argument('--os-project-id', + metavar='', + default=utils.env('OS_PROJECT_ID'), + help='Another way to specify tenant ID. ' + 'This option is mutually exclusive with ' + ' --os-tenant-id. ' + 'Defaults to env[OS_PROJECT_ID].') + parser.add_argument('--os-project-name', + metavar='', + default=utils.env('OS_PROJECT_NAME'), + help='Another way to specify tenant name. ' + 'This option is mutually exclusive with ' + ' --os-tenant-name. ' + 'Defaults to env[OS_PROJECT_NAME].') + parser.add_argument('--os-project-domain-id', + metavar='', + default=utils.env('OS_PROJECT_DOMAIN_ID'), + help='Defaults to env[OS_PROJECT_DOMAIN_ID].') + parser.add_argument('--os-project-domain-name', + metavar='', + default=utils.env('OS_PROJECT_DOMAIN_NAME'), + help='Defaults to env[OS_PROJECT_DOMAIN_NAME].') + parser.add_argument('--os-auth-token', + metavar='', + default=utils.env('OS_AUTH_TOKEN'), + help='Defaults to env[OS_AUTH_TOKEN].') + parser.add_argument('--watcher-api-version', + metavar='', + default=utils.env('WATCHER_API_VERSION'), + help='Defaults to env[WATCHER_API_VERSION].') + parser.add_argument('--os-endpoint-type', + default=utils.env('OS_ENDPOINT_TYPE'), + help='Defaults to env[OS_ENDPOINT_TYPE] or ' + '"publicURL"') + parser.epilog = ('See "watcher help COMMAND" for help ' + 'on a specific command.') + session.Session.register_cli_options(parser) + return parser + + def configure_logging(self): + """Configure logging for the app.""" + self.log_configurator = logs.LogConfigurator(self.options) + self.dump_stack_trace = self.log_configurator.dump_trace + + def prepare_to_run_command(self, cmd): + """Prepares to run the command + + Checks if the minimal parameters are provided and creates the + client interface. + This is inherited from the framework. + """ + self.client_manager = namedtuple('ClientManager', 'infra_optim') + if cmd.auth_required: + client = self.create_client(self.options) + setattr(self.client_manager, 'infra-optim', client) + + def run(self, argv): + ret_val = 1 + self.command_options = argv + try: + ret_val = super(WatcherShell, self).run(argv) + return ret_val + except Exception as e: + if not logging.getLogger('').handlers: + logging.basicConfig() + self.log.error('Exception raised: %s', str(e)) + + return ret_val + + finally: + self.log.info("END return value: %s", ret_val) + + +def main(argv=sys.argv[1:]): + watcher_app = WatcherShell() + return watcher_app.run(argv) + +if __name__ == '__main__': # pragma: no cover + sys.exit(main(sys.argv[1:]))