diff --git a/doc/source/webapi/v2/status.rst b/doc/source/webapi/v2/status.rst index 782d36e..85db748 100644 --- a/doc/source/webapi/v2/status.rst +++ b/doc/source/webapi/v2/status.rst @@ -17,7 +17,7 @@ Hosts .. rest-controller:: surveil.api.controllers.v2.status.hosts:HostController :webprefix: /v2/status/hosts/ -.. rest-controller:: surveil.api.controllers.v2.status.hosts.config:ConfigController +.. rest-controller:: surveil.api.controllers.v2.status.hosts:ConfigController :webprefix: /v2/status/hosts/(host_name)/config .. rest-controller:: surveil.api.controllers.v2.status.metrics:MetricsController @@ -38,6 +38,13 @@ Hosts .. rest-controller:: surveil.api.controllers.v2.logs.notifications:NotificationsController :webprefix: /v2/status/hosts/(host_name)/events/notifications +Services +======== + +.. rest-controller:: surveil.api.controllers.v2.status.services:ServicesController + :webprefix: /v2/status/services + + Metrics ======= @@ -45,4 +52,16 @@ Metrics :webprefix: /v2/status/metrics .. rest-controller:: surveil.api.controllers.v2.status.metrics:MetricController - :webprefix: /v2/status/metrics/ \ No newline at end of file + :webprefix: /v2/status/metrics/ + +Types +===== + +.. autotype:: surveil.api.datamodel.status.live_service.LiveService + :members: + +.. autotype:: surveil.api.datamodel.status.live_host.LiveHost + :members: + +.. autotype:: surveil.api.datamodel.status.live_query.LiveQuery + :members: diff --git a/requirements.txt b/requirements.txt index f492fd9..ce9068a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ oslo.middleware oslo.policy>=0.3.0 keystonemiddleware PasteDeploy +influxdb==2.0.1 diff --git a/surveil/api/config.py b/surveil/api/config.py index 0747092..91de77c 100644 --- a/surveil/api/config.py +++ b/surveil/api/config.py @@ -26,13 +26,15 @@ server = { # as long as is it in the same format. surveil_api_config = { "mongodb_uri": "mongodb://mongo:27017", - "ws_arbiter_url": "http://shinken:7760" + "ws_arbiter_url": "http://shinken:7760", + "influxdb_uri": "influxdb://root:root@influxdb:8086/db" } app_hooks = [ hooks.DBHook( surveil_api_config['mongodb_uri'], - surveil_api_config['ws_arbiter_url'] + surveil_api_config['ws_arbiter_url'], + surveil_api_config['influxdb_uri'] ) ] diff --git a/surveil/api/controllers/v2/status/__init__.py b/surveil/api/controllers/v2/status/__init__.py index eb590b5..a14102f 100644 --- a/surveil/api/controllers/v2/status/__init__.py +++ b/surveil/api/controllers/v2/status/__init__.py @@ -16,10 +16,11 @@ from pecan import rest from surveil.api.controllers.v2.status import hosts as v2_hosts from surveil.api.controllers.v2.status import metrics +from surveil.api.controllers.v2.status import services as v2_services class StatusController(rest.RestController): # events = EventsController() hosts = v2_hosts.HostsController() - # services = ServicesController() + services = v2_services.ServicesController() metrics = metrics.MetricsController() diff --git a/surveil/api/controllers/v2/status/hosts/__init__.py b/surveil/api/controllers/v2/status/hosts.py similarity index 64% rename from surveil/api/controllers/v2/status/hosts/__init__.py rename to surveil/api/controllers/v2/status/hosts.py index ceae6f6..92ec0c9 100644 --- a/surveil/api/controllers/v2/status/hosts/__init__.py +++ b/surveil/api/controllers/v2/status/hosts.py @@ -14,18 +14,30 @@ import pecan from pecan import rest +import wsmeext.pecan as wsme_pecan from surveil.api.controllers.v2 import logs -from surveil.api.controllers.v2.status.hosts import config from surveil.api.controllers.v2.status import metrics +from surveil.api.datamodel.status import live_host +from surveil.api.datamodel.status import live_query +from surveil.api.handlers.status import live_host_handler class HostsController(rest.RestController): - @pecan.expose() + @wsme_pecan.wsexpose([live_host.LiveHost]) def get_all(self): """Returns all hosts.""" - return "ALLL HOSSSSSSSST" + handler = live_host_handler.HostHandler(pecan.request) + hosts = handler.get_all() + return hosts + + @wsme_pecan.wsexpose([live_host.LiveHost], body=live_query.LiveQuery) + def post(self, query): + """Given a LiveQuery, returns all matching hosts.""" + handler = live_host_handler.HostHandler(pecan.request) + hosts = handler.get_all(live_query=query) + return hosts @pecan.expose() def _lookup(self, host_name, *remainder): @@ -37,7 +49,7 @@ class HostController(rest.RestController): # services = ServicesController() # See init for controller creation. We need host_name to instanciate it # externalcommands = ExternalCommandsController() - config = config.ConfigController() + # config = config.ConfigController() events = logs.LogsController() metrics = metrics.MetricsController() @@ -52,3 +64,11 @@ class HostController(rest.RestController): output = '{"host_name": "myhostname", "alias": %s}' % self._id return output + + +class ConfigController(rest.RestController): + + @pecan.expose() + def get_all(self): + """Returns config from a specific host.""" + return "Dump CONFIG" diff --git a/surveil/api/controllers/v2/status/hosts/config/__init__.py b/surveil/api/controllers/v2/status/hosts/config/__init__.py deleted file mode 100644 index 44bdc75..0000000 --- a/surveil/api/controllers/v2/status/hosts/config/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2014 - Savoir-Faire Linux 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. - -import pecan -from pecan import rest - - -class ConfigController(rest.RestController): - - @pecan.expose() - def get_all(self): - """Returns config from a specific host.""" - return "Dump CONFIG" \ No newline at end of file diff --git a/surveil/api/controllers/v2/status/services.py b/surveil/api/controllers/v2/status/services.py new file mode 100644 index 0000000..2615156 --- /dev/null +++ b/surveil/api/controllers/v2/status/services.py @@ -0,0 +1,38 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +import pecan +from pecan import rest +import wsmeext.pecan as wsme_pecan + +from surveil.api.datamodel.status import live_query +from surveil.api.datamodel.status import live_service +from surveil.api.handlers.status import live_service_handler + + +class ServicesController(rest.RestController): + + @wsme_pecan.wsexpose([live_service.LiveService]) + def get_all(self): + """Returns all services.""" + handler = live_service_handler.ServiceHandler(pecan.request) + services = handler.get_all() + return services + + @wsme_pecan.wsexpose([live_service.LiveService], body=live_query.LiveQuery) + def post(self, query): + """Given a LiveQuery, returns all matching services.""" + handler = live_service_handler.ServiceHandler(pecan.request) + services = handler.get_all(live_query=query) + return services diff --git a/surveil/api/datamodel/status/__init__.py b/surveil/api/datamodel/status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveil/api/datamodel/status/live_host.py b/surveil/api/datamodel/status/live_host.py new file mode 100644 index 0000000..b3bb830 --- /dev/null +++ b/surveil/api/datamodel/status/live_host.py @@ -0,0 +1,49 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +import wsme +import wsme.types as wtypes + +from surveil.api.controllers.v1.datamodel import types + + +class LiveHost(types.Base): + host_name = wsme.wsattr(wtypes.text, mandatory=False) + """The name of the host""" + + description = wsme.wsattr(wtypes.text, mandatory=False) + """The description of the host""" + + state = wsme.wsattr(int, mandatory=False) + """The current state of the host""" + + last_check = wsme.wsattr(int, mandatory=False) + """The last time the host was checked""" + + last_state_change = wsme.wsattr(int, mandatory=False) + """The last time the state has changed""" + + plugin_output = wsme.wsattr(wtypes.text, mandatory=False) + """Plugin output of the last check""" + + @classmethod + def sample(cls): + return cls( + host_name='CoolHost', + description='Very Nice Host', + state=0, + last_check=1429220785, + last_state_change=1429220785, + plugin_output='PING OK - Packet loss = 0%, RTA = 0.02 ms' + ) diff --git a/surveil/api/datamodel/status/live_query.py b/surveil/api/datamodel/status/live_query.py new file mode 100644 index 0000000..a7bad19 --- /dev/null +++ b/surveil/api/datamodel/status/live_query.py @@ -0,0 +1,42 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +import json + +import wsme +import wsme.types as wtypes + +from surveil.api.controllers.v1.datamodel import types + + +class LiveQuery(types.Base): + """Holds a sample query encoded in json.""" + + filters = wsme.wsattr(wtypes.text, mandatory=True) + "The filter expression encoded in json." + + fields = wsme.wsattr(wtypes.text, mandatory=True) + "List of fields to include in the response." + + @classmethod + def sample(cls): + return cls( + fields=json.dumps(['host_name', 'last_check']), + filters=json.dumps({ + "isnot": { + "state": ["0", "1"], + "host_state": ["2"] + } + }) + ) diff --git a/surveil/api/datamodel/status/live_service.py b/surveil/api/datamodel/status/live_service.py new file mode 100644 index 0000000..6675559 --- /dev/null +++ b/surveil/api/datamodel/status/live_service.py @@ -0,0 +1,53 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +import wsme +import wsme.types as wtypes + +from surveil.api.controllers.v1.datamodel import types + + +class LiveService(types.Base): + host_name = wsme.wsattr(wtypes.text, mandatory=False) + """The host for the service""" + + service_description = wsme.wsattr(wtypes.text, mandatory=False) + """The name of the service""" + + description = wsme.wsattr(wtypes.text, mandatory=False) + """The description of the sevice""" + + state = wsme.wsattr(int, mandatory=False) + """The current state of the service""" + + last_check = wsme.wsattr(int, mandatory=False) + """The last time the service was checked""" + + last_state_change = wsme.wsattr(int, mandatory=False) + """The last time the state has changed""" + + plugin_output = wsme.wsattr(wtypes.text, mandatory=False) + """Plugin output of the last check""" + + @classmethod + def sample(cls): + return cls( + host_name='Webserver', + service_name='Apache', + description='Serves Stuff', + state=0, + last_check=1429220785, + last_state_change=1429220785, + plugin_output='HTTP OK - GOT NICE RESPONSE' + ) diff --git a/surveil/api/handlers/status/__init__.py b/surveil/api/handlers/status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveil/api/handlers/status/liveQuery_filter.py b/surveil/api/handlers/status/liveQuery_filter.py new file mode 100644 index 0000000..29477f1 --- /dev/null +++ b/surveil/api/handlers/status/liveQuery_filter.py @@ -0,0 +1,52 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +import json + + +def filter_dict_list_with_live_query(item_list, live_query): + filters = json.loads(live_query.filters) + + matching_items = [] + + for item in item_list: + matches = True + + # Filters are, for example, 'isnot' or 'is' + for filter in filters.items(): + + # Fields are, for example, 'STATE' + for field in filter[1].items(): + + # Values are, for example, 0, 1, UP, Down... + for value in field[1]: + + if filter[0] == "isnot": + if item[field[0]] == value: + matches = False + break + elif filter[0] == "is": + if item[field[0]] != value: + matches = False + break + + if matches: + fields = json.loads(live_query.fields) + matching_item = {} + for field in fields: + matching_item[field] = item[field] + + matching_items.append(matching_item) + + return matching_items diff --git a/surveil/api/handlers/status/live_host_handler.py b/surveil/api/handlers/status/live_host_handler.py new file mode 100644 index 0000000..5ef85d2 --- /dev/null +++ b/surveil/api/handlers/status/live_host_handler.py @@ -0,0 +1,58 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +from __future__ import print_function + +from surveil.api.datamodel.status import live_host +from surveil.api.handlers import handler +from surveil.api.handlers.status import liveQuery_filter as query_filter + + +class HostHandler(handler.Handler): + """Fulfills a request on the live hosts.""" + + def get_all(self, live_query=None): + """Return all live hosts.""" + cli = self.request.influxdb_client + query = "SELECT * from HOST_STATE GROUP BY host_name LIMIT 1" + response = cli.query(query) + + host_dicts = [] + + for item in response.items(): + first_entry = next(item[1]) + + host_dict = { + "host_name": item[0][1]['host_name'], + "description": item[0][1]['host_name'], + "state": first_entry['state'], + "last_check": int(first_entry['last_check']), + "last_state_change": int(first_entry['last_state_change']), + "plugin_output": first_entry['output'] + } + + host_dicts.append(host_dict) + + if live_query: + host_dicts = query_filter.filter_dict_list_with_live_query( + host_dicts, + live_query + ) + + hosts = [] + for host_dict in host_dicts: + host = live_host.LiveHost(**host_dict) + hosts.append(host) + + return hosts diff --git a/surveil/api/handlers/status/live_service_handler.py b/surveil/api/handlers/status/live_service_handler.py new file mode 100644 index 0000000..4708c1c --- /dev/null +++ b/surveil/api/handlers/status/live_service_handler.py @@ -0,0 +1,64 @@ +# Copyright 2014 - Savoir-Faire Linux 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. + +from __future__ import print_function + +from surveil.api.datamodel.status import live_service +from surveil.api.handlers import handler +from surveil.api.handlers.status import liveQuery_filter as query_filter + + +class ServiceHandler(handler.Handler): + """Fulfills a request on live services.""" + + def get_all(self, live_query=None): + """Return all live services.""" + cli = self.request.influxdb_client + query = ( + "SELECT * from SERVICE_STATE " + "GROUP BY host_name, service_description " + "LIMIT 1" + ) + + response = cli.query(query) + + service_dicts = [] + + for item in response.items(): + first_entry = next(item[1]) + + service_dict = { + "service_description": item[0][1]['service_description'], + "host_name": item[0][1]['host_name'], + "description": item[0][1]['service_description'], + "state": first_entry['state'], + "last_check": int(first_entry['last_check']), + "last_state_change": int(first_entry['last_state_change']), + "plugin_output": first_entry['output'] + } + + service_dicts.append(service_dict) + + if live_query: + service_dicts = query_filter.filter_dict_list_with_live_query( + service_dicts, + live_query + ) + + services = [] + for service_dict in service_dicts: + service = live_service.LiveService(**service_dict) + services.append(service) + + return services diff --git a/surveil/api/hooks.py b/surveil/api/hooks.py index a79eb98..18a8ba2 100644 --- a/surveil/api/hooks.py +++ b/surveil/api/hooks.py @@ -12,21 +12,26 @@ # License for the specific language governing permissions and limitations # under the License. +import influxdb from pecan import hooks import pymongo class DBHook(hooks.PecanHook): - def __init__(self, mongo_url, ws_arbiter_url): + def __init__(self, mongo_url, ws_arbiter_url, influxdb_url): self.mongo_url = mongo_url self.ws_arbiter_url = ws_arbiter_url + self.influxdb_url = influxdb_url def before(self, state): self.mongoclient = pymongo.MongoClient(self.mongo_url) state.request.mongo_connection = self.mongoclient state.request.ws_arbiter_url = self.ws_arbiter_url + state.request.influxdb_client = influxdb.InfluxDBClient.from_DSN( + self.influxdb_url + ) def after(self, state): self.mongoclient.close() diff --git a/surveil/tests/api/controllers/v2/status/__init__.py b/surveil/tests/api/controllers/v2/status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveil/tests/api/controllers/v2/status/test_hosts.py b/surveil/tests/api/controllers/v2/status/test_hosts.py new file mode 100644 index 0000000..53c20cf --- /dev/null +++ b/surveil/tests/api/controllers/v2/status/test_hosts.py @@ -0,0 +1,93 @@ +# Copyright 2015 - Savoir-Faire Linux 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. + +import json + +import httpretty + +from surveil.tests.api import functionalTest + + +class TestStatusHosts(functionalTest.FunctionalTest): + + def setUp(self): + super(TestStatusHosts, self).setUp() + self.influxdb_response = ( + '{"results":[{"series":[{"name":"HOST_STATE","tags":{"host_nam' + 'e":"localhost"},"columns":["time","last_check","last_state_chan' + 'ge","output","state","state_type"],"values":[["201' + '5-04-19T01:09:24Z",1.429405764e+09,1.429405765316929e+09,"OK ' + '- localhost: rta 0.033ms, lost 0%",0,"HARD"]]},{"name":"' + 'HOST_STATE","tags":{"host_name":"test_keystone"},"columns":["' + 'time","last_check","last_state_change","output","state",' + '"state_type"],"values":[["2015-04-19T01:09:23Z",1.4294057' + '63e+09,1.429405765317144e+09,"OK - 127.0.0.1: rta 0.032ms, lo' + 'st 0%",0,"HARD"]]},{"name":"HOST_STATE","tags":{"host_na' + 'me":"ws-arbiter"},"columns":["time","last_check","last_state_ch' + 'ange","output","state","state_type"],"values":[["2' + '015-04-19T01:09:24Z",1.429405764e+09,1.429405765317063e+09,"O' + 'K - localhost: rta 0.030ms, lost 0%",0,"HARD"]]}]}]}' + ) + + @httpretty.activate + def test_get_all_hosts(self): + httpretty.register_uri(httpretty.GET, + "http://influxdb:8086/query", + body=self.influxdb_response) + + response = self.app.get("/v2/status/hosts") + + expected = [ + {"description": "localhost", + "last_state_change": 1429405765, + "plugin_output": "OK - localhost: rta 0.033ms, lost 0%", + "last_check": 1429405764, + "state": 0, + "host_name": "localhost"}, + {"description": "test_keystone", + "last_state_change": 1429405765, + "plugin_output": "OK - 127.0.0.1: rta 0.032ms, lost 0%", + "last_check": 1429405763, + "state": 0, + "host_name": "test_keystone"}, + {"description": "ws-arbiter", + "last_state_change": 1429405765, + "plugin_output": "OK - localhost: rta 0.030ms, lost 0%", + "last_check": 1429405764, + "state": 0, + "host_name": "ws-arbiter"}] + + self.assertEqual(json.loads(response.body), expected) + + @httpretty.activate + def test_query_hosts(self): + httpretty.register_uri(httpretty.GET, + "http://influxdb:8086/query", + body=self.influxdb_response) + + query = { + 'fields': json.dumps(['host_name', 'last_check']), + 'filters': json.dumps({ + "isnot": { + "host_name": ['localhost'], + "description": ["test_keystone"] + } + }) + } + + response = self.app.post_json("/v2/status/hosts", params=query) + + expected = [{"host_name": "ws-arbiter", "last_check": 1429405764}] + + self.assertEqual(json.loads(response.body), expected) diff --git a/surveil/tests/api/controllers/v2/status/test_services.py b/surveil/tests/api/controllers/v2/status/test_services.py new file mode 100644 index 0000000..c48e829 --- /dev/null +++ b/surveil/tests/api/controllers/v2/status/test_services.py @@ -0,0 +1,94 @@ +# Copyright 2015 - Savoir-Faire Linux 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. + +import json + +import httpretty + +from surveil.tests.api import functionalTest + + +class TestStatusServices(functionalTest.FunctionalTest): + + def setUp(self): + super(TestStatusServices, self).setUp() + self.influxdb_response = ( + '{"results":[{"series":[{"name":"SERVICE_STATE","tags":{"host_nam' + 'e":"test_keystone","service_description":"Check KeyStone service' + '."},"columns":["time","last_check","last_state_change","output",' + '"state","state_type"],"values":[["2015-04-19T18:20:34Z",1.429467' + '634e+09,1.429467636632134e+09,"There was no suitable authenticat' + 'ion url for this request",3,"SOFT"]]},{"name":"SERVICE_STATE","t' + 'ags":{"host_name":"ws-arbiter","service_description":"check-ws-a' + 'rbiter"},"columns":["time","last_check","last_state_change","out' + 'put","state","state_type"],"values":[["2015-04-19T18:20:33Z",1.4' + '29467633e+09,1.429467635629833e+09,"TCP OK - 0.000 second respon' + 'se time on port 7760",0,"HARD"]]}]}]}' + ) + + @httpretty.activate + def test_get_all_services(self): + httpretty.register_uri(httpretty.GET, + "http://influxdb:8086/query", + body=self.influxdb_response) + + response = self.app.get("/v2/status/services") + + expected = [ + {'description': 'Check KeyStone service.', + 'last_state_change': 1429467636, + 'plugin_output': + 'There was no suitable authentication url for this request', + 'last_check': 1429467634, + 'state': 3, + 'host_name': 'test_keystone', + 'service_description': 'Check KeyStone service.'}, + {'description': 'check-ws-arbiter', + 'last_state_change': 1429467635, + 'plugin_output': + 'TCP OK - 0.000 second response time on port 7760', + 'last_check': 1429467633, + 'state': 0, + 'host_name': 'ws-arbiter', + 'service_description': 'check-ws-arbiter'} + ] + + self.assertEqual(json.loads(response.body), expected) + + @httpretty.activate + def test_query_services(self): + httpretty.register_uri(httpretty.GET, + "http://influxdb:8086/query", + body=self.influxdb_response) + + query = { + 'fields': json.dumps(['host_name', 'service_description']), + 'filters': json.dumps({ + "isnot": { + "host_name": ['ws-arbiter'], + }, + "is": { + "service_description": ["Check KeyStone service."] + } + }) + } + + response = self.app.post_json("/v2/status/services", params=query) + + expected = [ + {'host_name': 'test_keystone', + 'service_description': 'Check KeyStone service.'} + ] + + self.assertEqual(json.loads(response.body), expected) diff --git a/surveil/tests/api/functionalTest.py b/surveil/tests/api/functionalTest.py index 2f98f97..9712f28 100644 --- a/surveil/tests/api/functionalTest.py +++ b/surveil/tests/api/functionalTest.py @@ -14,6 +14,7 @@ import os +import influxdb import mongomock from oslo_config import cfg import pecan @@ -37,20 +38,26 @@ class FunctionalTest(base.BaseTestCase): self.mongoconnection = mongomock.Connection() self.ws_arbiter_url = "http://localhost:7760" + self.influxdb_client = influxdb.InfluxDBClient.from_DSN( + 'influxdb://root:root@influxdb:8086/db' + ) class TestHook(hooks.PecanHook): - def __init__(self, mongoclient, wsarbiterurl): + def __init__(self, mongoclient, wsarbiterurl, influxdb_client): self.mongoclient = mongoclient self.ws_arbiter_url = wsarbiterurl + self.influxdb_client = influxdb_client def before(self, state): state.request.mongo_connection = self.mongoclient state.request.ws_arbiter_url = self.ws_arbiter_url + state.request.influxdb_client = self.influxdb_client app_hooks = [ TestHook( self.mongoconnection, - self.ws_arbiter_url + self.ws_arbiter_url, + self.influxdb_client ) ] diff --git a/surveil/tests/api/handlers/__init__.py b/surveil/tests/api/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveil/tests/api/handlers/live/__init__.py b/surveil/tests/api/handlers/live/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveil/tests/api/handlers/live/test_liveQuery_filter.py b/surveil/tests/api/handlers/live/test_liveQuery_filter.py new file mode 100644 index 0000000..5f35290 --- /dev/null +++ b/surveil/tests/api/handlers/live/test_liveQuery_filter.py @@ -0,0 +1,86 @@ +# Copyright 2015 - Savoir-Faire Linux 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. + +import json + +from surveil.api.datamodel.status import live_query +from surveil.api.handlers.status import liveQuery_filter as query_filter +from surveil.tests import base + + +class LiveQueryFilterTest(base.BaseTestCase): + + def setUp(self): + self.items = [ + {"description": "localhost", + "last_state_change": 1429400991, + "plugin_output": "OK - localhost: rta 0.047ms, lost 0%", + "last_check": 1429400990, + "state": 0, + "host_name": "localhost"}, + {"description": "test_keystone", + "last_state_change": 1429400986, + "plugin_output": "OK - 127.0.0.1: rta 0.045ms, lost 0%", + "last_check": 1429400984, "state": 2, + "host_name": "test_keystone"}, + {"description": "ws-arbiter", + "last_state_change": 1429400991, + "plugin_output": "OK - localhost: rta 0.042ms, lost 0%", + "last_check": 1429400990, + "state": 2, + "host_name": "ws-arbiter"} + ] + + def test_query_builder_filter_isnot(self): + query = live_query.LiveQuery( + fields=json.dumps(['host_name', 'last_check']), + filters=json.dumps({ + "isnot": { + "state": [0, 1], + "description": ["test_keystone"] + } + }) + ) + + result = query_filter.filter_dict_list_with_live_query( + self.items, + query + ) + + expected = [{"last_check": 1429400990, "host_name": "ws-arbiter"}] + + self.assertItemsEqual(result, expected) + + def test_query_builder_filter_is(self): + query = live_query.LiveQuery( + fields=json.dumps(['host_name']), + filters=json.dumps({ + "is": { + "state": [0], + "description": ["localhost"] + }, + "isnot": { + "state": [1] + } + }) + ) + + result = query_filter.filter_dict_list_with_live_query( + self.items, + query + ) + + expected = [{"host_name": "localhost"}] + + self.assertItemsEqual(result, expected)