Skip to content

Commit af36aba

Browse files
committed
fix: DPU Extension Service status only include used DPUs
Signed-off-by: Felicity Xu <hanyux@nvidia.com>
1 parent d07e67b commit af36aba

4 files changed

Lines changed: 140 additions & 27 deletions

File tree

‎crates/api-model/src/instance/config/network.rs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,54 @@ impl InstanceNetworkConfig {
240240
}
241241
}
242242

243+
/// Returns the DPU machine IDs used by the instance network configuration.
244+
pub fn get_used_dpus(
245+
&self,
246+
device_to_id_map: &HashMap<String, Vec<MachineId>>,
247+
) -> Vec<MachineId> {
248+
let device_locators: Vec<DeviceLocator> = self
249+
.interfaces
250+
.iter()
251+
.filter_map(|i| i.device_locator.clone())
252+
.collect();
253+
254+
let legacy_physical_interface_count = self
255+
.interfaces
256+
.iter()
257+
.filter(|iface| {
258+
iface.function_id == InterfaceFunctionId::Physical {}
259+
&& iface.device_locator.is_none()
260+
})
261+
.count();
262+
263+
let use_primary_dpu_only = legacy_physical_interface_count > 0
264+
|| device_locators.is_empty()
265+
|| device_to_id_map.is_empty();
266+
267+
if use_primary_dpu_only {
268+
return device_to_id_map
269+
.values()
270+
.find_map(|ids| ids.first().copied())
271+
.into_iter()
272+
.collect();
273+
}
274+
275+
let mut used_dpus = Vec::new();
276+
for iface in &self.interfaces {
277+
let Some(device_locator) = iface.device_locator.as_ref() else {
278+
continue;
279+
};
280+
if let Some(dpu_ids) = device_to_id_map.get(&device_locator.device)
281+
&& let Some(dpu_id) = dpu_ids.get(device_locator.device_instance)
282+
&& !used_dpus.contains(dpu_id)
283+
{
284+
used_dpus.push(*dpu_id);
285+
}
286+
}
287+
288+
used_dpus
289+
}
290+
243291
/// Validates the network configuration.
244292
///
245293
/// Note: this is also called on POST-resolution configs (i.e. after

