fix: wire distinct M3 simulator identities

Merge PR #159 after Code Queue rebase and validation. Runner verified M3 cardinality, hardware-loop smoke, patch-panel runtime smoke, m3 readonly contract, and git diff check. This is source/config blocker removal only; it does not claim DEV-LIVE M3 pass.
This commit is contained in:
Lyon
2026-05-23 00:09:29 +08:00
committed by GitHub
parent 792262c5d1
commit 7a0648088c
20 changed files with 791 additions and 53 deletions
+162 -10
View File
@@ -6,24 +6,74 @@ import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const namespace = "hwlab-dev";
const m3EndpointMap = Object.freeze({
res_boxsimu_2: "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201"
});
const expected = Object.freeze({
"hwlab-box-simu": {
kind: "StatefulSet",
replicas: 2,
serviceName: "hwlab-box-simu",
instanceNames: ["hwlab-box-simu-1", "hwlab-box-simu-2"],
env: {
HWLAB_BOX_ID: ["boxsimu_1", "boxsimu_2"]
HWLAB_BOX_ID: ["boxsimu_1", "boxsimu_2"],
HWLAB_BOX_RESOURCE_ID: ["res_boxsimu_1", "res_boxsimu_2"],
HWLAB_GATEWAY_SESSION_ID: ["gws_gwsimu_1", "gws_gwsimu_2"]
}
},
"hwlab-gateway-simu": {
kind: "StatefulSet",
replicas: 2,
serviceName: "hwlab-gateway-simu",
instanceNames: ["hwlab-gateway-simu-1", "hwlab-gateway-simu-2"],
env: {
HWLAB_BOX_RESOURCES: ["res_boxsimu_1", "res_boxsimu_2"]
HWLAB_GATEWAY_ID: ["gwsimu_1", "gwsimu_2"],
HWLAB_BOX_IDS: ["boxsimu_1", "boxsimu_2"],
HWLAB_BOX_RESOURCES: ["res_boxsimu_1", "res_boxsimu_2"],
HWLAB_BOX_URLS: [
"http://hwlab-box-simu-1.hwlab-dev.svc.cluster.local:7201",
"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201"
]
}
},
"hwlab-patch-panel": {
kind: "Deployment",
replicas: 1,
env: {}
env: {
HWLAB_PATCH_PANEL_ENDPOINT_MAP: m3EndpointMap
}
}
});
const expectedInstanceServices = Object.freeze([
{
serviceId: "hwlab-box-simu",
name: "hwlab-box-simu-1",
instanceId: "res_boxsimu_1",
podName: "hwlab-box-simu-0",
port: 7201
},
{
serviceId: "hwlab-box-simu",
name: "hwlab-box-simu-2",
instanceId: "res_boxsimu_2",
podName: "hwlab-box-simu-1",
port: 7201
},
{
serviceId: "hwlab-gateway-simu",
name: "hwlab-gateway-simu-1",
instanceId: "gwsimu_1",
podName: "hwlab-gateway-simu-0",
port: 7101
},
{
serviceId: "hwlab-gateway-simu",
name: "hwlab-gateway-simu-2",
instanceId: "gwsimu_2",
podName: "hwlab-gateway-simu-1",
port: 7101
}
]);
async function readJson(relativePath) {
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
@@ -45,6 +95,14 @@ function splitList(value) {
.filter(Boolean);
}
function parseEnvValue(value) {
const text = String(value ?? "").trim();
if (text.startsWith("{")) {
return JSON.parse(text);
}
return text;
}
function assertSameMembers(actual, required, label) {
assert.deepEqual([...actual].sort(), [...required].sort(), label);
}
@@ -52,7 +110,15 @@ function assertSameMembers(actual, required, label) {
function workloadByName(workloads) {
return new Map(
listItems(workloads)
.filter((item) => item?.kind === "Deployment")
.filter((item) => item?.kind === "Deployment" || item?.kind === "StatefulSet")
.map((item) => [item.metadata?.name, item])
);
}
function servicesByName(services) {
return new Map(
listItems(services)
.filter((item) => item?.kind === "Service")
.map((item) => [item.metadata?.name, item])
);
}
@@ -62,9 +128,13 @@ function assertDevK8sCardinality(workloads) {
for (const [serviceId, requirement] of Object.entries(expected)) {
const deployment = byName.get(serviceId);
assert.ok(deployment, `deploy/k8s/base/workloads.yaml missing ${serviceId} Deployment`);
assert.ok(deployment, `deploy/k8s/base/workloads.yaml missing ${serviceId} workload`);
assert.equal(deployment.kind, requirement.kind, `${serviceId} workload kind`);
assert.equal(deployment.metadata?.namespace, namespace, `${serviceId} namespace`);
assert.equal(deployment.spec?.replicas, requirement.replicas, `${serviceId} replicas`);
if (requirement.serviceName) {
assert.equal(deployment.spec?.serviceName, requirement.serviceName, `${serviceId} stateful serviceName`);
}
const labels = deployment.metadata?.labels ?? {};
assert.equal(labels["hwlab.pikastech.local/service-id"], serviceId, `${serviceId} service label`);
@@ -74,11 +144,43 @@ function assertDevK8sCardinality(workloads) {
const env = envMap(container.env);
for (const [name, values] of Object.entries(requirement.env)) {
assertSameMembers(splitList(env[name]), values, `${serviceId} ${name}`);
if (Array.isArray(values)) {
assertSameMembers(splitList(env[name]), values, `${serviceId} ${name}`);
} else {
assert.deepEqual(parseEnvValue(env[name]), values, `${serviceId} ${name}`);
}
}
}
}
function assertDevK8sInstanceServices(services) {
const byName = servicesByName(services);
for (const expectedService of expectedInstanceServices) {
const service = byName.get(expectedService.name);
assert.ok(service, `deploy/k8s/base/services.yaml missing ${expectedService.name}`);
assert.equal(service.metadata?.namespace, namespace, `${expectedService.name} namespace`);
assert.equal(
service.metadata?.labels?.["hwlab.pikastech.local/service-id"],
expectedService.serviceId,
`${expectedService.name} service-id label`
);
assert.equal(
service.metadata?.labels?.["hwlab.pikastech.local/instance-id"],
expectedService.instanceId,
`${expectedService.name} instance-id label`
);
assert.equal(
service.spec?.selector?.["statefulset.kubernetes.io/pod-name"],
expectedService.podName,
`${expectedService.name} pod selector`
);
const port = service.spec?.ports?.find((entry) => entry.name === "http") ?? service.spec?.ports?.[0];
assert.equal(port?.port, expectedService.port, `${expectedService.name} port`);
assert.equal(port?.targetPort, "http", `${expectedService.name} targetPort`);
}
}
function assertDeployManifestCardinality(deploy) {
assert.equal(deploy.environment, "dev", "deploy/deploy.json environment");
assert.equal(deploy.namespace, namespace, "deploy/deploy.json namespace");
@@ -91,19 +193,69 @@ function assertDeployManifestCardinality(deploy) {
assert.equal(service.namespace, namespace, `${serviceId} deploy namespace`);
assert.equal(service.profile, "dev", `${serviceId} deploy profile`);
assert.equal(service.replicas, requirement.replicas, `${serviceId} deploy replicas`);
if (requirement.instanceNames) {
assertSameMembers(service.instanceNames ?? [], requirement.instanceNames, `${serviceId} deploy instanceNames`);
assert.equal(service.identityStrategy, "indexed-workloads", `${serviceId} deploy identityStrategy`);
}
for (const [name, values] of Object.entries(requirement.env)) {
assertSameMembers(splitList(service.env?.[name]), values, `${serviceId} deploy ${name}`);
if (Array.isArray(values)) {
assertSameMembers(splitList(service.env?.[name]), values, `${serviceId} deploy ${name}`);
} else {
assert.deepEqual(parseEnvValue(service.env?.[name]), values, `${serviceId} deploy ${name}`);
}
}
}
}
const [deploy, workloads] = await Promise.all([
function assertDeployManifestInstanceMappings(deploy) {
const mappings = deploy.k3s?.serviceMappings ?? [];
const byName = new Map(mappings.map((mapping) => [mapping.name, mapping]));
for (const expectedService of expectedInstanceServices) {
const mapping = byName.get(expectedService.name);
assert.ok(mapping, `deploy/deploy.json missing k3s serviceMapping ${expectedService.name}`);
assert.equal(mapping.serviceId, expectedService.serviceId, `${expectedService.name} serviceId`);
assert.equal(mapping.namespace, namespace, `${expectedService.name} namespace`);
assert.equal(mapping.port, expectedService.port, `${expectedService.name} port`);
assert.equal(mapping.targetPort, "http", `${expectedService.name} targetPort`);
}
}
function assertM3FixtureWiring(topology) {
assert.deepEqual(topology.services?.patchPanel?.activeConnections, [
{
fromResourceId: "res_boxsimu_1",
fromPort: "DO1",
toResourceId: "res_boxsimu_2",
toPort: "DI1"
}
], "M3 fixture active patch-panel wiring");
assert.deepEqual(topology.wiringConfig?.connections, [
{
from: {
resourceId: "res_boxsimu_1",
port: "DO1"
},
to: {
resourceId: "res_boxsimu_2",
port: "DI1"
},
mode: "exclusive"
}
], "M3 fixture wiring config");
}
const [deploy, workloads, services, topology] = await Promise.all([
readJson("deploy/deploy.json"),
readJson("deploy/k8s/base/workloads.yaml")
readJson("deploy/k8s/base/workloads.yaml"),
readJson("deploy/k8s/base/services.yaml"),
readJson("fixtures/mvp/m3-hardware-loop/topology.json")
]);
assertDeployManifestCardinality(deploy);
assertDeployManifestInstanceMappings(deploy);
assertDevK8sCardinality(workloads);
assertDevK8sInstanceServices(services);
assertM3FixtureWiring(topology);
console.log("validated DEV M3 manifest cardinality: 2 box-simu, 2 gateway-simu, 1 patch-panel");
console.log("validated DEV M3 manifest cardinality: 2 distinct box-simu, 2 distinct gateway-simu, 1 patch-panel, M3 DO1->DI1 wiring");