github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/containerizedworkload/containerizedworkload_controller.go (about) 1 // Copyright (c) 2021, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package containerizedworkload 5 6 import ( 7 "context" 8 errors "errors" 9 10 "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 11 "github.com/verrazzano/verrazzano/application-operator/controllers/appconfig" 12 "github.com/verrazzano/verrazzano/application-operator/controllers/clusters" 13 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 14 vzlogInit "github.com/verrazzano/verrazzano/pkg/log" 15 vzlog2 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 16 "go.uber.org/zap" 17 appsv1 "k8s.io/api/apps/v1" 18 corev1 "k8s.io/api/core/v1" 19 "k8s.io/apimachinery/pkg/labels" 20 "k8s.io/apimachinery/pkg/selection" 21 "k8s.io/apimachinery/pkg/types" 22 23 oamv1 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 24 "k8s.io/apimachinery/pkg/runtime" 25 ctrl "sigs.k8s.io/controller-runtime" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 ) 29 30 type Reconciler struct { 31 client.Client 32 Log *zap.SugaredLogger 33 Scheme *runtime.Scheme 34 } 35 36 const controllerName = "containerizedworkload" 37 38 // SetupWithManager registers our controller with the manager 39 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { 40 return ctrl.NewControllerManagedBy(mgr). 41 For(&oamv1.ContainerizedWorkload{}). 42 Complete(r) 43 } 44 45 // Reconcile checks restart version annotations on an ContainerizedWorkload and 46 // restarts as needed. 47 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 48 if ctx == nil { 49 return ctrl.Result{}, errors.New("context cannot be nil") 50 } 51 52 // We do not want any resource to get reconciled if it is in namespace kube-system 53 // This is due to a bug found in OKE, it should not affect functionality of any vz operators 54 // If this is the case then return success 55 if req.Namespace == vzconst.KubeSystem { 56 log := zap.S().With(vzlogInit.FieldResourceNamespace, req.Namespace, vzlogInit.FieldResourceName, req.Name, vzlogInit.FieldController, controllerName) 57 log.Infof("Containerized workload resource %v should not be reconciled in kube-system namespace, ignoring", req.NamespacedName) 58 return reconcile.Result{}, nil 59 } 60 61 var workload oamv1.ContainerizedWorkload 62 if err := r.Client.Get(ctx, req.NamespacedName, &workload); err != nil { 63 return clusters.IgnoreNotFoundWithLog(err, zap.S()) 64 } 65 log, err := clusters.GetResourceLogger("containerizedworkload", req.NamespacedName, &workload) 66 if err != nil { 67 zap.S().Errorf("Failed to create controller logger for containerized workload resource: %v", err) 68 return clusters.NewRequeueWithDelay(), nil 69 } 70 log.Oncef("Reconciling containerized workload resource %v, generation %v", req.NamespacedName, workload.Generation) 71 72 res, err := r.doReconcile(ctx, workload, log) 73 if clusters.ShouldRequeue(res) { 74 return res, nil 75 } 76 // Never return an error since it has already been logged and we don't want the 77 // controller runtime to log again (with stack trace). Just re-queue if there is an error. 78 if err != nil { 79 return clusters.NewRequeueWithDelay(), nil 80 } 81 82 log.Oncef("Finished reconciling containerized workload %v", req.NamespacedName) 83 84 return ctrl.Result{}, nil 85 } 86 87 // doReconcile performs the reconciliation operations for the ContainerizedWorkload 88 func (r *Reconciler) doReconcile(ctx context.Context, workload oamv1.ContainerizedWorkload, log vzlog2.VerrazzanoLogger) (ctrl.Result, error) { 89 // Label the service with the OAM app and component, if the service exists. Errors will be logged in the method, we still 90 // need to process restart annotation even if there's an error 91 if err := r.updateServiceLabels(ctx, workload, log); err != nil { 92 return reconcile.Result{}, err 93 } 94 95 // get the user-specified restart version - if it's missing then there's nothing to do here 96 restartVersion, ok := workload.Annotations[vzconst.RestartVersionAnnotation] 97 if !ok || len(restartVersion) == 0 { 98 log.Debug("No restart version annotation found, nothing to do") 99 return reconcile.Result{}, nil 100 } 101 102 if err := r.restartWorkload(ctx, restartVersion, &workload, log); err != nil { 103 return reconcile.Result{}, err 104 } 105 106 return reconcile.Result{}, nil 107 } 108 109 func (r *Reconciler) restartWorkload(ctx context.Context, restartVersion string, workload *oamv1.ContainerizedWorkload, log vzlog2.VerrazzanoLogger) error { 110 log.Debugf("Marking container %s with restart-version %s", workload.Name, restartVersion) 111 var deploymentList appsv1.DeploymentList 112 componentNameReq, _ := labels.NewRequirement(oam.LabelAppComponent, selection.Equals, []string{workload.ObjectMeta.Labels[oam.LabelAppComponent]}) 113 appNameReq, _ := labels.NewRequirement(oam.LabelAppName, selection.Equals, []string{workload.ObjectMeta.Labels[oam.LabelAppName]}) 114 selector := labels.NewSelector() 115 selector = selector.Add(*componentNameReq, *appNameReq) 116 err := r.Client.List(ctx, &deploymentList, &client.ListOptions{Namespace: workload.Namespace, LabelSelector: selector}) 117 if err != nil { 118 return err 119 } 120 for index := range deploymentList.Items { 121 deployment := &deploymentList.Items[index] 122 if err := appconfig.DoRestartDeployment(ctx, r.Client, restartVersion, deployment, log); err != nil { 123 return err 124 } 125 } 126 return nil 127 } 128 129 // updateServiceLabels looks up the Service associated with the workload, and updates its OAM 130 // app and component labels if needed. Any errors will be logged and not returned since we don't want 131 // this to fail anything. 132 func (r *Reconciler) updateServiceLabels(ctx context.Context, workload oamv1.ContainerizedWorkload, log vzlog2.VerrazzanoLogger) error { 133 svc, err := r.getWorkloadService(ctx, workload, log) 134 if err != nil { 135 return err 136 } 137 if svc == nil { 138 return nil 139 } 140 if svc.Labels == nil { 141 svc.Labels = map[string]string{} 142 } 143 if svc.Labels[oam.LabelAppName] == workload.Labels[oam.LabelAppName] && 144 svc.Labels[oam.LabelAppComponent] == workload.Labels[oam.LabelAppComponent] { 145 // nothing to do, return 146 return nil 147 } 148 log.Infof("Updating service OAM app and component labels for %s/%s", svc.Namespace, svc.Name) 149 svc.Labels[oam.LabelAppName] = workload.Labels[oam.LabelAppName] 150 svc.Labels[oam.LabelAppComponent] = workload.Labels[oam.LabelAppComponent] 151 err = r.Update(ctx, svc) 152 if err != nil { 153 return log.ErrorfNewErr("Failed to update Service %s for ContainerizedWorkload %s/%s: %v", svc.Name, workload.Namespace, workload.Name, err) 154 } 155 return nil 156 } 157 158 // getWorkloadService retrieves the Service associated with the workload 159 func (r *Reconciler) getWorkloadService(ctx context.Context, workload oamv1.ContainerizedWorkload, log vzlog2.VerrazzanoLogger) (*corev1.Service, error) { 160 svcName := "" 161 for _, res := range workload.Status.Resources { 162 if res.Kind == "Service" && res.APIVersion == "v1" { 163 svcName = res.Name 164 } 165 } 166 if svcName == "" { 167 log.Progressf("Service does not exist in status of ContainerizedWorkload %s/%s", workload.Namespace, workload.Name) 168 return nil, nil 169 } 170 svc := corev1.Service{} 171 if err := r.Get(ctx, types.NamespacedName{Name: svcName, Namespace: workload.Namespace}, &svc); err != nil { 172 log.Errorf("Failed to retrieve Service %s for ContainerizedWorkload %s/%s: %v", svcName, workload.Namespace, workload.Name, err) 173 return nil, err 174 } 175 return &svc, nil 176 }