diff --git a/READMEs/README-sai-power.md b/READMEs/README-sai-power.md
index d2b0008..604451f 100644
--- a/READMEs/README-sai-power.md
+++ b/READMEs/README-sai-power.md
@@ -122,15 +122,22 @@ or suspend at any time.
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
+$ 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
+$ wget -O- http://10.199.0.10:3333/power-off/hostname
```
+You can get a list of controllable hostnames from sai-power, this
+info is coming from the JSON conf
+
+```
+$ wget -O- http://10.199.0.10:3333/ 2>/dev/null
+l2,w11-l2,b32,rpi5,rv2,ubuntu_rpi4,rpi3B_netbsdBE
+
Asking sai-power to do it has some advantages:
- it will work the same no matter the details of that particular builder's
diff --git a/assets/sai.css b/assets/sai.css
index 5ea141b..c44e92f 100644
--- a/assets/sai.css
+++ b/assets/sai.css
@@ -696,6 +696,52 @@ div.bdr {
opacity: 0.35;
}
+/* Style for the powering-up state */
+.powering-up {
+ position: relative; /* Needed for the ::after pseudo-element */
+}
+
+/* The spinner pseudo-element */
+.powering-up::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 24px; /* Adjust size as needed */
+ height: 24px;
+ margin-top: -12px; /* Half of height */
+ margin-left: -12px; /* Half of width */
+ border: 3px solid rgba(0, 0, 0, 0.2);
+ border-top-color: #007bff; /* Spinner color */
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ z-index: 10; /* Ensure it's on top */
+}
+
+@keyframes spin { to { transform: rotate(360deg); } }
+
+/* Style for the powering-down state */
+.powering-down {
+ position: relative; /* Needed for the ::after pseudo-element */
+}
+
+/* The spinner pseudo-element */
+.powering-down::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 24px; /* Adjust size as needed */
+ height: 24px;
+ margin-top: -12px; /* Half of height */
+ margin-left: -12px; /* Half of width */
+ border: 3px solid rgba(0, 0, 0, 0.2);
+ border-top-color: #ff4136; /* Spinner color */
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ z-index: 10; /* Ensure it's on top */
+}
+
img.branch {
display: inline-block;
diff --git a/assets/sai.js b/assets/sai.js
index 4882e2b..c795775 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -871,6 +871,11 @@ function createBuilderDiv(plat) {
platDiv.className = "ibuil bdr";
if (!plat.online)
platDiv.className += " offline";
+ // Add a new class if the builder is powering up
+ if (plat.powering_up)
+ platDiv.className += " powering-up";
+ if (plat.powering_down)
+ platDiv.className += " powering-down";
platDiv.id = "binfo-" + plat.name;
platDiv.title = plat.platform + "@" + plat.name.split('.')[0] + " / " + plat.peer_ip;
@@ -1130,6 +1135,8 @@ function ws_open_sai()
nestedPlatforms.forEach(plat => {
const div = existingDivs.get(plat.name) || createBuilderDiv(plat);
div.classList.toggle('offline', !plat.online);
+ div.classList.toggle('powering-up', !!plat.powering_up);
+ div.classList.toggle('powering-down', !!plat.powering_down);
groupDiv.appendChild(div);
});
tdContainer.appendChild(groupDiv);
@@ -1138,6 +1145,8 @@ function ws_open_sai()
mainPlatforms.forEach(plat => {
const div = existingDivs.get(plat.name) || createBuilderDiv(plat);
div.classList.toggle('offline', !plat.online);
+ div.classList.toggle('powering-up', !!plat.powering_up);
+ div.classList.toggle('powering-down', !!plat.powering_down);
tdContainer.appendChild(div);
});
}
diff --git a/src/common/include/private.h b/src/common/include/private.h
index 0720c6b..6d0e62d 100644
--- a/src/common/include/private.h
+++ b/src/common/include/private.h
@@ -404,6 +404,8 @@ typedef struct sai_plat {
lws_dll2_owner_t loads;
int online; /* 1 = connected, 0 = offline */
uint64_t last_seen; /* unix time */
+ int powering_up; /* 1 = sai-power is booting it */
+ int powering_down;
int instances;
int ongoing;
@@ -438,6 +440,14 @@ typedef struct sai_browse_rx_taskinfo {
uint8_t logs;
} sai_browse_rx_taskinfo_t;
+/* sai-power -> sai-server, tells it that a platform is being powered up */
+typedef struct sai_power_state {
+ lws_dll2_t list; /* for parser */
+ char host[256];
+ int powering_up;
+ int powering_down;
+} sai_power_state_t;
+
extern const lws_struct_map_t
lsm_schema_json_map_task[],
lsm_schema_sq3_map_task[],
@@ -458,10 +468,11 @@ extern const lws_struct_map_t
lsm_schema_json_map_can[1],
lsm_schema_json_map_task[1],
lsm_schema_json_map_event[1],
- lsm_resource[4]
+ lsm_resource[4],
+ lsm_power_state[3]
;
extern const lws_struct_map_t lsm_plat[6];
-extern const lws_struct_map_t lsm_plat_for_json[8];
+extern const lws_struct_map_t lsm_plat_for_json[10];
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 e612330..4899184 100644
--- a/src/common/struct-metadata.c
+++ b/src/common/struct-metadata.c
@@ -58,6 +58,8 @@ const lws_struct_map_t lsm_plat_for_json[] = {
LSM_STRING_PTR(sai_plat_t, platform,"platform"),
LSM_SIGNED(sai_plat_t, online, "online"), // MUST be present
LSM_UNSIGNED(sai_plat_t, last_seen, "last_seen"),
+ LSM_SIGNED(sai_plat_t, powering_up, "powering_up"),
+ LSM_SIGNED(sai_plat_t, powering_down, "powering_down"),
LSM_CARRAY(sai_plat_t, peer_ip, "peer_ip"),
};
@@ -237,6 +239,13 @@ const lws_struct_map_t lsm_schema_json_map_artifact[] = {
"com-warmcat-sai-artifact"),
};
+const lws_struct_map_t lsm_power_state[] = {
+ LSM_CARRAY(sai_power_state_t, host, "host"),
+ LSM_SIGNED(sai_power_state_t, powering_up, "powering_up"),
+ 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"),
};
diff --git a/src/power/p-comms.c b/src/power/p-comms.c
index 942bfb6..566ee30 100644
--- a/src/power/p-comms.c
+++ b/src/power/p-comms.c
@@ -29,12 +29,60 @@
#include "../common/struct-metadata.c"
+/* Map for the "powering up" message we send to the server */
+static const lws_struct_map_t lsm_schema_power_state[] = {
+ LSM_SCHEMA(sai_power_state_t, NULL, lsm_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)
+{
+ saip_server_t *sps;
+ sai_power_state_t *ps;
+
+ /* 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 */
+ ps = malloc(sizeof(*ps));
+ if (!ps)
+ return;
+
+ memset(ps, 0, sizeof(*ps));
+ lws_strncpy(ps->host, plat_name, sizeof(ps->host));
+ ps->powering_up = (char)up;
+ ps->powering_down = (char)down;
+
+ /* 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(&ps->list, &pss->ps_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);
+}
+
static lws_ss_state_return_t
saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
@@ -97,6 +145,8 @@ 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
@@ -136,6 +186,35 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
return 0;
}
+static lws_ss_state_return_t
+saip_m_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
+ int *flags)
+{
+ 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;
+
+ /* 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 && 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);
+ }
+ lws_struct_json_serialize_destroy(&js);
+
+ /* If there are more to send, request another writable callback */
+ if (pss->ps_owner.head)
+ if (lws_ss_request_tx(lws_ss_from_user(pss)))
+ lwsl_ss_warn(lws_ss_from_user(pss), "tx request failed");
+
+ return LWSSSSRET_OK;
+}
static int
cleanup_on_ss_destroy(struct lws_dll2 *d, void *user)
@@ -218,6 +297,13 @@ saip_m_state(void *userobj, void *sh, lws_ss_constate_t state,
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);
+
+ lws_dll2_remove(&ps->list);
+ free(ps);
+ } lws_end_foreach_dll_safe(d, d1);
+
/*
* clean up any ongoing spawns related to this connection
*/
@@ -245,4 +331,5 @@ saip_m_state(void *userobj, void *sh, lws_ss_constate_t state,
LWS_SS_INFO("sai_power", saip_server_link_t)
.rx = saip_m_rx,
.state = saip_m_state,
+ .tx = saip_m_tx, /* We need a TX handler to send messages */
};
diff --git a/src/power/p-private.h b/src/power/p-private.h
index fe08d82..5b75277 100644
--- a/src/power/p-private.h
+++ b/src/power/p-private.h
@@ -137,5 +137,6 @@ int
saip_config_global(struct sai_power *power, const char *d);
extern int saip_config(struct sai_power *power, const char *d);
extern void saip_config_destroy(struct sai_power *power);
-
+extern void
+saip_notify_server_power_state(const char *plat_name, int up, int down);
diff --git a/src/power/p-sai.c b/src/power/p-sai.c
index b9bda4a..d340775 100644
--- a/src/power/p-sai.c
+++ b/src/power/p-sai.c
@@ -195,6 +195,7 @@ saip_sul_action_power_off(struct lws_sorted_usec_list *sul)
}
lwsl_warn("%s: powering off host %s\n", __func__, sp->host);
+ saip_notify_server_power_state(sp->host, 0, 1);
if (lws_ss_create(power.context, 0, &ssi_saip_smartplug_t,
(void *)sp->power_off_url, NULL, NULL, NULL)) {
@@ -281,7 +282,7 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
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", (int)len, path);
+ 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"
@@ -301,6 +302,28 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
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));
@@ -324,6 +347,7 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
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))
@@ -347,9 +371,10 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
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);
diff --git a/src/server/s-comms.c b/src/server/s-comms.c
index 85aed2e..dae4680 100644
--- a/src/server/s-comms.c
+++ b/src/server/s-comms.c
@@ -763,30 +763,6 @@ callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
sais_builder_disconnected(vhd, wsi);
- /*
- * Find any builder-tracking objects that were using this departing
- * connection. Mark them as offline in the database.
- * Also remove from the in-memory list of active builders.
- */
- 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);
- char q[256];
-
- if (cb->wsi == wsi) {
- lwsl_warn("%s: Builder '%s' disconnected. Removing from live list.\n",
- __func__, cb->name);
- lws_snprintf(q, sizeof(q), "UPDATE builders SET online=0 WHERE name='%s'", cb->name);
- sai_sqlite3_statement(vhd->server.pdb, q, "set builder offline");
-
- /* remove from active in-memory list */
- lws_dll2_remove(&cb->sai_plat_list);
- free(cb);
- }
-
- } lws_end_foreach_dll_safe(p, p1);
-
sais_resource_wellknown_remove_pss(&pss->vhd->server, pss);
if (pss->blob_artifact) {
@@ -809,12 +785,51 @@ callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
case LWS_CALLBACK_RECEIVE:
- if (!pss->vhd)
- pss->vhd = vhd;
+ /*
+ * A ws client sent us something... it could be a builder or
+ * it could be sai-power. We can tell which by the `is_power`
+ * flag we set in the pss during ESTABLISHED.
+ */
+
+ if (pss->is_power) {
+ struct lejp_ctx ctx;
+ lws_struct_args_t a;
+ sai_power_state_t *ps;
+ const lws_struct_map_t lsm_schema_power_state[] = {
+ LSM_SCHEMA(sai_power_state_t, NULL, lsm_power_state,
+ "com.warmcat.sai.powerstate"),
+ };
+
+ /* 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.ac_block_size = 512;
+
+ lws_struct_json_init_parse(&ctx, NULL, &a);
+ if (lejp_parse(&ctx, (uint8_t *)in, (int)len) < 0 || !a.dest) {
+ lwsl_warn("Failed to parse msg from sai-power\n");
+ lwsac_free(&a.ac);
+ 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);
+ }
+
+ lwsac_free(&a.ac);
+ break; // Exit case
+ }
- lwsl_info("SWT_BUILDER RX: %d\n", (int)len);
/*
- * Builder sent us something on websockets
+ * This is a message from a builder
*/
pss->wsi = wsi;
if (sais_ws_json_rx_builder(vhd, pss, in, len))
diff --git a/src/server/s-private.h b/src/server/s-private.h
index 2c72ba7..4981a74 100644
--- a/src/server/s-private.h
+++ b/src/server/s-private.h
@@ -31,6 +31,7 @@ struct sai_plat;
typedef struct sai_platm {
lws_dll2_owner_t builder_owner;
lws_dll2_owner_t subs_owner;
+ lws_dll2_owner_t power_state_owner; /* sai_power_state_t */
/* the list of well-known, configured resources */
lws_dll2_owner_t resource_wellknown_owner; /* sai_resource_wellknown_t */
@@ -50,6 +51,11 @@ typedef struct sai_platform {
/* build and name over-allocated here */
} sai_platform_t;
+typedef struct sai_powering_up_plat {
+ lws_dll2_t list;
+ char name[256];
+} sai_powering_up_plat_t;
+
typedef enum {
SAIN_ACTION_INVALID,
SAIN_ACTION_REPO_UPDATED
@@ -185,6 +191,7 @@ struct vhd {
struct lws_dll2_owner builders;
struct lws_dll2_owner sai_powers;
struct lws_dll2_owner pending_plats;
+ lws_dll2_owner_t powering_up_list; /* sai_powering_up_plat_t */
struct lwsac *ac_plats;
@@ -318,10 +325,14 @@ sais_platforms_with_tasks_pending(struct vhd *vhd);
sai_plat_t *
sais_builder_from_uuid(struct vhd *vhd, const char *hostname, const char *_file, int _line);
+sai_plat_t *
+sais_builder_from_host(struct vhd *vhd, const char *host);
void
sais_builder_disconnected(struct vhd *vhd, struct lws *wsi);
+void
+sais_set_builder_power_state(struct vhd *vhd, const char *name, int up, int down);
void
sais_mark_all_builders_offline(struct vhd *vhd);
diff --git a/src/server/s-websrv.c b/src/server/s-websrv.c
index b51f541..f904009 100644
--- a/src/server/s-websrv.c
+++ b/src/server/s-websrv.c
@@ -167,6 +167,7 @@ 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),
@@ -210,6 +211,21 @@ sais_list_builders(struct vhd *vhd)
builder_from_db->ongoing = 0;
}
+ builder_from_db->powering_up = 0;
+ builder_from_db->powering_down = 0;
+
+ lws_start_foreach_dll(struct lws_dll2 *, p, vhd->server.power_state_owner.head) {
+ sai_power_state_t *ps = lws_container_of(p, sai_power_state_t, list);
+ size_t host_len = strlen(ps->host);
+
+ if (!strncmp(builder_from_db->name, ps->host, host_len) &&
+ builder_from_db->name[host_len] == '.') {
+ builder_from_db->powering_up = ps->powering_up;
+ builder_from_db->powering_down = ps->powering_down;
+ break;
+ }
+ } lws_end_foreach_dll(p);
+
js = lws_struct_json_serialize_create(
lsm_schema_map_plat_simple,
LWS_ARRAY_SIZE(lsm_schema_map_plat_simple),
diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c
index 69b29df..d9f3cea 100644
--- a/src/server/s-ws-builder.c
+++ b/src/server/s-ws-builder.c
@@ -258,6 +258,70 @@ sais_builder_from_uuid(struct vhd *vhd, const char *hostname, const char *_file,
return NULL;
}
+sai_plat_t *
+sais_builder_from_host(struct vhd *vhd, const char *host)
+{
+ lws_start_foreach_dll(struct lws_dll2 *, p,
+ vhd->server.builder_owner.head) {
+ sai_plat_t *cb = lws_container_of(p, sai_plat_t,
+ sai_plat_list);
+ size_t host_len = strlen(host);
+
+ if (!strncmp(cb->name, host, host_len) &&
+ cb->name[host_len] == '.')
+ return cb;
+
+ } lws_end_foreach_dll(p);
+
+ return NULL;
+}
+
+void
+sais_set_builder_power_state(struct vhd *vhd, const char *name, int up, int down)
+{
+ sai_power_state_t *ps = NULL;
+ sai_plat_t *live_builder;
+
+ if (up) {
+ live_builder = sais_builder_from_host(vhd, name);
+ if (live_builder)
+ return;
+ }
+
+ if (down) {
+ live_builder = sais_builder_from_host(vhd, name);
+ if (!live_builder)
+ return;
+ }
+
+ lws_start_foreach_dll(struct lws_dll2 *, p, vhd->server.power_state_owner.head) {
+ ps = lws_container_of(p, sai_power_state_t, list);
+ if (!strcmp(ps->host, name))
+ break;
+ ps = NULL;
+ } lws_end_foreach_dll(p);
+
+ if (!ps && (up || down)) {
+ ps = malloc(sizeof(*ps));
+ if (!ps)
+ return;
+ memset(ps, 0, sizeof(*ps));
+ lws_strncpy(ps->host, name, sizeof(ps->host));
+ lws_dll2_add_tail(&ps->list, &vhd->server.power_state_owner);
+ }
+
+ if (ps) {
+ ps->powering_up = (char)up;
+ ps->powering_down = (char)down;
+ if (!ps->powering_up && !ps->powering_down) {
+ lws_dll2_remove(&ps->list);
+ free(ps);
+ }
+ }
+
+ sais_list_builders(vhd);
+}
+
/*
* Called from the builder protocol LWS_CALLBACK_CLOSED handler
*/
@@ -276,9 +340,28 @@ sais_builder_disconnected(struct vhd *vhd, struct lws *wsi)
cb = lws_container_of(p, sai_plat_t, sai_plat_list);
if (cb->wsi == wsi) {
+ char q[256];
+
lwsl_notice("%s: Builder '%s' disconnected\n", __func__,
cb->name);
+ lws_snprintf(q, sizeof(q), "UPDATE builders SET online=0 WHERE name='%s'", cb->name);
+ sai_sqlite3_statement(vhd->server.pdb, q, "set builder offline");
+
+ const char *dot = strchr(cb->name, '.');
+ if (dot) {
+ char host[128];
+ lws_strnncpy(host, cb->name, dot - cb->name, sizeof(host));
+ lws_start_foreach_dll_safe(struct lws_dll2 *, p2, p3, vhd->server.power_state_owner.head) {
+ sai_power_state_t *ps = lws_container_of(p2, sai_power_state_t, list);
+ if (!strcmp(ps->host, host)) {
+ lws_dll2_remove(&ps->list);
+ free(ps);
+ break;
+ }
+ } lws_end_foreach_dll_safe(p2, p3);
+ }
+
lws_dll2_remove(&cb->sai_plat_list);
free(cb);
}
@@ -433,6 +516,13 @@ handle:
lws_dll2_add_tail(&live_cb->sai_plat_list, &vhd->server.builder_owner);
}
}
+
+ const char *dot = strchr(build->name, '.');
+ if (dot) {
+ char host[128];
+ lws_strnncpy(host, build->name, dot - build->name, sizeof(host));
+ sais_set_builder_power_state(vhd, host, 0, 0);
+ }
} lws_end_foreach_dll(pb);
/* The lwsac from the parsed message is now completely disposable */