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}