diff --git a/internal/provisioning/hostagent/networkmanager/network_manager.go b/internal/provisioning/hostagent/networkmanager/network_manager.go index 4b932137c..2415a3aa9 100644 --- a/internal/provisioning/hostagent/networkmanager/network_manager.go +++ b/internal/provisioning/hostagent/networkmanager/network_manager.go @@ -51,8 +51,9 @@ type Interface interface { Start() error // GetDevice returns the PCI device by serial number GetDevice(serialNumber string) (hostutil.Device, bool) - // AddNetworkRequest adds a network request for a DPU - AddNetworkRequest(dpu *provisioningv1.DPU) error + // AddNetworkRequest adds a network request for a DPU. + // If vfCount is non-nil it overrides the value derived from the DPUFlavor. + AddNetworkRequest(dpu *provisioningv1.DPU, vfCount *int) error } type NetworkManager struct { @@ -247,7 +248,7 @@ func (nm *NetworkManager) processNetworkRequest(nr NetworkRequest) error { return nil } -func (nm *NetworkManager) AddNetworkRequest(dpu *provisioningv1.DPU) error { +func (nm *NetworkManager) AddNetworkRequest(dpu *provisioningv1.DPU, vfCount *int) error { nm.Lock() defer nm.Unlock() if !nm.initialized { @@ -256,7 +257,22 @@ func (nm *NetworkManager) AddNetworkRequest(dpu *provisioningv1.DPU) error { return fmt.Errorf("DPU is nil") } - if _, ok := nm.reqs[string(dpu.UID)]; ok { + if existing, ok := nm.reqs[string(dpu.UID)]; ok { + if vfCount == nil { + n, err := nm.getNumOfVFs(dpu) + if err != nil { + return fmt.Errorf("failed to get number of VFs: %w", err) + } + vfCount = &n + } + if *vfCount != 0 && existing.NumOfVFs != *vfCount { + existing.NumOfVFs = *vfCount + if err := writeNetworkRequestFile(&existing); err != nil { + return fmt.Errorf("failed to update network request file: %w", err) + } + nm.reqs[existing.UID] = existing + klog.Infof("Updated VF count to %d for DPU %s/%s", *vfCount, existing.DPUNamespace, existing.DpuName) + } return nil } @@ -272,9 +288,15 @@ func (nm *NetworkManager) AddNetworkRequest(dpu *provisioningv1.DPU) error { } nr.PCIAddress = dev.Address - numOfVFs, err := nm.getNumOfVFs(dpu) - if err != nil { - return fmt.Errorf("failed to get number of VFs: %w", err) + var numOfVFs int + if vfCount != nil && *vfCount != 0 { + numOfVFs = *vfCount + } else { + var err error + numOfVFs, err = nm.getNumOfVFs(dpu) + if err != nil { + return fmt.Errorf("failed to get number of VFs: %w", err) + } } nr.NumOfVFs = numOfVFs diff --git a/internal/provisioning/hostagent/networkmanager/network_manager_test.go b/internal/provisioning/hostagent/networkmanager/network_manager_test.go index bf13a747f..5ebfb3b54 100644 --- a/internal/provisioning/hostagent/networkmanager/network_manager_test.go +++ b/internal/provisioning/hostagent/networkmanager/network_manager_test.go @@ -102,7 +102,7 @@ var _ = Describe("NetworkManager", func() { }, } - err := nm.AddNetworkRequest(dpu) + err := nm.AddNetworkRequest(dpu, nil) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("network manager is not initialized")) }) @@ -111,7 +111,7 @@ var _ = Describe("NetworkManager", func() { nm := NewNetworkManager(nil) nm.initialized = true // Bypass initialization check - err := nm.AddNetworkRequest(nil) + err := nm.AddNetworkRequest(nil, nil) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("DPU is nil")) }) @@ -134,10 +134,118 @@ var _ = Describe("NetworkManager", func() { } // Should return nil since request already exists - err := nm.AddNetworkRequest(dpu) + err := nm.AddNetworkRequest(dpu, nil) Expect(err).NotTo(HaveOccurred()) }) + Context("vfCount override on existing request", func() { + var ( + tempDir string + origNetworkRequestDir string + ) + + BeforeEach(func() { + var err error + tempDir, err = os.MkdirTemp("", "nm-vfcount-test-*") + Expect(err).NotTo(HaveOccurred()) + origNetworkRequestDir = NetworkRequestDir + NetworkRequestDir = tempDir + }) + + AfterEach(func() { + NetworkRequestDir = origNetworkRequestDir + _ = os.RemoveAll(tempDir) + }) + + It("should update VF count on existing request when vfCount is provided", func() { + nm := NewNetworkManager(nil) + nm.initialized = true + + dpu := &provisioningv1.DPU{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpu", + Namespace: "default", + UID: "test-uid-vf-update", + }, + } + nm.reqs["test-uid-vf-update"] = NetworkRequest{ + UID: "test-uid-vf-update", + NumOfVFs: 4, + DpuName: "test-dpu", + } + + vfCount := 8 + err := nm.AddNetworkRequest(dpu, &vfCount) + Expect(err).NotTo(HaveOccurred()) + Expect(nm.reqs["test-uid-vf-update"].NumOfVFs).To(Equal(8)) + }) + + It("should not update VF count when vfCount is nil", func() { + nm := NewNetworkManager(nil) + nm.initialized = true + + dpu := &provisioningv1.DPU{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpu", + Namespace: "default", + UID: "test-uid-nil-vf", + }, + } + nm.reqs["test-uid-nil-vf"] = NetworkRequest{ + UID: "test-uid-nil-vf", + NumOfVFs: 4, + } + + err := nm.AddNetworkRequest(dpu, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(nm.reqs["test-uid-nil-vf"].NumOfVFs).To(Equal(4)) + }) + + It("should not update VF count when vfCount is zero", func() { + nm := NewNetworkManager(nil) + nm.initialized = true + + dpu := &provisioningv1.DPU{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpu", + Namespace: "default", + UID: "test-uid-zero-vf", + }, + } + nm.reqs["test-uid-zero-vf"] = NetworkRequest{ + UID: "test-uid-zero-vf", + NumOfVFs: 4, + } + + vfCount := 0 + err := nm.AddNetworkRequest(dpu, &vfCount) + Expect(err).NotTo(HaveOccurred()) + Expect(nm.reqs["test-uid-zero-vf"].NumOfVFs).To(Equal(4)) + }) + + It("should not update VF count when it matches existing", func() { + nm := NewNetworkManager(nil) + nm.initialized = true + + dpu := &provisioningv1.DPU{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpu", + Namespace: "default", + UID: "test-uid-same-vf", + }, + } + nm.reqs["test-uid-same-vf"] = NetworkRequest{ + UID: "test-uid-same-vf", + NumOfVFs: 4, + } + + vfCount := 4 + err := nm.AddNetworkRequest(dpu, &vfCount) + Expect(err).NotTo(HaveOccurred()) + Expect(nm.reqs["test-uid-same-vf"].NumOfVFs).To(Equal(4)) + }) + }) + It("should return error when device not found by serial number", func() { nm := NewNetworkManager(nil) nm.initialized = true @@ -154,7 +262,7 @@ var _ = Describe("NetworkManager", func() { }, } - err := nm.AddNetworkRequest(dpu) + err := nm.AddNetworkRequest(dpu, nil) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("PCI address of device")) Expect(err.Error()).To(ContainSubstring("not found")) diff --git a/internal/provisioning/hostagent/phase/network/hostnetwork.go b/internal/provisioning/hostagent/phase/network/hostnetwork.go index c0fc6e564..eb75d116b 100644 --- a/internal/provisioning/hostagent/phase/network/hostnetwork.go +++ b/internal/provisioning/hostagent/phase/network/hostnetwork.go @@ -31,10 +31,10 @@ const ( ) type Handler struct { - AddNetworkRequest func(dpu *provisioningv1.DPU) error + AddNetworkRequest func(dpu *provisioningv1.DPU, vfCount *int) error } -func NewHandler(addNetworkRequest func(dpu *provisioningv1.DPU) error) *Handler { +func NewHandler(addNetworkRequest func(dpu *provisioningv1.DPU, vfCount *int) error) *Handler { return &Handler{ AddNetworkRequest: addNetworkRequest, } @@ -42,7 +42,7 @@ func NewHandler(addNetworkRequest func(dpu *provisioningv1.DPU) error) *Handler func (h *Handler) Handle(ctx context.Context, dpu *provisioningv1.DPU) (provisioningv1.DPUStatus, ctrl.Result, error) { log := log.FromContext(ctx) - if err := h.AddNetworkRequest(dpu); err != nil { + if err := h.AddNetworkRequest(dpu, nil); err != nil { log.Error(err, "Failed to add network request") hostutil.NewCondition(condition).Failure(err, "FailedToSetupHostNetwork").Set(&dpu.Status.Conditions) return dpu.Status, ctrl.Result{}, err diff --git a/internal/provisioning/hostagent/service/installation_service.go b/internal/provisioning/hostagent/service/installation_service.go index 44922759b..e8d5199cf 100644 --- a/internal/provisioning/hostagent/service/installation_service.go +++ b/internal/provisioning/hostagent/service/installation_service.go @@ -67,7 +67,7 @@ const ( // NetworkConfigurator is an interface for triggering host network configuration. // It is satisfied by networkmanager.NetworkManager. type NetworkConfigurator interface { - AddNetworkRequest(dpu *provisioningv1.DPU) error + AddNetworkRequest(dpu *provisioningv1.DPU, vfCount *int) error } type InstallationService struct { @@ -329,7 +329,7 @@ func (s *InstallationService) ConfigureHostVFs(req *restful.Request, resp *restf return } - if err := s.networkManager.AddNetworkRequest(dpu); err != nil { + if err := s.networkManager.AddNetworkRequest(dpu, &request.VFCount); err != nil { klog.Errorf("failed to add network request for DPU %s/%s: %v", request.DPUNamespace, request.DPUName, err) _ = resp.WriteError(http.StatusInternalServerError, err) return diff --git a/internal/provisioning/hostagent/service/installation_service_test.go b/internal/provisioning/hostagent/service/installation_service_test.go index 105b658d7..27b2b392b 100644 --- a/internal/provisioning/hostagent/service/installation_service_test.go +++ b/internal/provisioning/hostagent/service/installation_service_test.go @@ -35,12 +35,12 @@ import ( ) type mockNetworkConfigurator struct { - addNetworkRequestFunc func(dpu *provisioningv1.DPU) error + addNetworkRequestFunc func(dpu *provisioningv1.DPU, vfCount *int) error } -func (m *mockNetworkConfigurator) AddNetworkRequest(dpu *provisioningv1.DPU) error { +func (m *mockNetworkConfigurator) AddNetworkRequest(dpu *provisioningv1.DPU, vfCount *int) error { if m.addNetworkRequestFunc != nil { - return m.addNetworkRequestFunc(dpu) + return m.addNetworkRequestFunc(dpu, vfCount) } return nil } @@ -385,18 +385,21 @@ var _ = Describe("InstallationService", func() { installationService.networkManager = mockNM }) - It("should successfully configure host VF", func() { + It("should successfully configure host VF with VFCount", func() { dpu := createDPU("test-dpu", testNS.Name) var receivedDPU *provisioningv1.DPU - mockNM.addNetworkRequestFunc = func(dpu *provisioningv1.DPU) error { + var receivedVFCount *int + mockNM.addNetworkRequestFunc = func(dpu *provisioningv1.DPU, vfCount *int) error { receivedDPU = dpu + receivedVFCount = vfCount return nil } request := types.ConfigureHostVFsRequest{ DPUName: dpu.Name, DPUNamespace: dpu.Namespace, + VFCount: 16, } req, err := json.Marshal(request) Expect(err).To(Succeed()) @@ -412,6 +415,10 @@ var _ = Describe("InstallationService", func() { Expect(receivedDPU.Spec.SerialNumber).To(Equal(dpu.Spec.SerialNumber)) Expect(receivedDPU.Spec.DPUFlavor).To(Equal(dpu.Spec.DPUFlavor)) Expect(receivedDPU.Spec.BFB).To(Equal(dpu.Spec.BFB)) + + By("VFCount should be passed through to AddNetworkRequest") + Expect(receivedVFCount).NotTo(BeNil()) + Expect(*receivedVFCount).To(Equal(16)) }) It("should return 404 when DPU not found", func() { @@ -430,7 +437,7 @@ var _ = Describe("InstallationService", func() { It("should return 500 when AddNetworkRequest fails", func() { dpu := createDPU("test-dpu", testNS.Name) - mockNM.addNetworkRequestFunc = func(dpu *provisioningv1.DPU) error { + mockNM.addNetworkRequestFunc = func(dpu *provisioningv1.DPU, vfCount *int) error { return fmt.Errorf("network manager is not initialized") } diff --git a/internal/provisioning/hostagent/service/types/types.go b/internal/provisioning/hostagent/service/types/types.go index 9cf0e47de..4660a948d 100644 --- a/internal/provisioning/hostagent/service/types/types.go +++ b/internal/provisioning/hostagent/service/types/types.go @@ -30,4 +30,5 @@ type UpdateStatusRequest struct { type ConfigureHostVFsRequest struct { DPUName string `json:"dpuName"` DPUNamespace string `json:"dpuNamespace"` + VFCount int `json:"vfCount"` }