diff --git a/devstack/plugin.sh b/devstack/plugin.sh index c12692918..227e5d137 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -216,6 +216,12 @@ function configure_vitrage { else write_uwsgi_config "$VITRAGE_UWSGI_FILE" "$VITRAGE_PUBLIC_UWSGI" "/rca" fi + + if [[ ! -z "$VITRAGE_COORDINATION_URL" ]]; then + iniset $VITRAGE_CONF coordination backend_url "$VITRAGE_COORDINATION_URL" + elif is_service_enabled etcd3; then + iniset $VITRAGE_CONF coordination backend_url "etcd3://${SERVICE_HOST}:$ETCD_PORT" + fi } # init_vitrage() - Initialize etc. diff --git a/doc/source/contributor/vitrage-api.rst b/doc/source/contributor/vitrage-api.rst index 531172f15..e46c9bc73 100644 --- a/doc/source/contributor/vitrage-api.rst +++ b/doc/source/contributor/vitrage-api.rst @@ -1783,7 +1783,7 @@ Request Examples :: - DELETE /v1/resources/`` + DELETE /v1/webhook/`` Host: 127.0.0.1:8999 User-Agent: keystoneauth1/2.3.0 python-requests/2.9.1 CPython/2.7.6 Accept: application/json @@ -1799,4 +1799,133 @@ Response Body ============= Returns a success message if the webhook is deleted, otherwise an error -message is returned. \ No newline at end of file +message is returned. + + +Service list +^^^^^^^^^^^^ + +Lists the vitrage services present in the system + +GET /v1/services/ +~~~~~~~~~~~~~~~~~~ + +Headers +======= + +- X-Auth-Token (string, required) - Keystone auth token +- Accept (string) - application/json + +Path Parameters +=============== + +None. + +Query Parameters +================ + +None. + +Request Body +============ + +None. + +Request Examples +================ + +:: + + GET //v1/services/ HTTP/1.1 + Host: 135.248.19.18:8999 + X-Auth-Token: 2b8882ba2ec44295bf300aecb2caa4f7 + Accept: application/json + + + +ResponseStatus code +=================== + +- 200 - OK +- 404 - Not Found +- 500 - Service API not supported +- 500 - Failed to connect to coordination backend + +Response Body +============= + +Returns a JSON object with a list of all services. + +Response Examples +================= + +:: + + [ + { + "Created At": "2019-02-10T11:07:15+00:00", + "Hostname": "controller-1", + "Process Id": 23161, + "Name": "ApiWorker worker(0)" + }, + { + "Created At": "2019-02-10T11:07:15+00:00", + "Hostname": "controller-1", + "Process Id": 23153, + "Name": "EvaluatorWorker worker(0)" + }, + { + "Created At": "2019-02-10T11:07:15+00:00", + "Hostname": "controller-1", + "Process Id": 23155, + "Name": "EvaluatorWorker worker(1)" + }, + { + "Created At": "2019-02-10T11:07:15+00:00", + "Hostname": "controller-1", + "Process Id": 23157, + "Name": "EvaluatorWorker worker(2)" + }, + { + "Created At": "2019-02-10T11:07:15+00:00", + "Hostname": "controller-1", + "Process Id": 23158, + "Name": "EvaluatorWorker worker(3)" + }, + { + "Created At": "2019-02-10T11:07:33+00:00", + "Hostname": "controller-1", + "Process Id": 23366, + "Name": "MachineLearningService worker(0)" + }, + { + "Created At": "2019-02-10T11:07:35+00:00", + "Hostname": "controller-1", + "Process Id": 23475, + "Name": "PersistorService worker(0)" + }, + { + "Created At": "2019-02-10T11:07:15+00:00", + "Hostname": "controller-1", + "Process Id": 23164, + "Name": "SnmpParsingService worker(0)" + }, + { + "Created At": "2019-02-10T11:14:30+00:00", + "Hostname": "controller-1", + "Process Id": 25698, + "Name": "VitrageApi" + }, + { + "Created At": "2019-02-10T11:14:30+00:00", + "Hostname": "controller-1", + "Process Id": 25699, + "Name": "VitrageApi" + }, + { + "Created At": "2019-02-10T11:07:32+00:00", + "Hostname": "controller-1", + "Process Id": 23352, + "Name": "VitrageNotifierService worker(0)" + } + ] diff --git a/lower-constraints.txt b/lower-constraints.txt index cce90f9dd..769dd7c87 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -142,3 +142,6 @@ wrapt==1.10.11 futures==3.0.0 docutils==0.11 python-zaqarclient==1.2.0 +tooz==1.58.0 +zake==0.1.6 +psutil==5.4.3 diff --git a/openstack-common.conf b/openstack-common.conf deleted file mode 100644 index 75f7b4b6d..000000000 --- a/openstack-common.conf +++ /dev/null @@ -1,6 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from oslo-incubator.git - -# The base module to hold the copy of openstack.common -base=vitrage diff --git a/releasenotes/notes/add_service_list-d8e28adabc26f1cf.yaml b/releasenotes/notes/add_service_list-d8e28adabc26f1cf.yaml new file mode 100644 index 000000000..615f4860c --- /dev/null +++ b/releasenotes/notes/add_service_list-d8e28adabc26f1cf.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added a new API to list all vitrage services present in the system. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 457d9b157..2deac1fcb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,3 +52,5 @@ cotyledon>=1.6.8 # Apache-2.0 futures>=3.0.0;python_version=='2.7' or python_version=='2.6' # BSD pytz>=2013.6 # MIT tenacity>=4.9.0 +tooz>=1.58.0 # Apache-2.0 +psutil>=5.4.3 # BSD \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt index 1cee64349..b39f3082e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,3 +12,4 @@ testtools>=2.3.0 # MIT stestr>=2.0.0 # Apache-2.0 reno>=2.7.0 # Apache-2.0 mock>=2.0.0 # BSD +zake>=0.1.6 # Apache-2.0 diff --git a/vitrage/api/app.py b/vitrage/api/app.py index 685b6be65..df74543b0 100644 --- a/vitrage/api/app.py +++ b/vitrage/api/app.py @@ -41,7 +41,8 @@ def setup_app(root, conf=None): hooks.GCHook(), hooks.RPCHook(conf), hooks.ContextHook(), - hooks.DBHook(conf)] + hooks.DBHook(conf), + hooks.CoordinatorHook(conf)] app = pecan.make_app( root, diff --git a/vitrage/api/controllers/v1/root.py b/vitrage/api/controllers/v1/root.py index 138228124..29c51f41a 100644 --- a/vitrage/api/controllers/v1/root.py +++ b/vitrage/api/controllers/v1/root.py @@ -15,6 +15,7 @@ from vitrage.api.controllers.v1 import alarm from vitrage.api.controllers.v1 import event from vitrage.api.controllers.v1 import rca from vitrage.api.controllers.v1 import resource +from vitrage.api.controllers.v1 import service from vitrage.api.controllers.v1 import template from vitrage.api.controllers.v1 import topology from vitrage.api.controllers.v1 import webhook @@ -31,3 +32,4 @@ class V1Controller(object): webhook = webhook.WebhookController() template = template.TemplateController() event = event.EventController() + service = service.ServiceController() diff --git a/vitrage/api/controllers/v1/service.py b/vitrage/api/controllers/v1/service.py new file mode 100644 index 000000000..fb6790d10 --- /dev/null +++ b/vitrage/api/controllers/v1/service.py @@ -0,0 +1,49 @@ +# Copyright 2019 - Nokia 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. + +from operator import itemgetter + +from oslo_log import log +import pecan + +from pecan.core import abort +from vitrage.api.policy import enforce + +LOG = log.getLogger(__name__) + + +# noinspection PyBroadException +class ServiceController(object): + @pecan.expose('json') + def index(self): + return self.get() + + @pecan.expose('json') + def get(self): + enforce("get service list", pecan.request.headers, + pecan.request.enforcer, {}) + + LOG.info('received get service list') + + coordinator = pecan.request.coordinator + if not coordinator.backend_url: + abort(500, 'Service API not supported') + if not coordinator.is_active(): + abort(500, 'Failed to connect to coordination backend') + + try: + return sorted(coordinator.get_services(), key=itemgetter('name')) + except Exception: + LOG.exception('failed to get service list.') + abort(404, 'Failed to get service list.') diff --git a/vitrage/api/hooks.py b/vitrage/api/hooks.py index aef5bec4e..74d854cca 100644 --- a/vitrage/api/hooks.py +++ b/vitrage/api/hooks.py @@ -17,6 +17,7 @@ from oslo_policy import policy from pecan import hooks from vitrage.common import policies +from vitrage.coordination import coordination from vitrage import messaging from vitrage import rpc as vitrage_rpc from vitrage import storage @@ -95,3 +96,14 @@ class GCHook(hooks.PecanHook): def after(self, state): gc.collect() + + +class CoordinatorHook(hooks.PecanHook): + + def __init__(self, conf): + self.coordinator = coordination.Coordinator(conf) + self.coordinator.start() + self.coordinator.join_group() + + def before(self, state): + state.request.coordinator = self.coordinator diff --git a/vitrage/common/policies/__init__.py b/vitrage/common/policies/__init__.py index e584e36be..efcafda2a 100644 --- a/vitrage/common/policies/__init__.py +++ b/vitrage/common/policies/__init__.py @@ -16,6 +16,7 @@ from vitrage.common.policies import alarms from vitrage.common.policies import event from vitrage.common.policies import rca from vitrage.common.policies import resource +from vitrage.common.policies import service from vitrage.common.policies import template from vitrage.common.policies import topology from vitrage.common.policies import webhook @@ -29,5 +30,6 @@ def list_rules(): template.list_rules(), topology.list_rules(), resource.list_rules(), - webhook.list_rules() + webhook.list_rules(), + service.list_rules(), ) diff --git a/vitrage/common/policies/service.py b/vitrage/common/policies/service.py new file mode 100644 index 000000000..f0176702f --- /dev/null +++ b/vitrage/common/policies/service.py @@ -0,0 +1,37 @@ +# Copyright 2019 - Nokia 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. + +from oslo_policy import policy + +from vitrage.common.policies import base + +SERVICE = 'get service list' + +rules = [ + policy.DocumentedRuleDefault( + name=SERVICE, + check_str=base.UNPROTECTED, + description='Get the vitrage services for the OpenStack cluster', + operations=[ + { + 'path': '/service', + 'method': 'GET' + } + ] + ) +] + + +def list_rules(): + return rules diff --git a/vitrage/coordination/__init__.py b/vitrage/coordination/__init__.py new file mode 100644 index 000000000..a71b3a04c --- /dev/null +++ b/vitrage/coordination/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2019 - Nokia 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. + +from oslo_config import cfg + +OPTS = [ + cfg.StrOpt('backend_url', + help='The backend URL to use for the coordination service. If ' + 'left empty, membership api will not work') +] diff --git a/vitrage/coordination/coordination.py b/vitrage/coordination/coordination.py new file mode 100644 index 000000000..a1fb4d5fa --- /dev/null +++ b/vitrage/coordination/coordination.py @@ -0,0 +1,113 @@ +# Copyright 2019 - Nokia 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 json +import os +import psutil +import socket + +import six +import tenacity +import tooz.coordination + +from oslo_log import log +from oslo_utils import timeutils + +LOG = log.getLogger(__name__) + + +class Coordinator(object): + def __init__(self, conf, my_id=None): + self.conf = conf + self.backend_url = self.conf.coordination.backend_url + self.my_id = my_id or ' '.join(psutil.Process(os.getpid()).cmdline()) + self.coordinator = None + if self.backend_url: + self.coordinator = tooz.coordination.get_coordinator( + self.backend_url, six.b('%s_%s' % (my_id, os.getpid()))) + + def start(self): + if self.backend_url: + try: + self.coordinator.start(start_heart=True) + LOG.info('Coordination backend started successfully.') + except tooz.coordination.ToozError: + LOG.exception('Error connecting to coordination backend.') + + def stop(self): + if not self.is_active(): + return + try: + self.coordinator.stop() + except tooz.coordination.ToozError: + LOG.exception('Error connecting to coordination backend.') + + def is_active(self): + return self.coordinator and self.coordinator.is_started + + @tenacity.retry(stop=tenacity.stop_after_attempt(5)) + def join_group(self, group_id='vitrage'): + if not self.is_active() or not group_id: + return + + try: + now = timeutils.utcnow(with_timezone=True).replace(microsecond=0) + isoformat = now.isoformat() + + capabilities = json.dumps( + { + 'name': self.my_id, + 'hostname': socket.gethostname(), + 'process': os.getpid(), + 'created': isoformat + } + ) + join_req = self.coordinator.join_group(six.b(group_id), + six.b(capabilities)) + join_req.get() + + LOG.info('Joined service group:%s, member:%s', + group_id, self.my_id) + + return + except tooz.coordination.MemberAlreadyExist: + return + except tooz.coordination.GroupNotCreated as e: + create_grp_req = self.coordinator.create_group(six.b(group_id)) + + try: + create_grp_req.get() + except tooz.coordination.GroupAlreadyExist: + pass + + # Re-raise exception to join group again. + raise e + + def leave_group(self, group_id): + if self.is_active(): + self.coordinator.leave_group(six.b(group_id)) + LOG.info('Left group %s', group_id) + + def get_services(self, group_id='vitrage'): + if not self.is_active(): + return [] + + while True: + get_members_req = self.coordinator.get_members(six.b(group_id)) + try: + return [json.loads( + self.coordinator.get_member_capabilities( + six.b(group_id), member).get().decode('us-ascii')) + for member in get_members_req.get()] + except tooz.coordination.GroupNotCreated: + self.join_group(group_id) diff --git a/vitrage/coordination/service.py b/vitrage/coordination/service.py new file mode 100644 index 000000000..f84e221a9 --- /dev/null +++ b/vitrage/coordination/service.py @@ -0,0 +1,32 @@ +# Copyright 2019 - Nokia 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 cotyledon + +from vitrage.coordination.coordination import Coordinator + + +class Service(cotyledon.Service): + + def __init__(self, worker_id, conf): + super(Service, self).__init__(worker_id) + self.coordinator = Coordinator(conf, '%s worker(%s)' % (self.name, + worker_id)) + + def run(self): + self.coordinator.start() + self.coordinator.join_group() + + def terminate(self): + self.coordinator.stop() diff --git a/vitrage/entity_graph/workers.py b/vitrage/entity_graph/workers.py index 29cd61ad5..beb9582af 100644 --- a/vitrage/entity_graph/workers.py +++ b/vitrage/entity_graph/workers.py @@ -36,6 +36,7 @@ from vitrage.api_handler.apis.webhook import WebhookApis from vitrage.common.constants import TemplateStatus as TStatus from vitrage.common.constants import TemplateTypes as TType from vitrage.common.exception import VitrageError +from vitrage.coordination import service as coord from vitrage.entity_graph import EVALUATOR_TOPIC from vitrage.evaluator.actions.base import ActionMode from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator @@ -214,12 +215,12 @@ class GraphWorkersManager(cotyledon.ServiceManager): os._exit(0) -class GraphCloneWorkerBase(cotyledon.Service): +class GraphCloneWorkerBase(coord.Service): def __init__(self, worker_id, conf, task_queues): - super(GraphCloneWorkerBase, self).__init__(worker_id) + super(GraphCloneWorkerBase, self).__init__(worker_id, conf) self._conf = conf self._task_queue = task_queues[worker_id] self._entity_graph = NXGraph() @@ -232,6 +233,7 @@ class GraphCloneWorkerBase(cotyledon.Service): raise NotImplementedError def run(self): + super(GraphCloneWorkerBase, self).run() self._entity_graph.notifier._subscriptions = [] # Quick n dirty self._init_instance() if self._entity_graph.num_vertices(): diff --git a/vitrage/machine_learning/service.py b/vitrage/machine_learning/service.py index c0513828b..ba2c7bfbd 100644 --- a/vitrage/machine_learning/service.py +++ b/vitrage/machine_learning/service.py @@ -12,22 +12,22 @@ # License for the specific language governing permissions and limitations # under the License. -import cotyledon from oslo_log import log import oslo_messaging as oslo_m from oslo_utils import importutils +from vitrage.coordination import service as coord from vitrage import messaging from vitrage.opts import register_opts LOG = log.getLogger(__name__) -class MachineLearningService(cotyledon.Service): +class MachineLearningService(coord.Service): def __init__(self, worker_id, conf): - super(MachineLearningService, self).__init__(worker_id) + super(MachineLearningService, self).__init__(worker_id, conf) self.conf = conf self.machine_learning_plugins = self.get_machine_learning_plugins(conf) transport = messaging.get_transport(conf) @@ -38,6 +38,7 @@ class MachineLearningService(cotyledon.Service): [VitrageEventEndpoint(self.machine_learning_plugins)]) def run(self): + super(MachineLearningService, self).run() LOG.info("Vitrage Machine Learning Service - Starting...") self.listener.start() @@ -45,6 +46,7 @@ class MachineLearningService(cotyledon.Service): LOG.info("Vitrage Machine Learning Service - Started!") def terminate(self): + super(MachineLearningService, self).terminate() LOG.info("Vitrage Machine Learning Service - Stopping...") self.listener.stop() diff --git a/vitrage/notifier/service.py b/vitrage/notifier/service.py index c8ebe22ed..bb98d5f0f 100644 --- a/vitrage/notifier/service.py +++ b/vitrage/notifier/service.py @@ -11,26 +11,28 @@ # 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 cotyledon from oslo_log import log import oslo_messaging from oslo_utils import importutils +from vitrage.coordination import service as coord from vitrage import messaging from vitrage.opts import register_opts LOG = log.getLogger(__name__) -class VitrageNotifierService(cotyledon.Service): +class VitrageNotifierService(coord.Service): def __init__(self, worker_id, conf): - super(VitrageNotifierService, self).__init__(worker_id) + super(VitrageNotifierService, self).__init__(worker_id, conf) self.conf = conf self.notifiers = self.get_notifier_plugins(conf) self._init_listeners(self.conf) def run(self): + super(VitrageNotifierService, self).run() + LOG.info("Vitrage Notifier Service - Starting...") for listener in self.listeners: @@ -39,6 +41,7 @@ class VitrageNotifierService(cotyledon.Service): LOG.info("Vitrage Notifier Service - Started!") def terminate(self): + super(VitrageNotifierService, self).terminate() LOG.info("Vitrage Notifier Service - Stopping...") for listener in self.listeners: diff --git a/vitrage/opts.py b/vitrage/opts.py index 1e33a68c1..259998d01 100644 --- a/vitrage/opts.py +++ b/vitrage/opts.py @@ -19,6 +19,7 @@ from oslo_log import log from oslo_utils import importutils import vitrage.api +import vitrage.coordination import vitrage.datasources import vitrage.entity_graph.consistency import vitrage.evaluator @@ -63,6 +64,7 @@ def list_opts(): ('webhook', vitrage.notifier.plugins.webhook.OPTS), ('snmp_parsing', vitrage.snmp_parsing.OPTS), ('zaqar', vitrage.notifier.plugins.zaqar.OPTS), + ('coordination', vitrage.coordination.OPTS), ('DEFAULT', itertools.chain( vitrage.os_clients.OPTS, vitrage.rpc.OPTS, diff --git a/vitrage/persistency/service.py b/vitrage/persistency/service.py index 3c92eabcd..935f4ba50 100644 --- a/vitrage/persistency/service.py +++ b/vitrage/persistency/service.py @@ -17,7 +17,6 @@ from __future__ import print_function from datetime import timedelta from concurrent.futures import ThreadPoolExecutor -import cotyledon import dateutil.parser from futurist import periodics @@ -31,6 +30,7 @@ from vitrage.common.constants import HistoryProps as HProps from vitrage.common.constants import NotifierEventTypes as NETypes from vitrage.common.constants import VertexProperties as VProps from vitrage.common.utils import spawn +from vitrage.coordination import service as coord from vitrage import messaging from vitrage.storage.sqlalchemy import models from vitrage.utils.datetime import utcnow @@ -38,9 +38,9 @@ from vitrage.utils.datetime import utcnow LOG = log.getLogger(__name__) -class PersistorService(cotyledon.Service): +class PersistorService(coord.Service): def __init__(self, worker_id, conf, db_connection): - super(PersistorService, self).__init__(worker_id) + super(PersistorService, self).__init__(worker_id, conf) self.conf = conf self.db_connection = db_connection transport = messaging.get_transport(conf) @@ -52,6 +52,7 @@ class PersistorService(cotyledon.Service): self.scheduler = Scheduler(conf, db_connection) def run(self): + super(PersistorService, self).run() LOG.info("Vitrage Persistor Service - Starting...") self.listener.start() @@ -60,6 +61,7 @@ class PersistorService(cotyledon.Service): LOG.info("Vitrage Persistor Service - Started!") def terminate(self): + super(PersistorService, self).terminate() LOG.info("Vitrage Persistor Service - Stopping...") self.listener.stop() diff --git a/vitrage/snmp_parsing/service.py b/vitrage/snmp_parsing/service.py index e4ee20cfb..7686ec4f9 100644 --- a/vitrage/snmp_parsing/service.py +++ b/vitrage/snmp_parsing/service.py @@ -15,7 +15,6 @@ from datetime import datetime import json -import cotyledon from oslo_log import log import oslo_messaging from oslo_utils import uuidutils @@ -28,6 +27,7 @@ from pysnmp.proto.rfc1902 import Integer import sys from vitrage.common.constants import EventProperties +from vitrage.coordination import service as coord from vitrage.datasources.transformer_base import extract_field_value from vitrage.messaging import get_transport from vitrage.snmp_parsing.properties import SnmpEventProperties as SEProps @@ -36,16 +36,17 @@ from vitrage.utils.file import load_yaml_file LOG = log.getLogger(__name__) -class SnmpParsingService(cotyledon.Service): +class SnmpParsingService(coord.Service): RUN_FOREVER = 1 def __init__(self, worker_id, conf): - super(SnmpParsingService, self).__init__(worker_id) + super(SnmpParsingService, self).__init__(worker_id, conf) self.conf = conf self.listening_port = conf.snmp_parsing.snmp_listening_port self._init_oslo_notifier() def run(self): + super(SnmpParsingService, self).run() LOG.info("Vitrage SNMP Parsing Service - Starting...") transport_dispatcher = AsyncoreDispatcher() @@ -72,6 +73,7 @@ class SnmpParsingService(cotyledon.Service): raise def terminate(self): + super(SnmpParsingService, self).terminate() LOG.info("Vitrage SNMP Parsing Service - Stopping...") LOG.info("Vitrage SNMP Parsing Service - Stopped!") diff --git a/vitrage/tests/functional/api/v1/test_auth.py b/vitrage/tests/functional/api/v1/test_auth.py index 0200e3daf..d29b2fff2 100644 --- a/vitrage/tests/functional/api/v1/test_auth.py +++ b/vitrage/tests/functional/api/v1/test_auth.py @@ -20,7 +20,8 @@ import uuid from keystonemiddleware import fixture as ksm_fixture -from mock import mock +# noinspection PyPackageRequirements +import mock from vitrage.tests.functional.api.v1 import FunctionalTest EVENT_DETAILS = { diff --git a/vitrage/tests/functional/api/v1/test_basic.py b/vitrage/tests/functional/api/v1/test_basic.py index b757a2fc1..a8430fa36 100644 --- a/vitrage/tests/functional/api/v1/test_basic.py +++ b/vitrage/tests/functional/api/v1/test_basic.py @@ -18,7 +18,8 @@ import uuid from datetime import datetime -from mock import mock +# noinspection PyPackageRequirements +import mock from six.moves import http_client as httplib from vitrage.tests.functional.api.v1 import FunctionalTest diff --git a/vitrage/tests/functional/api/v1/test_keycloak.py b/vitrage/tests/functional/api/v1/test_keycloak.py index 4ea1bf304..18bdc25e2 100644 --- a/vitrage/tests/functional/api/v1/test_keycloak.py +++ b/vitrage/tests/functional/api/v1/test_keycloak.py @@ -14,9 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# noinspection PyPackageRequirements from datetime import datetime -from mock import mock +# noinspection PyPackageRequirements +import mock import requests_mock from vitrage.middleware.keycloak import KeycloakAuth from vitrage.tests.functional.api.v1 import FunctionalTest diff --git a/vitrage/tests/functional/api/v1/test_noauth.py b/vitrage/tests/functional/api/v1/test_noauth.py index 960e5695c..ae9a83ee1 100755 --- a/vitrage/tests/functional/api/v1/test_noauth.py +++ b/vitrage/tests/functional/api/v1/test_noauth.py @@ -17,7 +17,7 @@ from datetime import datetime # noinspection PyPackageRequirements -from mock import mock +import mock from vitrage.common.utils import compress_obj from vitrage.storage.sqlalchemy import models diff --git a/vitrage/tests/functional/api/v1/test_service.py b/vitrage/tests/functional/api/v1/test_service.py new file mode 100644 index 000000000..04a839d5b --- /dev/null +++ b/vitrage/tests/functional/api/v1/test_service.py @@ -0,0 +1,84 @@ +# Copyright 2019 - Nokia 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. + +from datetime import datetime +import os +import socket + +# noinspection PyPackageRequirements +from iso8601.iso8601 import UTC +# noinspection PyPackageRequirements +import mock +from oslo_utils import timeutils +# noinspection PyPackageRequirements +import webtest + +from vitrage.api import app +from vitrage.coordination.coordination import Coordinator +from vitrage.tests.functional.api.v1 import FunctionalTest + + +class ServiceTest(FunctionalTest): + + SERVICE_CREATION_TIME = datetime(2015, 1, 26, 12, 57, 4, tzinfo=UTC) + + def __init__(self, *args, **kwds): + super(ServiceTest, self).__init__(*args, **kwds) + self.auth = 'noauth' + + def test_get_services_no_backend(self): + resp = self.get_json('/service/', expect_errors=True) + self.assertEqual(500, resp.status_code) + self.assertIn('Service API not supported', resp.text) + + def test_get_services_no_connection_to_backend(self): + self._use_zake_as_backend() + with mock.patch('pecan.request') as request: + request.coordinator.is_active.return_value = False + resp = self.get_json('/service/', expect_errors=True) + + self.assertEqual(500, resp.status_code) + self.assertIn('Failed to connect to coordination backend', + resp.text) + + @mock.patch.object(timeutils, 'utcnow') + def test_get_services(self, utcnow): + now = self._mock_service_creation_time(utcnow) + + # NOTE(eyalb) we want to force coordinator to be initialized with a + # custom name otherwise it will take the command line string as a name + name = 'vitrage' + with mock.patch('vitrage.coordination.coordination.Coordinator', + new=lambda _: Coordinator(self.CONF, name)): + self._use_zake_as_backend() + + data = self.get_json('/service/') + + self.assert_list_equal([ + { + 'name': name, + 'hostname': socket.gethostname(), + 'process': os.getpid(), + 'created': now + } + ], data) + + def _mock_service_creation_time(self, utcnow): + utcnow.return_value = self.SERVICE_CREATION_TIME + return utcnow.return_value.isoformat() + + # noinspection PyAttributeOutsideInit + def _use_zake_as_backend(self): + self.CONF.set_override('backend_url', 'zake://', 'coordination') + self.app = webtest.TestApp(app.load_app(self.CONF)) diff --git a/vitrage/tests/unit/datasources/prometheus/test_prometheus_driver.py b/vitrage/tests/unit/datasources/prometheus/test_prometheus_driver.py index cc1343893..48151c008 100644 --- a/vitrage/tests/unit/datasources/prometheus/test_prometheus_driver.py +++ b/vitrage/tests/unit/datasources/prometheus/test_prometheus_driver.py @@ -12,8 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. -from mock import mock +# noinspection PyPackageRequirements +import mock from oslo_config import cfg +# noinspection PyPackageRequirements from testtools import matchers from vitrage.common.constants import DatasourceOpts as DSOpts @@ -61,12 +63,12 @@ class PrometheusDriverTest(base.BaseTest): # Test Action observed_valid_ip = driver._adjust_label_value(valid_ip) observed_not_ip = driver._adjust_label_value(not_ip) - observed_unvalid_ip = driver._adjust_label_value(invalid_ip) + observed_invalid_ip = driver._adjust_label_value(invalid_ip) # Test assertions self.assertEqual(hostname, observed_valid_ip) self.assertEqual(not_ip, observed_not_ip) - self.assertEqual(invalid_ip, observed_unvalid_ip) + self.assertEqual(invalid_ip, observed_invalid_ip) @mock.patch('socket.gethostbyaddr') def test_calculate_host_vitrage_entity_unique_props(self, mock_socket): diff --git a/vitrage/tests/unit/snmp_parsing/test_snmp_parsing.py b/vitrage/tests/unit/snmp_parsing/test_snmp_parsing.py index 7c827a879..06f2ec46a 100644 --- a/vitrage/tests/unit/snmp_parsing/test_snmp_parsing.py +++ b/vitrage/tests/unit/snmp_parsing/test_snmp_parsing.py @@ -124,6 +124,9 @@ class TestSnmpParsing(base.BaseTest): super(TestSnmpParsing, cls).setUpClass() cls.conf = cfg.ConfigOpts() cls.conf.register_opts(cls.OPTS, group='snmp_parsing') + cls.conf.register_opt( + cfg.StrOpt('backend_url', default='zake://'), group='coordination' + ) def test_convert_binds_to_dict(self): parsing_service = SnmpParsingService(1, self.conf)