66
77use super :: DbFetchOptions ;
88use super :: check_limit;
9+ use crate :: Omdb ;
10+ use crate :: helpers:: CONNECTION_OPTIONS_HEADING ;
911use crate :: helpers:: const_max_len;
1012use crate :: helpers:: datetime_opt_rfc3339_concise;
1113use crate :: helpers:: datetime_rfc3339_concise;
@@ -23,6 +25,7 @@ use clap::Subcommand;
2325use diesel:: AggregateExpressionMethods ;
2426use diesel:: dsl:: { count, min} ;
2527use diesel:: prelude:: * ;
28+ use internal_dns_types:: names:: ServiceName ;
2629use nexus_db_lookup:: DbConnection ;
2730use nexus_db_model:: ereport as model;
2831use nexus_db_model:: ereport:: DbEna ;
@@ -57,9 +60,21 @@ enum Commands {
5760 Reporters ( ReportersArgs ) ,
5861
5962 /// Summarize ereports by class, marking which classes a diagnosis engine
60- /// in Nexus consumes (per
61- /// `nexus_types::fm::ereport::known_ereport_classes`).
62- Classes ,
63+ /// in Nexus consumes (fetched from Nexus's lockstep API; falls back to
64+ /// `?` if Nexus is unreachable).
65+ Classes ( ClassesArgs ) ,
66+ }
67+
68+ #[ derive( Debug , Args , Clone ) ]
69+ struct ClassesArgs {
70+ /// URL of the Nexus lockstep API. If not provided, looks up an instance
71+ /// in internal DNS.
72+ #[ clap(
73+ long,
74+ env = "OMDB_NEXUS_URL" ,
75+ help_heading = CONNECTION_OPTIONS_HEADING ,
76+ ) ]
77+ nexus_internal_url : Option < String > ,
6378}
6479
6580#[ derive( Debug , Args , Clone ) ]
@@ -105,6 +120,8 @@ struct ReportersArgs {
105120}
106121
107122pub ( super ) async fn cmd_db_ereport (
123+ omdb : & Omdb ,
124+ log : & slog:: Logger ,
108125 datastore : & DataStore ,
109126 fetch_opts : & DbFetchOptions ,
110127 args : & EreportArgs ,
@@ -121,7 +138,9 @@ pub(super) async fn cmd_db_ereport(
121138 cmd_db_ereporters ( datastore, args) . await
122139 }
123140
124- Commands :: Classes => cmd_db_ereport_classes ( datastore) . await ,
141+ Commands :: Classes ( ref args) => {
142+ cmd_db_ereport_classes ( omdb, log, datastore, args) . await
143+ }
125144 }
126145}
127146
@@ -474,14 +493,30 @@ async fn cmd_db_ereporters(
474493 Ok ( ( ) )
475494}
476495
477- async fn cmd_db_ereport_classes ( datastore : & DataStore ) -> anyhow:: Result < ( ) > {
496+ async fn cmd_db_ereport_classes (
497+ omdb : & Omdb ,
498+ log : & slog:: Logger ,
499+ datastore : & DataStore ,
500+ args : & ClassesArgs ,
501+ ) -> anyhow:: Result < ( ) > {
478502 use std:: collections:: BTreeMap ;
479-
480- let known: std:: collections:: BTreeSet < & ' static str > =
481- nexus_types:: fm:: ereport:: known_ereport_classes ( )
482- . iter ( )
483- . copied ( )
484- . collect ( ) ;
503+ use std:: collections:: BTreeSet ;
504+
505+ // Try to fetch the known list from Nexus. If anything fails, fall back
506+ // to "?" for every row — DB totals are still useful even without Nexus.
507+ let known_from_nexus =
508+ fetch_known_classes_from_nexus ( omdb, log, args) . await ;
509+ let known: BTreeSet < String > = match & known_from_nexus {
510+ Ok ( list) => list. iter ( ) . cloned ( ) . collect ( ) ,
511+ Err ( err) => {
512+ eprintln ! (
513+ "warning: could not fetch known ereport classes from Nexus: \
514+ {err:#}"
515+ ) ;
516+ BTreeSet :: new ( )
517+ }
518+ } ;
519+ let nexus_reachable = known_from_nexus. is_ok ( ) ;
485520
486521 let conn = datastore. pool_connection_for_tests ( ) . await ?;
487522
@@ -513,32 +548,35 @@ async fn cmd_db_ereport_classes(datastore: &DataStore) -> anyhow::Result<()> {
513548 by_class. entry ( class) . or_default ( ) . unmarked = unmarked;
514549 }
515550
516- // Whether *this* omdb's build has a diagnosis engine that consumes a
551+ // Whether the deployed Nexus has a diagnosis engine that consumes a
517552 // given ereport class.
518553 #[ derive( PartialEq , Eq ) ]
519- enum KnownToOmdb {
520- /// Class has rows in the DB AND is in `known_ereport_classes()` .
554+ enum KnownToNexus {
555+ /// Class has rows in the DB AND is in the list returned by Nexus .
521556 Yes ,
522- /// Class has rows in the DB but is NOT in `known_ereport_classes()` .
557+ /// Class has rows in the DB but is NOT in the list returned by Nexus .
523558 No ,
524559 /// Class is NULL — strict-match policy means the loader never
525560 /// surfaces these to FM analysis.
526561 NullClass ,
562+ /// Could not reach Nexus — known/unknown is undetermined.
563+ Unknown ,
527564 }
528- impl std:: fmt:: Display for KnownToOmdb {
565+ impl std:: fmt:: Display for KnownToNexus {
529566 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
530567 f. write_str ( match self {
531568 Self :: Yes => "yes" ,
532569 Self :: No => "no" ,
533570 Self :: NullClass => "-" ,
571+ Self :: Unknown => "?" ,
534572 } )
535573 }
536574 }
537575
538576 #[ derive( Tabled ) ]
539577 #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
540578 struct ClassRow < ' a > {
541- known : KnownToOmdb ,
579+ known : KnownToNexus ,
542580 total : i64 ,
543581 unmarked : i64 ,
544582 /// Variable-length, so it goes last: wrapping on a narrow terminal
@@ -549,13 +587,16 @@ async fn cmd_db_ereport_classes(datastore: &DataStore) -> anyhow::Result<()> {
549587 let mut rows: Vec < ClassRow < ' _ > > = by_class
550588 . iter ( )
551589 . map ( |( class, ClassCounts { total, unmarked } ) | {
552- let ( known_marker, class_str) : ( KnownToOmdb , & str ) = match class {
553- None => ( KnownToOmdb :: NullClass , "(NULL)" ) ,
590+ let ( known_marker, class_str) : ( KnownToNexus , & str ) = match class {
591+ None => ( KnownToNexus :: NullClass , "(NULL)" ) ,
592+ Some ( c) if !nexus_reachable => {
593+ ( KnownToNexus :: Unknown , c. as_str ( ) )
594+ }
554595 Some ( c) => {
555596 let k = if known. contains ( c. as_str ( ) ) {
556- KnownToOmdb :: Yes
597+ KnownToNexus :: Yes
557598 } else {
558- KnownToOmdb :: No
599+ KnownToNexus :: No
559600 } ;
560601 ( k, c. as_str ( ) )
561602 }
@@ -569,48 +610,79 @@ async fn cmd_db_ereport_classes(datastore: &DataStore) -> anyhow::Result<()> {
569610 } )
570611 . collect ( ) ;
571612
572- // Sort: unknown-but-present first (highest unmarked), then known, then NULL.
613+ // Sort: unknown-but-present first (highest unmarked), then known, then
614+ // undetermined, then NULL.
573615 rows. sort_by ( |a, b| {
574616 let priority = |row : & ClassRow < ' _ > | match row. known {
575- KnownToOmdb :: No => 0 ,
576- KnownToOmdb :: Yes => 1 ,
577- KnownToOmdb :: NullClass => 2 ,
617+ KnownToNexus :: No => 0 ,
618+ KnownToNexus :: Yes => 1 ,
619+ KnownToNexus :: Unknown => 2 ,
620+ KnownToNexus :: NullClass => 3 ,
578621 } ;
579622 priority ( a)
580623 . cmp ( & priority ( b) )
581624 . then_with ( || b. unmarked . cmp ( & a. unmarked ) )
582625 . then_with ( || a. class . cmp ( b. class ) )
583626 } ) ;
584627
585- println ! (
586- "note: KNOWN reflects which classes have a diagnosis engine in Nexus \
587- as of\n the control plane build that produced this omdb; the \
588- currently-deployed\n Nexus may differ if it was built from a \
589- different commit.\n "
590- ) ;
628+ if nexus_reachable {
629+ println ! (
630+ "note: KNOWN reflects which classes the currently-deployed Nexus \
631+ knows how\n to consume.\n "
632+ ) ;
633+ } else {
634+ println ! (
635+ "note: could not reach Nexus to determine known ereport classes.\n "
636+ ) ;
637+ }
591638
592639 let mut table = tabled:: Table :: new ( & rows) ;
593640 table
594641 . with ( tabled:: settings:: Style :: empty ( ) )
595642 . with ( tabled:: settings:: Padding :: new ( 0 , 1 , 0 , 0 ) ) ;
596643 println ! ( "{table}" ) ;
597644
598- // Footer: classes this omdb knows about but has no DB rows for.
599- let seen_known: std:: collections:: BTreeSet < & str > = rows
600- . iter ( )
601- . filter ( |r| r. known == KnownToOmdb :: Yes )
602- . map ( |r| r. class )
603- . collect ( ) ;
604- let absent: Vec < & & ' static str > =
605- known. iter ( ) . filter ( |c| !seen_known. contains ( * c) ) . collect ( ) ;
606- if !absent. is_empty ( ) {
607- println ! (
608- "\n Classes known to this omdb but with no rows in the database:"
609- ) ;
610- for c in absent {
611- println ! ( " {c}" ) ;
645+ // Footer: classes Nexus knows about but with no rows in the database.
646+ if nexus_reachable {
647+ let seen_known: BTreeSet < & str > = rows
648+ . iter ( )
649+ . filter ( |r| r. known == KnownToNexus :: Yes )
650+ . map ( |r| r. class )
651+ . collect ( ) ;
652+ let absent: Vec < & String > =
653+ known. iter ( ) . filter ( |c| !seen_known. contains ( c. as_str ( ) ) ) . collect ( ) ;
654+ if !absent. is_empty ( ) {
655+ println ! (
656+ "\n Classes Nexus knows about but with no rows in the database:"
657+ ) ;
658+ for c in absent {
659+ println ! ( " {c}" ) ;
660+ }
612661 }
613662 }
614663
615664 Ok ( ( ) )
616665}
666+
667+ async fn fetch_known_classes_from_nexus (
668+ omdb : & Omdb ,
669+ log : & slog:: Logger ,
670+ args : & ClassesArgs ,
671+ ) -> anyhow:: Result < Vec < String > > {
672+ let nexus_url = match & args. nexus_internal_url {
673+ Some ( url) => url. clone ( ) ,
674+ None => {
675+ let addr = omdb
676+ . dns_lookup_one ( log. clone ( ) , ServiceName :: NexusLockstep )
677+ . await
678+ . context ( "resolving Nexus lockstep service via internal DNS" ) ?;
679+ format ! ( "http://{addr}" )
680+ }
681+ } ;
682+ let client = nexus_lockstep_client:: Client :: new ( & nexus_url, log. clone ( ) ) ;
683+ let resp = client
684+ . fm_known_ereport_classes_list ( )
685+ . await
686+ . context ( "calling Nexus fm_known_ereport_classes_list" ) ?;
687+ Ok ( resp. into_inner ( ) )
688+ }
0 commit comments