From bdcbd0ee411005bcc82f80fcbce16783ebae9d6d Mon Sep 17 00:00:00 2001 From: David Pacheco Date: Fri, 29 Sep 2023 13:18:40 -0700 Subject: [PATCH] for debugging --- Cargo.lock | 2 + nexus/db-model/src/inventory.rs | 58 ++++- nexus/db-model/src/schema.rs | 2 +- nexus/db-queries/src/authz/api_resources.rs | 55 +++++ nexus/db-queries/src/authz/omicron.polar | 10 + nexus/db-queries/src/authz/oso_generic.rs | 1 + .../db-queries/src/db/datastore/inventory.rs | 205 ++++++++++++++++++ nexus/db-queries/src/db/datastore/mod.rs | 1 + nexus/inventory/Cargo.toml | 1 + nexus/inventory/src/builder.rs | 8 +- nexus/inventory/src/lib.rs | 114 +--------- nexus/types/Cargo.toml | 1 + nexus/types/src/inventory.rs | 122 +++++++++++ nexus/types/src/lib.rs | 1 + schema/crdb/dbinit.sql | 4 +- 15 files changed, 465 insertions(+), 120 deletions(-) create mode 100644 nexus/db-queries/src/db/datastore/inventory.rs create mode 100644 nexus/types/src/inventory.rs diff --git a/Cargo.lock b/Cargo.lock index 83795604d7..3eaa039301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4416,6 +4416,7 @@ dependencies = [ "gateway-messages", "nexus-db-model", "nexus-db-queries", + "nexus-types", "strum", "uuid", ] @@ -4491,6 +4492,7 @@ dependencies = [ "chrono", "dns-service-client 0.1.0", "futures", + "gateway-client", "newtype_derive", "omicron-common 0.1.0", "omicron-passwords 0.1.0", diff --git a/nexus/db-model/src/inventory.rs b/nexus/db-model/src/inventory.rs index 84a5fa4ed2..7f1e160d46 100644 --- a/nexus/db-model/src/inventory.rs +++ b/nexus/db-model/src/inventory.rs @@ -4,11 +4,15 @@ use crate::impl_enum_type; use crate::schema::{ - hw_baseboard_id, inv_caboose, inv_root_of_trust, inv_service_processor, - sw_caboose, + hw_baseboard_id, inv_caboose, inv_collection, inv_root_of_trust, + inv_service_processor, sw_caboose, }; +use chrono::DateTime; +use chrono::Utc; use db_macros::Asset; +use diesel::expression::AsExpression; use nexus_types::identity::Asset; +use nexus_types::inventory::{BaseboardId, Collection, PowerState}; use uuid::Uuid; impl_enum_type!( @@ -26,6 +30,16 @@ impl_enum_type!( A2 => b"A2" ); +impl From for HwPowerState { + fn from(p: PowerState) -> Self { + match p { + PowerState::A0 => HwPowerState::A0, + PowerState::A1 => HwPowerState::A1, + PowerState::A2 => HwPowerState::A2, + } + } +} + impl_enum_type!( #[derive(SqlType, Debug, QueryId)] #[diesel(postgres_type(name = "hw_rot_slot"))] @@ -55,3 +69,43 @@ impl_enum_type!( RotSlotA => b"rot_slot_A" RotSlotB => b"rot_slot_B" ); + +#[derive(Queryable, Insertable, Clone, Debug, Selectable)] +#[diesel(table_name = inv_collection)] +pub struct InvCollection { + pub id: Uuid, + pub time_started: DateTime, + pub time_done: DateTime, + pub collector: String, + pub comment: String, +} + +impl<'a> From<&'a Collection> for InvCollection { + fn from(c: &'a Collection) -> Self { + InvCollection { + id: Uuid::new_v4(), + time_started: c.time_started, + time_done: c.time_done, + collector: c.collector.clone(), + comment: c.comment.clone(), + } + } +} + +#[derive(Queryable, Insertable, Clone, Debug, Selectable)] +#[diesel(table_name = hw_baseboard_id)] +pub struct HwBaseboardId { + pub id: Uuid, + pub part_number: String, + pub serial_number: String, +} + +impl<'a> From<&'a BaseboardId> for HwBaseboardId { + fn from(c: &'a BaseboardId) -> Self { + HwBaseboardId { + id: Uuid::new_v4(), + part_number: c.part_number.clone(), + serial_number: c.serial_number.clone(), + } + } +} diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index 014385c6b2..11b02c3a1f 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1160,7 +1160,7 @@ table! { time_collected -> Timestamptz, source -> Text, - baseboard_revision -> Int4, + baseboard_revision -> Int8, hubris_archive_id -> Text, power_state -> crate::HwPowerStateEnum, } diff --git a/nexus/db-queries/src/authz/api_resources.rs b/nexus/db-queries/src/authz/api_resources.rs index ec959e2907..b22fe1ac25 100644 --- a/nexus/db-queries/src/authz/api_resources.rs +++ b/nexus/db-queries/src/authz/api_resources.rs @@ -473,6 +473,61 @@ impl AuthorizedResource for DeviceAuthRequestList { } } +/// Synthetic resource used for modeling access to low-level hardware inventory +/// data +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Inventory; +pub const INVENTORY: Inventory = Inventory {}; + +impl oso::PolarClass for Inventory { + fn get_polar_class_builder() -> oso::ClassBuilder { + // Roles are not directly attached to Inventory + oso::Class::builder() + .with_equality_check() + .add_method( + "has_role", + |_: &Inventory, _actor: AuthenticatedActor, _role: String| { + false + }, + ) + .add_attribute_getter("fleet", |_| FLEET) + } +} + +impl AuthorizedResource for Inventory { + fn load_roles<'a, 'b, 'c, 'd, 'e, 'f>( + &'a self, + opctx: &'b OpContext, + datastore: &'c DataStore, + authn: &'d authn::Context, + roleset: &'e mut RoleSet, + ) -> futures::future::BoxFuture<'f, Result<(), Error>> + where + 'a: 'f, + 'b: 'f, + 'c: 'f, + 'd: 'f, + 'e: 'f, + { + load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset) + .boxed() + } + + fn on_unauthorized( + &self, + _: &Authz, + error: Error, + _: AnyActor, + _: Action, + ) -> Error { + error + } + + fn polar_class(&self) -> oso::Class { + Self::get_polar_class() + } +} + /// Synthetic resource describing the list of Certificates associated with a /// Silo #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/nexus/db-queries/src/authz/omicron.polar b/nexus/db-queries/src/authz/omicron.polar index 119eccc8e9..87fdf72f6a 100644 --- a/nexus/db-queries/src/authz/omicron.polar +++ b/nexus/db-queries/src/authz/omicron.polar @@ -365,6 +365,16 @@ resource DnsConfig { has_relation(fleet: Fleet, "parent_fleet", dns_config: DnsConfig) if dns_config.fleet = fleet; +# Describes the policy for reading and modifying low-level inventory +resource Inventory { + permissions = [ "read", "modify" ]; + relations = { parent_fleet: Fleet }; + "read" if "viewer" on "parent_fleet"; + "modify" if "admin" on "parent_fleet"; +} +has_relation(fleet: Fleet, "parent_fleet", inventory: Inventory) + if inventory.fleet = fleet; + # Describes the policy for accessing "/v1/system/ip-pools" in the API resource IpPoolList { permissions = [ diff --git a/nexus/db-queries/src/authz/oso_generic.rs b/nexus/db-queries/src/authz/oso_generic.rs index bcd7a42945..e642062ead 100644 --- a/nexus/db-queries/src/authz/oso_generic.rs +++ b/nexus/db-queries/src/authz/oso_generic.rs @@ -106,6 +106,7 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result { Database::get_polar_class(), DnsConfig::get_polar_class(), Fleet::get_polar_class(), + Inventory::get_polar_class(), IpPoolList::get_polar_class(), ConsoleSessionList::get_polar_class(), DeviceAuthRequestList::get_polar_class(), diff --git a/nexus/db-queries/src/db/datastore/inventory.rs b/nexus/db-queries/src/db/datastore/inventory.rs new file mode 100644 index 0000000000..cf42f2cc2a --- /dev/null +++ b/nexus/db-queries/src/db/datastore/inventory.rs @@ -0,0 +1,205 @@ +// 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::DataStore; +use crate::authz; +use crate::context::OpContext; +use crate::db; +use crate::db::error::public_error_from_diesel_pool; +use crate::db::error::ErrorHandler; +use crate::db::TransactionError; +use async_bb8_diesel::AsyncConnection; +use async_bb8_diesel::AsyncRunQueryDsl; +use async_bb8_diesel::PoolError; +use diesel::expression::AsExpression; +use diesel::query_dsl::methods::SelectDsl; +use diesel::IntoSql; +use nexus_db_model::HwBaseboardId; +use nexus_db_model::HwPowerState; +use nexus_db_model::HwPowerStateEnum; +use nexus_db_model::InvCollection; +use nexus_types::inventory::BaseboardId; +use nexus_types::inventory::Collection; +use omicron_common::api::external::Error; + +impl DataStore { + /// Store a complete inventory collection into the database + pub async fn inventory_insert_collection( + &self, + opctx: &OpContext, + collection: &Collection, + ) -> Result<(), Error> { + opctx.authorize(authz::Action::Modify, &authz::INVENTORY).await?; + + // It's not critical that this be done in one transaction. But it keeps + // the database a little tidier because we won't have half-inserted + // collections in there. + let pool = self.pool_authorized(opctx).await?; + pool.transaction_async(|conn| async move { + let row_collection = InvCollection::from(collection); + let collection_id = row_collection.id; + + // Insert a record describing the collection itself. + { + use db::schema::inv_collection::dsl; + let _ = diesel::insert_into(dsl::inv_collection) + .values(row_collection) + .execute_async(&conn) + .await + .map_err(|e| { + TransactionError::CustomError( + public_error_from_diesel_pool( + e.into(), + ErrorHandler::Server, + ) + .internal_context( + "inserting new inventory collection", + ), + ) + })?; + } + + // Insert records for any baseboards that do not already exist in + // the database. + { + use db::schema::hw_baseboard_id::dsl; + for baseboard_id in &collection.baseboards { + let _ = diesel::insert_into(dsl::hw_baseboard_id) + .values(HwBaseboardId::from(baseboard_id.as_ref())) + .on_conflict_do_nothing() + .execute_async(&conn) + .await + .map_err(|e| { + TransactionError::CustomError( + public_error_from_diesel_pool( + e.into(), + ErrorHandler::Server, + ) + .internal_context("inserting baseboard"), + ) + }); + } + } + + // XXX-dap insert the errors + + // XXX-dap remove this + // We now need to fetch the ids for those baseboards. We need these + // ids in order to insert, for example, inv_service_processor + // records, which have a foreign key into the hw_baseboard_id table. + // + // This approach sucks. With raw SQL, we could do something like + // + // INSERT INTO inv_service_processor + // SELECT + // id + // [other service_processor column values] + // FROM hw_baseboard_id + // WHERE part_number = ... AND serial_number = ...; + // + // This way, we don't need to know the id directly. + + // XXX-dap + //self.inventory_insert_cabooses(opctx, &collection.cabooses, &conn) + // .await?; + + { + use db::schema::hw_baseboard_id::dsl as baseboard_dsl; + use db::schema::inv_service_processor::dsl as sp_dsl; + + for (_, sp) in &collection.sps { + let selection = + db::schema::hw_baseboard_id::table.select(( + collection_id.into_sql::(), + baseboard_dsl::id, + sp.time_collected + .into_sql::(), + sp.source.into_sql::(), + i64::from(sp.baseboard_revision) + .into_sql::(), + sp.hubris_archive + .into_sql::(), + HwPowerState::from(sp.power_state).into_sql(), + )); + + diesel::insert_into( + db::schema::inv_service_processor::table, + ) + .values(selection) + .into_columns(( + sp_dsl::inv_collection_id, + sp_dsl::hw_baseboard_id, + sp_dsl::time_collected, + sp_dsl::source, + sp_dsl::baseboard_revision, + sp_dsl::hubris_archive_id, + sp_dsl::power_state, + )) + .execute_async(&conn) + .await + .map_err(|e| { + TransactionError::CustomError( + public_error_from_diesel_pool( + e.into(), + ErrorHandler::Server, + ) + .internal_context("inserting service processor"), + ) + }); + } + } + + //self.inventory_insert_sps(opctx, &collection.sps, &conn).await?; + //self.inventory_insert_cabooses_found( + // opctx, + // &collection.cabooses, + // &collection.sps, + // &conn, + //) + //.await?; + Ok(()) + }) + .await + .map_err(|error| match error { + TransactionError::CustomError(e) => e, + TransactionError::Pool(e) => { + public_error_from_diesel_pool(e, ErrorHandler::Server) + } + }) + } + + //async fn inventory_insert_baseboards( + // &self, + // baseboards: impl Iterator, + // conn: &(impl async_bb8_diesel::AsyncConnection< + // crate::db::pool::DbConnection, + // ConnErr, + // > + Sync), + //) -> Result<(), TransactionError> + //where + // ConnErr: From + Send + 'static, + // ConnErr: Into, + //{ + // use db::schema::hw_baseboard_id::dsl; + + // for baseboard_id in baseboards { + // let _ = diesel::insert_into(dsl::hw_baseboard_id) + // .values(HwBaseboardId::from(baseboard_id)) + // .on_conflict_do_nothing() + // .execute_async(conn) + // .await + // .map_err(|e| { + // TransactionError::CustomError( + // public_error_from_diesel_pool( + // e.into(), + // ErrorHandler::Server, + // ) + // .internal_context("inserting baseboard"), + // ) + // }); + // } + + // Ok(()) + //} +} diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index f653675728..f7146049f3 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -59,6 +59,7 @@ mod external_ip; mod identity_provider; mod image; mod instance; +mod inventory; mod ip_pool; mod network_interface; mod oximeter; diff --git a/nexus/inventory/Cargo.toml b/nexus/inventory/Cargo.toml index 6a16f45b36..aeb9895df7 100644 --- a/nexus/inventory/Cargo.toml +++ b/nexus/inventory/Cargo.toml @@ -12,5 +12,6 @@ gateway-client.workspace = true gateway-messages.workspace = true nexus-db-model.workspace = true nexus-db-queries.workspace = true +nexus-types.workspace = true strum.workspace = true uuid.workspace = true diff --git a/nexus/inventory/src/builder.rs b/nexus/inventory/src/builder.rs index 4842cb55f5..e52f1b77c8 100644 --- a/nexus/inventory/src/builder.rs +++ b/nexus/inventory/src/builder.rs @@ -32,7 +32,7 @@ pub enum CabooseWhich { pub struct CollectionBuilder { errors: Vec, time_started: DateTime, - creator: String, + collector: String, comment: String, baseboards: BTreeSet>, cabooses: BTreeSet>, @@ -40,11 +40,11 @@ pub struct CollectionBuilder { } impl CollectionBuilder { - pub fn new(creator: &str, comment: &str) -> Self { + pub fn new(collector: &str, comment: &str) -> Self { CollectionBuilder { errors: vec![], time_started: Utc::now(), - creator: creator.to_owned(), + collector: collector.to_owned(), comment: comment.to_owned(), baseboards: BTreeSet::new(), cabooses: BTreeSet::new(), @@ -57,7 +57,7 @@ impl CollectionBuilder { errors: self.errors, time_started: self.time_started, time_done: Utc::now(), - creator: self.creator, + collector: self.collector, comment: self.comment, baseboards: self.baseboards, cabooses: self.cabooses, diff --git a/nexus/inventory/src/lib.rs b/nexus/inventory/src/lib.rs index e61e18bcb0..7e47ab317d 100644 --- a/nexus/inventory/src/lib.rs +++ b/nexus/inventory/src/lib.rs @@ -9,117 +9,9 @@ //! on parts of Nexus (beyond the database crates) and could conceivably be put //! into other components. -pub use collector::Collector; - -use anyhow::anyhow; -use chrono::DateTime; -use chrono::Utc; -use gateway_client::types::PowerState; -use gateway_client::types::RotSlot; -use std::collections::BTreeMap; -use std::collections::BTreeSet; -use std::sync::Arc; - mod builder; mod collector; -/// Results of collecting inventory from various Omicron components -#[derive(Debug)] -pub struct Collection { - /// errors encountered during collection - pub errors: Vec, - /// time the collection started - pub time_started: DateTime, - /// time the collection eneded - pub time_done: DateTime, - /// name of the agent doing the collecting (generally, this Nexus's uuid) - pub creator: String, - /// reason for triggering this collection - pub comment: String, - - pub baseboards: BTreeSet>, - pub cabooses: BTreeSet>, - pub sps: BTreeMap, ServiceProcessor>, -} - -#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] -pub struct BaseboardId { - pub serial_number: String, - pub part_number: String, -} - -#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] -pub struct Caboose { - pub board: String, - pub git_commit: String, - pub name: String, - pub version: String, -} - -impl From for Caboose { - fn from(c: gateway_client::types::SpComponentCaboose) -> Self { - Caboose { - board: c.board, - git_commit: c.git_commit, - name: c.name, - // The MGS API uses an `Option` here because old SP versions did not - // supply it. But modern SP versions do. So we should never hit - // this `unwrap_or()`. - version: c.version.unwrap_or(String::from("")), - } - } -} - -#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] -pub struct ServiceProcessor { - pub baseboard: Arc, - pub time_collected: DateTime, - pub source: String, - - pub hubris_archive: String, - pub power_state: PowerState, - pub rot: Option, - - pub sp_slot0_caboose: Option>, - pub sp_slot1_caboose: Option>, - pub rot_slot_a_caboose: Option>, - pub rot_slot_b_caboose: Option>, -} - -#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] -pub struct RotState { - pub active_slot: RotSlot, - pub persistent_boot_preference: RotSlot, - pub pending_persistent_boot_preference: Option, - pub transient_boot_preference: Option, - pub slot_a_sha3_256_digest: Option, - pub slot_b_sha3_256_digest: Option, -} - -impl TryFrom for RotState { - type Error = anyhow::Error; - fn try_from( - value: gateway_client::types::RotState, - ) -> Result { - match value { - gateway_client::types::RotState::Enabled { - active, - pending_persistent_boot_preference, - persistent_boot_preference, - slot_a_sha3_256_digest, - slot_b_sha3_256_digest, - transient_boot_preference, - } => Ok(RotState { - active_slot: active, - persistent_boot_preference, - pending_persistent_boot_preference, - transient_boot_preference, - slot_a_sha3_256_digest, - slot_b_sha3_256_digest, - }), - gateway_client::types::RotState::CommunicationFailed { - message, - } => Err(anyhow!("communication with SP failed: {}", message)), - } - } -} +pub use collector::Collector; +// XXX-remove this +pub use nexus_types::inventory::*; diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index c0f175cf31..e854079fa3 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -23,5 +23,6 @@ uuid.workspace = true api_identity.workspace = true dns-service-client.workspace = true +gateway-client.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs new file mode 100644 index 0000000000..5b34bdf606 --- /dev/null +++ b/nexus/types/src/inventory.rs @@ -0,0 +1,122 @@ +// 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/. + +//! Types representing collection of hardware/software inventory +//! +//! This lives in nexus/types because it's used by both nexus/db-model and +//! nexus/inventory. (It could as well just live in nexus/db-model, but +//! nexus/inventory does not currently know about nexus/db-model and it's +//! convenient to separate these concerns.) + +use anyhow::anyhow; +use chrono::DateTime; +use chrono::Utc; +use gateway_client::types::RotSlot; +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::sync::Arc; + +pub type PowerState = gateway_client::types::PowerState; + +/// Results of collecting inventory from various Omicron components +#[derive(Debug)] +pub struct Collection { + /// errors encountered during collection + pub errors: Vec, + /// time the collection started + pub time_started: DateTime, + /// time the collection eneded + pub time_done: DateTime, + /// name of the agent doing the collecting (generally, this Nexus's uuid) + pub collector: String, + /// reason for triggering this collection + pub comment: String, + + pub baseboards: BTreeSet>, + pub cabooses: BTreeSet>, + pub sps: BTreeMap, ServiceProcessor>, +} + +#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] +pub struct BaseboardId { + pub part_number: String, + pub serial_number: String, +} + +#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] +pub struct Caboose { + pub board: String, + pub git_commit: String, + pub name: String, + pub version: String, +} + +impl From for Caboose { + fn from(c: gateway_client::types::SpComponentCaboose) -> Self { + Caboose { + board: c.board, + git_commit: c.git_commit, + name: c.name, + // The MGS API uses an `Option` here because old SP versions did not + // supply it. But modern SP versions do. So we should never hit + // this `unwrap_or()`. + version: c.version.unwrap_or(String::from("")), + } + } +} + +#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] +pub struct ServiceProcessor { + pub baseboard: Arc, + pub time_collected: DateTime, + pub source: String, + + pub baseboard_revision: u32, + pub hubris_archive: String, + pub power_state: PowerState, + pub rot: Option, + + pub sp_slot0_caboose: Option>, + pub sp_slot1_caboose: Option>, + pub rot_slot_a_caboose: Option>, + pub rot_slot_b_caboose: Option>, +} + +#[derive(Clone, Debug, Ord, Eq, PartialOrd, PartialEq)] +pub struct RotState { + pub active_slot: RotSlot, + pub persistent_boot_preference: RotSlot, + pub pending_persistent_boot_preference: Option, + pub transient_boot_preference: Option, + pub slot_a_sha3_256_digest: Option, + pub slot_b_sha3_256_digest: Option, +} + +impl TryFrom for RotState { + type Error = anyhow::Error; + fn try_from( + value: gateway_client::types::RotState, + ) -> Result { + match value { + gateway_client::types::RotState::Enabled { + active, + pending_persistent_boot_preference, + persistent_boot_preference, + slot_a_sha3_256_digest, + slot_b_sha3_256_digest, + transient_boot_preference, + } => Ok(RotState { + active_slot: active, + persistent_boot_preference, + pending_persistent_boot_preference, + transient_boot_preference, + slot_a_sha3_256_digest, + slot_b_sha3_256_digest, + }), + gateway_client::types::RotState::CommunicationFailed { + message, + } => Err(anyhow!("communication with SP failed: {}", message)), + } + } +} diff --git a/nexus/types/src/lib.rs b/nexus/types/src/lib.rs index 3f864b0f17..a48c4d3b00 100644 --- a/nexus/types/src/lib.rs +++ b/nexus/types/src/lib.rs @@ -32,3 +32,4 @@ pub mod external_api; pub mod identity; pub mod internal_api; +pub mod inventory; diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index e280a8e035..a8248be0a8 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -2592,7 +2592,7 @@ CREATE TABLE IF NOT EXISTS inv_collection ( -- Supports: finding latest collection to use, finding oldest collection to -- clean up CREATE INDEX IF NOT EXISTS inv_collection_by_time - ON omicron.public.inv_collection (time_done); + ON omicron.public.inv_collection (time_done) WHERE time_done IS NOT NULL; -- list of errors generated during a collection CREATE TABLE IF NOT EXISTS omicron.public.inv_collection_errors ( @@ -2618,7 +2618,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.inv_service_processor ( source TEXT NOT NULL, -- Data from MGS "Get SP Info" API. See MGS API documentation. - baseboard_revision INT4 NOT NULL, + baseboard_revision INT8 NOT NULL, hubris_archive_id TEXT NOT NULL, power_state omicron.public.hw_power_state NOT NULL,