From 7afe83c8a2d5c413acf88469cdbdc2c491ae5145 Mon Sep 17 00:00:00 2001 From: Kyr Shatskyy Date: Mon, 30 Nov 2020 21:37:05 +0100 Subject: [PATCH] teuthology-suite: add Rocket.Chat notification Add Rocket.Chat notification for sleep before teardown. For details see https://rocket.chat/ Signed-off-by: Kyr Shatskyy --- scripts/suite.py | 3 + setup.py | 1 + teuthology/config.py | 1 + teuthology/run_tasks.py | 86 +++++++++++++++++++ teuthology/suite/run.py | 2 + .../rocketchat-sleep-before-teardown.jinja2 | 6 ++ 6 files changed, 99 insertions(+) create mode 100644 teuthology/templates/rocketchat-sleep-before-teardown.jinja2 diff --git a/scripts/suite.py b/scripts/suite.py index 98f7448111..e826d57a3e 100644 --- a/scripts/suite.py +++ b/scripts/suite.py @@ -109,6 +109,9 @@ Scheduler arguments: When tests finish or time out, send an email here. May also be specified in ~/.teuthology.yaml as 'results_email' + --rocketchat Comma separated list of Rocket.Chat channels where + to send a message when tests finished or time out. + To be used with --sleep-before-teardown option. -N , --num Number of times to run/queue the job [default: 1] -l , --limit Queue at most this many jobs diff --git a/setup.py b/setup.py index 7b08ee8313..e595da4a64 100644 --- a/setup.py +++ b/setup.py @@ -64,6 +64,7 @@ setup( 'configparser', 'ansible>=2.0', 'prettytable', + 'rocket-python >= 1.2.15', 'manhole', 'humanfriendly', ], diff --git a/teuthology/config.py b/teuthology/config.py index 1d5cee59b2..18e26d2cc3 100644 --- a/teuthology/config.py +++ b/teuthology/config.py @@ -185,6 +185,7 @@ class TeuthologyConfig(YamlConfig): 'size': 1, }, }, + 'rocketchat': None, 'sleep_before_teardown': 0, } diff --git a/teuthology/run_tasks.py b/teuthology/run_tasks.py index 99e5a4abb7..ab392261b4 100644 --- a/teuthology/run_tasks.py +++ b/teuthology/run_tasks.py @@ -4,6 +4,7 @@ import os import sys import time import types +import yaml from copy import deepcopy from humanfriendly import format_timespan @@ -203,6 +204,41 @@ def run_tasks(tasks, ctx): del exc_info timer.mark("tasks complete") + +def build_rocketchat_message(ctx, stack, sleep_time_sec, template_path=None): + message_template_path = template_path or os.path.dirname(__file__) + \ + '/templates/rocketchat-sleep-before-teardown.jinja2' + + with open(message_template_path) as f: + template_text = f.read() + + template = jinja2.Template(template_text) + archive_path = ctx.config.get('archive_path') + job_id = ctx.config.get('job_id') + status = get_status(ctx.summary) + stack_path = ' -> '.join(task for task, _ in stack) + suite_name=ctx.config.get('suite') + sleep_date=time.time() + sleep_date_str=time.strftime('%Y-%m-%d %H:%M:%S', + time.gmtime(sleep_date)) + + message = template.render( + sleep_time=format_timespan(sleep_time_sec), + sleep_time_sec=sleep_time_sec, + sleep_date=sleep_date_str, + owner=ctx.owner, + run_name=ctx.name, + job_id=ctx.config.get('job_id'), + job_desc=ctx.config.get('description'), + job_info=get_results_url(ctx.name, job_id), + job_logs=get_http_log_path(archive_path, job_id), + suite_name=suite_name, + status=status, + task_stack=stack_path, + ) + return message + + def build_email_body(ctx, stack, sleep_time_sec): email_template_path = os.path.dirname(__file__) + \ '/templates/email-sleep-before-teardown.jinja2' @@ -239,7 +275,57 @@ def build_email_body(ctx, stack, sleep_time_sec): ) return (subject.strip(), body.strip()) + +def rocketchat_send_message(ctx, message, channels): + """ + Send the message to the given RocketChat channels + + Before sending the message we read the config file + from `~/.config/rocketchat.api/settings.yaml` which + must include next records: + + username: 'userloginname' + password: 'userbigsecret' + domain: 'https://chat.suse.de' + + :param message: plain text message content in the Rocket.Chat + messaging format + :param channels: a list of channels where to send the message, + the user private channel should be prefixed + with '@' symbol + """ + try: + from rocketchat.api import RocketChatAPI + except Exception as e: + log.warning(f'rocketchat: Failed to import rocketchat.api: {e}') + return + + settings_path = \ + os.environ.get('HOME') + '/.config/rocketchat.api/settings.yaml' + + try: + with open(settings_path) as f: + settings = yaml.safe_load(f) + except Exception as e: + log.warning(f'rocketchat: Failed to load settings from {settings_path}: {e}') + + r = RocketChatAPI(settings=settings) + for channel in channels: + try: + r.send_message(message, channel) + except Exception as e: + log.warning(f'rocketchat: Failed to send message to "{channel}" channel: {e}') + + def notify_sleep_before_teardown(ctx, stack, sleep_time): + rocketchat = ctx.config.get('rocketchat', None) + + if rocketchat: + channels = [_ for _ in [_.strip() for _ in rocketchat.split(',')] if _] + log.info("Sending a message to Rocket.Chat channels: %s", channels) + message = build_rocketchat_message(ctx, stack, sleep_time) + rocketchat_send_message(ctx, message, channels) + email = ctx.config.get('email', None) if not email: # we have no email configured, return silently diff --git a/teuthology/suite/run.py b/teuthology/suite/run.py index b84a6312b0..3b64185ee9 100644 --- a/teuthology/suite/run.py +++ b/teuthology/suite/run.py @@ -323,6 +323,8 @@ class Run(object): job_config.owner = self.args.owner if self.args.sleep_before_teardown: job_config.sleep_before_teardown = int(self.args.sleep_before_teardown) + if self.args.rocketchat: + job_config.rocketchat = self.args.rocketchat return job_config def build_base_args(self): diff --git a/teuthology/templates/rocketchat-sleep-before-teardown.jinja2 b/teuthology/templates/rocketchat-sleep-before-teardown.jinja2 new file mode 100644 index 0000000000..4109ec5a03 --- /dev/null +++ b/teuthology/templates/rocketchat-sleep-before-teardown.jinja2 @@ -0,0 +1,6 @@ +The teuthology job [{{ job_id }}]({{ job_info }}) for suite *{{ suite_name }}* owned by '{{ owner }}' has fallen asleep with status '{{ status }}' at {{ sleep_date }} for __{{ sleep_time }}__ ({{ sleep_time_sec }} seconds). +Open [teuthology.log]({{ job_logs }}teuthology.log) for details, or go to [all logs]({{ job_logs}}). + +Job Description: {{ job_desc }} +Run Name: {{ run_name }} +Task Stack: {{ task_stack }} -- 2.39.5