diff --git a/internal/controller/randeployment_controller.go b/internal/controller/randeployment_controller.go index 54d2b92..5662dbf 100644 --- a/internal/controller/randeployment_controller.go +++ b/internal/controller/randeployment_controller.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "time" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" @@ -35,6 +36,7 @@ import ( configref "github.com/nephio-project/api/references/v1alpha1" workloadv1alpha1 "github.com/nephio-project/api/workload/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func GetSupportedProviders() []string { @@ -69,18 +71,19 @@ type NfResource interface { GetService() []*corev1.Service } -func (r *RANDeploymentReconciler) CreateAll(ctx context.Context, ranDeployment *workloadv1alpha1.NFDeployment, nfResource NfResource, configInfo *ConfigInfo) { +func (r *RANDeploymentReconciler) CreateAll(ctx context.Context, ranDeployment *workloadv1alpha1.NFDeployment, nfResource NfResource, configInfo *ConfigInfo) []error { namespacedName := types.NamespacedName{Namespace: ranDeployment.Namespace, Name: ranDeployment.Name} logger := log.FromContext(ctx).WithValues("RANDeployment", namespacedName) + outErrorList := []error{} var err error namespaceProvided := ranDeployment.Namespace - for _, resource := range nfResource.GetServiceAccount() { if resource.ObjectMeta.Namespace == "" { resource.ObjectMeta.Namespace = namespaceProvided } err = r.Create(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Creating resource of GetServiceAccount()") } } @@ -91,6 +94,7 @@ func (r *RANDeploymentReconciler) CreateAll(ctx context.Context, ranDeployment * } err = r.Create(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Creating resource of GetConfigMap()") } } @@ -101,6 +105,7 @@ func (r *RANDeploymentReconciler) CreateAll(ctx context.Context, ranDeployment * } err = r.Create(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Creating resource of GetDeployment()") } } @@ -110,24 +115,27 @@ func (r *RANDeploymentReconciler) CreateAll(ctx context.Context, ranDeployment * } err = r.Create(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Creating resource of GetService()") } } + return outErrorList } -func (r *RANDeploymentReconciler) DeleteAll(ctx context.Context, ranDeployment *workloadv1alpha1.NFDeployment, nfResource NfResource, configInfo *ConfigInfo) { +func (r *RANDeploymentReconciler) DeleteAll(ctx context.Context, ranDeployment *workloadv1alpha1.NFDeployment, nfResource NfResource, configInfo *ConfigInfo) []error { namespacedName := types.NamespacedName{Namespace: ranDeployment.Namespace, Name: ranDeployment.Name} logger := log.FromContext(ctx).WithValues("RANDeployment", namespacedName) + outErrorList := []error{} var err error namespaceProvided := ranDeployment.Namespace - for _, resource := range nfResource.GetServiceAccount() { if resource.ObjectMeta.Namespace == "" { resource.ObjectMeta.Namespace = namespaceProvided } err = r.Delete(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Deleting resource of GetServiceAccount()") } } @@ -138,6 +146,7 @@ func (r *RANDeploymentReconciler) DeleteAll(ctx context.Context, ranDeployment * } err = r.Delete(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Deleting resource of GetConfigMap()") } } @@ -148,6 +157,7 @@ func (r *RANDeploymentReconciler) DeleteAll(ctx context.Context, ranDeployment * } err = r.Delete(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Deleting resource of GetDeployment()") } @@ -159,10 +169,12 @@ func (r *RANDeploymentReconciler) DeleteAll(ctx context.Context, ranDeployment * } err = r.Delete(ctx, resource) if err != nil { + outErrorList = append(outErrorList, err) logger.Error(err, "Error During Deleting resource of GetService()") } } + return outErrorList } @@ -222,6 +234,36 @@ func (r *RANDeploymentReconciler) GetConfigs(ctx context.Context, ranDeployment return configInfo, nil } +/* +For status: +Accepted Condition-Type (CamelCased) are: + 1. invalidProvider + 2. invalidConfigInfo + 3. resourceCreation + 4. resourceDeletion +*/ +func (r *RANDeploymentReconciler) updateStatusIfRequired(ctx context.Context, ranDeployment *workloadv1alpha1.NFDeployment, curCondition metav1.Condition) error { + + for index, oldCond := range ranDeployment.Status.Conditions { + if oldCond.Type == curCondition.Type { + // Check if the condition is different from the previous one + if oldCond.Reason == curCondition.Reason && oldCond.Message == curCondition.Message && oldCond.Status == curCondition.Status { + // Do Nothing + return nil + } else { + // CurCondition is New + ranDeployment.Status.Conditions[index] = curCondition + err := r.Status().Update(ctx, ranDeployment) + return err + } + } + } + // The condition-type is new + ranDeployment.Status.Conditions = append(ranDeployment.Status.Conditions, curCondition) + err := r.Status().Update(ctx, ranDeployment) + return err +} + //+kubebuilder:rbac:groups=workload.nephio.org,resources=randeployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=workload.nephio.org,resources=randeployments/status,verbs=get;update;patch //+kubebuilder:rbac:groups=workload.nephio.org,resources=randeployments/finalizers,verbs=update @@ -253,6 +295,23 @@ func (r *RANDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques if !slices.Contains(GetSupportedProviders(), instance.Spec.Provider) { logger.Info("Reconcile called for not supported provider", "Provider", instance.Spec.Provider) + // Update it in Status + supportedProvider := "" + for _, provider := range GetSupportedProviders() { + supportedProvider += (provider + ", ") + } + curCondition := metav1.Condition{ + Type: "invalidProvider", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionFalse, + Reason: "invalidProvider", + Message: instance.Spec.Provider + " Not supported| Supported Providers are " + supportedProvider, + } + err = r.updateStatusIfRequired(ctx, instance, curCondition) + if err != nil { + logger.Error(err, " | Unable to update status with type: invalidProvider") + } + return ctrl.Result{}, nil } logger.Info("RANDeployment", "RANDeployment CR", instance.Spec) @@ -260,6 +319,18 @@ func (r *RANDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques configInfo, err := r.GetConfigs(ctx, instance) if err != nil || configInfo == nil { logger.Error(err, "Failed to get required ConfigInfo") + curCondition := metav1.Condition{ + Type: "invalidConfigInfo", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionFalse, + Reason: "invalidConfigInfo", + Message: "Failed to get required ConfigInfo | Error: " + err.Error(), + } + statusErr := r.updateStatusIfRequired(ctx, instance, curCondition) + if err != nil { + logger.Error(statusErr, " | Unable to update status with type: invalidConfigInfo") + } + return ctrl.Result{}, err } // name of our custom finalizer @@ -269,25 +340,54 @@ func (r *RANDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Adding a Finaliser also adds the DeletionTimestamp while deleting if !controllerutil.ContainsFinalizer(instance, myFinalizerName) { // Assumed to be called only during CR-Creation - + var errList []error switch resourceType := instance.Spec.Provider; resourceType { case "cucp.openairinterface.org": logger.Info("--- Creation for CUCP") cucpResource := CuCpResources{} - r.CreateAll(ctx, instance, cucpResource, configInfo) + errList = r.CreateAll(ctx, instance, cucpResource, configInfo) logger.Info("--- CUCP Created") case "cuup.openairinterface.org": logger.Info("--- Creation for CUUP") cuupResource := CuUpResources{} - r.CreateAll(ctx, instance, cuupResource, configInfo) + errList = r.CreateAll(ctx, instance, cuupResource, configInfo) logger.Info("--- CUUP Created") case "du.openairinterface.org": logger.Info("--- Creation for DU") duResource := DuResources{} - r.CreateAll(ctx, instance, duResource, configInfo) + errList = r.CreateAll(ctx, instance, duResource, configInfo) logger.Info("--- DU Created") } + // Update Status: + var curCondition metav1.Condition + if len(errList) == 0 { + curCondition = metav1.Condition{ + Type: "resourceCreation", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionTrue, + Reason: "resourceCreation", + Message: "All resources created successfully", + } + } else { + message := "" + for _, err := range errList { + message += (err.Error() + ", ") + } + curCondition = metav1.Condition{ + Type: "resourceCreation", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionFalse, + Reason: "resourceCreation", + Message: message, + } + + } + err = r.updateStatusIfRequired(ctx, instance, curCondition) + if err != nil { + logger.Error(err, " | Unable to update status with type: resourceCreation") + } + controllerutil.AddFinalizer(instance, myFinalizerName) if err := r.Update(ctx, instance); err != nil { return ctrl.Result{}, err @@ -296,26 +396,55 @@ func (r *RANDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques } else { // The object is assumed to be deleted if controllerutil.ContainsFinalizer(instance, myFinalizerName) { - + var errList []error switch resourceType := instance.Spec.Provider; resourceType { case "cucp.openairinterface.org": logger.Info("--- Deletion for CUCP") cucpResource := CuCpResources{} - r.DeleteAll(ctx, instance, cucpResource, configInfo) + errList = r.DeleteAll(ctx, instance, cucpResource, configInfo) logger.Info("--- CUCP Deleted") case "cuup.openairinterface.org": logger.Info("--- Deletion for CUUP") cuupResource := CuUpResources{} - r.DeleteAll(ctx, instance, cuupResource, configInfo) + errList = r.DeleteAll(ctx, instance, cuupResource, configInfo) logger.Info("--- CUUP Deleted") case "du.openairinterface.org": logger.Info("--- Deletion for DU") duResource := DuResources{} - r.DeleteAll(ctx, instance, duResource, configInfo) + errList = r.DeleteAll(ctx, instance, duResource, configInfo) logger.Info("--- DU Deleted") } + // Update Status: + var curCondition metav1.Condition + if len(errList) == 0 { + curCondition = metav1.Condition{ + Type: "resourceDeletion", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionTrue, + Reason: "resourceDeletion", + Message: "All resources deleted successfully", + } + } else { + message := "" + for _, err := range errList { + message += (err.Error() + ", ") + } + curCondition = metav1.Condition{ + Type: "resourceDeletion", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionFalse, + Reason: "resourceDeletion", + Message: message, + } + + } + err = r.updateStatusIfRequired(ctx, instance, curCondition) + if err != nil { + logger.Error(err, " | Unable to update status with type: resourceDeletion") + } + // remove our finalizer from the list and update it. controllerutil.RemoveFinalizer(instance, myFinalizerName) if err := r.Update(ctx, instance); err != nil { diff --git a/internal/controller/randeployment_controller_test.go b/internal/controller/randeployment_controller_test.go index 65d618e..76149c0 100644 --- a/internal/controller/randeployment_controller_test.go +++ b/internal/controller/randeployment_controller_test.go @@ -340,6 +340,11 @@ func TestDeleteAll(t *testing.T) { } func TestReconcileErrorScenarios(t *testing.T) { + supportedProvider := "" + for _, provider := range GetSupportedProviders() { + supportedProvider += (provider + ", ") + } + cases := map[string]struct { ranDeployment *workloadv1alpha1.NFDeployment mockReturnErr error @@ -355,6 +360,18 @@ func TestReconcileErrorScenarios(t *testing.T) { "Ran-deployment Nf Doesn't have a provider field": { ranDeployment: &workloadv1alpha1.NFDeployment{ Spec: workloadv1alpha1.NFDeploymentSpec{}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + { + Type: "invalidProvider", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionFalse, + Reason: "invalidProvider", + Message: " Not supported| Supported Providers are " + supportedProvider, + }, + }, + }, }, mockReturnErr: nil, expectedError: nil, @@ -370,6 +387,18 @@ func TestReconcileErrorScenarios(t *testing.T) { }, }, }, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + { + Type: "invalidConfigInfo", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionFalse, + Reason: "invalidConfigInfo", + Message: "Failed to get required ConfigInfo | Error: " + "Not supported API version \"dummy-apiversion\"", + }, + }, + }, }, mockReturnErr: nil, expectedError: fmt.Errorf("Not supported API version \"dummy-apiversion\""), @@ -399,6 +428,14 @@ func TestReconcileErrorScenarios(t *testing.T) { } func TestReconcileCreateScenarios(t *testing.T) { + mustCondition := metav1.Condition{ + Type: "resourceCreation", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionTrue, + Reason: "resourceCreation", + Message: "All resources created successfully", + } + cases := map[string]struct { ranDeployment *workloadv1alpha1.NFDeployment expectedError error @@ -406,18 +443,36 @@ func TestReconcileCreateScenarios(t *testing.T) { "Create DU": { ranDeployment: &workloadv1alpha1.NFDeployment{ Spec: workloadv1alpha1.NFDeploymentSpec{Provider: "du.openairinterface.org"}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + mustCondition, + }, + }, }, expectedError: nil, }, "Create CUCP": { ranDeployment: &workloadv1alpha1.NFDeployment{ Spec: workloadv1alpha1.NFDeploymentSpec{Provider: "cucp.openairinterface.org"}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + mustCondition, + }, + }, }, expectedError: nil, }, "Create CUUP": { ranDeployment: &workloadv1alpha1.NFDeployment{ Spec: workloadv1alpha1.NFDeploymentSpec{Provider: "cuup.openairinterface.org"}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + mustCondition, + }, + }, }, expectedError: nil, }, @@ -456,6 +511,14 @@ func TestReconcileCreateScenarios(t *testing.T) { } func TestReconcileDeleteScenarios(t *testing.T) { + mustCondition := metav1.Condition{ + Type: "resourceDeletion", + LastTransitionTime: metav1.Time{Time: time.Now()}, + Status: metav1.ConditionTrue, + Reason: "resourceDeletion", + Message: "All resources deleted successfully", + } + cases := map[string]struct { ranDeployment *workloadv1alpha1.NFDeployment expectedError error @@ -466,6 +529,12 @@ func TestReconcileDeleteScenarios(t *testing.T) { DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: workloadv1alpha1.NFDeploymentSpec{Provider: "du.openairinterface.org"}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + mustCondition, + }, + }, }, expectedError: nil, }, @@ -475,6 +544,12 @@ func TestReconcileDeleteScenarios(t *testing.T) { DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: workloadv1alpha1.NFDeploymentSpec{Provider: "cucp.openairinterface.org"}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + mustCondition, + }, + }, }, expectedError: nil, }, @@ -484,6 +559,12 @@ func TestReconcileDeleteScenarios(t *testing.T) { DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: workloadv1alpha1.NFDeploymentSpec{Provider: "cuup.openairinterface.org"}, + Status: workloadv1alpha1.NFDeploymentStatus{ + // The operator must give this Status-Condition + Conditions: []metav1.Condition{ + mustCondition, + }, + }, }, expectedError: nil, }, diff --git a/test-infra/ReadMe.md b/test-infra/ReadMe.md index 51790aa..ccefe04 100644 --- a/test-infra/ReadMe.md +++ b/test-infra/ReadMe.md @@ -51,15 +51,6 @@ Follow the steps 3-6 of [Master Excercise](https://docs.nephio.org/docs/guides/u ``` ### Step-7: Deploy UE (20 MHz): -The current ue-image is not able to connect to the du using service (oai-gnb-du.oai-ran-du), Therefore, we provide the du-ip to ue manually as workaround: (Todo: Ideal way to resolve) -```bash -DU_POD=$(kubectl get pods -n oai-ran-du --context edge-admin@edge -l app.kubernetes.io/name=oai-gnb-du -o jsonpath='{.items[*].metadata.name}') -DU_IP=$(kubectl get pods $DU_POD -n oai-ran-du --context edge-admin@edge -o jsonpath="{.status.podIP}") -echo $DU_IP -``` -Copy the du-ip to the "uedeployment.yaml" under the Additional-Option "--rfsimulator.serveraddr" to both 20Mhz and 40Mhz UE. - -#### Deploy the ue ```bash cd test-infra/oai-ue/ kubectl apply -f namespace.yaml --context edge-admin@edge diff --git a/test-infra/oai-ue/20Mhz/uedeployment.yaml b/test-infra/oai-ue/20Mhz/uedeployment.yaml index 20718d3..7813cc2 100644 --- a/test-infra/oai-ue/20Mhz/uedeployment.yaml +++ b/test-infra/oai-ue/20Mhz/uedeployment.yaml @@ -1,10 +1,8 @@ apiVersion: apps/v1 kind: Deployment -metadata: # kpt-merge: oai-ue/oai-nr-ue +metadata: name: oai-nr-ue namespace: oai-ue - annotations: - internal.kpt.dev/upstream-identifier: 'apps|Deployment|oai-ue|oai-nr-ue' spec: replicas: 1 selector: @@ -33,7 +31,11 @@ spec: env: - name: USE_ADDITIONAL_OPTIONS # oai-gnb-du.oai-ran-du - value: "--sa --rfsim --rfsimulator.serveraddr 192.168.1.21 --log_config.global_log_options level,nocolor,time -r 51 --numerology 1 -C 3450720000 --ssb 186 --uicc0.imsi 001010000000100" + value: "--sa --rfsim --log_config.global_log_options level,nocolor,time -r 51 --numerology 1 -C 3450720000 --ssb 186 --uicc0.imsi 001010000000100" + command: ["/bin/bash", "-c"] + args: + - RFSIM_IP_ADDRESS=$(getent hosts oai-gnb-du.oai-ran-du | awk '{print $1}'); + exec /opt/oai-nr-ue/bin/nr-uesoftmodem -O /opt/oai-nr-ue/etc/nr-ue.conf $USE_ADDITIONAL_OPTIONS --rfsimulator.serveraddr $RFSIM_IP_ADDRESS; volumeMounts: - mountPath: /opt/oai-nr-ue/etc/nr-ue.conf name: configuration diff --git a/test-infra/oai-ue/40Mhz/uedeployment.yaml b/test-infra/oai-ue/40Mhz/uedeployment.yaml index 09f2522..7085f8b 100644 --- a/test-infra/oai-ue/40Mhz/uedeployment.yaml +++ b/test-infra/oai-ue/40Mhz/uedeployment.yaml @@ -1,10 +1,8 @@ apiVersion: apps/v1 kind: Deployment -metadata: # kpt-merge: oai-ue/oai-nr-ue +metadata: name: oai-nr-ue namespace: oai-ue - annotations: - internal.kpt.dev/upstream-identifier: 'apps|Deployment|oai-ue|oai-nr-ue' spec: replicas: 1 selector: @@ -33,7 +31,11 @@ spec: env: - name: USE_ADDITIONAL_OPTIONS # oai-gnb-du.oai-ran-du - value: "--sa -E --rfsimulator.serveraddr 192.168.1.21 --rfsim --log_config.global_log_options level,nocolor,time -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000100 " + value: "--sa -E --rfsim --log_config.global_log_options level,nocolor,time -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000100 " + command: ["/bin/bash", "-c"] + args: + - RFSIM_IP_ADDRESS=$(getent hosts oai-gnb-du.oai-ran-du | awk '{print $1}'); + exec /opt/oai-nr-ue/bin/nr-uesoftmodem -O /opt/oai-nr-ue/etc/nr-ue.conf $USE_ADDITIONAL_OPTIONS --rfsimulator.serveraddr $RFSIM_IP_ADDRESS; volumeMounts: - mountPath: /opt/oai-nr-ue/etc/nr-ue.conf name: configuration