mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:38:14 +08:00
257 lines
7.6 KiB
JavaScript
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);
|
|
},
|
|
);
|