fix: map M3 indexed service identities
Host closeout: reviewed source/test diff and clean merge state. Accepting only the EndpointSlice identity mapping and regression test. The runner's live-mutating M3 smoke evidence is excluded from acceptance because the task prompt forbade live mutation; M3 acceptance remains separate and requires explicit authorized live validation.
This commit is contained in:
@@ -76,6 +76,39 @@ const serviceTargets = Object.freeze([
|
||||
}
|
||||
]);
|
||||
|
||||
const kubernetesM3ServiceIdentities = Object.freeze({
|
||||
"hwlab-box-simu": Object.freeze({
|
||||
serviceId: "hwlab-box-simu"
|
||||
}),
|
||||
"hwlab-box-simu-1": Object.freeze({
|
||||
serviceId: "hwlab-box-simu",
|
||||
targetId: "box-simu-1",
|
||||
instanceId: "res_boxsimu_1"
|
||||
}),
|
||||
"hwlab-box-simu-2": Object.freeze({
|
||||
serviceId: "hwlab-box-simu",
|
||||
targetId: "box-simu-2",
|
||||
instanceId: "res_boxsimu_2"
|
||||
}),
|
||||
"hwlab-gateway-simu": Object.freeze({
|
||||
serviceId: "hwlab-gateway-simu"
|
||||
}),
|
||||
"hwlab-gateway-simu-1": Object.freeze({
|
||||
serviceId: "hwlab-gateway-simu",
|
||||
targetId: "gateway-simu-1",
|
||||
instanceId: "gwsimu_1"
|
||||
}),
|
||||
"hwlab-gateway-simu-2": Object.freeze({
|
||||
serviceId: "hwlab-gateway-simu",
|
||||
targetId: "gateway-simu-2",
|
||||
instanceId: "gwsimu_2"
|
||||
}),
|
||||
"hwlab-patch-panel": Object.freeze({
|
||||
serviceId: "hwlab-patch-panel",
|
||||
targetId: "patch-panel"
|
||||
})
|
||||
});
|
||||
|
||||
function parseArgs(argv) {
|
||||
const flags = new Set();
|
||||
const values = new Map();
|
||||
@@ -744,6 +777,22 @@ function serviceDefinitionByServiceId(serviceId) {
|
||||
return serviceTargets.find((target) => target.serviceId === serviceId);
|
||||
}
|
||||
|
||||
function kubernetesServiceIdentity(item) {
|
||||
const serviceName = item?.metadata?.labels?.["kubernetes.io/service-name"];
|
||||
const mapped = kubernetesM3ServiceIdentities[serviceName];
|
||||
if (!mapped) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labels = item?.metadata?.labels ?? {};
|
||||
return {
|
||||
serviceName,
|
||||
serviceId: mapped.serviceId,
|
||||
targetId: mapped.targetId ?? null,
|
||||
instanceId: labels["hwlab.pikastech.local/instance-id"] ?? mapped.instanceId ?? null
|
||||
};
|
||||
}
|
||||
|
||||
function serviceEndpointUrl(address, port) {
|
||||
return `http://${address}:${port}`;
|
||||
}
|
||||
@@ -795,14 +844,14 @@ function collectKubernetesDirectTargetDiscovery() {
|
||||
};
|
||||
}
|
||||
|
||||
const requiredServices = new Set(["hwlab-box-simu", "hwlab-gateway-simu", "hwlab-patch-panel"]);
|
||||
const services = {};
|
||||
|
||||
for (const item of document.items ?? []) {
|
||||
const serviceId = item.metadata?.labels?.["kubernetes.io/service-name"];
|
||||
if (!requiredServices.has(serviceId)) {
|
||||
const identity = kubernetesServiceIdentity(item);
|
||||
if (!identity) {
|
||||
continue;
|
||||
}
|
||||
const { serviceName, serviceId, targetId, instanceId } = identity;
|
||||
const port = item.ports?.find((entry) => entry.name === "http")?.port ?? item.ports?.[0]?.port;
|
||||
if (!Number.isInteger(port)) {
|
||||
continue;
|
||||
@@ -817,12 +866,20 @@ function collectKubernetesDirectTargetDiscovery() {
|
||||
url: serviceEndpointUrl(address, port),
|
||||
address,
|
||||
port,
|
||||
targetRef: endpoint.targetRef?.name ?? "unknown"
|
||||
targetRef: endpoint.targetRef?.name ?? "unknown",
|
||||
serviceName,
|
||||
serviceId,
|
||||
targetId,
|
||||
instanceId
|
||||
});
|
||||
}
|
||||
}
|
||||
services[serviceId] = {
|
||||
services[serviceName] = {
|
||||
endpointSlice: item.metadata?.name ?? "unknown",
|
||||
serviceName,
|
||||
serviceId,
|
||||
targetId,
|
||||
instanceId,
|
||||
port,
|
||||
count: endpoints.length,
|
||||
endpoints
|
||||
@@ -841,7 +898,8 @@ function collectKubernetesDirectTargetDiscovery() {
|
||||
|
||||
function candidateTargetsFromKubernetes(discovery) {
|
||||
const candidates = [];
|
||||
for (const [serviceId, service] of Object.entries(discovery.services ?? {})) {
|
||||
for (const [serviceName, service] of Object.entries(discovery.services ?? {})) {
|
||||
const serviceId = service.serviceId ?? serviceName;
|
||||
const definition = serviceDefinitionByServiceId(serviceId);
|
||||
if (!definition) {
|
||||
continue;
|
||||
@@ -850,9 +908,11 @@ function candidateTargetsFromKubernetes(discovery) {
|
||||
for (const endpoint of service.endpoints ?? []) {
|
||||
candidates.push({
|
||||
...definition,
|
||||
id: `${definition.id}-candidate-${index}`,
|
||||
id: service.targetId ?? endpoint.targetId ?? `${definition.id}-candidate-${index}`,
|
||||
baseUrl: endpoint.url,
|
||||
discoverySource: discovery.source,
|
||||
serviceName,
|
||||
instanceId: endpoint.instanceId ?? service.instanceId ?? null,
|
||||
targetRef: endpoint.targetRef,
|
||||
address: endpoint.address,
|
||||
port: endpoint.port
|
||||
@@ -883,6 +943,8 @@ function summarizeCandidate(candidate) {
|
||||
return {
|
||||
id: candidate.id,
|
||||
serviceId: candidate.serviceId,
|
||||
serviceName: candidate.serviceName,
|
||||
instanceId: candidate.instanceId,
|
||||
baseUrl: candidate.baseUrl,
|
||||
targetRef: candidate.targetRef,
|
||||
ok: candidate.ok,
|
||||
@@ -1719,8 +1781,11 @@ async function main() {
|
||||
export {
|
||||
classifyDirectTargetBlockerScope,
|
||||
classifyLiveOperationError,
|
||||
candidateTargetsFromKubernetes,
|
||||
createLiveOperationPlan,
|
||||
collectKubernetesDirectTargetDiscovery,
|
||||
hasRequiredM3Connection,
|
||||
kubernetesServiceIdentity,
|
||||
m3FailureClassifications,
|
||||
parseArgs,
|
||||
requireSafetyGates
|
||||
|
||||
@@ -6,9 +6,11 @@ import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
candidateTargetsFromKubernetes,
|
||||
classifyDirectTargetBlockerScope,
|
||||
createLiveOperationPlan,
|
||||
hasRequiredM3Connection,
|
||||
kubernetesServiceIdentity,
|
||||
parseArgs,
|
||||
requireSafetyGates
|
||||
} from "./dev-m3-hardware-loop-smoke.mjs";
|
||||
@@ -153,3 +155,89 @@ test("patch-panel route preflight rejects missing res_boxsimu_1 DO1 to res_boxsi
|
||||
assert.equal(Boolean(requiredRoute.active), true);
|
||||
assert.equal(Boolean(requiredRoute.configured), true);
|
||||
});
|
||||
|
||||
test("Kubernetes discovery preserves current indexed M3 service identities", () => {
|
||||
const endpointSlice = (serviceName, address, port, targetRef, labels = {}) => ({
|
||||
metadata: {
|
||||
name: `${serviceName}-slice`,
|
||||
labels: {
|
||||
"kubernetes.io/service-name": serviceName,
|
||||
...labels
|
||||
}
|
||||
},
|
||||
ports: [{ name: "http", port }],
|
||||
endpoints: [
|
||||
{
|
||||
addresses: [address],
|
||||
conditions: { ready: true },
|
||||
targetRef: { name: targetRef }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const discovery = {
|
||||
status: "observed",
|
||||
source: "kubernetes-endpointslices",
|
||||
services: Object.fromEntries(
|
||||
[
|
||||
endpointSlice("hwlab-box-simu-1", "10.0.0.11", 7201, "hwlab-box-simu-0", {
|
||||
"hwlab.pikastech.local/instance-id": "res_boxsimu_1"
|
||||
}),
|
||||
endpointSlice("hwlab-box-simu-2", "10.0.0.12", 7201, "hwlab-box-simu-1", {
|
||||
"hwlab.pikastech.local/instance-id": "res_boxsimu_2"
|
||||
}),
|
||||
endpointSlice("hwlab-gateway-simu-1", "10.0.0.21", 7101, "hwlab-gateway-simu-0", {
|
||||
"hwlab.pikastech.local/instance-id": "gwsimu_1"
|
||||
}),
|
||||
endpointSlice("hwlab-gateway-simu-2", "10.0.0.22", 7101, "hwlab-gateway-simu-1", {
|
||||
"hwlab.pikastech.local/instance-id": "gwsimu_2"
|
||||
}),
|
||||
endpointSlice("hwlab-patch-panel", "10.0.0.31", 7301, "hwlab-patch-panel-abc")
|
||||
].map((item) => {
|
||||
const identity = kubernetesServiceIdentity(item);
|
||||
const port = item.ports[0].port;
|
||||
const endpoints = item.endpoints.flatMap((endpoint) =>
|
||||
endpoint.addresses.map((address) => ({
|
||||
url: `http://${address}:${port}`,
|
||||
address,
|
||||
port,
|
||||
targetRef: endpoint.targetRef.name,
|
||||
serviceName: identity.serviceName,
|
||||
serviceId: identity.serviceId,
|
||||
targetId: identity.targetId,
|
||||
instanceId: identity.instanceId
|
||||
}))
|
||||
);
|
||||
return [
|
||||
identity.serviceName,
|
||||
{
|
||||
endpointSlice: item.metadata.name,
|
||||
...identity,
|
||||
port,
|
||||
count: endpoints.length,
|
||||
endpoints
|
||||
}
|
||||
];
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
const candidates = candidateTargetsFromKubernetes(discovery);
|
||||
|
||||
assert.deepEqual(
|
||||
candidates.map((candidate) => [
|
||||
candidate.id,
|
||||
candidate.serviceName,
|
||||
candidate.serviceId,
|
||||
candidate.instanceId,
|
||||
candidate.baseUrl
|
||||
]),
|
||||
[
|
||||
["box-simu-1", "hwlab-box-simu-1", "hwlab-box-simu", "res_boxsimu_1", "http://10.0.0.11:7201"],
|
||||
["box-simu-2", "hwlab-box-simu-2", "hwlab-box-simu", "res_boxsimu_2", "http://10.0.0.12:7201"],
|
||||
["gateway-simu-1", "hwlab-gateway-simu-1", "hwlab-gateway-simu", "gwsimu_1", "http://10.0.0.21:7101"],
|
||||
["gateway-simu-2", "hwlab-gateway-simu-2", "hwlab-gateway-simu", "gwsimu_2", "http://10.0.0.22:7101"],
|
||||
["patch-panel", "hwlab-patch-panel", "hwlab-patch-panel", null, "http://10.0.0.31:7301"]
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user