fixed a bug with docker bridge subnet deduplication

This commit is contained in:
Maya
2025-10-22 14:54:40 -04:00
parent db60b29f26
commit 34cffb7ef4
12 changed files with 107 additions and 50 deletions
@@ -190,15 +190,21 @@ impl DiscoversNetworkedEntities for Discovery<DockerScanDiscovery> {
.scan_interfaces(self.discovery_type(), daemon_id, network_id)
.await?;
tracing::info!("Host subnets {:?}", host_subnets);
let docker_subnets = self
.get_subnets_from_docker_networks(daemon_id, network_id)
.await?;
let subnets = [host_subnets, docker_subnets].concat();
tracing::info!("Docker subnets {:?}", docker_subnets);
let subnets: Vec<Subnet> = [host_subnets, docker_subnets].concat();
let subnet_futures = subnets.iter().map(|subnet| self.create_subnet(subnet));
let subnets = try_join_all(subnet_futures).await?;
tracing::info!("Subnets {:?}", subnets);
Ok(subnets)
}
}
@@ -217,6 +223,7 @@ impl Discovery<DockerScanDiscovery> {
}
/// Create service which has all discovered containers in containers field
/// Host will also have
pub async fn create_docker_daemon_service(&self, services: Vec<Service>) -> Result<(), Error> {
let daemon_id = self.as_ref().config_store.get_id().await?;
let network_id = self
@@ -61,12 +61,7 @@ impl DiscoversNetworkedEntities for Discovery<NetworkScanDiscovery> {
cancel: CancellationToken,
) -> Result<(), Error> {
// Ignore docker bridge subnets, they are discovered through Docker Discovery
let subnets: Vec<Subnet> = self
.discover_create_subnets()
.await?
.into_iter()
.filter(|s| s.base.subnet_type != SubnetType::DockerBridge)
.collect();
let subnets: Vec<Subnet> = self.discover_create_subnets().await?;
let total_ips_across_subnets: usize = subnets
.iter()
@@ -106,6 +101,12 @@ impl DiscoversNetworkedEntities for Discovery<NetworkScanDiscovery> {
.utils
.scan_interfaces(self.discovery_type(), daemon_id, network_id)
.await?;
let subnets: Vec<Subnet> = subnets
.into_iter()
.filter(|s| s.base.subnet_type != SubnetType::DockerBridge)
.collect();
let subnet_futures = subnets.iter().map(|subnet| self.create_subnet(subnet));
let subnets = try_join_all(subnet_futures).await?;
+8 -1
View File
@@ -59,6 +59,7 @@ impl HostService {
) -> Result<(Host, Vec<Service>)> {
// Create host first (handles duplicates via upsert_host)
// Manually created and needs actual UUID
let mut created_host = if host.id == Uuid::nil() {
self.create_host(Host::new(host.base.clone()), &host.base.network_id)
.await?
@@ -97,12 +98,15 @@ impl HostService {
/// Create a new host
async fn create_host(&self, host: Host, network_id: &Uuid) -> Result<Host> {
let lock = self.get_host_lock(&host.id).await;
let _guard = lock.lock().await;
tracing::debug!("Creating host {:?}", host);
let all_hosts = self.storage.get_all(network_id).await?;
let host_from_storage = match all_hosts.into_iter().find(|h| host.eq(h)) {
// If both are from discovery, or if they have the same ID but for some reason the create route is being used, upsert data
// If both are from discovery, or if they have the same ID, upsert data
Some(existing_host)
if (host.base.source.discriminant() == EntitySourceDiscriminants::Discovery
&& existing_host.base.source.discriminant()
@@ -288,6 +292,9 @@ impl HostService {
return Err(anyhow!("Can't consolidate a host that has a daemon. Consolidate the other host into the daemon host."));
}
let lock = self.get_host_lock(&destination_host.id).await;
let _guard1 = lock.lock().await;
tracing::debug!(
"Consolidating host {:?} into host {:?}",
other_host,
+8 -8
View File
@@ -102,15 +102,15 @@ impl ServiceService {
) -> Result<Service> {
let mut binding_updates = 0;
let lock = self.get_service_lock(&existing_service.id).await;
let _guard = lock.lock().await;
tracing::debug!(
"Upserting new service data {:?} into {:?}",
new_service_data,
existing_service
);
let lock = self.get_service_lock(&existing_service.id).await;
let _guard = lock.lock().await;
for new_service_binding in &new_service_data.base.bindings {
if !existing_service.base.bindings.contains(new_service_binding) {
binding_updates += 1;
@@ -227,11 +227,11 @@ impl ServiceService {
}
pub async fn update_service(&self, mut service: Service) -> Result<Service> {
tracing::debug!("Updating service: {:?}", service);
let lock = self.get_service_lock(&service.id).await;
let _guard = lock.lock().await;
tracing::debug!("Updating service: {:?}", service);
let current_service = self
.get_service(&service.id)
.await?
@@ -409,6 +409,9 @@ impl ServiceService {
}
pub async fn delete_service(&self, id: &Uuid) -> Result<()> {
let lock = self.get_service_lock(id).await;
let _guard = lock.lock().await;
let service = self
.get_service(id)
.await?
@@ -416,9 +419,6 @@ impl ServiceService {
let mut all_services = self.get_all_services(&service.base.network_id).await?;
let lock = self.get_service_lock(&service.id).await;
let _guard = lock.lock().await;
self.update_group_service_bindings(&service, None).await?;
let container_update_futures = all_services.iter_mut().filter_map(|s| {
+52 -2
View File
@@ -1,4 +1,5 @@
use crate::server::{
discovery::types::base::{DiscoveryType, EntitySource},
hosts::service::HostService,
subnets::{storage::SubnetStorage, types::base::Subnet},
};
@@ -24,8 +25,56 @@ impl SubnetService {
pub async fn create_subnet(&self, subnet: Subnet) -> Result<Subnet> {
let all_subnets = self.storage.get_all(&subnet.base.network_id).await?;
tracing::debug!("Creating subnet {:?}", subnet);
let subnet_from_storage = match all_subnets.iter().find(|s| subnet.eq(s)) {
Some(existing_subnet) => {
// Docker will default to the same subnet range for bridge networks, so we need a way to distinguish docker bridge subnets
// with the same CIDR but which originate from different hosts
// This branch returns the existing subnet for docker bridge subnets created from the same host
// And the same subnet for all other sources provided CIDRs match
Some(existing_subnet)
if {
let result = match (&existing_subnet.base.source, &subnet.base.source) {
(
EntitySource::Discovery {
metadata: existing_metadata,
},
EntitySource::Discovery { metadata },
) => {
// Only one metadata entry will be present for subnet which is trying to be created bc it is brand new / just discovered
if let Some(metadata) = metadata.first() {
existing_metadata.iter().any(|other_m| {
match (metadata.discovery_type, other_m.discovery_type) {
// Only return existing if they originate from the same host
(
DiscoveryType::Docker { host_id },
DiscoveryType::Docker {
host_id: other_host_id,
},
) => host_id == other_host_id,
// Always return existing for other types
_ => true,
}
})
} else {
return Err(anyhow::anyhow!("Error comparing discovered subnets during creation: subnet missing discovery metadata"));
}
}
// Don't apply this to other cases - same CIDR means same subnet
_ => true,
};
tracing::info!(
"Dedup check result: {}, existing: {:?}, new: {:?}",
result,
existing_subnet.base.source,
subnet.base.source
);
result
} =>
{
tracing::warn!(
"Duplicate subnet for {}: {} found, returning existing {}: {}",
subnet.base.name,
@@ -35,7 +84,8 @@ impl SubnetService {
);
existing_subnet.clone()
}
None => {
// If there's no existing subnet, create a new one
_ => {
self.storage.create(&subnet).await?;
tracing::info!("Created subnet {}: {}", subnet.base.name, subnet.id);
subnet
+5 -16
View File
@@ -1,8 +1,6 @@
use std::net::Ipv4Addr;
use crate::server::discovery::types::base::{
DiscoveryMetadata, DiscoveryType, EntitySource, EntitySourceDiscriminants,
};
use crate::server::discovery::types::base::{DiscoveryMetadata, DiscoveryType, EntitySource};
use crate::server::services::types::definitions::ServiceDefinitionExt;
use crate::server::shared::types::api::deserialize_empty_string_as_none;
use chrono::{DateTime, Utc};
@@ -10,7 +8,6 @@ use cidr::{IpCidr, Ipv4Cidr};
use pnet::ipnetwork::IpNetwork;
use serde::{Deserialize, Serialize};
use std::hash::Hash;
use strum::IntoDiscriminant;
use strum_macros::{Display, EnumDiscriminants, EnumIter, IntoStaticStr};
use uuid::Uuid;
use validator::Validate;
@@ -146,18 +143,10 @@ impl Subnet {
impl PartialEq for Subnet {
fn eq(&self, other: &Self) -> bool {
(self.base.cidr == other.base.cidr
&& self.base.network_id == other.base.network_id
&& self.base.source.discriminant() != EntitySourceDiscriminants::System
&& other.base.source.discriminant() != EntitySourceDiscriminants::System)
|| self.id == other.id
// let sources_match = match (&self.base.source, &other.base.source) {
// (SubnetSource::Discovery(daemon_id), SubnetSource::Discovery(other_daemon_id)) => {
// daemon_id == other_daemon_id
// },
// _ => false
// };
// cidr_match
let network_match =
self.base.cidr == other.base.cidr && self.base.network_id == other.base.network_id;
network_match || self.id == other.id
}
}
@@ -109,7 +109,7 @@ impl EdgeBuilder {
.collect();
if group_docker_bridges_by_host {
// If subnets are grouped, pick an arbitrary
// If subnets are grouped, pick an arbitrary subnet ID to use for grouping
if let (Some(first_interface_id), Some(first_subnet_id)) = (
container_subnet_interface_ids.first(),
container_subnets.first(),