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