Skip to content

Commit 0257a75

Browse files
authored
Merge pull request #2530 from balena-os/async-os-updates
Async os updates
2 parents 9dc6bb0 + ea87039 commit 0257a75

9 files changed

Lines changed: 120 additions & 56 deletions

File tree

docker-compose.test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ services:
2020
- mock-systemd
2121
volumes:
2222
- dbus:/shared/dbus
23-
- tmp:/mnt/root/tmp/balena-supervisor/services
23+
- services:/mnt/root/tmp/balena-supervisor/services
2424
- ./test/data/root:/mnt/root
2525
- ./test/data/root/mnt/boot:/mnt/boot
2626
- ./test/lib/wait-for-it.sh:/wait-for-it.sh
@@ -78,7 +78,7 @@ services:
7878
stop_grace_period: 3s
7979
volumes:
8080
- dbus:/shared/dbus
81-
- tmp:/mnt/root/tmp/balena-supervisor/services
81+
- services:/mnt/root/tmp/balena-supervisor/services
8282
# Set required supervisor configuration variables here
8383
environment:
8484
DOCKER_HOST: tcp://docker:2375
@@ -110,7 +110,7 @@ volumes:
110110
driver_opts:
111111
type: tmpfs
112112
device: tmpfs
113-
tmp:
113+
services:
114114
driver_opts:
115115
type: tmpfs
116116
device: tmpfs

docker-compose.yml

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
version: '2.3'
22

3+
x-helios-image: &helios-image
4+
image: ghcr.io/balena-io/helios:0.25.21
5+
36
services:
47
# This service can only be named `main`, `balena-supervisor` or `core`
58
# in a multi-container supervisor, the API `supervisor_release` needs to be able to
@@ -8,10 +11,81 @@ services:
811
core:
912
build: ./
1013
privileged: true
11-
tty: true
14+
tty: false
1215
restart: always
1316
network_mode: host
1417
labels:
1518
io.balena.features.balena-api: '1'
1619
io.balena.features.dbus: '1'
1720
io.balena.features.balena-socket: '1'
21+
22+
# This is the next generation supervisor service. When started, it becomes a proxy
23+
# for calls between the existing supervisor and the balenaAPI and containers
24+
core-next:
25+
<<: *helios-image
26+
restart: on-failure
27+
# required to distinguish between stderr/stdout, the supervisor defaults to true
28+
tty: false
29+
labels:
30+
# Needed to get access to supervisor api variables
31+
# and credentials
32+
io.balena.features.supervisor-api: '1'
33+
# Needed for access to DOCKER_HOST
34+
io.balena.features.balena-socket: '1'
35+
# Needed to stop the supervisor service
36+
io.balena.features.dbus: '1'
37+
# Needed to get access to the API credentials
38+
io.balena.features.balena-api: '1'
39+
# Needed to get Host OS metadata
40+
io.balena.features.host-os.board-rev: '1'
41+
environment:
42+
- DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
43+
# Needed for locks and balenahup. This needs to match the device
44+
# used for the `runtime` volume
45+
- BALENA_HOST_RUNTIME_DIR=/tmp/balena-supervisor
46+
# Only send error logs to dashboard
47+
- BALENA_LOGS_LEVEL=error
48+
volumes:
49+
# Critical configurations
50+
- config:/config/helios
51+
# Request caching
52+
- cache:/cache/helios
53+
# Persistent system state data that can be reconstructed from
54+
# the target state
55+
- state:/local/helios
56+
# Runtime directory. Data here does not persist after reboots
57+
- runtime:/tmp/run
58+
59+
# The service below relays requests between tcp port 48484 and the socket exposed by
60+
# the core-next service
61+
service-relay:
62+
<<: *helios-image
63+
command: socat TCP-LISTEN:48484,reuseaddr,fork UNIX-CONNECT:/tmp/run/helios.sock
64+
# required to distinguish between stderr/stdout, the supervisor defaults to true
65+
tty: false
66+
ports:
67+
- 48484
68+
depends_on:
69+
- core
70+
volumes:
71+
- runtime:/tmp/run
72+
environment:
73+
# Only send error logs to dashboard
74+
- BALENA_LOGS_LEVEL=error
75+
healthcheck:
76+
test: ['CMD', 'wget', '-O', '-', '-q', 'http://127.0.0.1:48484/ping']
77+
78+
volumes:
79+
config:
80+
state:
81+
labels:
82+
# the database version, can be bumped to re-create the state
83+
io.balena.private.db-version: 0
84+
cache:
85+
runtime:
86+
driver: local
87+
driver_opts:
88+
type: none
89+
o: bind
90+
# XXX: this directory needs to exist on the host before the container is started
91+
device: /tmp/balena-supervisor

