Author: Andy Green Date: Thu May 15 16:45:32 2025 +0100 sai-power: accept power-off requests diff --git a/README.md b/README.md index 09041dd..425a62f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI status](https://warmcat.com/sai/status/sai)](https://warmcat.com/git/sai) -`Sai` (pronouced like 'sigh', "Trial" in Japanese) is a very lightweight +`Sai` (pronouced like 'sigh': "Trial" in Japanese) is a very lightweight lws-based network-aware distributed CI builder and coordinating server. You can run the sai-builder daemon on any number of devices ad-hoc without central registration or inbound internet access, to offer builds for those @@ -86,6 +86,15 @@ the browser, with JWT-authentication for manual job control. For these, if the browser has an authentic JWT signed by the server, it can see and operate these privileged controls. + - sai-power is an optional daemon that runs on a machine on the builder subnet, + when builders identify they are idle, they can suspend themselves, or ask + sai-power to turn the builder off (after they have cleanly shutdown themselves) + at a smartplug. sai-power also monitors sai-server, and when it sees there + is a job ready for the platform offered by the builder, either resume the + builder with WOL, or power the builder up at its smartplug. Since builders in + most cases spend most of their time idle, this enables a very good optimization + of average power down to nearly zero. + ## Build flow and support for embedded ![build flow](READMEs/sai-build-test-flow.png) diff --git a/READMEs/README-sai-power.md b/READMEs/README-sai-power.md new file mode 100644 index 0000000..75b9be2 --- /dev/null +++ b/READMEs/README-sai-power.md @@ -0,0 +1,123 @@ +# sai-power + +## Overview + +sai-power is an optional helper daemon that is designed to run on the same +subnet as the builders, and be up all the time. + +It has two tasks: + + - monitor the situation on sai-server on behalf of that are OFF or suspended, + and turn them on / resume them when there are jobs they could run on the + platforms they offer. + + - help builders who see they are idle and want to shut down, by powering them + OFF via their smartplug a short time after requested to do so. + +## Configuration of sai-power + +sai-power looks for JSON config at /etc/sai/power/conf. It looks like this + +``` +{ + "perms": "sai:nobody", + + "servers": [ + { + "url" : "wss://libwebsockets.org:4444/sai/builder", + + "platforms": [ + { + "name": "rocky9/x86_64-amd/gcc", + "host": "l2", + "power-on": { + "type": "wol", + "mac": "00:e0:4c:68:01:30" + }, + "power-off": { + "type": "suspend" + } + }, + { + "name": "rocky9/aarch64-a72a55-rk3588/gcc", + "host": "b32", + "power-on": { + "type": "tasmota", + "url": "http://10.199.0.240/cm?cmnd=Power%20On" + }, + "power-off": { + "type": "tasmota", + "url": "http://10.199.0.240/cm?cmnd=Power%20Off" + } + }, + { + "name": "ubuntu-noble/riscv64/gcc", + "host": "rv2", + "power-on": { + "type": "tasmota", + "url": "http://10.199.0.104/cm?cmnd=Power%20On" + }, + "power-off": { + "type": "tasmota", + "url": "http://10.199.0.104/cm?cmnd=Power%20Off" + } + } + + ] + } + ] +} +``` + +It describes one or more sai-server that it should keep tabs on, and for each server, +a list of builders and how to manage their power. + +Two types of builder power management is supported + + - "suspend"... the builder will enter suspend mode by itself after it sees that it + has been idle (there are no more jobs) for some time. sai-power will resume it + when jobs that it can handle appear, using WOL. + + - "tasmota"... the builder will ask sai-power to turn off its smartplug in a few + 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. + +## Configuration at the builder + +Builders that participate in sai-power management need to point to sai-power +in ther configuration, for example + +``` + "host": "myhostname", + "sai-power": "http://10.199.0.10:3333", +``` + +The builder's "host" entry is matched to the sai-power configuration to allow +the builder to request sai power to turn itself off. + +## Builder power control manually + +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. + +To simplify that case, you can manually ask sai-power to start up a builder and +for that session only, disable its idle detection. + +``` +# wget -O- http://10.199.0.10:3333/power-on/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 + + - 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. + + diff --git a/etc-sai-EXAMPLE/builder/conf b/etc-sai-EXAMPLE/builder/conf index c6a9179..aae370e 100644 --- a/etc-sai-EXAMPLE/builder/conf +++ b/etc-sai-EXAMPLE/builder/conf @@ -3,6 +3,11 @@ "home": "/home/sai", "host": "bionic-noi", + # if you are using auto-power-on and off, the builder needs + # to point to a sai-power instance that can coordinate power-off + # when the builder decides it is idle + # "sai-power": "http://10.199.0.10:3333", + "platforms": [ { "name": "linux-ubuntu-1804/x86_64-amd/gcc", diff --git a/scripts/builder-conf b/scripts/builder-conf index 09bdc45..bc0fe83 100644 --- a/scripts/builder-conf +++ b/scripts/builder-conf @@ -6,6 +6,11 @@ "home": "/home/sai", "host": "myhostname", + # if you are using auto-power-on and off, the builder needs + # to point to a sai-power instance that can coordinate power-off + # when the builder decides it is idle + # "sai-power": "http://10.199.0.10:3333", + "platforms": [ { "name": "linux-debian-buster/x86_64-amd/gcc", diff --git a/scripts/sai-builder.service b/scripts/sai-builder.service index 96f3c30..a38a645 100644 --- a/scripts/sai-builder.service +++ b/scripts/sai-builder.service @@ -1,5 +1,6 @@ [Unit] Description=Sai Builder +After=network-online.target ntp.service [Service] Environment="LWS_NTP_SERVER=192.168.4.1" diff --git a/src/builder/b-conf.c b/src/builder/b-conf.c index a15b864..546428b 100644 --- a/src/builder/b-conf.c +++ b/src/builder/b-conf.c @@ -285,6 +285,10 @@ saib_conf_global_cb(struct lejp_ctx *ctx, char reason) pp = &a->builder->host; break; + case LEJPM_SAI_POWER: + pp = &a->builder->url_sai_power; + break; + case LEJPM_METRICS_URI: pp = &a->builder->metrics_uri; break; diff --git a/src/builder/b-private.h b/src/builder/b-private.h index 784e676..4e213e8 100644 --- a/src/builder/b-private.h +++ b/src/builder/b-private.h @@ -110,6 +110,7 @@ struct sai_builder { const char *metrics_path; const char *metrics_secret; + const char *url_sai_power; const char *power_off_type; const char *power_off_url; @@ -123,6 +124,8 @@ struct sai_builder { const char *host; /* prepended before hostname */ sai_mirror_instance_t mi; + + char stay; }; struct jpargs { diff --git a/src/builder/b-sai.c b/src/builder/b-sai.c index 8da2cc4..dd5f5dc 100644 --- a/src/builder/b-sai.c +++ b/src/builder/b-sai.c @@ -117,6 +117,19 @@ static const char * const default_ss_policy = "\"metadata\": [" "{\"url\": \"\"}" "]" + "}}," + /* + * Used to connect to sai-power to ask for power-off + */ + "{\"sai_power\": {" + "\"endpoint\":" "\"${url}\"," + "\"protocol\":" "\"h1\"," + "\"http_url\":" "\"\"," /* filled in by url */ + "\"http_method\":" "\"GET\"," + "\"retry\":" "\"default\"," + "\"metadata\": [" + "{\"url\": \"\"}" + "]" "}}" "]}" ; @@ -246,6 +259,64 @@ saib_create_resproxy_listen_uds(struct lws_context *context, return 0; } +/* + * This is used to check with sai-power if we should stay up (due to the power + * being turned on manually) + */ + + +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; +} saib_power_stay_t; + + +static lws_ss_state_return_t +saib_power_stay_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + lwsl_notice("%s: %.*s\n", __func__, (int)len, buf); + + if (len >= 1) { + builder.stay = *buf == '1'; + + lwsl_err("%s: setting %s builder stay: %d\n", __func__, + builder.host, builder.stay); + } + + return 0; +} + +static lws_ss_state_return_t +sai_power_stay_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + saib_power_stay_t *g = (saib_power_stay_t *)userobj; + lws_ss_state_return_t r; + char path[256]; + + lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name(state)); + + switch ((int)state) { + case LWSSSCS_CREATING: + snprintf(path, sizeof(path) - 1, "%s/stay/%s", + builder.url_sai_power, builder.host); + + r = lws_ss_set_metadata(lws_ss_from_user(g), "url", path, strlen(path)); + if (r) + lwsl_err("%s: set_metadata said %d\n", __func__, (int)r); + + return lws_ss_request_tx(lws_ss_from_user(g)); + } + + return LWSSSSRET_OK; +} + +LWS_SS_INFO("sai_power", saib_power_stay_t) + .rx = saib_power_stay_rx, + .state = sai_power_stay_state, +}; + static int app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, int current, int target) @@ -382,6 +453,16 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, } lws_end_foreach_dll(pxx); + /* + * Find out if we were powered up manually (in which case we + * should suppress idle handling) + */ + + if (lws_ss_create(builder.context, 0, &ssi_saib_power_stay_t, + NULL, NULL, NULL, NULL)) { + lwsl_err("%s: failed to create sai-power-stay ss\n", __func__); + break; + } break; } @@ -390,6 +471,50 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, } /* + * This is used to fire http request to sai-power for power-down + */ + + +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; +} saib_power_link_t; + +static lws_ss_state_return_t +sai_power_link_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + saib_power_link_t *g = (saib_power_link_t *)userobj; + lws_ss_state_return_t r; + char path[256]; + + lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name(state)); + + switch ((int)state) { + case LWSSSCS_CREATING: + snprintf(path, sizeof(path) - 1, "%s/power-off/%s", + builder.url_sai_power, + (const char *)lws_ss_opaque_from_user(g)); + + lwsl_notice("%s: setting url metadata %s\n", __func__, path); + + r = lws_ss_set_metadata(lws_ss_from_user(g), "url", path, strlen(path)); + if (r) + lwsl_err("%s: set_metadata said %d\n", __func__, (int)r); + + return lws_ss_request_tx(lws_ss_from_user(g)); + } + + return LWSSSSRET_OK; +} + +LWS_SS_INFO("sai_power", saib_power_link_t) + .state = sai_power_link_state, +}; + + +/* * The grace time is up, ask for the suspend */ @@ -399,20 +524,65 @@ sul_idle_cb(lws_sorted_usec_list_t *sul) ssize_t n; uint8_t te = 1; - lwsl_notice("%s: requesting suspend...\n", __func__); + if (builder.stay) + return; + + if (builder.power_off_type && + !strcmp(builder.power_off_type, "suspend")) { + + lwsl_notice("%s: requesting suspend...\n", __func__); + + n = write(lws_spawn_get_fd_stdxxx(lsp_suspender, 0), &te, 1); + if (n == 1) { + sleep(2); + /* + * There were 0 tasks ongoing for us to suspend, start off + * with the same assumption and set the idle grace time + */ + lws_sul_schedule(builder.context, 0, &builder.sul_idle, + sul_idle_cb, SAI_IDLE_GRACE_US); + lwsl_notice("%s: resuming after suspend\n", __func__); + } else + lwsl_err("%s: failed to request suspend\n", __func__); + + return; + } + + /* + * Do we have a url for sai-power? If not, nothing we can do. + */ + + if (!builder.url_sai_power) + return; + /* + * The plan is ask sai-power to turn us off... + */ + + lwsl_notice("%s: creating sai-power ss...\n", __func__); + + if (lws_ss_create(builder.context, 0, &ssi_saib_power_link_t, + (void *)builder.host, NULL, NULL, NULL)) { + lwsl_err("%s: failed to create sai-power ss\n", __func__); + return; + } + + /* + * Give the http action some time to complete (else we will kill + * everything including the http as soon as we progress on to shutdown) + */ + + sleep(2); + + lwsl_notice("%s: doing shutdown...\n", __func__); + + /* + * In the grace time for actioning the power-off, we should shutdown + * cleanly + */ + + te = 0; n = write(lws_spawn_get_fd_stdxxx(lsp_suspender, 0), &te, 1); - if (n == 1) { - sleep(2); - /* - * There were 0 tasks ongoing for us to suspend, start off - * with the same assumption and set the idle grace time - */ - lws_sul_schedule(builder.context, 0, &builder.sul_idle, - sul_idle_cb, SAI_IDLE_GRACE_US); - lwsl_notice("%s: resuming after suspend\n", __func__); - } else - lwsl_err("%s: failed to request suspend\n", __func__); } static lws_state_notify_link_t * const app_notifier_list[] = { @@ -482,14 +652,21 @@ int main(int argc, const char **argv) if (n <= 0) continue; - if (n == 1 && d == 2) { + if (d == 2) { lwsl_warn("%s: suspend process ending\n", __func__); break; } p = fork(); if (!p) - execl("/usr/bin/systemctl", "/usr/bin/systemctl", "suspend", NULL); + switch(d) { + case 0: + execl("/usr/sbin/shutdown", "/usr/sbin/shutdown", "--halt", "now", NULL); + break; + case 1: + execl("/usr/bin/systemctl", "/usr/bin/systemctl", "suspend", NULL); + break; + } else waitpid(p, &status, 0); } @@ -640,7 +817,7 @@ int main(int argc, const char **argv) goto bail; } - if (!strcmp(builder.power_off_type, "suspend")) { + { struct lws_spawn_piped_info info; char rpath[PATH_MAX]; const char * const ea[] = { rpath, "-s", NULL }; @@ -695,7 +872,7 @@ int main(int argc, const char **argv) bail: - if (!strcmp(builder.power_off_type, "suspend") && lsp_suspender) { + if (lsp_suspender) { uint8_t te = 2; /* @@ -737,8 +914,7 @@ bail: saib_config_destroy(&builder); - if (!strcmp(builder.power_off_type, "suspend")) - lws_sul_cancel(&builder.sul_idle); + lws_sul_cancel(&builder.sul_idle); /* * Clean up after the spawn threads diff --git a/src/builder/b-task.c b/src/builder/b-task.c index aa9c659..14257bc 100644 --- a/src/builder/b-task.c +++ b/src/builder/b-task.c @@ -161,11 +161,11 @@ saib_task_destroy(struct sai_nspawn *ns) * Account that we're not doing this task any more */ - lwsl_notice("%s: ongoing %d -> %d\n", __func__, - ns->sp->ongoing, ns->sp->ongoing - 1); + // lwsl_notice("%s: ongoing %d -> %d\n", __func__, + // ns->sp->ongoing, ns->sp->ongoing - 1); ns->sp->ongoing--; - if (!strcmp(builder.power_off_type, "suspend") && !ns->sp->ongoing) { + if (!ns->sp->ongoing) { int m = 0; /* @@ -183,12 +183,10 @@ saib_task_destroy(struct sai_nspawn *ns) m++; } lws_end_foreach_dll_safe(d, d1); - if (!m) { - lwsl_notice("%s: scheduling suspend grace time\n", __func__); + if (!m) lws_sul_schedule(builder.context, 0, &builder.sul_idle, sul_idle_cb, SAI_IDLE_GRACE_US); - } } /* @@ -718,11 +716,9 @@ saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len) sp->ongoing++; ns->task->told_ongoing = 1; - if (!strcmp(builder.power_off_type, "suspend")) { - /* we're busy, we're not in the mood for suspending */ - lwsl_notice("%s: cancelling suspend grace time\n", __func__); - lws_sul_cancel(&ns->builder->sul_idle); - } + /* we're busy, we're not in the mood for suspending */ + lwsl_notice("%s: cancelling suspend grace time\n", __func__); + lws_sul_cancel(&ns->builder->sul_idle); /* * Let the mirror thread get on with things... diff --git a/src/power/p-comms.c b/src/power/p-comms.c index d551000..4823b43 100644 --- a/src/power/p-comms.c +++ b/src/power/p-comms.c @@ -17,6 +17,8 @@ * 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 part of sai-power that handles communication with sai-server */ #include @@ -76,10 +78,9 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) } if (!strcmp(sp->power_on_type, "tasmota")) { - if (lws_ss_create(lws_ss_cx_from_user(pss), 0, &ssi_saip_smartplug_t, - sp, NULL, NULL, NULL)) { + (void *)sp->power_on_url, NULL, NULL, NULL)) { lwsl_err("%s: failed to create smartplug secure stream\n", __func__); } diff --git a/src/power/p-conf.c b/src/power/p-conf.c index 6411158..4ac0418 100644 --- a/src/power/p-conf.c +++ b/src/power/p-conf.c @@ -33,6 +33,7 @@ static const char * const paths_global[] = { "perms", "servers[].url", "servers[].platforms[].name", + "servers[].platforms[].host", "servers[].platforms[].power-on.type", "servers[].platforms[].power-on.mac", "servers[].platforms[].power-on.url", @@ -48,6 +49,7 @@ enum enum_paths_global { LEJPM_PERMS, LEJPM_SERVERS_URL, LEJPM_SERVERS_PLATFORMS_NAME, + LEJPM_SERVERS_PLATFORMS_HOST, LEJPM_SERVERS_PLATFORMS_POWER_ON_TYPE, LEJPM_SERVERS_PLATFORMS_POWER_ON_MAC, LEJPM_SERVERS_PLATFORMS_POWER_ON_URL, @@ -137,6 +139,10 @@ saip_conf_global_cb(struct lejp_ctx *ctx, char reason) pp = &a->sai_server_plat->name; break; + case LEJPM_SERVERS_PLATFORMS_HOST: + pp = &a->sai_server_plat->host; + break; + case LEJPM_SERVERS_PLATFORMS_POWER_ON_TYPE: pp = &a->sai_server_plat->power_on_type; break; diff --git a/src/power/p-private.h b/src/power/p-private.h index fc3eb62..725ebb6 100644 --- a/src/power/p-private.h +++ b/src/power/p-private.h @@ -60,13 +60,18 @@ struct saip_ws_pss; typedef struct saip_server_plat { struct lws_dll2 list; + lws_sorted_usec_list_t sul_delay_off; + const char *name; + const char *host; const char *power_on_type; const char *power_on_url; const char *power_on_mac; const char *power_off_type; const char *power_off_url; + char stay; + } saip_server_plat_t; typedef struct saip_server { diff --git a/src/power/p-sai.c b/src/power/p-sai.c index 1949a67..d86b6ce 100644 --- a/src/power/p-sai.c +++ b/src/power/p-sai.c @@ -128,8 +128,7 @@ static const char * const default_ss_policy = "{\"local\": {" "\"server\":" "true," "\"port\":" "3333," - "\"protocol\":" "\"ws\"," - "\"ws_subprotocol\":" "\"com-warmcat-sai-power\"," + "\"protocol\":" "\"h1\"," "\"tls\":" "false," "\"metadata\": [" "{\"path\": \"\"}," @@ -145,6 +144,7 @@ static const char * const default_ss_policy = "\"port\":" "80," "\"protocol\":" "\"h1\"," "\"http_url\":" "\"\"," /* filled in by url */ + "\"http_method\":" "\"GET\"," "\"tls\":" "false," "\"retry\":" "\"default\"," "\"metadata\": [" @@ -160,6 +160,24 @@ static const struct lws_protocols *pprotocols[] = { // &protocol_ws_power, 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); + + lwsl_warn("%s: powering off host %s\n", __func__, sp->host); + + if (lws_ss_create(power.context, 0, &ssi_saip_smartplug_t, + (void *)sp->power_off_url, NULL, NULL, NULL)) { + lwsl_err("%s: failed to create smartplug secure stream\n", + __func__); + } +} + +/* + * local-side h1 server for builders to connect to + */ LWS_SS_USER_TYPEDEF char payload[200]; @@ -197,11 +215,32 @@ local_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, return r; } +static saip_server_plat_t * +find_platform(const char *host) +{ + 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 (!strcmp(host, sp->host)) + return sp; + + } lws_end_foreach_dll(px1); + } lws_end_foreach_dll(px); + + return NULL; +} + 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; + saip_server_plat_t *sp; + char *path = NULL; + size_t len; lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name(state)); @@ -211,7 +250,17 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state, case LWSSSCS_SERVER_TXN: - lwsl_ss_user(lws_ss_from_user(g), "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", (int)len, path); + + /* + * 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 @@ -220,14 +269,91 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state, */ lws_ss_server_ack(lws_ss_from_user(g), 0); - if (lws_ss_set_metadata(lws_ss_from_user(g), "mime", "text/html", 9)) - return LWSSSSRET_DISCONNECT_ME; + g->pos = 0; + + if (!strncmp(path, "/stay/", 6)) { + sp = find_platform(&path[6]); + + 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", &path[6]); + goto bail; + } + + if (!strncmp(path, "/power-on/", 10)) { + sp = find_platform(&path[10]); + if (!sp) { + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Unable to find host %s", &path[11]); + 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), + "Resumed %s with stay", &path[10]); + 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", &path[10]); + goto bail; + } + if (lws_ss_create(power.context, 0, + &ssi_saip_smartplug_t, + (void *)sp->power_on_url, + NULL, NULL, NULL)) { + 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); + + 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 (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; + } + + /* + * Let's have a look at the platform + */ - g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), - "Hello World: %lu", - (unsigned long)lws_now_usecs()); - g->pos = 0; + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Unable to find host %s", &path[11]); + sp = find_platform(&path[11]); + if (sp) { + /* + * 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), + "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); } @@ -419,7 +545,7 @@ int main(int argc, const char **argv) */ info.pprotocols = pprotocols; - info.uid = 883; + //info.uid = 883; info.pt_serv_buf_size = 32 * 1024; info.rlimit_nofile = 20000; info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; diff --git a/src/power/p-smartplug.c b/src/power/p-smartplug.c index f97a113..c053f45 100644 --- a/src/power/p-smartplug.c +++ b/src/power/p-smartplug.c @@ -36,7 +36,7 @@ saip_spc_state(void *userobj, void *sh, lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) { saip_smartplug_t *pss = (saip_smartplug_t *)userobj; - saip_server_plat_t *sp = (saip_server_plat_t *)lws_ss_opaque_from_user(pss); + 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); @@ -45,11 +45,10 @@ saip_spc_state(void *userobj, void *sh, lws_ss_constate_t state, case LWSSSCS_CREATING: - lwsl_notice("%s: binding ss to %s\n", __func__, sp->power_on_url); + lwsl_notice("%s: binding ss to %s\n", __func__, op_url); if (lws_ss_set_metadata(lws_ss_from_user(pss), - "url", sp->power_on_url, - strlen(sp->power_on_url))) + "url", op_url, strlen(op_url))) lwsl_warn("%s: unable to set metadata\n", __func__); return lws_ss_client_connect(lws_ss_from_user(pss)); diff --git a/src/server/s-task.c b/src/server/s-task.c index cf20cbb..be34783 100644 --- a/src/server/s-task.c +++ b/src/server/s-task.c @@ -393,8 +393,6 @@ sais_platforms_with_tasks_pending(struct vhd *vhd) lws_dll2_owner_t o; int n; - lwsl_err("%s: ++++++++ entry\n", __func__); - /* lose everything we were holding on to from last time */ sais_destroy_pending_plat_list(vhd); @@ -402,20 +400,16 @@ 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 != 3 and state != 4 and state != 5) and created < %llu", - (unsigned long long)(lws_now_secs() - 10)); + lws_snprintf(pf, sizeof(pf)," and (state == 0 or state == 1 or state == 2)"); n = lws_struct_sq3_deserialize(vhd->server.pdb, pf, "created desc ", - lsm_schema_sq3_map_event, &o, &ac, 0, 10); + lsm_schema_sq3_map_event, &o, &ac, 0, 20); if (n < 0 || !o.head) { /* error, or there are no events that aren't complete */ goto bail; } - - lwsl_err("%s: starting scan\n", __func__); - /* * Iterate through the events looking at his event-specific database * for platforms that have pending tasks... @@ -431,7 +425,7 @@ sais_platforms_with_tasks_pending(struct vhd *vhd) if (sqlite3_prepare_v2(pdb, "select distinct platform " "from tasks where " - "(state != 3 and state != 4 and state != 5)", -1, &sm, + "(state == 0)", -1, &sm, NULL) != SQLITE_OK) { lwsl_err("%s: Unable to %s\n", __func__, sqlite3_errmsg(pdb)); @@ -441,11 +435,9 @@ sais_platforms_with_tasks_pending(struct vhd *vhd) do { n = sqlite3_step(sm); - if (n == SQLITE_ROW) { - lwsl_err("%s: scanned plat %s\n", __func__, (const char *)sqlite3_column_text(sm, 0)); + if (n == SQLITE_ROW) sais_find_or_add_pending_plat(vhd, (const char *)sqlite3_column_text(sm, 0)); - } } while (n == SQLITE_ROW); sqlite3_reset(sm);