# -*- coding: utf-8 -*- import anyjson from . import trigger, WorkerTests, BodyReceiver from twisted.internet.defer import inlineCallbacks, returnValue from twisted.web import http, client from twisted.web.http_headers import Headers from StringIO import StringIO from moira.checker import state class ApiTests(WorkerTests): @inlineCallbacks def request(self, method, url, content=None, state=http.OK, add_headers=None): body = None if content is None else client.FileBodyProducer( StringIO(content)) headers = {'Content-Type': ['application/json'], 'X-WebAuth-User': ['tester']} if add_headers: headers.update(add_headers) response = yield self.client.request(method, self.url_prefix + url, Headers(headers), body) self.assertEqual(state, response.code) body_receiver = BodyReceiver() response.deliverBody(body_receiver) body = yield body_receiver.finished if response.headers.getRawHeaders('content-type') == ['application/json']: body = anyjson.loads(body) returnValue((response, body)) @trigger("not-existing") @inlineCallbacks def testTriggerNotFound(self): response, body = yield self.request('GET', 'trigger/{0}'.format(self.trigger.id), state=http.NOT_FOUND) @trigger("throttling") @inlineCallbacks def testThrottling(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["DevOps.Metric"], \ "warn_value": "1e-7", "error_value": 50, "tags": ["tag1", "tag2"] }', ) yield self.db.setTriggerThrottling(self.trigger.id, self.now + 3600) yield self.db.addThrottledEvent(self.trigger.id, self.now + 3600, {'trigger_id': self.trigger.id}) response, json = yield self.request('GET', 'trigger/{0}/throttling'.format(self.trigger.id)) self.assertTrue(json['throttling']) response, json = yield self.request('DELETE', 'trigger/{0}/throttling'.format(self.trigger.id)) response, json = yield self.request('GET', 'trigger/{0}/throttling'.format(self.trigger.id)) self.assertFalse(json['throttling']) @inlineCallbacks def testPatternCleanup(self): response, body = yield self.request('PUT', 'trigger/name', '{"targets": ["DevOps.*.Metric"], \ "warn_value": 1, "error_value": 2}') patterns = yield self.db.getPatterns() self.assertEqual(list(patterns), ["DevOps.*.Metric"]) response, body = yield self.request('PUT', 'trigger/name', '{"targets": ["DevOps.*.OtherMetric"], \ "warn_value": 1, "error_value": 2}') patterns = yield self.db.getPatterns() self.assertEqual(list(patterns), ["DevOps.*.OtherMetric"]) response, body = yield self.request('DELETE', 'trigger/name') patterns = yield self.db.getPatterns() self.assertEqual(len(patterns), 0) @trigger("last-check-cleanup") @inlineCallbacks def testLastCheckCleanup(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"targets": ["aliasByNode(DevOps.*.Metric, 1)"], \ "warn_value": 1, "error_value": 2}') yield self.db.sendMetric('DevOps.*.Metric', 'DevOps.Node1.Metric', self.now - 60, 0) yield self.db.sendMetric('DevOps.*.Metric', 'DevOps.Node2.Metric', self.now - 60, 0) yield self.trigger.check() yield self.db.delPatternMetrics('DevOps.*.Metric') check = yield self.db.getTriggerLastCheck(self.trigger.id) self.assertTrue('Node1' in check['metrics']) yield self.db.sendMetric('DevOps.*.Metric', 'DevOps.Node1.Metric', self.now, 0) response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"targets": ["aliasByNode(DevOps.*.Metric, 1)"], \ "warn_value": 1, "error_value": 2}') check = yield self.db.getTriggerLastCheck(self.trigger.id) self.assertTrue('Node1' in check['metrics']) self.assertFalse('Node2' in check['metrics']) @trigger("delete-tag") @inlineCallbacks def testTagDeletion(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], \ "warn_value": "1e-7", "error_value": 50, "tags": ["tag1", "tag2"] }', ) response, body = yield self.request('GET', 'trigger/{0}'.format(self.trigger.id)) response, body = yield self.request('DELETE', 'tag/tag1', state=http.BAD_REQUEST) response, body = yield self.request('DELETE', 'trigger/{0}'.format(self.trigger.id)) response, body = yield self.request('DELETE', 'tag/tag1') @trigger("good-trigger") @inlineCallbacks def testTargetWithBracketsPUT(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["aliasByNode(KE.Databases.{Mirroring-1,AG}.*.IsSynchronized,3)"], \ "warn_value": "1e-7", "error_value": 50, "tags": ["tag1", "tag2"] }', ) @trigger("good-trigger") @inlineCallbacks def testTargetWithNonAsciiNamePUT(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "Тестовый триггер", "targets": ["Metric.One"], \ "warn_value": "1e-7", "error_value": 50, "tags": ["tag1", "tag2"] }', ) @trigger("good-trigger") @inlineCallbacks def testSimpleTriggerPUT(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], \ "warn_value": "1e-7", "error_value": 50, "tags": ["tag1", "tag2"] }', ) response, tags = yield self.request('GET', 'tag/stats') response, patterns = yield self.request('GET', 'pattern') self.assertEqual(2, len(tags["list"])) self.assertEqual(1, len(patterns["list"])) self.assertEqual(self.trigger.id, patterns["list"][0]["triggers"][0]["id"]) response, triggers = yield self.request('GET', 'trigger') self.assertEqual(1, len(triggers["list"])) @trigger("trigger-update") @inlineCallbacks def testTriggerUpdate(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], \ "warn_value": "1", "error_value": 50, "tags": ["tag1", "tag2"] }', ) response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], \ "warn_value": "1", "error_value": 50, "tags": ["tag1"] }', ) @trigger("good-trigger") @inlineCallbacks def testTriggersPaging(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], \ "warn_value": "1e-7", "error_value": 50, "tags": ["tag1", "tag2"] }') yield self.trigger.check() response, triggers = yield self.request('GET', 'trigger/page?p=0&size=10') self.assertEqual(1, len(triggers["list"])) self.assertEqual(0, triggers["page"]) self.assertEqual(10, triggers["size"]) self.assertEqual(1, triggers["total"]) response, triggers = yield self.request('GET', 'trigger/page?p=0&size=10', add_headers={'Cookie': ['moira_filter_tags=tag1; \ moira_filter_ok=true']}) self.assertEqual(1, len(triggers["list"])) self.assertEqual(1, triggers["total"]) response, triggers = yield self.request('GET', 'trigger/page?p=0&size=10', add_headers={'Cookie': ['moira_filter_tags=']}) self.assertEqual(1, len(triggers["list"])) self.assertEqual(1, triggers["total"]) response, triggers = yield self.request('GET', 'trigger/page?p=0&size=10', add_headers={'Cookie': ['moira_filter_tags=not-exising; \ moira_filter_ok=true']}) self.assertEqual(0, len(triggers["list"])) self.assertEqual(0, triggers["total"]) @trigger("expression-trigger") @inlineCallbacks def testExpressionTriggerPUT(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], \ "tags": ["tag1", "tag2"], "expression": "ERROR if t1 > 1 else OK" }', ) response, triggers = yield self.request('GET', 'trigger') self.assertEqual(1, len(triggers["list"])) @trigger("not-json-trigger") @inlineCallbacks def testSendNotJsonTrigger(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), "i am not json", http.BAD_REQUEST) self.flushLoggedErrors() self.assertEqual("Content is not json", body) @trigger("not-list-targets") @inlineCallbacks def testSendNotListTargets(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{ "name":"111", \ "targets":{"target":"DevOps.system.*.loadavg.load"}, \ "expression":"WARN if t1 > 10 else OK", \ "tags":"1111" }', http.BAD_REQUEST) self.flushLoggedErrors() self.assertEqual("Invalid trigger targets", body) @trigger("invalid-expression-trigger") @inlineCallbacks def testSendInvalidExpressionTrigger(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name":"test trigger","targets":["metric"], \ "warn_value":-0.1, "error_value":0.1,"ttl":600,"ttl_state":"NODATA", \ "tags":["tag1"],"expression":"ERROR if"}', http.BAD_REQUEST) self.flushLoggedErrors() self.assertEqual("Invalid expression", body) @trigger("wrong-time-span") @inlineCallbacks def testSendWrongTimeSpan(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["movingAverage(*, \\"10g\\")"], \ "warn_value": "1e-7", "error_value": 50}', http.BAD_REQUEST) self.flushLoggedErrors() self.assertEqual("Invalid graphite targets", body) @trigger("without-warn-value") @inlineCallbacks def testSendWithoutWarnValue(self): response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["sumSeries(*)"], "error_value": 50 }', http.BAD_REQUEST) self.flushLoggedErrors() self.assertEqual("warn_value is required", body) @trigger("test-events") @inlineCallbacks def testEvents(self): yield self.db.pushEvent({ "trigger_id": self.trigger.id, "state": state.OK, "old_state": state.WARN, "timestamp": self.now - 120, "metric": "test metric" }) yield self.db.pushEvent({ "trigger_id": self.trigger.id, "state": state.WARN, "old_state": state.OK, "timestamp": self.now, "metric": "test metric" }) response, events = yield self.request('GET', 'event/{0}'.format(self.trigger.id)) self.assertEqual(2, len(events['list'])) response, events = yield self.request('GET', 'event') self.assertEqual(2, len(events['list'])) @inlineCallbacks def testUserContact(self): contact = {'value': 'tester@company.com', 'type': 'email'} response, saved = yield self.request('PUT', 'contact', anyjson.dumps(contact)) contact['id'] = saved['id'] contact['user'] = 'tester' self.assertEqual(contact, saved) response, settings = yield self.request('GET', 'user/settings') self.assertEqual([contact], settings["contacts"]) response, settings = yield self.request('GET', 'user/settings') self.assertEqual(contact['id'], settings["contacts"][0]["id"]) response, body = yield self.request('DELETE', 'contact/' + str(contact['id'])) response, settings = yield self.request('GET', 'user/settings') self.assertEqual([], settings["contacts"]) @inlineCallbacks def testUserSubscriptions(self): contact = {'value': 'tester@company.com', 'type': 'email'} response, contact = yield self.request('PUT', 'contact', anyjson.dumps(contact)) response, sub = yield self.request('PUT', 'subscription', anyjson.dumps({ "contacts": [contact["id"]], "tags": ["devops", "tag1"] })) response, body = yield self.request('PUT', 'subscription/' + str(sub["id"]) + "/test") response, subscriptions = yield self.request('GET', 'subscription') self.assertEqual(sub['id'], subscriptions["list"][0]["id"]) response, settings = yield self.request('GET', 'user/settings') self.assertEqual(sub['id'], settings["subscriptions"][0]["id"]) subs = yield self.db.getTagSubscriptions("devops") self.assertEqual(sub["id"], subs[0]["id"]) subs = yield self.db.getTagSubscriptions("tag1") self.assertEqual(sub["id"], subs[0]["id"]) sub["tags"].remove("tag1") response, updated_sub = yield self.request('PUT', 'subscription', anyjson.serialize(sub)) subs = yield self.db.getTagSubscriptions("tag1") self.assertEqual(len(subs), 0) response, updated_sub = yield self.request('DELETE', 'subscription/' + str(sub["id"])) subs = yield self.db.getTagSubscriptions("devops") self.assertEqual(len(subs), 0) @inlineCallbacks def testUserContactDelete(self): contact = {'value': 'tester@company.com', 'type': 'email'} response, contact = yield self.request('PUT', 'contact', anyjson.dumps(contact)) response, sub = yield self.request('PUT', 'subscription', anyjson.dumps({ "contacts": [contact["id"]], "tags": ["devops", "tag1"] })) response, body = yield self.request('PUT', 'subscription/' + str(sub["id"]) + "/test") response, body = yield self.request('DELETE', 'contact/' + str(contact["id"])) response, subscriptions = yield self.request('GET', 'subscription') self.assertNotIn(contact['id'], subscriptions["list"][0]["contacts"]) @trigger("test-metrics") @inlineCallbacks def testMetricDeletion(self): pattern = "devops.functest.*" metric1 = "devops.functest.m1" metric2 = "devops.functest.m2" yield self.db.sendMetric(pattern, metric1, self.now - 60, 1) yield self.db.sendMetric(pattern, metric1, self.now, 2) yield self.db.sendMetric(pattern, metric2, self.now, 3) response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["' + pattern + '"], \ "warn_value": 5, "error_value": 10 }', ) response, metrics = yield self.request('GET', 'trigger/{0}/metrics?from={1}&to={2}' .format(self.trigger.id, self.now - 60, self.now)) self.assertEqual(2, len(metrics)) self.assertEqual([1, 2], [v['value'] for v in metrics[metric1]]) metrics = yield self.db.getPatternMetrics(pattern) self.assertTrue(metric1 in metrics) self.assertTrue(metric2 in metrics) yield self.trigger.check() check = yield self.db.getTriggerLastCheck(self.trigger.id) self.assertEqual(2, len(check['metrics'])) response, data = yield self.request('DELETE', 'trigger/{0}/metrics?name={1}' .format(self.trigger.id, metric1)) metrics = yield self.db.getPatternMetrics(pattern) self.assertFalse(metric1 in metrics) self.assertFalse(metric2 in metrics) check = yield self.db.getTriggerLastCheck(self.trigger.id) self.assertEqual(1, len(check['metrics'])) @trigger("test-trigger-maintenance") @inlineCallbacks def testTriggerMaintenance(self): metric = "devops.functest.m" yield self.db.sendMetric(metric, metric, self.now - 60, 0) response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["' + metric + '"], \ "warn_value": 0, "error_value": 1, "tags":["tag1"] }', ) response, _ = yield self.request('PUT', 'tag/tag1/data', anyjson.dumps({"maintenance": self.now})) yield self.trigger.check(now=self.now - 1) events, total = yield self.db.getEvents() self.assertEqual(0, total) response, _ = yield self.request('PUT', 'tag/tag1/data', anyjson.dumps({})) yield self.db.sendMetric(metric, metric, self.now, 1) yield self.trigger.check() events, total = yield self.db.getEvents() self.assertEqual(1, total) @trigger("test-trigger-maintenance2") @inlineCallbacks def testTriggerMaintenance2(self): metric = "devops.functest.m" yield self.db.sendMetric(metric, metric, self.now - 60, 1) response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["' + metric + '"], \ "warn_value": 1, "error_value": 2, "tags":["tag1"] }', ) response, _ = yield self.request('PUT', 'tag/tag1/data', anyjson.dumps({"maintenance": self.now})) yield self.trigger.check(now=self.now - 1) events, total = yield self.db.getEvents() self.assertEqual(0, total) response, _ = yield self.request('PUT', 'tag/tag1/data', anyjson.dumps({})) yield self.db.sendMetric(metric, metric, self.now, 0) yield self.db.sendMetric(metric, metric, self.now + 60, 1) yield self.db.sendMetric(metric, metric, self.now + 120, 1) yield self.trigger.check(now=self.now + 120) yield self.assert_trigger_metric(metric, 1, state.WARN) events, total = yield self.db.getEvents() self.assertEqual(2, total) @trigger("test-metric-maintenance") @inlineCallbacks def testMetricMaintenance(self): metric = "devops.functest.m" yield self.db.sendMetric(metric, metric, self.now - 60, 0) response, body = yield self.request('PUT', 'trigger/{0}'.format(self.trigger.id), '{"name": "test trigger", "targets": ["' + metric + '"], \ "warn_value": 0, "error_value": 1, "tags":["tag1"] }', ) yield self.trigger.check(now=self.now - 1) events, total = yield self.db.getEvents() self.assertEqual(1, total) response, _ = yield self.request('PUT', 'trigger/{0}/maintenance'.format(self.trigger.id), anyjson.dumps({metric: self.now})) yield self.db.sendMetric(metric, metric, self.now, 1) yield self.trigger.check() events, total = yield self.db.getEvents() self.assertEqual(1, total) yield self.db.sendMetric(metric, metric, self.now + 60, -1) yield self.trigger.check(now=self.now + 60) events, total = yield self.db.getEvents() self.assertEqual(2, total) @inlineCallbacks def testUserLogin(self): response, user = yield self.request('GET', 'user') self.assertEqual('tester', user["login"])