Skip to content
Merged
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
3 changes: 2 additions & 1 deletion nexus/db-model/src/schema_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
///
/// This must be updated when you change the database schema. Refer to
/// schema/crdb/README.adoc in the root of this repository for details.
pub const SCHEMA_VERSION: Version = Version::new(259, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(260, 0, 0);

/// List of all past database schema versions, in *reverse* order
///
Expand All @@ -28,6 +28,7 @@ pub static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
// | leaving the first copy as an example for the next person.
// v
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
KnownVersion::new(260, "ereport-trim-serial-trailing-nulls"),
KnownVersion::new(259, "vmm-failure-reason"),
KnownVersion::new(258, "lookup-unmarked-ereports-by-class"),
KnownVersion::new(257, "add-disk-adoption-requests"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use super::super::schema::{DataMigrationFns, MigrationContext};
use futures::future::BoxFuture;
use pretty_assertions::assert_eq;
use uuid::Uuid;

// Ereport restart IDs — one per test case.
const CLEAN_SERIAL: Uuid =
Uuid::from_u128(25800001_0000_0000_0000_000000000001);
const NULL_PADDED_SERIAL: Uuid =
Uuid::from_u128(25800001_0000_0000_0000_000000000002);
const NULL_SERIAL: Uuid = Uuid::from_u128(25800001_0000_0000_0000_000000000003);

const COLLECTOR: Uuid = Uuid::from_u128(25800002_0000_0000_0000_000000000001);

pub(crate) fn checks() -> DataMigrationFns {
DataMigrationFns::new().before(before).after(after)
}

fn before<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> {
Box::pin(async move {
// Insert ereport rows with various serial_number values:
// 1. A clean serial number with no trailing nulls.
// 2. A serial number with trailing null bytes.
// 3. A NULL serial_number (should remain NULL).
ctx.client
.batch_execute(&format!(
"
INSERT INTO omicron.public.ereport (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Lovely test cases!

restart_id, ena, time_collected, collector_id,
serial_number, report, reporter, slot_type, slot
) VALUES
('{CLEAN_SERIAL}', 1, now(), '{COLLECTOR}',
'BRM42220031',
'{{}}', 'sp', 'sled', 0),
('{NULL_PADDED_SERIAL}', 1, now(), '{COLLECTOR}',
E'2EKRJYKV\\x00\\x00\\x00',
'{{}}', 'sp', 'sled', 1),
('{NULL_SERIAL}', 1, now(), '{COLLECTOR}',
NULL,
'{{}}', 'sp', 'sled', 2);
"
))
.await
.expect(
"failed to insert test data for ereport-trim-serial-trailing-nulls"
);

// Verify that the null-padded serial number actually has the expected
// nulls in it.
let rows = ctx
.client
.query(
&format!(
"SELECT restart_id, serial_number
FROM omicron.public.ereport
WHERE restart_id IN (
'{CLEAN_SERIAL}',
'{NULL_PADDED_SERIAL}',
'{NULL_SERIAL}'
)
ORDER BY restart_id"
),
&[],
)
.await
.expect("failed to query ereport rows after migration");

assert_eq!(rows.len(), 3, "expected 3 test ereport rows");

for row in &rows {
let id: Uuid = row.get("restart_id");
let serial: Option<String> = row.get("serial_number");

match id {
id if id == CLEAN_SERIAL => {
assert_eq!(serial.as_deref(), Some("BRM42220031"),);
}
id if id == NULL_PADDED_SERIAL => {
assert_eq!(
serial.as_deref(),
Some("2EKRJYKV\0\0\0"),
"2EKRJYKV should be null-padded",
);
}
id if id == NULL_SERIAL => {
assert_eq!(serial, None, "NULL serial_number be None");
}
other => panic!("unexpected restart_id: {other}"),
}
}
})
}

fn after<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> {
Box::pin(async move {
// Verify the serial numbers after the migration.
let rows = ctx
.client
.query(
&format!(
"SELECT restart_id, serial_number
FROM omicron.public.ereport
WHERE restart_id IN (
'{CLEAN_SERIAL}',
'{NULL_PADDED_SERIAL}',
'{NULL_SERIAL}'
)
ORDER BY restart_id"
),
&[],
)
.await
.expect("failed to query ereport rows after migration");

assert_eq!(rows.len(), 3, "expected 3 test ereport rows");

for row in &rows {
let id: Uuid = row.get("restart_id");
let serial: Option<String> = row.get("serial_number");

match id {
id if id == CLEAN_SERIAL => {
assert_eq!(
serial.as_deref(),
Some("BRM42220031"),
"clean serial number should be unchanged"
);
}
id if id == NULL_PADDED_SERIAL => {
assert_eq!(
serial.as_deref(),
Some("2EKRJYKV"),
"serial with trailing NUL bytes should be trimmed"
);
}
id if id == NULL_SERIAL => {
assert_eq!(
serial, None,
"NULL serial_number should remain NULL"
);
}
other => panic!("unexpected restart_id: {other}"),
}
}

// Clean up test data.
ctx.client
.batch_execute(&format!(
"
DELETE FROM omicron.public.ereport
WHERE restart_id IN (
'{CLEAN_SERIAL}',
'{NULL_PADDED_SERIAL}',
'{NULL_SERIAL}'
);
"
))
.await
.unwrap();
})
}
2 changes: 2 additions & 0 deletions nexus/tests/integration_tests/data_migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mod delete_nexus_default_allow_firewall_rule;
mod disk_types;
mod drop_uninitialized_svc_enabled_not_online_state;
mod ereport_everyone_gets_a_slot;
mod ereport_trim_serial_trailing_nulls;
mod fix_leaked_bp_oximeter_read_policy_rows;
mod fix_session_token_column_order;
mod inv_clear_mupdate_override;
Expand Down Expand Up @@ -90,6 +91,7 @@ pub(crate) fn get_migration_checks() -> BTreeMap<Version, DataMigrationFns> {
register!(delete_nexus_default_allow_firewall_rule);
register!(drop_uninitialized_svc_enabled_not_online_state);
register!(bgp_unnumbered_peer_cleanup);
register!(ereport_trim_serial_trailing_nulls);

map
}
2 changes: 1 addition & 1 deletion schema/crdb/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8626,7 +8626,7 @@ INSERT INTO omicron.public.db_metadata (
version,
target_version
) VALUES
(TRUE, NOW(), NOW(), '259.0.0', NULL)
(TRUE, NOW(), NOW(), '260.0.0', NULL)
ON CONFLICT DO NOTHING;

COMMIT;
12 changes: 12 additions & 0 deletions schema/crdb/ereport-trim-serial-trailing-nulls/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Strip trailing NUL (\0) characters from the serial_number column in the
-- ereport table. The service processor null-pads OXV2 serial numbers. MGS
-- was stripping these trailing NULLs when collecting the inventory, but
-- not when ingesting ereports. This was fixed in
-- https://github.com/oxidecomputer/management-gateway-service#491
Comment thread
hawkw marked this conversation as resolved.
-- so we can now update any previously-nully ereport serials to match.
SET LOCAL disallow_full_table_scans = off;

UPDATE omicron.public.ereport
SET serial_number = rtrim(serial_number, chr(0))
WHERE serial_number IS NOT NULL
AND serial_number != rtrim(serial_number, chr(0));
Loading