Source code for scheduler.make_atq

"""
make_atq.py
~~~~~~~~~~~
This script takes the Borealis schedule file (ending in .scd) and converts the schedule into commands
that are scheduled using the system ``at`` service.

Logs are printed to stdout. Specific logs for each time the schedule is updated are also created
in ``borealis_schedules/logs/``.

This script can be configured to run automatically when the .scd file is modified, using Linux services.
Specifically, a ``.path`` and ``.service`` file are required, and templates for each given below. A pre-requisite
for this setup is a file called ``.borealis.env`` defined in the user's home directory, containing the following::

   BOREALISPATH=/path/to/borealis/
   RADAR_ID=sas  # or whatever you define it as
   PYTHON_VERSION=3.12  # or whatever version you use

monitor-schedule.path::

   [Unit]
   Description="Monitor schedule file and update schedule when changed."

   [Path]
   PathChanged=/home/radar/borealis_schedules/lab.scd
   Unit=schedule-radar.service

   [Install]
   WantedBy=multi-user.target

schedule-radar.service::

   [Unit]
   Description="Run script to schedule the Borealis radar"

   [Service]
   User=radar
   Group=users
   EnvironmentFile=/home/radar/.borealis.env
   ExecStart=/bin/bash -c '${BOREALISPATH}/borealis_env${PYTHON_VERSION}/bin/python${PYTHON_VERSION} ${BOREALISPATH}/scheduler/make_atq.py /home/radar/borealis_schedules/'

   [Install]
   WantedBy=multi-user.target

"""

import argparse
import datetime as dt
import os
import subprocess as sp

from scd_utils import ScheduleError, SCDUtils


[docs] def tee_print(msg: str, logfile): """ Prints to stdout and to a logfile. """ print(msg) print(msg, file=logfile)
[docs] def backup_atq(time_of_interest: dt.datetime, scd_dir: str, site_id: str): """ Creates a backup of the current ``atq`` schedule, saving it to ``[scd_dir]/atq_backup/YYYYmmdd-HHMM.[site_id].atq``. :param time_of_interest: Time that the schedule is being evaluated for. :type time_of_interest: dt.datetime :param scd_dir: Path to the directory containing schedule files. :type scd_dir: str :param site_id: Three-letter radar identifier, e.g. "sas" :type site_id: str """ backup_dir = f"{scd_dir}/atq_backups" backup_time_str = time_of_interest.strftime("%Y%m%d-%H%M") if not os.path.exists(backup_dir): os.makedirs(backup_dir) backup_file = f"{backup_dir}/{backup_time_str}.{site_id}.atq" output = sp.check_output(SCDUtils.get_atq_cmd, shell=True) with open(backup_file, "wb") as f: f.write(output)
[docs] def make_schedule(scd_dir: str, site_id: str): """ Reads the schedule file and uses ``at`` to schedule all relevant lines from the schedule. If the schedule is empty or contains invalid lines, it will send a Slack message alerting that the operation has failed. :param scd_dir: Path to the directory containing schedule files. :type scd_dir: str :param site_id: Three-letter radar identifier, e.g. "sas" :type site_id: str """ scd_file = f"{scd_dir}/{site_id}.scd" scd_util = SCDUtils(scd_file, site_id) time_of_interest = dt.datetime.now(dt.timezone.utc) year = time_of_interest.strftime("%Y") month = time_of_interest.strftime("%m") log_dir = f"{os.environ['HOME']}/logs/make_atq/{year}/{month}/" if not os.path.exists(log_dir): os.makedirs(log_dir) log_time_str = time_of_interest.strftime("%Y%m%d") log_file = f"{log_dir}/{log_time_str}.make_atq.log" log_msg_header = f"Updated at {time_of_interest}\n" start_time = time_of_interest.strftime("%Y-%m-%d %H:%M:%S") with open(log_file, "a") as f: f.write("#" * 80 + "\n\n") tee_print(f"\n{start_time} - Scheduler booted", f) tee_print("Making schedule...", f) backup_atq(time_of_interest, scd_dir, site_id) try: new_atq_str = scd_util.parse_and_schedule(time_of_interest) with open(log_file, "a") as f: f.write(log_msg_header) f.write(new_atq_str) f.write("\n") except ScheduleError as e: logtime = time_of_interest.strftime("%c") error_msg = ( f"{logtime}: Unable to make schedule\n\tException thrown:\n\t\t{str(e)}\n" ) with open(log_file, "a") as f: f.write(log_msg_header) f.write(error_msg) tee_print("Failed to make schedule, sending alert to Slack.", f) relevant_lines = scd_util.get_relevant_lines(time_of_interest) message = f"make_atq @ {site_id}: Failed to schedule {[str(x) for x in relevant_lines]}" command = f""" curl --silent --header "Content-type: application/json" --data "{{'text':{message}}}" "${{!SLACK_WEBHOOK}}" """ sp.call(command.split(), shell=True)
[docs] def parser(): argparser = argparse.ArgumentParser(description="Schedules new SCD file entries") argparser.add_argument("scd_dir", help="The scd working directory") return argparser
if __name__ == "__main__": args = parser().parse_args() scd_dir = args.scd_dir site_id = os.environ["RADAR_ID"] make_schedule(scd_dir, site_id)