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
 
root / scripts / sai-power.service
Author[]Andy Green <andy@warmcat.com> 2026-06-02 05:39 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-06-05 13:28 UTC
Treeef7c8a0f844991f91c50c059198534fbea91dc52   Raw Patch
 
sai-virt: add libvirt apis
sai-virt: add libvirt apis
diff --git a/CMakeLists.txt b/CMakeLists.txt index ef531f1..2e0d0f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ project(sai C) find_package(libwebsockets CONFIG REQUIRED) list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) include(CheckCSourceCompiles) +include(CheckSymbolExists) include(LwsCheckRequirements) if (CMAKE_HOST_SYSTEM_NAME MATCHES "NetBSD" OR CMAKE_HOST_SYSTEM_NAME MATCHES "SunOS") @@ -114,6 +115,11 @@ if (APPLE) link_directories("/usr/local/lib") endif() +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(CMAKE_C_FLAGS "-I/usr/local/include ${CMAKE_C_FLAGS}") + link_directories("/usr/local/lib") +endif() + if (LWS_OPENSSL_INCLUDE_DIRS) set(CMAKE_C_FLAGS "-I\"${LWS_OPENSSL_INCLUDE_DIRS}\" ${CMAKE_C_FLAGS}") endif() @@ -132,6 +138,15 @@ CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(LWS_HAVE_LIBCAP)\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_LIBCAP) +if (APPLE) + set(CMAKE_REQUIRED_LIBRARIES "-framework IOKit") + check_symbol_exists(kIOMainPortDefault "mach/mach.h;IOKit/IOKitLib.h" HAVE_KIOMAINPORTDEFAULT) + set(CMAKE_REQUIRED_LIBRARIES "") + if (HAVE_KIOMAINPORTDEFAULT) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_KIOMAINPORTDEFAULT") + endif() +endif() + #set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -v") set (CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${LIBWEBSOCKETS_INCLUDE_DIRS}") diff --git a/assets/linux-fedora44.svg b/assets/linux-fedora44.svg new file mode 100644 index 0000000..bfd8b52 --- /dev/null +++ b/assets/linux-fedora44.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><defs><style>@font-face{font-family:"PT Sans Narrow";src:url(data:application/font-woff;charset=utf-8;base64,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) format("woff"); font-weight:normal;font-style:normal;}</style></defs><circle cx="1.943" cy="1.943" r="1.943" fill="#f9f9f9" fill-opacity=".12"/><path d="M.296 3.233C.334 2.69.23 2.125.362 1.598.492.966 1.027.458 1.664.353c.86-.22 1.744.512 1.877 1.35.06.583-.143 1.223-.662 1.544-.434.373-1.01.325-1.542.32-.363-.03-.907.152-1.04-.333z" fill="#51a2da" paint-order="markers fill stroke"/><path d="M1.814 1.75l.007.27-.33-.013c-.174.064-.357.247-.34.42.002.05-.013.104.025.144.056.08.128.208.222.214.12.035.236.05.342-.007.118-.095.195-.204.23-.34V1.334c.03-.192.127-.407.398-.427.077 0 .157-.016.23.015.12.03.2.12.264.222.042.093.093.19.082.295.006.114-.086.12-.166.147-.034-.032-.093-.046-.1-.098.004-.117-.023-.21-.097-.295-.123-.048-.19-.02-.312.027-.02.075-.064.146-.054.226l.004.293.225.001c.093.098.066.3-.067.273h-.16l.007.566c-.02.052-.026.11-.052.16-.185.235-.406.334-.673.358-.293-.06-.507-.243-.604-.498l-.038-.26a.79.79 0 0 1 .491-.566c.077-.015.153-.034.23-.026l.236.005z" fill="#fff" paint-order="markers fill stroke"/><text xml:space="preserve" x="-626.964" y="-109.218" font-size=".987" font-family="PT Sans Narrow" letter-spacing="0" word-spacing="0" writing-mode="lr-tb" direction="ltr" dominant-baseline="auto" baseline-shift="baseline"><tspan x="1.789" y="3.178" font-weight="600" font-family="A-OTF Shin Go Pro">32</tspan></text></svg> diff --git a/assets/sai.css b/assets/sai.css index 7075275..b060fba 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -647,7 +647,7 @@ div.ibuil { display: inline-block; vertical-align:top; text-align:left; - line-height: 50%; + line-height: normal; float:left; margin: 0.1em; font-weight: normal; @@ -659,7 +659,7 @@ div.ibuil { user-select: none; -webkit-user-select: none; cursor: pointer; - width: 80px; + width: 80px; } div.ibuil table { @@ -1303,6 +1303,9 @@ canvas.power-graph { .plat-peer-ip { font-size: 0.8em; color: #555; + display: block; + word-break: break-all; + margin-top: 2px; } .watchers-row { display: flex; diff --git a/assets/sai.js b/assets/sai.js index 8dac6a9..f057d2e 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -1631,7 +1631,7 @@ function createBuilderDiv(plat) { `<div class="res-bar"><div class="res-bar-inner res-bar-disk w-0"></div></div>` + `</div>`; if (authd && auth_is_admin && plat.peer_ip) - innerHTML += `<span class="plat-peer-ip">${hsanitize(plat.peer_ip)}</span>`; + innerHTML += `<div class="plat-peer-ip">${hsanitize(plat.peer_ip)}</div>`; innerHTML += `</td></tr></tbody></table>`; platDiv.innerHTML = innerHTML; diff --git a/src/builder/CMakeLists.txt b/src/builder/CMakeLists.txt index f27d026..50f43b5 100644 --- a/src/builder/CMakeLists.txt +++ b/src/builder/CMakeLists.txt @@ -46,7 +46,7 @@ add_dependencies(${SUB} sai_git_hash) if (SAI_BUILDER) if (APPLE) - target_link_libraries(sai-builder "-framework IOKit") + target_link_libraries(sai-builder "-framework IOKit" "-framework CoreFoundation") endif() endif() @@ -80,7 +80,7 @@ add_dependencies(${SUB} sai_git_hash) endif() if (MSVC OR WIN32) - target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib advapi32.lib) endif() if (MSVC OR WIN32) diff --git a/src/builder/b-private.h b/src/builder/b-private.h index 39d31ce..98f5375 100644 --- a/src/builder/b-private.h +++ b/src/builder/b-private.h @@ -77,7 +77,7 @@ struct saib_opaque_spawn { }; #define SAI_LOAD_REPORT_US (1 * LWS_US_PER_SEC) -#define SAI_IDLE_GRACE_US (30 * LWS_US_PER_SEC) +#define SAI_IDLE_GRACE_US (builder.one_shot_active ? LWS_US_PER_SEC : (30 * LWS_US_PER_SEC)) #define SAI_STAY_POLL_US (20 * LWS_US_PER_SEC) #define SAI_CLEANUP_JOBS_INTERVAL_US (60 * 60 * LWS_US_PER_SEC) #define SAI_CLEANUP_JOB_DIR_MIN_AGE_SECS (24ull * 3600u) @@ -162,6 +162,8 @@ struct sai_builder { char event_affinity[65]; char event_affinity_active; + char one_shot_task_uuid[65]; + char one_shot_active; /* resource management */ diff --git a/src/builder/b-sai.c b/src/builder/b-sai.c index dff1381..03487a8 100644 --- a/src/builder/b-sai.c +++ b/src/builder/b-sai.c @@ -30,6 +30,7 @@ #include <signal.h> #include <limits.h> #include <stdlib.h> +#include <fcntl.h> #include <sys/types.h> #if !defined(WIN32) @@ -44,6 +45,8 @@ #if defined(__APPLE__) #include <sys/stat.h> /* for mkdir() */ #include <mach-o/dyld.h> +#include <IOKit/IOKitLib.h> +#include <CoreFoundation/CoreFoundation.h> #endif #if defined(WIN32) @@ -573,6 +576,11 @@ saib_app_run(int argc, const char **argv) lwsl_notice("%s: event affinity mode enabled (ephemeral VM)\n", __func__); } + if (lws_cmdline_option(argc, argv, "-O")) { + builder.one_shot_active = 1; + lwsl_notice("%s: one-shot mode enabled (single task ephemeral VM)\n", __func__); + } + #if defined(__NetBSD__) || defined(__OpenBSD__) if (lws_cmdline_option(argc, argv, "-D")) { if (lws_daemonize("/var/run/sai_builder.pid")) @@ -626,6 +634,95 @@ saib_app_run(int argc, const char **argv) return 1; } +#if defined(__linux__) + /* + * If running inside a VM spawned by sai-virt, we might have been + * passed a dynamic prefix via QEMU fw_cfg. + */ + { + int fd = open("/sys/firmware/qemu_fw_cfg/by_name/opt/sai_builder_id/raw", O_RDONLY); + if (fd >= 0) { + char fw_id[128]; + ssize_t fw_n = read(fd, fw_id, sizeof(fw_id) - 1); + if (fw_n > 0) { + fw_id[fw_n] = '\0'; + /* Remove any trailing newline */ + while (fw_n > 0 && (fw_id[fw_n - 1] == '\n' || fw_id[fw_n - 1] == '\r')) + fw_id[--fw_n] = '\0'; + + if (fw_n > 0) { + char compound[256]; + lws_snprintf(compound, sizeof(compound), "%s-%s", fw_id, builder.host ? builder.host : ""); + + char *new_host = lwsac_use(&builder.conf_head, strlen(compound) + 1, 512); + if (new_host) { + strcpy(new_host, compound); + builder.host = new_host; + lwsl_notice("%s: Applied dynamic fw_cfg builder identity: %s\n", __func__, builder.host); + } + } + } + close(fd); + } + } +#elif defined(WIN32) + { + HKEY hKey; + char fw_id[128]; + DWORD dwType = REG_SZ; + DWORD dwSize = sizeof(fw_id); + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\BIOS", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + if (RegQueryValueExA(hKey, "SystemSerialNumber", NULL, &dwType, (LPBYTE)fw_id, &dwSize) == ERROR_SUCCESS) { + if (!strncmp(fw_id, "sai_builder_id:", 15)) { + char *id = fw_id + 15; + char compound[256]; + lws_snprintf(compound, sizeof(compound), "%s-%s", id, builder.host ? builder.host : ""); + + char *new_host = lwsac_use(&builder.conf_head, strlen(compound) + 1, 512); + if (new_host) { + strcpy(new_host, compound); + builder.host = new_host; + lwsl_notice("%s: Applied dynamic SMBIOS builder identity: %s\n", __func__, builder.host); + } + } + } + RegCloseKey(hKey); + } + } +#elif defined(__APPLE__) + { +#if defined(HAVE_KIOMAINPORTDEFAULT) + io_service_t platformExpert = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice")); +#else + io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); +#endif + if (platformExpert) { + CFTypeRef serialNumberAsCFString = IORegistryEntryCreateCFProperty(platformExpert, CFSTR("IOPlatformSerialNumber"), kCFAllocatorDefault, 0); + if (serialNumberAsCFString) { + if (CFGetTypeID(serialNumberAsCFString) == CFStringGetTypeID()) { + char fw_id[128]; + if (CFStringGetCString(serialNumberAsCFString, fw_id, sizeof(fw_id), kCFStringEncodingUTF8)) { + if (!strncmp(fw_id, "sai_builder_id:", 15)) { + char *id = fw_id + 15; + char compound[256]; + lws_snprintf(compound, sizeof(compound), "%s-%s", id, builder.host ? builder.host : ""); + + char *new_host = lwsac_use(&builder.conf_head, strlen(compound) + 1, 512); + if (new_host) { + strcpy(new_host, compound); + builder.host = new_host; + lwsl_notice("%s: Applied dynamic SMBIOS builder identity: %s\n", __func__, builder.host); + } + } + } + } + CFRelease(serialNumberAsCFString); + } + IOObjectRelease(platformExpert); + } + } +#endif /* * We need to sample the true uid / gid we should use inside diff --git a/src/builder/b-task.c b/src/builder/b-task.c index 9ec00f1..c4e31d2 100644 --- a/src/builder/b-task.c +++ b/src/builder/b-task.c @@ -177,6 +177,19 @@ saib_can_accept_task(sai_task_t *task, sai_plat_t *sp) unsigned int executing = 0; + if (builder.one_shot_active) { + if (!builder.one_shot_task_uuid[0]) { + lws_strncpy(builder.one_shot_task_uuid, task->uuid, + sizeof(builder.one_shot_task_uuid)); + lwsl_notice("%s: locked one-shot affinity to task %s\n", + __func__, builder.one_shot_task_uuid); + } else if (strcmp(builder.one_shot_task_uuid, task->uuid)) { + lwsl_notice("%s: reject task %s: one-shot affinity locked to %s\n", + __func__, task->uuid, builder.one_shot_task_uuid); + return 1; + } + } + if (builder.event_affinity_active) { if (!builder.event_affinity[0]) { lws_strncpy(builder.event_affinity, task->event_uuid, diff --git a/src/common/c-utils.c b/src/common/c-utils.c index 473d798..29cf97f 100644 --- a/src/common/c-utils.c +++ b/src/common/c-utils.c @@ -142,8 +142,6 @@ sai_ss_serialize_queue_helper(struct lws_ss_handle *h, r = lws_struct_json_serialize(js, buf + LWS_PRE, sizeof(buf) - LWS_PRE, &w); - // lwsl_hexdump_err(buf + LWS_PRE, w); - sai_ss_queue_frag_on_buflist_REQUIRES_LWS_PRE(h, buflist, buf + LWS_PRE, w, (unsigned int)((fi ? LWSSS_FLAG_SOM : 0) | (r == LSJS_RESULT_FINISH ? LWSSS_FLAG_EOM : 0))); diff --git a/src/power/p-sai.c b/src/power/p-sai.c index fba4a73..def40ab 100644 --- a/src/power/p-sai.c +++ b/src/power/p-sai.c @@ -219,12 +219,12 @@ sul_pcon_check_cb(lws_sorted_usec_list_t *sul) /* Rule 2: User Keep On -> Turn ON */ if (pc->flags & SAIP_PCON_F_MANUAL_STAY) { target_on = 1; - lwsl_warn("%s: PCON %s has user keep on -> Force ON\n", __func__, pc->name); + // lwsl_warn("%s: PCON %s has user keep on -> Force ON\n", __func__, pc->name); } /* Rule 3: Server Requested -> Turn ON */ else if (pc->flags & SAIP_PCON_F_NEEDED) { target_on = 1; - lwsl_warn("%s: PCON %s has server request -> Force ON\n", __func__, pc->name); + // lwsl_warn("%s: PCON %s has server request -> Force ON\n", __func__, pc->name); } lwsl_info("%s: PCON %s check: target=%d, current=%d (flags=0x%x)\n", diff --git a/src/server/s-power.c b/src/server/s-power.c index 1d77d5a..9f0cdc2 100644 --- a/src/server/s-power.c +++ b/src/server/s-power.c @@ -288,6 +288,7 @@ bail: */ struct pcon_lookup_ctx { + char *start; char *p; const char *end; int *n; @@ -304,6 +305,20 @@ cb_lookup_pcon(void *user, int cols, char **values, char **name) m = strlen(values[0]); + if (ctx->start) { + const char *curr = ctx->start; + while (curr && *curr) { + const char *comma = strchr(curr, ','); + size_t len = comma ? (size_t)(comma - curr) : strlen(curr); + if (len == m && !strncmp(curr, values[0], m)) { + return 0; + } + if (!comma) + break; + curr = comma + 1; + } + } + if (*ctx->n) *ctx->p++ = ','; @@ -400,14 +415,17 @@ sais_power_tx(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl) char q[256], query[256]; int r; + ctx.start = (char *)start; ctx.p = (char *)p; ctx.end = (const char *)end; ctx.n = &n; lws_sql_purify(q, pl->plat, sizeof(q)); lws_snprintf(query, sizeof(query), - "SELECT DISTINCT pcon FROM builders WHERE platform = '%s'", - q); + "SELECT DISTINCT pcon FROM builders WHERE platform = '%s' " + "UNION " + "SELECT DISTINCT pcon_name FROM pcon_builders WHERE builder_name = '%s'", + q, q); r = sqlite3_exec(vhd->server.pdb, query, cb_lookup_pcon, &ctx, NULL); diff --git a/src/server/s-ws-web.c b/src/server/s-ws-web.c index b665f1d..3045e10 100644 --- a/src/server/s-ws-web.c +++ b/src/server/s-ws-web.c @@ -219,34 +219,7 @@ sais_list_pcons(struct vhd *vhd) sqlite3_finalize(pcon_stmt); } - /* Iterate PCONs and populate controlled builders */ - lws_start_foreach_dll(struct lws_dll2 *, d, pmb.power_controllers.head) { - pc = lws_container_of(d, sai_power_controller_t, list); - char query[256]; - sqlite3_stmt *stmt; - - /* Manually query builders mapped to this pcon to avoid LWS struct nested 0-row ac free bug */ - lws_snprintf(query, sizeof(query), - "SELECT builder_name FROM pcon_builders WHERE pcon_name = '%s' " - "ORDER BY builder_name LIMIT 100", pc->name); - - if (sqlite3_prepare_v2(vhd->server.pdb, query, -1, &stmt, NULL) == SQLITE_OK) { - while (sqlite3_step(stmt) == SQLITE_ROW) { - const char *bname = (const char *)sqlite3_column_text(stmt, 0); - if (bname) { - sai_controlled_builder_t *c = - lwsac_use_zero(&ac, sizeof(*c), 2048); - if (c) { - lws_strncpy(c->name, bname, sizeof(c->name)); - lws_dll2_add_tail(&c->list, - &pc->controlled_builders_owner); - } - } - } - sqlite3_finalize(stmt); - } - } lws_end_foreach_dll(d); /* Serialize */ js = lws_struct_json_serialize_create(lsm_schema_power_managed_builders, diff --git a/src/virt/CMakeLists.txt b/src/virt/CMakeLists.txt index ac66ef4..489d49d 100644 --- a/src/virt/CMakeLists.txt +++ b/src/virt/CMakeLists.txt @@ -1,55 +1,66 @@ -set(SUB "sai-virt") -set(CPACK_DEBIAN_VIRT_PACKAGE_NAME "sai-virt") - -set(SRCS - v-sai.c - v-ws-server.c - v-libvirt.c - v-http-api.c - ../common/c-utils.c - ../common/struct-metadata.c -) - -set(requirements 1) -require_lws_config(LWS_WITH_CLIENT 1 requirements) -require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) -require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) - -if (requirements) - add_executable(${SUB} ${SRCS}) -add_dependencies(${SUB} sai_git_hash) - if (APPLE) - set_property(TARGET ${SUB} PROPERTY MACOSX_RPATH YES) - endif() - - if (SAI_LWS_INC_PATH) - target_include_directories(${SUB} PRIVATE ${SAI_LWS_INC_PATH}) - endif() - - if (HAS_LIBCAP) - find_library(CAP_LIB_PATH NAMES "cap") - endif() - - target_link_libraries(${SUB} websockets ${SAI_LWS_LIB_PATH}) - - message("LWS_OPENSSL_LIBRARIES ${SUB} '${LWS_OPENSSL_LIBRARIES}'") - if (LWS_OPENSSL_LIBRARIES) - target_link_libraries(${SUB} ${LWS_OPENSSL_LIBRARIES}) - endif() - - if (SAI_EXT_PTHREAD_LIBRARIES) - target_link_libraries(${SUB} ${SAI_EXT_PTHREAD_LIBRARIES}) - endif() - if (HAS_LIBCAP) - target_link_libraries(${SUB} ${CAP_LIB_PATH}) - endif() - - if (MSVC OR WIN32) - target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) - endif() - - install(TARGETS "sai-virt" - RUNTIME DESTINATION "${BIN_DIR}" COMPONENT virt) - -endif(requirements) +find_library(VIRT_LIB_PATH NAMES "virt") + +if (VIRT_LIB_PATH) + + set(SUB "sai-virt") + set(CPACK_DEBIAN_VIRT_PACKAGE_NAME "sai-virt") + + set(SRCS + v-sai.c + v-ws-server.c + v-libvirt.c + v-http-api.c + v-conf.c + ../common/c-utils.c + ../common/struct-metadata.c + ) + + set(requirements 1) + require_lws_config(LWS_WITH_CLIENT 1 requirements) + require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) + require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) + + if (requirements) + add_executable(${SUB} ${SRCS}) + add_dependencies(${SUB} sai_git_hash) + if (APPLE) + set_property(TARGET ${SUB} PROPERTY MACOSX_RPATH YES) + endif() + + if (SAI_LWS_INC_PATH) + target_include_directories(${SUB} PRIVATE ${SAI_LWS_INC_PATH}) + endif() + + if (HAS_LIBCAP) + find_library(CAP_LIB_PATH NAMES "cap") + endif() + + target_link_libraries(${SUB} websockets ${SAI_LWS_LIB_PATH} ${VIRT_LIB_PATH}) + + message("LWS_OPENSSL_LIBRARIES ${SUB} '${LWS_OPENSSL_LIBRARIES}'") + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries(${SUB} ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES) + target_link_libraries(${SUB} ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + if (HAS_LIBCAP) + target_link_libraries(${SUB} ${CAP_LIB_PATH}) + endif() + + if (MSVC OR WIN32) + target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS "sai-virt" + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT virt) + + endif(requirements) + +else() + message("libvirt not found, skipping sai-virt") +endif() + include(CPack) + diff --git a/src/virt/v-conf.c b/src/virt/v-conf.c new file mode 100644 index 0000000..33a663e --- /dev/null +++ b/src/virt/v-conf.c @@ -0,0 +1,203 @@ +/* + * sai-virt - v-conf.c + * + * Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#include "v-private.h" + +static const char * const paths_plat[] = { + "name", + "platform", + "base_image", + "overlay_size", +}; + +enum { + VJ_NAME, + VJ_PLATFORM, + VJ_BASE_IMAGE, + VJ_OVERLAY_SIZE, +}; + +struct v_conf_ctx { + saiv_plat_t *plat; +}; + +static signed char +saiv_conf_plat_cb(struct lejp_ctx *ctx, char reason) +{ + struct v_conf_ctx *v = (struct v_conf_ctx *)ctx->user; + + if (reason == LEJPCB_OBJECT_START && ctx->path_match == 0) { + v->plat = malloc(sizeof(*v->plat)); + if (!v->plat) + return -1; + memset(v->plat, 0, sizeof(*v->plat)); + return 0; + } + + if (reason == LEJPCB_OBJECT_END && ctx->path_match == 0) { + if (v->plat && v->plat->name[0] && v->plat->base_image[0]) { + lws_dll2_add_tail(&v->plat->list, &virt.plat_owner); + lwsl_notice("Added platform %s (base %s)\n", v->plat->name, v->plat->base_image); + v->plat = NULL; + } else if (v->plat) { + lwsl_err("Platform definition missing name or base_image\n"); + free(v->plat); + v->plat = NULL; + } + return 0; + } + + if (reason == LEJPCB_VAL_STR_END) { + switch (ctx->path_match - 1) { + case VJ_NAME: + lws_strncpy(v->plat->name, ctx->buf, sizeof(v->plat->name)); + break; + case VJ_PLATFORM: + lws_strncpy(v->plat->platform, ctx->buf, sizeof(v->plat->platform)); + break; + case VJ_BASE_IMAGE: + lws_strncpy(v->plat->base_image, ctx->buf, sizeof(v->plat->base_image)); + break; + case VJ_OVERLAY_SIZE: + lws_strncpy(v->plat->overlay_size, ctx->buf, sizeof(v->plat->overlay_size)); + break; + } + } + + return 0; +} + +static int +saiv_conf_dir_cb(const char *dirpath, void *user, struct lws_dir_entry *lde) +{ + char filepath[256]; + struct lejp_ctx ctx; + struct v_conf_ctx vctx; + int fd, m = 0; + ssize_t n; + uint8_t buf[1024]; + + if (lde->name[0] == '.') + return 0; + + lws_snprintf(filepath, sizeof(filepath), "%s/%s", dirpath, lde->name); + + fd = open(filepath, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", filepath); + return 0; + } + + memset(&vctx, 0, sizeof(vctx)); + lejp_construct(&ctx, saiv_conf_plat_cb, &vctx, paths_plat, LWS_ARRAY_SIZE(paths_plat)); + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + m = lejp_parse(&ctx, buf, (int)n); + if (m < 0 && m != LEJP_CONTINUE) { + lwsl_err("%s: JSON decode error %d\n", filepath, m); + break; + } + } + close(fd); + lejp_destruct(&ctx); + + if (vctx.plat) + free(vctx.plat); + + return 0; +} + +int +saiv_config(struct sai_virt *virt, const char *d) +{ + lwsl_notice("Parsing configuration directory %s\n", d); + return lws_dir(d, virt, saiv_conf_dir_cb); +} + +static const char * const paths_global[] = { + "servers[].url", + "max_vms", +}; + +enum { + VJG_SERVER_URL, + VJG_MAX_VMS, +}; + +static signed char +saiv_conf_global_cb(struct lejp_ctx *ctx, char reason) +{ + struct sai_virt *v = (struct sai_virt *)ctx->user; + + if (reason == LEJPCB_VAL_STR_END) { + switch (ctx->path_match - 1) { + case VJG_SERVER_URL: + { + saiv_server_t *srv = malloc(sizeof(*srv)); + if (srv) { + memset(srv, 0, sizeof(*srv)); + srv->url = strdup(ctx->buf); + if (lws_ss_create(v->context, 0, &ssi_saiv_server_link_t, + srv, &srv->ss, NULL, NULL)) { + lwsl_err("%s: failed to create ss\n", __func__); + free((void *)srv->url); + free(srv); + } else { + lws_dll2_add_tail(&srv->list, &v->sai_server_owner); + lwsl_notice("Added server %s\n", srv->url); + } + } + break; + } + case VJG_MAX_VMS: + v->max_vms = atoi(ctx->buf); + lwsl_notice("Set max_vms to %d\n", v->max_vms); + break; + } + } + return 0; +} + +int +saiv_config_global(struct sai_virt *virt, const char *filepath) +{ + struct lejp_ctx ctx; + int fd, m = 0; + ssize_t n; + uint8_t buf[1024]; + + lwsl_notice("Parsing global configuration %s\n", filepath); + + fd = open(filepath, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", filepath); + return 1; + } + + lejp_construct(&ctx, saiv_conf_global_cb, virt, paths_global, LWS_ARRAY_SIZE(paths_global)); + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + m = lejp_parse(&ctx, buf, (int)n); + if (m < 0 && m != LEJP_CONTINUE) { + lwsl_err("%s: JSON decode error %d\n", filepath, m); + break; + } + } + close(fd); + lejp_destruct(&ctx); + + return 0; +} diff --git a/src/virt/v-http-api.c b/src/virt/v-http-api.c index e0d3ae2..5a7ab55 100644 --- a/src/virt/v-http-api.c +++ b/src/virt/v-http-api.c @@ -63,6 +63,27 @@ callback_virt_http(struct lws *wsi, enum lws_callback_reasons reason, if (len > 6 && !strncmp(path, "/stay/", 6)) { lws_strncpy(vm_id, path + 6, sizeof(vm_id)); lwsl_notice("%s: Received stay for %s\n", __func__, vm_id); + + saiv_vm_t *found_vm = NULL; + lws_start_foreach_dll(struct lws_dll2 *, d, virt.plat_owner.head) { + saiv_plat_t *vp = lws_container_of(d, saiv_plat_t, list); + lws_start_foreach_dll(struct lws_dll2 *, v, vp->vm_owner.head) { + saiv_vm_t *vm = lws_container_of(v, saiv_vm_t, list); + if (!strcmp(vm->name, vm_id)) { + found_vm = vm; + break; + } + } lws_end_foreach_dll(v); + if (found_vm) + break; + } lws_end_foreach_dll(d); + + if (found_vm) { + /* Extend the safety timeout since the VM is alive and communicating */ + lws_sul_schedule(virt.context, 0, &found_vm->sul_timeout, + saiv_vm_timeout_cb, 5 * 60 * LWS_US_PER_SEC); + } + /* We never return stay = true for ephemeral VMs */ uint8_t stay_res = '0'; if (lws_write(wsi, &stay_res, 1, LWS_WRITE_HTTP) != 1) diff --git a/src/virt/v-libvirt.c b/src/virt/v-libvirt.c index 605d9db..d31a844 100644 --- a/src/virt/v-libvirt.c +++ b/src/virt/v-libvirt.c @@ -12,44 +12,74 @@ #include <libwebsockets.h> #include <string.h> #include <stdio.h> +#include <stdlib.h> +#include <libvirt/libvirt.h> #include "v-private.h" -/* - * We use virsh via lws_spawn_piped to avoid a hard dependency on libvirt.so - * and to easily integrate with the asynchronous lws event loop. - */ +virConnectPtr conn; -static void -reap_virsh(void *opaque, const lws_spawn_resource_us_t *res, siginfo_t *si, int we_killed_him) +static char * +replace_string(const char *orig, const char *rep, const char *with) { - lwsl_notice("%s: virsh exited with code %d\n", __func__, si->si_status); + char *result; + char *ins; + char *tmp; + size_t len_rep; + size_t len_with; + size_t len_front; + size_t count; + + if (!orig || !rep) + return NULL; + len_rep = strlen(rep); + if (len_rep == 0) + return NULL; + if (!with) + with = ""; + len_with = strlen(with); + + ins = (char *)orig; + for (count = 0; (tmp = strstr(ins, rep)); ++count) { + ins = tmp + len_rep; + } + + tmp = result = malloc(strlen(orig) + (count * len_with) + 1); + if (!result) + return NULL; + + while (count--) { + ins = strstr(orig, rep); + len_front = lws_ptr_diff_size_t(ins, orig); + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; + } + strcpy(tmp, orig); + return result; } -static int -spawn_virsh_command(struct sai_virt *virt, const char * const *exec_array) +static void +strip_xml_tags(char *xml, const char *start_tag, const char *end_tag) { - struct lws_spawn_piped_info info; - struct lws_spawn_piped *lsp; - - memset(&info, 0, sizeof(info)); - info.vh = virt->vhost; - info.exec_array = exec_array; - info.max_log_lines = 10; - info.reap_cb = reap_virsh; - - lsp = lws_spawn_piped(&info); - if (!lsp) { - lwsl_err("%s: failed to spawn virsh\n", __func__); - return 1; + char *start; + while ((start = strstr(xml, start_tag))) { + char *end = strstr(start, end_tag); + if (!end) + break; + end += strlen(end_tag); + memmove(start, end, strlen(end) + 1); } - - return 0; } static int ops_libvirt_init(struct sai_virt *virt) { + conn = virConnectOpen("qemu:///system"); + if (!conn) { + lwsl_err("Failed to open connection to qemu:///system\n"); + return 1; + } lwsl_notice("%s: libvirt ops initialized\n", __func__); return 0; } @@ -57,19 +87,202 @@ ops_libvirt_init(struct sai_virt *virt) static int ops_libvirt_spawn(struct sai_virt *virt, struct saiv_vm *vm) { - const char * const exec_array[] = { - "/usr/bin/virsh", "list", "--all", NULL - }; + virDomainPtr dom; + virStoragePoolPtr pool; + virStorageVolPtr vol; + char *xml, *xml2, *xml3; + char vol_xml[1024]; + char overlay_path[256]; + char orig_name_tag[128]; + char new_name_tag[128]; + char orig_source_tag[256]; + char new_source_tag[256]; + const char *shm_pool_xml = "<pool type='dir'><name>sai_shm</name><target><path>/dev/shm</path></target></pool>"; - lwsl_notice("%s: Spawning ephemeral VM %s for platform: %s\n", __func__, vm->name, vm->plat->name); + lwsl_notice("%s: Spawning ephemeral VM %s for platform: %s (base %s)\n", + __func__, vm->name, vm->plat->name, vm->plat->base_image); - return spawn_virsh_command(virt, exec_array); + if (!conn) + return 1; + + /* 1. Ensure the /dev/shm storage pool exists */ + pool = virStoragePoolLookupByName(conn, "sai_shm"); + if (!pool) { + pool = virStoragePoolCreateXML(conn, shm_pool_xml, 0); + if (!pool) { + lwsl_err("Failed to create transient shm storage pool\n"); + return 1; + } + } else { + /* If it exists but is inactive, start it */ + int active = virStoragePoolIsActive(pool); + if (active == 0) + virStoragePoolCreate(pool, 0); + } + + long capacity_size = 20; + const char *capacity_unit = "G"; + if (vm->plat->overlay_size[0]) { + char *p; + capacity_size = strtol(vm->plat->overlay_size, &p, 10); + if (p && *p) + capacity_unit = p; + } + + /* 2. Create the overlay volume using libvirt API */ + lws_snprintf(vol_xml, sizeof(vol_xml), + "<volume>" + " <name>%s.qcow2</name>" + " <capacity unit='%s'>%ld</capacity>" + " <target><format type='qcow2'/></target>" + " <backingStore>" + " <path>%s</path>" + " <format type='qcow2'/>" + " </backingStore>" + "</volume>", + vm->name, + capacity_unit, capacity_size, + vm->plat->base_image); + + vol = virStorageVolCreateXML(pool, vol_xml, 0); + if (!vol) { + lwsl_err("Failed to create libvirt storage volume for overlay\n"); + virStoragePoolFree(pool); + return 1; + } + virStorageVolFree(vol); + virStoragePoolFree(pool); + + lws_snprintf(overlay_path, sizeof(overlay_path), "/dev/shm/%s.qcow2", vm->name); + + /* 3. Get base domain XML and manipulate it */ + dom = virDomainLookupByName(conn, vm->plat->name); + if (!dom) { + lwsl_err("Failed to find base domain %s\n", vm->plat->name); + return 1; + } + + xml = virDomainGetXMLDesc(dom, 0); + virDomainFree(dom); + + if (!xml) { + lwsl_err("Failed to get XML for base domain\n"); + return 1; + } + + /* Replace <name>base</name> with <name>vm->name</name> */ + lws_snprintf(orig_name_tag, sizeof(orig_name_tag), "<name>%s</name>", vm->plat->name); + lws_snprintf(new_name_tag, sizeof(new_name_tag), "<name>%s</name>", vm->name); + xml2 = replace_string(xml, orig_name_tag, new_name_tag); + free(xml); + + /* Replace <source file='base_image'/> with <source file='overlay_path'/> */ + lws_snprintf(orig_source_tag, sizeof(orig_source_tag), "file='%s'", vm->plat->base_image); + lws_snprintf(new_source_tag, sizeof(new_source_tag), "file='%s'", overlay_path); + xml3 = replace_string(xml2, orig_source_tag, new_source_tag); + free(xml2); + + if (!xml3) { + lwsl_err("Failed to manipulate XML\n"); + return 1; + } + + /* Inject qemu namespace into <domain> */ + char *xml4 = replace_string(xml3, "<domain type=", "<domain xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' type="); + if (!xml4) + xml4 = strdup(xml3); + free(xml3); + + /* Inject smbios mode='sysinfo' into <os> if missing */ + char *xml4a = xml4; + if (!strstr(xml4, "<smbios mode='sysinfo'/>")) { + xml4a = replace_string(xml4, "</os>", " <smbios mode='sysinfo'/>\n </os>"); + if (xml4a) + free(xml4); + else + xml4a = xml4; + } + + /* Inject SMBIOS serial number and fw_cfg for builder identity */ + char fw_cfg_tag[512]; + lws_snprintf(fw_cfg_tag, sizeof(fw_cfg_tag), + " <sysinfo type='smbios'>\n" + " <system>\n" + " <entry name='serial'>sai_builder_id:%s</entry>\n" + " </system>\n" + " </sysinfo>\n" + " <qemu:commandline>\n" + " <qemu:arg value='-fw_cfg'/>\n" + " <qemu:arg value='name=opt/sai_builder_id,string=%s'/>\n" + " </qemu:commandline>\n" + "</domain>", vm->name, vm->name); + + char *xml5 = replace_string(xml4a, "</domain>", fw_cfg_tag); + if (xml5) { + free(xml4a); + } else { + xml5 = xml4a; + } + + if (!xml5) { + lwsl_err("Failed to manipulate XML\n"); + return 1; + } + + /* Remove UUID so libvirt generates a new one, avoiding conflicts with the base VM */ + strip_xml_tags(xml5, "<uuid>", "</uuid>"); + /* Remove MAC addresses so libvirt generates new ones, avoiding network conflicts */ + strip_xml_tags(xml5, "<mac address=", "/>"); + + /* 4. Boot the transient domain */ + dom = virDomainCreateXML(conn, xml5, 0); + free(xml5); + + if (!dom) { + lwsl_err("Failed to create transient domain %s\n", vm->name); + return 1; + } + + virDomainFree(dom); + lwsl_notice("Successfully spawned ephemeral VM %s\n", vm->name); + + return 0; } static int ops_libvirt_destroy(struct sai_virt *virt, struct saiv_vm *vm) { + virDomainPtr dom; + virStoragePoolPtr pool; + virStorageVolPtr vol; + char vol_name[128]; + lwsl_notice("%s: Destroying ephemeral VM: %s\n", __func__, vm->name); + + if (!conn) + return 1; + + dom = virDomainLookupByName(conn, vm->name); + if (dom) { + virDomainDestroy(dom); + virDomainFree(dom); + } else { + lwsl_warn("Domain %s not found during destroy\n", vm->name); + } + + pool = virStoragePoolLookupByName(conn, "sai_shm"); + if (pool) { + lws_snprintf(vol_name, sizeof(vol_name), "%s.qcow2", vm->name); + vol = virStorageVolLookupByName(pool, vol_name); + if (vol) { + virStorageVolDelete(vol, 0); + virStorageVolFree(vol); + } else { + lwsl_warn("Volume %s not found in pool sai_shm\n", vol_name); + } + virStoragePoolFree(pool); + } + return 0; } diff --git a/src/virt/v-private.h b/src/virt/v-private.h index 273d685..7b57f7b 100644 --- a/src/virt/v-private.h +++ b/src/virt/v-private.h @@ -29,6 +29,9 @@ typedef struct sai_virt_ops { typedef struct saiv_plat { lws_dll2_t list; char name[64]; + char platform[128]; + char base_image[128]; + char overlay_size[32]; int wait_magnification; int starting_vms; @@ -84,5 +87,6 @@ extern const sai_virt_ops_t ops_libvirt; extern const struct lws_protocols virt_protocols[]; int saiv_config(struct sai_virt *virt, const char *d); +int saiv_config_global(struct sai_virt *virt, const char *filepath); #endif diff --git a/src/virt/v-sai.c b/src/virt/v-sai.c index 5b0014f..84a6322 100644 --- a/src/virt/v-sai.c +++ b/src/virt/v-sai.c @@ -21,6 +21,9 @@ static int interrupted; static const char * const default_ss_policy = "{" + "\"release\": \"01234567\"," + "\"product\": \"sai-virt\"," + "\"schema-version\": 1," "\"retry\": [" "{\"default\": {" "\"backoff\": [1000, 2000, 3000, 5000, 10000]," @@ -33,8 +36,11 @@ static const char * const default_ss_policy = "\"s\": [" "{\"sai_power_client\": {" "\"endpoint\": \"${url}\"," + "\"port\": 443," "\"protocol\": \"ws\"," - "\"ws_subprotocol\": \"com-warmcat-sai-builder\"," + "\"tls\": true," + "\"nailed_up\": true," + "\"ws_subprotocol\": \"com-warmcat-sai\"," "\"http_url\": \"\"," "\"retry\": \"default\"," "\"metadata\": [" @@ -75,7 +81,8 @@ int main(int argc, const char **argv) memset(&info, 0, sizeof info); info.port = 8000; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | - LWS_SERVER_OPTION_VALIDATE_UTF8; + LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS; info.pprotocols = pprotocols; signal(SIGINT, sigint_handler); @@ -97,30 +104,11 @@ int main(int argc, const char **argv) return 1; } - /* We can spawn mac-m1, windows-10, etc. (Mocked for now) */ - const char *plats[] = {"windows-x86_64", "mac-m1"}; - for (size_t i = 0; i < LWS_ARRAY_SIZE(plats); i++) { - saiv_plat_t *vp = malloc(sizeof(*vp)); - if (vp) { - memset(vp, 0, sizeof(*vp)); - lws_strncpy(vp->name, plats[i], sizeof(vp->name)); - lws_dll2_add_tail(&vp->list, &virt.plat_owner); - } - } + /* Parse platforms from /etc/sai/virt/conf.d */ + saiv_config(&virt, "/etc/sai/virt/conf.d"); - /* We create the server link manually for testing skeleton */ - saiv_server_t *srv = malloc(sizeof(*srv)); - if (srv) { - memset(srv, 0, sizeof(*srv)); - srv->url = "warmcat.com"; /* example */ - if (lws_ss_create(virt.context, 0, &ssi_saiv_server_link_t, - srv, &srv->ss, NULL, NULL)) { - lwsl_err("%s: failed to create ss\n", __func__); - free(srv); - } else { - lws_dll2_add_tail(&srv->list, &virt.sai_server_owner); - } - } + /* Parse global configuration from /etc/sai/virt/conf */ + saiv_config_global(&virt, "/etc/sai/virt/conf"); while (!lws_service(virt.context, 0) && !interrupted) ; @@ -129,11 +117,22 @@ int main(int argc, const char **argv) saiv_server_t *s = lws_container_of(d, saiv_server_t, list); lws_ss_destroy(&s->ss); lws_dll2_remove(d); + free((void *)s->url); free(s); } lws_end_foreach_dll_safe(d, d1); lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, virt.plat_owner.head) { saiv_plat_t *p = lws_container_of(d, saiv_plat_t, list); + + lws_start_foreach_dll_safe(struct lws_dll2 *, v, v1, p->vm_owner.head) { + saiv_vm_t *vm = lws_container_of(v, saiv_vm_t, list); + if (virt.ops) + virt.ops->destroy(&virt, vm); + lws_dll2_remove(v); + lws_sul_cancel(&vm->sul_timeout); + free(vm); + } lws_end_foreach_dll_safe(v, v1); + lws_dll2_remove(d); free(p); } lws_end_foreach_dll_safe(d, d1); diff --git a/src/virt/v-ws-server.c b/src/virt/v-ws-server.c index a9f1bdc..347810e 100644 --- a/src/virt/v-ws-server.c +++ b/src/virt/v-ws-server.c @@ -18,12 +18,16 @@ * When we connect, we masquerade as a builder/pcon and send our platforms */ static lws_ss_state_return_t -saiv_server_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, - size_t *len, int *flags) +saiv_server_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) { saiv_server_link_t *g = (saiv_server_link_t *)userobj; + lws_ss_state_return_t r; - return sai_ss_tx_from_buflist_helper(g->ss, &g->bl_tx, buf, len, flags); + r = sai_ss_tx_from_buflist_helper(g->ss, &g->bl_tx, buf, len, flags); + if (r == LWSSSSRET_OK) + lwsl_notice("%s: Transmitted %zu bytes (flags=%d)\n", __func__, *len, *flags); + return r; } static void @@ -87,46 +91,51 @@ saiv_server_rx(void *userobj, const uint8_t *buf, size_t len, int flags) sai_platform_pending_tasks_t *pt = (sai_platform_pending_tasks_t *)a.dest; lwsl_notice("%s: Pending tasks for pcons: %s\n", __func__, pt->pcons); - int total_wheel_weight = 0; + if (pt && pt->tasks.head) { + while (virt.running_vms < virt.max_vms) { + int total_wheel_weight = 0; - /* Step 1: Calculate true demand and populate the wheel */ - lws_start_foreach_dll(struct lws_dll2 *, p, pt->tasks.head) { - sai_platform_pending_task_t *t = lws_container_of(p, sai_platform_pending_task_t, list); + /* Step 1: Count true demand */ + lws_start_foreach_dll(struct lws_dll2 *, p, pt->tasks.head) { + sai_platform_pending_task_t *t = lws_container_of(p, sai_platform_pending_task_t, list); - saiv_plat_t *found_vp = NULL; - lws_start_foreach_dll(struct lws_dll2 *, d, virt.plat_owner.head) { - saiv_plat_t *vp = lws_container_of(d, saiv_plat_t, list); - if (!strcmp(vp->name, t->plat)) { - found_vp = vp; - break; - } - } lws_end_foreach_dll(d); + saiv_plat_t *found_vp = NULL; + lws_start_foreach_dll(struct lws_dll2 *, d, virt.plat_owner.head) { + saiv_plat_t *vp = lws_container_of(d, saiv_plat_t, list); + const char *pname = vp->platform[0] ? vp->platform : vp->name; + if (!strcmp(pname, t->plat)) { + found_vp = vp; + break; + } + } lws_end_foreach_dll(d); - if (found_vp) { - int true_demand = (int)t->pending - found_vp->starting_vms; - if (true_demand > 0) { - /* Apply wait magnification factor */ - total_wheel_weight += true_demand + found_vp->wait_magnification; - } - } - } lws_end_foreach_dll(p); + if (found_vp) { + int true_demand = (int)t->pending - found_vp->starting_vms; + if (true_demand > 0) { + /* Apply wait magnification factor */ + total_wheel_weight += true_demand + found_vp->wait_magnification; + } + } + } lws_end_foreach_dll(p); - /* Step 2: Roll the dice if there is demand */ - if (total_wheel_weight > 0 && virt.running_vms < virt.max_vms) { - /* LWS random */ - uint32_t r; - lws_get_random(virt.context, &r, sizeof(r)); - int target = (int)(r % (uint32_t)total_wheel_weight); + if (total_wheel_weight == 0) + break; + + /* Step 2: Roll the dice */ + uint32_t r; + lws_get_random(virt.context, &r, sizeof(r)); + int target = (int)(r % (uint32_t)total_wheel_weight); - saiv_plat_t *winner = NULL; + saiv_plat_t *winner = NULL; - lws_start_foreach_dll(struct lws_dll2 *, p, pt->tasks.head) { + lws_start_foreach_dll(struct lws_dll2 *, p, pt->tasks.head) { sai_platform_pending_task_t *t = lws_container_of(p, sai_platform_pending_task_t, list); saiv_plat_t *found_vp = NULL; lws_start_foreach_dll(struct lws_dll2 *, d, virt.plat_owner.head) { saiv_plat_t *vp = lws_container_of(d, saiv_plat_t, list); - if (!strcmp(vp->name, t->plat)) { + const char *pname = vp->platform[0] ? vp->platform : vp->name; + if (!strcmp(pname, t->plat)) { found_vp = vp; break; } @@ -185,9 +194,13 @@ saiv_server_state(void *userobj, void *sh, lws_ss_constate_t state, switch (state) { case LWSSSCS_CREATING: - lwsl_notice("%s: CREATING\n", __func__); - /* We'd set metadata url here from the config, skipped for skeleton */ + { + saiv_server_t *srv = (saiv_server_t *)g->opaque_data; + lwsl_notice("%s: CREATING (url: %s)\n", __func__, srv->url); + if (lws_ss_set_metadata(g->ss, "url", srv->url, strlen(srv->url))) + return LWSSSSRET_DESTROY_ME; break; + } case LWSSSCS_CONNECTED: lwsl_notice("%s: Connected to sai-server\n", __func__); @@ -199,18 +212,21 @@ saiv_server_state(void *userobj, void *sh, lws_ss_constate_t state, sai_power_controller_t *pc = lwsac_use_zero(&ac, sizeof(*pc), 512); if (pc) { lws_strncpy(pc->name, virt.hostname, sizeof(pc->name)); + lws_strncpy(pc->type, "virt", sizeof(pc->type)); + pc->on = 1; lws_dll2_add_tail(&pc->list, &pmb.power_controllers); - } - /* Register our platforms as the "builders" we manage */ - lws_start_foreach_dll(struct lws_dll2 *, d, virt.plat_owner.head) { - saiv_plat_t *vp = lws_container_of(d, saiv_plat_t, list); - sai_power_managed_builder_t *bp = lwsac_use_zero(&ac, sizeof(*bp), 512); - if (bp) { - lws_strncpy(bp->name, vp->name, sizeof(bp->name)); - lws_dll2_add_tail(&bp->list, &pmb.builders); - } - } lws_end_foreach_dll(d); + /* Register our platforms as the "builders" we manage */ + lws_start_foreach_dll(struct lws_dll2 *, d, virt.plat_owner.head) { + saiv_plat_t *vp = lws_container_of(d, saiv_plat_t, list); + sai_controlled_builder_t *c = lwsac_use_zero(&ac, sizeof(*c), 512); + if (c) { + const char *pname = vp->platform[0] ? vp->platform : vp->name; + lws_strncpy(c->name, pname, sizeof(c->name)); + lws_dll2_add_tail(&c->list, &pc->controlled_builders_owner); + } + } lws_end_foreach_dll(d); + } sai_ss_serialize_queue_helper(g->ss, &g->bl_tx, lsm_schema_power_managed_builders, diff --git a/src/web/CMakeLists.txt b/src/web/CMakeLists.txt index 8eb8e40..d1301a0 100644 --- a/src/web/CMakeLists.txt +++ b/src/web/CMakeLists.txt @@ -124,6 +124,7 @@ add_dependencies(${SUB} sai_git_hash) ../../assets/linux-centos-8.svg ../../assets/linux-fedora-32-riscv.svg ../../assets/linux-fedora-32.svg + ../../assets/linux-fedora44.svg ../../assets/linux-gentoo.svg ../../assets/linux-ubuntu-1804.svg ../../assets/linux-ubuntu-2004.svg diff --git a/src/web/w-comms.c b/src/web/w-comms.c index 9da21d6..e012760 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -238,8 +238,6 @@ w_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, case LWS_CALLBACK_HTTP: - lwsl_wsi_notice(wsi, "_HTTP"); - if (!vhd) { lwsl_err("%s: NULL vhd\n", __func__); return -1;
Page fetched 0s ago, creation time: 11ms (vhost etag hits: 0%, cache hits: 0%)