diff --git a/src/virt/v-http-api.c b/src/virt/v-http-api.c
index e76e26a..e0d3ae2 100644
--- a/src/virt/v-http-api.c
+++ b/src/virt/v-http-api.c
@@ -27,8 +27,34 @@ callback_virt_http(struct lws *wsi, enum lws_callback_reasons reason,
if (len > 16 && !strncmp(path, "/auto-power-off/", 16)) {
lws_strncpy(vm_id, path + 16, sizeof(vm_id));
lwsl_notice("%s: Received auto-power-off for %s\n", __func__, vm_id);
- if (virt.ops)
- virt.ops->destroy(&virt, 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) {
+ if (virt.ops)
+ virt.ops->destroy(&virt, found_vm);
+
+ if (found_vm->plat->starting_vms > 0)
+ found_vm->plat->starting_vms--;
+
+ virt.running_vms--;
+
+ lws_dll2_remove(&found_vm->list);
+ lws_sul_cancel(&found_vm->sul_timeout);
+ free(found_vm);
+ }
lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
return -1; /* hang up */
diff --git a/src/virt/v-libvirt.c b/src/virt/v-libvirt.c
index 5cfa876..605d9db 100644
--- a/src/virt/v-libvirt.c
+++ b/src/virt/v-libvirt.c
@@ -55,32 +55,21 @@ ops_libvirt_init(struct sai_virt *virt)
}
static int
-ops_libvirt_spawn(struct sai_virt *virt, const char *platform)
+ops_libvirt_spawn(struct sai_virt *virt, struct saiv_vm *vm)
{
- /*
- * Mock implementation:
- * Ideally, we would run:
- * virsh virt-clone --original sai-template-<platform> --name sai-ephemeral-<uuid> --auto-clone
- * virsh start sai-ephemeral-<uuid>
- */
const char * const exec_array[] = {
"/usr/bin/virsh", "list", "--all", NULL
};
- lwsl_notice("%s: Spawning ephemeral VM for platform: %s\n", __func__, platform);
+ lwsl_notice("%s: Spawning ephemeral VM %s for platform: %s\n", __func__, vm->name, vm->plat->name);
return spawn_virsh_command(virt, exec_array);
}
static int
-ops_libvirt_destroy(struct sai_virt *virt, const char *vm_id)
+ops_libvirt_destroy(struct sai_virt *virt, struct saiv_vm *vm)
{
- /*
- * Mock implementation:
- * virsh destroy <vm_id>
- * virsh undefine <vm_id> --remove-all-storage
- */
- lwsl_notice("%s: Destroying ephemeral VM: %s\n", __func__, vm_id);
+ lwsl_notice("%s: Destroying ephemeral VM: %s\n", __func__, vm->name);
return 0;
}
diff --git a/src/virt/v-private.h b/src/virt/v-private.h
index c7fd1fd..273d685 100644
--- a/src/virt/v-private.h
+++ b/src/virt/v-private.h
@@ -17,23 +17,46 @@
struct sai_virt;
+struct saiv_vm;
+
typedef struct sai_virt_ops {
const char *name;
int (*init)(struct sai_virt *virt);
- int (*spawn)(struct sai_virt *virt, const char *platform);
- int (*destroy)(struct sai_virt *virt, const char *vm_id);
+ int (*spawn)(struct sai_virt *virt, struct saiv_vm *vm);
+ int (*destroy)(struct sai_virt *virt, struct saiv_vm *vm);
} sai_virt_ops_t;
+typedef struct saiv_plat {
+ lws_dll2_t list;
+ char name[64];
+
+ int wait_magnification;
+ int starting_vms;
+
+ lws_dll2_owner_t vm_owner;
+} saiv_plat_t;
+
+typedef struct saiv_vm {
+ lws_dll2_t list;
+ saiv_plat_t *plat;
+ char name[64];
+ lws_sorted_usec_list_t sul_timeout;
+} saiv_vm_t;
+
/*
* Represents the virt process state
*/
struct sai_virt {
lws_dll2_owner_t sai_server_owner; /* servers we connect to */
+ lws_dll2_owner_t plat_owner; /* platforms we can spawn */
struct lws_context *context;
struct lws_vhost *vhost;
const sai_virt_ops_t *ops;
+ int running_vms;
+ int max_vms;
+
const char *bind; /* listen socket binding */
const char *perms; /* user:group */
const char *port; /* port we listen on */
diff --git a/src/virt/v-sai.c b/src/virt/v-sai.c
index 1113272..5b0014f 100644
--- a/src/virt/v-sai.c
+++ b/src/virt/v-sai.c
@@ -65,6 +65,8 @@ int main(int argc, const char **argv)
if (gethostname(virt.hostname, sizeof(virt.hostname) - 1))
lws_strncpy(virt.hostname, "unknown", sizeof(virt.hostname));
+ virt.max_vms = 4;
+
const struct lws_protocols *pprotocols[] = {
&virt_protocols[0],
NULL
@@ -95,6 +97,17 @@ 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);
+ }
+ }
+
/* We create the server link manually for testing skeleton */
saiv_server_t *srv = malloc(sizeof(*srv));
if (srv) {
@@ -119,6 +132,12 @@ int main(int argc, const char **argv)
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_dll2_remove(d);
+ free(p);
+ } lws_end_foreach_dll_safe(d, d1);
+
lws_context_destroy(virt.context);
return 0;
diff --git a/src/virt/v-ws-server.c b/src/virt/v-ws-server.c
index 87dbf8a..a8f7b84 100644
--- a/src/virt/v-ws-server.c
+++ b/src/virt/v-ws-server.c
@@ -26,6 +26,25 @@ saiv_server_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
return sai_ss_tx_from_buflist_helper(g->ss, &g->bl_tx, buf, len, flags);
}
+static void
+saiv_vm_timeout_cb(lws_sorted_usec_list_t *sul)
+{
+ saiv_vm_t *vm = lws_container_of(sul, saiv_vm_t, sul_timeout);
+
+ lwsl_err("%s: VM %s timed out, purging\n", __func__, vm->name);
+
+ if (virt.ops)
+ virt.ops->destroy(&virt, vm);
+
+ if (vm->plat->starting_vms > 0)
+ vm->plat->starting_vms--;
+
+ virt.running_vms--;
+
+ lws_dll2_remove(&vm->list);
+ free(vm);
+}
+
static lws_ss_state_return_t
saiv_server_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
@@ -57,13 +76,89 @@ 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;
+
+ /* 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);
- lwsl_notice(" - %s: %u pending\n", t->plat, t->pending);
- if (t->pending > 0 && virt.ops) {
- virt.ops->spawn(&virt, t->plat);
+
+ 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);
+
+ 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);
+
+ saiv_plat_t *winner = NULL;
+
+ 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);
+
+ if (found_vp) {
+ int true_demand = (int)t->pending - found_vp->starting_vms;
+ if (true_demand > 0) {
+ target -= (true_demand + found_vp->wait_magnification);
+ if (target < 0) {
+ winner = found_vp;
+ break;
+ } else {
+ /* This platform wasn't picked, increase its wait magnification */
+ found_vp->wait_magnification++;
+ }
+ }
+ }
+ } lws_end_foreach_dll(p);
+
+ /* Step 3: Spawn the winner and reset its magnification */
+ if (winner && virt.ops) {
+ lwsl_notice("%s: Wheel picked platform %s (wait factor %d reset)\n",
+ __func__, winner->name, winner->wait_magnification);
+
+ saiv_vm_t *vm = malloc(sizeof(*vm));
+ if (vm) {
+ memset(vm, 0, sizeof(*vm));
+ vm->plat = winner;
+ lws_snprintf(vm->name, sizeof(vm->name), "sai-vm-%s-%u", winner->name, (unsigned int)lws_now_usecs());
+ lws_dll2_add_tail(&vm->list, &winner->vm_owner);
+
+ winner->starting_vms++;
+ virt.running_vms++;
+ winner->wait_magnification = 0;
+ virt.ops->spawn(&virt, vm);
+
+ /* Clean up if it never connects and terminates itself */
+ lws_sul_schedule(virt.context, 0, &vm->sul_timeout,
+ saiv_vm_timeout_cb, 5 * 60 * LWS_US_PER_SEC); /* 5 min */
+ }
+ }
+ }
}
lwsac_free(&a.ac);
@@ -91,15 +186,14 @@ saiv_server_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_strncpy(r.builder_name, virt.hostname, sizeof(r.builder_name));
lws_strncpy(r.power_controller_name, virt.hostname, sizeof(r.power_controller_name));
- /* 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++) {
+ 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_builder_platform_t *bp = lwsac_use_zero(&ac, sizeof(*bp), 512);
if (bp) {
- lws_strncpy(bp->name, plats[i], sizeof(bp->name));
+ lws_strncpy(bp->name, vp->name, sizeof(bp->name));
lws_dll2_add_tail(&bp->list, &r.platforms_owner);
}
- }
+ } lws_end_foreach_dll(d);
sai_ss_serialize_queue_helper(g->ss, &g->bl_tx,
lsm_schema_builder_registration,