src/compose/service-manager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ export async function start(service: Service) {
372372
);
373373
}
374374

375-
logger.attach(container.id, { serviceId });
375+
logger.attach(container.id, { serviceId, logLevel: service.logLevel() });
376376

377377
if (!alreadyStarted) {
378378
logger.logSystemEvent(LogTypes.startServiceSuccess, { service });
@@ -433,8 +433,10 @@ export function listenToEvents() {
433433
`serviceId not defined for service: ${service.serviceName} in ServiceManager.listenToEvents`,
434434
);
435435
}
436+
436437
logger.attach(data.id, {
437438
serviceId,
439+
logLevel: service.logLevel(),
438440
});
439441
} else if (status === 'destroy') {
440442
logMonitor.detach(data.id);
@@ -489,8 +491,10 @@ export async function attachToRunning() {
489491
`containerId not defined for service: ${service.serviceName} in ServiceManager.attachToRunning`,
490492
);
491493
}
494+
492495
logger.attach(service.containerId, {
493496
serviceId,
497+
logLevel: service.logLevel(),
494498
});
495499
}
496500
}

src/compose/service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import log from '../lib/supervisor-console';
1414
import * as conversions from '../lib/conversions';
1515
import { checkInt } from '../lib/validation';
1616
import { InternalInconsistencyError } from '../lib/errors';
17+
import { LogLevel } from '../logging/types';
1718

1819
import type { EnvVarObject } from '../types';
1920
import type {
@@ -1185,6 +1186,18 @@ class ServiceImpl implements Service {
11851186
`${updateLock.lockPath(appId, serviceName)}:/tmp/balena`,
11861187
];
11871188
}
1189+
1190+
public logLevel(): LogLevel {
1191+
switch (this.config.environment['BALENA_LOGS_LEVEL']) {
1192+
case 'none':
1193+
return LogLevel.None;
1194+
case 'err':
1195+
case 'error':
1196+
return LogLevel.Err;
1197+
default:
1198+
return LogLevel.Info;
1199+
}
1200+
}
11881201
}
11891202

11901203
export const Service = ServiceImpl;

src/compose/types/service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type Dockerode from 'dockerode';
33
import { isAbsolute } from 'path';
44

55
import type { PortMap } from '../ports';
6+
import type { LogLevel } from '../../logging/types';
67

78
export interface ComposeHealthcheck {
89
test: string | string[];
@@ -392,4 +393,5 @@ export interface Service {
392393
containerIds: Dictionary<string>;
393394
}): Dockerode.ContainerCreateOptions;
394395
handoverCompleteFullPathsOnHost(): string[];
396+
logLevel(): LogLevel;
395397
}

src/lib/supervisor-metadata.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ const SUPERVISOR_APPS: { [arch: string]: string } = {
1818
rpi: '6822565f766e413e96d9bebe2227cdcc',
1919
};
2020

