Skip to main content

isothrum_compiler/
topology.rs

1//! The drawing the editor sends: machines (the topology's nodes), the LANs drawn around them, and
2//! the cables between their ports. Read once into types that hold only what Isoloom can take: an
3//! OS Isoloom knows, a port's protocol, an appliance kind.
4
5use std::num::NonZeroU16;
6
7use indexmap::IndexMap;
8use isoloom_spec::syntax::{OsRef, PortProtocol};
9use isoloom_spec::{Appliance, Tc};
10use serde::{Deserialize, Serialize};
11
12/// A project as drawn: its machines, LANs and cables.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct Topology {
16    /// Kebab-case id of the project, the spec's `name`.
17    pub name: String,
18    /// The nodes, by name: machines, and automatic routers.
19    pub nodes: IndexMap<String, Node>,
20    /// The LANs: name → its block. Machines join one through [`Node::networks`].
21    #[serde(default)]
22    pub networks: IndexMap<String, Lan>,
23    /// The cables between two nodes' ports.
24    #[serde(default)]
25    pub links: Vec<Link>,
26}
27
28/// A LAN (or VLAN): one shared segment for every machine on it.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct Lan {
32    /// An IPv4 block inside 10.0.0.0/8, /24 to /29 (Isoloom's rule), e.g. `10.10.1.0/24`.
33    pub cidr: String,
34    /// Whether machines on it may reach the internet (Isoloom's default: yes).
35    #[serde(default = "yes")]
36    pub internet: bool,
37    /// A VLAN of a switch: the switch (or the first of the switches a trunk joins) it belongs to.
38    /// In the spec it nests under that switch's LAN (`sw1.vlan10`).
39    #[serde(default)]
40    pub switch: Option<String>,
41    /// A VLAN's id on its switch.
42    #[serde(default)]
43    pub vlan: Option<u16>,
44    /// The switch's machine when it runs a Cisco OS (a node of its own, with no networks):
45    /// Isoloom's `switch` on the LAN its VLANs nest under.
46    #[serde(default)]
47    pub device: Option<String>,
48    /// How the LAN behaves (a slow or lossy link): Isoloom's `tc`.
49    #[serde(default)]
50    pub tc: Option<Tc>,
51}
52
53fn yes() -> bool {
54    true
55}
56
57/// A node of the drawing: a machine, or an automatic router.
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59#[serde(deny_unknown_fields)]
60pub struct Node {
61    /// What it is.
62    pub kind: Kind,
63    /// The machine the user lands on (Isoloom's `access`).
64    #[serde(default)]
65    pub access: bool,
66    /// The ports it answers on.
67    #[serde(default)]
68    pub services: Vec<Service>,
69    /// The LANs it is on: LAN name → the last octet of its address there, or `None` for the
70    /// next free one.
71    #[serde(default)]
72    pub networks: IndexMap<String, Option<u8>>,
73}
74
75/// A port a machine answers on, and what it speaks: `tcp` (the default), `udp`, or TCP speaking
76/// `http`, `https` or `tls` (Isoloom's port `protocol`).
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
78#[serde(deny_unknown_fields)]
79pub struct Service {
80    /// The port, 1 to 65535.
81    pub port: NonZeroU16,
82    /// What it speaks.
83    #[serde(default)]
84    pub protocol: PortProtocol,
85}
86
87/// What a node is.
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase", deny_unknown_fields)]
90#[non_exhaustive]
91pub enum Kind {
92    /// A container from a published image: Isoloom's `app.image` (or a network appliance's).
93    Container {
94        /// The image, by tag or digest. Empty for `cisco-dynamips`, which Isoloom builds.
95        image: String,
96        /// The image runs no service of its own (a stock Linux image): it is kept running idle,
97        /// as a machine to work from.
98        #[serde(default)]
99        idle: bool,
100        /// A network OS Isoloom wires as its image expects (Cisco IOL, as vrnetlab builds it).
101        #[serde(default)]
102        appliance: Option<Appliance>,
103        /// The appliance's own configuration (IOS commands), written to `configs/<name>.cfg`.
104        #[serde(default)]
105        config: Option<String>,
106    },
107    /// A machine of an OS, set up by its provisioning steps: Isoloom's `os` plus `app.steps`
108    /// (each target decides whether it is a VM or a container built from the OS's image).
109    Vm {
110        /// The OS, `<family>@<version>` (`debian@12`).
111        os: OsRef,
112        /// Steps run in order: `.sh` scripts (`.ps1` on Windows), or Ansible playbooks (`.yml`).
113        #[serde(default)]
114        provision: Vec<String>,
115    },
116    /// An automatic router: not a machine. The networks it is on (LANs, VLANs, cables) can all
117    /// reach each other, through Isoloom's own router (`reach` rules both ways); nothing to
118    /// configure. A router you configure yourself is a container or a VM (FRR, for instance).
119    Router {},
120}
121
122/// A cable between two ports. A port carries at most one cable.
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124#[serde(deny_unknown_fields)]
125pub struct Link {
126    /// One end.
127    pub a: Endpoint,
128    /// The other end.
129    pub b: Endpoint,
130    /// How the cable behaves (latency, jitter, loss, bandwidth): Isoloom's `tc` on its network.
131    #[serde(default)]
132    pub tc: Option<Tc>,
133}
134
135/// One end of a cable: a node's port.
136#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
137#[serde(deny_unknown_fields)]
138pub struct Endpoint {
139    /// The node.
140    pub node: String,
141    /// The port's name on the node (`eth0`, `GigabitEthernet0/1`, ...).
142    pub port: String,
143}