Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend healthcheck to fail when a single mapping is in a broken state #200

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def linkcode_resolve(domain, info):
pass
try:
lines, first_line = inspect.getsourcelines(item)
lineno = "#L%d-L%s" % (first_line, first_line + len(lines) - 1)
lineno = f"#L{first_line:d}-L{first_line + len(lines) - 1}"
except (OSError, TypeError):
pass
return (
Expand Down
17 changes: 17 additions & 0 deletions heroku_connect/contrib/heroku_connect_health_check/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,20 @@ def check_status(self):
)
)
)

connection_state = utils.get_connection(connection["id"], deep=True)

for mapping in connection_state["mappings"]:
object_name = mapping["object_name"]
state = mapping["state"]

if (
state in utils.ERROR_MAPPING_STATES
or state in utils.TEMPORARY_ERROR_MAPPING_STATES
):
self.add_error(
ServiceUnavailable(
f"mapping {object_name} on connection {connection['name']} "
f"is in state {state}"
)
)
66 changes: 65 additions & 1 deletion heroku_connect/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Utility methods for Django Heroku Connect."""

import os
from enum import Enum
from enum import Enum, unique
from functools import lru_cache

import requests
Expand Down Expand Up @@ -32,6 +32,70 @@ class ConnectionStates:
)


@unique
class MappingState(str, Enum):
# https://devcenter.heroku.com/articles/mapping-states-reference#list-of-mapping-states
DATA_SYNCED = "DATA_SYNCED"
INITIAL = "INITIAL"
RESYNC = "RESYNC"
RELOAD_TABLE = "RELOAD_TABLE"
SCHEMA_CHANGED = "SCHEMA_CHANGED"
POLLING_SF_CHANGES = "POLLING_SF_CHANGES"
POLLING_SF_BULK = "POLLING_SF_BULK"
POLLING_SF_FOR_DELETES = "POLLING_SF_FOR_DELETES"
WAIT_BULK_LOAD = "WAIT_BULK_LOAD"
LOADING_BULK_JOB = "LOADING_BULK_JOB"
RESOLVE_EXTERNAL_IDS = "RESOLVE_EXTERNAL_IDS"
BULK_LOAD_ERROR = "BULK_LOAD_ERROR"
WAIT_BULK_UPDATE = "WAIT_BULK_UPDATE"
APPLYING_BULK_UPDATE = "APPLYING_BULK_UPDATE"
POLL_EXTERNAL_IDS = "POLL_EXTERNAL_IDS"
RECOVERY = "RECOVERY"
ABORTED = "ABORTED"
SYSTEM_ERROR = "SYSTEM_ERROR"
DB_UNAVAILABLE = "DB_UNAVAILABLE"
BAD_CONFIG = "BAD_CONFIG"
SYNC_REMOVED = "SYNC_REMOVED"
INACTIVE_ORG = "INACTIVE_ORG"
UNAUTHORIZED = "UNAUTHORIZED"


OK_MAPPING_STATES = (
MappingState.DATA_SYNCED,
MappingState.RESYNC,
MappingState.RELOAD_TABLE,
MappingState.POLLING_SF_CHANGES,
MappingState.POLLING_SF_BULK,
MappingState.POLLING_SF_FOR_DELETES,
MappingState.WAIT_BULK_LOAD,
MappingState.LOADING_BULK_JOB,
MappingState.RESOLVE_EXTERNAL_IDS,
MappingState.WAIT_BULK_UPDATE,
MappingState.APPLYING_BULK_UPDATE,
MappingState.POLL_EXTERNAL_IDS,
MappingState.ABORTED,
)

# mapping states that mean that the sync is not working, but will typically resolve
# itself
TEMPORARY_ERROR_MAPPING_STATES = (
MappingState.INITIAL,
MappingState.SCHEMA_CHANGED,
MappingState.RECOVERY,
)

# mapping states that are permanent errors
ERROR_MAPPING_STATES = (
MappingState.BULK_LOAD_ERROR,
MappingState.SYSTEM_ERROR,
MappingState.DB_UNAVAILABLE,
MappingState.BAD_CONFIG,
MappingState.SYNC_REMOVED,
MappingState.INACTIVE_ORG,
MappingState.UNAUTHORIZED,
)


class WriteAlgorithm(Enum):
MERGE_WRITES = 1
ORDERED_WRITES = 2
Expand Down
55 changes: 55 additions & 0 deletions tests/contrib/test_healthcheck.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import json
import secrets

Expand All @@ -11,6 +12,46 @@
from tests import fixtures


@httpretty.activate
def test_check_status_mapping():
httpretty.register_uri(
httpretty.GET,
"https://connect-eu.heroku.com/api/v3/connections",
body=json.dumps(fixtures.connections),
status=200,
content_type="application/json",
)
httpretty.register_uri(
httpretty.GET,
"https://connect-eu.heroku.com/api/v3/connections/1?deep=true",
body=json.dumps(fixtures.connection),
status=200,
content_type="application/json",
)
hc = HerokuConnectHealthCheck()
hc.check_status()
assert not hc.errors

failed_connection = copy.deepcopy(fixtures.connection)
failed_connection["mappings"][0]["state"] = "BAD_CONFIG"

httpretty.register_uri(
httpretty.GET,
"https://connect-eu.heroku.com/api/v3/connections/1?deep=true",
body=json.dumps(failed_connection),
status=200,
content_type="application/json",
)

hc = HerokuConnectHealthCheck()
hc.check_status()
assert hc.errors
assert (
hc.errors[0].message
== "mapping Account on connection sample name is in state BAD_CONFIG"
)


@httpretty.activate
def test_check_status():
httpretty.register_uri(
Expand All @@ -20,6 +61,13 @@ def test_check_status():
status=200,
content_type="application/json",
)
httpretty.register_uri(
httpretty.GET,
"https://connect-eu.heroku.com/api/v3/connections/1?deep=true",
body=json.dumps(fixtures.connection),
status=200,
content_type="application/json",
)
hc = HerokuConnectHealthCheck()
hc.check_status()
assert not hc.errors
Expand Down Expand Up @@ -79,6 +127,13 @@ def test_health_check_url(client):
status=200,
content_type="application/json",
)
httpretty.register_uri(
httpretty.GET,
"https://connect-eu.heroku.com/api/v3/connections/1?deep=true",
body=json.dumps(fixtures.connection),
status=200,
content_type="application/json",
)
response = client.get("/ht/")
assert response.status_code == 200
assert b"<td>Heroku Connect</td>" in response.content
2 changes: 1 addition & 1 deletion tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"schema_name": "salesforce",
"db_key": "DATABASE_URL",
"state": "IDLE",
"mappings": [{"id": "XYZ", "object_name": "Account", "state": "SCHEMA_CHANGED"}],
"mappings": [{"id": "XYZ", "object_name": "Account", "state": "DATA_SYNCED"}],
}

connections = {"count": 1, "results": [connection]}
Loading