Skip to content
Merged
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
14 changes: 14 additions & 0 deletions rust/agama-lib/share/examples/network/bond.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"network": {
"connections": [
{
"id": "bond0",
"bond": {
"ports": ["eth0", "eth1"],
"mode": "active-backup",
"options": "primary=eth1"
}
}
]
}
}
13 changes: 13 additions & 0 deletions rust/agama-lib/share/examples/network/bridge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"network": {
"connections": [
{
"id": "br0",
"bridge": {
"ports": ["eth0", "eth1"],
"stp": false
}
}
]
}
}
64 changes: 59 additions & 5 deletions rust/agama-lib/share/profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,44 @@
}
}
},
"bridge": {
"type": "object",
"title": "Bridge configuration",
"additionalProperties": false,
"properties": {
"stp": {
"title": "whether the Spanning Tree Protocol is enabled or not",
"type": "boolean"
},
"forwardDelay": {
"title": "Spanning Tree Protocol forward delay, in seconds",
"type": "integer",
"minimum": 0
},
"priority": {
"title": "Spanning Tree Protocol priority (lower values are 'better')",
"type": "integer",
"minimum": 0
},
"maxAge": {
"title": "Spanning Tree Protocol maximum message age, in seconds",
"type": "integer",
"minimum": 0
},
"helloTime": {
"title": "Spanning Tree Protocol hello time, in seconds",
"type": "integer",
"minimum": 0
},
"ports": {
"type": "array",
"items": {
"title": "A list of the interfaces or connections to be part of the bridge",
"type": "string"
}
}
}
},
"match": {
"type": "object",
"title": "Match settings",
Expand Down Expand Up @@ -669,7 +707,11 @@
}
},
"required": ["name"],
"oneOf": [{ "required": ["body"] }, { "required": ["url"] }, { "required": ["content"]}]
"oneOf": [
{ "required": ["body"] },
{ "required": ["url"] },
{ "required": ["content"] }
]
},
"postPartitioning": {
"title": "User-defined installation script that runs after the partitioning finishes",
Expand Down Expand Up @@ -697,7 +739,11 @@
}
},
"required": ["name"],
"oneOf": [{ "required": ["body"] }, { "required": ["url"] }, { "required": ["content"]}]
"oneOf": [
{ "required": ["body"] },
{ "required": ["url"] },
{ "required": ["content"] }
]
},
"postScript": {
"title": "User-defined installation script that runs after the installation finishes",
Expand Down Expand Up @@ -730,7 +776,11 @@
}
},
"required": ["name"],
"oneOf": [{ "required": ["body"] }, { "required": ["url"] }, { "required": ["content"]}]
"oneOf": [
{ "required": ["body"] },
{ "required": ["url"] },
{ "required": ["content"] }
]
},
"initScript": {
"title": "User-defined installation script that runs during the first boot of the target system, once the installation is finished",
Expand Down Expand Up @@ -758,7 +808,11 @@
}
},
"required": ["name"],
"oneOf": [{ "required": ["body"] }, { "required": ["url"] }, { "required": ["content"]}]
"oneOf": [
{ "required": ["body"] },
{ "required": ["url"] },
{ "required": ["content"] }
]
},
"file": {
"title": "User-defined file to deploy",
Expand Down Expand Up @@ -795,7 +849,7 @@
}
},
"required": ["destination"],
"oneOf": [{ "required": ["url"] }, { "required": ["content"]}]
"oneOf": [{ "required": ["url"] }, { "required": ["content"] }]
}
}
}
1 change: 0 additions & 1 deletion rust/agama-lib/src/hostname/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ impl<'a> HostnameClient<'a> {
let settings = HostnameSettings {
hostname: Some(hostname),
static_hostname: Some(static_hostname),
..Default::default()
};

Ok(settings)
Expand Down
33 changes: 32 additions & 1 deletion rust/agama-lib/src/network/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ fn add_ordered_connection(
}
}

if let Some(bridge) = &conn.bridge {
for port in &bridge.ports {
if let Some(conn) = find_connection(port, conns) {
add_ordered_connection(conn, conns, ordered);
} else if !ordered.contains(&conn.id) {
ordered.push(port.clone());
}
}
}