‎crates/api-model/src/instance/status/extension_service.rs‎

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl InstanceExtensionServicesStatus {
4444
/// For each extension service, we aggregate the statuses from all DPUs.
4545
/// The config passed must be from database (not rpc InstanceConfig), and must contain any terminating services.
4646
pub fn from_config_and_observations(
47-
dpu_id_to_device_map: &HashMap<String, Vec<MachineId>>,
47+
dpu_ids: &[MachineId],
4848
config: Versioned<&InstanceExtensionServicesConfig>,
4949
observations: &HashMap<MachineId, InstanceExtensionServiceStatusObservation>,
5050
) -> Self {
@@ -57,15 +57,11 @@ impl InstanceExtensionServicesStatus {
5757
};
5858
}
5959

60-
// Extract all unique DPU machine IDs from the dpu_id_to_device_map
61-
let all_dpu_ids: Vec<MachineId> =
62-
dpu_id_to_device_map.values().flatten().copied().collect();
63-
6460
// Instance allocation rejects non-empty service_configs on zero-DPU
6561
// hosts, so, in practice, we *shouldn't* reach here. BUT, if we do,
6662
// assume it's from something like a stale pre-validation instance,
6763
// and just report unsynced.
68-
if all_dpu_ids.is_empty() {
64+
if dpu_ids.is_empty() {
6965
return Self::unsynced_for_config(&config);
7066
}
7167

@@ -76,7 +72,7 @@ impl InstanceExtensionServicesStatus {
7672
for service in config.service_configs.iter() {
7773
let mut dpu_statuses = vec![];
7874

79-
for dpu_id in &all_dpu_ids {
75+
for dpu_id in dpu_ids {
8076
match observations.get(dpu_id) {
8177
// DPU has observation with matching config version
8278
Some(obs) if obs.config_version == config.version => {
@@ -447,12 +443,6 @@ mod tests {
447443
ExtensionServiceId::from_str("00000000-0000-0000-0000-000000000000").unwrap()
448444
}
449445

450-
fn create_dpu_map_with_one_dpu() -> HashMap<String, Vec<MachineId>> {
451-
let mut map = HashMap::new();
452-
map.insert("device0".to_string(), vec![get_test_machine_id()]);
453-
map
454-
}
455-
456446
fn create_service_config(version: ConfigVersion) -> InstanceExtensionServicesConfig {
457447
InstanceExtensionServicesConfig {
458448
service_configs: vec![InstanceExtensionServiceConfig {
@@ -497,10 +487,9 @@ mod tests {
497487
let config = create_service_config(service_version);
498488

499489
let config_version = ConfigVersion::initial();
500-
let dpu_map = create_dpu_map_with_one_dpu();
501490

502491
let status = InstanceExtensionServicesStatus::from_config_and_observations(
503-
&dpu_map,
492+
&vec![get_test_machine_id()],
504493
Versioned::new(&config, config_version),
505494
&HashMap::new(),
506495
);
@@ -530,15 +519,14 @@ mod tests {
530519
let config = create_service_config(service_version);
531520
let config_version = ConfigVersion::initial();
532521

533-
let dpu_map = create_dpu_map_with_one_dpu();
534522
let observations = create_observation(
535523
config_version,
536524
service_version,
537525
ExtensionServiceDeploymentStatus::Running,
538526
);
539527

540528
let status = InstanceExtensionServicesStatus::from_config_and_observations(
541-
&dpu_map,
529+
&vec![get_test_machine_id()],
542530
Versioned::new(&config, config_version),
543531
&observations,
544532
);
@@ -568,15 +556,14 @@ mod tests {
568556
fn extension_service_status_with_outdated_observation() {
569557
let config = create_service_config(ConfigVersion::initial());
570558
let version = ConfigVersion::initial();
571-
let dpu_map = create_dpu_map_with_one_dpu();
572559
let observations = create_observation(
573560
ConfigVersion::initial(),
574561
ConfigVersion::initial(),
575562
ExtensionServiceDeploymentStatus::Running,
576563
);
577564

578565
let status = InstanceExtensionServicesStatus::from_config_and_observations(
579-
&dpu_map,
566+
&vec![get_test_machine_id()],
580567
Versioned::new(&config, version.increment()), // Increment config version for extension service config
581568
&observations, // Observation has initial config version
582569
);
@@ -631,7 +618,7 @@ mod tests {
631618
);
632619

633620
let status = InstanceExtensionServicesStatus::from_config_and_observations(
634-
&dpu_map,
621+
&vec![get_test_machine_id()],
635622
Versioned::new(&config, config_version),
636623
&observations,
637624
);
@@ -718,7 +705,7 @@ mod tests {
718705
);
719706

720707
let status = InstanceExtensionServicesStatus::from_config_and_observations(
721-
&dpu_map,
708+
&vec![get_test_machine_id()],
722709
Versioned::new(&config, config_version),
723710
&observations,
724711
);
@@ -745,6 +732,77 @@ mod tests {
745732
assert_eq!(readiness, ExtensionServicesReadiness::Ready);
746733
}
747734

735+
#[test]
736+
fn extension_service_status_scopes_to_target_dpus() {
737+
let service_version = ConfigVersion::initial();
738+
let config = create_service_config(service_version);
739+
let config_version = ConfigVersion::initial();
740+
741+
let dpu1_id = get_test_machine_id();
742+
let dpu2_id =
743+
MachineId::from_str("fm100ds27v4uuq7sgs4gsjummskt0b3tedugtpevjrbfh6su081n9jufcq0")
744+
.unwrap();
745+
let mut observations = HashMap::new();
746+
observations.insert(
747+
dpu1_id,
748+
InstanceExtensionServiceStatusObservation {
749+
config_version,
750+
instance_config_version: None,
751+
extension_service_statuses: vec![ExtensionServiceStatusObservation {
752+
service_id: get_test_service_id(),
753+
service_type: ExtensionServiceType::KubernetesPod,
754+
service_name: "test-service".to_string(),
755+
version: service_version,
756+
removed: None,
757+
overall_state: ExtensionServiceDeploymentStatus::Running,
758+
components: vec![],
759+
message: String::new(),
760+
}],
761+
observed_at: chrono::Utc::now(),
762+
},
763+
);
764+
observations.insert(
765+
dpu2_id,
766+
InstanceExtensionServiceStatusObservation {
767+
config_version,
768+
instance_config_version: None,
769+
extension_service_statuses: vec![ExtensionServiceStatusObservation {
770+
service_id: get_test_service_id(),
771+
service_type: ExtensionServiceType::KubernetesPod,
772+
service_name: "test-service".to_string(),
773+
version: service_version,
774+
removed: None,
775+
overall_state: ExtensionServiceDeploymentStatus::Pending,
776+
components: vec![],
777+
message: "No pod sandbox found".to_string(),
778+
}],
779+
observed_at: chrono::Utc::now(),
780+
},
781+
);
782+
783+
let status = InstanceExtensionServicesStatus::from_config_and_observations(
784+
&[dpu1_id],
785+
Versioned::new(&config, config_version),
786+
&observations,
787+
);
788+
789+
assert_eq!(status.configs_synced, SyncState::Synced);
790+
assert_eq!(status.extension_services.len(), 1);
791+
assert_eq!(
792+
status.extension_services[0].overall_status,
793+
ExtensionServiceDeploymentStatus::Running
794+
);
795+
assert_eq!(status.extension_services[0].dpu_statuses.len(), 1);
796+
assert_eq!(
797+
status.extension_services[0].dpu_statuses[0].machine_id,
798+
dpu1_id
799+
);
800+
assert_eq!(
801+
compute_extension_services_readiness(&status),
802+
ExtensionServicesReadiness::Ready
803+
);
804+
}
805+
748806
#[test]
749807
fn extension_service_calculate_overall_status_all_running() {
750808
let dpu_statuses = vec![
@@ -880,7 +938,7 @@ mod tests {
880938
);
881939

882940
let status = InstanceExtensionServicesStatus::from_config_and_observations(
883-
&dpu_map,
941+
&vec![get_test_machine_id()],
884942
Versioned::new(&config, config_version),
885943
&observations,
886944
);

‎crates/machine-controller/src/handler.rs‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6377,19 +6377,24 @@ impl StateHandler for InstanceStateHandler {
63776377
}
63786378

63796379
// Gets extension services status from DB, checks if any removed services are fully terminated
6380-
// across all DPUs, if so, remove them from the instance config in the DB(without updating the version).
6380+
// across targeted DPUs, if so, remove them from the instance config in the DB(without updating the version).
63816381
fn get_extension_services_status(
63826382
mh_snapshot: &ManagedHostStateSnapshot,
63836383
instance: &InstanceSnapshot,
63846384
) -> InstanceExtensionServicesStatus {
6385-
let (_, dpu_id_to_device_map) = mh_snapshot
6385+
let (_, device_to_id_map) = mh_snapshot
63866386
.host_snapshot
63876387
.get_dpu_device_and_id_mappings()
63886388
.unwrap_or_else(|_| (HashMap::default(), HashMap::default()));
63896389

6390-
// Gather instance extension services status from all DPU observations
6390+
let mut target_dpus = instance.config.network.get_used_dpus(&device_to_id_map);
6391+
if target_dpus.is_empty() {
6392+
target_dpus = device_to_id_map.values().flatten().copied().collect();
6393+
}
6394+
6395+
// Gather instance extension services status from targeted DPUs.
63916396
InstanceExtensionServicesStatus::from_config_and_observations(
6392-
&dpu_id_to_device_map,
6397+
&target_dpus,
63936398
Versioned::new(
63946399
&instance.config.extension_services,
63956400
instance.extension_services_config_version,

‎crates/rpc/src/model/instance/status.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ pub fn instance_status_from_config_and_observation(
102102
// If observations.network.instance_config_version was None, then "ignore"
103103
}
104104

105+
let used_dpu_ids = network_config.value.get_used_dpus(&dpu_id_to_device_map);
106+
105107
let network =
106108
model::instance::status::network::InstanceNetworkStatus::from_config_and_observations(
107109
dpu_id_to_device_map.clone(),
@@ -117,7 +119,7 @@ pub fn instance_status_from_config_and_observation(
117119

118120
let extension_services =
119121
model::instance::status::extension_service::InstanceExtensionServicesStatus::from_config_and_observations(
120-
&dpu_id_to_device_map,
122+
&used_dpu_ids,
121123
extension_services_config,
122124
&observations.extension_services,
123125
);

0 commit comments

Comments
 (0)