Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ All notable changes to this project will be documented in this file.
- Extend `validate_program_account!` migration to remaining user and multicastgroup allowlist processors (`set_bgp_status`, `delete`, `closeaccount`, publisher/subscriber `add`/`remove`)
- Add `OutboundIcmp` target type (`= 2`) to the geolocation onchain program, enabling ICMP-based probing as an alternative to TWAMP for outbound geolocation targets
- Allow pending users with subs to be deleted
- Add `doublezero link topology {create,delete,clear,backfill,list}` subcommands for managing flex-algo topologies; `topology clear` auto-discovers tagged links when `--links` is omitted
- Add `TopologyInfo` onchain account for IS-IS flex-algo link classification: auto-assigned TE admin-group bit (1–62), derived flex-algo number (128 + bit), and constraint type (`include-any`/`include-all`); capped at 62 topologies via `AdminGroupBits` resource extension
- Add `link_topologies: Vec<Pubkey>` (capped at 8) and `link_flags: u8` (bit 0 = unicast-drained) to the `Link` account
- Add `include_topologies` to the `Tenant` account for topology-filtered routing opt-in
Expand Down
38 changes: 34 additions & 4 deletions client/doublezero/src/cli/link.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use clap::{Args, Subcommand};
use doublezero_cli::link::{
accept::AcceptLinkCliCommand, delete::*, dzx_create::CreateDZXLinkCliCommand, get::*,
latency::LinkLatencyCliCommand, list::*, sethealth::SetLinkHealthCliCommand, update::*,
wan_create::*,
use doublezero_cli::{
link::{
accept::AcceptLinkCliCommand, delete::*, dzx_create::CreateDZXLinkCliCommand, get::*,
latency::LinkLatencyCliCommand, list::*, sethealth::SetLinkHealthCliCommand, update::*,
wan_create::*,
},
topology::{
backfill::BackfillTopologyCliCommand, clear::ClearTopologyCliCommand,
create::CreateTopologyCliCommand, delete::DeleteTopologyCliCommand,
list::ListTopologyCliCommand,
},
};

#[derive(Args, Debug)]
Expand Down Expand Up @@ -53,4 +60,27 @@ pub enum LinkCommands {
// Hidden because this is an internal/operational command not intended for general CLI users.
#[clap(hide = true)]
SetHealth(SetLinkHealthCliCommand),
/// Manage link topologies
#[clap()]
Topology(TopologyLinkCommand),
}

#[derive(Args, Debug)]
pub struct TopologyLinkCommand {
#[command(subcommand)]
pub command: TopologyCommands,
}

#[derive(Debug, Subcommand)]
pub enum TopologyCommands {
/// Create a new topology
Create(CreateTopologyCliCommand),
/// Delete a topology
Delete(DeleteTopologyCliCommand),
/// Clear a topology from links
Clear(ClearTopologyCliCommand),
/// Backfill FlexAlgoNodeSegment entries on existing Vpnv4 loopbacks
Backfill(BackfillTopologyCliCommand),
/// List all topologies
List(ListTopologyCliCommand),
}
9 changes: 8 additions & 1 deletion client/doublezero/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::cli::{
AirdropCommands, AuthorityCommands, FeatureFlagsCommands, FoundationAllowlistCommands,
GlobalConfigCommands, QaAllowlistCommands,
},
link::LinkCommands,
link::{LinkCommands, TopologyCommands},
location::LocationCommands,
user::UserCommands,
};
Expand Down Expand Up @@ -232,6 +232,13 @@ async fn main() -> eyre::Result<()> {
LinkCommands::Latency(args) => args.execute(&client, &mut handle),
LinkCommands::Delete(args) => args.execute(&client, &mut handle),
LinkCommands::SetHealth(args) => args.execute(&client, &mut handle),
LinkCommands::Topology(args) => match args.command {
TopologyCommands::Create(args) => args.execute(&client, &mut handle),
TopologyCommands::Delete(args) => args.execute(&client, &mut handle),
TopologyCommands::Clear(args) => args.execute(&client, &mut handle),
TopologyCommands::Backfill(args) => args.execute(&client, &mut handle),
TopologyCommands::List(args) => args.execute(&client, &mut handle),
},
},
Command::AccessPass(command) => match command.command {
cli::accesspass::AccessPassCommands::Set(args) => args.execute(&client, &mut handle),
Expand Down
35 changes: 34 additions & 1 deletion smartcontract/cli/src/doublezerocommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ use doublezero_sdk::{
remove_administrator::RemoveAdministratorTenantCommand, update::UpdateTenantCommand,
update_payment_status::UpdatePaymentStatusCommand,
},
topology::{
backfill::BackfillTopologyCommand, clear::ClearTopologyCommand,
create::CreateTopologyCommand, delete::DeleteTopologyCommand,
list::ListTopologyCommand,
},
user::{
create::CreateUserCommand, create_subscribe::CreateSubscribeUserCommand,
delete::DeleteUserCommand, get::GetUserCommand, list::ListUserCommand,
Expand All @@ -107,7 +112,8 @@ use doublezero_sdk::{
},
telemetry::LinkLatencyStats,
DZClient, Device, DoubleZeroClient, Exchange, GetGlobalConfigCommand, GetGlobalStateCommand,
GlobalConfig, GlobalState, Link, Location, MulticastGroup, ResourceExtensionOwned, User,
GlobalConfig, GlobalState, Link, Location, MulticastGroup, ResourceExtensionOwned,
TopologyInfo, User,
};
use doublezero_serviceability::state::{
accesspass::AccessPass, accountdata::AccountData, contributor::Contributor,
Expand Down Expand Up @@ -337,6 +343,15 @@ pub trait CliCommand {
cmd: GetResourceCommand,
) -> eyre::Result<(Pubkey, ResourceExtensionOwned)>;
fn close_resource(&self, cmd: CloseResourceCommand) -> eyre::Result<Signature>;

fn create_topology(&self, cmd: CreateTopologyCommand) -> eyre::Result<(Signature, Pubkey)>;
fn delete_topology(&self, cmd: DeleteTopologyCommand) -> eyre::Result<Signature>;
fn clear_topology(&self, cmd: ClearTopologyCommand) -> eyre::Result<Signature>;
fn backfill_topology(&self, cmd: BackfillTopologyCommand) -> eyre::Result<Signature>;
fn list_topology(
&self,
cmd: ListTopologyCommand,
) -> eyre::Result<HashMap<Pubkey, TopologyInfo>>;
}

pub struct CliCommandImpl<'a> {
Expand Down Expand Up @@ -803,4 +818,22 @@ impl CliCommand for CliCommandImpl<'_> {
fn close_resource(&self, cmd: CloseResourceCommand) -> eyre::Result<Signature> {
cmd.execute(self.client)
}
fn create_topology(&self, cmd: CreateTopologyCommand) -> eyre::Result<(Signature, Pubkey)> {
cmd.execute(self.client)
}
fn delete_topology(&self, cmd: DeleteTopologyCommand) -> eyre::Result<Signature> {
cmd.execute(self.client)
}
fn clear_topology(&self, cmd: ClearTopologyCommand) -> eyre::Result<Signature> {
cmd.execute(self.client)
}
fn backfill_topology(&self, cmd: BackfillTopologyCommand) -> eyre::Result<Signature> {
cmd.execute(self.client)
}
fn list_topology(
&self,
cmd: ListTopologyCommand,
) -> eyre::Result<HashMap<Pubkey, TopologyInfo>> {
cmd.execute(self.client)
}
}
1 change: 1 addition & 0 deletions smartcontract/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod resource;
pub mod subscribe;
pub mod tenant;
pub mod tests;
pub mod topology;
pub mod user;
pub mod util;
pub mod validators;
95 changes: 95 additions & 0 deletions smartcontract/cli/src/topology/backfill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::{
doublezerocommand::CliCommand,
requirements::{CHECK_BALANCE, CHECK_ID_JSON},
};
use clap::Args;
use doublezero_sdk::commands::topology::backfill::BackfillTopologyCommand;
use solana_sdk::pubkey::Pubkey;
use std::io::Write;