if !ordered.contains(&conn.id) {
ordered.push(conn.id.to_owned())
}
Expand All @@ -119,7 +129,7 @@ fn default_connection(id: &str) -> NetworkConnection {
#[cfg(test)]
mod tests {
use super::ordered_connections;
use crate::network::settings::{BondSettings, NetworkConnection};
use crate::network::settings::{BondSettings, BridgeSettings, NetworkConnection};

#[test]
fn test_ordered_connections() {
Expand All @@ -132,6 +142,15 @@ mod tests {
..Default::default()
};

let bridge = NetworkConnection {
id: "br0".to_string(),
bridge: Some(BridgeSettings {
ports: vec!["eth0".to_string(), "eth1".to_string(), "eth3".to_string()],
..Default::default()
}),
..Default::default()
};

let eth0 = NetworkConnection {
id: "eth0".to_string(),
..Default::default()
Expand All @@ -157,6 +176,18 @@ mod tests {
"bond0".to_string(),
"eth2".to_string()
]
);

let conns = vec![bridge];
let ordered = ordered_connections(&conns);
assert_eq!(
ordered,
vec![
"eth0".to_string(),
"eth1".to_string(),
"eth3".to_string(),
"br0".to_string()
]
)
}
}
103 changes: 83 additions & 20 deletions rust/agama-network/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
//! * This module contains the types that represent the network concepts. They are supposed to be
//! agnostic from the real network service (e.g., NetworkManager).
use crate::error::NetworkStateError;
use crate::settings::{BondSettings, IEEE8021XSettings, NetworkConnection, WirelessSettings};
use crate::settings::{
BondSettings, BridgeSettings, IEEE8021XSettings, NetworkConnection, WirelessSettings,
};
use crate::types::{BondMode, ConnectionState, DeviceState, DeviceType, Status, SSID};
use agama_utils::openapi::schemas;
use cidr::IpInet;
Expand Down Expand Up @@ -220,28 +222,32 @@ impl NetworkState {
controller: &Connection,
ports: Vec<String>,
) -> Result<(), NetworkStateError> {
if let ConnectionConfig::Bond(_) = &controller.config {
let mut controlled = vec![];
for port in ports {
let connection = self
.get_connection_by_interface(&port)
.or_else(|| self.get_connection(&port))
.ok_or(NetworkStateError::UnknownConnection(port))?;
controlled.push(connection.uuid);
}
match &controller.config {
ConnectionConfig::Bond(_) | ConnectionConfig::Bridge(_) => {
let mut controlled = vec![];
for port in ports {
let connection = self
.get_connection_by_interface(&port)
.or_else(|| self.get_connection(&port))
.ok_or(NetworkStateError::UnknownConnection(port))?;
controlled.push(connection.uuid);
}

for conn in self.connections.iter_mut() {
if controlled.contains(&conn.uuid) {
conn.controller = Some(controller.uuid);
} else if conn.controller == Some(controller.uuid) {
conn.controller = None;
for conn in self.connections.iter_mut() {
if controlled.contains(&conn.uuid) {
conn.controller = Some(controller.uuid);
if conn.interface.is_none() {
conn.interface = Some(conn.id.clone());
}
} else if conn.controller == Some(controller.uuid) {
conn.controller = None;
}
}
Ok(())
}
Ok(())
} else {
Err(NetworkStateError::NotControllerConnection(
_ => Err(NetworkStateError::NotControllerConnection(
controller.id.to_owned(),
))
)),
}
}
}
Expand Down Expand Up @@ -625,6 +631,10 @@ impl TryFrom<NetworkConnection> for Connection {
let config = BondConfig::try_from(bond_config)?;
connection.config = config.into();
}
if let Some(bridge_config) = conn.bridge {
let config = BridgeConfig::try_from(bridge_config)?;
connection.config = config.into();
}

if let Some(ieee_8021x_config) = conn.ieee_8021x {
connection.ieee_8021x_config = Some(IEEE8021XConfig::try_from(ieee_8021x_config)?);
Expand Down Expand Up @@ -692,6 +702,9 @@ impl TryFrom<Connection> for NetworkConnection {
ConnectionConfig::Bond(config) => {
connection.bond = Some(BondSettings::try_from(config)?);
}
ConnectionConfig::Bridge(config) => {
connection.bridge = Some(BridgeSettings::try_from(config)?);
}
_ => {}
}

Expand Down Expand Up @@ -720,6 +733,12 @@ pub enum PortConfig {
Bridge(BridgePortConfig),
}

impl From<BridgeConfig> for ConnectionConfig {
fn from(value: BridgeConfig) -> Self {
Self::Bridge(value)
}
}

impl From<BondConfig> for ConnectionConfig {
fn from(value: BondConfig) -> Self {
Self::Bond(value)
Expand Down Expand Up @@ -1696,7 +1715,8 @@ impl TryFrom<BondConfig> for BondSettings {

#[derive(Debug, Default, PartialEq, Clone, Serialize, utoipa::ToSchema)]
pub struct BridgeConfig {
pub stp: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub stp: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -1709,6 +1729,49 @@ pub struct BridgeConfig {
pub ageing_time: Option<u32>,
}

impl TryFrom<ConnectionConfig> for BridgeConfig {
type Error = NetworkStateError;

fn try_from(value: ConnectionConfig) -> Result<Self, Self::Error> {
match value {
ConnectionConfig::Bridge(config) => Ok(config),
_ => Err(NetworkStateError::UnexpectedConfiguration),
}
}
}

impl TryFrom<BridgeSettings> for BridgeConfig {
type Error = NetworkStateError;

fn try_from(settings: BridgeSettings) -> Result<Self, Self::Error> {
let stp = settings.stp;
let priority = settings.priority;
let forward_delay = settings.forward_delay;
let hello_time = settings.forward_delay;

Ok(BridgeConfig {
stp,
priority,
forward_delay,
hello_time,
..Default::default()
})
}
}

impl TryFrom<BridgeConfig> for BridgeSettings {
type Error = NetworkStateError;

fn try_from(bridge: BridgeConfig) -> Result<Self, Self::Error> {
Ok(BridgeSettings {
stp: bridge.stp,
priority: bridge.priority,
forward_delay: bridge.forward_delay,
hello_time: bridge.hello_time,
..Default::default()
})
}
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, utoipa::ToSchema)]
pub struct BridgePortConfig {
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
Loading