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
Author[]Andy Green <andy@warmcat.com> 2025-05-17 05:23 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-05-30 07:10 UTC
Tree07f5082282e1804ec62f7940cf5d4866aa34fd05   Raw Patch
 
server: use single vhost for power protocol too
server: use single vhost for power protocol too
diff --git a/READMEs/README-sai-power.md b/READMEs/README-sai-power.md index 75b9be2..d2b0008 100644 --- a/READMEs/README-sai-power.md +++ b/READMEs/README-sai-power.md @@ -82,6 +82,21 @@ Two types of builder power management is supported seconds, then enter shutdown. sai-power will turn it back on again at the smartplug when jobs it can handle appear at a server it knows how to use. +Some builders, eg, repurposed laptops, may suspend well but not actually have any +simple way to be powered down and turned on again (eg, only way is power key on +keyboard; battery will try to power it if you simply turn off mains power to it). +Conversely, some SBCs can be powered on and off from the mains well, turning on +automatically when power reapplied, but suspend or WOL does not work properly on +them. So both approaches are needed. + +Blade 3 SBC has another corner case, if you shutdown -h now before removing the +power, the SBC stays awake enough with power in its capacitors for many minutes +to not come back up properly when you reapply power; it reaches a kind of zombie +state where it feels it is OFF despite being powered. You can work around this +kind of case by using shutdown -H instead, which does the OS shutdown but doesn't +ask the kernel to "turn OFF". This drains the caps in under a second and it is +able to be managed normally then. + ## Configuration at the builder Builders that participate in sai-power management need to point to sai-power @@ -100,24 +115,36 @@ the builder to request sai power to turn itself off. Builders that are managed by sai-power are OFF a lot, basically any time it finished building the last push until the next one, which may be whole days or more. -If you need to ssh in to them, that would be inconvenient since they are literally OFF. +If you need to ssh in to them to manage them, that is inconvenient since they are +literally OFF. Even if you caught them while on and building, they can choose to go OFF +or suspend at any time. -To simplify that case, you can manually ask sai-power to start up a builder and -for that session only, disable its idle detection. +To simplify that case, you can manually ask sai-power to start up a builder. ``` # wget -O- http://10.199.0.10:3333/power-on/hostname ``` +You can also manually turn off the builder from the same api. + +``` +# wget -O- http://10.199.0.10:3333/power-off/hostname +``` + Asking sai-power to do it has some advantages: - it will work the same no matter the details of that particular builder's - power arrangements, ie, if suspend / resume or needs a specific smartplug, - sai-power knows what to do depending on the hostname + power arrangements, ie, if suspend / resume, or needs a specific smartplug, + sai-power knows what to do depending on the hostname (and config) while + the "api" url is the same. - sai-power can remember if you did it manually (as opposed to sai-power starting the builder since it saw jobs available) and inform the builder after it starts that for this session, it shouldn't auto suspend / power down. +In order to reset the idle detection, you should manually use sai-power to +power down the builder. Next time it starts, idle detection will be operational +again. + diff --git a/src/power/p-conf.c b/src/power/p-conf.c index 4ac0418..a907a80 100644 --- a/src/power/p-conf.c +++ b/src/power/p-conf.c @@ -31,6 +31,7 @@ static const char * const paths_global[] = { "perms", + "wol-if", "servers[].url", "servers[].platforms[].name", "servers[].platforms[].host", @@ -47,6 +48,7 @@ static const char * const paths_global[] = { enum enum_paths_global { LEJPM_PERMS, + LEJPM_WOL_IF, LEJPM_SERVERS_URL, LEJPM_SERVERS_PLATFORMS_NAME, LEJPM_SERVERS_PLATFORMS_HOST, @@ -130,6 +132,10 @@ saip_conf_global_cb(struct lejp_ctx *ctx, char reason) pp = &a->power->perms; break; + case LEJPM_WOL_IF: + pp = &a->power->wol_if; + break; + case LEJPM_SERVERS_URL: pp = &a->sai_server->url; lwsl_user("%s: server url %.*s\n", __func__, ctx->npos, ctx->buf); diff --git a/src/power/p-private.h b/src/power/p-private.h index 725ebb6..00dfd19 100644 --- a/src/power/p-private.h +++ b/src/power/p-private.h @@ -102,6 +102,8 @@ struct sai_power { const char *power_off; + const char *wol_if; + const char *bind; /* listen socket binding */ const char *perms; /* user:group */ diff --git a/src/power/p-sai.c b/src/power/p-sai.c index d86b6ce..d6eef2d 100644 --- a/src/power/p-sai.c +++ b/src/power/p-sai.c @@ -112,7 +112,7 @@ static const char * const default_ss_policy = "\"endpoint\":" "\"${url}\"," "\"port\":" "443," "\"protocol\":" "\"ws\"," - "\"ws_subprotocol\":" "\"com-warmcat-sai-power\"," + "\"ws_subprotocol\":" "\"com-warmcat-sai\"," "\"http_url\":" "\"\"," /* filled in by url */ "\"nailed_up\":" "true," "\"tls\":" "true," @@ -166,6 +166,11 @@ 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; + } + lwsl_warn("%s: powering off host %s\n", __func__, sp->host); if (lws_ss_create(power.context, 0, &ssi_saip_smartplug_t, @@ -291,9 +296,13 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state, goto bail; } if (sp->power_on_mac) { - write(lws_spawn_get_fd_stdxxx(lsp_wol, 0), - sp->power_on_mac, strlen(sp->power_on_mac)); - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + 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", &path[10], errno); + else + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), "Resumed %s with stay", &path[10]); sp->stay = 1; goto bail; @@ -456,6 +465,8 @@ int main(int argc, const char **argv) printf("%s: WOL subprocess generation...\n", __func__); + info.wol_if = argv[2]; + cx = lws_create_context(&info); if (!cx) { lwsl_err("%s: failed to create wol cx\n", __func__); @@ -482,15 +493,14 @@ int main(int argc, const char **argv) min[n] = '\0'; - { int fd = open("/tmp/q", O_CREAT | O_TRUNC | O_RDWR, 0644); write(fd, min, (size_t)(n + 1)); close(fd); } - if (lws_parse_mac(min, mac)) { lwsl_user("Failed to parse mac '%s'\n", min); } else if (lws_wol(cx, NULL, mac)) { lwsl_user("Failed to WOL '%s'\n", min); - } else + } else { lwsl_user("Sent WOL to '%s'\n", min); + } } return 0; @@ -544,11 +554,22 @@ int main(int argc, const char **argv) * Let's parse the global bits out of the config */ + lwsl_notice("%s: config dir %s\n", __func__, config_dir); + if (saip_config_global(&power, config_dir)) { + lwsl_err("%s: global config failed\n", __func__); + + return 1; + } + + info.wol_if = power.wol_if; + if (power.wol_if) + lwsl_notice("%s: WOL bound to interface %s\n", __func__, power.wol_if); + info.pprotocols = pprotocols; //info.uid = 883; info.pt_serv_buf_size = 32 * 1024; info.rlimit_nofile = 20000; - info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; signal(SIGINT, sigint_handler); @@ -569,13 +590,6 @@ int main(int argc, const char **argv) return 1; } - lwsl_notice("%s: config dir %s\n", __func__, config_dir); - if (saip_config_global(&power, config_dir)) { - lwsl_err("%s: global config failed\n", __func__); - - return 1; - } - power.vhost = lws_create_vhost(power.context, &info); if (!power.vhost) { lwsl_err("Failed to create tls vhost\n"); @@ -585,7 +599,7 @@ int main(int argc, const char **argv) { struct lws_spawn_piped_info info; char rpath[PATH_MAX]; - const char * const ea[] = { rpath, "-s", NULL }; + const char * const ea[] = { rpath, "-s", power.wol_if, NULL }; realpath(argv[0], rpath); diff --git a/src/power/p-smartplug.c b/src/power/p-smartplug.c index c053f45..f7bc668 100644 --- a/src/power/p-smartplug.c +++ b/src/power/p-smartplug.c @@ -38,13 +38,12 @@ saip_spc_state(void *userobj, void *sh, lws_ss_constate_t state, saip_smartplug_t *pss = (saip_smartplug_t *)userobj; const char *op_url = (const char *)lws_ss_opaque_from_user(pss); - lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state), - (unsigned int)ack); +// lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state), +// (unsigned int)ack); switch (state) { case LWSSSCS_CREATING: - lwsl_notice("%s: binding ss to %s\n", __func__, op_url); if (lws_ss_set_metadata(lws_ss_from_user(pss), @@ -53,10 +52,6 @@ saip_spc_state(void *userobj, void *sh, lws_ss_constate_t state, return lws_ss_client_connect(lws_ss_from_user(pss)); - case LWSSSCS_QOS_ACK_REMOTE: - lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); - break; - default: break; } diff --git a/src/server/s-comms.c b/src/server/s-comms.c index 22ef381..9421ae9 100644 --- a/src/server/s-comms.c +++ b/src/server/s-comms.c @@ -696,6 +696,15 @@ callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, break; } + if (!strcmp((char *)start, "/power")) { + lwsl_info("%s: ESTABLISHED: power\n", __func__); + pss->wsi = wsi; + pss->is_power = 1; + lws_dll2_add_head(&pss->same, &vhd->sai_powers); + sais_platforms_with_tasks_pending(vhd); + break; + } + lwsl_err("%s: unknown URL '%s'\n", __func__, start); return -1; @@ -780,142 +789,34 @@ callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, 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 = - { "com-warmcat-sai", callback_ws, sizeof(struct pss), 0 }; - - - /* - * This is where the sai-power connections to us end up. They - * want to know summaries of platforms that have open jobs on any - * event, re-sent every time it might have changed. - */ - - -static int -callback_ws_power(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_power *pss = (struct pss_power *)user; - int n; - size_t m; - - (void)end; - (void)p; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), - sizeof(struct vhd)); - - vhd->context = lws_get_context(wsi); - vhd->vhost = lws_get_vhost(wsi); - - if (lws_pvo_get_str(in, "database", &vhd->sqlite3_path_lhs)) { - lwsl_err("%s: database pvo required\n", __func__); - return -1; - } - - lws_snprintf((char *)buf, sizeof(buf), "%s-events.sqlite3", - vhd->sqlite3_path_lhs); - - if (lws_struct_sq3_open(vhd->context, (char *)buf, 1, - &vhd->server.pdb)) { - lwsl_err("%s: Unable to open session db %s: %s\n", - __func__, vhd->sqlite3_path_lhs, sqlite3_errmsg( - vhd->server.pdb)); - - return -1; - } - break; + if (pss->is_power) { + n = 0; + lws_start_foreach_dll(struct lws_dll2 *, px, vhd->pending_plats.head) { + sais_plat_t *pl = lws_container_of(px, sais_plat_t, list); + size_t m; - case LWS_CALLBACK_PROTOCOL_DESTROY: - goto passthru; + if (n) + *p++ = ','; + m = strlen(pl->plat); + if (lws_ptr_diff_size_t(end, p) < m + 2) + break; + memcpy(p, pl->plat, m); + p += m; + n = 1; - /* - * ws connections from sai-power clients - */ + } lws_end_foreach_dll(px); - case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - return 0; - case LWS_CALLBACK_ESTABLISHED: - pss->wsi = wsi; - pss->vhd = vhd; - if (!vhd) - return -1; + lwsl_hexdump_notice(start, lws_ptr_diff_size_t(p, start)); - if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { - if (lws_hdr_copy(wsi, (char *)start, 64, - WSI_TOKEN_GET_URI) < 0) + if (lws_write(pss->wsi, start, lws_ptr_diff_size_t(p, start), + LWS_WRITE_TEXT) < 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 - - lws_dll2_add_head(&pss->same, &vhd->sai_powers); - sais_platforms_with_tasks_pending(vhd); - - break; - - case LWS_CALLBACK_CLOSED: - - lwsl_user("%s: CLOSED sai-power conn\n", __func__); - /* remove pss from vhd->sai_powers */ - lws_dll2_remove(&pss->same); - break; - - case LWS_CALLBACK_RECEIVE: - break; - - case LWS_CALLBACK_SERVER_WRITEABLE: - if (!vhd) { - lwsl_notice("%s: no vhd\n", __func__); break; } - n = 0; - lws_start_foreach_dll(struct lws_dll2 *, px, vhd->pending_plats.head) { - sais_plat_t *pl = lws_container_of(px, sais_plat_t, list); - - if (n) - *p++ = ','; - m = strlen(pl->plat); - if (lws_ptr_diff_size_t(end, p) < m + 2) - break; - memcpy(p, pl->plat, m); - p += m; - n = 1; - - } lws_end_foreach_dll(px); - - - lwsl_hexdump_notice(start, lws_ptr_diff_size_t(p, start)); - - if (lws_write(pss->wsi, start, lws_ptr_diff_size_t(p, start), - LWS_WRITE_TEXT) < 0) - return -1; - - break; + return sais_ws_json_tx_builder(vhd, pss, buf, sizeof(buf)); default: passthru: @@ -925,5 +826,5 @@ passthru: return lws_callback_http_dummy(wsi, reason, user, in, len); } -const struct lws_protocols protocol_ws_power = - { "com-warmcat-sai-power", callback_ws_power, sizeof(struct pss_power), 0 }; +const struct lws_protocols protocol_ws = + { "com-warmcat-sai", callback_ws, sizeof(struct pss), 0 }; diff --git a/src/server/s-private.h b/src/server/s-private.h index f6bcd67..9c67b55 100644 --- a/src/server/s-private.h +++ b/src/server/s-private.h @@ -147,17 +147,11 @@ struct pss { unsigned int wants_event_updates:1; unsigned int announced:1; unsigned int bulk_binary_data:1; + unsigned int is_power:1; uint8_t ovstate; /* SOS_ substate when doing overview */ }; -struct pss_power { - struct vhd *vhd; - struct lws *wsi; - - struct lws_dll2 same; /* owner: vhd.builders */ -}; - typedef struct sais_sqlite_cache { lws_dll2_t list; char uuid[65]; diff --git a/src/server/s-sai.c b/src/server/s-sai.c index b77c52c..8f76a09 100644 --- a/src/server/s-sai.c +++ b/src/server/s-sai.c @@ -55,7 +55,6 @@ static const char * const default_ss_policy = static const struct lws_protocols *pprotocols[] = { &protocol_ws, - &protocol_ws_power, #if defined(LWS_WITH_SYS_METRICS) && defined(LWS_WITH_PLUGINS_BUILTIN) &lws_openmetrics_export_protocols[LWSOMPROIDX_PROX_HTTP_SERVER], &lws_openmetrics_export_protocols[LWSOMPROIDX_PROX_WS_SERVER], diff --git a/src/server/s-task.c b/src/server/s-task.c index be34783..307447f 100644 --- a/src/server/s-task.c +++ b/src/server/s-task.c @@ -165,6 +165,8 @@ sais_set_task_state(struct vhd *vhd, const char *builder_name, sais_taskchange(vhd->h_ss_websrv, task_uuid, state); + sais_platforms_with_tasks_pending(vhd); + /* * So, how many tasks for this event? */ @@ -302,7 +304,7 @@ sais_task_pending(struct vhd *vhd, struct lwsac **pac, const char *platform) if (!sais_event_db_ensure_open(vhd, e->uuid, 0, &pdb)) { lws_snprintf(pf, sizeof(pf), - " and state=0 and platform='%s'", esc); + " and (state == 0) and (platform == '%s')", esc); n = lws_struct_sq3_deserialize(pdb, pf, NULL, lsm_schema_sq3_map_task, &ot, pac, 0, 1); @@ -374,7 +376,7 @@ static void sais_notify_all_sai_power(struct vhd *vhd) { lws_start_foreach_dll(struct lws_dll2 *, p, vhd->sai_powers.head) { - struct pss_power *pss = lws_container_of(p, struct pss_power, same); + struct pss *pss = lws_container_of(p, struct pss, same); lws_callback_on_writable(pss->wsi); @@ -400,7 +402,7 @@ sais_platforms_with_tasks_pending(struct vhd *vhd) * Collect a list of events that still have any open tasks */ - lws_snprintf(pf, sizeof(pf)," and (state == 0 or state == 1 or state == 2)"); + lws_snprintf(pf, sizeof(pf)," and (state != 3 and state != 4 and state != 5)"); n = lws_struct_sq3_deserialize(vhd->server.pdb, pf, "created desc ", lsm_schema_sq3_map_event, &o, &ac, 0, 20); @@ -425,7 +427,7 @@ sais_platforms_with_tasks_pending(struct vhd *vhd) if (sqlite3_prepare_v2(pdb, "select distinct platform " "from tasks where " - "(state == 0)", -1, &sm, + "(state == 0 or state == 1)", -1, &sm, NULL) != SQLITE_OK) { lwsl_err("%s: Unable to %s\n", __func__, sqlite3_errmsg(pdb)); @@ -632,6 +634,8 @@ sais_allocate_task(struct vhd *vhd, struct pss *pss, sai_plat_t *cb, pss->a.ac = NULL; + sais_platforms_with_tasks_pending(vhd); + /* * We are going to leave here with a live pss->a.ac (pointed into by * task->one_event) that the caller has to take responsibility to
Page fetched 0s ago, creation time: 4ms (vhost etag hits: 0%, cache hits: 0%)