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:
Alexander Chadin
2016-09-21 00:13:20 +03:00
parent a48a9213f0
commit 4e86151f81
8 changed files with 505 additions and 1 deletions

View File

@@ -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

View 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)

View 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')

View File

@@ -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")

View File

@@ -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)

View File

@@ -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']

View 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

View 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))