-
Notifications
You must be signed in to change notification settings - Fork 17
feat: ietf-schedule basic recurrence implementation #1523
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| if [ -f /run/infix-update ]; then | ||
| printf '\n\033[1;33m *** %s ***\033[0m\n\n' "$(cat /run/infix-update)" | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #!/bin/sh | ||
| # Check for available firmware updates and notify on login if one exists. | ||
| # Called by the scheduler when predefined-action infix-schedule:check-update fires. | ||
|
|
||
| NOTIFY_FILE=/run/infix-update | ||
| TAG=infix-update | ||
|
|
||
| # Source os-release for VERSION and IMAGE_ID | ||
| if [ ! -f /etc/os-release ]; then | ||
| logger -t "$TAG" "ERROR: /etc/os-release not found" | ||
| exit 1 | ||
| fi | ||
| . /etc/os-release | ||
|
|
||
| # Dev/dirty builds have no comparable semver — always show the latest release | ||
| IS_RELEASE=true | ||
| if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then | ||
| IS_RELEASE=false | ||
| fi | ||
|
|
||
| # Read configured update-url from sysrepo, fall back to upstream | ||
| UPDATE_URL=$(sysrepocfg -d running -f json \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should look to use the 'copy' shell command instead of sysrepocfg here, since there will be problem calling sysrepocfg for everone except root: admin@infix-00-00-00: copy also respect nacm if i remember it correctly. |
||
| -x '/ietf-system:system/infix-system:software/check-update/update-url' \ | ||
| 2>/dev/null \ | ||
| | grep -o '"update-url": *"[^"]*"' \ | ||
| | grep -o '"[^"]*"$' \ | ||
| | tr -d '"') | ||
| UPDATE_URL=${UPDATE_URL:-"https://github.com/kernelkit/infix"} | ||
|
|
||
| # Derive API URL from the configured update URL. | ||
| # Default (github.com): https://github.com/org/repo → https://api.github.com/repos/org/repo | ||
| REPO=$(echo "$UPDATE_URL" | sed 's|https://github.com/||; s|/*$||') | ||
| API_URL="https://api.github.com/repos/${REPO}/releases/latest" | ||
|
|
||
| LATEST_TAG=$(curl -sSL --max-time 10 "$API_URL" 2>/dev/null \ | ||
| | sed -n 's/.*"tag_name" *: *"\([^"]*\)".*/\1/p' \ | ||
| | head -1) | ||
| if [ -z "$LATEST_TAG" ]; then | ||
| logger -p daemon.info -t "$TAG" "Update check skipped: could not reach ${API_URL}" | ||
| exit 0 | ||
| fi | ||
| LATEST=${LATEST_TAG#v} | ||
|
|
||
| # Compare: is $1 strictly newer than $2? | ||
| newer() { | ||
| [ "$1" = "$2" ] && return 1 | ||
| [ "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -1)" = "$1" ] | ||
| } | ||
|
|
||
| if [ "$IS_RELEASE" = false ] || newer "$LATEST" "$VERSION"; then | ||
| BUNDLE_URL="${UPDATE_URL}/releases/download/${LATEST_TAG}/${IMAGE_ID}-${LATEST}.tar.gz" | ||
| MSG="Firmware update available: ${LATEST_TAG}, running ${VERSION} (see ${BUNDLE_URL})" | ||
| logger -t "$TAG" "$MSG — ${BUNDLE_URL}" | ||
| printf '%s\n' "$MSG" > "$NOTIFY_FILE" | ||
| else | ||
| logger -p daemon.debug -t "$TAG" "No update available (current: $VERSION, latest: $LATEST)" | ||
| rm -f "$NOTIFY_FILE" | ||
| fi | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Cron daemon for infix-schedule | ||
| service [S12345] crond -f -- Cron daemon | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not start in runlevel S, 2345 should be enough. This since we have seen slow platforms (arm32) struggle with starting to much, when it should focus on confd. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| /* SPDX-License-Identifier: BSD-3-Clause */ | ||
|
|
||
| #include <signal.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| #include <libite/lite.h> | ||
| #include <srx/common.h> | ||
| #include <srx/lyx.h> | ||
| #include <srx/srx_val.h> | ||
| #include "core.h" | ||
|
|
||
| #define XPATH_BASE "/ietf-system:system/infix-schedule:schedules" | ||
| #define CRONTAB_FILE "/var/spool/cron/crontabs/root" | ||
|
|
||
| static const char *action_to_cmd(const char *action) | ||
| { | ||
| if (!action) | ||
| return NULL; | ||
| if (strstr(action, "reboot")) | ||
| return "/usr/sbin/reboot"; | ||
| if (strstr(action, "check-update")) | ||
| return "/usr/sbin/infix-check-update"; | ||
| return NULL; | ||
| } | ||
|
|
||
| /* | ||
| * Convert ietf-schedule recurrence to a 5-field cron expression. | ||
| * | ||
| * Frequency mapping: | ||
| * secondly → * * * * * (cron minimum is 1 minute) | ||
| * minutely/N → *\/N * * * * | ||
| * hourly/N → 0 *\/N * * * | ||
| * daily/N → 0 0 *\/N * * | ||
| * weekly/N → 0 0 * * *\/N | ||
| * monthly/N → 0 0 1 *\/N * | ||
| * | ||
| * Optional by-* leaves refine the expression: | ||
| * byminute → replaces the minute field | ||
| * byhour → replaces the hour field | ||
| * byday → replaces the day-of-week field | ||
| */ | ||
| static void build_cron_expr(struct lyd_node *recurrence, char *expr, size_t sz) | ||
| { | ||
| const char *freq, *ivstr; | ||
| struct lyd_node *node; | ||
| char min[64], hr[64], dom[64], mon[64], dow[64]; | ||
| int iv, first; | ||
|
|
||
| snprintf(min, sizeof(min), "*"); | ||
| snprintf(hr, sizeof(hr), "*"); | ||
| snprintf(dom, sizeof(dom), "*"); | ||
| snprintf(mon, sizeof(mon), "*"); | ||
| snprintf(dow, sizeof(dow), "*"); | ||
|
|
||
| freq = lydx_get_cattr(recurrence, "frequency"); | ||
| ivstr = lydx_get_cattr(recurrence, "interval"); | ||
| if (!freq || !ivstr) | ||
| goto done; | ||
|
|
||
| iv = atoi(ivstr); | ||
| if (iv <= 0) | ||
| iv = 1; | ||
|
|
||
| if (strstr(freq, "secondly")) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it not possible to set in cron, it should not be possible to set in YANG, block it in yang model, the problematic here is that it is an identity, do not remember how we did with unsupported identities. Lets discuss this. We have as a policy that everything should be validated in the yang model (using deviate or must expression, mandatory etc), to keep the c-code as little as possible. |
||
| /* Sub-minute intervals cannot be expressed in standard cron. | ||
| * Map to every minute as the finest available granularity. */ | ||
| snprintf(min, sizeof(min), "*"); | ||
| } else if (strstr(freq, "minutely")) { | ||
| if (iv == 1) | ||
| snprintf(min, sizeof(min), "*"); | ||
| else | ||
| snprintf(min, sizeof(min), "*/%d", iv); | ||
| } else if (strstr(freq, "hourly")) { | ||
| snprintf(min, sizeof(min), "0"); | ||
| if (iv == 1) | ||
| snprintf(hr, sizeof(hr), "*"); | ||
| else | ||
| snprintf(hr, sizeof(hr), "*/%d", iv); | ||
| } else if (strstr(freq, "daily")) { | ||
| snprintf(min, sizeof(min), "0"); | ||
| snprintf(hr, sizeof(hr), "0"); | ||
| if (iv > 1) | ||
| snprintf(dom, sizeof(dom), "*/%d", iv); | ||
| } else if (strstr(freq, "weekly")) { | ||
| snprintf(min, sizeof(min), "0"); | ||
| snprintf(hr, sizeof(hr), "0"); | ||
| if (iv == 1) | ||
| snprintf(dow, sizeof(dow), "*"); | ||
| else | ||
| snprintf(dow, sizeof(dow), "*/%d", iv); | ||
| } else if (strstr(freq, "monthly")) { | ||
| snprintf(min, sizeof(min), "0"); | ||
| snprintf(hr, sizeof(hr), "0"); | ||
| snprintf(dom, sizeof(dom), "1"); | ||
| if (iv > 1) | ||
| snprintf(mon, sizeof(mon), "*/%d", iv); | ||
| } | ||
|
|
||
| /* byminute: override minute field with explicit list */ | ||
| first = 1; | ||
| LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byminute") { | ||
| const char *val = lyd_get_value(node); | ||
| if (!val) continue; | ||
| if (first) { snprintf(min, sizeof(min), "%s", val); first = 0; } | ||
| else strncat(min, ",", sizeof(min) - strlen(min) - 1), | ||
| strncat(min, val, sizeof(min) - strlen(min) - 1); | ||
| } | ||
|
|
||
| /* byhour: override hour field with explicit list */ | ||
| first = 1; | ||
| LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byhour") { | ||
| const char *val = lyd_get_value(node); | ||
| if (!val) continue; | ||
| if (first) { snprintf(hr, sizeof(hr), "%s", val); first = 0; } | ||
| else strncat(hr, ",", sizeof(hr) - strlen(hr) - 1), | ||
| strncat(hr, val, sizeof(hr) - strlen(hr) - 1); | ||
| } | ||
|
|
||
| /* byday: override day-of-week field */ | ||
| first = 1; | ||
| LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byday") { | ||
| const char *val = lydx_get_cattr(node, "weekday"); | ||
| const char *num = NULL; | ||
| if (!val) continue; | ||
| /* map YANG weekday names to cron numbers (0=sunday) */ | ||
| if (!strcmp(val, "sunday")) num = "0"; | ||
| else if (!strcmp(val, "monday")) num = "1"; | ||
| else if (!strcmp(val, "tuesday")) num = "2"; | ||
| else if (!strcmp(val, "wednesday")) num = "3"; | ||
| else if (!strcmp(val, "thursday")) num = "4"; | ||
| else if (!strcmp(val, "friday")) num = "5"; | ||
| else if (!strcmp(val, "saturday")) num = "6"; | ||
| if (!num) continue; | ||
| if (first) { snprintf(dow, sizeof(dow), "%s", num); first = 0; } | ||
| else strncat(dow, ",", sizeof(dow) - strlen(dow) - 1), | ||
| strncat(dow, num, sizeof(dow) - strlen(dow) - 1); | ||
| } | ||
|
|
||
| done: | ||
| snprintf(expr, sz, "%s %s %s %s %s", min, hr, dom, mon, dow); | ||
| } | ||
|
|
||
| static void reload_crond(void) | ||
| { | ||
| char *args[] = { "pkill", "-HUP", "crond", NULL }; | ||
|
|
||
| runbg(args, 0); | ||
| } | ||
|
|
||
| static void apply_schedules(struct lyd_node *config) | ||
| { | ||
| struct lyd_node *schedules, *sched; | ||
| FILE *fp; | ||
| int count = 0; | ||
|
|
||
| makepath("/var/spool/cron/crontabs"); | ||
| fp = fopen(CRONTAB_FILE, "w"); | ||
| if (!fp) { | ||
| ERROR("schedule: failed to open %s", CRONTAB_FILE); | ||
| return; | ||
| } | ||
| fprintf(fp, "# Managed by infix-schedule\n"); | ||
|
|
||
| schedules = config ? lydx_get_xpathf(config, XPATH_BASE) : NULL; | ||
| if (!schedules) | ||
| goto out; | ||
|
|
||
| LYX_LIST_FOR_EACH(lyd_child(schedules), sched, "schedule") { | ||
| struct lyd_node *recurrence; | ||
| const char *name, *action, *cmd; | ||
| char expr[128]; | ||
|
|
||
| if (!lydx_is_enabled(sched, "enabled")) | ||
| continue; | ||
|
|
||
| name = lydx_get_cattr(sched, "name"); | ||
|
|
||
| recurrence = lydx_get_child(sched, "recurrence"); | ||
| if (!recurrence) | ||
| continue; | ||
|
|
||
| build_cron_expr(recurrence, expr, sizeof(expr)); | ||
|
|
||
| action = lydx_get_cattr(sched, "predefined-action"); | ||
| cmd = action_to_cmd(action); | ||
| if (!cmd) | ||
| continue; | ||
|
|
||
| fprintf(fp, "# %s\n%s %s\n", name ?: "unnamed", expr, cmd); | ||
| NOTE("schedule: %s → cron '%s %s'", name ?: "unnamed", expr, cmd); | ||
| count++; | ||
| } | ||
|
|
||
| out: | ||
| fclose(fp); | ||
| reload_crond(); | ||
| NOTE("schedule: %d active job(s) written to crontab", count); | ||
| } | ||
|
|
||
| int schedule_change(sr_session_ctx_t *session, struct lyd_node *config, | ||
| struct lyd_node *diff, sr_event_t event, struct confd *confd) | ||
| { | ||
| if (event != SR_EV_DONE) | ||
| return SR_ERR_OK; | ||
|
|
||
| apply_schedules(config); | ||
| return SR_ERR_OK; | ||
| } | ||
|
|
||
| int schedule_init(struct confd *confd) | ||
| { | ||
| sr_data_t *data = NULL; | ||
|
|
||
| /* | ||
| * Sync the crontab with current running config at startup so | ||
| * scheduled jobs survive across reboots. | ||
| */ | ||
| sr_get_data(confd->session, "//.", 0, 0, 0, &data); | ||
| apply_schedules(data ? data->tree : NULL); | ||
| if (data) | ||
| sr_release_data(data); | ||
|
|
||
| return SR_ERR_OK; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,4 +57,6 @@ MODULES=( | |
| "ieee1588-ptp-tt@2023-08-14.yang -e timestamp-correction" | ||
| "ieee802-dot1as-gptp@2025-12-10.yang" | ||
| "infix-ptp@2026-04-07.yang" | ||
| "ietf-schedule@2026-03-10.yang -e basic-recurrence" | ||
| "infix-schedule@2026-05-27.yang" | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this file you also need to update the reference to all updated yang models, in this case infix-system-software@.....yang |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use jq here instead of calls to misc shell programs, this to limit forking:
like this (This is pure AI, not tested, just to give you a hint what i mean):