Skip to content

Commit ca2acf5

Browse files
committed
sq
Resolves: # Change-type:
1 parent d5ca204 commit ca2acf5

File tree

2 files changed

+99
-24
lines changed

2 files changed

+99
-24
lines changed

src/features/hostapp/hooks/target-hostapp.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ hooks.addPureHook('PATCH', 'resin', 'device', {
6363

6464
/**
6565
* Enforce the "device.should_be_operated_by__release[0].semver >= device.os_version" invariant.
66-
* When a device reports its current OS version and the release pointed by the should_be_operated_by__release
66+
* When a device reports its current OS version and the release pointed to by the should_be_operated_by__release
6767
* is lower than the reported os_version or null, we find the hostApp release for the os_version and update the
6868
* device's should_be_operated_by__release.
6969
* Setting the should_be_operated_by__release when it's null has the semantics of pinning the device to its current OS release.
@@ -149,7 +149,7 @@ async function setOSReleaseIfNewer(
149149
},
150150
});
151151
const devicesToUpdate = devices.filter((device) => {
152-
const targetHostappRelease = device.should_be_operated_by__release[1];
152+
const targetHostappRelease = device.should_be_operated_by__release[0];
153153
if (targetHostappRelease == null) {
154154
return true;
155155
}
@@ -183,15 +183,24 @@ async function setOSReleaseIfNewer(
183183
return Promise.all(
184184
Array.from(devicesByDeviceTypeId.entries()).map(
185185
async ([deviceTypeId, affectedDevices]) => {
186-
const osRelease = await getOSReleaseResource(
186+
const newOsRelease = await getOSReleaseResource(
187187
api,
188188
newOsVersion,
189189
newOsVariant,
190190
deviceTypeId,
191191
);
192192

193-
if (osRelease == null) {
194-
return;
193+
if (newOsRelease == null) {
194+
// When the newOsRelease is not found, and since we have already checked that
195+
// newOsVersion > should_be_operated_by__release, we need to clear the
196+
// should_be_operated_by__release of devices that have it set, otherwise the
197+
// should_be_operated_by__release >= os_version invariant would be violated.
198+
affectedDevices = affectedDevices.filter(
199+
(d) => d.should_be_operated_by__release[0] != null,
200+
);
201+
if (affectedDevices.length === 0) {
202+
return;
203+
}
195204
}
196205

197206
const affectedDeviceIds = affectedDevices.map((d) => d.id);
@@ -203,7 +212,7 @@ async function setOSReleaseIfNewer(
203212
},
204213
},
205214
body: {
206-
should_be_operated_by__release: osRelease.id,
215+
should_be_operated_by__release: newOsRelease?.id ?? null,
207216
},
208217
});
209218
},

test/15_target-hostapp.ts

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as semver from 'balena-semver';
12
import _ from 'lodash';
23
import * as fixtures from './test-lib/fixtures.js';
34
import * as fakeDevice from './test-lib/fake-device.js';
@@ -34,7 +35,7 @@ export default () => {
3435
/* eslint-enable @typescript-eslint/naming-convention */
3536
let rpi3hostAppReleaseId: number;
3637
let failedIntelNucHostAppReleaseId: number;
37-
let esrTagOnlyHostAppReleaseId: number;
38+
let esrTagOnlyNonSemverHostAppReleaseId: number;
3839
let esrUnifiedHostAppReleaseId: number;
3940
let esrSemverOnlyHostAppReleaseId: number;
4041
let invalidatedTagOnlyReleaseId: number;
@@ -63,7 +64,8 @@ export default () => {
6364
nuc2_51_0_rev2prodSemverOnlyId = fx.releases.release2_51_0_rev2.id;
6465
rpi3hostAppReleaseId = fx.releases.rpi3release.id;
6566
failedIntelNucHostAppReleaseId = fx.releases.releaseIntelNucFailed.id;
66-
esrTagOnlyHostAppReleaseId = fx.releases.releaseNucEsrTagOnly.id;
67+
esrTagOnlyNonSemverHostAppReleaseId =
68+
fx.releases.releaseNucEsrTagOnly.id;
6769
esrUnifiedHostAppReleaseId = fx.releases.releaseNucEsrUnified.id;
6870
esrSemverOnlyHostAppReleaseId = fx.releases.releaseNucEsrSemverOnly.id;
6971
invalidatedTagOnlyReleaseId =
@@ -155,12 +157,12 @@ export default () => {
155157
() => unifiedSemverRevHostAppReleaseId,
156158
],
157159
[
158-
'ESR release_tag only',
160+
'ESR non-Semver release_tag only',
159161
{
160162
os_version: 'balenaOS 2021.01.0',
161163
os_variant: 'prod',
162164
},
163-
() => esrTagOnlyHostAppReleaseId,
165+
() => esrTagOnlyNonSemverHostAppReleaseId,
164166
],
165167
[
166168
'unified ESR',
@@ -435,15 +437,27 @@ export default () => {
435437
'balenaOS 2.51.0+rev2',
436438
() => nuc2_51_0_rev2prodSemverOnlyId,
437439
],
440+
// [
441+
// 'ESR non-Semver release_tag only',
442+
// 'balenaOS 2021.01.0',
443+
// () => esrTagOnlyNonSemverHostAppReleaseId,
444+
// ],
445+
// [
446+
// 'unified ESR',
447+
// 'balenaOS 2023.1.0',
448+
// () => esrUnifiedHostAppReleaseId,
449+
// ],
438450
] as const
439451
).forEach(([titlePart, osVersion, getReleaseId]) => {
440452
it(`should succeed in PATCHing the device.should_be_operated_by__release to a greater version (with ${titlePart})`, async () => {
441453
await supertest(admin)
442454
.patch(`/${version}/device(${device1.id})`)
443455
.send({ should_be_operated_by__release: getReleaseId() })
444456
.expect(200);
457+
const initialOsVersion = 'balenaOS 2.49.0+rev1';
458+
expect(semver.gt(osVersion, initialOsVersion)).to.be.true;
445459
await expectResourceToMatch(pineUser, 'device', device1.id, {
446-
os_version: 'balenaOS 2.49.0+rev1',
460+
os_version: initialOsVersion,
447461
os_variant: 'prod',
448462
should_be_operated_by__release: { __id: getReleaseId() },
449463
});
@@ -466,9 +480,11 @@ export default () => {
466480
admin,
467481
applicationId,
468482
);
483+
const initialOsVersion = 'balenaOS 2.88.4';
484+
expect(semver.gt(initialOsVersion, osVersion)).to.be.true;
469485
await downgradeTestDevice.patchStateV2({
470486
local: {
471-
os_version: 'balenaOS 2.88.4',
487+
os_version: initialOsVersion,
472488
os_variant: 'prod',
473489
},
474490
});
@@ -510,7 +526,6 @@ export default () => {
510526
applicationId,
511527
);
512528
await downgradeTestDevice.patchStateV2({
513-
// this version is greater than the one provided by prodNucHostappReleaseId
514529
local: {
515530
os_version: osVersion,
516531
os_variant: 'prod',
@@ -525,9 +540,11 @@ export default () => {
525540
},
526541
);
527542
// run the actual test
543+
const oldUnkownOsVersion = 'balenaOS 2.9.9+rev99';
544+
expect(semver.gt(osVersion, oldUnkownOsVersion)).to.be.true;
528545
await downgradeTestDevice.patchStateV2({
529546
local: {
530-
os_version: 'balenaOS 2.9.9+rev99',
547+
os_version: oldUnkownOsVersion,
531548
os_variant: 'prod',
532549
},
533550
});
@@ -536,7 +553,7 @@ export default () => {
536553
'device',
537554
downgradeTestDevice.id,
538555
{
539-
os_version: 'balenaOS 2.9.9+rev99',
556+
os_version: oldUnkownOsVersion,
540557
os_variant: 'prod',
541558
should_be_operated_by__release: { __id: getReleaseId() },
542559
},
@@ -551,9 +568,11 @@ export default () => {
551568
admin,
552569
applicationId,
553570
);
571+
const oldOsVersion = 'balenaOS 2.49.0+rev1';
572+
expect(semver.gt(osVersion, oldOsVersion)).to.be.true;
554573
await preprovisionedDevice.patchStateV2({
555574
local: {
556-
os_version: 'balenaOS 2.49.0+rev1',
575+
os_version: oldOsVersion,
557576
os_variant: 'prod',
558577
},
559578
});
@@ -569,7 +588,6 @@ export default () => {
569588
);
570589
// run the actual test
571590
await preprovisionedDevice.patchStateV2({
572-
// this version is greater than the one provided by prodNucHostappReleaseId
573591
local: {
574592
os_version: osVersion,
575593
os_variant: 'prod',
@@ -587,7 +605,7 @@ export default () => {
587605
);
588606
});
589607

590-
it(`should update the device.should_be_operated_by__release when the state PATCH reports a newer os_version (n a release when with ${titlePart})`, async () => {
608+
it(`should update the device.should_be_operated_by__release when the state PATCH reports a newer os_version (when on a release ${titlePart} release)`, async () => {
591609
// if a device is preprovisioned and pinned to a release with a semver
592610
// less than the version it initially checks in with, we need to clear
593611
// the old hostApp release, otherwise the model would imply a scheduled OS downgrade.
@@ -596,7 +614,6 @@ export default () => {
596614
applicationId,
597615
);
598616
await preprovisionedDevice.patchStateV2({
599-
// this version is greater than the one provided by prodNucHostappReleaseId
600617
local: {
601618
os_version: osVersion,
602619
os_variant: 'prod',
@@ -610,11 +627,12 @@ export default () => {
610627
should_be_operated_by__release: { __id: getReleaseId() },
611628
},
612629
);
630+
const newerVersion = 'balenaOS 2.88.4';
631+
expect(semver.gt(newerVersion, osVersion)).to.be.true;
613632
// run the actual test
614633
await preprovisionedDevice.patchStateV2({
615-
// this version is greater than the one provided by prodNucHostappReleaseId
616634
local: {
617-
os_version: 'balenaOS 2.88.4',
635+
os_version: newerVersion,
618636
os_variant: 'prod',
619637
},
620638
});
@@ -623,21 +641,69 @@ export default () => {
623641
'device',
624642
preprovisionedDevice.id,
625643
{
626-
os_version: 'balenaOS 2.88.4',
644+
os_version: newerVersion,
627645
os_variant: 'prod',
628646
should_be_operated_by__release: { __id: unifiedHostAppReleaseId },
629647
},
630648
);
631649
});
650+
651+
it(`should null the device.should_be_operated_by__release when the state PATCH reports a newer "unknown" os_version (when on a release ${titlePart} release)`, async () => {
652+
// if a device is preprovisioned and pinned to a release with a semver
653+
// less than the version it initially checks in with, we need to clear
654+
// the old hostApp release, otherwise the model would imply a scheduled OS downgrade.
655+
const preprovisionedDevice = await fakeDevice.provisionDevice(
656+
admin,
657+
applicationId,
658+
);
659+
await preprovisionedDevice.patchStateV2({
660+
local: {
661+
os_version: osVersion,
662+
os_variant: 'prod',
663+
},
664+
});
665+
await expectResourceToMatch(
666+
pineUser,
667+
'device',
668+
preprovisionedDevice.id,
669+
{
670+
should_be_operated_by__release: { __id: getReleaseId() },
671+
},
672+
);
673+
const newerUnknownVersion = 'balenaOS 100.1.2';
674+
expect(semver.gt(newerUnknownVersion, osVersion)).to.be.true;
675+
// run the actual test
676+
await preprovisionedDevice.patchStateV2({
677+
// this is a super new balenaOS version that is greater than the one provided by prodNucHostappReleaseId
678+
local: {
679+
os_version: newerUnknownVersion,
680+
os_variant: 'prod',
681+
},
682+
});
683+
await expectResourceToMatch(
684+
pineUser,
685+
'device',
686+
preprovisionedDevice.id,
687+
{
688+
os_version: newerUnknownVersion,
689+
os_variant: 'prod',
690+
should_be_operated_by__release: null,
691+
},
692+
);
693+
});
632694
});
633695

634696
it('should succeed in PATCHing device to ESR release', async () => {
635697
await supertest(admin)
636698
.patch(`/${version}/device(${device1.id})`)
637-
.send({ should_be_operated_by__release: esrTagOnlyHostAppReleaseId })
699+
.send({
700+
should_be_operated_by__release: esrTagOnlyNonSemverHostAppReleaseId,
701+
})
638702
.expect(200);
639703
await expectResourceToMatch(pineUser, 'device', device1.id, {
640-
should_be_operated_by__release: { __id: esrTagOnlyHostAppReleaseId },
704+
should_be_operated_by__release: {
705+
__id: esrTagOnlyNonSemverHostAppReleaseId,
706+
},
641707
});
642708
});
643709

0 commit comments

Comments
 (0)