1use crate::{
2 nns_data_center::{
3 NnsDataCenterCacheRequest, NnsDataCenterHostError, NnsDataCenterListReport,
4 NnsDataCenterListRequest, NnsDataCenterRefreshReport, NnsDataCenterRefreshRequest,
5 build_nns_data_center_list_report, refresh_nns_data_center_report,
6 },
7 nns_node::{
8 NNS_NODE_SUBNET_KIND_APPLICATION, NNS_NODE_SUBNET_KIND_SYSTEM,
9 NNS_NODE_SUBNET_KIND_UNKNOWN, NnsNodeCacheRequest, NnsNodeHostError, NnsNodeListFilters,
10 NnsNodeListReport, NnsNodeListRequest, NnsNodeRefreshReport, NnsNodeRefreshRequest,
11 build_nns_node_list_report, refresh_nns_node_report,
12 },
13 nns_node_operator::{
14 NnsNodeOperatorCacheRequest, NnsNodeOperatorHostError, NnsNodeOperatorListReport,
15 NnsNodeOperatorListRequest, NnsNodeOperatorRefreshReport, NnsNodeOperatorRefreshRequest,
16 build_nns_node_operator_list_report, refresh_nns_node_operator_report,
17 },
18 nns_node_provider::{
19 NnsNodeProviderCacheRequest, NnsNodeProviderHostError, NnsNodeProviderListReport,
20 NnsNodeProviderListRequest, NnsNodeProviderRefreshReport, NnsNodeProviderRefreshRequest,
21 build_nns_node_provider_list_report, refresh_nns_node_provider_report,
22 },
23 nns_render::yes_no,
24 subnet_catalog::{
25 DEFAULT_STALE_AFTER_SECONDS, SubnetCatalogCacheRequest, SubnetCatalogFilters,
26 SubnetCatalogHostError, SubnetCatalogListReport, SubnetCatalogListRequest,
27 SubnetCatalogRefreshReport, SubnetCatalogRefreshRequest, build_subnet_catalog_list_report,
28 refresh_subnet_catalog,
29 },
30 table::{ColumnAlign, render_table},
31};
32use canic_subnet_catalog::{MAINNET_NETWORK, SubnetKind};
33use serde::{Deserialize, Serialize};
34use std::{collections::BTreeSet, path::PathBuf};
35use thiserror::Error as ThisError;
36
37pub const NNS_TOPOLOGY_SUMMARY_REPORT_SCHEMA_VERSION: u32 = 2;
38pub const NNS_TOPOLOGY_REFRESH_REPORT_SCHEMA_VERSION: u32 = 1;
39
40#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct NnsTopologySummaryRequest {
45 pub icp_root: PathBuf,
46 pub network: String,
47 pub source_endpoint: String,
48 pub now_unix_secs: u64,
49}
50
51#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct NnsTopologyRefreshRequest {
56 pub icp_root: PathBuf,
57 pub network: String,
58 pub source_endpoint: String,
59 pub now_unix_secs: u64,
60 pub lock_stale_after_seconds: u64,
61 pub dry_run: bool,
62}
63
64#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
68pub struct NnsTopologySummaryReport {
69 pub schema_version: u32,
70 pub network: String,
71 pub source_endpoint: String,
72 pub subnet_count: usize,
73 pub application_subnet_count: usize,
74 pub system_subnet_count: usize,
75 pub unknown_subnet_count: usize,
76 pub routing_range_count: usize,
77 pub node_count: usize,
78 pub application_node_count: usize,
79 pub system_node_count: usize,
80 pub unknown_node_count: usize,
81 pub node_provider_count: usize,
82 pub node_operator_count: usize,
83 pub data_center_count: usize,
84 pub nodes_with_known_node_provider_count: usize,
85 pub nodes_with_unknown_node_provider_count: usize,
86 pub nodes_with_known_node_operator_count: usize,
87 pub nodes_with_unknown_node_operator_count: usize,
88 pub nodes_with_known_data_center_count: usize,
89 pub nodes_with_unknown_data_center_count: usize,
90 pub node_operators_with_known_node_provider_count: usize,
91 pub node_operators_with_unknown_node_provider_count: usize,
92 pub node_operators_with_known_data_center_count: usize,
93 pub node_operators_with_unknown_data_center_count: usize,
94 pub subnet_catalog_stale: bool,
95 pub subnet_catalog_stale_reason: String,
96 pub registry_versions: Vec<NnsTopologyRegistryVersionRow>,
97}
98
99#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
103pub struct NnsTopologyRegistryVersionRow {
104 pub source: String,
105 pub registry_version: u64,
106 pub fetched_at: String,
107 pub source_endpoint: String,
108 pub stale: Option<bool>,
109}
110
111#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
115pub struct NnsTopologyRefreshReport {
116 pub schema_version: u32,
117 pub network: String,
118 pub source_endpoint: String,
119 pub dry_run: bool,
120 pub component_count: usize,
121 pub wrote_cache_count: usize,
122 pub replaced_existing_cache_count: usize,
123 pub components: Vec<NnsTopologyRefreshRow>,
124}
125
126#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
130pub struct NnsTopologyRefreshRow {
131 pub source: String,
132 pub cache_path: String,
133 pub refresh_lock_path: String,
134 pub registry_version: u64,
135 pub fetched_at: String,
136 pub source_endpoint: String,
137 pub fetched_by: String,
138 pub dry_run: bool,
139 pub wrote_cache: bool,
140 pub replaced_existing_cache: bool,
141 pub item_count: usize,
142}
143
144#[derive(Debug, ThisError)]
148pub enum NnsTopologyHostError {
149 #[error(
150 "`canic nns topology` supports only the mainnet `ic` network\n\nThe NNS topology report is derived from public Internet Computer mainnet registry records.\nLocal replica NNS registry discovery is not implemented yet.\n\nTry:\n canic --network ic nns topology summary\n canic --network ic nns topology refresh"
151 )]
152 UnsupportedNetwork { network: String },
153
154 #[error(transparent)]
155 Subnet(#[from] SubnetCatalogHostError),
156
157 #[error(transparent)]
158 Node(#[from] NnsNodeHostError),
159
160 #[error(transparent)]
161 NodeProvider(#[from] NnsNodeProviderHostError),
162
163 #[error(transparent)]
164 NodeOperator(#[from] NnsNodeOperatorHostError),
165
166 #[error(transparent)]
167 DataCenter(#[from] NnsDataCenterHostError),
168}
169
170pub fn build_nns_topology_summary_report(
171 request: &NnsTopologySummaryRequest,
172) -> Result<NnsTopologySummaryReport, NnsTopologyHostError> {
173 enforce_mainnet_network(&request.network)?;
174
175 let subnet_report = build_subnet_catalog_list_report(&SubnetCatalogListRequest {
176 cache: SubnetCatalogCacheRequest {
177 icp_root: request.icp_root.clone(),
178 network: request.network.clone(),
179 },
180 now_unix_secs: request.now_unix_secs,
181 stale_after_seconds: DEFAULT_STALE_AFTER_SECONDS,
182 filters: SubnetCatalogFilters::default(),
183 show_ranges: false,
184 range_limit: 1,
185 range_offset: 0,
186 })?;
187 let node_report = build_nns_node_list_report(&NnsNodeListRequest {
188 cache: NnsNodeCacheRequest {
189 icp_root: request.icp_root.clone(),
190 network: request.network.clone(),
191 },
192 source_endpoint: request.source_endpoint.clone(),
193 now_unix_secs: request.now_unix_secs,
194 filters: NnsNodeListFilters::default(),
195 })?;
196 let node_provider_report = build_nns_node_provider_list_report(&NnsNodeProviderListRequest {
197 cache: NnsNodeProviderCacheRequest {
198 icp_root: request.icp_root.clone(),
199 network: request.network.clone(),
200 },
201 source_endpoint: request.source_endpoint.clone(),
202 now_unix_secs: request.now_unix_secs,
203 })?;
204 let node_operator_report = build_nns_node_operator_list_report(&NnsNodeOperatorListRequest {
205 cache: NnsNodeOperatorCacheRequest {
206 icp_root: request.icp_root.clone(),
207 network: request.network.clone(),
208 },
209 source_endpoint: request.source_endpoint.clone(),
210 now_unix_secs: request.now_unix_secs,
211 })?;
212 let data_center_report = build_nns_data_center_list_report(&NnsDataCenterListRequest {
213 cache: NnsDataCenterCacheRequest {
214 icp_root: request.icp_root.clone(),
215 network: request.network.clone(),
216 },
217 source_endpoint: request.source_endpoint.clone(),
218 now_unix_secs: request.now_unix_secs,
219 })?;
220
221 Ok(topology_summary_report_from_reports(
222 request.network.clone(),
223 request.source_endpoint.clone(),
224 subnet_report,
225 node_report,
226 node_provider_report,
227 node_operator_report,
228 data_center_report,
229 ))
230}
231
232pub fn refresh_nns_topology_report(
233 request: &NnsTopologyRefreshRequest,
234) -> Result<NnsTopologyRefreshReport, NnsTopologyHostError> {
235 enforce_mainnet_network(&request.network)?;
236
237 let subnet_report = refresh_subnet_catalog(&SubnetCatalogRefreshRequest {
238 cache: SubnetCatalogCacheRequest {
239 icp_root: request.icp_root.clone(),
240 network: request.network.clone(),
241 },
242 source_endpoint: request.source_endpoint.clone(),
243 now_unix_secs: request.now_unix_secs,
244 lock_stale_after_seconds: request.lock_stale_after_seconds,
245 dry_run: request.dry_run,
246 output_path: None,
247 })?;
248 let node_report = refresh_nns_node_report(&NnsNodeRefreshRequest {
249 cache: NnsNodeCacheRequest {
250 icp_root: request.icp_root.clone(),
251 network: request.network.clone(),
252 },
253 source_endpoint: request.source_endpoint.clone(),
254 now_unix_secs: request.now_unix_secs,
255 lock_stale_after_seconds: request.lock_stale_after_seconds,
256 dry_run: request.dry_run,
257 output_path: None,
258 })?;
259 let node_provider_report = refresh_nns_node_provider_report(&NnsNodeProviderRefreshRequest {
260 cache: NnsNodeProviderCacheRequest {
261 icp_root: request.icp_root.clone(),
262 network: request.network.clone(),
263 },
264 source_endpoint: request.source_endpoint.clone(),
265 now_unix_secs: request.now_unix_secs,
266 lock_stale_after_seconds: request.lock_stale_after_seconds,
267 dry_run: request.dry_run,
268 output_path: None,
269 })?;
270 let node_operator_report = refresh_nns_node_operator_report(&NnsNodeOperatorRefreshRequest {
271 cache: NnsNodeOperatorCacheRequest {
272 icp_root: request.icp_root.clone(),
273 network: request.network.clone(),
274 },
275 source_endpoint: request.source_endpoint.clone(),
276 now_unix_secs: request.now_unix_secs,
277 lock_stale_after_seconds: request.lock_stale_after_seconds,
278 dry_run: request.dry_run,
279 output_path: None,
280 })?;
281 let data_center_report = refresh_nns_data_center_report(&NnsDataCenterRefreshRequest {
282 cache: NnsDataCenterCacheRequest {
283 icp_root: request.icp_root.clone(),
284 network: request.network.clone(),
285 },
286 source_endpoint: request.source_endpoint.clone(),
287 now_unix_secs: request.now_unix_secs,
288 lock_stale_after_seconds: request.lock_stale_after_seconds,
289 dry_run: request.dry_run,
290 output_path: None,
291 })?;
292
293 Ok(topology_refresh_report_from_reports(
294 request.network.clone(),
295 request.source_endpoint.clone(),
296 request.dry_run,
297 NnsTopologyRefreshComponentReports {
298 subnet: subnet_report,
299 node: node_report,
300 node_provider: node_provider_report,
301 node_operator: node_operator_report,
302 data_center: data_center_report,
303 },
304 ))
305}
306
307#[must_use]
308pub fn nns_topology_summary_report_text(report: &NnsTopologySummaryReport) -> String {
309 let mut lines = Vec::new();
310 lines.push(format!(
311 "topology: {} subnets {} nodes {} node_operators {} node_providers {} data_centers {}",
312 report.network,
313 report.subnet_count,
314 report.node_count,
315 report.node_operator_count,
316 report.node_provider_count,
317 report.data_center_count
318 ));
319 lines.push("counts:".to_string());
320 lines.push(render_count_table(report));
321 lines.push("subnet_kinds:".to_string());
322 lines.push(render_kind_table(report));
323 lines.push("join_coverage:".to_string());
324 lines.push(render_join_coverage_table(report));
325 lines.push("registry_versions:".to_string());
326 lines.push(render_registry_version_table(report));
327 lines.join("\n")
328}
329
330#[must_use]
331pub fn nns_topology_refresh_report_text(report: &NnsTopologyRefreshReport) -> String {
332 let mut lines = Vec::new();
333 lines.push(format!(
334 "topology_refresh: {} components {} wrote {} replaced {} dry_run {}",
335 report.network,
336 report.component_count,
337 report.wrote_cache_count,
338 report.replaced_existing_cache_count,
339 yes_no(report.dry_run)
340 ));
341 lines.push(format!("source_endpoint: {}", report.source_endpoint));
342 lines.push(render_refresh_table(report));
343 lines.join("\n")
344}
345
346fn topology_summary_report_from_reports(
347 network: String,
348 source_endpoint: String,
349 subnet_report: SubnetCatalogListReport,
350 node_report: NnsNodeListReport,
351 node_provider_report: NnsNodeProviderListReport,
352 node_operator_report: NnsNodeOperatorListReport,
353 data_center_report: NnsDataCenterListReport,
354) -> NnsTopologySummaryReport {
355 let application_subnet_count = subnet_count_by_kind(&subnet_report, SubnetKind::Application);
356 let system_subnet_count = subnet_count_by_kind(&subnet_report, SubnetKind::System);
357 let unknown_subnet_count = subnet_count_by_kind(&subnet_report, SubnetKind::Unknown);
358 let application_node_count =
359 node_count_by_subnet_kind(&node_report, NNS_NODE_SUBNET_KIND_APPLICATION);
360 let system_node_count = node_count_by_subnet_kind(&node_report, NNS_NODE_SUBNET_KIND_SYSTEM);
361 let unknown_node_count = node_count_by_subnet_kind(&node_report, NNS_NODE_SUBNET_KIND_UNKNOWN);
362 let join_coverage = topology_summary_join_coverage_counts(
363 &node_report,
364 &node_provider_report,
365 &node_operator_report,
366 &data_center_report,
367 );
368 let registry_versions = topology_summary_registry_versions(
369 &subnet_report,
370 &node_report,
371 &node_provider_report,
372 &node_operator_report,
373 &data_center_report,
374 );
375
376 NnsTopologySummaryReport {
377 schema_version: NNS_TOPOLOGY_SUMMARY_REPORT_SCHEMA_VERSION,
378 network,
379 source_endpoint,
380 subnet_count: subnet_report.subnets.len(),
381 application_subnet_count,
382 system_subnet_count,
383 unknown_subnet_count,
384 routing_range_count: subnet_report
385 .subnets
386 .iter()
387 .map(|subnet| subnet.range_count)
388 .sum(),
389 node_count: node_report.node_count,
390 application_node_count,
391 system_node_count,
392 unknown_node_count,
393 node_provider_count: node_provider_report.node_provider_count,
394 node_operator_count: node_operator_report.node_operator_count,
395 data_center_count: data_center_report.data_center_count,
396 nodes_with_known_node_provider_count: join_coverage.nodes_with_known_node_provider_count,
397 nodes_with_unknown_node_provider_count: node_report
398 .node_count
399 .saturating_sub(join_coverage.nodes_with_known_node_provider_count),
400 nodes_with_known_node_operator_count: join_coverage.nodes_with_known_node_operator_count,
401 nodes_with_unknown_node_operator_count: node_report
402 .node_count
403 .saturating_sub(join_coverage.nodes_with_known_node_operator_count),
404 nodes_with_known_data_center_count: join_coverage.nodes_with_known_data_center_count,
405 nodes_with_unknown_data_center_count: node_report
406 .node_count
407 .saturating_sub(join_coverage.nodes_with_known_data_center_count),
408 node_operators_with_known_node_provider_count: join_coverage
409 .node_operators_with_known_node_provider_count,
410 node_operators_with_unknown_node_provider_count: node_operator_report
411 .node_operator_count
412 .saturating_sub(join_coverage.node_operators_with_known_node_provider_count),
413 node_operators_with_known_data_center_count: join_coverage
414 .node_operators_with_known_data_center_count,
415 node_operators_with_unknown_data_center_count: node_operator_report
416 .node_operator_count
417 .saturating_sub(join_coverage.node_operators_with_known_data_center_count),
418 subnet_catalog_stale: subnet_report.catalog_stale,
419 subnet_catalog_stale_reason: subnet_report.stale_reason,
420 registry_versions,
421 }
422}
423
424struct NnsTopologyJoinCoverageCounts {
428 nodes_with_known_node_provider_count: usize,
429 nodes_with_known_node_operator_count: usize,
430 nodes_with_known_data_center_count: usize,
431 node_operators_with_known_node_provider_count: usize,
432 node_operators_with_known_data_center_count: usize,
433}
434
435fn topology_summary_join_coverage_counts(
436 node_report: &NnsNodeListReport,
437 node_provider_report: &NnsNodeProviderListReport,
438 node_operator_report: &NnsNodeOperatorListReport,
439 data_center_report: &NnsDataCenterListReport,
440) -> NnsTopologyJoinCoverageCounts {
441 let node_provider_principals = node_provider_report
442 .node_providers
443 .iter()
444 .map(|provider| provider.node_provider_principal.as_str())
445 .collect::<BTreeSet<_>>();
446 let node_operator_principals = node_operator_report
447 .node_operators
448 .iter()
449 .map(|operator| operator.node_operator_principal.as_str())
450 .collect::<BTreeSet<_>>();
451 let data_center_ids = data_center_report
452 .data_centers
453 .iter()
454 .map(|data_center| data_center.data_center_id.as_str())
455 .collect::<BTreeSet<_>>();
456
457 NnsTopologyJoinCoverageCounts {
458 nodes_with_known_node_provider_count: node_count_with_known_node_provider(
459 node_report,
460 &node_provider_principals,
461 ),
462 nodes_with_known_node_operator_count: node_count_with_known_node_operator(
463 node_report,
464 &node_operator_principals,
465 ),
466 nodes_with_known_data_center_count: node_count_with_known_data_center(
467 node_report,
468 &data_center_ids,
469 ),
470 node_operators_with_known_node_provider_count: operator_count_with_known_node_provider(
471 node_operator_report,
472 &node_provider_principals,
473 ),
474 node_operators_with_known_data_center_count: operator_count_with_known_data_center(
475 node_operator_report,
476 &data_center_ids,
477 ),
478 }
479}
480
481fn topology_summary_registry_versions(
482 subnet_report: &SubnetCatalogListReport,
483 node_report: &NnsNodeListReport,
484 node_provider_report: &NnsNodeProviderListReport,
485 node_operator_report: &NnsNodeOperatorListReport,
486 data_center_report: &NnsDataCenterListReport,
487) -> Vec<NnsTopologyRegistryVersionRow> {
488 vec![
489 registry_version_row(
490 "subnet_catalog",
491 subnet_report.registry_version,
492 subnet_report.fetched_at.clone(),
493 None,
494 Some(subnet_report.catalog_stale),
495 ),
496 registry_version_row(
497 "nodes",
498 node_report.registry_version,
499 node_report.fetched_at.clone(),
500 Some(node_report.source_endpoint.clone()),
501 None,
502 ),
503 registry_version_row(
504 "node_providers",
505 node_provider_report.registry_version,
506 node_provider_report.fetched_at.clone(),
507 Some(node_provider_report.source_endpoint.clone()),
508 None,
509 ),
510 registry_version_row(
511 "node_operators",
512 node_operator_report.registry_version,
513 node_operator_report.fetched_at.clone(),
514 Some(node_operator_report.source_endpoint.clone()),
515 None,
516 ),
517 registry_version_row(
518 "data_centers",
519 data_center_report.registry_version,
520 data_center_report.fetched_at.clone(),
521 Some(data_center_report.source_endpoint.clone()),
522 None,
523 ),
524 ]
525}
526
527fn topology_refresh_report_from_reports(
528 network: String,
529 source_endpoint: String,
530 dry_run: bool,
531 reports: NnsTopologyRefreshComponentReports,
532) -> NnsTopologyRefreshReport {
533 let components = vec![
534 refresh_row_from_subnet_report(reports.subnet),
535 refresh_row_from_node_report(reports.node),
536 refresh_row_from_node_provider_report(reports.node_provider),
537 refresh_row_from_node_operator_report(reports.node_operator),
538 refresh_row_from_data_center_report(reports.data_center),
539 ];
540 let wrote_cache_count = components
541 .iter()
542 .filter(|component| component.wrote_cache)
543 .count();
544 let replaced_existing_cache_count = components
545 .iter()
546 .filter(|component| component.replaced_existing_cache)
547 .count();
548
549 NnsTopologyRefreshReport {
550 schema_version: NNS_TOPOLOGY_REFRESH_REPORT_SCHEMA_VERSION,
551 network,
552 source_endpoint,
553 dry_run,
554 component_count: components.len(),
555 wrote_cache_count,
556 replaced_existing_cache_count,
557 components,
558 }
559}
560
561struct NnsTopologyRefreshComponentReports {
565 subnet: SubnetCatalogRefreshReport,
566 node: NnsNodeRefreshReport,
567 node_provider: NnsNodeProviderRefreshReport,
568 node_operator: NnsNodeOperatorRefreshReport,
569 data_center: NnsDataCenterRefreshReport,
570}
571
572fn refresh_row_from_subnet_report(report: SubnetCatalogRefreshReport) -> NnsTopologyRefreshRow {
573 NnsTopologyRefreshRow {
574 source: "subnet_catalog".to_string(),
575 cache_path: report.catalog_path,
576 refresh_lock_path: report.refresh_lock_path,
577 registry_version: report.registry_version,
578 fetched_at: report.fetched_at,
579 source_endpoint: report.source_endpoint,
580 fetched_by: report.fetched_by,
581 dry_run: report.dry_run,
582 wrote_cache: report.wrote_catalog,
583 replaced_existing_cache: report.replaced_existing_catalog,
584 item_count: report.subnet_count,
585 }
586}
587
588fn refresh_row_from_node_report(report: NnsNodeRefreshReport) -> NnsTopologyRefreshRow {
589 NnsTopologyRefreshRow {
590 source: "nodes".to_string(),
591 cache_path: report.cache_path,
592 refresh_lock_path: report.refresh_lock_path,
593 registry_version: report.registry_version,
594 fetched_at: report.fetched_at,
595 source_endpoint: report.source_endpoint,
596 fetched_by: report.fetched_by,
597 dry_run: report.dry_run,
598 wrote_cache: report.wrote_cache,
599 replaced_existing_cache: report.replaced_existing_cache,
600 item_count: report.node_count,
601 }
602}
603
604fn refresh_row_from_node_provider_report(
605 report: NnsNodeProviderRefreshReport,
606) -> NnsTopologyRefreshRow {
607 NnsTopologyRefreshRow {
608 source: "node_providers".to_string(),
609 cache_path: report.cache_path,
610 refresh_lock_path: report.refresh_lock_path,
611 registry_version: report.registry_version,
612 fetched_at: report.fetched_at,
613 source_endpoint: report.source_endpoint,
614 fetched_by: report.fetched_by,
615 dry_run: report.dry_run,
616 wrote_cache: report.wrote_cache,
617 replaced_existing_cache: report.replaced_existing_cache,
618 item_count: report.node_provider_count,
619 }
620}
621
622fn refresh_row_from_node_operator_report(
623 report: NnsNodeOperatorRefreshReport,
624) -> NnsTopologyRefreshRow {
625 NnsTopologyRefreshRow {
626 source: "node_operators".to_string(),
627 cache_path: report.cache_path,
628 refresh_lock_path: report.refresh_lock_path,
629 registry_version: report.registry_version,
630 fetched_at: report.fetched_at,
631 source_endpoint: report.source_endpoint,
632 fetched_by: report.fetched_by,
633 dry_run: report.dry_run,
634 wrote_cache: report.wrote_cache,
635 replaced_existing_cache: report.replaced_existing_cache,
636 item_count: report.node_operator_count,
637 }
638}
639
640fn refresh_row_from_data_center_report(
641 report: NnsDataCenterRefreshReport,
642) -> NnsTopologyRefreshRow {
643 NnsTopologyRefreshRow {
644 source: "data_centers".to_string(),
645 cache_path: report.cache_path,
646 refresh_lock_path: report.refresh_lock_path,
647 registry_version: report.registry_version,
648 fetched_at: report.fetched_at,
649 source_endpoint: report.source_endpoint,
650 fetched_by: report.fetched_by,
651 dry_run: report.dry_run,
652 wrote_cache: report.wrote_cache,
653 replaced_existing_cache: report.replaced_existing_cache,
654 item_count: report.data_center_count,
655 }
656}
657
658fn enforce_mainnet_network(network: &str) -> Result<(), NnsTopologyHostError> {
659 if network == MAINNET_NETWORK {
660 return Ok(());
661 }
662 Err(NnsTopologyHostError::UnsupportedNetwork {
663 network: network.to_string(),
664 })
665}
666
667fn subnet_count_by_kind(report: &SubnetCatalogListReport, kind: SubnetKind) -> usize {
668 report
669 .subnets
670 .iter()
671 .filter(|subnet| subnet.subnet_kind == kind)
672 .count()
673}
674
675fn node_count_by_subnet_kind(report: &NnsNodeListReport, kind: &str) -> usize {
676 report
677 .nodes
678 .iter()
679 .filter(|node| node.subnet_kind.eq_ignore_ascii_case(kind))
680 .count()
681}
682
683fn node_count_with_known_node_provider(
684 report: &NnsNodeListReport,
685 providers: &BTreeSet<&str>,
686) -> usize {
687 report
688 .nodes
689 .iter()
690 .filter(|node| providers.contains(node.node_provider_principal.as_str()))
691 .count()
692}
693
694fn node_count_with_known_node_operator(
695 report: &NnsNodeListReport,
696 operators: &BTreeSet<&str>,
697) -> usize {
698 report
699 .nodes
700 .iter()
701 .filter(|node| operators.contains(node.node_operator_principal.as_str()))
702 .count()
703}
704
705fn node_count_with_known_data_center(
706 report: &NnsNodeListReport,
707 data_centers: &BTreeSet<&str>,
708) -> usize {
709 report
710 .nodes
711 .iter()
712 .filter(|node| data_centers.contains(node.data_center_id.as_str()))
713 .count()
714}
715
716fn operator_count_with_known_node_provider(
717 report: &NnsNodeOperatorListReport,
718 providers: &BTreeSet<&str>,
719) -> usize {
720 report
721 .node_operators
722 .iter()
723 .filter(|operator| providers.contains(operator.node_provider_principal.as_str()))
724 .count()
725}
726
727fn operator_count_with_known_data_center(
728 report: &NnsNodeOperatorListReport,
729 data_centers: &BTreeSet<&str>,
730) -> usize {
731 report
732 .node_operators
733 .iter()
734 .filter(|operator| data_centers.contains(operator.data_center_id.as_str()))
735 .count()
736}
737
738fn registry_version_row(
739 source: &str,
740 registry_version: u64,
741 fetched_at: String,
742 source_endpoint: Option<String>,
743 stale: Option<bool>,
744) -> NnsTopologyRegistryVersionRow {
745 NnsTopologyRegistryVersionRow {
746 source: source.to_string(),
747 registry_version,
748 fetched_at,
749 source_endpoint: source_endpoint.unwrap_or_else(|| "-".to_string()),
750 stale,
751 }
752}
753
754fn render_count_table(report: &NnsTopologySummaryReport) -> String {
755 let headers = ["METRIC", "COUNT"];
756 let rows = [
757 ["subnets".to_string(), report.subnet_count.to_string()],
758 [
759 "routing_ranges".to_string(),
760 report.routing_range_count.to_string(),
761 ],
762 ["nodes".to_string(), report.node_count.to_string()],
763 [
764 "node_operators".to_string(),
765 report.node_operator_count.to_string(),
766 ],
767 [
768 "node_providers".to_string(),
769 report.node_provider_count.to_string(),
770 ],
771 [
772 "data_centers".to_string(),
773 report.data_center_count.to_string(),
774 ],
775 ];
776 let alignments = [ColumnAlign::Left, ColumnAlign::Right];
777 render_table(&headers, &rows, &alignments)
778}
779
780fn render_kind_table(report: &NnsTopologySummaryReport) -> String {
781 let headers = ["KIND", "SUBNETS", "NODES"];
782 let rows = [
783 [
784 NNS_NODE_SUBNET_KIND_APPLICATION.to_string(),
785 report.application_subnet_count.to_string(),
786 report.application_node_count.to_string(),
787 ],
788 [
789 NNS_NODE_SUBNET_KIND_SYSTEM.to_string(),
790 report.system_subnet_count.to_string(),
791 report.system_node_count.to_string(),
792 ],
793 [
794 NNS_NODE_SUBNET_KIND_UNKNOWN.to_string(),
795 report.unknown_subnet_count.to_string(),
796 report.unknown_node_count.to_string(),
797 ],
798 ];
799 let alignments = [ColumnAlign::Left, ColumnAlign::Right, ColumnAlign::Right];
800 render_table(&headers, &rows, &alignments)
801}
802
803fn render_join_coverage_table(report: &NnsTopologySummaryReport) -> String {
804 let headers = ["LINK", "KNOWN", "UNKNOWN"];
805 let rows = [
806 [
807 "nodes_to_node_providers".to_string(),
808 report.nodes_with_known_node_provider_count.to_string(),
809 report.nodes_with_unknown_node_provider_count.to_string(),
810 ],
811 [
812 "nodes_to_node_operators".to_string(),
813 report.nodes_with_known_node_operator_count.to_string(),
814 report.nodes_with_unknown_node_operator_count.to_string(),
815 ],
816 [
817 "nodes_to_data_centers".to_string(),
818 report.nodes_with_known_data_center_count.to_string(),
819 report.nodes_with_unknown_data_center_count.to_string(),
820 ],
821 [
822 "node_operators_to_node_providers".to_string(),
823 report
824 .node_operators_with_known_node_provider_count
825 .to_string(),
826 report
827 .node_operators_with_unknown_node_provider_count
828 .to_string(),
829 ],
830 [
831 "node_operators_to_data_centers".to_string(),
832 report
833 .node_operators_with_known_data_center_count
834 .to_string(),
835 report
836 .node_operators_with_unknown_data_center_count
837 .to_string(),
838 ],
839 ];
840 let alignments = [ColumnAlign::Left, ColumnAlign::Right, ColumnAlign::Right];
841 render_table(&headers, &rows, &alignments)
842}
843
844fn render_registry_version_table(report: &NnsTopologySummaryReport) -> String {
845 let headers = ["SOURCE", "VERSION", "FETCHED_AT", "STALE", "ENDPOINT"];
846 let rows = report
847 .registry_versions
848 .iter()
849 .map(|row| {
850 [
851 row.source.clone(),
852 row.registry_version.to_string(),
853 row.fetched_at.clone(),
854 row.stale
855 .map_or_else(|| "-".to_string(), |stale| yes_no(stale).to_string()),
856 row.source_endpoint.clone(),
857 ]
858 })
859 .collect::<Vec<_>>();
860 let alignments = [
861 ColumnAlign::Left,
862 ColumnAlign::Right,
863 ColumnAlign::Left,
864 ColumnAlign::Left,
865 ColumnAlign::Left,
866 ];
867 render_table(&headers, &rows, &alignments)
868}
869
870fn render_refresh_table(report: &NnsTopologyRefreshReport) -> String {
871 let headers = [
872 "SOURCE",
873 "COUNT",
874 "VERSION",
875 "FETCHED_AT",
876 "WROTE",
877 "REPLACED",
878 "CACHE",
879 ];
880 let rows = report
881 .components
882 .iter()
883 .map(|row| {
884 [
885 row.source.clone(),
886 row.item_count.to_string(),
887 row.registry_version.to_string(),
888 row.fetched_at.clone(),
889 yes_no(row.wrote_cache).to_string(),
890 yes_no(row.replaced_existing_cache).to_string(),
891 row.cache_path.clone(),
892 ]
893 })
894 .collect::<Vec<_>>();
895 let alignments = [
896 ColumnAlign::Left,
897 ColumnAlign::Right,
898 ColumnAlign::Right,
899 ColumnAlign::Left,
900 ColumnAlign::Left,
901 ColumnAlign::Left,
902 ColumnAlign::Left,
903 ];
904 render_table(&headers, &rows, &alignments)
905}
906
907#[cfg(test)]
908mod tests;