Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pin to target OS release in startOsUpdate and skip action-based HUP if OS >=vTODO #1498

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 92 additions & 32 deletions src/models/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export type DeviceMetrics = Pick<
>;

const DEFAULT_DAYS_OF_REQUESTED_HISTORY = 7;
const SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION = 'vTODO';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to compare ESR releases?

const SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION_ESR = 'vTODO-ESR';

const getDeviceModel = function (
deps: InjectedDependenciesParam,
Expand Down Expand Up @@ -392,8 +394,12 @@ const getDeviceModel = function (
groupByNavigationPoperty: 'is_of__device_type',
fn: async (devices, deviceTypeId) => {
const dt = await getDeviceType(deviceTypeId);
const toPinOSRelease = new Map<string, number>();
for (const device of devices) {
// this will throw an error if the action isn't available
// Validate current device type, OS version, and OS variant.
// Ensure target OS version is newer than current OS version.
// Also ensure device is online if we're using the actions
// proxy which requires a connection to the device.
exports._checkOsUpdateTarget(
{
...device,
Expand All @@ -402,27 +408,65 @@ const getDeviceModel = function (
targetOsVersion,
);

// Validate target OS version is available for the device type.
const osVersions = await getAvailableOsVersions(dt.slug, isDraft);

if (
!osVersions.some(
(v) => bSemver.compare(v.raw_version, targetOsVersion) === 0,
)
) {
// Find target OS release
const targetRelease = osVersions.find(
(v) => bSemver.compare(v.raw_version, targetOsVersion) === 0,
);

if (!targetRelease) {
throw new errors.BalenaInvalidParameterError(
'targetOsVersion',
targetOsVersion,
);
}

// Mark device to skip action proxy based HUP
// if target OS version >= SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION
if (
// Non-ESR comparison
bSemver.compare(
targetOsVersion,
SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION,
) >= 0 ||
// ESR comparison
bSemver.compare(
targetOsVersion,
SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION_ESR,
) >= 0
) {
toPinOSRelease.set(device.uuid, targetRelease.id);
}
}

// use the v2 device actions api for detached updates
// Trigger OS update via actions proxy, or pin device to target OS release
// if current OS version >= SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION
await limitedMap(devices, async (device) => {
results[device.uuid] = await osUpdateHelper.startOsUpdate(
device.uuid,
targetOsVersion,
options.runDetached === true ? 'v2' : 'v1',
);
const targetReleaseId = toPinOSRelease.get(device.uuid);
if (targetReleaseId) {
// Pin device to target OS release and let Supervisor manage HUP
await sdkInstance.pine.patch({
resource: 'device',
id: device.id,
body: {
should_be_operated_by__release: targetReleaseId,
},
});

results[device.uuid] = {
status: 'pinned',
};
} else {
// Use actions proxy for HUP
results[device.uuid] = await osUpdateHelper.startOsUpdate(
device.uuid,
targetOsVersion,
// use the v2 device actions api for detached updates
options.runDetached === true ? 'v2' : 'v1',
);
}
});
},
});
Expand Down Expand Up @@ -2199,7 +2243,7 @@ const getDeviceModel = function (
* @param {Object} device - A device object
* @param {String} targetOsVersion - semver-compatible version for the target device
* @throws Exception if update isn't supported
* @returns {void}
* @returns {String} current OS version in semver format
*/
_checkOsUpdateTarget(
{
Expand All @@ -2212,32 +2256,28 @@ const getDeviceModel = function (
is_of__device_type: [Pick<DeviceType, 'slug'>];
},
targetOsVersion: string,
) {
): string {
if (!uuid) {
throw new Error('The uuid of the device is not available');
}

if (!is_online) {
throw new Error(`The device is offline: ${uuid}`);
}

if (!os_version) {
throw new Error(
`The current os version of the device is not available: ${uuid}`,
);
}

const deviceType = is_of__device_type?.[0]?.slug;
if (!deviceType) {
// Error if the property is missing
if (os_variant === undefined) {
throw new Error(
`The device type of the device is not available: ${uuid}`,
`The os variant of the device is not available: ${uuid}`,
);
}

// error the property is missing
if (os_variant === undefined) {
const deviceType = is_of__device_type?.[0]?.slug;
if (!deviceType) {
throw new Error(
`The os variant of the device is not available: ${uuid}`,
`The device type of the device is not available: ${uuid}`,
);
}

Expand All @@ -2247,15 +2287,35 @@ const getDeviceModel = function (
os_variant,
}) ?? os_version;

// if the os_version couldn't be parsed
// rely on getHUPActionType to throw an error
// Validate target OS version is newer than current OS version
if (bSemver.compare(currentOsVersion, targetOsVersion) >= 0) {
throw new Error('OS downgrades are not allowed');
}

// this will throw an error if the action isn't available
hupActionHelper().getHUPActionType(
deviceType,
currentOsVersion,
targetOsVersion,
);
// If the os_version couldn't be parsed, rely on getHUPActionType to
// throw an error if <SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION.
// Otherwise, just continue as as we should allow pinning from an
// invalid current OS version to a valid target OS version (assuming
// target OS version is valid).
if (
!bSemver.gte(currentOsVersion, SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION)
) {
// It only matters that the device is online if we're
// HUP-ing using the actions proxy which requires a
// connection to the device.
if (!is_online) {
throw new Error(`The device is offline: ${uuid}`);
}

// This will throw an error if the action isn't available
hupActionHelper().getHUPActionType(
deviceType,
currentOsVersion,
targetOsVersion,
);
}

return currentOsVersion;
},

/**
Expand Down
4 changes: 3 additions & 1 deletion src/util/device-actions/os-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export interface OsUpdateActionResult {
| 'done'
| 'error'
| 'configuring'
| 'triggered';
| 'triggered'
// Only used for OS >= vTODO which supports Supervisor managed HUP.
| 'pinned';
parameters?: {
target_version: string;
};
Expand Down
Loading