From 461ef5e0880ab18a0fecab62e1251723ed46e469 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C4=93teris=20Caune?=
Date: Tue, 9 Jun 2020 15:16:39 +0300
Subject: [PATCH] Paused ping handling can be controlled via API. Fixes #376
---
CHANGELOG.md | 3 +++
hc/api/models.py | 1 +
hc/api/schemas.py | 1 +
hc/api/tests/test_create_check.py | 12 ++++++++++++
hc/api/tests/test_get_check.py | 3 ++-
hc/api/tests/test_update_check.py | 17 +++++++++++++++++
hc/api/views.py | 3 +++
hc/lib/jsonschema.py | 4 ++++
hc/lib/tests/test_jsonschema.py | 4 ++++
templates/docs/api.html | 28 ++++++++++++++++++++++++++++
templates/docs/api.md | 28 ++++++++++++++++++++++++++++
11 files changed, 103 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd9e1bb2..bd9a34ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file.
## Unreleased
+### Improvements
+- Paused ping handling can be controlled via API (#376)
+
### Bug Fixes
- Removing Pager Team integration, project appears to be discontinued
diff --git a/hc/api/models.py b/hc/api/models.py
index 4f31ad8d..59a97f95 100644
--- a/hc/api/models.py
+++ b/hc/api/models.py
@@ -217,6 +217,7 @@ class Check(models.Model):
"status": self.get_status(),
"last_ping": isostring(self.last_ping),
"next_ping": isostring(self.get_grace_start()),
+ "manual_resume": self.manual_resume,
}
if self.last_duration:
diff --git a/hc/api/schemas.py b/hc/api/schemas.py
index fd1a87da..b7117b9d 100644
--- a/hc/api/schemas.py
+++ b/hc/api/schemas.py
@@ -9,6 +9,7 @@ check = {
"schedule": {"type": "string", "format": "cron", "maxLength": 100},
"tz": {"type": "string", "format": "timezone", "maxLength": 36},
"channels": {"type": "string"},
+ "manual_resume": {"type": "boolean"},
"unique": {
"type": "array",
"items": {"enum": ["name", "tags", "timeout", "grace"]},
diff --git a/hc/api/tests/test_create_check.py b/hc/api/tests/test_create_check.py
index bc04f68c..1e74bdd6 100644
--- a/hc/api/tests/test_create_check.py
+++ b/hc/api/tests/test_create_check.py
@@ -225,3 +225,15 @@ class CreateCheckTestCase(BaseTestCase):
r = self.post({"api_key": "R" * 32, "name": "Foo"})
self.assertEqual(r.status_code, 401)
+
+ def test_it_sets_manual_resume(self):
+ r = self.post({"api_key": "X" * 32, "manual_resume": True})
+
+ self.assertEqual(r.status_code, 201)
+ check = Check.objects.get()
+ self.assertTrue(check.manual_resume)
+
+ def test_it_rejects_non_boolean_manual_resume(self):
+ r = self.post({"api_key": "X" * 32, "manual_resume": "surprise"})
+
+ self.assertEqual(r.status_code, 400)
diff --git a/hc/api/tests/test_get_check.py b/hc/api/tests/test_get_check.py
index f4ae91c8..1501c620 100644
--- a/hc/api/tests/test_get_check.py
+++ b/hc/api/tests/test_get_check.py
@@ -33,7 +33,7 @@ class GetCheckTestCase(BaseTestCase):
self.assertEqual(r["Access-Control-Allow-Origin"], "*")
doc = r.json()
- self.assertEqual(len(doc), 13)
+ self.assertEqual(len(doc), 14)
self.assertEqual(doc["timeout"], 3600)
self.assertEqual(doc["grace"], 900)
@@ -43,6 +43,7 @@ class GetCheckTestCase(BaseTestCase):
self.assertEqual(doc["status"], "new")
self.assertEqual(doc["channels"], str(self.c1.code))
self.assertEqual(doc["desc"], "This is description")
+ self.assertFalse(doc["manual_resume"])
def test_it_handles_invalid_uuid(self):
r = self.get("not-an-uuid")
diff --git a/hc/api/tests/test_update_check.py b/hc/api/tests/test_update_check.py
index c18eb54f..5e3a2c17 100644
--- a/hc/api/tests/test_update_check.py
+++ b/hc/api/tests/test_update_check.py
@@ -225,3 +225,20 @@ class UpdateCheckTestCase(BaseTestCase):
r = self.post(self.check.code, {"api_key": "R" * 32, "name": "Foo"})
self.assertEqual(r.status_code, 401)
+
+ def test_it_sets_manual_resume_to_true(self):
+ r = self.post(self.check.code, {"api_key": "X" * 32, "manual_resume": True})
+ self.assertEqual(r.status_code, 200)
+
+ self.check.refresh_from_db()
+ self.assertTrue(self.check.manual_resume)
+
+ def test_it_sets_manual_resume_to_false(self):
+ self.check.manual_resume = True
+ self.check.save()
+
+ r = self.post(self.check.code, {"api_key": "X" * 32, "manual_resume": False})
+ self.assertEqual(r.status_code, 200)
+
+ self.check.refresh_from_db()
+ self.assertFalse(self.check.manual_resume)
diff --git a/hc/api/views.py b/hc/api/views.py
index 3cd684a1..300ae376 100644
--- a/hc/api/views.py
+++ b/hc/api/views.py
@@ -92,6 +92,9 @@ def _update(check, spec):
if "desc" in spec:
check.desc = spec["desc"]
+ if "manual_resume" in spec:
+ check.manual_resume = spec["manual_resume"]
+
if "timeout" in spec and "schedule" not in spec:
check.kind = "simple"
check.timeout = td(seconds=spec["timeout"])
diff --git a/hc/lib/jsonschema.py b/hc/lib/jsonschema.py
index d8d1f0b0..8051975f 100644
--- a/hc/lib/jsonschema.py
+++ b/hc/lib/jsonschema.py
@@ -39,6 +39,10 @@ def validate(obj, schema, obj_name="value"):
if "maximum" in schema and obj > schema["maximum"]:
raise ValidationError("%s is too large" % obj_name)
+ elif schema.get("type") == "boolean":
+ if not isinstance(obj, bool):
+ raise ValidationError("%s is not a boolean" % obj_name)
+
elif schema.get("type") == "array":
if not isinstance(obj, list):
raise ValidationError("%s is not an array" % obj_name)
diff --git a/hc/lib/tests/test_jsonschema.py b/hc/lib/tests/test_jsonschema.py
index 302d6cb7..4049787b 100644
--- a/hc/lib/tests/test_jsonschema.py
+++ b/hc/lib/tests/test_jsonschema.py
@@ -82,3 +82,7 @@ class JsonSchemaTestCase(TestCase):
def test_it_checks_timezone_format(self):
with self.assertRaises(ValidationError):
validate("X/Y", {"type": "string", "format": "timezone"})
+
+ def test_it_checks_boolean_type(self):
+ with self.assertRaises(ValidationError):
+ validate(123, {"type": "boolean"})
diff --git a/templates/docs/api.html b/templates/docs/api.html
index 1cbd7552..faea2f74 100644
--- a/templates/docs/api.html
+++ b/templates/docs/api.html
@@ -117,6 +117,7 @@ specified value.
"status": "up",
"last_ping": "2020-03-24T14:02:03+00:00",
"next_ping": "2020-03-24T15:02:03+00:00",
+ "manual_resume": false,
"ping_url": "PING_ENDPOINT31365bce-8da9-4729-8ff3-aaa71d56b712",
"update_url": "SITE_ROOT/api/v1/checks/31365bce-8da9-4729-8ff3-aaa71d56b712",
"pause_url": "SITE_ROOT/api/v1/checks/31365bce-8da9-4729-8ff3-aaa71d56b712/pause",
@@ -132,6 +133,7 @@ specified value.
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"ping_url": "PING_ENDPOINT803f680d-e89b-492b-82ef-2be7b774a92d",
"update_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d",
"pause_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d/pause",
@@ -158,6 +160,7 @@ is added. This identifier is stable across API calls. Example:
"status": "up",
"last_ping": "2020-03-24T14:02:03+00:00",
"next_ping": "2020-03-24T15:02:03+00:00",
+ "manual_resume": false,
"unique_key": "a6c7b0a8a66bed0df66abfdab3c77736861703ee",
"timeout": 3600
},
@@ -170,6 +173,7 @@ is added. This identifier is stable across API calls. Example:
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"unique_key": "124f983e0e3dcaeba921cfcef46efd084576e783",
"schedule": "15 5 * * *",
"tz": "UTC"
@@ -208,6 +212,7 @@ is added. This identifier is stable across API calls. Example:
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"ping_url": "PING_ENDPOINT803f680d-e89b-492b-82ef-2be7b774a92d",
"update_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d",
"pause_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d/pause",
@@ -234,6 +239,7 @@ construct these URLs by itself.
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"unique_key": "124f983e0e3dcaeba921cfcef46efd084576e783",
"schedule": "15 5 * * *",
"tz": "UTC"
@@ -299,6 +305,15 @@ ignored and "schedule" will be used.
Example:
{"tz": "Europe/Riga"}
+manual_resume
+
+boolean, optional, default value: false.
+Controls whether a paused ping resumes automatically when pinged (the default),
+or not. If set to false, a paused check will leave the paused state when it receives
+a ping. If set to true, a paused check will ignore pings and stay paused until it is
+either manually resumed from the web dashboard or the manual_resume
flag is
+changed.
+
channels
string, optional
@@ -362,6 +377,7 @@ the limit is 20 checks per account.
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
@@ -427,6 +443,15 @@ ignored and "schedule" will be used.
Example:
{"tz": "Europe/Riga"}
+manual_resume
+
+boolean, optional, default value: false.
+Controls whether a paused ping resumes automatically when pinged (the default),
+or not. If set to false, a paused check will leave the paused state when it receives
+a ping. If set to true, a paused check will ignore pings and stay paused until it is
+either manually resumed from the web dashboard or the manual_resume
flag is
+changed.
+
channels
string, optional.
@@ -476,6 +501,7 @@ field values.
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
@@ -520,6 +546,7 @@ header is sometimes required by some network proxies and web servers.
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "paused",
@@ -561,6 +588,7 @@ check that was just deleted.
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
diff --git a/templates/docs/api.md b/templates/docs/api.md
index 3d6d7bee..42923080 100644
--- a/templates/docs/api.md
+++ b/templates/docs/api.md
@@ -97,6 +97,7 @@ curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/
"status": "up",
"last_ping": "2020-03-24T14:02:03+00:00",
"next_ping": "2020-03-24T15:02:03+00:00",
+ "manual_resume": false,
"ping_url": "PING_ENDPOINT31365bce-8da9-4729-8ff3-aaa71d56b712",
"update_url": "SITE_ROOT/api/v1/checks/31365bce-8da9-4729-8ff3-aaa71d56b712",
"pause_url": "SITE_ROOT/api/v1/checks/31365bce-8da9-4729-8ff3-aaa71d56b712/pause",
@@ -112,6 +113,7 @@ curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"ping_url": "PING_ENDPOINT803f680d-e89b-492b-82ef-2be7b774a92d",
"update_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d",
"pause_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d/pause",
@@ -139,6 +141,7 @@ is added. This identifier is stable across API calls. Example:
"status": "up",
"last_ping": "2020-03-24T14:02:03+00:00",
"next_ping": "2020-03-24T15:02:03+00:00",
+ "manual_resume": false,
"unique_key": "a6c7b0a8a66bed0df66abfdab3c77736861703ee",
"timeout": 3600
},
@@ -151,6 +154,7 @@ is added. This identifier is stable across API calls. Example:
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"unique_key": "124f983e0e3dcaeba921cfcef46efd084576e783",
"schedule": "15 5 * * *",
"tz": "UTC"
@@ -197,6 +201,7 @@ curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"ping_url": "PING_ENDPOINT803f680d-e89b-492b-82ef-2be7b774a92d",
"update_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d",
"pause_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d/pause",
@@ -227,6 +232,7 @@ construct these URLs by itself.
"status": "down",
"last_ping": "2020-03-23T10:19:32+00:00",
"next_ping": null,
+ "manual_resume": false,
"unique_key": "124f983e0e3dcaeba921cfcef46efd084576e783",
"schedule": "15 5 * * *",
"tz": "UTC"
@@ -305,6 +311,15 @@ tz
{"tz": "Europe/Riga"}
+manual_resume
+: boolean, optional, default value: false.
+
+ Controls whether a paused ping resumes automatically when pinged (the default),
+ or not. If set to false, a paused check will leave the paused state when it receives
+ a ping. If set to true, a paused check will ignore pings and stay paused until it is
+ either manually resumed from the web dashboard or the `manual_resume` flag is
+ changed.
+
channels
: string, optional
@@ -381,6 +396,7 @@ curl SITE_ROOT/api/v1/checks/ \
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
@@ -459,6 +475,15 @@ tz
{"tz": "Europe/Riga"}
+manual_resume
+: boolean, optional, default value: false.
+
+ Controls whether a paused ping resumes automatically when pinged (the default),
+ or not. If set to false, a paused check will leave the paused state when it receives
+ a ping. If set to true, a paused check will ignore pings and stay paused until it is
+ either manually resumed from the web dashboard or the `manual_resume` flag is
+ changed.
+
channels
: string, optional.
@@ -521,6 +546,7 @@ curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
@@ -575,6 +601,7 @@ header is sometimes required by some network proxies and web servers.
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "paused",
@@ -625,6 +652,7 @@ curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \
"n_pings": 0,
"name": "Backups",
"next_ping": null,
+ "manual_resume": false,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",