mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-05-29 05:44:22 +00:00
Support matching on device type within contracts
Closes: #1191 Change-type: minor Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
2279430819
commit
c9c0e650cb
@ -1165,9 +1165,9 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTarget(apps, dependent, source, maybeTrx) {
|
setTarget(apps, dependent, source, maybeTrx) {
|
||||||
const setInTransaction = (filteredApps, trx) => {
|
const setInTransaction = (filtered, trx) => {
|
||||||
return Promise.try(() => {
|
return Promise.try(() => {
|
||||||
const appsArray = _.map(filteredApps, function(app, appId) {
|
const appsArray = _.map(filtered, function(app, appId) {
|
||||||
const appClone = _.clone(app);
|
const appClone = _.clone(app);
|
||||||
appClone.appId = checkInt(appId);
|
appClone.appId = checkInt(appId);
|
||||||
appClone.source = source;
|
appClone.source = source;
|
||||||
@ -1208,38 +1208,38 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// filter those out and add the target state to the database
|
// filter those out and add the target state to the database
|
||||||
/** @type { { [appName: string]: string[]; } } */
|
/** @type { { [appName: string]: string[]; } } */
|
||||||
const contractViolators = {};
|
const contractViolators = {};
|
||||||
return Promise.resolve(validateTargetContracts(apps))
|
const fulfilledContracts = validateTargetContracts(apps);
|
||||||
.then(fulfilledContracts => {
|
const filteredApps = _.cloneDeep(apps);
|
||||||
const filteredApps = _.cloneDeep(apps);
|
_.each(
|
||||||
_.each(
|
fulfilledContracts,
|
||||||
fulfilledContracts,
|
(
|
||||||
(
|
{ valid, unmetServices, fulfilledServices, unmetAndOptional },
|
||||||
{ valid, unmetServices, fulfilledServices, unmetAndOptional },
|
appId,
|
||||||
appId,
|
) => {
|
||||||
) => {
|
if (!valid) {
|
||||||
if (!valid) {
|
contractViolators[apps[appId].name] = unmetServices;
|
||||||
contractViolators[apps[appId].name] = unmetServices;
|
return delete filteredApps[appId];
|
||||||
return delete filteredApps[appId];
|
|
||||||
} else {
|
|
||||||
// valid is true, but we could still be missing
|
|
||||||
// some optional containers, and need to filter
|
|
||||||
// these out of the target state
|
|
||||||
filteredApps[appId].services = _.pickBy(
|
|
||||||
filteredApps[appId].services,
|
|
||||||
({ serviceName }) => fulfilledServices.includes(serviceName),
|
|
||||||
);
|
|
||||||
if (unmetAndOptional.length !== 0) {
|
|
||||||
return this.reportOptionalContainers(unmetAndOptional);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (maybeTrx != null) {
|
|
||||||
return setInTransaction(filteredApps, maybeTrx);
|
|
||||||
} else {
|
} else {
|
||||||
return this.db.transaction(setInTransaction);
|
// valid is true, but we could still be missing
|
||||||
|
// some optional containers, and need to filter
|
||||||
|
// these out of the target state
|
||||||
|
filteredApps[appId].services = _.pickBy(
|
||||||
|
filteredApps[appId].services,
|
||||||
|
({ serviceName }) => fulfilledServices.includes(serviceName),
|
||||||
|
);
|
||||||
|
if (unmetAndOptional.length !== 0) {
|
||||||
|
return this.reportOptionalContainers(unmetAndOptional);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
);
|
||||||
|
let promise;
|
||||||
|
if (maybeTrx != null) {
|
||||||
|
promise = setInTransaction(filteredApps, maybeTrx);
|
||||||
|
} else {
|
||||||
|
promise = this.db.transaction(setInTransaction);
|
||||||
|
}
|
||||||
|
return promise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._targetVolatilePerImageId = {};
|
this._targetVolatilePerImageId = {};
|
||||||
})
|
})
|
||||||
|
@ -6,8 +6,6 @@ import * as _ from 'lodash';
|
|||||||
import { Blueprint, Contract, ContractObject } from '@balena/contrato';
|
import { Blueprint, Contract, ContractObject } from '@balena/contrato';
|
||||||
|
|
||||||
import { ContractValidationError, InternalInconsistencyError } from './errors';
|
import { ContractValidationError, InternalInconsistencyError } from './errors';
|
||||||
import * as osRelease from './os-release';
|
|
||||||
import supervisorVersion = require('./supervisor-version');
|
|
||||||
import { checkTruthy } from './validation';
|
import { checkTruthy } from './validation';
|
||||||
|
|
||||||
export { ContractObject };
|
export { ContractObject };
|
||||||
@ -35,50 +33,43 @@ export interface ServiceContracts {
|
|||||||
[serviceName: string]: { contract?: ContractObject; optional: boolean };
|
[serviceName: string]: { contract?: ContractObject; optional: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
type PotentialContractRequirements = 'sw.supervisor' | 'sw.l4t';
|
type PotentialContractRequirements =
|
||||||
type ContractRequirementVersions = {
|
| 'sw.supervisor'
|
||||||
|
| 'sw.l4t'
|
||||||
|
| 'hw.device-type';
|
||||||
|
type ContractRequirements = {
|
||||||
[key in PotentialContractRequirements]?: string;
|
[key in PotentialContractRequirements]?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let contractRequirementVersions = async () => {
|
const contractRequirementVersions: ContractRequirements = {};
|
||||||
const versions: ContractRequirementVersions = {
|
|
||||||
'sw.supervisor': supervisorVersion,
|
|
||||||
};
|
|
||||||
// We add a mock l4t version if one doesn't exist. This
|
|
||||||
// means that contracts used on mixed device fleets will
|
|
||||||
// still work when only a subset of the devices have an
|
|
||||||
// l4t string (for example a mixed fleet of rpi4 and tx2)
|
|
||||||
versions['sw.l4t'] = (await osRelease.getL4tVersion()) || '0';
|
|
||||||
|
|
||||||
return versions;
|
export function intialiseContractRequirements(opts: {
|
||||||
};
|
supervisorVersion: string;
|
||||||
|
deviceType: string;
|
||||||
// When running in tests, we need this function to be
|
l4tVersion?: string;
|
||||||
// repeatedly executed, but on-device, this should only be
|
}) {
|
||||||
// executed once per run
|
contractRequirementVersions['sw.supervisor'] = opts.supervisorVersion;
|
||||||
if (process.env.TEST !== '1') {
|
contractRequirementVersions['sw.l4t'] = opts.l4tVersion;
|
||||||
contractRequirementVersions = _.once(contractRequirementVersions);
|
contractRequirementVersions['hw.device-type'] = opts.deviceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidRequirementType(
|
function isValidRequirementType(
|
||||||
requirementVersions: ContractRequirementVersions,
|
requirementVersions: ContractRequirements,
|
||||||
requirement: string,
|
requirement: string,
|
||||||
) {
|
) {
|
||||||
return requirement in requirementVersions;
|
return requirement in requirementVersions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function containerContractsFulfilled(
|
export function containerContractsFulfilled(
|
||||||
serviceContracts: ServiceContracts,
|
serviceContracts: ServiceContracts,
|
||||||
): Promise<ApplicationContractResult> {
|
): ApplicationContractResult {
|
||||||
const containers = _(serviceContracts)
|
const containers = _(serviceContracts)
|
||||||
.map('contract')
|
.map('contract')
|
||||||
.compact()
|
.compact()
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
const versions = await contractRequirementVersions();
|
|
||||||
|
|
||||||
const blueprintMembership: Dictionary<number> = {};
|
const blueprintMembership: Dictionary<number> = {};
|
||||||
for (const component of _.keys(versions)) {
|
for (const component of _.keys(contractRequirementVersions)) {
|
||||||
blueprintMembership[component] = 1;
|
blueprintMembership[component] = 1;
|
||||||
}
|
}
|
||||||
const blueprint = new Blueprint(
|
const blueprint = new Blueprint(
|
||||||
@ -97,9 +88,10 @@ export async function containerContractsFulfilled(
|
|||||||
});
|
});
|
||||||
|
|
||||||
universe.addChildren(
|
universe.addChildren(
|
||||||
[...getContractsFromVersions(versions), ...containers].map(
|
[
|
||||||
c => new Contract(c),
|
...getContractsFromVersions(contractRequirementVersions),
|
||||||
),
|
...containers,
|
||||||
|
].map(c => new Contract(c)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const solution = blueprint.reproduce(universe);
|
const solution = blueprint.reproduce(universe);
|
||||||
@ -182,23 +174,33 @@ const contractObjectValidator = t.type({
|
|||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
function getContractsFromVersions(versions: ContractRequirementVersions) {
|
function getContractsFromVersions(components: ContractRequirements) {
|
||||||
return _.map(versions, (version, component) => ({
|
return _.map(components, (value, component) => {
|
||||||
type: component,
|
if (component === 'hw.device-type') {
|
||||||
slug: component,
|
return {
|
||||||
name: component,
|
type: component,
|
||||||
version,
|
slug: component,
|
||||||
}));
|
name: value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: component,
|
||||||
|
slug: component,
|
||||||
|
name: component,
|
||||||
|
version: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateContract(contract: unknown): Promise<boolean> {
|
export function validateContract(contract: unknown): boolean {
|
||||||
const result = contractObjectValidator.decode(contract);
|
const result = contractObjectValidator.decode(contract);
|
||||||
|
|
||||||
if (isLeft(result)) {
|
if (isLeft(result)) {
|
||||||
throw new Error(reporter(result).join('\n'));
|
throw new Error(reporter(result).join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const requirementVersions = await contractRequirementVersions();
|
const requirementVersions = contractRequirementVersions;
|
||||||
|
|
||||||
for (const { type } of result.right.requires || []) {
|
for (const { type } of result.right.requires || []) {
|
||||||
if (!isValidRequirementType(requirementVersions, type)) {
|
if (!isValidRequirementType(requirementVersions, type)) {
|
||||||
@ -208,9 +210,9 @@ export async function validateContract(contract: unknown): Promise<boolean> {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
export async function validateTargetContracts(
|
export function validateTargetContracts(
|
||||||
apps: Dictionary<AppWithContracts>,
|
apps: Dictionary<AppWithContracts>,
|
||||||
): Promise<Dictionary<ApplicationContractResult>> {
|
): Dictionary<ApplicationContractResult> {
|
||||||
const appsFulfilled: Dictionary<ApplicationContractResult> = {};
|
const appsFulfilled: Dictionary<ApplicationContractResult> = {};
|
||||||
|
|
||||||
for (const appId of _.keys(apps)) {
|
for (const appId of _.keys(apps)) {
|
||||||
@ -222,7 +224,7 @@ export async function validateTargetContracts(
|
|||||||
|
|
||||||
if (svc.contract) {
|
if (svc.contract) {
|
||||||
try {
|
try {
|
||||||
await validateContract(svc.contract);
|
validateContract(svc.contract);
|
||||||
|
|
||||||
serviceContracts[svc.serviceName] = {
|
serviceContracts[svc.serviceName] = {
|
||||||
contract: svc.contract,
|
contract: svc.contract,
|
||||||
@ -240,9 +242,7 @@ export async function validateTargetContracts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isEmpty(serviceContracts)) {
|
if (!_.isEmpty(serviceContracts)) {
|
||||||
appsFulfilled[appId] = await containerContractsFulfilled(
|
appsFulfilled[appId] = containerContractsFulfilled(serviceContracts);
|
||||||
serviceContracts,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
appsFulfilled[appId] = {
|
appsFulfilled[appId] = {
|
||||||
valid: true,
|
valid: true,
|
||||||
|
@ -3,7 +3,9 @@ import Config, { ConfigKey } from './config';
|
|||||||
import Database from './db';
|
import Database from './db';
|
||||||
import DeviceState from './device-state';
|
import DeviceState from './device-state';
|
||||||
import EventTracker from './event-tracker';
|
import EventTracker from './event-tracker';
|
||||||
|
import { intialiseContractRequirements } from './lib/contracts';
|
||||||
import { normaliseLegacyDatabase } from './lib/migration';
|
import { normaliseLegacyDatabase } from './lib/migration';
|
||||||
|
import * as osRelease from './lib/os-release';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import SupervisorAPI from './supervisor-api';
|
import SupervisorAPI from './supervisor-api';
|
||||||
|
|
||||||
@ -93,6 +95,12 @@ export class Supervisor {
|
|||||||
...conf,
|
...conf,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
intialiseContractRequirements({
|
||||||
|
supervisorVersion: version,
|
||||||
|
deviceType: await this.config.get('deviceType'),
|
||||||
|
l4tVersion: await osRelease.getL4tVersion(),
|
||||||
|
});
|
||||||
|
|
||||||
log.debug('Starting api binder');
|
log.debug('Starting api binder');
|
||||||
await this.apiBinder.initClient();
|
await this.apiBinder.initClient();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import DeviceState from '../src/device-state';
|
|||||||
import { loadTargetFromFile } from '../src/device-state/preload';
|
import { loadTargetFromFile } from '../src/device-state/preload';
|
||||||
|
|
||||||
import Service from '../src/compose/service';
|
import Service from '../src/compose/service';
|
||||||
|
import { intialiseContractRequirements } from '../src/lib/contracts';
|
||||||
|
|
||||||
const mockedInitialConfig = {
|
const mockedInitialConfig = {
|
||||||
RESIN_SUPERVISOR_CONNECTIVITY_CHECK: 'true',
|
RESIN_SUPERVISOR_CONNECTIVITY_CHECK: 'true',
|
||||||
@ -227,6 +228,11 @@ describe('deviceState', () => {
|
|||||||
return env;
|
return env;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
intialiseContractRequirements({
|
||||||
|
supervisorVersion: '11.0.0',
|
||||||
|
deviceType: 'intel-nuc',
|
||||||
|
});
|
||||||
|
|
||||||
deviceState = new DeviceState({
|
deviceState = new DeviceState({
|
||||||
db,
|
db,
|
||||||
config,
|
config,
|
||||||
|
@ -7,53 +7,48 @@ import * as semver from 'semver';
|
|||||||
import * as constants from '../src/lib/constants';
|
import * as constants from '../src/lib/constants';
|
||||||
import {
|
import {
|
||||||
containerContractsFulfilled,
|
containerContractsFulfilled,
|
||||||
|
intialiseContractRequirements,
|
||||||
validateContract,
|
validateContract,
|
||||||
} from '../src/lib/contracts';
|
} from '../src/lib/contracts';
|
||||||
import * as osRelease from '../src/lib/os-release';
|
import * as osRelease from '../src/lib/os-release';
|
||||||
import supervisorVersion = require('../src/lib/supervisor-version');
|
import supervisorVersion = require('../src/lib/supervisor-version');
|
||||||
|
|
||||||
describe('Container contracts', () => {
|
describe('Container contracts', () => {
|
||||||
let execStub: SinonStub;
|
|
||||||
before(() => {
|
before(() => {
|
||||||
execStub = stub(child_process, 'exec').returns(
|
intialiseContractRequirements({
|
||||||
Promise.resolve([
|
supervisorVersion: '11.0.0',
|
||||||
Buffer.from('4.9.140-l4t-r32.2+g3dcbed5'),
|
deviceType: 'intel-nuc',
|
||||||
Buffer.from(''),
|
l4tVersion: '32.2',
|
||||||
]),
|
});
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
execStub.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Contract validation', () => {
|
describe('Contract validation', () => {
|
||||||
it('should correctly validate a contract with no requirements', () =>
|
it('should correctly validate a contract with no requirements', () =>
|
||||||
expect(
|
expect(() =>
|
||||||
validateContract({
|
validateContract({
|
||||||
slug: 'user-container',
|
slug: 'user-container',
|
||||||
}),
|
}),
|
||||||
).to.be.fulfilled);
|
).to.be.not.throw());
|
||||||
|
|
||||||
it('should correctly validate a contract with extra fields', () =>
|
it('should correctly validate a contract with extra fields', () =>
|
||||||
expect(
|
expect(() =>
|
||||||
validateContract({
|
validateContract({
|
||||||
slug: 'user-container',
|
slug: 'user-container',
|
||||||
name: 'user-container',
|
name: 'user-container',
|
||||||
version: '3.0.0',
|
version: '3.0.0',
|
||||||
}),
|
}),
|
||||||
).to.be.fulfilled);
|
).to.be.not.throw());
|
||||||
|
|
||||||
it('should not validate a contract without the minimum required fields', () => {
|
it('should not validate a contract without the minimum required fields', () => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
expect(validateContract({})).to.be.rejected,
|
expect(() => validateContract({})).to.throw(),
|
||||||
expect(validateContract({ name: 'test' })).to.be.rejected,
|
expect(() => validateContract({ name: 'test' })).to.throw(),
|
||||||
expect(validateContract({ requires: [] })).to.be.rejected,
|
expect(() => validateContract({ requires: [] })).to.throw(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly validate a contract with requirements', () =>
|
it('should correctly validate a contract with requirements', () =>
|
||||||
expect(
|
expect(() =>
|
||||||
validateContract({
|
validateContract({
|
||||||
slug: 'user-container',
|
slug: 'user-container',
|
||||||
requires: [
|
requires: [
|
||||||
@ -66,10 +61,10 @@ describe('Container contracts', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).to.be.fulfilled);
|
).to.not.throw());
|
||||||
|
|
||||||
it('should not validate a contract with requirements without the minimum required fields', () => {
|
it('should not validate a contract with requirements without the minimum required fields', () => {
|
||||||
return expect(
|
return expect(() =>
|
||||||
validateContract({
|
validateContract({
|
||||||
slug: 'user-container',
|
slug: 'user-container',
|
||||||
requires: [
|
requires: [
|
||||||
@ -78,7 +73,7 @@ describe('Container contracts', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).to.be.rejected;
|
).to.throw();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -105,7 +100,7 @@ describe('Container contracts', () => {
|
|||||||
|
|
||||||
it('Should correctly run containers with no requirements', async () => {
|
it('Should correctly run containers with no requirements', async () => {
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -118,7 +113,7 @@ describe('Container contracts', () => {
|
|||||||
.to.have.property('valid')
|
.to.have.property('valid')
|
||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -141,7 +136,7 @@ describe('Container contracts', () => {
|
|||||||
|
|
||||||
it('should correctly run containers whose requirements are satisfied', async () => {
|
it('should correctly run containers whose requirements are satisfied', async () => {
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -162,7 +157,7 @@ describe('Container contracts', () => {
|
|||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -183,7 +178,7 @@ describe('Container contracts', () => {
|
|||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -204,7 +199,7 @@ describe('Container contracts', () => {
|
|||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -228,7 +223,7 @@ describe('Container contracts', () => {
|
|||||||
.to.have.property('valid')
|
.to.have.property('valid')
|
||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -264,7 +259,7 @@ describe('Container contracts', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should refuse to run containers whose requirements are not satisfied', async () => {
|
it('Should refuse to run containers whose requirements are not satisfied', async () => {
|
||||||
let fulfilled = await containerContractsFulfilled({
|
let fulfilled = containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -287,7 +282,7 @@ describe('Container contracts', () => {
|
|||||||
.to.have.property('unmetServices')
|
.to.have.property('unmetServices')
|
||||||
.that.deep.equals(['service']);
|
.that.deep.equals(['service']);
|
||||||
|
|
||||||
fulfilled = await containerContractsFulfilled({
|
fulfilled = containerContractsFulfilled({
|
||||||
service2: {
|
service2: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -310,7 +305,7 @@ describe('Container contracts', () => {
|
|||||||
.to.have.property('unmetServices')
|
.to.have.property('unmetServices')
|
||||||
.that.deep.equals(['service2']);
|
.that.deep.equals(['service2']);
|
||||||
|
|
||||||
fulfilled = await containerContractsFulfilled({
|
fulfilled = containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -354,7 +349,7 @@ describe('Container contracts', () => {
|
|||||||
valid,
|
valid,
|
||||||
unmetServices,
|
unmetServices,
|
||||||
fulfilledServices,
|
fulfilledServices,
|
||||||
} = await containerContractsFulfilled({
|
} = containerContractsFulfilled({
|
||||||
service1: {
|
service1: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -379,7 +374,7 @@ describe('Container contracts', () => {
|
|||||||
valid,
|
valid,
|
||||||
unmetServices,
|
unmetServices,
|
||||||
fulfilledServices,
|
fulfilledServices,
|
||||||
} = await containerContractsFulfilled({
|
} = containerContractsFulfilled({
|
||||||
service1: {
|
service1: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -445,11 +440,23 @@ describe('L4T version detection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('L4T comparison', () => {
|
describe('L4T comparison', () => {
|
||||||
|
const seedEngine = async (version: string) => {
|
||||||
|
if (execStub != null) {
|
||||||
|
execStub.restore();
|
||||||
|
}
|
||||||
|
seedExec(version);
|
||||||
|
intialiseContractRequirements({
|
||||||
|
supervisorVersion: '11.0.0',
|
||||||
|
deviceType: 'intel-nuc',
|
||||||
|
l4tVersion: await osRelease.getL4tVersion(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
it('should allow semver matching even when l4t does not fulfill semver', async () => {
|
it('should allow semver matching even when l4t does not fulfill semver', async () => {
|
||||||
seedExec('4.4.38-l4t-r31.0');
|
await seedEngine('4.4.38-l4t-r31.0');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -469,7 +476,7 @@ describe('L4T version detection', () => {
|
|||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -490,10 +497,10 @@ describe('L4T version detection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow semver matching when l4t does fulfill semver', async () => {
|
it('should allow semver matching when l4t does fulfill semver', async () => {
|
||||||
seedExec('4.4.38-l4t-r31.0.1');
|
await seedEngine('4.4.38-l4t-r31.0.1');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
@ -513,7 +520,7 @@ describe('L4T version detection', () => {
|
|||||||
.that.equals(true);
|
.that.equals(true);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await containerContractsFulfilled({
|
containerContractsFulfilled({
|
||||||
service: {
|
service: {
|
||||||
contract: {
|
contract: {
|
||||||
type: 'sw.container',
|
type: 'sw.container',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user