-
Notifications
You must be signed in to change notification settings - Fork 17
Add support for ietf-schedule basic recurrence #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
Open
saba8814
wants to merge
1
commit into
kernelkit:main
Choose a base branch
from
saba8814:feat/add-scheduler-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,583
−1
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,3 @@ | ||
| if [ -s /run/infix-update ]; then | ||
| printf '\n\033[1;33m *** %s ***\033[0m\n\n' "$(cat /run/infix-update)" | ||
| fi |
This file contains hidden or 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 @@ | ||
| f /run/infix-update 0666 admin admin |
This file contains hidden or 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,56 @@ | ||
| #!/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 running config, fall back to upstream | ||
| UPDATE_URL=$(copy running-config \ | ||
| -x '/ietf-system:system/infix-system:software/check-update/update-url' \ | ||
| 2>/dev/null \ | ||
| | jq -r '.. | objects | ."update-url"? // empty') | ||
| 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)" | ||
| printf '' > "$NOTIFY_FILE" | ||
| fi |
This file contains hidden or 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 hidden or 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 hidden or 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,2 @@ | ||
| # Cron daemon for infix-schedule | ||
| service [2345] crond -f -- Cron daemon |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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,242 @@ | ||
| /* 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/admin" | ||
|
|
||
| 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: | ||
| * 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 | ||
| * bymonthday → replaces the day-of-month field (positive values only, 1-31) | ||
| * byyearmonth → replaces the month 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, "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); | ||
| } | ||
|
|
||
| /* bymonthday: override day-of-month field */ | ||
| first = 1; | ||
| LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "bymonthday") { | ||
| const char *val = lyd_get_value(node); | ||
| if (!val) continue; | ||
| if (first) { snprintf(dom, sizeof(dom), "%s", val); first = 0; } | ||
| else strncat(dom, ",", sizeof(dom) - strlen(dom) - 1), | ||
| strncat(dom, val, sizeof(dom) - strlen(dom) - 1); | ||
| } | ||
|
|
||
| /* byyearmonth: override month field */ | ||
| first = 1; | ||
| LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byyearmonth") { | ||
| const char *val = lyd_get_value(node); | ||
| if (!val) continue; | ||
| if (first) { snprintf(mon, sizeof(mon), "%s", val); first = 0; } | ||
| else strncat(mon, ",", sizeof(mon) - strlen(mon) - 1), | ||
| strncat(mon, val, sizeof(mon) - strlen(mon) - 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; | ||
| } |
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.