Add service support
This patch set adds service support to watcherclient. There is two new commands: 1. service list 2. service show Change-Id: I669fd69ca7e854f8f576a017447ef003c6cf3d3b Partially-Implements: blueprint watcher-service-list
This commit is contained in:
@@ -61,6 +61,9 @@ openstack.infra_optim.v1 =
|
||||
optimize_scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
|
||||
optimize_scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
|
||||
|
||||
optimize_service_show = watcherclient.v1.service_shell:ShowService
|
||||
optimize_service_list = watcherclient.v1.service_shell:ListService
|
||||
|
||||
# The same as above but used by the 'watcher' command
|
||||
watcherclient.v1 =
|
||||
goal_show = watcherclient.v1.goal_shell:ShowGoal
|
||||
@@ -94,6 +97,9 @@ watcherclient.v1 =
|
||||
scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
|
||||
scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
|
||||
|
||||
service_show = watcherclient.v1.service_shell:ShowService
|
||||
service_list = watcherclient.v1.service_shell:ListService
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
||||
|
184
watcherclient/tests/v1/test_service.py
Normal file
184
watcherclient/tests/v1/test_service.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.service
|
||||
|
||||
SERVICE1 = {
|
||||
'id': 1,
|
||||
'name': 'watcher-applier',
|
||||
'host': 'controller',
|
||||
'status': 'ACTIVE',
|
||||
}
|
||||
|
||||
SERVICE2 = {
|
||||
'id': 2,
|
||||
'name': 'watcher-decision-engine',
|
||||
'host': 'controller',
|
||||
'status': 'FAILED',
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
'/v1/services':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1]},
|
||||
),
|
||||
},
|
||||
'/v1/services/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1]},
|
||||
)
|
||||
},
|
||||
'/v1/services/%s' % SERVICE1['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE1,
|
||||
),
|
||||
},
|
||||
'/v1/services/%s' % SERVICE1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE1,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_pagination = {
|
||||
'/v1/services':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1],
|
||||
"next": "http://127.0.0.1:6385/v1/services/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/services/?limit=1':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
'/v1/services/?sort_key=id':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1, SERVICE2]}
|
||||
),
|
||||
},
|
||||
'/v1/services/?sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE2, SERVICE1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ServiceManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ServiceManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
|
||||
def test_services_list(self):
|
||||
services = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/services', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(services))
|
||||
|
||||
def test_services_list_detail(self):
|
||||
services = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
('GET', '/v1/services/detail', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(services))
|
||||
|
||||
def test_services_list_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(limit=1)
|
||||
expect = [
|
||||
('GET', '/v1/services/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(services, matchers.HasLength(1))
|
||||
|
||||
def test_services_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(limit=0)
|
||||
expect = [
|
||||
('GET', '/v1/services', {}, None),
|
||||
('GET', '/v1/services/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(services, matchers.HasLength(2))
|
||||
|
||||
def test_services_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(sort_key='id')
|
||||
expect = [
|
||||
('GET', '/v1/services/?sort_key=id', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(services))
|
||||
|
||||
def test_services_list_sort_dir(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(sort_dir='desc')
|
||||
expect = [
|
||||
('GET', '/v1/services/?sort_dir=desc', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(services))
|
||||
|
||||
def test_services_show(self):
|
||||
service = self.mgr.get(SERVICE1['id'])
|
||||
expect = [
|
||||
('GET', '/v1/services/%s' % SERVICE1['id'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(SERVICE1['id'], service.id)
|
||||
|
||||
def test_services_show_by_name(self):
|
||||
service = self.mgr.get(SERVICE1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/services/%s' % SERVICE1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(SERVICE1['name'], service.name)
|
123
watcherclient/tests/v1/test_service_shell.py
Normal file
123
watcherclient/tests/v1/test_service_shell.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 Servionica
|
||||
#
|
||||
# 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 import shell
|
||||
from watcherclient.tests.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
SERVICE_1 = {
|
||||
'name': 'watcher-applier',
|
||||
'host': 'controller',
|
||||
'status': 'ACTIVE',
|
||||
'last_seen_up': None,
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
SERVICE_2 = {
|
||||
'name': 'watcher-decision-engine',
|
||||
'host': 'controller',
|
||||
'status': 'FAILED',
|
||||
'last_seen_up': None,
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class ServiceShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.SERVICE_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.SERVICE_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.SERVICE_FIELDS
|
||||
FIELD_LABELS = resource_fields.SERVICE_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_service_manager = mock.patch.object(resource, 'ServiceManager')
|
||||
self.m_service_mgr_cls = p_service_manager.start()
|
||||
self.addCleanup(p_service_manager.stop)
|
||||
|
||||
self.m_service_mgr = mock.Mock()
|
||||
self.m_service_mgr_cls.return_value = self.m_service_mgr
|
||||
|
||||
self.stdout = six.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_service_list(self):
|
||||
service1 = resource.Service(mock.Mock(), SERVICE_1)
|
||||
service2 = resource.Service(mock.Mock(), SERVICE_2)
|
||||
self.m_service_mgr.list.return_value = [
|
||||
service1, service2]
|
||||
|
||||
exit_code, results = self.run_cmd('service list')
|
||||
|
||||
for res in results:
|
||||
del res['ID']
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(service1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(service2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_service_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_service_list_detail(self):
|
||||
service1 = resource.Service(mock.Mock(), SERVICE_1)
|
||||
service2 = resource.Service(mock.Mock(), SERVICE_2)
|
||||
self.m_service_mgr.list.return_value = [
|
||||
service1, service2]
|
||||
|
||||
exit_code, results = self.run_cmd('service list --detail')
|
||||
|
||||
for res in results:
|
||||
del res['ID']
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(service1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(service2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_service_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_service_show_by_name(self):
|
||||
service = resource.Service(mock.Mock(), SERVICE_1)
|
||||
self.m_service_mgr.get.return_value = service
|
||||
|
||||
exit_code, result = self.run_cmd('service show watcher-applier')
|
||||
|
||||
del result['ID']
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(service, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_service_mgr.get.assert_called_once_with('watcher-applier')
|
@@ -20,6 +20,7 @@ from watcherclient.v1 import audit
|
||||
from watcherclient.v1 import audit_template
|
||||
from watcherclient.v1 import goal
|
||||
from watcherclient.v1 import scoring_engine
|
||||
from watcherclient.v1 import service
|
||||
from watcherclient.v1 import strategy
|
||||
|
||||
Action = action.Action
|
||||
@@ -34,6 +35,8 @@ Goal = goal.Goal
|
||||
GoalManager = goal.GoalManager
|
||||
ScoringEngine = scoring_engine.ScoringEngine
|
||||
ScoringEngineManager = scoring_engine.ScoringEngineManager
|
||||
Service = service.Service
|
||||
ServiceManager = service.ServiceManager
|
||||
Strategy = strategy.Strategy
|
||||
StrategyManager = strategy.StrategyManager
|
||||
|
||||
@@ -41,4 +44,4 @@ __all__ = (
|
||||
"Action", "ActionManager", "ActionPlan", "ActionPlanManager",
|
||||
"Audit", "AuditManager", "AuditTemplate", "AuditTemplateManager",
|
||||
"Goal", "GoalManager", "ScoringEngine", "ScoringEngineManager",
|
||||
"Strategy", "StrategyManager")
|
||||
"Service", "ServiceManager", "Strategy", "StrategyManager")
|
||||
|
@@ -38,6 +38,7 @@ class Client(object):
|
||||
self.action_plan = v1.ActionPlanManager(self.http_client)
|
||||
self.goal = v1.GoalManager(self.http_client)
|
||||
self.scoring_engine = v1.ScoringEngineManager(self.http_client)
|
||||
self.service = v1.ServiceManager(self.http_client)
|
||||
self.strategy = v1.StrategyManager(self.http_client)
|
||||
# self.metric_collector = v1.MetricCollectorManager(self.http_client)
|
||||
|
||||
|
@@ -118,3 +118,9 @@ SCORING_ENGINE_FIELD_LABELS = ['UUID', 'Name', 'Description', 'Metainfo']
|
||||
|
||||
SCORING_ENGINE_SHORT_LIST_FIELDS = ['uuid', 'name', 'description']
|
||||
SCORING_ENGINE_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Description']
|
||||
|
||||
# Services
|
||||
SERVICE_FIELDS = ['id', 'name', 'host', 'status', 'last_seen_up']
|
||||
SERVICE_FIELD_LABELS = ['ID', 'Name', 'Host', 'Status', 'Last seen up']
|
||||
SERVICE_SHORT_LIST_FIELDS = ['id', 'name', 'host', 'status']
|
||||
SERVICE_SHORT_LIST_FIELD_LABELS = ['ID', 'Name', 'Host', 'Status']
|
||||
|
78
watcherclient/v1/service.py
Normal file
78
watcherclient/v1/service.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Servionica
|
||||
#
|
||||
# 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.common import base
|
||||
from watcherclient.common import utils
|
||||
|
||||
|
||||
class Service(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Service %s>" % self._info
|
||||
|
||||
|
||||
class ServiceManager(base.Manager):
|
||||
resource_class = Service
|
||||
|
||||
@staticmethod
|
||||
def _path(service=None):
|
||||
return ('/v1/services/%s' % service
|
||||
if service else '/v1/services')
|
||||
|
||||
def list(self, limit=None, sort_key=None, sort_dir=None, detail=False):
|
||||
"""Retrieve a list of services.
|
||||
|
||||
:param limit: The maximum number of results to return per
|
||||
request, if:
|
||||
|
||||
1) limit > 0, the maximum number of services to return.
|
||||
2) limit == 0, return the entire list of services.
|
||||
3) limit param is NOT specified (None), the number of items
|
||||
returned respect the maximum imposed by the Watcher API
|
||||
(see Watcher's api.max_limit option).
|
||||
|
||||
:param sort_key: Optional, field used for sorting.
|
||||
|
||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||
default) or 'desc'.
|
||||
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about services.
|
||||
|
||||
:returns: A list of services.
|
||||
|
||||
"""
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(limit, sort_key, sort_dir)
|
||||
|
||||
path = ''
|
||||
if detail:
|
||||
path += 'detail'
|
||||
if filters:
|
||||
path += '?' + '&'.join(filters)
|
||||
|
||||
if limit is None:
|
||||
return self._list(self._path(path), "services")
|
||||
else:
|
||||
return self._list_pagination(self._path(path), "services",
|
||||
limit=limit)
|
||||
|
||||
def get(self, service):
|
||||
try:
|
||||
return self._list(self._path(service))[0]
|
||||
except IndexError:
|
||||
return None
|
103
watcherclient/v1/service_shell.py
Normal file
103
watcherclient/v1/service_shell.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 Servionica
|
||||
#
|
||||
# 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 osc_lib 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 ShowService(command.ShowOne):
|
||||
"""Show detailed information about a given service."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowService, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'service',
|
||||
metavar='<service>',
|
||||
help=_('ID or name of the service'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
try:
|
||||
service = client.service.get(parsed_args.service)
|
||||
except exceptions.HTTPNotFound as exc:
|
||||
raise exceptions.CommandError(str(exc))
|
||||
|
||||
columns = res_fields.SERVICE_FIELDS
|
||||
column_headers = res_fields.SERVICE_FIELD_LABELS
|
||||
|
||||
return column_headers, utils.get_item_properties(service, columns)
|
||||
|
||||
|
||||
class ListService(command.Lister):
|
||||
"""List information on retrieved services."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListService, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--detail',
|
||||
dest='detail',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_("Show detailed information about each service."))
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of services 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")
|
||||
|
||||
params = {}
|
||||
if parsed_args.detail:
|
||||
fields = res_fields.SERVICE_FIELDS
|
||||
field_labels = res_fields.SERVICE_FIELD_LABELS
|
||||
else:
|
||||
fields = res_fields.SERVICE_SHORT_LIST_FIELDS
|
||||
field_labels = res_fields.SERVICE_SHORT_LIST_FIELD_LABELS
|
||||
|
||||
params.update(
|
||||
common_utils.common_params_for_list(
|
||||
parsed_args, fields, field_labels))
|
||||
|
||||
try:
|
||||
data = client.service.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))
|
Reference in New Issue
Block a user