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

Keep last version of deployment #1960

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions src/libostree/ostree-deployment-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#pragma once

#include "ostree.h"
#include "ostree-deployment.h"

G_BEGIN_DECLS
Expand Down Expand Up @@ -54,9 +55,12 @@ struct _OstreeDeployment
gboolean finalization_locked;
char **overlay_initrds;
char *overlay_initrds_id;
gchar *version;
gboolean version_is_cached;
};

void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);
const char *_ostree_deployment_get_version (OstreeDeployment *self, OstreeRepo *repo);

void _ostree_deployment_set_overlay_initrds (OstreeDeployment *self, char **overlay_initrds);

Expand Down
28 changes: 28 additions & 0 deletions src/libostree/ostree-deployment.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ ostree_deployment_get_bootserial (OstreeDeployment *self)
return self->bootserial;
}

const char *
_ostree_deployment_get_version (OstreeDeployment *self,
OstreeRepo *repo)
{
g_return_val_if_fail (repo != NULL, NULL);

if (!self->version_is_cached)
{
/* Try extracting a version for this deployment. */
const gchar *csum = ostree_deployment_get_csum (self);

g_autoptr(GVariant) variant = NULL;
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum,
&variant, NULL))
return NULL;

g_autoptr(GVariant) metadata = g_variant_get_child_value (variant, 0);
g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_VERSION, "s", &self->version);

self->version_is_cached = TRUE;
}

return self->version;
}

/**
* ostree_deployment_get_bootconfig:
* @self: Deployment
Expand Down Expand Up @@ -255,6 +280,8 @@ ostree_deployment_clone (OstreeDeployment *self)
OstreeDeployment *ret = ostree_deployment_new (
self->index, self->osname, self->csum, self->deployserial, self->bootcsum, self->bootserial);

ret->version = g_strdup (self->version);

new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig);
ostree_deployment_set_bootconfig (ret, new_bootconfig);

Expand Down Expand Up @@ -324,6 +351,7 @@ ostree_deployment_finalize (GObject *object)
g_free (self->osname);
g_free (self->csum);
g_free (self->bootcsum);
g_free (self->version);
g_clear_object (&self->bootconfig);
g_clear_pointer (&self->origin, g_key_file_unref);
g_strfreev (self->overlay_initrds);
Expand Down
34 changes: 12 additions & 22 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -2005,24 +2005,9 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
if (val == NULL)
return glnx_throw (error, "No PRETTY_NAME or ID in /etc/os-release");

g_autofree char *deployment_version = NULL;
const gchar *deployment_version = NULL;
if (repo)
{
/* Try extracting a version for this deployment. */
const char *csum = ostree_deployment_get_csum (deployment);
g_autoptr (GVariant) variant = NULL;
g_autoptr (GVariant) metadata = NULL;

/* XXX Copying ot_admin_checksum_version() + bits from
* ot-admin-builtin-status.c. Maybe this should be
* public API in libostree? */
if (ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum, &variant, NULL))
{
metadata = g_variant_get_child_value (variant, 0);
(void)g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_VERSION, "s",
&deployment_version);
}
}
deployment_version = _ostree_deployment_get_version (deployment, repo);

/* XXX The SYSLINUX bootloader backend actually parses the title string
* (specifically, it looks for the substring "(ostree"), so further
Expand Down Expand Up @@ -3799,6 +3784,11 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
g_variant_builder_add (builder, "{sv}", "overlay-initrds",
g_variant_new_strv ((const char *const *)opts->overlay_initrds, -1));

/* Proxy across any flags */
if (opts && opts->finalization_flags)
g_variant_builder_add (builder, "{sv}", "write-deployment-flags",
g_variant_new_uint32 ((guint32)opts->finalization_flags));

const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
return FALSE;
Expand Down Expand Up @@ -3992,14 +3982,14 @@ _ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancel
staged->staged = FALSE;
g_ptr_array_remove_index (self->deployments, 0);

