Initial commit of DPU Service generation code#814
Initial commit of DPU Service generation code#814aadvani-nvidia wants to merge 4 commits intoNVIDIA:mainfrom
Conversation
🔐 TruffleHog Secret Scan✅ No secrets or credentials found! Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉 🕐 Last updated: 2026-04-04 02:05:21 UTC | Commit: ad4aa0e |
crates/dpf/src/services.rs
Outdated
| pub const DOCA_HBN_SERVICE_NETWORK: &str = "mybrhbn"; | ||
|
|
||
| /// DHCP Service Definitions | ||
| pub const DHCP_SERVER_SERVICE_NAME: &str = "carbide-dhcp-server"; |
There was a problem hiding this comment.
these don't go in the DPF SDK crate. pull the carbide configs out into the carbide DPF module with the default v2 services
Signed-off-by: Ashok Advani <aadvani@nvidia.com>
- Added DpuServiceInterface and ServiceChains Signed-off-by: Ashok Advani <aadvani@nvidia.com>
ad4aa0e to
5f8e370
Compare
There was a problem hiding this comment.
Pull request overview
Adds initial “DPU Service generation” support to the DPF SDK/API, extending initialization to generate additional CRDs (Service NADs and Service Interfaces) and introducing Carbide-specific v2 service definitions.
Changes:
- Extend SDK service types to include Service NAD specification and interface template types.
- Add repositories + SDK initialization logic to create/apply
DPUServiceNADandDPUServiceInterfaceresources. - Update API setup to supply an explicit v2 service set; add YAML-generation helper tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/dpf/src/types.rs | Adds Service NAD and service-interface-template SDK types. |
| crates/dpf/src/sdk.rs | Builds/applies NAD + service interface templates during initialization; changes deployment service-chain construction. |
| crates/dpf/src/repository/traits.rs | Introduces DpuServiceNADRepository and adds apply() to DpuServiceInterfaceRepository. |
| crates/dpf/src/repository/kube.rs | Implements Kube-backed CRUD/apply for DPUServiceNAD and DPUServiceInterface. |
| crates/dpf/src/test/sdk_initialization.rs | Extends mock repos and adds YAML-generation tests for initialized resources/v2 services. |
| crates/dpf/src/lib.rs | Re-exports new Service NAD SDK types. |
| crates/dpf/Cargo.toml | Adds serde_yaml dependency (currently used by tests). |
| crates/api/src/setup.rs | Populates v2 services list instead of an empty list. |
| crates/api/src/dpf_services.rs | Adds Carbide v2 service definitions and default registry/image constants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Service Network Attachment Definition (NAD) | ||
| #[derive(Debug, Clone)] | ||
| pub enum ServiceNADResourceType { | ||
| Vf, | ||
| Sf, | ||
| Veth, | ||
| } |
There was a problem hiding this comment.
ServiceNADResourceType is used by reference in sdk::build_service_nad, but the enum is not Copy. Matching on service_nad.resource_type will try to move the value out of &ServiceNAD and won’t compile. Fix by deriving Copy for this enum (and/or matching on &service_nad.resource_type / cloning).
| /// Service Network Attachment Definition (NAD) | ||
| #[derive(Debug, Clone)] | ||
| pub enum DpuServiceInterfaceTemplateType { | ||
| Vlan, | ||
| Physical, | ||
| Pf, | ||
| Vf, | ||
| Ovn, | ||
| Service, | ||
| } |
There was a problem hiding this comment.
DpuServiceInterfaceTemplateType is matched on via iface.iface_type in sdk::apply_service_interface_templates, but the enum is not Copy. This will attempt to move the value out of a borrowed &DpuServiceInterfaceTemplateDefinition and won’t compile. Derive Copy for this enum (and/or match on a reference / clone before matching).
| } | ||
|
|
||
|
|
||
| /// Service Network Attachment Definition (NAD) |
There was a problem hiding this comment.
The comment above DpuServiceInterfaceTemplateType says “Service Network Attachment Definition (NAD)”, but the enum represents service interface template types. This is misleading documentation for a public SDK type; please update the doc comment to describe interface templates instead of NADs.
| /// Service Network Attachment Definition (NAD) | |
| /// Interface template type for a DPU service. |
| let all_switches: Vec<DpuDeploymentServiceChainsSwitches> = vec![ | ||
| DpuDeploymentServiceChainsSwitches { | ||
| ports: vec![ | ||
| DpuDeploymentServiceChainsSwitchesPorts { | ||
| service_interface: Some( | ||
| DpuDeploymentServiceChainsSwitchesPortsServiceInterface { | ||
| match_labels: BTreeMap::from([( | ||
| "interface".to_string(), | ||
| "p0".to_string(), | ||
| )]), | ||
| ipam: None, | ||
| }, | ||
| DpuDeploymentServiceChainsSwitchesPorts { | ||
| service: Some(DpuDeploymentServiceChainsSwitchesPortsService { | ||
| name: chain.service_name.clone(), | ||
| interface: chain.service_interface.clone(), | ||
| ipam: None, | ||
| }), | ||
| service_interface: None, | ||
| ), | ||
| service: Some( | ||
| DpuDeploymentServiceChainsSwitchesPortsService { | ||
| name: "doca-hbn".to_string(), | ||
| interface: "p0_if".to_string(), | ||
| ipam: None, | ||
| } | ||
| ) | ||
| } | ||
| ], | ||
| service_mtu: None, | ||
| }, | ||
| DpuDeploymentServiceChainsSwitches { | ||
| ports: vec![ | ||
| DpuDeploymentServiceChainsSwitchesPorts { | ||
| service_interface: Some( | ||
| DpuDeploymentServiceChainsSwitchesPortsServiceInterface { | ||
| match_labels: BTreeMap::from([( | ||
| "interface".to_string(), | ||
| "pf0hpf".to_string(), | ||
| )]), | ||
| ipam: None, | ||
| }, | ||
| ], | ||
| service_mtu: None, | ||
| }) | ||
| }) | ||
| .collect(); | ||
| ), | ||
| service: Some( | ||
| DpuDeploymentServiceChainsSwitchesPortsService { | ||
| name: "doca-hbn".to_string(), | ||
| interface: "pf0hpf_if".to_string(), | ||
| ipam: None, | ||
| } | ||
| ) | ||
| }, | ||
| DpuDeploymentServiceChainsSwitchesPorts { | ||
| service_interface: None, | ||
| service: Some( | ||
| DpuDeploymentServiceChainsSwitchesPortsService { | ||
| name: "carbide-dhcp-server".to_string(), | ||
| interface: "d_pf0hpf_if".to_string(), | ||
| ipam: None, | ||
| } | ||
| ) | ||
| } | ||
| ], | ||
| service_mtu: None, | ||
| } | ||
| ]; |
There was a problem hiding this comment.
build_deployment() no longer derives service_chains from svc.service_chain_switches and instead hard-codes switches for specific service/interface names (e.g. doca-hbn, carbide-dhcp-server). This breaks callers that supply their own service_chain_switches and can generate incorrect deployments when services are renamed or omitted. Restore the previous behavior of building switches from services[*].service_chain_switches (or gate the hard-coded chain behind an explicit config flag).
| hyper = { features = ["client", "http1"], workspace = true } | ||
| http = { workspace = true } | ||
| once_cell = { workspace = true } | ||
| serde_yaml = { workspace = true } |
There was a problem hiding this comment.
serde_yaml is only used in crates/dpf/src/test/sdk_initialization.rs (tests), but it is added as a normal dependency. This needlessly pulls it into non-test builds of the dpf crate. Move serde_yaml to [dev-dependencies] (or gate it behind a feature used only for these tests).
| #[tokio::test] | ||
| async fn test_generate_yaml_for_initialized_resources() { | ||
| let mock = InitializationMock::default(); | ||
|
|
||
| let config = InitDpfResourcesConfig { | ||
| bfb_url: "http://example.com/test.bfb".to_string(), | ||
| deployment_name: "carbide-deployment".to_string(), | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| let sdk = crate::sdk::DpfSdkBuilder::new(mock.clone(), TEST_NS, "test-password".to_string()) | ||
| .initialize(&config) | ||
| .await | ||
| .unwrap(); | ||
| drop(sdk); | ||
|
|
||
| for entry in mock.bfbs.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.flavors.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.service_templates.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.service_configs.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.nads.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.deployments.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| } |
There was a problem hiding this comment.
These tests only print YAML to stdout and contain no assertions, so they won’t catch regressions and may add noisy output to CI. Consider converting them into snapshot/assertion-based tests (e.g. compare against expected YAML/JSON) or marking them #[ignore] if they’re intended as local generation helpers.
| #[tokio::test] | ||
| async fn test_generate_yaml_for_v2_services() { | ||
| use crate::types::{ServiceInterface, ServiceNAD, ServiceNADResourceType}; | ||
|
|
||
| let mock = InitializationMock::default(); | ||
|
|
||
| let doca_helm = "https://helm.ngc.nvidia.com/nvidia/doca"; | ||
| let carbide_helm = "https://gitlab-master.nvidia.com/aadvani/my-helm-project/-/raw/main/charts-repo"; | ||
| let doca_image = "nvcr.io/nvidia/doca"; | ||
| let carbide_image = "https://gitlab-master.nvidia.com/aadvani/my-helm-project"; | ||
|
|
||
| let services = vec![ | ||
| crate::services::dts_service(&crate::services::ServiceRegistryConfig::default()), | ||
| ServiceDefinition { | ||
| helm_values: Some(serde_json::json!({ | ||
| "image": { | ||
| "repository": format!("{}/forge-dhcp-server", carbide_image), | ||
| "tag": "v1.9.5-arm64-distroless", | ||
| } | ||
| })), | ||
| interfaces: vec![ServiceInterface { | ||
| name: "d_pf0hpf_if".to_string(), | ||
| network: "mybrsfc-dhcp".to_string(), | ||
| }], | ||
| service_nad: Some(ServiceNAD { | ||
| name: "mybrsfc-dhcp".to_string(), | ||
| bridge: Some("br-sfc".to_string()), | ||
| resource_type: ServiceNADResourceType::Sf, | ||
| ipam: Some(false), | ||
| mtu: Some(1500), | ||
| }), | ||
| ..ServiceDefinition::new("carbide-dhcp-server", carbide_helm, "carbide-dhcp-server", "2.0.9") | ||
| }, | ||
| ServiceDefinition { | ||
| helm_values: Some(serde_json::json!({ | ||
| "image": { | ||
| "repository": format!("{}/doca-hbn", doca_image), | ||
| "tag": "3.2.1-doca3.2.1", | ||
| }, | ||
| "resources": { "memory": "6Gi", "nvidia.com/bf_sf": 2 }, | ||
| })), | ||
| config_values: Some(serde_json::json!({ | ||
| "configuration": { | ||
| "startupYAMLJ2": concat!( | ||
| "- header:\n", | ||
| " model: BLUEFIELD\n", | ||
| " nvue-api-version: nvue_v1\n", | ||
| " rev-id: 1.0\n", | ||
| " version: HBN 2.4.0\n", | ||
| "- set:\n", | ||
| " interface:\n", | ||
| " p0_if:\n", | ||
| " type: swp\n", | ||
| " pf0hpf_if:\n", | ||
| " type: swp\n", | ||
| ) | ||
| } | ||
| })), | ||
| interfaces: vec![ | ||
| ServiceInterface { name: "p0_if".to_string(), network: "mybrhbn".to_string() }, | ||
| ServiceInterface { name: "pf0hpf_if".to_string(), network: "mybrhbn".to_string() }, | ||
| ], | ||
| ..ServiceDefinition::new("doca-hbn", doca_helm, "doca-hbn", "1.0.5") | ||
| }, | ||
| ServiceDefinition { | ||
| helm_values: Some(serde_json::json!({ | ||
| "image": { | ||
| "repository": format!("{}/forge-dpu-agent", carbide_image), | ||
| "tag": "v0.3-arm64-multistage", | ||
| } | ||
| })), | ||
| ..ServiceDefinition::new("carbide-dpu-agent", carbide_helm, "carbide-dpu-agent", "0.4.0") | ||
| }, | ||
| ]; | ||
|
|
||
| let config = InitDpfResourcesConfig { | ||
| bfb_url: "http://example.com/test.bfb".to_string(), | ||
| deployment_name: "carbide-deployment".to_string(), | ||
| services, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| let sdk = crate::sdk::DpfSdkBuilder::new(mock.clone(), TEST_NS, "test-password".to_string()) | ||
| .initialize(&config) | ||
| .await | ||
| .unwrap(); | ||
| drop(sdk); | ||
|
|
||
| for entry in mock.bfbs.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.flavors.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.service_templates.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.service_configs.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.nads.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.service_interfaces.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| for entry in mock.deployments.iter() { | ||
| println!("---\n{}", serde_yaml::to_string(entry.value()).unwrap()); | ||
| } | ||
| } |
There was a problem hiding this comment.
This test builds a large “v2 services” config and then only prints generated YAML without assertions. If this is meant to validate generated CRDs, add concrete checks (counts, key fields, or snapshots). If it’s only a manual YAML generator, mark it #[ignore] to avoid running in normal test suites.
crates/api/src/dpf_services.rs
Outdated
| pub const DEFAULT_CARBIDE_HELM_REGISTRY: &str = "https://gitlab-master.nvidia.com/aadvani/my-helm-project/-/raw/main/charts-repo"; | ||
|
|
||
| /// Default DOCA container image registry prefix. | ||
| pub const DEFAULT_DOCA_IMAGE_REGISTRY: &str = "nvcr.io/nvidia/doca"; | ||
|
|
||
| /// Default Carbide container image registry prefix. | ||
| pub const DEFAULT_CARBIDE_IMAGE_REGISTRY: &str = "https://gitlab-master.nvidia.com/aadvani/my-helm-project"; |
There was a problem hiding this comment.
The default Carbide helm/image registries are set to a specific internal GitLab project URL. As defaults, this can break deployments outside that environment and makes the API behavior environment-specific. Consider defaulting to the public/official registries (or leaving defaults empty) and relying on configuration to override for internal development.
| pub const DEFAULT_CARBIDE_HELM_REGISTRY: &str = "https://gitlab-master.nvidia.com/aadvani/my-helm-project/-/raw/main/charts-repo"; | |
| /// Default DOCA container image registry prefix. | |
| pub const DEFAULT_DOCA_IMAGE_REGISTRY: &str = "nvcr.io/nvidia/doca"; | |
| /// Default Carbide container image registry prefix. | |
| pub const DEFAULT_CARBIDE_IMAGE_REGISTRY: &str = "https://gitlab-master.nvidia.com/aadvani/my-helm-project"; | |
| /// Default Carbide helm registry is intentionally unset and should be provided via configuration. | |
| pub const DEFAULT_CARBIDE_HELM_REGISTRY: &str = ""; | |
| /// Default DOCA container image registry prefix. | |
| pub const DEFAULT_DOCA_IMAGE_REGISTRY: &str = "nvcr.io/nvidia/doca"; | |
| /// Default Carbide container image registry prefix is intentionally unset and should be provided via configuration. | |
| pub const DEFAULT_CARBIDE_IMAGE_REGISTRY: &str = ""; |
crates/api/src/dpf_services.rs
Outdated
| pub const DEFAULT_DOCA_IMAGE_REGISTRY: &str = "nvcr.io/nvidia/doca"; | ||
|
|
||
| /// Default Carbide container image registry prefix. | ||
| pub const DEFAULT_CARBIDE_IMAGE_REGISTRY: &str = "https://gitlab-master.nvidia.com/aadvani/my-helm-project"; |
There was a problem hiding this comment.
DEFAULT_CARBIDE_IMAGE_REGISTRY includes an https:// scheme, but container image references typically must be in the form registry/namespace without a URL scheme. If this value is used to build the Helm image.repository, it will likely produce invalid image references (e.g. https://.../forge-dpu-agent). Consider storing the registry as gitlab-master.nvidia.com/... (no scheme) and keeping transport/protocol out of the image name.
| pub const DEFAULT_CARBIDE_IMAGE_REGISTRY: &str = "https://gitlab-master.nvidia.com/aadvani/my-helm-project"; | |
| pub const DEFAULT_CARBIDE_IMAGE_REGISTRY: &str = "gitlab-master.nvidia.com/aadvani/my-helm-project"; |
Signed-off-by: Ashok Advani <aadvani@nvidia.com>
Signed-off-by: Ashok Advani <aadvani@nvidia.com>
Description
Type of Change
Related Issues (Optional)
Breaking Changes
Testing
Additional Notes