Repository navigation
[fm] add SMF service diagnosis engine for services in maintenance #11712
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
Open
smklein
wants to merge
4
commits into
main
Choose a base branch
from
fm-smf-service-de
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3076a3d
[fm] add in-service sleds to fault management analysis input
smklein 10afa15
move is_propolis_zone into nexus-types for reuse by fault management
smklein d352afe
[fm] add SMF service diagnosis engine for services in maintenance
smklein 1deb962
simplify fm_fact_smf_service comment in dbinit.sql
smklein File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // 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/. | ||
|
|
||
| //! Database representation of the SMF service diagnosis engine's facts. | ||
| //! | ||
| //! Each SMF service fact is stored as typed columns in the | ||
| //! `fm_fact_smf_service` table. See [`nexus_types::fm::SmfServiceFact`] for | ||
| //! semantics. | ||
|
|
||
| use crate::DbTypedUuid; | ||
| use crate::impl_enum_type; | ||
| use chrono::{DateTime, Utc}; | ||
| use nexus_db_schema::schema::fm_fact_smf_service; | ||
| use nexus_types::fm; | ||
| use nexus_types::fm::case::FactMetadata; | ||
| use nexus_types::fm::{FactPayload, SmfServiceFact, SmfServiceFactPayload}; | ||
| use omicron_uuid_kinds::{ | ||
| CaseKind, CollectionKind, FactKind, SitrepKind, SledKind, | ||
| }; | ||
|
|
||
| impl_enum_type!( | ||
| FmFactSmfServiceKindEnum: | ||
|
|
||
| #[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, PartialEq, Eq)] | ||
| pub enum FmFactSmfServiceKind; | ||
|
|
||
| InMaintenance => b"in_maintenance" | ||
| ); | ||
|
|
||
| /// Diesel row for the `fm_fact_smf_service` table. | ||
| #[derive(Queryable, Insertable, Clone, Debug, Selectable)] | ||
| #[diesel(table_name = fm_fact_smf_service)] | ||
| pub struct FmFactSmfService { | ||
| pub id: DbTypedUuid<FactKind>, | ||
| /// The sitrep to which this fact belongs. | ||
| /// | ||
| /// This will change as the fact is carried forward from one sitrep to the | ||
| /// next. | ||
| pub sitrep_id: DbTypedUuid<SitrepKind>, | ||
| pub case_id: DbTypedUuid<CaseKind>, | ||
| /// Sitrep in which this fact was first added. | ||
| /// | ||
| /// Preserved unchanged when the fact is carried forward; debug-only. | ||
| pub created_sitrep_id: DbTypedUuid<SitrepKind>, | ||
| pub comment: String, | ||
|
|
||
| pub sled_id: DbTypedUuid<SledKind>, | ||
| pub zone_name: String, | ||
| pub fmri: String, | ||
| pub kind: FmFactSmfServiceKind, | ||
|
|
||
| pub observed_in_inv: DbTypedUuid<CollectionKind>, | ||
| pub time_observed: DateTime<Utc>, | ||
| } | ||
|
|
||
| impl FmFactSmfService { | ||
| /// Build a row from a fact's shared metadata (`metadata`) and its | ||
| /// already-dispatched SMF service payload (`smf_fact`). | ||
| pub fn from_sitrep( | ||
| sitrep_id: impl Into<DbTypedUuid<SitrepKind>>, | ||
| case_id: impl Into<DbTypedUuid<CaseKind>>, | ||
| metadata: &FactMetadata, | ||
| smf_fact: &SmfServiceFact, | ||
| ) -> Self { | ||
| let FactMetadata { id, created_sitrep_id, comment } = metadata; | ||
| let kind = match smf_fact { | ||
| SmfServiceFact::InMaintenance(_) => { | ||
| FmFactSmfServiceKind::InMaintenance | ||
| } | ||
| }; | ||
| let SmfServiceFactPayload { | ||
| sled_id, | ||
| zone_name, | ||
| fmri, | ||
| observed_in_inv, | ||
| time_observed, | ||
| } = smf_fact.payload(); | ||
| Self { | ||
| id: (*id).into(), | ||
| sitrep_id: sitrep_id.into(), | ||
| case_id: case_id.into(), | ||
| created_sitrep_id: (*created_sitrep_id).into(), | ||
| comment: comment.clone(), | ||
| sled_id: (*sled_id).into(), | ||
| zone_name: zone_name.clone(), | ||
| fmri: fmri.clone(), | ||
| kind, | ||
| observed_in_inv: (*observed_in_inv).into(), | ||
| time_observed: *time_observed, | ||
| } | ||
| } | ||
|
|
||
| /// Reconstruct an in-memory fact from a row. | ||
| pub fn into_fact(self) -> fm::case::Fact { | ||
| let payload = SmfServiceFactPayload { | ||
| sled_id: self.sled_id.into(), | ||
| zone_name: self.zone_name, | ||
| fmri: self.fmri, | ||
| observed_in_inv: self.observed_in_inv.into(), | ||
| time_observed: self.time_observed, | ||
| }; | ||
| let payload = match self.kind { | ||
| FmFactSmfServiceKind::InMaintenance => { | ||
| FactPayload::SmfService(SmfServiceFact::InMaintenance(payload)) | ||
| } | ||
| }; | ||
| fm::case::Fact { | ||
| metadata: fm::case::FactMetadata { | ||
| id: self.id.into(), | ||
| created_sitrep_id: self.created_sitrep_id.into(), | ||
| comment: self.comment, | ||
| }, | ||
| payload, | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ use nexus_db_schema::schema::fm_ereport_in_case::dsl as case_ereport_dsl; | |
| use nexus_db_schema::schema::fm_fact_certificate::dsl as fact_cert_dsl; | ||
| use nexus_db_schema::schema::fm_fact_physical_disk::dsl as fact_pd_dsl; | ||
| use nexus_db_schema::schema::fm_fact_saga::dsl as fact_saga_dsl; | ||
| use nexus_db_schema::schema::fm_fact_smf_service::dsl as fact_smf_dsl; | ||
| use nexus_db_schema::schema::fm_sitrep::dsl as sitrep_dsl; | ||
| use nexus_db_schema::schema::fm_sitrep_analysis_report::dsl as analysis_report_dsl; | ||
| use nexus_db_schema::schema::fm_sitrep_history::dsl as history_dsl; | ||
|
|
@@ -139,6 +140,7 @@ sitrep_child_tables! { | |
| FmFactPhysicalDisk => { table: "fm_fact_physical_disk" }, | ||
| FmFactSaga => { table: "fm_fact_saga" }, | ||
| FmFactCertificate => { table: "fm_fact_certificate" }, | ||
| FmFactSmfService => { table: "fm_fact_smf_service" }, | ||
| AnalysisReport => { table: "fm_sitrep_analysis_report" }, | ||
| } | ||
|
|
||
|
|
@@ -669,6 +671,31 @@ impl DataStore { | |
| } | ||
| } | ||
|
|
||
| // --- SMF service diagnosis engine facts --- | ||
| let mut paginator: Paginator<DbTypedUuid<FactKind>> = | ||
| Paginator::new(SQL_BATCH_SIZE, PaginationOrder::Descending); | ||
| while let Some(p) = paginator.next() { | ||
| let batch = paginated( | ||
| fact_smf_dsl::fm_fact_smf_service, | ||
| fact_smf_dsl::id, | ||
| &p.current_pagparams(), | ||
| ) | ||
| .filter(fact_smf_dsl::sitrep_id.eq(id.into_untyped_uuid())) | ||
| .select(model::fm::FmFactSmfService::as_select()) | ||
| .load_async(conn) | ||
| .await | ||
| .map_err(|e| { | ||
| public_error_from_diesel(e, ErrorHandler::Server) | ||
| .internal_context("failed to load SMF service case facts") | ||
| })?; | ||
|
|
||
| paginator = p.found_batch(&batch, &|f| f.id); | ||
| for row in batch { | ||
| let case_id: CaseUuid = row.case_id.into(); | ||
| insert_fact_for_case(&mut by_case, case_id, row.into_fact())?; | ||
| } | ||
| } | ||
|
Comment on lines
+674
to
+697
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how many more of these do you think we're gonna have to write before we make a macro that does this? |
||
|
|
||
| Ok(by_case) | ||
| } | ||
|
|
||
|
|
@@ -930,6 +957,7 @@ impl DataStore { | |
| let mut physical_disk_facts = Vec::new(); | ||
| let mut saga_facts = Vec::new(); | ||
| let mut certificate_facts = Vec::new(); | ||
| let mut smf_service_facts = Vec::new(); | ||
| for case in sitrep.cases { | ||
| let case_id = case.id; | ||
| cases.push(model::fm::CaseMetadata::from_sitrep(sitrep_id, &case)); | ||
|
|
@@ -984,6 +1012,16 @@ impl DataStore { | |
| ), | ||
| ); | ||
| } | ||
| fm::FactPayload::SmfService(smf_fact) => { | ||
| smf_service_facts.push( | ||
| model::fm::FmFactSmfService::from_sitrep( | ||
| sitrep_id, | ||
| case_id, | ||
| &fact.metadata, | ||
| smf_fact, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1081,6 +1119,19 @@ impl DataStore { | |
| })?; | ||
| } | ||
|
|
||
| if !smf_service_facts.is_empty() { | ||
| diesel::insert_into(fact_smf_dsl::fm_fact_smf_service) | ||
| .values(smf_service_facts) | ||
| .execute_async(&*conn) | ||
| .await | ||
| .map_err(|e| { | ||
| public_error_from_diesel(e, ErrorHandler::Server) | ||
| .internal_context( | ||
| "failed to insert SMF service case facts", | ||
| ) | ||
| })?; | ||
| } | ||
|
|
||
| if !cases.is_empty() { | ||
| diesel::insert_into(case_dsl::fm_case) | ||
| .values(cases) | ||
|
|
@@ -3137,13 +3188,57 @@ mod tests { | |
| "best certificate has expired", | ||
| ); | ||
|
|
||
| // An SMF service case, exercising the fm_fact_smf_service | ||
| // read/write/GC paths. | ||
| let case7 = { | ||
| let mut facts = iddqd::IdOrdMap::new(); | ||
| facts | ||
| .insert_unique(fm::case::Fact { | ||
| metadata: fm::case::FactMetadata { | ||
| id: FactUuid::new_v4(), | ||
| created_sitrep_id: sitrep_id, | ||
| comment: "a representative in-maintenance fact" | ||
| .to_string(), | ||
| }, | ||
| payload: fm::FactPayload::SmfService( | ||
| fm::SmfServiceFact::InMaintenance( | ||
| fm::SmfServiceFactPayload { | ||
| sled_id: omicron_uuid_kinds::SledUuid::new_v4(), | ||
| zone_name: "oxz_ntp".to_string(), | ||
| fmri: "svc:/oxide/ntp:default".to_string(), | ||
| observed_in_inv: | ||
| omicron_uuid_kinds::CollectionUuid::new_v4( | ||
| ), | ||
| time_observed: omicron_common::now_db_precision( | ||
| ), | ||
| }, | ||
| ), | ||
| ), | ||
| }) | ||
| .unwrap(); | ||
| fm::Case { | ||
| id: omicron_uuid_kinds::CaseUuid::new_v4(), | ||
| metadata: fm::case::Metadata { | ||
| created_sitrep_id: sitrep_id, | ||
| closed_sitrep_id: None, | ||
| de: fm::DiagnosisEngineKind::SmfService, | ||
| comment: "an SMF service in maintenance".to_string(), | ||
| }, | ||
| ereports: iddqd::IdOrdMap::new(), | ||
| alerts_requested: iddqd::IdOrdMap::new(), | ||
| support_bundles_requested: iddqd::IdOrdMap::new(), | ||
| facts, | ||
| } | ||
| }; | ||
|
|
||
| let mut cases = iddqd::IdOrdMap::new(); | ||
| cases.insert_unique(case1.clone()).expect("failed to insert case 1"); | ||
| cases.insert_unique(case2.clone()).expect("failed to insert case 2"); | ||
| cases.insert_unique(case3).expect("failed to insert case 3"); | ||
| cases.insert_unique(case4).expect("failed to insert case 4"); | ||
| cases.insert_unique(case5).expect("failed to insert case 5"); | ||
| cases.insert_unique(case6).expect("failed to insert case 6"); | ||
| cases.insert_unique(case7).expect("failed to insert case 7"); | ||
| let mut ereports_by_id = iddqd::IdOrdMap::new(); | ||
| for case in cases.iter() { | ||
| ereports_by_id | ||
|
|
@@ -3193,6 +3288,7 @@ mod tests { | |
| open_cases: Default::default(), | ||
| closed_cases_copied_forward: Default::default(), | ||
| in_service_disks: Default::default(), | ||
| in_service_sleds: Default::default(), | ||
| observed_sagas: Default::default(), | ||
| observed_silo_certificates: Default::default(), | ||
| config: None, | ||
|
|
@@ -3453,7 +3549,7 @@ mod tests { | |
| .get_result_async::<i64>(&*conn) | ||
| .await | ||
| .expect("failed to count cases before deletion"); | ||
| assert_eq!(cases_before, 6, "six cases should exist before deletion"); | ||
| assert_eq!(cases_before, 7, "seven cases should exist before deletion"); | ||
|
|
||
| let case_ereports_before: i64 = case_ereport_dsl::fm_ereport_in_case | ||
| .filter( | ||
|
|
@@ -3836,6 +3932,7 @@ mod tests { | |
| num_ereporter_restarts: 0, | ||
| unknown_restart_ereport_ids: Default::default(), | ||
| in_service_disks: Default::default(), | ||
| in_service_sleds: Default::default(), | ||
| observed_sagas: Default::default(), | ||
| observed_silo_certificates: Default::default(), | ||
| config: None, | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps nitpicky of me but:
kind: FmFactSmfServiceKindfeels like it's saying it's the kind of service, and it took me a moment to realize this was the fact kind. But I realize it's a conventional name, so... 🤷♀️