Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / scripts / sai-builder.service
Author[]Andy Green <andy@warmcat.com> 2025-09-25 03:06 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-09-26 04:56 UTC
Treec9a7ecc4b423d7422b89ace4c00ae96cf5d99e03   Raw Patch
 
web: integrate builder power control
web: integrate builder power control

Co-developed-by: Gemini 2.5 Pro
diff --git a/assets/sai.js b/assets/sai.js index 3d98622..e5ad7ce 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -1051,8 +1051,9 @@ function createBuilderDiv(plat) { }); const menuItems = [ - { label: `<b>SAI Hash:</b> ${plat.sai_hash}` }, - { label: `<b>LWS Hash:</b> ${plat.lws_hash}` }, + { label: `<b>SAI:</b> ${plat.sai_hash}` }, + { label: `<b>LWS:</b> ${plat.lws_hash}` }, + /* { label: "Update SAI", callback: () => { @@ -1063,8 +1064,37 @@ function createBuilderDiv(plat) { sai.send(JSON.stringify(rebuildMsg)); } } + */ ]; + if (plat.power_managed && authd) { + if (plat.stay_on) { + menuItems.push({ + label: "Release Stay", + callback: () => { + const stayMsg = { + schema: "com.warmcat.sai.stay", + builder_name: plat.name.split('.')[0], + stay_on: 0 + }; + sai.send(JSON.stringify(stayMsg)); + } + }); + } else { + menuItems.push({ + label: "Stay On", + callback: () => { + const stayMsg = { + schema: "com.warmcat.sai.stay", + builder_name: plat.name.split('.')[0], + stay_on: 1 + }; + sai.send(JSON.stringify(stayMsg)); + } + }); + } + } + platDiv.addEventListener("contextmenu", function(event) { if (!authd) return; diff --git a/src/common/include/private.h b/src/common/include/private.h index 9177117..563efee 100644 --- a/src/common/include/private.h +++ b/src/common/include/private.h @@ -459,6 +459,8 @@ typedef struct sai_plat { unsigned int job_limit; char windows; + char power_managed; + char stay_on; int index; /* used to create unique build dir path */ } sai_plat_t; @@ -531,7 +533,44 @@ typedef struct sai_build_metric_db { int parallel; } sai_build_metric_db_t; +/* + * Browser -> sai-web -> sai-server -> sai-power + * + * A browser user wants to set or release a "stay" on a builder, so it won't + * power down automatically when idle. + */ +typedef struct sai_stay { + lws_dll2_t list; + char builder_name[64]; + char stay_on; /* 0 = release, 1 = set */ +} sai_stay_t; + +/* sai-power -> sai-server, tells it the builders it can manage */ +typedef struct sai_power_managed_builder { + lws_dll2_t list; + char name[64]; + char stay_on; +} sai_power_managed_builder_t; + +typedef struct sai_power_managed_builders { + lws_dll2_t list; + lws_dll2_owner_t builders; /* sai_power_managed_builder_t */ +} sai_power_managed_builders_t; + + +typedef struct sai_stay_state_update { + lws_dll2_t list; + char builder_name[64]; + char stay_on; +} sai_stay_state_update_t; + + extern const lws_struct_map_t + lsm_stay[2], + lsm_schema_stay[1], + lsm_power_managed_builder[2], + lsm_power_managed_builders_list[1], + lsm_schema_power_managed_builders[1], lsm_schema_json_map_task[], lsm_schema_sq3_map_task[], lsm_schema_sq3_map_event[], @@ -561,8 +600,8 @@ extern const lws_struct_map_t lsm_load_report_members[9] ; extern const lws_struct_map_t lsm_build_metric[12]; -extern const lws_struct_map_t lsm_plat[8]; -extern const lws_struct_map_t lsm_plat_for_json[11]; +extern const lws_struct_map_t lsm_plat[10]; +extern const lws_struct_map_t lsm_plat_for_json[13]; extern const lws_ss_info_t ssi_said_logproxy; extern struct lws_ss_handle *ssh[3]; diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c index 2ffe9b3..9ad8618 100644 --- a/src/common/struct-metadata.c +++ b/src/common/struct-metadata.c @@ -92,6 +92,8 @@ const lws_struct_map_t lsm_plat[] = { /* !!! keep extern length in common/includ LSM_CARRAY (sai_plat_t, sai_hash, "sai_hash"), LSM_CARRAY (sai_plat_t, lws_hash, "lws_hash"), LSM_UNSIGNED (sai_plat_t, windows, "windows"), + LSM_UNSIGNED (sai_plat_t, power_managed, "power_managed"), + LSM_UNSIGNED (sai_plat_t, stay_on, "stay_on"), }; // This is the map for serializing to JSON @@ -107,6 +109,8 @@ const lws_struct_map_t lsm_plat_for_json[] = { LSM_CARRAY(sai_plat_t, sai_hash, "sai_hash"), LSM_CARRAY(sai_plat_t, lws_hash, "lws_hash"), LSM_UNSIGNED(sai_plat_t, windows, "windows"), + LSM_UNSIGNED(sai_plat_t, power_managed, "power_managed"), + LSM_UNSIGNED(sai_plat_t, stay_on, "stay_on"), }; const lws_struct_map_t lsm_schema_map_plat_simple[] = { @@ -307,7 +311,43 @@ const lws_struct_map_t lsm_power_state[] = { LSM_SIGNED(sai_power_state_t, powering_down, "powering_down"), }; - const lws_struct_map_t lsm_schema_sq3_map_artifact[] = { LSM_SCHEMA_DLL2 (sai_artifact_t, list, NULL, lsm_artifact, "artifacts"), }; + +const lws_struct_map_t lsm_stay[] = { + LSM_CARRAY(sai_stay_t, builder_name, "builder_name"), + LSM_UNSIGNED(sai_stay_t, stay_on, "stay_on"), +}; + +const lws_struct_map_t lsm_schema_stay[] = { + LSM_SCHEMA(sai_stay_t, NULL, lsm_stay, "com.warmcat.sai.power.stay"), +}; + + +const lws_struct_map_t lsm_power_managed_builder[] = { + LSM_CARRAY(sai_power_managed_builder_t, name, "name"), + LSM_UNSIGNED(sai_power_managed_builder_t, stay_on, "stay_on"), +}; + +const lws_struct_map_t lsm_power_managed_builders_list[] = { + LSM_LIST(sai_power_managed_builders_t, builders, + sai_power_managed_builder_t, list, NULL, + lsm_power_managed_builder, "builders"), +}; + +const lws_struct_map_t lsm_schema_power_managed_builders[] = { + LSM_SCHEMA(sai_power_managed_builders_t, NULL, + lsm_power_managed_builders_list, + "com.warmcat.sai.power_managed_builders"), +}; + +const lws_struct_map_t lsm_stay_state_update[] = { + LSM_CARRAY(sai_stay_state_update_t, builder_name, "builder_name"), + LSM_UNSIGNED(sai_stay_state_update_t, stay_on, "stay_on"), +}; + +const lws_struct_map_t lsm_schema_stay_state_update[] = { + LSM_SCHEMA(sai_stay_state_update_t, NULL, lsm_stay_state_update, + "com.warmcat.sai.stay_state_update"), +}; diff --git a/src/power/CMakeLists.txt b/src/power/CMakeLists.txt index 3948ef5..979852e 100644 --- a/src/power/CMakeLists.txt +++ b/src/power/CMakeLists.txt @@ -6,6 +6,7 @@ set(SRCS p-conf.c p-comms.c p-smartplug.c + p-api.c ) set(requirements 1) diff --git a/src/power/p-api.c b/src/power/p-api.c new file mode 100644 index 0000000..8c9e635 --- /dev/null +++ b/src/power/p-api.c @@ -0,0 +1,434 @@ +/* + * sai-power + * + * Copyright (C) 2019 - 2025 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * This is the h1 API that can be used on the LAN side + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> + +#if defined(__linux__) +#include <unistd.h> +#endif + +#if defined(__APPLE__) +#include <sys/stat.h> /* for mkdir() */ +#include <unistd.h> /* for chown() */ +#endif + +#include "p-private.h" + +extern struct lws_spawn_piped *lsp_wol; + +extern struct sai_power power; + + +static void +saip_sul_action_power_off(struct lws_sorted_usec_list *sul) +{ + saip_server_plat_t *sp = lws_container_of(sul, + saip_server_plat_t, sul_delay_off); + + if (!sp->power_off_url) { + lwsl_notice("%s: no power_off_url for %s\n", __func__, sp->host); + return; + } + if (!sp->ss_tasmota_off) { + lwsl_notice("%s: no power_off ss for %s\n", __func__, sp->host); + return; + } + + lwsl_warn("%s: powering off host %s\n", __func__, sp->host); + saip_notify_server_power_state(sp->host, 0, 1); + + if (lws_ss_client_connect(sp->ss_tasmota_off)) + lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota OFF secure stream"); +} + +saip_server_plat_t * +find_platform(struct sai_power *pwr, const char *host) +{ + lws_start_foreach_dll(struct lws_dll2 *, px, pwr->sai_server_owner.head) { + saip_server_t *s = lws_container_of(px, saip_server_t, list); + + lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) { + saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list); + + if (!strcmp(host, sp->host)) + return sp; + + } lws_end_foreach_dll(px1); + } lws_end_foreach_dll(px); + + return NULL; +} + +void +saip_notify_server_stay_state(const char *plat_name, int stay_on) +{ + saip_server_t *sps; + sai_stay_state_update_t *ssu; + + /* Find the first (usually only) configured sai-server connection */ + if (!power.sai_server_owner.head) { + lwsl_warn("%s: No sai-server configured to notify\n", __func__); + return; + } + sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list); + if (!sps->ss) { + lwsl_warn("%s: Not connected to sai-server to notify\n", __func__); + return; + } + + /* Allocate and queue the notification message */ + ssu = malloc(sizeof(*ssu)); + if (!ssu) + return; + + memset(ssu, 0, sizeof(*ssu)); + lws_strncpy(ssu->builder_name, plat_name, sizeof(ssu->builder_name)); + ssu->stay_on = (char)stay_on; + + /* The per-connection user object for the server link is a saip_server_link_t */ + { + saip_server_link_t *pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss); + lws_dll2_add_tail(&ssu->list, &pss->stay_state_update_owner); + } + + /* Request a writable callback to send the message */ + if (lws_ss_request_tx(sps->ss)) + lwsl_ss_warn(sps->ss, "Unable to request tx"); + + lwsl_notice("%s: Queued notification for %s\n", __func__, plat_name); +} + +void +saip_set_stay(const char *builder_name, int stay_on) +{ + saip_server_plat_t *sp = find_platform(&power, builder_name); + saip_server_link_t *pss; + saip_server_t *sps; + + if (!sp) + return; + + sp->stay = (char)stay_on; + saip_notify_server_stay_state(builder_name, stay_on); + + if (stay_on) { + if (sp->power_on_mac) { + saip_notify_server_power_state(sp->host, 1, 0); + write(lws_spawn_get_fd_stdxxx(lsp_wol, 0), + sp->power_on_mac, strlen(sp->power_on_mac)); + } + if (sp->power_on_url) { + if (lws_ss_client_connect(sp->ss_tasmota_on)) + lwsl_notice("%s: tasmota connect failed\n", __func__); + saip_notify_server_power_state(sp->host, 1, 0); + } + } else { + /* + * power-off is delayed, so we just set the stay flag... + * but let's cancel any pending power-off + */ + lws_sul_cancel(&sp->sul_delay_off); + } + + + /* Find the first (usually only) configured sai-server connection */ + if (!power.sai_server_owner.head) { + lwsl_warn("%s: No sai-server configured to notify\n", __func__); + return; + } + + sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list); + pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss); + + saip_queue_stay_info(sps, sp, pss); +} + +/* + * local-side h1 server for builders to connect to + */ + +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; +} local_srv_t; + +static lws_ss_state_return_t +local_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + local_srv_t *g = (local_srv_t *)userobj; + lws_ss_state_return_t r = LWSSSSRET_OK; + + if (g->size == g->pos) + return LWSSSSRET_TX_DONT_SEND; + + if (*len > g->size - g->pos) + *len = g->size - g->pos; + + if (!g->pos) + *flags |= LWSSS_FLAG_SOM; + + memcpy(buf, g->payload + g->pos, *len); + g->pos += *len; + + if (g->pos != g->size) /* more to do */ + r = lws_ss_request_tx(lws_ss_from_user(g)); + else + *flags |= LWSSS_FLAG_EOM; + + lwsl_ss_info(lws_ss_from_user(g), "TX %zu, flags 0x%x, r %d", *len, + (unsigned int)*flags, (int)r); + + return r; +} + +static lws_ss_state_return_t +local_srv_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + local_srv_t *g = (local_srv_t *)userobj; + sai_power_managed_builders_t *pmb; + sai_power_managed_builder_t *b; + char *path = NULL, pn[128]; + saip_server_link_t *pss; + saip_server_plat_t *sp; + saip_server_t *sps; + int apo = 0; + size_t len; + + // lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name((int)state)); + + switch ((int)state) { + case LWSSSCS_CREATING: + return lws_ss_request_tx(lws_ss_from_user(g)); + + case LWSSSCS_SERVER_TXN: + + lws_ss_get_metadata(lws_ss_from_user(g), "path", (const void **)&path, &len); + lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN path '%.*s' (%d)", (int)len, path, (int)len); + + /* + * path is containing a string like "/power-off/b32" + * match the last part to a known platform and find out how + * to power that off + */ + + if (lws_ss_set_metadata(lws_ss_from_user(g), "mime", "text/html", 9)) + return LWSSSSRET_DISCONNECT_ME; + + /* + * A transaction is starting on an accepted connection. Say + * that we're OK with the transaction, prepare the user + * object with the response, and request tx to start sending it. + */ + lws_ss_server_ack(lws_ss_from_user(g), 0); + + g->pos = 0; + + if (len == 1 && path[0] == '/') { + /* print controllable platforms */ + + g->size = 0; + + lws_start_foreach_dll(struct lws_dll2 *, px, power.sai_server_owner.head) { + saip_server_t *s = lws_container_of(px, saip_server_t, list); + + lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) { + saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list); + + if (g->size) + g->payload[g->size++] = ','; + g->size = g->size + (size_t)lws_snprintf(g->payload + g->size, sizeof(g->payload) - g->size - 3, "%s", sp->host); + + } lws_end_foreach_dll(px1); + } lws_end_foreach_dll(px); + + g->payload[g->size] = '\0'; + goto bail; + } + + if (len > 6 && !strncmp(path, "/stay/", 6)) { + lws_strnncpy(pn, &path[6], len - 6, sizeof(pn)); + + sp = find_platform(&power, pn); + + if (sp) + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "%c", '0' + sp->stay); + else + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "unknown host %s", pn); + goto bail; + } + + if (len > 10 && !strncmp(path, "/power-on/", 10)) { + lws_strnncpy(pn, &path[10], len - 10, sizeof(pn)); + sp = find_platform(&power, pn); + if (!sp) { + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Unable to find host %s", pn); + goto bail; + } + if (sp->power_on_mac) { + saip_notify_server_power_state(sp->host, 1, 0); + if (write(lws_spawn_get_fd_stdxxx(lsp_wol, 0), + sp->power_on_mac, strlen(sp->power_on_mac)) != + (ssize_t)strlen(sp->power_on_mac)) + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Write to resume %s failed %d", pn, errno); + else + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Resumed %s with stay", pn); + sp->stay = 1; + goto bail; + } + if (!sp->power_on_url) { + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "no power-on-url entry for %s", pn); + goto bail; + } + + if (lws_ss_client_connect(sp->ss_tasmota_on)) { + lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota ON secure stream"); + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "power-on ss failed create %s", sp->host); + goto bail; + } + + lwsl_warn("%s: powered on host %s\n", __func__, sp->host); + sp->stay = 1; /* so builder can understand it's manual */ + saip_notify_server_power_state(sp->host, 1, 0); + + pmb = malloc(sizeof(*pmb)); + if (!pmb) + return 1; + memset(pmb, 0, sizeof(*pmb)); + + b = malloc(sizeof(*b)); + if (!b) { + free(pmb); + return 1; + } + memset(b, 0, sizeof(*b)); + + lws_strncpy(b->name, sp->host, sizeof(b->name)); + b->stay_on = sp->stay; + + sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list); + pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss); + + lws_dll2_add_tail(&b->list, &pmb->builders); + lws_dll2_add_tail(&pmb->list, &pss->managed_builders_owner); + + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Manually powered on %s", sp->host); + goto bail; + } + + if (len > 16 && !strncmp(path, "/auto-power-off/", 16)) { + apo = 1; + lws_strnncpy(pn, &path[16], len - 16, sizeof(pn)); + goto power_off; + } + + if (len < 11 || strncmp(path, "/power-off/", 11)) { + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "URL path needs to start with /power-off/"); + goto bail; + } + + lws_strnncpy(pn, &path[11], len - 11, sizeof(pn)); + +power_off: + + /* + * Let's have a look at the platform + */ + + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Unable to find host %s", pn); + + sp = find_platform(&power, pn); + if (sp) { + + if (apo) { + char needs[128]; + + /* + * Since it's not a manual request, + * we should deny it if any deps still need us + */ + + needs[0] = '\0'; + lws_start_foreach_dll(struct lws_dll2 *, px1, sp->dependencies_owner.head) { + saip_server_plat_t *sp1 = lws_container_of(px1, saip_server_plat_t, dependencies_list); + + if (sp1->needed) + lws_snprintf(needs, sizeof(needs) - 1 - strlen(needs), "%s ", sp1->name); + + } lws_end_foreach_dll(px1); + + if (needs[0] || sp->needed) { + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "NAK: %s needed: %d, deps needed: '%s'", + pn, sp->needed, needs); + goto bail; + } + } + + /* + * OK this is it, schedule it to happen + */ + lws_sul_schedule(lws_ss_cx_from_user(g), 0, + &sp->sul_delay_off, + saip_sul_action_power_off, + 3 * LWS_USEC_PER_SEC); + + lwsl_warn("%s: scheduled powering off host %s\n", + __func__, sp->host); + + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "ACK: Scheduled powering off host %s", sp->host); + + sp->stay = 0; /* reset any manual power up */ + } + +bail: + return lws_ss_request_tx_len(lws_ss_from_user(g), + (unsigned long)g->size); + } + + return LWSSSSRET_OK; +} + + +LWS_SS_INFO("local", local_srv_t) + .tx = local_srv_tx, + .state = local_srv_state, +}; diff --git a/src/power/p-comms.c b/src/power/p-comms.c index 4ace8e7..b6523ba 100644 --- a/src/power/p-comms.c +++ b/src/power/p-comms.c @@ -35,14 +35,6 @@ static const lws_struct_map_t lsm_schema_power_state[] = { "com.warmcat.sai.powerstate"), }; -LWS_SS_USER_TYPEDEF - char payload[200]; - size_t size; - size_t pos; - - lws_dll2_owner_t ps_owner; -} saip_server_link_t; - void saip_notify_server_power_state(const char *plat_name, int up, int down) { @@ -83,6 +75,59 @@ saip_notify_server_power_state(const char *plat_name, int up, int down) lwsl_notice("%s: Queued notification for %s\n", __func__, plat_name); } +int +saip_queue_stay_info(saip_server_t *sps, saip_server_plat_t *sp, saip_server_link_t *pss) +{ + sai_power_managed_builders_t *pmb; + sai_power_managed_builder_t *b; + + pmb = malloc(sizeof(*pmb)); + if (!pmb) + return 1; + + memset(pmb, 0, sizeof(*pmb)); + + /* queue the update for the builder state */ + + b = malloc(sizeof(*b)); + if (!b) { + free(pmb); + return 1; + } + + memset(b, 0, sizeof(*b)); + lws_strncpy(b->name, sp->host, sizeof(b->name)); + b->stay_on = sp->stay; + + lws_dll2_add_tail(&b->list, &pmb->builders); + lws_dll2_add_tail(&pmb->list, &pss->managed_builders_owner); + + if (lws_ss_request_tx(sps->ss)) + lwsl_ss_warn(sps->ss, "Unable to request tx"); + + return 0; +} + +int +saip_builder_bringup(saip_server_t *sps, saip_server_plat_t *sp, saip_server_link_t *pss) +{ + saip_notify_server_power_state(sp->name, 1, 0); + + if (!strcmp(sp->power_on_type, "wol")) { + lwsl_notice("%s: triggering WOL\n", __func__); + write(lws_spawn_get_fd_stdxxx(lsp_wol, 0), + sp->power_on_mac, strlen(sp->power_on_mac)); + } + + if (!strcmp(sp->power_on_type, "tasmota")) { + lwsl_ss_notice(sp->ss_tasmota_on, "starting tasmota"); + if (lws_ss_client_connect(sp->ss_tasmota_on)) + lwsl_ss_err(sp->ss_tasmota_on, "failed to connect tasmota ON secure stream"); + } + + return saip_queue_stay_info(sps, sp, pss); +} + static lws_ss_state_return_t saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) { @@ -91,10 +136,29 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) const char *p = (const char *)buf, *end = (const char *)buf + len; char plat[128], benched[4096]; size_t n, bp = 0; + lws_struct_args_t a; + struct lejp_ctx ctx; lwsl_info("%s: len %d, flags: %d (saip_server_t %p)\n", __func__, (int)len, flags, (void *)sps); lwsl_hexdump_info(buf, len); + memset(&a, 0, sizeof(a)); + a.map_st[0] = lsm_schema_stay; + a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_stay); + a.ac_block_size = 512; + + lws_struct_json_init_parse(&ctx, NULL, &a); + if (lejp_parse(&ctx, (uint8_t *)buf, (int)len) >= 0 && a.dest) { + sai_stay_t *stay = (sai_stay_t *)a.dest; + + lwsl_warn("%s: received stay %s: %d\n", __func__, stay->builder_name, stay->stay_on); + + saip_set_stay(stay->builder_name, stay->stay_on); + lwsac_free(&a.ac); + return 0; + } + lwsac_free(&a.ac); + lws_start_foreach_dll(struct lws_dll2 *, px, sps->sai_plat_owner.head) { saip_server_plat_t *sp = lws_container_of(px, saip_server_plat_t, list); sp->needed = 0; @@ -148,25 +212,14 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) if (sp->needed) { lwsl_notice("%s: Needed builders: %s\n", __func__, sp->name); - saip_notify_server_power_state(sp->name, 1, 0); - /* * Server said this platform or at least one dependency * has pending jobs. sai-power config says this builder * can do jobs on that platform. Let's make sure it * is powered on. */ - if (!strcmp(sp->power_on_type, "wol")) { - lwsl_notice("%s: triggering WOL\n", __func__); - write(lws_spawn_get_fd_stdxxx(lsp_wol, 0), - sp->power_on_mac, strlen(sp->power_on_mac)); - } - - if (!strcmp(sp->power_on_type, "tasmota")) { - lwsl_ss_notice(sp->ss_tasmota_on, "starting tasmota"); - if (lws_ss_client_connect(sp->ss_tasmota_on)) - lwsl_ss_err(sp->ss_tasmota_on, "failed to connect tasmota ON secure stream"); - } + + saip_builder_bringup(sps, sp, pss); } else { bp += (size_t)lws_snprintf(&benched[bp], sizeof(benched) - bp - 1, "%s%s", !bp ? "" : ", ", sp->name); @@ -178,8 +231,6 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) if (bp) lwsl_notice("%s: Benched builders: %s\n", __func__, benched); - - (void)sps; return 0; @@ -192,26 +243,68 @@ saip_m_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, saip_server_link_t *pss = (saip_server_link_t *)userobj; lws_struct_serialize_t *js; - if (!pss->ps_owner.head) - return LWSSSSRET_TX_DONT_SEND; + if (pss->managed_builders_owner.head) { + sai_power_managed_builders_t *pmb = lws_container_of(pss->managed_builders_owner.head, + sai_power_managed_builders_t, list); + + js = lws_struct_json_serialize_create(lsm_schema_power_managed_builders, + LWS_ARRAY_SIZE(lsm_schema_power_managed_builders), 0, pmb); + if (!js) + lwsl_ss_warn(lws_ss_from_user(pss), "Failed to serialize managed builder"); + else + /* crashes here walking pmb */ + if (lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH) + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; + + lws_dll2_remove(&pmb->list); + free(pmb); + goto sendify; + } + + if (pss->stay_state_update_owner.head) { + sai_stay_state_update_t *ssu = lws_container_of(pss->stay_state_update_owner.head, + sai_stay_state_update_t, list); + js = lws_struct_json_serialize_create(lsm_schema_stay_state_update, + LWS_ARRAY_SIZE(lsm_schema_stay_state_update), 0, ssu); + if (!js) + lwsl_ss_warn(lws_ss_from_user(pss), "Failed to serialize state update"); + else + if (lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH) + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; + + lws_dll2_remove(&ssu->list); + free(ssu); + goto sendify; + } - /* Dequeue the first pending notification */ - sai_power_state_t *ps = lws_container_of(pss->ps_owner.head, - sai_power_state_t, list); + if (pss->ps_owner.head) { + /* Dequeue the first pending notification */ + sai_power_state_t *ps = lws_container_of(pss->ps_owner.head, sai_power_state_t, list); + + js = lws_struct_json_serialize_create(lsm_schema_power_state, 1, 0, ps); + if (!js) + lwsl_ss_warn(lws_ss_from_user(pss), "Failed to serialize state update"); + else + if (lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH) + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; - js = lws_struct_json_serialize_create(lsm_schema_power_state, 1, 0, ps); - if (js && lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH) { - *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; lws_dll2_remove(&ps->list); free(ps); + goto sendify; } + + return LWSSSSRET_TX_DONT_SEND; + +sendify: lws_struct_json_serialize_destroy(&js); /* If there are more to send, request another writable callback */ - if (pss->ps_owner.head) + if (pss->ps_owner.head || pss->managed_builders_owner.head || pss->stay_state_update_owner.head) if (lws_ss_request_tx(lws_ss_from_user(pss))) lwsl_ss_warn(lws_ss_from_user(pss), "tx request failed"); + lwsl_hexdump_notice(buf, *len); + return LWSSSSRET_OK; } @@ -292,9 +385,37 @@ saip_m_state(void *userobj, void *sh, lws_ss_constate_t state, break; case LWSSSCS_CONNECTED: - lwsl_info("%s: CONNECTED: %p\n", __func__, sps->ss); - return lws_ss_request_tx(sps->ss); + { + saip_server_link_t *pss = (saip_server_link_t *)userobj; + sai_power_managed_builders_t *pmb = malloc(sizeof(*pmb)); + + lwsl_ss_notice(sps->ss, "@@@@@@@@@@@@@@ sai-power CONNECTED to server"); + + if (!pmb) + return LWSSSSRET_DISCONNECT_ME; + + memset(pmb, 0, sizeof(*pmb)); + + lws_start_foreach_dll(struct lws_dll2 *, p, + sps->sai_plat_owner.head) { + saip_server_plat_t *sp = lws_container_of(p, + saip_server_plat_t, list); + sai_power_managed_builder_t *b = malloc(sizeof(*b)); + if (!b) + continue; + + memset(b, 0, sizeof(*b)); + lws_strncpy(b->name, sp->host, sizeof(b->name)); + b->stay_on = sp->stay; + + lws_dll2_add_tail(&b->list, &pmb->builders); + } lws_end_foreach_dll(p); + + lws_dll2_add_tail(&pmb->list, &pss->managed_builders_owner); + + return lws_ss_request_tx(sps->ss); + } case LWSSSCS_DISCONNECTED: lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, pss->ps_owner.head) { sai_power_state_t *ps = lws_container_of(d, sai_power_state_t, list); diff --git a/src/power/p-intake.c b/src/power/p-intake.c deleted file mode 100644 index 01a3ef7..0000000 --- a/src/power/p-intake.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Sai server - * - * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - * - * This is a ws server run by sai-power, which accepts JSON config from builders - * that want to use it as a helper for managing their power state. - */ - -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> -#include <time.h> -#include <stdio.h> -#include <fcntl.h> - -#include "p-private.h" - -#include "../common/struct-metadata.c" - - -static int -p_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, - void *in, size_t len) -{ - struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get( - lws_get_vhost(wsi), lws_get_protocol(wsi)); - uint8_t buf[LWS_PRE + 8192], *start = &buf[LWS_PRE], *p = start, - *end = &buf[sizeof(buf) - LWS_PRE - 1]; - struct pss *pss = (struct pss *)user; - const char *pvo_resources; - int n; - - (void)end; - (void)p; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - - break; - - /* - * ws connections from builders - */ - - case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - return 0; - - case LWS_CALLBACK_ESTABLISHED: - pss->wsi = wsi; - pss->vhd = vhd; - if (!vhd) - return -1; - - if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { - if (lws_hdr_copy(wsi, (char *)start, 64, - WSI_TOKEN_GET_URI) < 0) - return -1; - } -#if defined(LWS_ROLE_H2) - else - if (lws_hdr_copy(wsi, (char *)start, 64, - WSI_TOKEN_HTTP_COLON_PATH) < 0) - return -1; -#endif - - if (!memcmp((char *)start, "/sai", 4)) - start += 4; - - if (!strcmp((char *)start, "/builder")) { - lwsl_info("%s: ESTABLISHED: builder\n", __func__); - pss->wsi = wsi; - /* - * this adds our pss part, but not the logical builder - * yet, until we get the ws rx - */ - lws_dll2_add_head(&pss->same, &vhd->builders); - break; - } - - lwsl_err("%s: unknown URL '%s'\n", __func__, start); - - return -1; - - case LWS_CALLBACK_CLOSED: - lwsac_free(&pss->query_ac); - - lwsl_wsi_user(wsi, "CLOSED builder->power connection", __func__); - /* remove pss from vhd->builders */ - lws_dll2_remove(&pss->same); - - /* - * Destroy any the builder-tracking objects that - * were using this departing connection - */ - - lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, - vhd->server.builder_owner.head) { - sai_plat_t *cb = lws_container_of(p, sai_plat_t, - sai_plat_list); - - if (cb->wsi == wsi) { - /* remove builder object itself from server list */ - cb->wsi = NULL; - lws_dll2_remove(&cb->sai_plat_list); - /* - * free the deserialized builder object, - * everything he pointed to was overallocated - * when his deep copy was made - */ - free(cb); - } - - } lws_end_foreach_dll_safe(p, p1); - - sais_resource_wellknown_remove_pss(&pss->vhd->server, pss); - - if (pss->blob_artifact) { - sqlite3_blob_close(pss->blob_artifact); - pss->blob_artifact = NULL; - } - - if (pss->pdb_artifact) { - sais_event_db_close(pss->vhd, &pss->pdb_artifact); - pss->pdb_artifact = NULL; - } - - /* - * Update the sai-webs about the builder removal, so they - * can update their connected browsers - */ - lwsl_wsi_warn(wsi, "LWS_CALLBACK_CLOSED: doing WSS_PREPARE_BUILDER_SUMMARY\n"); - - sais_list_builders(vhd); - break; - - case LWS_CALLBACK_RECEIVE: - break; - - case LWS_CALLBACK_SERVER_WRITEABLE: - if (!vhd) { - lwsl_notice("%s: no vhd\n", __func__); - break; - } - - return sais_ws_json_tx_builder(vhd, pss, buf, sizeof(buf)); - - default: -passthru: - break; - } - - return lws_callback_http_dummy(wsi, reason, user, in, len); -} - -const struct lws_protocols protocol_ws_power = - { "com-warmcat-sai-power", p_callback_ws, sizeof(struct pss), 0 }; diff --git a/src/power/p-private.h b/src/power/p-private.h index 390d071..822e39a 100644 --- a/src/power/p-private.h +++ b/src/power/p-private.h @@ -132,6 +132,18 @@ struct jpargs { int next_plat_index; }; +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; + + lws_dll2_owner_t ps_owner; + lws_dll2_owner_t managed_builders_owner; + lws_dll2_owner_t stay_state_update_owner; +} saip_server_link_t; + + + extern struct sai_power power; extern const lws_ss_info_t ssi_saip_server_link_t, ssi_saip_smartplug_t; extern const struct lws_protocols protocol_com_warmcat_sai, protocol_ws_power; @@ -143,3 +155,7 @@ extern void saip_config_destroy(struct sai_power *power); extern void saip_notify_server_power_state(const char *plat_name, int up, int down); +void +saip_set_stay(const char *builder_name, int stay_on); +int +saip_queue_stay_info(saip_server_t *sps, saip_server_plat_t *sp, saip_server_link_t *pss); diff --git a/src/power/p-sai.c b/src/power/p-sai.c index 0a372ff..cf9ee95 100644 --- a/src/power/p-sai.c +++ b/src/power/p-sai.c @@ -81,6 +81,8 @@ struct lws_spawn_piped *lsp_wol; struct sai_power power; +extern const lws_ss_info_t ssi_local_srv_t; + static const char * const default_ss_policy = "{" "\"retry\": [" /* named backoff / retry strategies */ @@ -183,289 +185,6 @@ static const struct lws_protocols *pprotocols[] = { NULL }; -static void -saip_sul_action_power_off(struct lws_sorted_usec_list *sul) -{ - saip_server_plat_t *sp = lws_container_of(sul, - saip_server_plat_t, sul_delay_off); - - if (!sp->power_off_url) { - lwsl_notice("%s: no power_off_url for %s\n", __func__, sp->host); - return; - } - if (!sp->ss_tasmota_off) { - lwsl_notice("%s: no power_off ss for %s\n", __func__, sp->host); - return; - } - - lwsl_warn("%s: powering off host %s\n", __func__, sp->host); - saip_notify_server_power_state(sp->host, 0, 1); - - if (lws_ss_client_connect(sp->ss_tasmota_off)) - lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota OFF secure stream"); -} - -saip_server_plat_t * -find_platform(struct sai_power *pwr, const char *host) -{ - lws_start_foreach_dll(struct lws_dll2 *, px, pwr->sai_server_owner.head) { - saip_server_t *s = lws_container_of(px, saip_server_t, list); - - lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) { - saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list); - - if (!strcmp(host, sp->host)) - return sp; - - } lws_end_foreach_dll(px1); - } lws_end_foreach_dll(px); - - return NULL; -} - - -/* - * local-side h1 server for builders to connect to - */ - -LWS_SS_USER_TYPEDEF - char payload[200]; - size_t size; - size_t pos; -} local_srv_t; - -static lws_ss_state_return_t -local_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, - int *flags) -{ - local_srv_t *g = (local_srv_t *)userobj; - lws_ss_state_return_t r = LWSSSSRET_OK; - - if (g->size == g->pos) - return LWSSSSRET_TX_DONT_SEND; - - if (*len > g->size - g->pos) - *len = g->size - g->pos; - - if (!g->pos) - *flags |= LWSSS_FLAG_SOM; - - memcpy(buf, g->payload + g->pos, *len); - g->pos += *len; - - if (g->pos != g->size) /* more to do */ - r = lws_ss_request_tx(lws_ss_from_user(g)); - else - *flags |= LWSSS_FLAG_EOM; - - lwsl_ss_info(lws_ss_from_user(g), "TX %zu, flags 0x%x, r %d", *len, - (unsigned int)*flags, (int)r); - - return r; -} - -static lws_ss_state_return_t -local_srv_state(void *userobj, void *sh, lws_ss_constate_t state, - lws_ss_tx_ordinal_t ack) -{ - local_srv_t *g = (local_srv_t *)userobj; - char *path = NULL, pn[128]; - saip_server_plat_t *sp; - int apo = 0; - size_t len; - - // lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name((int)state)); - - switch ((int)state) { - case LWSSSCS_CREATING: - return lws_ss_request_tx(lws_ss_from_user(g)); - - case LWSSSCS_SERVER_TXN: - - lws_ss_get_metadata(lws_ss_from_user(g), "path", (const void **)&path, &len); - lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN path '%.*s' (%d)", (int)len, path, (int)len); - - /* - * path is containing a string like "/power-off/b32" - * match the last part to a known platform and find out how - * to power that off - */ - - if (lws_ss_set_metadata(lws_ss_from_user(g), "mime", "text/html", 9)) - return LWSSSSRET_DISCONNECT_ME; - - /* - * A transaction is starting on an accepted connection. Say - * that we're OK with the transaction, prepare the user - * object with the response, and request tx to start sending it. - */ - lws_ss_server_ack(lws_ss_from_user(g), 0); - - g->pos = 0; - - if (len == 1 && path[0] == '/') { - /* print controllable platforms */ - - g->size = 0; - - lws_start_foreach_dll(struct lws_dll2 *, px, power.sai_server_owner.head) { - saip_server_t *s = lws_container_of(px, saip_server_t, list); - - lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) { - saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list); - - if (g->size) - g->payload[g->size++] = ','; - g->size = g->size + (size_t)lws_snprintf(g->payload + g->size, sizeof(g->payload) - g->size - 3, "%s", sp->host); - - } lws_end_foreach_dll(px1); - } lws_end_foreach_dll(px); - - g->payload[g->size] = '\0'; - goto bail; - } - - if (len > 6 && !strncmp(path, "/stay/", 6)) { - lws_strnncpy(pn, &path[6], len - 6, sizeof(pn)); - - sp = find_platform(&power, pn); - - if (sp) - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "%c", '0' + sp->stay); - else - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "unknown host %s", pn); - goto bail; - } - - if (len > 10 && !strncmp(path, "/power-on/", 10)) { - lws_strnncpy(pn, &path[10], len - 10, sizeof(pn)); - sp = find_platform(&power, pn); - if (!sp) { - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "Unable to find host %s", pn); - goto bail; - } - if (sp->power_on_mac) { - saip_notify_server_power_state(sp->host, 1, 0); - if (write(lws_spawn_get_fd_stdxxx(lsp_wol, 0), - sp->power_on_mac, strlen(sp->power_on_mac)) != - (ssize_t)strlen(sp->power_on_mac)) - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "Write to resume %s failed %d", pn, errno); - else - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "Resumed %s with stay", pn); - sp->stay = 1; - goto bail; - } - if (!sp->power_on_url) { - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "no power-on-url entry for %s", pn); - goto bail; - } - - if (lws_ss_client_connect(sp->ss_tasmota_on)) { - lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota ON secure stream"); - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "power-on ss failed create %s", sp->host); - goto bail; - } - - lwsl_warn("%s: powered on host %s\n", __func__, sp->host); - saip_notify_server_power_state(sp->host, 1, 0); - - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "Manually powered on %s", sp->host); - - sp->stay = 1; /* so builder can understand it's manual */ - goto bail; - } - - if (len > 16 && !strncmp(path, "/auto-power-off/", 16)) { - apo = 1; - lws_strnncpy(pn, &path[16], len - 16, sizeof(pn)); - goto power_off; - } - - if (len < 11 || strncmp(path, "/power-off/", 11)) { - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "URL path needs to start with /power-off/"); - goto bail; - } - - lws_strnncpy(pn, &path[11], len - 11, sizeof(pn)); - -power_off: - - /* - * Let's have a look at the platform - */ - - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "Unable to find host %s", pn); - - sp = find_platform(&power, pn); - if (sp) { - - if (apo) { - char needs[128]; - - /* - * Since it's not a manual request, - * we should deny it if any deps still need us - */ - - needs[0] = '\0'; - lws_start_foreach_dll(struct lws_dll2 *, px1, sp->dependencies_owner.head) { - saip_server_plat_t *sp1 = lws_container_of(px1, saip_server_plat_t, dependencies_list); - - if (sp1->needed) - lws_snprintf(needs, sizeof(needs) - 1 - strlen(needs), "%s ", sp1->name); - - } lws_end_foreach_dll(px1); - - if (needs[0] || sp->needed) { - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "NAK: %s needed: %d, deps needed: '%s'", - pn, sp->needed, needs); - goto bail; - } - } - - /* - * OK this is it, schedule it to happen - */ - lws_sul_schedule(lws_ss_cx_from_user(g), 0, - &sp->sul_delay_off, - saip_sul_action_power_off, - 3 * LWS_USEC_PER_SEC); - - lwsl_warn("%s: scheduled powering off host %s\n", - __func__, sp->host); - - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "ACK: Scheduled powering off host %s", sp->host); - - sp->stay = 0; /* reset any manual power up */ - } - -bail: - return lws_ss_request_tx_len(lws_ss_from_user(g), - (unsigned long)g->size); - } - - return LWSSSSRET_OK; -} - - -LWS_SS_INFO("local", local_srv_t) - .tx = local_srv_tx, - .state = local_srv_state, -}; - - static int app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, int current, int target) diff --git a/src/server/s-comms.c b/src/server/s-comms.c index e122be3..53c4e44 100644 --- a/src/server/s-comms.c +++ b/src/server/s-comms.c @@ -740,7 +740,7 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, } if (!strcmp((char *)start, "/power")) { - lwsl_info("%s: ESTABLISHED: power\n", __func__); + lwsl_notice("%s: ESTABLISHED: power connection\n", __func__); pss->wsi = wsi; pss->is_power = 1; lws_dll2_add_head(&pss->same, &vhd->sai_powers); @@ -793,17 +793,23 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, struct lejp_ctx ctx; lws_struct_args_t a; sai_power_state_t *ps; - const lws_struct_map_t lsm_schema_power_state[] = { + const lws_struct_map_t lsm_schema_map_power[] = { LSM_SCHEMA(sai_power_state_t, NULL, lsm_power_state, "com.warmcat.sai.powerstate"), + LSM_SCHEMA(sai_power_managed_builders_t, NULL, + lsm_power_managed_builders_list, + "com.warmcat.sai.power_managed_builders"), + LSM_SCHEMA(sai_stay_state_update_t, NULL, + lsm_stay_state_update, + "com.warmcat.sai.stay_state_update"), }; /* This is a message from sai-power */ lwsl_notice("RX from sai-power: %.*s\n", (int)len, (const char *)in); memset(&a, 0, sizeof(a)); - a.map_st[0] = lsm_schema_power_state; - a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_power_state); + a.map_st[0] = lsm_schema_map_power; + a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_map_power); a.ac_block_size = 512; lws_struct_json_init_parse(&ctx, NULL, &a); @@ -813,17 +819,79 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, break; // Exit case } - ps = (sai_power_state_t *)a.dest; - if (ps->powering_up) { - lwsl_notice("sai-power is powering up: %s\n", ps->host); - sais_set_builder_power_state(vhd, ps->host, 1, 0); - } else if (ps->powering_down) { - lwsl_notice("sai-power is powering down: %s\n", ps->host); - sais_set_builder_power_state(vhd, ps->host, 0, 1); + switch (a.top_schema_index) { + case 0: /* powerstate */ + ps = (sai_power_state_t *)a.dest; + if (ps->powering_up) { + lwsl_notice("sai-power is powering up: %s\n", ps->host); + sais_set_builder_power_state(vhd, ps->host, 1, 0); + } else if (ps->powering_down) { + lwsl_notice("sai-power is powering down: %s\n", ps->host); + sais_set_builder_power_state(vhd, ps->host, 0, 1); + } + break; + + case 1: { + sai_power_managed_builders_t *pmb = (sai_power_managed_builders_t *)a.dest; + + lws_start_foreach_dll(struct lws_dll2 *, p, pmb->builders.head) { + sai_power_managed_builder_t *b = lws_container_of(p, + sai_power_managed_builder_t, list); + char q[256]; + lwsl_notice("%s: Marking builder %s as power-managed\n", + __func__, b->name); + lws_snprintf(q, sizeof(q), + "UPDATE builders SET power_managed=1 WHERE name = '%s' OR name LIKE '%s.%%'", + b->name, b->name); + if (sai_sqlite3_statement(vhd->server.pdb, q, "set power_managed")) + lwsl_err("%s: Failed to mark builder %s as power-managed\n", + __func__, b->name); + + sai_plat_t *cb; + lws_start_foreach_dll(struct lws_dll2 *, p2, + vhd->server.builder_owner.head) { + cb = lws_container_of(p2, sai_plat_t, + sai_plat_list); + const char *dot = strchr(cb->name, '.'); + if (dot && !strncmp(cb->name, b->name, (size_t)(dot - cb->name))) + cb->stay_on = b->stay_on; + } lws_end_foreach_dll(p2); + + } lws_end_foreach_dll(p); + + sais_list_builders(vhd); + + break; + } + case 2: { + sai_stay_state_update_t *ssu = (sai_stay_state_update_t *)a.dest; + sai_plat_t *cb; + + lwsl_notice("%s: Received stay_state_update for %s, stay_on=%d\n", + __func__, ssu->builder_name, ssu->stay_on); + + lws_start_foreach_dll(struct lws_dll2 *, p, + vhd->server.builder_owner.head) { + cb = lws_container_of(p, sai_plat_t, + sai_plat_list); + + const char *dot = strchr(cb->name, '.'); + + if (dot && !strncmp(cb->name, ssu->builder_name, (size_t)(dot - cb->name))) { + lwsl_notice("%s: Updating builder %s stay_on from %d to %d\n", + __func__, cb->name, cb->stay_on, ssu->stay_on); + cb->stay_on = ssu->stay_on; + sais_list_builders(vhd); + break; + } + } lws_end_foreach_dll(p); + + break; + } } lwsac_free(&a.ac); - break; // Exit case + break; } /* diff --git a/src/server/s-private.h b/src/server/s-private.h index 57d6b9c..7707193 100644 --- a/src/server/s-private.h +++ b/src/server/s-private.h @@ -112,6 +112,7 @@ struct pss { lws_dll2_owner_t platform_owner; /* sai_platform_t builder offers */ lws_dll2_owner_t task_cancel_owner; /* sai_platform_t builder offers */ lws_dll2_owner_t rebuild_owner; + lws_dll2_owner_t stay_owner; lws_dll2_owner_t aft_owner; /* for statefully spooling artifact info */ lws_dll2_owner_t res_owner; /* sai_resource_requisition_t * owner of resource objects related diff --git a/src/server/s-websrv.c b/src/server/s-websrv.c index a0a4635..89835d6 100644 --- a/src/server/s-websrv.c +++ b/src/server/s-websrv.c @@ -90,6 +90,8 @@ static const lws_struct_map_t lsm_schema_json_map[] = { "com.warmcat.sai.rebuild"), LSM_SCHEMA (sai_browse_rx_platreset_t, NULL, lsm_browser_platreset, "com.warmcat.sai.platreset"), + LSM_SCHEMA (sai_stay_t, NULL, lsm_stay, + "com.warmcat.sai.stay"), }; enum { @@ -101,6 +103,7 @@ enum { SAIS_WS_WEBSRV_RX_VIEWERCOUNT, SAIS_WS_WEBSRV_RX_REBUILD, SAIS_WS_WEBSRV_RX_PLATRESET, + SAIS_WS_WEBSRV_RX_STAY, }; void @@ -218,7 +221,6 @@ sais_websrv_broadcast(struct lws_ss_handle *hsrv, const char *str, size_t len) int sais_list_builders(struct vhd *vhd) { - lwsl_warn("%s: ENTRY\n", __func__); lws_dll2_owner_t db_builders_owner; struct lwsac *ac = NULL; char *p = vhd->json_builders, *end = p + sizeof(vhd->json_builders), @@ -253,12 +255,20 @@ sais_list_builders(struct vhd *vhd) live_builder = sais_builder_from_uuid(vhd, builder_from_db->name, __FILE__, __LINE__); if (live_builder) { + lwsl_notice("%s: live_builder %s found, stay_on: %d, copying to db_builder (stay_on: %d)\n", + __func__, live_builder->name, live_builder->stay_on, builder_from_db->stay_on); builder_from_db->online = 1; lws_strncpy(builder_from_db->peer_ip, live_builder->peer_ip, sizeof(builder_from_db->peer_ip)); + builder_from_db->stay_on = live_builder->stay_on; } else builder_from_db->online = 0; + if (builder_from_db->power_managed) + lwsl_notice("%s: builder %s is power managed (stay: %d)\n", + __func__, builder_from_db->name, + builder_from_db->stay_on); + builder_from_db->powering_up = 0; builder_from_db->powering_down = 0; @@ -296,6 +306,7 @@ sais_list_builders(struct vhd *vhd) p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "]}"); + lwsl_notice("%s: Broadcasting builder list: %s\n", __func__, vhd->json_builders); sais_websrv_broadcast(vhd->h_ss_websrv, vhd->json_builders, lws_ptr_diff_size_t(p, vhd->json_builders)); @@ -741,6 +752,25 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags) } lws_end_foreach_dll(p); } break; + + case SAIS_WS_WEBSRV_RX_STAY: + { + sai_stay_t *stay = (sai_stay_t *)a.dest; + + lws_start_foreach_dll(struct lws_dll2 *, p, + m->vhd->sai_powers.head) { + struct pss *pss_power = lws_container_of(p, struct pss, same); + sai_stay_t *s = malloc(sizeof(*s)); + if (s) { + *s = *stay; + lws_dll2_add_tail(&s->list, &pss_power->stay_owner); + lws_callback_on_writable(pss_power->wsi); + } + } lws_end_foreach_dll(p); + + lwsac_free(&a.ac); + break; + } } return 0; diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c index 8d35e54..c02b6d3 100644 --- a/src/server/s-ws-builder.c +++ b/src/server/s-ws-builder.c @@ -522,16 +522,21 @@ handle: sai_plat_t *live_cb; /* - * Step 1: Upsert this platform into the persistent database. + * Step 1: Update this platform in the persistent database. */ - build->online = 1; - build->last_seen = (uint64_t)lws_now_secs(); - lws_strncpy(build->peer_ip, pss->peer_ip, sizeof(build->peer_ip)); - if (lws_struct_sq3_upsert(vhd->server.pdb, "builders", lsm_plat, - LWS_ARRAY_SIZE(lsm_plat), build, "name")) { + char q[1024]; + + lws_snprintf(q, sizeof(q), + "INSERT INTO builders (name, platform, online, last_seen, peer_ip, sai_hash, lws_hash, windows) " + "VALUES ('%s', '%s', 1, %llu, '%s', '%s', '%s', %d) " + "ON CONFLICT(name) DO UPDATE SET online=1, last_seen=excluded.last_seen, " + "peer_ip=excluded.peer_ip, sai_hash=excluded.sai_hash, lws_hash=excluded.lws_hash", + build->name, build->platform, (unsigned long long)lws_now_secs(), + pss->peer_ip, build->sai_hash, build->lws_hash, build->windows); + + if (sai_sqlite3_statement(vhd->server.pdb, q, "upsert builder")) lwsl_err("%s: Failed to upsert builder %s\n", - __func__, build->name); - } + __func__, build->name); /* * Step 2: Update the long-lived, malloc'd in-memory list. @@ -1097,6 +1102,27 @@ sais_ws_json_tx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, goto send_json; } + if (pss->stay_owner.head) { + /* + * Pending stay message to send + */ + sai_stay_t *s = lws_container_of(pss->stay_owner.head, + sai_stay_t, list); + + js = lws_struct_json_serialize_create(lsm_schema_stay, + LWS_ARRAY_SIZE(lsm_schema_stay), 0, s); + if (!js) + return 1; + + n = (int)lws_struct_json_serialize(js, p, lws_ptr_diff_size_t(end, p), &w); + lws_struct_json_serialize_destroy(&js); + + lws_dll2_remove(&s->list); + free(s); + + goto send_json; + } + if (pss->rebuild_owner.head) { /* * Pending rebuild message to send diff --git a/src/web/w-websrv.c b/src/web/w-websrv.c index d83ee53..3ef960b 100644 --- a/src/web/w-websrv.c +++ b/src/web/w-websrv.c @@ -298,6 +298,7 @@ saiw_lp_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, *flags = (som ? LWSSS_FLAG_SOM : 0) | (final ? LWSSS_FLAG_EOM : 0); lwsl_ss_notice(m->ss, "Sending %d ssflags %d", (int)*len, (int)*flags); + lwsl_hexdump_notice(buf, *len); if (m->wbltx) return lws_ss_request_tx(m->ss); diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c index 28ea478..cbccfa3 100644 --- a/src/web/w-ws-browser.c +++ b/src/web/w-ws-browser.c @@ -80,6 +80,8 @@ static const lws_struct_map_t lsm_schema_json_map_bwsrx[] = { "com.warmcat.sai.rebuild"), LSM_SCHEMA (sai_browse_rx_platreset_t, NULL, lsm_browser_platreset, "com.warmcat.sai.platreset"), + LSM_SCHEMA (sai_stay_t, NULL, lsm_stay, + "com.warmcat.sai.stay"), }; enum { @@ -93,6 +95,7 @@ enum { SAIM_WS_BROWSER_RX_JS_HELLO, SAIM_WS_BROWSER_RX_REBUILD, SAIM_WS_BROWSER_RX_PLATRESET, + SAIM_WS_BROWSER_RX_STAY, }; @@ -510,6 +513,17 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl, ss_flags); break; + case SAIM_WS_BROWSER_RX_STAY: + if (!sais_conn_auth(pss)) + goto auth_error; + + /* + * User is asking us to set or release a stay on a builder + */ + + saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl, ss_flags); + break; + case SAIM_WS_BROWSER_RX_TASKREBUILDLASTSTEP: if (!sais_conn_auth(pss)) goto auth_error;
Page fetched 0s ago, creation time: 45ms (vhost etag hits: 0%, cache hits: 0%)