Compare commits
10 Commits
fix/pxe_in
...
doc/clone
| Author | SHA1 | Date | |
|---|---|---|---|
| 51a5afbb6d | |||
| 5e1580e5c1 | |||
| 008b03f979 | |||
| c069207f12 | |||
|
|
7368184917 | ||
| 05205f4ac1 | |||
| 3174645c97 | |||
| cb66b7592e | |||
| a815f6ac9c | |||
| e5eb7fde9f |
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -1780,6 +1780,7 @@ dependencies = [
|
|||||||
name = "example-nanodc"
|
name = "example-nanodc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"brocade",
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
@@ -1788,6 +1789,7 @@ dependencies = [
|
|||||||
"harmony_tui",
|
"harmony_tui",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -1806,6 +1808,7 @@ dependencies = [
|
|||||||
name = "example-okd-install"
|
name = "example-okd-install"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"brocade",
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
@@ -1836,13 +1839,16 @@ dependencies = [
|
|||||||
name = "example-opnsense"
|
name = "example-opnsense"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"brocade",
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
|
"harmony_secret",
|
||||||
"harmony_tui",
|
"harmony_tui",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -1851,6 +1857,7 @@ dependencies = [
|
|||||||
name = "example-pxe"
|
name = "example-pxe"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"brocade",
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
@@ -1865,6 +1872,15 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "example-remove-rook-osd"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-rust"
|
name = "example-rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1918,8 +1934,6 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"harmony_secret_derive",
|
|
||||||
"harmony_tui",
|
"harmony_tui",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
@@ -4613,15 +4627,6 @@ version = "0.8.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "remove_rook_osd"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"harmony",
|
|
||||||
"harmony_cli",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.27"
|
version = "0.11.27"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use log::{debug, info};
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{collections::HashSet, str::FromStr};
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct FastIronClient {
|
pub struct FastIronClient {
|
||||||
shell: BrocadeShell,
|
shell: BrocadeShell,
|
||||||
version: BrocadeInfo,
|
version: BrocadeInfo,
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ pub async fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait BrocadeClient {
|
pub trait BrocadeClient: std::fmt::Debug {
|
||||||
/// Retrieves the operating system and version details from the connected Brocade switch.
|
/// Retrieves the operating system and version details from the connected Brocade switch.
|
||||||
///
|
///
|
||||||
/// This is typically the first call made after establishing a connection to determine
|
/// This is typically the first call made after establishing a connection to determine
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use crate::{
|
|||||||
parse_brocade_mac_address, shell::BrocadeShell,
|
parse_brocade_mac_address, shell::BrocadeShell,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct NetworkOperatingSystemClient {
|
pub struct NetworkOperatingSystemClient {
|
||||||
shell: BrocadeShell,
|
shell: BrocadeShell,
|
||||||
version: BrocadeInfo,
|
version: BrocadeInfo,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use log::info;
|
|||||||
use russh::ChannelMsg;
|
use russh::ChannelMsg;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BrocadeShell {
|
pub struct BrocadeShell {
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|||||||
133
docs/doc-clone-and-restore-coreos.md
Normal file
133
docs/doc-clone-and-restore-coreos.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
## Working procedure to clone and restore CoreOS disk from OKD Cluster
|
||||||
|
|
||||||
|
### **Step 1 - take a backup**
|
||||||
|
```
|
||||||
|
sudo dd if=/dev/old of=/dev/backup status=progress
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 2 - clone beginning of old disk to new**
|
||||||
|
```
|
||||||
|
sudo dd if=/dev/old of=/dev/backup status=progress count=1000 bs=1M
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 3 - verify and modify disk partitions**
|
||||||
|
list disk partitions
|
||||||
|
```
|
||||||
|
sgdisk -p /dev/new
|
||||||
|
```
|
||||||
|
if new disk is smaller than old disk and there is space on the xfs partition of the old disk, modify partitions of new disk
|
||||||
|
```
|
||||||
|
gdisk /dev/new
|
||||||
|
```
|
||||||
|
inside of gdisk commands
|
||||||
|
```
|
||||||
|
-v -> verify table
|
||||||
|
-p -> print table
|
||||||
|
-d -> select partition to delete partition
|
||||||
|
-n -> recreate partition with same partition number as deleted partition
|
||||||
|
```
|
||||||
|
For end sector, either specify the new end or just press Enter for maximum available
|
||||||
|
When asked about partition type, enter the same type code (it will show the old one)
|
||||||
|
```
|
||||||
|
p - >to verify
|
||||||
|
w -> to write
|
||||||
|
```
|
||||||
|
make xfs file system for new partition <new4>
|
||||||
|
```
|
||||||
|
sudo mkfs.xfs -f /dev/new4
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 4 - copy old PARTUUID **
|
||||||
|
|
||||||
|
**careful here**
|
||||||
|
get old patuuid:
|
||||||
|
```
|
||||||
|
sgdisk -i <partition_number> /dev/old_disk # Note the "Partition unique GUID"
|
||||||
|
```
|
||||||
|
get labels
|
||||||
|
```
|
||||||
|
sgdisk -p /dev/old_disk # Shows partition names in the table
|
||||||
|
|
||||||
|
blkid /dev/old_disk* # Shows PARTUUIDs and labels for all partitions
|
||||||
|
```
|
||||||
|
set it on new disk
|
||||||
|
```
|
||||||
|
sgdisk -u <partition_number>:<old_partuuid> /dev/sdc
|
||||||
|
```
|
||||||
|
partition name:
|
||||||
|
```
|
||||||
|
sgdisk -c <partition_number>:"<old_name>" /dev/sdc
|
||||||
|
```
|
||||||
|
verify all:
|
||||||
|
```
|
||||||
|
lsblk -o NAME,SIZE,PARTUUID,PARTLABEL /dev/old_disk
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 5 - Mount disks and copy files from old to new disk**
|
||||||
|
|
||||||
|
mount files before copy:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir -p /mnt/new
|
||||||
|
mkdir -p /mnt/old
|
||||||
|
mount /dev/old4 /mnt/old
|
||||||
|
mount /dev/new4 /mnt/new
|
||||||
|
```
|
||||||
|
copy:
|
||||||
|
|
||||||
|
with -n flag can run as dry-run
|
||||||
|
```
|
||||||
|
rsync -aAXHvn --numeric-ids /source/ /destination/
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rsync -aAXHv --numeric-ids /source/ /destination/
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 6 - Set correct UUID for new partition 4**
|
||||||
|
to set uuid with xfs_admin you must unmount first
|
||||||
|
|
||||||
|
unmount old devices
|
||||||
|
```
|
||||||
|
umount /mnt/new
|
||||||
|
umount /mnt/old
|
||||||
|
```
|
||||||
|
|
||||||
|
to set correct uuid for partition 4
|
||||||
|
```
|
||||||
|
blkid /dev/old4
|
||||||
|
```
|
||||||
|
```
|
||||||
|
xfs_admin -U <old_uuid> /dev/new_partition
|
||||||
|
```
|
||||||
|
to set labels
|
||||||
|
get it
|
||||||
|
```
|
||||||
|
sgdisk -i 4 /dev/sda | grep "Partition name"
|
||||||
|
```
|
||||||
|
set it
|
||||||
|
```
|
||||||
|
sgdisk -c 4:"<label_name>" /dev/sdc
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
(check existing with xfs_admin -l /dev/old_partition)
|
||||||
|
Use xfs_admin -L <label> /dev/new_partition
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 7 - Verify**
|
||||||
|
|
||||||
|
verify everything:
|
||||||
|
```
|
||||||
|
sgdisk -p /dev/sda # Old disk
|
||||||
|
sgdisk -p /dev/sdc # New disk
|
||||||
|
```
|
||||||
|
```
|
||||||
|
lsblk -o NAME,SIZE,PARTUUID,PARTLABEL /dev/sda
|
||||||
|
lsblk -o NAME,SIZE,PARTUUID,PARTLABEL /dev/sdc
|
||||||
|
```
|
||||||
|
```
|
||||||
|
blkid /dev/sda* | grep UUID=
|
||||||
|
blkid /dev/sdc* | grep UUID=
|
||||||
|
```
|
||||||
|
|
||||||
@@ -122,7 +122,6 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Creating OPNsense VM using serial image..."
|
echo "Creating OPNsense VM using serial image..."
|
||||||
|
|
||||||
virt-install \
|
virt-install \
|
||||||
--connect "${CONNECT_URI}" \
|
--connect "${CONNECT_URI}" \
|
||||||
--name "${VM_OPN}" \
|
--name "${VM_OPN}" \
|
||||||
@@ -132,12 +131,11 @@ EOF
|
|||||||
--os-variant "${OS_VARIANT_OPN}" \
|
--os-variant "${OS_VARIANT_OPN}" \
|
||||||
--graphics none \
|
--graphics none \
|
||||||
--noautoconsole \
|
--noautoconsole \
|
||||||
--import \
|
--disk path="${disk_opn}",device=disk,bus=virtio,boot.order=1 \
|
||||||
--disk path="${OPN_IMG_PATH}",device=disk,bus=sata,boot.order=1 \
|
--disk path="${OPN_IMG_PATH}",device=disk,bus=usb,readonly=on,boot.order=2 \
|
||||||
--disk path="${disk_opn}",device=disk,bus=virtio,boot.order=2 \
|
|
||||||
--network network=default,model=virtio \
|
--network network=default,model=virtio \
|
||||||
--network network="${NET_HARMONYLAN}",model=virtio \
|
--network network="${NET_HARMONYLAN}",model=virtio \
|
||||||
--boot hd,menu=on
|
--boot uefi,menu=on
|
||||||
|
|
||||||
echo "OPNsense VM created. Connect with: sudo virsh console ${VM_OPN}"
|
echo "OPNsense VM created. Connect with: sudo virsh console ${VM_OPN}"
|
||||||
echo "The VM will boot from the serial installation image."
|
echo "The VM will boot from the serial installation image."
|
||||||
|
|||||||
@@ -17,3 +17,5 @@ harmony_secret = { path = "../../harmony_secret" }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
brocade = { path = "../../brocade" }
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use brocade::BrocadeOptions;
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
config::secret::SshKeyPair,
|
config::secret::SshKeyPair,
|
||||||
data::{FileContent, FilePath},
|
data::{FileContent, FilePath},
|
||||||
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
infra::opnsense::OPNSenseManagementInterface,
|
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
http::StaticFilesHttpScore,
|
http::StaticFilesHttpScore,
|
||||||
@@ -22,8 +23,9 @@ use harmony::{
|
|||||||
topology::{LogicalHost, UnmanagedRouter},
|
topology::{LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, mac_address};
|
use harmony_macros::{ip, mac_address};
|
||||||
use harmony_secret::SecretManager;
|
use harmony_secret::{Secret, SecretManager};
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -32,6 +34,26 @@ async fn main() {
|
|||||||
name: String::from("fw0"),
|
name: String::from("fw0"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
|
let switches: Vec<IpAddr> = vec![ip!("192.168.33.101")];
|
||||||
|
let brocade_options = Some(BrocadeOptions {
|
||||||
|
dry_run: *harmony::config::DRY_RUN,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let switch_client = BrocadeSwitchClient::init(
|
||||||
|
&switches,
|
||||||
|
&switch_auth.username,
|
||||||
|
&switch_auth.password,
|
||||||
|
brocade_options,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to switch");
|
||||||
|
|
||||||
|
let switch_client = Arc::new(switch_client);
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
let opnsense = Arc::new(
|
||||||
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||||
);
|
);
|
||||||
@@ -83,7 +105,7 @@ async fn main() {
|
|||||||
name: "wk2".to_string(),
|
name: "wk2".to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
switch: vec![],
|
switch_client: switch_client.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let inventory = Inventory {
|
let inventory = Inventory {
|
||||||
@@ -166,3 +188,9 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Secret, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BrocadeSwitchAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ log = { workspace = true }
|
|||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
brocade = { path = "../../brocade" }
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
use brocade::BrocadeOptions;
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
hardware::{Location, SwitchGroup},
|
hardware::{Location, SwitchGroup},
|
||||||
infra::opnsense::OPNSenseManagementInterface,
|
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
@@ -22,6 +23,26 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
|
let switches: Vec<IpAddr> = vec![ip!("192.168.1.101")]; // TODO: Adjust me
|
||||||
|
let brocade_options = Some(BrocadeOptions {
|
||||||
|
dry_run: *harmony::config::DRY_RUN,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let switch_client = BrocadeSwitchClient::init(
|
||||||
|
&switches,
|
||||||
|
&switch_auth.username,
|
||||||
|
&switch_auth.password,
|
||||||
|
brocade_options,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to switch");
|
||||||
|
|
||||||
|
let switch_client = Arc::new(switch_client);
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<OPNSenseFirewallConfig>().await;
|
let config = SecretManager::get_or_prompt::<OPNSenseFirewallConfig>().await;
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
|
|
||||||
@@ -58,7 +79,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: "bootstrap".to_string(),
|
name: "bootstrap".to_string(),
|
||||||
},
|
},
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch: vec![],
|
switch_client: switch_client.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,3 +96,9 @@ pub fn get_inventory() -> Inventory {
|
|||||||
control_plane_host: vec![],
|
control_plane_host: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Secret, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BrocadeSwitchAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ log = { workspace = true }
|
|||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
brocade = { path = "../../brocade" }
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
use brocade::BrocadeOptions;
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
config::secret::OPNSenseFirewallCredentials,
|
config::secret::OPNSenseFirewallCredentials,
|
||||||
hardware::{Location, SwitchGroup},
|
hardware::{Location, SwitchGroup},
|
||||||
infra::opnsense::OPNSenseManagementInterface,
|
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::SecretManager;
|
use harmony_secret::{Secret, SecretManager};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{net::IpAddr, sync::Arc};
|
use std::{net::IpAddr, sync::Arc};
|
||||||
|
|
||||||
pub async fn get_topology() -> HAClusterTopology {
|
pub async fn get_topology() -> HAClusterTopology {
|
||||||
@@ -16,6 +18,26 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
|
let switches: Vec<IpAddr> = vec![ip!("192.168.1.101")]; // TODO: Adjust me
|
||||||
|
let brocade_options = Some(BrocadeOptions {
|
||||||
|
dry_run: *harmony::config::DRY_RUN,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let switch_client = BrocadeSwitchClient::init(
|
||||||
|
&switches,
|
||||||
|
&switch_auth.username,
|
||||||
|
&switch_auth.password,
|
||||||
|
brocade_options,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to switch");
|
||||||
|
|
||||||
|
let switch_client = Arc::new(switch_client);
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>().await;
|
let config = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>().await;
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
|
|
||||||
@@ -52,7 +74,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: "cp0".to_string(),
|
name: "cp0".to_string(),
|
||||||
},
|
},
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch: vec![],
|
switch_client: switch_client.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,3 +91,9 @@ pub fn get_inventory() -> Inventory {
|
|||||||
control_plane_host: vec![],
|
control_plane_host: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Secret, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BrocadeSwitchAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,3 +16,6 @@ harmony_macros = { path = "../../harmony_macros" }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
harmony_secret = { path = "../../harmony_secret" }
|
||||||
|
brocade = { path = "../../brocade" }
|
||||||
|
serde = { workspace = true }
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use brocade::BrocadeOptions;
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
infra::opnsense::OPNSenseManagementInterface,
|
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
dummy::{ErrorScore, PanicScore, SuccessScore},
|
dummy::{ErrorScore, PanicScore, SuccessScore},
|
||||||
@@ -18,7 +19,9 @@ use harmony::{
|
|||||||
topology::{LogicalHost, UnmanagedRouter},
|
topology::{LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, mac_address};
|
use harmony_macros::{ip, mac_address};
|
||||||
|
use harmony_secret::{Secret, SecretManager};
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -27,6 +30,26 @@ async fn main() {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
|
let switches: Vec<IpAddr> = vec![ip!("192.168.5.101")]; // TODO: Adjust me
|
||||||
|
let brocade_options = Some(BrocadeOptions {
|
||||||
|
dry_run: *harmony::config::DRY_RUN,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let switch_client = BrocadeSwitchClient::init(
|
||||||
|
&switches,
|
||||||
|
&switch_auth.username,
|
||||||
|
&switch_auth.password,
|
||||||
|
brocade_options,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to switch");
|
||||||
|
|
||||||
|
let switch_client = Arc::new(switch_client);
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
let opnsense = Arc::new(
|
||||||
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||||
);
|
);
|
||||||
@@ -54,7 +77,7 @@ async fn main() {
|
|||||||
name: "cp0".to_string(),
|
name: "cp0".to_string(),
|
||||||
},
|
},
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch: vec![],
|
switch_client: switch_client.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let inventory = Inventory {
|
let inventory = Inventory {
|
||||||
@@ -109,3 +132,9 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Secret, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BrocadeSwitchAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use brocade::BrocadeOptions;
|
|
||||||
use harmony_macros::ip;
|
use harmony_macros::ip;
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use harmony_types::{
|
use harmony_types::{
|
||||||
net::{MacAddress, Url},
|
net::{MacAddress, Url},
|
||||||
switch::PortLocation,
|
switch::PortLocation,
|
||||||
@@ -14,8 +12,6 @@ use log::info;
|
|||||||
use crate::data::FileContent;
|
use crate::data::FileContent;
|
||||||
use crate::executors::ExecutorError;
|
use crate::executors::ExecutorError;
|
||||||
use crate::hardware::PhysicalHost;
|
use crate::hardware::PhysicalHost;
|
||||||
use crate::infra::brocade::BrocadeSwitchAuth;
|
|
||||||
use crate::infra::brocade::BrocadeSwitchClient;
|
|
||||||
use crate::modules::okd::crd::{
|
use crate::modules::okd::crd::{
|
||||||
InstallPlanApproval, OperatorGroup, OperatorGroupSpec, Subscription, SubscriptionSpec,
|
InstallPlanApproval, OperatorGroup, OperatorGroupSpec, Subscription, SubscriptionSpec,
|
||||||
nmstate::{self, NMState, NodeNetworkConfigurationPolicy, NodeNetworkConfigurationPolicySpec},
|
nmstate::{self, NMState, NodeNetworkConfigurationPolicy, NodeNetworkConfigurationPolicySpec},
|
||||||
@@ -30,7 +26,6 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -43,10 +38,10 @@ pub struct HAClusterTopology {
|
|||||||
pub tftp_server: Arc<dyn TftpServer>,
|
pub tftp_server: Arc<dyn TftpServer>,
|
||||||
pub http_server: Arc<dyn HttpServer>,
|
pub http_server: Arc<dyn HttpServer>,
|
||||||
pub dns_server: Arc<dyn DnsServer>,
|
pub dns_server: Arc<dyn DnsServer>,
|
||||||
|
pub switch_client: Arc<dyn SwitchClient>,
|
||||||
pub bootstrap_host: LogicalHost,
|
pub bootstrap_host: LogicalHost,
|
||||||
pub control_plane: Vec<LogicalHost>,
|
pub control_plane: Vec<LogicalHost>,
|
||||||
pub workers: Vec<LogicalHost>,
|
pub workers: Vec<LogicalHost>,
|
||||||
pub switch: Vec<LogicalHost>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -280,36 +275,15 @@ impl HAClusterTopology {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_switch_client(&self) -> Result<Box<dyn SwitchClient>, SwitchError> {
|
|
||||||
let auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
|
||||||
.await
|
|
||||||
.map_err(|e| SwitchError::new(format!("Failed to get credentials: {e}")))?;
|
|
||||||
|
|
||||||
// FIXME: We assume Brocade switches
|
|
||||||
let switches: Vec<IpAddr> = self.switch.iter().map(|s| s.ip).collect();
|
|
||||||
let brocade_options = Some(BrocadeOptions {
|
|
||||||
dry_run: *crate::config::DRY_RUN,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let client =
|
|
||||||
BrocadeSwitchClient::init(&switches, &auth.username, &auth.password, brocade_options)
|
|
||||||
.await
|
|
||||||
.map_err(|e| SwitchError::new(format!("Failed to connect to switch: {e}")))?;
|
|
||||||
|
|
||||||
Ok(Box::new(client))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn configure_port_channel(
|
async fn configure_port_channel(
|
||||||
&self,
|
&self,
|
||||||
host: &PhysicalHost,
|
host: &PhysicalHost,
|
||||||
config: &HostNetworkConfig,
|
config: &HostNetworkConfig,
|
||||||
) -> Result<(), SwitchError> {
|
) -> Result<(), SwitchError> {
|
||||||
debug!("Configuring port channel: {config:#?}");
|
debug!("Configuring port channel: {config:#?}");
|
||||||
let client = self.get_switch_client().await?;
|
|
||||||
|
|
||||||
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
||||||
|
|
||||||
client
|
self.switch_client
|
||||||
.configure_port_channel(&format!("Harmony_{}", host.id), switch_ports)
|
.configure_port_channel(&format!("Harmony_{}", host.id), switch_ports)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?;
|
.map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?;
|
||||||
@@ -333,10 +307,10 @@ impl HAClusterTopology {
|
|||||||
tftp_server: dummy_infra.clone(),
|
tftp_server: dummy_infra.clone(),
|
||||||
http_server: dummy_infra.clone(),
|
http_server: dummy_infra.clone(),
|
||||||
dns_server: dummy_infra.clone(),
|
dns_server: dummy_infra.clone(),
|
||||||
|
switch_client: dummy_infra.clone(),
|
||||||
bootstrap_host: dummy_host,
|
bootstrap_host: dummy_host,
|
||||||
control_plane: vec![],
|
control_plane: vec![],
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,8 +468,7 @@ impl HttpServer for HAClusterTopology {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Switch for HAClusterTopology {
|
impl Switch for HAClusterTopology {
|
||||||
async fn setup_switch(&self) -> Result<(), SwitchError> {
|
async fn setup_switch(&self) -> Result<(), SwitchError> {
|
||||||
let client = self.get_switch_client().await?;
|
self.switch_client.setup().await?;
|
||||||
client.setup().await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,8 +476,7 @@ impl Switch for HAClusterTopology {
|
|||||||
&self,
|
&self,
|
||||||
mac_address: &MacAddress,
|
mac_address: &MacAddress,
|
||||||
) -> Result<Option<PortLocation>, SwitchError> {
|
) -> Result<Option<PortLocation>, SwitchError> {
|
||||||
let client = self.get_switch_client().await?;
|
let port = self.switch_client.find_port(mac_address).await?;
|
||||||
let port = client.find_port(mac_address).await?;
|
|
||||||
Ok(port)
|
Ok(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,3 +676,25 @@ impl DnsServer for DummyInfra {
|
|||||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl SwitchClient for DummyInfra {
|
||||||
|
async fn setup(&self) -> Result<(), SwitchError> {
|
||||||
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_port(
|
||||||
|
&self,
|
||||||
|
_mac_address: &MacAddress,
|
||||||
|
) -> Result<Option<PortLocation>, SwitchError> {
|
||||||
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn configure_port_channel(
|
||||||
|
&self,
|
||||||
|
_channel_name: &str,
|
||||||
|
_switch_ports: Vec<PortLocation>,
|
||||||
|
) -> Result<u8, SwitchError> {
|
||||||
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
use std::{error::Error, net::Ipv4Addr, str::FromStr, sync::Arc};
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{self, Debug},
|
||||||
|
net::Ipv4Addr,
|
||||||
|
str::FromStr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
@@ -19,8 +25,8 @@ pub struct DHCPStaticEntry {
|
|||||||
pub ip: Ipv4Addr,
|
pub ip: Ipv4Addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for DHCPStaticEntry {
|
impl fmt::Display for DHCPStaticEntry {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mac = self
|
let mac = self
|
||||||
.mac
|
.mac
|
||||||
.iter()
|
.iter()
|
||||||
@@ -42,8 +48,8 @@ pub trait Firewall: Send + Sync {
|
|||||||
fn get_host(&self) -> LogicalHost;
|
fn get_host(&self) -> LogicalHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for dyn Firewall {
|
impl Debug for dyn Firewall {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_fmt(format_args!("Firewall {}", self.get_ip()))
|
f.write_fmt(format_args!("Firewall {}", self.get_ip()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +71,7 @@ pub struct PxeOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait DhcpServer: Send + Sync + std::fmt::Debug {
|
pub trait DhcpServer: Send + Sync + Debug {
|
||||||
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
|
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
|
||||||
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
|
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
|
||||||
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
|
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
|
||||||
@@ -104,8 +110,8 @@ pub trait DnsServer: Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for dyn DnsServer {
|
impl Debug for dyn DnsServer {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_fmt(format_args!("DnsServer {}", self.get_ip()))
|
f.write_fmt(format_args!("DnsServer {}", self.get_ip()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,8 +147,8 @@ pub enum DnsRecordType {
|
|||||||
TXT,
|
TXT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for DnsRecordType {
|
impl fmt::Display for DnsRecordType {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DnsRecordType::A => write!(f, "A"),
|
DnsRecordType::A => write!(f, "A"),
|
||||||
DnsRecordType::AAAA => write!(f, "AAAA"),
|
DnsRecordType::AAAA => write!(f, "AAAA"),
|
||||||
@@ -216,8 +222,8 @@ pub struct SwitchError {
|
|||||||
msg: String,
|
msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SwitchError {
|
impl fmt::Display for SwitchError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(&self.msg)
|
f.write_str(&self.msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +231,7 @@ impl std::fmt::Display for SwitchError {
|
|||||||
impl Error for SwitchError {}
|
impl Error for SwitchError {}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SwitchClient: Send + Sync {
|
pub trait SwitchClient: Debug + Send + Sync {
|
||||||
/// Executes essential, idempotent, one-time initial configuration steps.
|
/// Executes essential, idempotent, one-time initial configuration steps.
|
||||||
///
|
///
|
||||||
/// This is an opiniated procedure that setups a switch to provide high availability
|
/// This is an opiniated procedure that setups a switch to provide high availability
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub struct AlertingInterpret<S: AlertSender> {
|
|||||||
pub sender: S,
|
pub sender: S,
|
||||||
pub receivers: Vec<Box<dyn AlertReceiver<S>>>,
|
pub receivers: Vec<Box<dyn AlertReceiver<S>>>,
|
||||||
pub rules: Vec<Box<dyn AlertRule<S>>>,
|
pub rules: Vec<Box<dyn AlertRule<S>>>,
|
||||||
|
pub scrape_targets: Option<Vec<Box<dyn ScrapeTarget<S>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -38,6 +39,12 @@ impl<S: AlertSender + Installable<T>, T: Topology> Interpret<T> for AlertingInte
|
|||||||
debug!("installing rule: {:#?}", rule);
|
debug!("installing rule: {:#?}", rule);
|
||||||
rule.install(&self.sender).await?;
|
rule.install(&self.sender).await?;
|
||||||
}
|
}
|
||||||
|
if let Some(targets) = &self.scrape_targets {
|
||||||
|
for target in targets.iter() {
|
||||||
|
debug!("installing scrape_target: {:#?}", target);
|
||||||
|
target.install(&self.sender).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.sender.ensure_installed(inventory, topology).await?;
|
self.sender.ensure_installed(inventory, topology).await?;
|
||||||
Ok(Outcome::success(format!(
|
Ok(Outcome::success(format!(
|
||||||
"successfully installed alert sender {}",
|
"successfully installed alert sender {}",
|
||||||
@@ -77,6 +84,6 @@ pub trait AlertRule<S: AlertSender>: std::fmt::Debug + Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ScrapeTarget<S: AlertSender> {
|
pub trait ScrapeTarget<S: AlertSender>: std::fmt::Debug + Send + Sync {
|
||||||
async fn install(&self, sender: &S) -> Result<(), InterpretError>;
|
async fn install(&self, sender: &S) -> Result<Outcome, InterpretError>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode};
|
use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode};
|
||||||
use harmony_secret::Secret;
|
|
||||||
use harmony_types::{
|
use harmony_types::{
|
||||||
net::{IpAddress, MacAddress},
|
net::{IpAddress, MacAddress},
|
||||||
switch::{PortDeclaration, PortLocation},
|
switch::{PortDeclaration, PortLocation},
|
||||||
};
|
};
|
||||||
use option_ext::OptionExt;
|
use option_ext::OptionExt;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::topology::{SwitchClient, SwitchError};
|
use crate::topology::{SwitchClient, SwitchError};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BrocadeSwitchClient {
|
pub struct BrocadeSwitchClient {
|
||||||
brocade: Box<dyn BrocadeClient + Send + Sync>,
|
brocade: Box<dyn BrocadeClient + Send + Sync>,
|
||||||
}
|
}
|
||||||
@@ -114,12 +113,6 @@ impl SwitchClient for BrocadeSwitchClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -235,7 +228,7 @@ mod tests {
|
|||||||
assert_that!(*configured_interfaces).is_empty();
|
assert_that!(*configured_interfaces).is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct FakeBrocadeClient {
|
struct FakeBrocadeClient {
|
||||||
stack_topology: Vec<InterSwitchLink>,
|
stack_topology: Vec<InterSwitchLink>,
|
||||||
interfaces: Vec<InterfaceInfo>,
|
interfaces: Vec<InterfaceInfo>,
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use kube::CustomResource;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
modules::monitoring::kube_prometheus::crd::{
|
||||||
|
crd_alertmanager_config::CRDPrometheus, crd_prometheuses::LabelSelector,
|
||||||
|
},
|
||||||
|
topology::oberservability::monitoring::ScrapeTarget,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||||
|
#[kube(
|
||||||
|
group = "monitoring.coreos.com",
|
||||||
|
version = "v1alpha1",
|
||||||
|
kind = "ScrapeConfig",
|
||||||
|
plural = "scrapeconfigs",
|
||||||
|
namespaced
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ScrapeConfigSpec {
|
||||||
|
/// List of static configurations.
|
||||||
|
pub static_configs: Option<Vec<StaticConfig>>,
|
||||||
|
|
||||||
|
/// Kubernetes service discovery.
|
||||||
|
pub kubernetes_sd_configs: Option<Vec<KubernetesSDConfig>>,
|
||||||
|
|
||||||
|
/// HTTP-based service discovery.
|
||||||
|
pub http_sd_configs: Option<Vec<HttpSDConfig>>,
|
||||||
|
|
||||||
|
/// File-based service discovery.
|
||||||
|
pub file_sd_configs: Option<Vec<FileSDConfig>>,
|
||||||
|
|
||||||
|
/// DNS-based service discovery.
|
||||||
|
pub dns_sd_configs: Option<Vec<DnsSDConfig>>,
|
||||||
|
|
||||||
|
/// Consul service discovery.
|
||||||
|
pub consul_sd_configs: Option<Vec<ConsulSDConfig>>,
|
||||||
|
|
||||||
|
/// Relabeling configuration applied to discovered targets.
|
||||||
|
pub relabel_configs: Option<Vec<RelabelConfig>>,
|
||||||
|
|
||||||
|
/// Metric relabeling configuration applied to scraped samples.
|
||||||
|
pub metric_relabel_configs: Option<Vec<RelabelConfig>>,
|
||||||
|
|
||||||
|
/// Path to scrape metrics from (defaults to `/metrics`).
|
||||||
|
pub metrics_path: Option<String>,
|
||||||
|
|
||||||
|
/// Interval at which Prometheus scrapes targets (e.g., "30s").
|
||||||
|
pub scrape_interval: Option<String>,
|
||||||
|
|
||||||
|
/// Timeout for scraping (e.g., "10s").
|
||||||
|
pub scrape_timeout: Option<String>,
|
||||||
|
|
||||||
|
/// Optional job name override.
|
||||||
|
pub job_name: Option<String>,
|
||||||
|
|
||||||
|
/// Optional scheme (http or https).
|
||||||
|
pub scheme: Option<String>,
|
||||||
|
|
||||||
|
/// Authorization paramaters for snmp walk
|
||||||
|
pub params: Option<Params>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static configuration section of a ScrapeConfig.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct StaticConfig {
|
||||||
|
pub targets: Vec<String>,
|
||||||
|
|
||||||
|
pub labels: Option<LabelSelector>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relabeling configuration for target or metric relabeling.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RelabelConfig {
|
||||||
|
pub source_labels: Option<Vec<String>>,
|
||||||
|
pub separator: Option<String>,
|
||||||
|
pub target_label: Option<String>,
|
||||||
|
pub regex: Option<String>,
|
||||||
|
pub modulus: Option<u64>,
|
||||||
|
pub replacement: Option<String>,
|
||||||
|
pub action: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kubernetes service discovery configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct KubernetesSDConfig {
|
||||||
|
///"pod", "service", "endpoints"pub role: String,
|
||||||
|
pub namespaces: Option<NamespaceSelector>,
|
||||||
|
pub selectors: Option<Vec<LabelSelector>>,
|
||||||
|
pub api_server: Option<String>,
|
||||||
|
pub bearer_token_file: Option<String>,
|
||||||
|
pub tls_config: Option<TLSConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Namespace selector for Kubernetes service discovery.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NamespaceSelector {
|
||||||
|
pub any: Option<bool>,
|
||||||
|
pub match_names: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HTTP-based service discovery configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct HttpSDConfig {
|
||||||
|
pub url: String,
|
||||||
|
pub refresh_interval: Option<String>,
|
||||||
|
pub basic_auth: Option<BasicAuth>,
|
||||||
|
pub authorization: Option<Authorization>,
|
||||||
|
pub tls_config: Option<TLSConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// File-based service discovery configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct FileSDConfig {
|
||||||
|
pub files: Vec<String>,
|
||||||
|
pub refresh_interval: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DNS-based service discovery configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DnsSDConfig {
|
||||||
|
pub names: Vec<String>,
|
||||||
|
pub refresh_interval: Option<String>,
|
||||||
|
pub type_: Option<String>, // SRV, A, AAAA
|
||||||
|
pub port: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consul service discovery configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ConsulSDConfig {
|
||||||
|
pub server: String,
|
||||||
|
pub services: Option<Vec<String>>,
|
||||||
|
pub scheme: Option<String>,
|
||||||
|
pub datacenter: Option<String>,
|
||||||
|
pub tag_separator: Option<String>,
|
||||||
|
pub refresh_interval: Option<String>,
|
||||||
|
pub tls_config: Option<TLSConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic authentication credentials.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct BasicAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: Option<String>,
|
||||||
|
pub password_file: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bearer token or other auth mechanisms.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Authorization {
|
||||||
|
pub credentials: Option<String>,
|
||||||
|
pub credentials_file: Option<String>,
|
||||||
|
pub type_: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TLS configuration for secure scraping.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TLSConfig {
|
||||||
|
pub ca_file: Option<String>,
|
||||||
|
pub cert_file: Option<String>,
|
||||||
|
pub key_file: Option<String>,
|
||||||
|
pub server_name: Option<String>,
|
||||||
|
pub insecure_skip_verify: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authorization parameters for SNMP walk.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Params {
|
||||||
|
pub auth: Option<Vec<String>>,
|
||||||
|
pub module: Option<Vec<String>>,
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ pub mod crd_default_rules;
|
|||||||
pub mod crd_grafana;
|
pub mod crd_grafana;
|
||||||
pub mod crd_prometheus_rules;
|
pub mod crd_prometheus_rules;
|
||||||
pub mod crd_prometheuses;
|
pub mod crd_prometheuses;
|
||||||
|
pub mod crd_scrape_config;
|
||||||
pub mod grafana_default_dashboard;
|
pub mod grafana_default_dashboard;
|
||||||
pub mod grafana_operator;
|
pub mod grafana_operator;
|
||||||
pub mod prometheus_operator;
|
pub mod prometheus_operator;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ impl<T: Topology + HelmCommand + TenantManager> Score<T> for HelmPrometheusAlert
|
|||||||
sender: KubePrometheus { config },
|
sender: KubePrometheus { config },
|
||||||
receivers: self.receivers.clone(),
|
receivers: self.receivers.clone(),
|
||||||
rules: self.rules.clone(),
|
rules: self.rules.clone(),
|
||||||
|
scrape_targets: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ pub mod kube_prometheus;
|
|||||||
pub mod ntfy;
|
pub mod ntfy;
|
||||||
pub mod okd;
|
pub mod okd;
|
||||||
pub mod prometheus;
|
pub mod prometheus;
|
||||||
|
pub mod scrape_target;
|
||||||
|
|||||||
1
harmony/src/modules/monitoring/scrape_target/mod.rs
Normal file
1
harmony/src/modules/monitoring/scrape_target/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod server;
|
||||||
76
harmony/src/modules/monitoring/scrape_target/server.rs
Normal file
76
harmony/src/modules/monitoring/scrape_target/server.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use kube::api::ObjectMeta;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
interpret::{InterpretError, Outcome},
|
||||||
|
modules::monitoring::kube_prometheus::crd::{
|
||||||
|
crd_alertmanager_config::CRDPrometheus,
|
||||||
|
crd_scrape_config::{Params, RelabelConfig, ScrapeConfig, ScrapeConfigSpec, StaticConfig},
|
||||||
|
},
|
||||||
|
topology::oberservability::monitoring::ScrapeTarget,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct Server {
|
||||||
|
pub name: String,
|
||||||
|
pub ip: IpAddr,
|
||||||
|
pub auth: String,
|
||||||
|
pub module: String,
|
||||||
|
pub domain: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ScrapeTarget<CRDPrometheus> for Server {
|
||||||
|
async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
|
||||||
|
let scrape_config_spec = ScrapeConfigSpec {
|
||||||
|
static_configs: Some(vec![StaticConfig {
|
||||||
|
targets: vec![self.ip.to_string()],
|
||||||
|
labels: None,
|
||||||
|
}]),
|
||||||
|
scrape_interval: Some("2m".to_string()),
|
||||||
|
kubernetes_sd_configs: None,
|
||||||
|
http_sd_configs: None,
|
||||||
|
file_sd_configs: None,
|
||||||
|
dns_sd_configs: None,
|
||||||
|
params: Some(Params {
|
||||||
|
auth: Some(vec![self.auth.clone()]),
|
||||||
|
module: Some(vec![self.module.clone()]),
|
||||||
|
}),
|
||||||
|
consul_sd_configs: None,
|
||||||
|
relabel_configs: Some(vec![RelabelConfig {
|
||||||
|
action: None,
|
||||||
|
source_labels: Some(vec!["__address__".to_string()]),
|
||||||
|
separator: None,
|
||||||
|
target_label: Some("__param_target".to_string()),
|
||||||
|
regex: None,
|
||||||
|
replacement: Some(format!("snmp.{}:31080", self.domain.clone())),
|
||||||
|
modulus: None,
|
||||||
|
}]),
|
||||||
|
metric_relabel_configs: None,
|
||||||
|
metrics_path: Some("/snmp".to_string()),
|
||||||
|
scrape_timeout: Some("2m".to_string()),
|
||||||
|
job_name: Some(format!("snmp_exporter/cloud/{}", self.name.clone())),
|
||||||
|
scheme: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scrape_config = ScrapeConfig {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some(self.name.clone()),
|
||||||
|
namespace: Some(sender.namespace.clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
spec: scrape_config_spec,
|
||||||
|
};
|
||||||
|
sender
|
||||||
|
.client
|
||||||
|
.apply(&scrape_config, Some(&sender.namespace.clone()))
|
||||||
|
.await?;
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"installed scrape target {}",
|
||||||
|
self.name.clone()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user