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  }