mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-27 17:18:52 +00:00
96c68166a1
Change-type: patch Signed-off-by: Rich Bayliss <rich@balena.io> Signed-off-by: Cameron Diver <cameron@balena.io>
264 lines
7.3 KiB
TypeScript
264 lines
7.3 KiB
TypeScript
import * as chai from 'chai';
|
|
import { expect } from 'chai';
|
|
import * as chaiThings from 'chai-things';
|
|
import * as chaiLike from 'chai-like';
|
|
import _ = require('lodash');
|
|
|
|
import * as dbFormat from '../src/device-state/db-format';
|
|
import * as appMock from './lib/application-state-mock';
|
|
import * as mockedDockerode from './lib/mocked-dockerode';
|
|
|
|
import * as applicationManager from '../src/compose/application-manager';
|
|
import * as config from '../src/config';
|
|
import * as deviceState from '../src/device-state';
|
|
|
|
import Service from '../src/compose/service';
|
|
import Network from '../src/compose/network';
|
|
|
|
import prepare = require('./lib/prepare');
|
|
import { intialiseContractRequirements } from '../src/lib/contracts';
|
|
|
|
chai.use(chaiLike);
|
|
chai.use(chaiThings);
|
|
|
|
describe('compose/application-manager', () => {
|
|
before(async () => {
|
|
await config.initialized;
|
|
await dbFormat.setApps({}, 'test');
|
|
});
|
|
beforeEach(() => {
|
|
appMock.mockSupervisorNetwork(true);
|
|
});
|
|
afterEach(() => {
|
|
appMock.unmockAll();
|
|
});
|
|
|
|
it('should create an App from current state', async () => {
|
|
appMock.mockManagers(
|
|
[
|
|
Service.fromDockerContainer(
|
|
require('./data/docker-states/simple/inspect.json'),
|
|
),
|
|
],
|
|
[],
|
|
[],
|
|
);
|
|
|
|
const apps = await applicationManager.getCurrentApps();
|
|
expect(Object.keys(apps)).to.have.length(1);
|
|
const app = apps[1011165];
|
|
expect(app).to.have.property('appId').that.equals(1011165);
|
|
expect(app).to.have.property('services');
|
|
const services = _.keyBy(app.services, 'serviceId');
|
|
expect(services).to.have.property('43697');
|
|
expect(services[43697]).to.have.property('serviceName').that.equals('main');
|
|
});
|
|
|
|
it('should create multiple Apps when the current state reflects that', async () => {
|
|
appMock.mockManagers(
|
|
[
|
|
Service.fromDockerContainer(
|
|
require('./data/docker-states/simple/inspect.json'),
|
|
),
|
|
],
|
|
[],
|
|
[
|
|
Network.fromDockerNetwork(
|
|
require('./data/docker-states/networks/1623449_default.json'),
|
|
),
|
|
],
|
|
);
|
|
|
|
const apps = await applicationManager.getCurrentApps();
|
|
expect(Object.keys(apps)).to.deep.equal(['1011165', '1623449']);
|
|
});
|
|
|
|
it('should infer that we need to create the supervisor network if it does not exist', async () => {
|
|
appMock.mockSupervisorNetwork(false);
|
|
appMock.mockManagers([], [], []);
|
|
appMock.mockImages([], false, []);
|
|
|
|
const target = await deviceState.getTarget();
|
|
|
|
const steps = await applicationManager.getRequiredSteps(target.local.apps);
|
|
expect(steps).to.have.length(1);
|
|
expect(steps[0])
|
|
.to.have.property('action')
|
|
.that.equals('ensureSupervisorNetwork');
|
|
});
|
|
|
|
it('should kill a service which depends on the supervisor network, if we need to create the network', async () => {
|
|
appMock.mockSupervisorNetwork(false);
|
|
appMock.mockManagers(
|
|
[
|
|
Service.fromDockerContainer(
|
|
require('./data/docker-states/supervisor-api/inspect.json'),
|
|
),
|
|
],
|
|
[],
|
|
[],
|
|
);
|
|
appMock.mockImages([], false, []);
|
|
const target = await deviceState.getTarget();
|
|
|
|
const steps = await applicationManager.getRequiredSteps(target.local.apps);
|
|
|
|
expect(steps).to.have.length(1);
|
|
expect(steps[0]).to.have.property('action').that.equals('kill');
|
|
expect(steps[0])
|
|
.to.have.property('current')
|
|
.that.has.property('serviceName')
|
|
.that.equals('main');
|
|
});
|
|
|
|
it('should infer a cleanup step when a cleanup is required', async () => {
|
|
appMock.mockManagers([], [], []);
|
|
appMock.mockImages([], true, []);
|
|
|
|
const target = await deviceState.getTarget();
|
|
|
|
const steps = await applicationManager.getRequiredSteps(target.local.apps);
|
|
expect(steps).to.have.length(1);
|
|
expect(steps[0]).to.have.property('action').that.equals('cleanup');
|
|
});
|
|
|
|
it('should infer that an image should be removed if it is no longer referenced in current or target state', async () => {
|
|
appMock.mockManagers([], [], []);
|
|
appMock.mockImages([], false, [
|
|
{
|
|
name: 'registry2.balena-cloud.com/v2/asdasdasdasd@sha256:10',
|
|
appId: 1,
|
|
serviceId: 1,
|
|
serviceName: 'test',
|
|
imageId: 10,
|
|
dependent: 0,
|
|
releaseId: 4,
|
|
},
|
|
]);
|
|
|
|
const target = await deviceState.getTarget();
|
|
|
|
const steps = await applicationManager.getRequiredSteps(target.local.apps);
|
|
expect(steps).to.have.length(1);
|
|
expect(steps[0]).to.have.property('action').that.equals('removeImage');
|
|
expect(steps[0])
|
|
.to.have.property('image')
|
|
.that.has.property('name')
|
|
.that.equals('registry2.balena-cloud.com/v2/asdasdasdasd@sha256:10');
|
|
});
|
|
|
|
it.skip(
|
|
'should infer that an image should be saved if it is not in the database',
|
|
);
|
|
|
|
describe('MultiApp Support', () => {
|
|
const multiAppState = {
|
|
local: {
|
|
name: 'testy-mctestface',
|
|
config: {
|
|
HOST_CONFIG_gpu_mem: '512',
|
|
HOST_FIREWALL_MODE: 'off',
|
|
HOST_DISCOVERABILITY: 'true',
|
|
SUPERVISOR_CONNECTIVITY_CHECK: 'true',
|
|
SUPERVISOR_DELTA: 'false',
|
|
SUPERVISOR_DELTA_APPLY_TIMEOUT: '0',
|
|
SUPERVISOR_DELTA_REQUEST_TIMEOUT: '30000',
|
|
SUPERVISOR_DELTA_RETRY_COUNT: '30',
|
|
SUPERVISOR_DELTA_RETRY_INTERVAL: '10000',
|
|
SUPERVISOR_DELTA_VERSION: '2',
|
|
SUPERVISOR_INSTANT_UPDATE_TRIGGER: 'true',
|
|
SUPERVISOR_LOCAL_MODE: 'false',
|
|
SUPERVISOR_LOG_CONTROL: 'true',
|
|
SUPERVISOR_OVERRIDE_LOCK: 'false',
|
|
SUPERVISOR_POLL_INTERVAL: '60000',
|
|
SUPERVISOR_VPN_CONTROL: 'true',
|
|
SUPERVISOR_PERSISTENT_LOGGING: 'false',
|
|
},
|
|
apps: {
|
|
'1': {
|
|
appId: 1,
|
|
name: 'userapp',
|
|
commit: 'aaaaaaa',
|
|
releaseId: 1,
|
|
services: {
|
|
'1': {
|
|
serviceName: 'mainy-1-servicey',
|
|
imageId: 1,
|
|
image: 'registry2.resin.io/userapp/main',
|
|
environment: {},
|
|
labels: {},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
'100': {
|
|
appId: 100,
|
|
name: 'systemapp',
|
|
commit: 'bbbbbbb',
|
|
releaseId: 100,
|
|
services: {
|
|
'100': {
|
|
serviceName: 'mainy-2-systemapp',
|
|
imageId: 100,
|
|
image: 'registry2.resin.io/systemapp/main',
|
|
environment: {},
|
|
labels: {},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
dependent: { apps: [], devices: [] },
|
|
};
|
|
|
|
before(async () => {
|
|
await prepare();
|
|
|
|
await config.initialized;
|
|
await deviceState.initialized;
|
|
|
|
intialiseContractRequirements({
|
|
supervisorVersion: '11.0.0',
|
|
deviceType: 'intel-nuc',
|
|
});
|
|
});
|
|
|
|
it('should correctly generate steps for multiple apps', async () => {
|
|
appMock.mockImages([], false, []);
|
|
appMock.mockSupervisorNetwork(false);
|
|
appMock.mockManagers([], [], []);
|
|
|
|
await mockedDockerode.testWithData({}, async () => {
|
|
await deviceState.setTarget(multiAppState);
|
|
const target = await deviceState.getTarget();
|
|
|
|
// The network always should be created first
|
|
let steps = await applicationManager.getRequiredSteps(
|
|
target.local.apps,
|
|
);
|
|
expect(steps).to.contain.something.like({
|
|
action: 'ensureSupervisorNetwork',
|
|
});
|
|
expect(steps).to.have.length(1);
|
|
|
|
// Now we expect the steps to apply to multiple apps
|
|
appMock.mockSupervisorNetwork(true);
|
|
steps = await applicationManager.getRequiredSteps(target.local.apps);
|
|
|
|
expect(steps).to.not.be.null;
|
|
expect(steps).to.contain.something.like({
|
|
action: 'fetch',
|
|
serviceName: 'mainy-1-servicey',
|
|
});
|
|
expect(steps).to.contain.something.like({
|
|
action: 'fetch',
|
|
serviceName: 'mainy-2-systemapp',
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|