diff --git a/README.md b/README.md
index 09041dd..425a62f 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](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

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 <libwebsockets.h>
@@ -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);