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
13 changes: 12 additions & 1 deletion falcon-lab/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use crate::dendrite::NpuvmCommits;
use crate::scenario::{
InteropScenario, MgdDuoScenario, Scenario, ScenarioOptions,
Interop3LinkScenario, InteropScenario, MgdDuoScenario, Scenario,
ScenarioOptions,
};
use clap::{Args, Parser, Subcommand, ValueEnum};

Expand Down Expand Up @@ -45,6 +46,8 @@ struct TopologyRun {
enum RunTopology {
MgdDuo(ScenarioRun<MgdDuoScenario>),
Interop(ScenarioRun<InteropScenario>),
#[command(name = "interop-3-link")]
Interop3Link(ScenarioRun<Interop3LinkScenario>),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -90,6 +93,8 @@ struct TopologyCleanup {
enum CleanupTopology {
MgdDuo(ScenarioCleanup<MgdDuoScenario>),
Interop(ScenarioCleanup<InteropScenario>),
#[command(name = "interop-3-link")]
Interop3Link(ScenarioCleanup<Interop3LinkScenario>),
}

#[derive(Debug, Args)]
Expand All @@ -116,6 +121,9 @@ async fn run() -> anyhow::Result<()> {
RunTopology::Interop(cmd) => {
cmd.scenario.run(cmd.options.scenario_options()).await?;
}
RunTopology::Interop3Link(cmd) => {
cmd.scenario.run(cmd.options.scenario_options()).await?;
}
},
Command::Cleanup(cmd) => match cmd.topology {
CleanupTopology::MgdDuo(cmd) => {
Expand All @@ -124,6 +132,9 @@ async fn run() -> anyhow::Result<()> {
CleanupTopology::Interop(cmd) => {
cmd.scenario.cleanup()?;
}
CleanupTopology::Interop3Link(cmd) => {
cmd.scenario.cleanup()?;
}
},
Command::Serial(cmd) => {
libfalcon::cli::console(&cmd.node, ".falcon".into()).await?
Expand Down
48 changes: 47 additions & 1 deletion falcon-lab/src/scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
frr::FrrNode,
juniper::{JuniperNode, clear_staged_routing_configs},
mgd::{MgdNode, wait_for_mgd},
topo::{Interop, MgdDuo, Topology},
topo::{Interop, Interop3Link, MgdDuo, Topology},
wait_for_eq, wait_for_eq_stable,
};
use anyhow::{Context, Result};
Expand Down Expand Up @@ -70,6 +70,11 @@ const _: () = {
assert_falcon_compatible(InteropScenario::VARIANTS[i].name());
i += 1;
}
let mut i = 0;
while i < Interop3LinkScenario::VARIANTS.len() {
assert_falcon_compatible(Interop3LinkScenario::VARIANTS[i].name());
i += 1;
}
};

pub(crate) struct ScenarioOptions {
Expand Down Expand Up @@ -198,6 +203,37 @@ impl Scenario for InteropScenario {
}
}

#[derive(Copy, Clone, Debug, ValueEnum, strum::VariantArray)]
pub(crate) enum Interop3LinkScenario {
Bare,
}

impl Interop3LinkScenario {
pub const fn name(self) -> &'static str {
match self {
Self::Bare => "interop3_bare",
}
}
}

impl Scenario for Interop3LinkScenario {
type Topology = Interop3Link;

async fn run(self, options: ScenarioOptions) -> Result<()> {
match self {
Self::Bare => {
clear_staged_routing_configs()
.context("clear stale Junos config")?;
self.run_bare(options.persistent).await
}
}
}

fn cleanup(self) -> Result<()> {
cleanup_interop_3link_deployment(self)
}
}

const CR1_BFD_FRR_CONFIG: &str = "cr1-bfd-frr.conf";
const OP_TIMEOUT: Duration = Duration::from_secs(10);
const LAUNCH_TIMEOUT: Duration = Duration::from_secs(10 * 60);
Expand Down Expand Up @@ -623,6 +659,16 @@ fn cleanup_interop_deployment(scenario: InteropScenario) -> Result<()> {
config_result
}

fn cleanup_interop_3link_deployment(
scenario: Interop3LinkScenario,
) -> Result<()> {
let deployment_result = Interop3Link::build(scenario).map(drop);
let config_result =
clear_staged_routing_configs().context("clear stale Junos config");
deployment_result?;
config_result
}

async fn run_mgd_unnumbered(
scenario: MgdDuoScenario,
persistent: bool,
Expand Down
127 changes: 84 additions & 43 deletions falcon-lab/src/topo.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//! Testing topologies

use anyhow::Result;
use libfalcon::{Runner, node, unit::gb};
use libfalcon::{NodeRef, Runner, node, unit::gb};

use crate::{
eos::EosNode,
frr::FrrNode,
juniper::JuniperNode,
mgd::MgdNode,
scenario::{InteropScenario, MgdDuoScenario},
scenario::{Interop3LinkScenario, InteropScenario, MgdDuoScenario},
};

pub(crate) trait Topology: Sized {
Expand Down Expand Up @@ -62,56 +62,97 @@ pub struct Interop {
pub cr3: JuniperNode,
}

// The bare scenario only drives the runner; node handles arrive with the
// first scenario that interacts with individual VMs.
pub struct Interop3Link {
pub d: Runner,
}

struct InteropNodes {
d: Runner,
ox: NodeRef,
peer: NodeRef,
cr1: NodeRef,
cr2: NodeRef,
cr3: NodeRef,
}

fn build_interop(name: &str, links_per_peer: usize) -> Result<InteropNodes> {
let mut d = Runner::new(name);

node!(d, ox, "helios-3.0", 4, gb(4));
node!(d, cr1, "debian-13.2", 4, gb(4));
node!(d, cr2, "eos-4.35", 4, gb(4));
node!(d, cr3, "junos-23.2", 4, gb(4));
node!(d, peer, "helios-3.0", 4, gb(4));

let mut mac_counter = 0;
let mut new_mac = || {
mac_counter += 1;
format!("a8:40:25:00:00:{mac_counter:02}")
};

for remote in [cr1, cr2, cr3, peer] {
for _ in 0..links_per_peer {
d.softnpu_link(ox, remote, Some(new_mac()), None);
}
}

d.default_ext_link(ox)?;
d.default_ext_link(cr1)?;
d.default_ext_link(cr2)?;
d.default_ext_link(cr3)?;
d.default_ext_link(peer)?;

d.mount("cargo-bay", "/opt/cargo-bay", ox)?;
d.mount_linux("cargo-bay", "/opt/cargo-bay", cr1)?;
d.mount("cargo-bay", "/opt/cargo-bay", cr2)?;
d.mount_linux("cargo-bay", "/opt/cargo-bay", cr3)?;
d.mount("cargo-bay", "/opt/cargo-bay", peer)?;
// The Junos image mounts cargo-bay and applies staged configuration
// from guest-side systemd services. Keep the 9p device in the spec,
// but avoid Falcon's serial-driven setup/mount path for this node.
d.do_setup(cr3, false);

Ok(InteropNodes {
d,
ox,
peer,
cr1,
cr2,
cr3,
})
}

impl Topology for Interop {
type Scenario = InteropScenario;

fn build(scenario: InteropScenario) -> Result<Self> {
let mut d = Runner::new(scenario.name());

node!(d, ox, "helios-3.0", 4, gb(4));
node!(d, cr1, "debian-13.2", 4, gb(4));
node!(d, cr2, "eos-4.35", 4, gb(4));
node!(d, cr3, "junos-23.2", 4, gb(4));
node!(d, peer, "helios-3.0", 4, gb(4));

let mut mac_counter = 0;
let mut new_mac = || {
mac_counter += 1;
format!("a8:40:25:00:00:{mac_counter:02}")
};

d.softnpu_link(ox, cr1, Some(new_mac()), None);
d.softnpu_link(ox, cr2, Some(new_mac()), None);
d.softnpu_link(ox, cr3, Some(new_mac()), None);
d.softnpu_link(ox, peer, Some(new_mac()), None);

d.default_ext_link(ox)?;
d.default_ext_link(cr1)?;
d.default_ext_link(cr2)?;
d.default_ext_link(cr3)?;
d.default_ext_link(peer)?;

d.mount("cargo-bay", "/opt/cargo-bay", ox)?;
d.mount_linux("cargo-bay", "/opt/cargo-bay", cr1)?;
d.mount("cargo-bay", "/opt/cargo-bay", cr2)?;
d.mount_linux("cargo-bay", "/opt/cargo-bay", cr3)?;
d.mount("cargo-bay", "/opt/cargo-bay", peer)?;
// The Junos image mounts cargo-bay and applies staged configuration
// from guest-side systemd services. Keep the 9p device in the spec,
// but avoid Falcon's serial-driven setup/mount path for this node.
d.do_setup(cr3, false);

let nodes = build_interop(scenario.name(), 1)?;
Ok(Self {
d,
ox: MgdNode(ox),
peer: MgdNode(peer),
cr1: FrrNode(cr1),
cr2: EosNode(cr2),
cr3: JuniperNode(cr3),
d: nodes.d,
ox: MgdNode(nodes.ox),
peer: MgdNode(nodes.peer),
cr1: FrrNode(nodes.cr1),
cr2: EosNode(nodes.cr2),
cr3: JuniperNode(nodes.cr3),
})
}

fn runner_mut(&mut self) -> &mut Runner {
&mut self.d
}
}

impl Topology for Interop3Link {
type Scenario = Interop3LinkScenario;

fn build(scenario: Interop3LinkScenario) -> Result<Self> {
let nodes = build_interop(scenario.name(), 3)?;
Ok(Self { d: nodes.d })
}

fn runner_mut(&mut self) -> &mut Runner {
&mut self.d
}
}