From 4b6ec3fb03d866bbe5d59ac0e4bf0d9eb259eb22 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 26 Aug 2024 09:44:56 +0200 Subject: [PATCH] CheckSchema: Verify intermediate schema upgrades When skipping a version for an Icinga DB upgrade, all intermediate upgrade steps must be taken. While this is already stated in the documentation, it might be overlooked. This happened for one community user, upgrading from v1.1.0 to v1.2.0, skipping the intermediate schema upgrade for v1.1.1. > https://community.icinga.com/t/icingadb-failing-exactly-5-minutes-after-start/13955 First, the necessity for all upgrades in their release order was made more prominent in the documentation, hoping that less users would ignore this when skimming the upgrade docs. However, the real change here is adding another check to the CheckSchema function, verifying that all schema upgrades between the lowest known version and the highest known version in the icingadb_schema table exists. If an intermediate schema upgrade was skipped, as in the thread above, this raises a descriptive error. --- doc/04-Upgrading.md | 5 ++++- pkg/icingadb/schema.go | 31 ++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/doc/04-Upgrading.md b/doc/04-Upgrading.md index 74382e58f..8c97369a3 100644 --- a/doc/04-Upgrading.md +++ b/doc/04-Upgrading.md @@ -1,7 +1,10 @@ # Upgrading Icinga DB Specific version upgrades are described below. Please note that version upgrades are incremental. -If you are upgrading across multiple versions, make sure to follow the steps for each of them. + +If you are upgrading across multiple versions, make sure to follow the steps for each version in order of release. +For example, when upgrading from 1.1.0 to 1.2.0, +follow all instructions for upgrading to 1.1.1, then all for 1.2.0, including schema upgrades. ## Upgrading to Icinga DB v1.2.0 diff --git a/pkg/icingadb/schema.go b/pkg/icingadb/schema.go index 9aa7374e9..8eefc1df4 100644 --- a/pkg/icingadb/schema.go +++ b/pkg/icingadb/schema.go @@ -16,6 +16,11 @@ const ( ) // CheckSchema asserts the database schema of the expected version being present. +// +// Icinga DB uses incremental schema updates. Each schema version is identified by a continuous rising positive integer. +// With the initial schema import, the schema version of that time will be inserted in a row within the icingadb_schema +// table. Each subsequent schema update inserts another row with its version number. To have a consistent database +// schema, each schema update must be applied. NOTE: This might change in the future. func CheckSchema(ctx context.Context, db *database.DB) error { var expectedDbSchemaVersion uint16 switch db.DriverName() { @@ -25,13 +30,13 @@ func CheckSchema(ctx context.Context, db *database.DB) error { expectedDbSchemaVersion = expectedPostgresSchemaVersion } - var version uint16 + var versions []uint16 err := retry.WithBackoff( ctx, func(ctx context.Context) (err error) { - query := "SELECT version FROM icingadb_schema ORDER BY id DESC LIMIT 1" - err = db.QueryRowxContext(ctx, query).Scan(&version) + query := "SELECT version FROM icingadb_schema ORDER BY version ASC" + err = db.SelectContext(ctx, &versions, query) if err != nil { err = database.CantPerformQuery(err, query) } @@ -44,13 +49,29 @@ func CheckSchema(ctx context.Context, db *database.DB) error { return errors.Wrap(err, "can't check database schema version") } - if version != expectedDbSchemaVersion { + if len(versions) == 0 { + return fmt.Errorf("no database schema version is stored in the database") + } + + // Check if each schema update between the initial import and the latest version was applied or, in other words, + // that no schema update was left out. The loop goes over the ascending sorted array of schema versions, verifying + // that each element's successor is the increment of this version, ensuring no gaps in between. + for i := 0; i < len(versions)-1; i++ { + if versions[i] != versions[i+1]-1 { + return fmt.Errorf( + "incomplete database schema upgrade: intermediate version v%d is missing,"+ + " please make sure you have applied all database migrations after upgrading Icinga DB", + versions[i]+1) + } + } + + if latestVersion := versions[len(versions)-1]; latestVersion != expectedDbSchemaVersion { // Since these error messages are trivial and mostly caused by users, we don't need // to print a stack trace here. However, since errors.Errorf() does this automatically, // we need to use fmt instead. return fmt.Errorf( "unexpected database schema version: v%d (expected v%d), please make sure you have applied all database"+ - " migrations after upgrading Icinga DB", version, expectedDbSchemaVersion, + " migrations after upgrading Icinga DB", latestVersion, expectedDbSchemaVersion, ) }