/* TODO: Proxy across flags too?
r4f4 marked this conversation as resolved.
Show resolved Hide resolved
*
* But note that we always use NO_CLEAN to avoid adding more latency at
OstreeSysrootSimpleWriteDeploymentFlags flags = 0;
g_variant_lookup (self->staged_deployment_data, "write-deployment-flags", "u", &flags);
/*
* Note that we always use NO_CLEAN to avoid adding more latency at
* shutdown, and also because e.g. rpm-ostree wants to own the cleanup
* process.
*/
OstreeSysrootSimpleWriteDeploymentFlags flags
= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
if (!ostree_sysroot_simple_write_deployment (self, ostree_deployment_get_osname (staged), staged,
merge_deployment, flags, cancellable, error))
return FALSE;
Expand Down
49 changes: 49 additions & 0 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,9 @@ ostree_sysroot_init_osname (OstreeSysroot *self, const char *osname, GCancellabl
* specified, then no cleanup will be performed after adding the
* deployment. Make sure to call ostree_sysroot_cleanup() sometime
* later, instead.
*
* If %OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION is
* specified, then the previous version will not be garbage collected.
r4f4 marked this conversation as resolved.
Show resolved Hide resolved
*/
gboolean
ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osname,
Expand All @@ -1920,6 +1923,8 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
= (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING) > 0;
const gboolean retain_rollback
= (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK) > 0;
const gboolean retain_previous
= (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION) > 0;
gboolean retain = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN) > 0;

g_autoptr (GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot);
Expand All @@ -1941,6 +1946,34 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
if (!booted_deployment && !merge_deployment && (retain_pending || retain_rollback))
retain = TRUE;

/* tracks current versioned deployment */
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
const gchar *new_version = _ostree_deployment_get_version (new_deployment, repo);

gboolean retained_previous_version = FALSE;

/* we never prune booted and merge deployments, so if they exist and are of a
* different version from `new_version`, they already fulfill the criteria of
* retaining the previous version */
if (booted_deployment)
{
const gchar *booted_version =
_ostree_deployment_get_version (booted_deployment, repo);
retained_previous_version = (g_strcmp0 (booted_version, new_version) != 0);
}

/* checking also that booted and merge are not the same although that's not a
* big deal since we cache the version now (though this will still work in
* the NULL case)
*/
if (!retained_previous_version && merge_deployment &&
!ostree_deployment_equal (merge_deployment, booted_deployment))
{
const gchar *merge_version =
_ostree_deployment_get_version (merge_deployment, repo);
retained_previous_version = (g_strcmp0 (merge_version, new_version) != 0);
}

/* tracks when we come across the booted deployment */
gboolean before_booted = TRUE;
gboolean before_merge = TRUE;
Expand All @@ -1962,6 +1995,13 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
* deployments, fall back on merge deployment */
const gboolean passed_crossover = booted_deployment ? !before_booted : !before_merge;

gboolean is_previous_version = FALSE;
if (passed_crossover && osname_matches && !retained_previous_version)
{
const gchar *version = _ostree_deployment_get_version (deployment, repo);
is_previous_version = (g_strcmp0 (version, new_version) != 0);
}

/* Retain deployment if:
* - we're explicitly asked to, or
* - it's pinned
Expand All @@ -1974,6 +2014,15 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
|| (retain_pending && !passed_crossover) || (is_booted || is_merge)
|| (retain_rollback && passed_crossover))
g_ptr_array_add (new_deployments, g_object_ref (deployment));
/*
* - we're keeping the previous version deployment
*/
else if (retain_previous && !retained_previous_version && is_previous_version)
{
g_ptr_array_add (new_deployments, g_object_ref (deployment));
/* Just keep one previous version */
retained_previous_version = TRUE;
}

/* add right after booted/merge deployment */
if (!added_new && passed_crossover)
Expand Down
7 changes: 6 additions & 1 deletion src/libostree/ostree-sysroot.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,17 @@ _OSTREE_PUBLIC
gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, int fd, char **out_checksum,
GCancellable *cancellable, GError **error);

/**
* finalization_flags: only used in the staging path
*/
typedef struct
{
/* If set to true, then this deployment will be staged but "locked" and not automatically applied
* on reboot. */
gboolean locked;
gboolean unused_bools[7];
int unused_ints[8];
int finalization_flags;
int unused_ints[7];
char **override_kernel_argv;
char **overlay_initrds;
gpointer unused_ptrs[6];
Expand Down Expand Up @@ -259,6 +263,7 @@ typedef enum
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN = (1 << 2),
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING = (1 << 3),
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK = (1 << 4),
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION = (1 << 5),
} OstreeSysrootSimpleWriteDeploymentFlags;

