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