21-
export function isSupervisorApp(appUuid: string): boolean {
22-
return Object.values(SUPERVISOR_APPS).some((uuid) => uuid === appUuid);
23-
}
24-
2521
/**
2622
* Check if the supervisor in the target state belongs to the known
2723
* supervisors

src/logging/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { LogType } from '../lib/log-types';
66
import { BalenaLogBackend } from './balena-backend';
77
import { LocalLogBackend } from './local-backend';
88
import type { LogBackend } from './log-backend';
9-
import type { LogMessage } from './types';
9+
import { LogLevel, type LogMessage } from './types';
1010
import logMonitor from './monitor';
1111

1212
import * as globalEventBus from '../event-bus';
@@ -126,18 +126,31 @@ export function logSystemMessage(
126126
}
127127
}
128128

129-
type ServiceInfo = { serviceId: number };
130-
export function attach(containerId: string, { serviceId }: ServiceInfo): void {
129+
type ServiceInfo = { serviceId: number; logLevel: LogLevel };
130+
export function attach(
131+
containerId: string,
132+
{ serviceId, logLevel }: ServiceInfo,
133+
): void {
131134
// First detect if we already have an attached log stream
132135
// for this container
133136
if (logMonitor.isAttached(containerId)) {
134137
return;
135138
}
136139

140+
// skip service attach LogLevel is None
141+
if (logLevel === LogLevel.None) {
142+
return;
143+
}
144+
137145
// We do not grab a container lock here as we are only adding it to the list of container ids to monitor
138146
// and not directly affecting the container itself, and grabbing the lock can cause a delay which means
139147
// that early messages logged from the container are missed and never sent to the backend.
140148
logMonitor.attach(containerId, async (message) => {
149+
// ignore non stderr messages for an error only service
150+
if (logLevel === LogLevel.Err && !message.isStdErr) {
151+
return;
152+
}
153+
141154
await log({ ...message, serviceId });
142155
});
143156
}

src/logging/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ type ContainerLogMessage = BaseLogMessage & {
1111
isSystem?: false;
1212
};
1313
export type LogMessage = SystemLogMessage | ContainerLogMessage;
14+
15+
export enum LogLevel {
16+
None,
17+
Info,
18+
Err,
19+
}

src/supervisor.ts

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ import * as firewall from './lib/firewall';
1919
import * as constants from './lib/constants';
2020
import * as uname from './lib/uname';
2121

22-
// FIXME: remove when we release supervisor v19
23-
import * as servicesManager from './compose/service-manager';
24-
import * as supervisorMetadata from './lib/supervisor-metadata';
25-
2622
const startupConfigFields: config.ConfigKey[] = [
2723
'uuid',
2824
'listenPort',
@@ -36,42 +32,6 @@ const startupConfigFields: config.ConfigKey[] = [
3632
'legacyAppsPresent',
3733
];
3834

39-
// This function removes any `helios` overrides in order to restore
40-
// the supervisor functionality as the only service.
41-
//
42-
// - It removes the port/endpoint overrides
43-
// - It stops helios
44-
//
45-
// Returns true if the changes were applied
46-
//
47-
// FIXME: remove when we release supervisor v19
48-
async function recoverFromOverrides() {
49-
const { listenPortOverride, apiEndpointOverride } = await config.getMany([
50-
'listenPortOverride',
51-
'apiEndpointOverride',
52-
]);
53-
if (listenPortOverride != null || apiEndpointOverride != null) {
54-
// stop helios-api
55-
const [heliosApi] = (
56-
await servicesManager.getAll(`service-name=helios-api`)
57-
).filter(
58-
(svc) =>
59-
svc.appUuid != null && supervisorMetadata.isSupervisorApp(svc.appUuid),
60-
);
61-
if (heliosApi != null) {
62-
await servicesManager.kill(heliosApi, { wait: true });
63-
}
64-
65-
log.info('Removing supervisor overrides');
66-
await db.models('config').del().where({ key: 'listenPortOverride' });
67-
await db.models('config').del().where({ key: 'apiEndpointOverride' });
68-
69-
// terminate the process
70-
log.info('restarting');
71-
process.exit(1);
72-
}
73-
}
74-
7535
export class Supervisor {
7636
private api: SupervisorAPI;
7737

@@ -80,10 +40,6 @@ export class Supervisor {
8040

8141
await db.initialized();
8242
await config.initialized();
83-
84-
// FIXME: remove when we release supervisor v19
85-
await recoverFromOverrides();
86-
8743
await avahi.initialized();
8844
log.debug('Starting logging infrastructure');
8945
await logger.initialized();

0 commit comments

Comments
 (0)