_OSTREE_PUBLIC
Expand Down
8 changes: 7 additions & 1 deletion src/ostree/ot-admin-builtin-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ static gboolean opt_stage;
static gboolean opt_lock_finalization;
static gboolean opt_retain_pending;
static gboolean opt_retain_rollback;
static gboolean opt_retain_previous;
static gboolean opt_not_as_default;
static gboolean opt_no_prune;
static gboolean opt_no_merge;
Expand Down Expand Up @@ -65,6 +66,8 @@ static GOptionEntry options[] = {
"Do not delete pending deployments", NULL },
{ "retain-rollback", 0, 0, G_OPTION_ARG_NONE, &opt_retain_rollback,
"Do not delete rollback deployments", NULL },
{ "retain-previous-version", 0, 0, G_OPTION_ARG_NONE, &opt_retain_previous,
"Do not delete previous deployment if version differs from new deployment", NULL },
{ "not-as-default", 0, 0, G_OPTION_ARG_NONE, &opt_not_as_default,
"Append rather than prepend new deployment", NULL },
{ "karg-proc-cmdline", 0, 0, G_OPTION_ARG_NONE, &opt_kernel_proc_cmdline,
Expand Down Expand Up @@ -239,6 +242,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
.locked = opt_lock_finalization,
.override_kernel_argv = kargs_strv,
.overlay_initrds = overlay_initrd_chksums ? (char **)overlay_initrd_chksums->pdata : NULL,
.finalization_flags = opt_retain_previous ? OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION : 0,
};

g_autoptr (OstreeDeployment) new_deployment = NULL;
Expand All @@ -265,7 +269,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED);
}
/* use old API if we can to exercise it in CI */
if (!(overlay_initrd_chksums || opt_lock_finalization))
if (!(overlay_initrd_chksums || opt_lock_finalization || opt_retain_previous))
{
if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment,
kargs_strv, &new_deployment, cancellable, error))
Expand Down Expand Up @@ -308,6 +312,8 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING;
if (opt_retain_rollback)
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
if (opt_retain_previous)
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION;
}

if (opt_not_as_default)
Expand Down
46 changes: 42 additions & 4 deletions tests/kolainst/destructive/staged-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ EOF
# xref https://github.com/coreos/coreos-assembler/pull/2814
systemctl mask --now zincati
# Create a synthetic commit for upgrade
ostree commit --no-bindings --parent="${commit}" -b staged-deploy -I --consume t
ostree commit --no-bindings --parent="${commit}" -b staged-deploy -I --consume t --add-metadata-string=version=foobar
newcommit=$(ostree rev-parse staged-deploy)
orig_mtime=$(stat -c '%.Y' /sysroot/ostree/deploy)
systemctl show -p SubState ostree-finalize-staged.path | grep -q waiting
Expand Down Expand Up @@ -169,8 +169,46 @@ EOF
ostree admin undeploy 1
echo "ok staged retained"

# Deploy a new version
commit=${host_commit}
ostree checkout -H ${commit} t
ostree commit --no-bindings --parent="${commit}" -b same-version -I --consume t --add-metadata-string=version=foobaz
ostree admin deploy same-version --retain-previous-version

# Cleanup refs
ostree refs --delete staged-deploy nonstaged-deploy same-version
echo "ok cleanup refs"

/tmp/autopkgtest-reboot "3"
;;
"3")
# Check currently deployed versions
rpm-ostree status

# Make a new commit with the same version as the previous reboot
commit=$(rpmostree_query_json '.deployments[0].checksum')
cd /ostree/repo/tmp
ostree checkout -H ${commit} t
ostree commit --no-bindings --parent="${commit}" -b same-version-again -I --consume t --add-metadata-string=version=foobaz
ostree admin deploy same-version-again --retain-previous-version

# Check that previous version was kept
ostree admin status > status.txt
test $(grep -Fce 'Version: ' status.txt) = 3
echo "ok previous version retained"

# Check also for the staging path
rpm-ostree cleanup -p
ostree admin deploy --stage same-version-again --retain-previous-version
ostree admin finalize-staged

# Check that previous version was kept
ostree admin status > status.txt
test $(grep -Fce 'Version: ' status.txt) = 3
echo "ok previous version retained during stage"

# Cleanup refs
ostree refs --delete staged-deploy nonstaged-deploy
ostree refs --delete same-version-again
echo "ok cleanup refs"

# Now finally, try breaking staged updates and verify that ostree-boot-complete fails on the next boot
Expand All @@ -193,9 +231,9 @@ WantedBy=multi-user.target
EOF
systemctl enable hackaround-cosa-systemd-unit-checks.service

/tmp/autopkgtest-reboot "3"
/tmp/autopkgtest-reboot "4"
;;
"3")
"4")
assert_file_has_content /run/ostree-boot-complete-status.txt 'error: ostree-finalize-staged.service failed on previous boot.*Operation not permitted'
echo "ok boot-complete.service"
;;
Expand Down
Loading