Default new devices to Direct routing
This commit is contained in:
@@ -809,6 +809,25 @@ test('device policy is independent from pinning, persists, and keeps the last ap
|
||||
});
|
||||
const observedAt = '2026-08-07T12:00:00.000Z';
|
||||
const mac = '00:11:22:33:44:55';
|
||||
store.update((state) => ({
|
||||
...state,
|
||||
revision: state.revision + 1,
|
||||
devices: [{
|
||||
id: deviceId(mac),
|
||||
alias: '',
|
||||
pinned: false,
|
||||
deprioritized: false,
|
||||
hostname: null,
|
||||
manufacturer: null,
|
||||
mac,
|
||||
ip: '192.168.50.7',
|
||||
interface: 'eth0',
|
||||
firstSeenAt: observedAt,
|
||||
lastSeenAt: observedAt,
|
||||
source: 'neighbor',
|
||||
confidence: 'high',
|
||||
}],
|
||||
}));
|
||||
let observations = [{
|
||||
ip: '192.168.50.7',
|
||||
mac,
|
||||
@@ -900,14 +919,9 @@ test('device policy is independent from pinning, persists, and keeps the last ap
|
||||
observedAt,
|
||||
active: true,
|
||||
});
|
||||
failApply = true;
|
||||
snapshot = await service.refresh();
|
||||
const secondId = snapshot.devices.find((device) => device.mac === secondMac).id;
|
||||
failApply = true;
|
||||
await assert.rejects(
|
||||
service.setPolicy(secondId, 'direct', snapshot.revision),
|
||||
(error) => error.code === 'DEVICE_POLICY_APPLY_FAILED',
|
||||
);
|
||||
snapshot = service.snapshot();
|
||||
const byId = new Map(snapshot.devices.map((device) => [device.id, device]));
|
||||
assert.equal(byId.get(id).desiredPolicy, 'direct');
|
||||
assert.equal(byId.get(id).appliedPolicy, 'direct');
|
||||
@@ -931,6 +945,167 @@ test('device policy is independent from pinning, persists, and keeps the last ap
|
||||
);
|
||||
});
|
||||
|
||||
test('new devices default to Direct while known and ambiguous devices keep truthful applied routes', async (t) => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-new-device-policy-'));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const observedAt = '2026-08-31T12:00:00.000Z';
|
||||
const knownMac = '00:11:22:33:44:10';
|
||||
const newMac = '00:11:22:33:44:20';
|
||||
const ambiguousMac = '00:11:22:33:44:30';
|
||||
const retainedMac = '00:11:22:33:44:40';
|
||||
const store = createJsonStore({
|
||||
filePath: path.join(directory, 'devices.json'),
|
||||
defaultValue: {},
|
||||
migrate: migrateDeviceInventoryState,
|
||||
initializeMissing: true,
|
||||
});
|
||||
store.update((state) => ({
|
||||
...state,
|
||||
revision: state.revision + 1,
|
||||
devices: [{
|
||||
id: deviceId(knownMac),
|
||||
alias: '',
|
||||
pinned: false,
|
||||
deprioritized: false,
|
||||
hostname: null,
|
||||
manufacturer: null,
|
||||
mac: knownMac,
|
||||
ip: '192.168.50.10',
|
||||
interface: 'eth0',
|
||||
firstSeenAt: '2026-08-30T12:00:00.000Z',
|
||||
lastSeenAt: '2026-08-30T12:00:00.000Z',
|
||||
source: 'neighbor',
|
||||
confidence: 'high',
|
||||
}],
|
||||
policy: {
|
||||
...state.policy,
|
||||
byMac: {
|
||||
[retainedMac]: {
|
||||
desired: 'direct',
|
||||
applied: 'direct',
|
||||
status: 'failed',
|
||||
appliedAt: '2026-08-29T12:00:00.000Z',
|
||||
error: 'cleanup failed',
|
||||
operationId: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
let observations = [
|
||||
{ ip: '192.168.50.10', mac: knownMac, interface: 'eth0', observedAt, active: true },
|
||||
{ ip: '192.168.50.20', mac: newMac, interface: 'eth0', observedAt, active: true },
|
||||
{ ip: '192.168.50.30', mac: ambiguousMac, interface: 'eth0', observedAt, active: true },
|
||||
{ ip: '192.168.50.31', mac: ambiguousMac, interface: 'eth1', observedAt, active: true },
|
||||
{ ip: '192.168.50.40', mac: retainedMac, interface: 'eth0', observedAt, active: true },
|
||||
];
|
||||
let activeDevices = [];
|
||||
let generation = 0;
|
||||
let failApply = false;
|
||||
const appliedSets = [];
|
||||
const policySnapshot = () => ({
|
||||
epoch: 'policy-epoch',
|
||||
generation: `policy-rules-${generation}`,
|
||||
fingerprint: fingerprintDirectDevices(activeDevices),
|
||||
observedAt,
|
||||
appliedIds: activeDevices.map(({ id }) => id),
|
||||
});
|
||||
const createService = () => createDeviceInventoryService({
|
||||
store,
|
||||
observe: () => ({ observedAt, observations, error: null }),
|
||||
observePolicy: policySnapshot,
|
||||
applyPolicies: async (devices) => {
|
||||
appliedSets.push(structuredClone(devices));
|
||||
if (failApply) throw new Error('iptables unavailable');
|
||||
activeDevices = structuredClone(devices);
|
||||
generation += 1;
|
||||
return policySnapshot();
|
||||
},
|
||||
});
|
||||
|
||||
let service = createService();
|
||||
let snapshot = await service.refresh();
|
||||
const byMac = new Map(snapshot.devices.map((device) => [device.mac, device]));
|
||||
assert.equal(byMac.get(knownMac).desiredPolicy, 'vpn');
|
||||
assert.equal(byMac.get(knownMac).appliedPolicy, 'vpn');
|
||||
assert.equal(byMac.get(newMac).desiredPolicy, 'direct');
|
||||
assert.equal(byMac.get(newMac).appliedPolicy, 'direct');
|
||||
assert.equal(byMac.get(ambiguousMac).desiredPolicy, 'direct');
|
||||
assert.equal(byMac.get(ambiguousMac).appliedPolicy, 'vpn');
|
||||
assert.equal(byMac.get(ambiguousMac).policyStatus, 'pending');
|
||||
assert.equal(byMac.get(retainedMac).appliedPolicy, 'direct');
|
||||
assert.deepEqual(appliedSets.at(-1).map(({ mac }) => mac).sort(), [newMac, retainedMac].sort());
|
||||
|
||||
observations = observations.filter(({ mac, interface: deviceInterface }) => (
|
||||
mac !== ambiguousMac || deviceInterface === 'eth0'
|
||||
));
|
||||
snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices.find(({ mac }) => mac === ambiguousMac).appliedPolicy, 'direct');
|
||||
|
||||
const newId = snapshot.devices.find(({ mac }) => mac === newMac).id;
|
||||
snapshot = await service.setPolicy(newId, 'vpn', snapshot.revision);
|
||||
assert.equal(snapshot.devices.find(({ mac }) => mac === newMac).appliedPolicy, 'vpn');
|
||||
service = createService();
|
||||
snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices.find(({ mac }) => mac === newMac).appliedPolicy, 'vpn');
|
||||
|
||||
const reappearedMac = '00:11:22:33:44:50';
|
||||
observations.push({ ip: '192.168.50.50', mac: reappearedMac, interface: 'eth0', observedAt, active: true });
|
||||
failApply = true;
|
||||
snapshot = await service.refresh();
|
||||
const failed = snapshot.devices.find(({ mac }) => mac === reappearedMac);
|
||||
assert.equal(failed.desiredPolicy, 'direct');
|
||||
assert.equal(failed.appliedPolicy, 'vpn');
|
||||
assert.equal(failed.policyStatus, 'failed');
|
||||
failApply = false;
|
||||
service = createService();
|
||||
snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices.find(({ mac }) => mac === reappearedMac).appliedPolicy, 'direct');
|
||||
});
|
||||
|
||||
test('reappearing device preserves retained applied Direct when cleanup acknowledgement failed', async (t) => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-retained-device-policy-'));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const observedAt = '2026-08-31T12:00:00.000Z';
|
||||
const mac = '00:11:22:33:44:60';
|
||||
const store = createJsonStore({
|
||||
filePath: path.join(directory, 'devices.json'),
|
||||
defaultValue: {},
|
||||
migrate: migrateDeviceInventoryState,
|
||||
initializeMissing: true,
|
||||
});
|
||||
store.update((state) => ({
|
||||
...state,
|
||||
policy: {
|
||||
...state.policy,
|
||||
byMac: {
|
||||
[mac]: {
|
||||
desired: 'direct',
|
||||
applied: 'direct',
|
||||
status: 'failed',
|
||||
appliedAt: '2026-08-30T12:00:00.000Z',
|
||||
error: 'cleanup failed',
|
||||
operationId: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
const service = createDeviceInventoryService({
|
||||
store,
|
||||
observe: () => ({
|
||||
observedAt,
|
||||
error: null,
|
||||
observations: [{ ip: '192.168.50.60', mac, interface: 'eth0', observedAt, active: true }],
|
||||
}),
|
||||
applyPolicies: async () => { throw new Error('iptables unavailable'); },
|
||||
});
|
||||
|
||||
const snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices[0].desiredPolicy, 'direct');
|
||||
assert.equal(snapshot.devices[0].appliedPolicy, 'direct');
|
||||
assert.equal(snapshot.devices[0].policyStatus, 'failed');
|
||||
assert.equal(store.read().policy.byMac[mac].appliedAt, '2026-08-30T12:00:00.000Z');
|
||||
});
|
||||
|
||||
test('device inventory v1 migration creates a versioned backup', (t) => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-migration-'));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
|
||||
Reference in New Issue
Block a user