mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-18 21:27:54 +00:00
Mount data and state partitions on container startup
Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
parent
49ee1042a8
commit
4c948c8854
@ -22,6 +22,6 @@ testfs:
|
||||
# when restoring the filesystem
|
||||
cleanup:
|
||||
- /data/database.sqlite
|
||||
- /data/apps.json.preloaded
|
||||
- /mnt/data/apps.json.preloaded
|
||||
- /mnt/root/tmp/balena-supervisor/**/*.lock
|
||||
- /mnt/boot/splash/*.png
|
||||
|
@ -101,6 +101,9 @@ services:
|
||||
ROOT_MOUNTPOINT: /mnt/root
|
||||
BOOT_MOUNTPOINT: /mnt/boot
|
||||
HOST_OS_VERSION_PATH: /mnt/boot/os-release
|
||||
STATE_MOUNTPOINT: /mnt/state
|
||||
DATA_MOUNTPOINT: /mnt/data
|
||||
DATABASE_PATH: /data/database.sqlite
|
||||
# Set required mounts as tmpfs or volumes here
|
||||
# if specific files need to be backed up between tests,
|
||||
# make sure to add them to the `testfs` configuration under
|
||||
@ -109,6 +112,8 @@ services:
|
||||
- /data
|
||||
- /mnt/root
|
||||
- /mnt/boot
|
||||
- /mnt/state
|
||||
- /mnt/data
|
||||
|
||||
volumes:
|
||||
dbus:
|
||||
|
2
entry.sh
2
entry.sh
@ -33,7 +33,7 @@ fi
|
||||
# NOTE: this won't be necessary once the supervisor can update
|
||||
# itself, as using the label io.balena.features.journal-logs will
|
||||
# achieve the same objective
|
||||
if { [ ! -d /run/log/journal ] || [ -L /run/log/journal ]; } && [ -s "${ROOT_MOUNTPOINT}"/etc/machine-id ]; then
|
||||
if { [ ! -d /run/log/journal ] || [ -L /run/log/journal ]; } && [ -s "${STATE_MOUNTPOINT}"/machine-id ]; then
|
||||
# Only enter here if the directory does not exist or the location exists and is a symlink
|
||||
# (note that test -d /symlink-to-dir will return true)
|
||||
|
||||
|
@ -79,3 +79,23 @@ export HOST_OS_VERSION_PATH="${BOOT_MOUNTPOINT}/os-release"
|
||||
# CONFIG_MOUNT_POINT is set to /boot/config.json in Dockerfile.template,
|
||||
# but that's a legacy mount provided by host and we should override it.
|
||||
export CONFIG_MOUNT_POINT="${BOOT_MOUNTPOINT}/config.json"
|
||||
|
||||
# Set state mountpoint
|
||||
STATE_MOUNTPOINT="/mnt/state"
|
||||
setup_then_mount "state" "${STATE_MOUNTPOINT}"
|
||||
export STATE_MOUNTPOINT
|
||||
|
||||
# Set data mountpoint
|
||||
DATA_MOUNTPOINT="/mnt/data"
|
||||
setup_then_mount "data" "${DATA_MOUNTPOINT}"
|
||||
export DATA_MOUNTPOINT
|
||||
|
||||
# Mount the Supervisor database directory to a more accessible & backwards compatible location.
|
||||
# TODO: DB should be moved to a managed volume and mounted to /data in-container.
|
||||
# Handle the case of such a Supervisor volume already existing.
|
||||
# NOTE: After this PR, it should be good to remove the OS's /data/database.sqlite mount.
|
||||
if [ ! -f /data/database.sqlite ]; then
|
||||
mkdir -p "${DATA_MOUNTPOINT}/resin-data/balena-supervisor"
|
||||
mount -o bind,shared "${DATA_MOUNTPOINT}"/resin-data/balena-supervisor /data
|
||||
fi
|
||||
export DATABASE_PATH="/data/database.sqlite"
|
@ -4,7 +4,7 @@ import { VolumeInspectInfo } from 'dockerode';
|
||||
|
||||
import { isNotFoundError, InternalInconsistencyError } from '../lib/errors';
|
||||
import { safeRename } from '../lib/fs-utils';
|
||||
import { pathOnRoot } from '../lib/host-utils';
|
||||
import { pathOnData } from '../lib/host-utils';
|
||||
import { docker } from '../lib/docker-utils';
|
||||
import * as LogTypes from '../lib/log-types';
|
||||
import log from '../lib/supervisor-console';
|
||||
@ -97,8 +97,8 @@ export async function createFromPath(
|
||||
.getVolume(Volume.generateDockerName(volume.appId, volume.name))
|
||||
.inspect();
|
||||
|
||||
const volumePath = pathOnRoot(
|
||||
path.join('mnt/data', ...inspect.Mountpoint.split(path.sep).slice(3)),
|
||||
const volumePath = pathOnData(
|
||||
path.join(...inspect.Mountpoint.split(path.sep).slice(3)),
|
||||
);
|
||||
|
||||
await safeRename(oldPath, volumePath);
|
||||
|
@ -7,6 +7,9 @@ const supervisorNetworkInterface = 'supervisor0';
|
||||
const rootMountPoint = checkString(process.env.ROOT_MOUNTPOINT) || '/mnt/root';
|
||||
const withRootMount = (p: string) => path.join(rootMountPoint, p);
|
||||
const bootMountPoint = checkString(process.env.BOOT_MOUNTPOINT) || '/mnt/boot';
|
||||
const stateMountPoint =
|
||||
checkString(process.env.STATE_MOUNTPOINT) || '/mnt/state';
|
||||
const dataMountPoint = checkString(process.env.DATA_MOUNTPOINT) || '/mnt/data';
|
||||
|
||||
const constants = {
|
||||
// Root overlay paths
|
||||
@ -23,13 +26,13 @@ const constants = {
|
||||
checkString(process.env.HOST_OS_VERSION_PATH) ||
|
||||
withRootMount('/etc/os-release'),
|
||||
// Data paths
|
||||
dataMountPoint,
|
||||
databasePath:
|
||||
checkString(process.env.DATABASE_PATH) || '/data/database.sqlite',
|
||||
appsJsonPath:
|
||||
process.env.APPS_JSON_PATH || withRootMount('/mnt/data/apps.json'),
|
||||
migrationBackupFile: 'backup.tgz',
|
||||
appsJsonPath: path.join(dataMountPoint, 'apps.json'),
|
||||
migrationBackupFile: path.join(dataMountPoint, 'backup.tgz'),
|
||||
// State paths
|
||||
stateMountPoint: '/mnt/state',
|
||||
stateMountPoint,
|
||||
// Other constants: network, Engine, /sys
|
||||
containerId: checkString(process.env.SUPERVISOR_CONTAINER_ID) || undefined,
|
||||
dockerSocket: process.env.DOCKER_SOCKET || '/var/run/docker.sock',
|
||||
|
@ -30,6 +30,17 @@ export const pathExistsOnRoot = async (p: string) =>
|
||||
// This path is accessible from within the Supervisor container
|
||||
export const pathOnBoot = withBase(constants.bootMountPoint);
|
||||
|
||||
// Returns an absolute path starting from the hostOS data partition
|
||||
// This path is accessible from within the Supervisor container
|
||||
export const pathOnData = withBase(constants.dataMountPoint);
|
||||
|
||||
// Returns an absolute path starting from the hostOS state partition
|
||||
// This path is accessible from within the Supervisor container
|
||||
export const pathOnState = withBase(constants.stateMountPoint);
|
||||
|
||||
export const pathExistsOnState = async (p: string) =>
|
||||
await exists(pathOnState(p));
|
||||
|
||||
class CodedError extends Error {
|
||||
constructor(msg: string, readonly code: number | string) {
|
||||
super(msg);
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from './errors';
|
||||
import { docker } from './docker-utils';
|
||||
import { log } from './supervisor-console';
|
||||
import { pathOnRoot } from './host-utils';
|
||||
import { pathOnData } from './host-utils';
|
||||
import Volume from '../compose/volume';
|
||||
import * as logger from '../logger';
|
||||
import type {
|
||||
@ -35,9 +35,7 @@ async function createVolumeFromLegacyData(
|
||||
appUuid: string,
|
||||
): Promise<Volume | void> {
|
||||
const name = defaultLegacyVolume();
|
||||
const legacyPath = pathOnRoot(
|
||||
path.join('mnt/data/resin-data', appId.toString()),
|
||||
);
|
||||
const legacyPath = pathOnData(path.join('resin-data', appId.toString()));
|
||||
|
||||
try {
|
||||
return await volumeManager.createFromPath(
|
||||
|
@ -10,17 +10,14 @@ import * as constants from './constants';
|
||||
import { BackupError, isNotFoundError } from './errors';
|
||||
import { exec, exists, mkdirp, unlinkAll } from './fs-utils';
|
||||
import { log } from './supervisor-console';
|
||||
import { pathOnRoot } from './host-utils';
|
||||
import { pathOnData } from './host-utils';
|
||||
|
||||
export async function loadBackupFromMigration(
|
||||
targetState: TargetState,
|
||||
retryDelay: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const migrationExists = await exists(
|
||||
pathOnRoot(path.join('mnt/data', constants.migrationBackupFile)),
|
||||
);
|
||||
if (!migrationExists) {
|
||||
if (!(await exists(constants.migrationBackupFile))) {
|
||||
return;
|
||||
}
|
||||
log.info('Migration backup detected');
|
||||
@ -39,12 +36,12 @@ export async function loadBackupFromMigration(
|
||||
|
||||
const volumes = release?.volumes ?? {};
|
||||
|
||||
const backupPath = pathOnRoot('mnt/data/backup');
|
||||
const backupPath = pathOnData('backup');
|
||||
// We clear this path in case it exists from an incomplete run of this function
|
||||
await unlinkAll(backupPath);
|
||||
await mkdirp(backupPath);
|
||||
await exec(`tar -xzf backup.tgz -C ${backupPath}`, {
|
||||
cwd: pathOnRoot('mnt/data'),
|
||||
cwd: pathOnData(),
|
||||
});
|
||||
|
||||
for (const volumeName of await fs.readdir(backupPath)) {
|
||||
@ -84,9 +81,7 @@ export async function loadBackupFromMigration(
|
||||
}
|
||||
|
||||
await unlinkAll(backupPath);
|
||||
await unlinkAll(
|
||||
pathOnRoot(path.join('mnt/data', constants.migrationBackupFile)),
|
||||
);
|
||||
await unlinkAll(constants.migrationBackupFile);
|
||||
} catch (err) {
|
||||
log.error(`Error restoring migration backup, retrying: ${err}`);
|
||||
|
||||
|
@ -27,7 +27,7 @@ export async function getStorageInfo(): Promise<{
|
||||
let total = 0;
|
||||
// First we find the block device which the data partition is part of
|
||||
for (const partition of fsInfo) {
|
||||
if (partition.mount === '/data') {
|
||||
if (new RegExp('/data').test(partition.mount)) {
|
||||
mainFs = partition.fs;
|
||||
total = partition.size;
|
||||
break;
|
||||
|
@ -5,14 +5,12 @@ import * as path from 'path';
|
||||
import * as Lock from 'rwlock';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
|
||||
import * as constants from './constants';
|
||||
import {
|
||||
ENOENT,
|
||||
UpdatesLockedError,
|
||||
InternalInconsistencyError,
|
||||
} from './errors';
|
||||
import { exists } from './fs-utils';
|
||||
import { pathOnRoot } from './host-utils';
|
||||
import { pathOnRoot, pathExistsOnState } from './host-utils';
|
||||
import * as config from '../config';
|
||||
import * as lockfile from './lockfile';
|
||||
import { NumericIdentifier } from '../types';
|
||||
@ -48,8 +46,7 @@ export function abortIfHUPInProgress({
|
||||
}): Promise<boolean | never> {
|
||||
return Promise.all(
|
||||
['rollback-health-breadcrumb', 'rollback-altboot-breadcrumb'].map(
|
||||
(filename) =>
|
||||
exists(pathOnRoot(path.join(constants.stateMountPoint, filename))),
|
||||
(filename) => pathExistsOnState(filename),
|
||||
),
|
||||
).then((existsArray) => {
|
||||
const anyExists = existsArray.some((e) => e);
|
||||
|
@ -7,7 +7,7 @@ import * as updateLock from '~/lib/update-lock';
|
||||
import { UpdatesLockedError } from '~/lib/errors';
|
||||
import * as config from '~/src/config';
|
||||
import * as lockfile from '~/lib/lockfile';
|
||||
import { pathOnRoot } from '~/lib/host-utils';
|
||||
import { pathOnRoot, pathOnState } from '~/lib/host-utils';
|
||||
|
||||
describe('lib/update-lock', () => {
|
||||
describe('abortIfHUPInProgress', () => {
|
||||
@ -16,7 +16,7 @@ describe('lib/update-lock', () => {
|
||||
'rollback-altboot-breadcrumb',
|
||||
];
|
||||
|
||||
const breadcrumbsDir = pathOnRoot('/mnt/state');
|
||||
const breadcrumbsDir = pathOnState();
|
||||
|
||||
const createBreadcrumb = (breadcrumb: string) =>
|
||||
testfs({
|
||||
|
Loading…
Reference in New Issue
Block a user