mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 20:15:19 +08:00
fix(ci): bind provider evidence to rollout generation
This commit is contained in:
@@ -289,6 +289,34 @@ function providerPods(fixture) {
|
|||||||
]).items;
|
]).items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function readyProviderPodForGeneration(
|
||||||
|
fixture,
|
||||||
|
expectedGeneration,
|
||||||
|
timeoutMs = 60_000,
|
||||||
|
) {
|
||||||
|
assert.match(expectedGeneration, /^[1-9]\d*-[a-z0-9]+(?:-[a-z0-9]+)*$/);
|
||||||
|
return (
|
||||||
|
await waitFor(
|
||||||
|
`provider generation ${expectedGeneration}`,
|
||||||
|
timeoutMs,
|
||||||
|
() => {
|
||||||
|
const ready = providerPods(fixture).filter(podReady);
|
||||||
|
const matching = ready.filter(
|
||||||
|
(pod) =>
|
||||||
|
pod.metadata.annotations?.['qinglong.io/provider-generation'] ===
|
||||||
|
expectedGeneration,
|
||||||
|
);
|
||||||
|
return matching.length === 1
|
||||||
|
? { ready: true, value: matching[0] }
|
||||||
|
: {
|
||||||
|
ready: false,
|
||||||
|
fact: `${matching.length} matching of ${ready.length} Ready provider Pods`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
).value;
|
||||||
|
}
|
||||||
|
|
||||||
function providerObservationKey(pod) {
|
function providerObservationKey(pod) {
|
||||||
assert.match(pod?.metadata?.uid ?? '', /^[A-Za-z0-9][A-Za-z0-9._-]+$/);
|
assert.match(pod?.metadata?.uid ?? '', /^[A-Za-z0-9][A-Za-z0-9._-]+$/);
|
||||||
const provider = pod?.status?.containerStatuses?.find(
|
const provider = pod?.status?.containerStatuses?.find(
|
||||||
@@ -421,6 +449,7 @@ const server=https.createServer({key:fs.readFileSync('/var/run/provider/tls.key'
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deployProvider(fixture, adminImage, generation, value, nonce) {
|
async function deployProvider(fixture, adminImage, generation, value, nonce) {
|
||||||
|
const expectedGeneration = `${generation}-${nonce}`;
|
||||||
applySecret(fixture, 'ql3-provider-live-authority', 'Opaque', {
|
applySecret(fixture, 'ql3-provider-live-authority', 'Opaque', {
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
@@ -439,7 +468,7 @@ async function deployProvider(fixture, adminImage, generation, value, nonce) {
|
|||||||
metadata: {
|
metadata: {
|
||||||
labels: { 'app.kubernetes.io/name': PROVIDER_NAME },
|
labels: { 'app.kubernetes.io/name': PROVIDER_NAME },
|
||||||
annotations: {
|
annotations: {
|
||||||
'qinglong.io/provider-generation': `${generation}-${nonce}`,
|
'qinglong.io/provider-generation': expectedGeneration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
@@ -542,10 +571,12 @@ async function deployProvider(fixture, adminImage, generation, value, nonce) {
|
|||||||
`deployment/${PROVIDER_NAME}`,
|
`deployment/${PROVIDER_NAME}`,
|
||||||
'--timeout=3m',
|
'--timeout=3m',
|
||||||
]);
|
]);
|
||||||
const ready = providerPods(fixture).filter(podReady);
|
const ready = await readyProviderPodForGeneration(
|
||||||
assert.equal(ready.length, 1);
|
fixture,
|
||||||
assert.match(ready[0].status.podIP, /^\d{1,3}(?:\.\d{1,3}){3}$/);
|
expectedGeneration,
|
||||||
return ready[0];
|
);
|
||||||
|
assert.match(ready.status.podIP, /^\d{1,3}(?:\.\d{1,3}){3}$/);
|
||||||
|
return ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForJob(fixture, name, timeoutMs = 120_000) {
|
async function waitForJob(fixture, name, timeoutMs = 120_000) {
|
||||||
@@ -1091,8 +1122,9 @@ async function retryProviderEvidence(read, pause = undefined) {
|
|||||||
) {
|
) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
await (pause ??
|
await (
|
||||||
(() => new Promise((resolve) => setTimeout(resolve, 500))))();
|
pause ?? (() => new Promise((resolve) => setTimeout(resolve, 500)))
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw lastError;
|
throw lastError;
|
||||||
@@ -1630,29 +1662,42 @@ async function main() {
|
|||||||
let observedProviderRequests = 0;
|
let observedProviderRequests = 0;
|
||||||
let evidenceSequence = 0;
|
let evidenceSequence = 0;
|
||||||
const observeProvider = async (pod, sourceNodeName) => {
|
const observeProvider = async (pod, sourceNodeName) => {
|
||||||
applyExecutorNetworkPolicy(fixture, pod.status.podIP);
|
const expectedGeneration =
|
||||||
|
pod.metadata.annotations?.['qinglong.io/provider-generation'];
|
||||||
|
assert.match(
|
||||||
|
expectedGeneration ?? '',
|
||||||
|
/^[1-9]\d*-[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
||||||
|
);
|
||||||
|
let currentPod;
|
||||||
const evidence = await retryProviderEvidence(async () => {
|
const evidence = await retryProviderEvidence(async () => {
|
||||||
|
currentPod = await readyProviderPodForGeneration(
|
||||||
|
fixture,
|
||||||
|
expectedGeneration,
|
||||||
|
);
|
||||||
|
applyExecutorNetworkPolicy(fixture, currentPod.status.podIP);
|
||||||
evidenceSequence += 1;
|
evidenceSequence += 1;
|
||||||
return providerEvidence({
|
return providerEvidence({
|
||||||
fixture,
|
fixture,
|
||||||
adminImage,
|
adminImage,
|
||||||
name: `ql3-provider-evidence-${evidenceSequence}`,
|
name: `ql3-provider-evidence-${evidenceSequence}`,
|
||||||
nodeName: sourceNodeName,
|
nodeName: sourceNodeName,
|
||||||
providerPodIp: pod.status.podIP,
|
providerPodIp: currentPod.status.podIP,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
assert.ok(currentPod);
|
||||||
const count = evidence.requestCount;
|
const count = evidence.requestCount;
|
||||||
const observationKey = providerObservationKey(pod);
|
const observationKey = providerObservationKey(currentPod);
|
||||||
const previous = requestObservations.get(observationKey) ?? 0;
|
const previous = requestObservations.get(observationKey) ?? 0;
|
||||||
assert.ok(count >= previous);
|
assert.ok(count >= previous);
|
||||||
observedProviderRequests += count - previous;
|
observedProviderRequests += count - previous;
|
||||||
requestObservations.set(observationKey, count);
|
requestObservations.set(observationKey, count);
|
||||||
|
return currentPod;
|
||||||
};
|
};
|
||||||
|
|
||||||
applyExecutorNetworkPolicy(fixture, null);
|
applyExecutorNetworkPolicy(fixture, null);
|
||||||
const baseIdentity = await plan('base-denied');
|
const baseIdentity = await plan('base-denied');
|
||||||
const baseDenied = await execute('base-denied', baseIdentity);
|
const baseDenied = await execute('base-denied', baseIdentity);
|
||||||
await observeProvider(providerPod, baseDenied.nodeName);
|
providerPod = await observeProvider(providerPod, baseDenied.nodeName);
|
||||||
assert.equal(observedProviderRequests, 0);
|
assert.equal(observedProviderRequests, 0);
|
||||||
assert.equal(baseDenied.outcome, 'unreachable');
|
assert.equal(baseDenied.outcome, 'unreachable');
|
||||||
assert.equal(baseDenied.modelCount, null);
|
assert.equal(baseDenied.modelCount, null);
|
||||||
@@ -1668,7 +1713,7 @@ async function main() {
|
|||||||
const exactAllowed = await execute('exact-allowed', allowedIdentity);
|
const exactAllowed = await execute('exact-allowed', allowedIdentity);
|
||||||
const exactReplay = await execute('exact-replay', allowedIdentity);
|
const exactReplay = await execute('exact-replay', allowedIdentity);
|
||||||
assert.equal(exactReplay.status, 'existing');
|
assert.equal(exactReplay.status, 'existing');
|
||||||
await observeProvider(providerPod, exactAllowed.nodeName);
|
providerPod = await observeProvider(providerPod, exactAllowed.nodeName);
|
||||||
const exactCredentialAuditCount = Number(
|
const exactCredentialAuditCount = Number(
|
||||||
psql(
|
psql(
|
||||||
fixture,
|
fixture,
|
||||||
@@ -1723,7 +1768,7 @@ async function main() {
|
|||||||
});
|
});
|
||||||
const rotatedIdentity = await plan('rotated-material');
|
const rotatedIdentity = await plan('rotated-material');
|
||||||
const rotatedMaterial = await execute('rotated-material', rotatedIdentity);
|
const rotatedMaterial = await execute('rotated-material', rotatedIdentity);
|
||||||
await observeProvider(providerPod, rotatedMaterial.nodeName);
|
providerPod = await observeProvider(providerPod, rotatedMaterial.nodeName);
|
||||||
assert.equal(observedProviderRequests, 3);
|
assert.equal(observedProviderRequests, 3);
|
||||||
assert.equal(staleMaterial.outcome, 'unreachable');
|
assert.equal(staleMaterial.outcome, 'unreachable');
|
||||||
assert.equal(staleMaterial.modelCount, null);
|
assert.equal(staleMaterial.modelCount, null);
|
||||||
@@ -1741,7 +1786,7 @@ async function main() {
|
|||||||
assert.notEqual(providerPod.status.podIP, cidrBeforeReplacement);
|
assert.notEqual(providerPod.status.podIP, cidrBeforeReplacement);
|
||||||
const staleCidrIdentity = await plan('stale-cidr');
|
const staleCidrIdentity = await plan('stale-cidr');
|
||||||
const staleCidr = await execute('stale-cidr', staleCidrIdentity);
|
const staleCidr = await execute('stale-cidr', staleCidrIdentity);
|
||||||
await observeProvider(providerPod, staleCidr.nodeName);
|
providerPod = await observeProvider(providerPod, staleCidr.nodeName);
|
||||||
assert.equal(observedProviderRequests, 3);
|
assert.equal(observedProviderRequests, 3);
|
||||||
assert.equal(staleCidr.outcome, 'unreachable');
|
assert.equal(staleCidr.outcome, 'unreachable');
|
||||||
assert.equal(staleCidr.modelCount, null);
|
assert.equal(staleCidr.modelCount, null);
|
||||||
@@ -1754,7 +1799,7 @@ async function main() {
|
|||||||
});
|
});
|
||||||
const refreshedIdentity = await plan('refreshed-cidr');
|
const refreshedIdentity = await plan('refreshed-cidr');
|
||||||
const refreshedCidr = await execute('refreshed-cidr', refreshedIdentity);
|
const refreshedCidr = await execute('refreshed-cidr', refreshedIdentity);
|
||||||
await observeProvider(providerPod, refreshedCidr.nodeName);
|
providerPod = await observeProvider(providerPod, refreshedCidr.nodeName);
|
||||||
assert.equal(observedProviderRequests, 4);
|
assert.equal(observedProviderRequests, 4);
|
||||||
assert.equal(refreshedCidr.outcome, 'reachable');
|
assert.equal(refreshedCidr.outcome, 'reachable');
|
||||||
assert.equal(refreshedCidr.modelCount, 2);
|
assert.equal(refreshedCidr.modelCount, 2);
|
||||||
@@ -1852,7 +1897,7 @@ async function main() {
|
|||||||
});
|
});
|
||||||
const postFailoverIdentity = await plan('post-failover');
|
const postFailoverIdentity = await plan('post-failover');
|
||||||
const postFailover = await execute('post-failover', postFailoverIdentity);
|
const postFailover = await execute('post-failover', postFailoverIdentity);
|
||||||
await observeProvider(providerPod, postFailover.nodeName);
|
providerPod = await observeProvider(providerPod, postFailover.nodeName);
|
||||||
assert.equal(observedProviderRequests, 5);
|
assert.equal(observedProviderRequests, 5);
|
||||||
assert.equal(postFailover.outcome, 'reachable');
|
assert.equal(postFailover.outcome, 'reachable');
|
||||||
assert.equal(postFailover.modelCount, 2);
|
assert.equal(postFailover.modelCount, 2);
|
||||||
@@ -2090,6 +2135,7 @@ module.exports = {
|
|||||||
executorJob,
|
executorJob,
|
||||||
providerObservationKey,
|
providerObservationKey,
|
||||||
providerServerSource,
|
providerServerSource,
|
||||||
|
readyProviderPodForGeneration,
|
||||||
retryProviderEvidence,
|
retryProviderEvidence,
|
||||||
terminalJobSnapshot,
|
terminalJobSnapshot,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const {
|
|||||||
executorJob,
|
executorJob,
|
||||||
providerObservationKey,
|
providerObservationKey,
|
||||||
providerServerSource,
|
providerServerSource,
|
||||||
|
readyProviderPodForGeneration,
|
||||||
retryProviderEvidence,
|
retryProviderEvidence,
|
||||||
terminalJobSnapshot,
|
terminalJobSnapshot,
|
||||||
} = require('../../scripts/ql3-provider-credential-test-kubernetes-live-contract.cjs');
|
} = require('../../scripts/ql3-provider-credential-test-kubernetes-live-contract.cjs');
|
||||||
@@ -341,10 +342,14 @@ test('reads provider evidence from the exact ready Pod with trusted TLS SNI', ()
|
|||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.match(source, /providerPodIp: pod\.status\.podIP/);
|
assert.match(source, /currentPod = await readyProviderPodForGeneration/);
|
||||||
|
assert.match(source, /providerPodIp: currentPod\.status\.podIP/);
|
||||||
assert.match(source, /host:process\.argv\[1\]/);
|
assert.match(source, /host:process\.argv\[1\]/);
|
||||||
assert.match(source, /servername:process\.argv\[3\]/);
|
assert.match(source, /servername:process\.argv\[3\]/);
|
||||||
assert.match(source, /ca:fs\.readFileSync\('\/var\/run\/provider-ca\/ca\.crt'\)/);
|
assert.match(
|
||||||
|
source,
|
||||||
|
/ca:fs\.readFileSync\('\/var\/run\/provider-ca\/ca\.crt'\)/,
|
||||||
|
);
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
source,
|
source,
|
||||||
/fetch\('https:\/\/'\+process\.argv\[1\].*\/evidence/,
|
/fetch\('https:\/\/'\+process\.argv\[1\].*\/evidence/,
|
||||||
@@ -403,7 +408,12 @@ test('provider fixture uses headless DNS for exact Pod CIDR policy', async () =>
|
|||||||
return {
|
return {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
metadata: { uid: 'provider-uid' },
|
metadata: {
|
||||||
|
uid: 'provider-uid',
|
||||||
|
annotations: {
|
||||||
|
'qinglong.io/provider-generation': '1-initial',
|
||||||
|
},
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
podIP: '10.42.7.19',
|
podIP: '10.42.7.19',
|
||||||
conditions: [{ type: 'Ready', status: 'True' }],
|
conditions: [{ type: 'Ready', status: 'True' }],
|
||||||
@@ -420,6 +430,50 @@ test('provider fixture uses headless DNS for exact Pod CIDR policy', async () =>
|
|||||||
assert.equal(service.spec.clusterIP, 'None');
|
assert.equal(service.spec.clusterIP, 'None');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('binds a rollout to the exact ready provider generation', async () => {
|
||||||
|
const fixture = {
|
||||||
|
kubectlJson() {
|
||||||
|
return {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
uid: 'old-provider',
|
||||||
|
annotations: {
|
||||||
|
'qinglong.io/provider-generation': '1-initial',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
podIP: '10.42.7.18',
|
||||||
|
conditions: [{ type: 'Ready', status: 'True' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
uid: 'current-provider',
|
||||||
|
annotations: {
|
||||||
|
'qinglong.io/provider-generation': '2-material-rotation',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
podIP: '10.42.7.19',
|
||||||
|
conditions: [{ type: 'Ready', status: 'True' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const provider = await readyProviderPodForGeneration(
|
||||||
|
fixture,
|
||||||
|
'2-material-rotation',
|
||||||
|
1_000,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(provider.metadata.uid, 'current-provider');
|
||||||
|
assert.equal(provider.status.podIP, '10.42.7.19');
|
||||||
|
});
|
||||||
|
|
||||||
test('actor writes bounded failure diagnostics to its termination message', () => {
|
test('actor writes bounded failure diagnostics to its termination message', () => {
|
||||||
const source = fs.readFileSync(
|
const source = fs.readFileSync(
|
||||||
path.join(
|
path.join(
|
||||||
|
|||||||
Reference in New Issue
Block a user