#[derive(Args, Debug)]
pub struct BackfillTopologyCliCommand {
/// Name of the topology to backfill
#[arg(long)]
pub name: String,
/// Device account pubkeys to backfill (one or more)
#[arg(long = "device", value_name = "PUBKEY")]
pub device_pubkeys: Vec<Pubkey>,
}

impl BackfillTopologyCliCommand {
pub fn execute<C: CliCommand, W: Write>(self, client: &C, out: &mut W) -> eyre::Result<()> {
client.check_requirements(CHECK_ID_JSON | CHECK_BALANCE)?;

if self.device_pubkeys.is_empty() {
return Err(eyre::eyre!(
"at least one --device pubkey is required for backfill"
));
}

let sig = client.backfill_topology(BackfillTopologyCommand {
name: self.name.clone(),
device_pubkeys: self.device_pubkeys,
})?;

writeln!(
out,
"Backfilled topology '{}'. Signature: {}",
self.name, sig
)?;

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::doublezerocommand::MockCliCommand;
use doublezero_sdk::commands::topology::backfill::BackfillTopologyCommand;
use mockall::predicate::eq;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
use std::io::Cursor;

#[test]
fn test_backfill_topology_execute_success() {
let mut mock = MockCliCommand::new();
let device1 = Pubkey::new_unique();

mock.expect_check_requirements().returning(|_| Ok(()));
mock.expect_backfill_topology()
.with(eq(BackfillTopologyCommand {
name: "unicast-default".to_string(),
device_pubkeys: vec![device1],
}))
.returning(|_| Ok(Signature::new_unique()));

let cmd = BackfillTopologyCliCommand {
name: "unicast-default".to_string(),
device_pubkeys: vec![device1],
};
let mut out = Cursor::new(Vec::new());
let result = cmd.execute(&mock, &mut out);
assert!(result.is_ok());
let output = String::from_utf8(out.into_inner()).unwrap();
assert!(output.contains("Backfilled topology 'unicast-default'."));
}

#[test]
fn test_backfill_topology_requires_at_least_one_device() {
let mut mock = MockCliCommand::new();
mock.expect_check_requirements().returning(|_| Ok(()));

let cmd = BackfillTopologyCliCommand {
name: "unicast-default".to_string(),
device_pubkeys: vec![],
};
let mut out = Cursor::new(Vec::new());
let result = cmd.execute(&mock, &mut out);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("at least one --device pubkey is required"));
}
}
Loading
Loading