Files
qinglong/packages/ql3-cluster-admin/test/workerCredentialKubernetes.integration.test.cjs

257 lines
7.6 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
WorkerCredentialDeliveryConflictError,
workerCredentialDeliveryTokenDigest,
} = require('@qinglong/runtime-core/worker-credential-delivery');
const {
formatWorkerCredentialToken,
} = require('@qinglong/runtime-core/worker-credential-token');
const {
WorkerCredentialKubernetesDeliveryAdapter,
} = require('@qinglong/cluster-admin/worker-credential-kubernetes-delivery');
const enabled = Boolean(
process.env.QL3_TEST_KUBECONFIG &&
process.env.QL3_TEST_KUBERNETES_NAMESPACE,
);
const integrationTest = enabled ? test : test.skip;
const IDS = [
'223e4567-e89b-42d3-a456-426614174901',
'223e4567-e89b-42d3-a456-426614174902',
'223e4567-e89b-42d3-a456-426614174903',
'223e4567-e89b-42d3-a456-426614174904',
];
function token(credentialId, fill) {
return Buffer.from(formatWorkerCredentialToken(
credentialId,
Buffer.alloc(32, fill).toString('base64url'),
));
}
function intent(adapter, deliveryId, credentialId, material, overrides = {}) {
return {
deliveryId,
workerId: 'integration-worker-1',
credentialId,
credentialVersion: 1,
previousCredentialId: null,
secretDigest: 'b'.repeat(64),
tokenDigest: workerCredentialDeliveryTokenDigest(material),
deploymentTargetDigest: adapter.deploymentTargetDigest,
deploymentGeneration: `generation-${credentialId}`,
stagedAtMs: 1_000,
...overrides,
};
}
function committed(candidate) {
return {
...candidate,
version: 1,
state: 'credential_committed',
credentialCommittedAtMs: candidate.stagedAtMs,
publishedAtMs: null,
publicationDigest: null,
observedAtMs: null,
observedSessionId: null,
observedSessionVersion: null,
previousRevokedAtMs: null,
};
}
integrationTest(
'real Kubernetes API enforces resourceVersion single winner and delete preconditions',
async () => {
const k8s = await import('@kubernetes/client-node');
const config = new k8s.KubeConfig();
config.loadFromFile(process.env.QL3_TEST_KUBECONFIG);
const api = config.makeApiClient(k8s.CoreV1Api);
const deployments = config.makeApiClient(k8s.AppsV1Api);
const namespace = process.env.QL3_TEST_KUBERNETES_NAMESPACE;
const stageNamespace =
process.env.QL3_TEST_KUBERNETES_STAGE_NAMESPACE ??
`${namespace.slice(0, 57)}-stage`;
await api.createNamespace({
body: {
apiVersion: 'v1',
kind: 'Namespace',
metadata: { name: stageNamespace },
},
}).catch((error) => {
if (error?.code !== 409) throw error;
});
await api.createNamespacedSecret({
namespace,
body: {
apiVersion: 'v1',
kind: 'Secret',
type: 'Opaque',
metadata: {
name: 'integration-worker-credential',
labels: {
'app.kubernetes.io/managed-by': 'qinglong3',
'qinglong.io/worker-credential-target': 'prepared-v3',
},
},
data: {},
},
});
await deployments.createNamespacedDeployment({
namespace,
body: {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: 'integration-worker',
namespace,
labels: { 'app.kubernetes.io/component': 'worker' },
},
spec: {
replicas: 1,
strategy: { type: 'Recreate' },
selector: {
matchLabels: {
app: 'integration-worker',
'app.kubernetes.io/component': 'worker',
},
},
template: {
metadata: {
labels: {
app: 'integration-worker',
'app.kubernetes.io/component': 'worker',
},
},
spec: {
containers: [{
name: 'worker',
image: 'registry.k8s.io/pause:3.10.1',
volumeMounts: [{
name: 'credential',
mountPath: '/credential',
readOnly: true,
}],
}],
volumes: [{
name: 'credential',
projected: {
sources: [{
secret: {
name: 'integration-worker-credential',
items: [{
key: 'credential.token',
path: 'credential-token',
}],
},
}],
},
}],
},
},
},
},
});
const adapter = new WorkerCredentialKubernetesDeliveryAdapter(
api,
deployments,
{
clusterIdentity: 'ql3-k3s-integration',
namespace,
stageNamespace,
targetSecretName: 'integration-worker-credential',
targetDeploymentName: 'integration-worker',
targetDataKey: 'credential.token',
},
);
const first = token('integration_generation_1', 1);
const firstIntent = intent(
adapter,
IDS[0],
'integration_generation_1',
first,
);
await adapter.stage(firstIntent, first);
const initial = await adapter.publish(committed(firstIntent));
assert.match(initial.publicationDigest, /^[0-9a-f]{64}$/);
const initialTarget = await api.readNamespacedSecret({
namespace,
name: 'integration-worker-credential',
});
const second = token('integration_generation_2', 2);
const third = token('integration_generation_3', 3);
const secondIntent = intent(
adapter,
IDS[1],
'integration_generation_2',
second,
{ previousCredentialId: 'integration_generation_1' },
);
const thirdIntent = intent(
adapter,
IDS[2],
'integration_generation_3',
third,
{ previousCredentialId: 'integration_generation_1' },
);
await adapter.stage(secondIntent, second);
await adapter.stage(thirdIntent, third);
const results = await Promise.allSettled([
adapter.publish(committed(secondIntent)),
adapter.publish(committed(thirdIntent)),
]);
assert.equal(
results.filter((result) => result.status === 'fulfilled').length,
1,
);
const rejected = results.find((result) => result.status === 'rejected');
assert.ok(rejected.reason instanceof WorkerCredentialDeliveryConflictError);
const winner = results[0].status === 'fulfilled' ? secondIntent : thirdIntent;
const target = await api.readNamespacedSecret({
namespace,
name: 'integration-worker-credential',
});
assert.notEqual(
target.metadata.resourceVersion,
initialTarget.metadata.resourceVersion,
);
assert.equal(
target.metadata.annotations['qinglong.io/worker-credential-delivery-id'],
winner.deliveryId,
);
const deployment = await deployments.readNamespacedDeployment({
namespace,
name: 'integration-worker',
});
assert.equal(
deployment.spec.template.metadata.annotations[
'qinglong.io/worker-credential-generation'
],
winner.deploymentGeneration,
);
const orphan = token('integration_orphan', 4);
const orphanIntent = intent(
adapter,
IDS[3],
'integration_orphan',
orphan,
);
await adapter.stage(orphanIntent, orphan);
await adapter.discard(orphanIntent);
assert.equal(await adapter.inspect(orphanIntent.deliveryId), null);
const page = await adapter.listStaged({ limit: 4 });
assert.equal(page.stages.some((item) =>
item.deliveryId === orphanIntent.deliveryId), false);
first.fill(0);
second.fill(0);
third.fill(0);
orphan.fill(0);
},
);