-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from airflow-laminar/tkp/symphony
Add discord and symphony alerts
- Loading branch information
Showing
13 changed files
with
313 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import sys | ||
from asyncio import sleep | ||
from functools import lru_cache | ||
from logging import getLogger | ||
from queue import Queue | ||
from threading import Thread | ||
from time import sleep as time_sleep | ||
|
||
from airflow.listeners import hookimpl | ||
from airflow.models.dagrun import DagRun | ||
from airflow.plugins_manager import AirflowPlugin | ||
from discord import Client, Intents | ||
|
||
from airflow_priority import DagStatus, get_config_option, has_priority_tag | ||
|
||
__all__ = ("get_client", "send_metric_discord", "on_dag_run_failed", "DiscordPriorityPlugin") | ||
|
||
_log = getLogger(__name__) | ||
|
||
|
||
@lru_cache | ||
def get_client(): | ||
client = Client(intents=Intents.default()) | ||
client.queue = Queue() | ||
|
||
@client.event | ||
async def on_ready(): | ||
channel = client.get_channel(int(get_config_option("discord", "channel"))) | ||
while True: | ||
while client.queue.empty(): | ||
await sleep(5) | ||
await channel.send(client.queue.get()) | ||
|
||
token = get_config_option("discord", "token") | ||
t = Thread(target=client.run, args=(token,), daemon=True) | ||
t.start() | ||
return client | ||
|
||
|
||
def send_metric_discord(dag_id: str, priority: int, tag: DagStatus) -> None: | ||
client_queue = get_client().queue | ||
client_queue.put(f'A P{priority} DAG "{dag_id}" has {tag}!') | ||
while not client_queue.empty(): | ||
time_sleep(1) | ||
|
||
|
||
# @hookimpl | ||
# def on_dag_run_running(dag_run: DagRun, msg: str): | ||
# dag_id, priority = has_priority_tag(dag_run=dag_run) | ||
# if priority: | ||
# send_metric_slack(dag_id, priority, "running") | ||
|
||
|
||
# @hookimpl | ||
# def on_dag_run_success(dag_run: DagRun, msg: str): | ||
# dag_id, priority = has_priority_tag(dag_run=dag_run) | ||
# if priority: | ||
# send_metric_slack(dag_id, priority, "succeeded") | ||
|
||
|
||
@hookimpl | ||
def on_dag_run_failed(dag_run: DagRun, msg: str): | ||
dag_id, priority = has_priority_tag(dag_run=dag_run) | ||
if priority: | ||
send_metric_discord(dag_id, priority, "failed") | ||
|
||
|
||
try: | ||
# Call once to ensure plugin will work | ||
get_client() | ||
|
||
class DiscordPriorityPlugin(AirflowPlugin): | ||
name = "DiscordPriorityPlugin" | ||
listeners = [sys.modules[__name__]] | ||
except Exception: | ||
_log.exception("Plugin could not be enabled") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import ssl | ||
import sys | ||
from functools import lru_cache | ||
from httpx import post | ||
from logging import getLogger | ||
|
||
from airflow.listeners import hookimpl | ||
from airflow.models.dagrun import DagRun | ||
from airflow.plugins_manager import AirflowPlugin | ||
|
||
from airflow_priority import DagStatus, get_config_option, has_priority_tag | ||
|
||
__all__ = ("get_config_options", "get_headers", "get_room_id", "send_metric_symphony", "on_dag_run_failed", "SymphonyPriorityPlugin") | ||
|
||
|
||
_log = getLogger(__name__) | ||
|
||
|
||
@lru_cache | ||
def get_config_options(): | ||
return { | ||
"room_name": get_config_option("symphony", "room_name"), | ||
"message_create_url": get_config_option("symphony", "message_create_url"), | ||
"cert_file": get_config_option("symphony", "cert_file"), | ||
"key_file": get_config_option("symphony", "key_file"), | ||
"session_auth": get_config_option("symphony", "session_auth"), | ||
"key_auth": get_config_option("symphony", "key_auth"), | ||
"room_search_url": get_config_option("symphony", "room_search_url"), | ||
} | ||
|
||
|
||
def _client_cert_post(url: str, cert_file: str, key_file: str) -> str: | ||
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) | ||
context.load_cert_chain(certfile=cert_file, keyfile=key_file) | ||
response = post(url=url, verify=context, headers={"Content-Type": "application/json"}, data="{}") | ||
if response.status_code != 200: | ||
raise Exception(f"Cannot connect for symphony handshake to {url}: {response.status_code}") | ||
return response.json() | ||
|
||
|
||
@lru_cache | ||
def get_headers(): | ||
config_options = get_config_options() | ||
session_token = _client_cert_post(config_options["session_auth"], config_options["cert_file"], config_options["key_file"])["token"] | ||
key_manager_token = _client_cert_post(config_options["key_auth"], config_options["cert_file"], config_options["key_file"])["token"] | ||
return { | ||
"sessionToken": session_token, | ||
"keyManagerToken": key_manager_token, | ||
"Accept": "application/json", | ||
} | ||
|
||
|
||
@lru_cache | ||
def get_room_id(): | ||
config_options = get_config_options() | ||
|
||
res = post( | ||
url=config_options["room_search_url"], | ||
json={"query": config_options["room_name"]}, | ||
headers=get_headers(), | ||
) | ||
if res and res.status_code == 200: | ||
for room in res.json()["rooms"]: | ||
name = room.get("roomAttributes", {}).get("name") | ||
if name and name == config_options["room_name"]: | ||
return room.get("roomSystemInfo", {}).get("id") | ||
raise Exception("TODO") | ||
|
||
|
||
def send_metric_symphony(dag_id: str, priority: int, tag: DagStatus) -> None: | ||
return post( | ||
url=get_config_options()["message_create_url"].replace("SID", get_room_id()), | ||
json={"message": f'<messageML>A P{priority} DAG "{dag_id}" has {tag}!</messageML>'}, | ||
headers=get_headers(), | ||
) | ||
|
||
|
||
# @hookimpl | ||
# def on_dag_run_running(dag_run: DagRun, msg: str): | ||
# dag_id, priority = has_priority_tag(dag_run=dag_run) | ||
# if priority: | ||
# send_metric_slack(dag_id, priority, "running") | ||
|
||
|
||
# @hookimpl | ||
# def on_dag_run_success(dag_run: DagRun, msg: str): | ||
# dag_id, priority = has_priority_tag(dag_run=dag_run) | ||
# if priority: | ||
# send_metric_slack(dag_id, priority, "succeeded") | ||
|
||
|
||
@hookimpl | ||
def on_dag_run_failed(dag_run: DagRun, msg: str): | ||
dag_id, priority = has_priority_tag(dag_run=dag_run) | ||
if priority: | ||
send_metric_symphony(dag_id, priority, "failed") | ||
|
||
|
||
try: | ||
# Call once to ensure plugin will work | ||
get_config_options() | ||
|
||
class SymphonyPriorityPlugin(AirflowPlugin): | ||
name = "SymphonyPriorityPlugin" | ||
listeners = [sys.modules[__name__]] | ||
except Exception: | ||
_log.exception("Plugin could not be enabled") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from unittest.mock import patch | ||
|
||
|
||
def test_discord_send(airflow_config, dag_run): | ||
from airflow_priority.plugins.discord import send_metric_discord | ||
|
||
send_metric_discord("UNIT TEST", 1, "BEEN TESTED") | ||
|
||
|
||
def test_discord_priority_failed(airflow_config, dag_run): | ||
from airflow_priority.plugins.discord import on_dag_run_failed | ||
|
||
with patch("airflow_priority.plugins.discord.send_metric_discord") as p1: | ||
on_dag_run_failed(dag_run, "test") | ||
assert p1.call_count == 1 |
Oops, something went wrong.