OpenStackClient plugin for goal
In this changeset, I implemented the OpenStackClient plugin which allows Watcher to be used via the "openstack" unified CLI. Partially Implements: blueprint openstackclient-plugin Change-Id: I1e0a3830eb15a9bd5014bbbf45962627d21fe7fa
This commit is contained in:
parent
cbc578998a
commit
5586bbdff3
@ -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
|
||||
|
13
setup.cfg
13
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
|
||||
|
||||
|
46
watcherclient/common/command.py
Normal file
46
watcherclient/common/command.py
Normal file
@ -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
|
64
watcherclient/plugin.py
Normal file
64
watcherclient/plugin.py
Normal file
@ -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='<infra-optim-api-version>',
|
||||
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
|
0
watcherclient/tests/v1/osc/__init__.py
Normal file
0
watcherclient/tests/v1/osc/__init__.py
Normal file
67
watcherclient/tests/v1/osc/base.py
Normal file
67
watcherclient/tests/v1/osc/base.py
Normal file
@ -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}
|
131
watcherclient/tests/v1/osc/test_goal_shell.py
Normal file
131
watcherclient/tests/v1/osc/test_goal_shell.py
Normal file
@ -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')
|
@ -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")
|
@ -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)
|
||||
|
0
watcherclient/v1/osc/__init__.py
Normal file
0
watcherclient/v1/osc/__init__.py
Normal file
104
watcherclient/v1/osc/goal_shell.py
Normal file
104
watcherclient/v1/osc/goal_shell.py
Normal file
@ -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='<goal>',
|
||||
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='<limit>',
|
||||
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='<field>',
|
||||
help=_('Goal field that will be used for sorting.'))
|
||||
parser.add_argument(
|
||||
'--sort-dir',
|
||||
metavar='<direction>',
|
||||
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))
|
346
watcherclient/watcher.py
Normal file
346
watcherclient/watcher.py
Normal file
@ -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='<identity-api-version>',
|
||||
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='<auth-url>',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL].')
|
||||
parser.add_argument('--os-region-name', '-R',
|
||||
metavar='<region-name>',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME].')
|
||||
parser.add_argument('--os-username', '-U',
|
||||
metavar='<auth-user-name>',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME].')
|
||||
parser.add_argument('--os-user-id',
|
||||
metavar='<auth-user-id>',
|
||||
default=utils.env('OS_USER_ID'),
|
||||
help='Defaults to env[OS_USER_ID].')
|
||||
parser.add_argument('--os-password', '-P',
|
||||
metavar='<auth-password>',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD].')
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
metavar='<auth-user-domain-id>',
|
||||
default=utils.env('OS_USER_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_ID].')
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
metavar='<auth-user-domain-name>',
|
||||
default=utils.env('OS_USER_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_NAME].')
|
||||
parser.add_argument('--os-tenant-name', '-T',
|
||||
metavar='<auth-tenant-name>',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
parser.add_argument('--os-tenant-id', '-I',
|
||||
metavar='<tenant-id>',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
parser.add_argument('--os-project-id',
|
||||
metavar='<auth-project-id>',
|
||||
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='<auth-project-name>',
|
||||
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='<auth-project-domain-id>',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
metavar='<auth-project-domain-name>',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||
parser.add_argument('--os-auth-token',
|
||||
metavar='<auth-token>',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
parser.add_argument('--watcher-api-version',
|
||||
metavar='<watcher-api-version>',
|
||||
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:]))
|
Loading…
Reference in New Issue
Block a user