github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/vmo/controller.go (about)

     1  // Copyright (C) 2020, 2022, 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 vmo
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"reflect"
    11  	"time"
    12  
    13  	"github.com/verrazzano/pkg/diff"
    14  	vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    15  	clientset "github.com/verrazzano/verrazzano-monitoring-operator/pkg/client/clientset/versioned"
    16  	clientsetscheme "github.com/verrazzano/verrazzano-monitoring-operator/pkg/client/clientset/versioned/scheme"
    17  	informers "github.com/verrazzano/verrazzano-monitoring-operator/pkg/client/informers/externalversions"
    18  	listers "github.com/verrazzano/verrazzano-monitoring-operator/pkg/client/listers/vmcontroller/v1"
    19  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/config"
    20  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    21  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/metricsexporter"
    22  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/opensearch"
    23  	dashboards "github.com/verrazzano/verrazzano-monitoring-operator/pkg/opensearch_dashboards"
    24  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/signals"
    25  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/upgrade"
    26  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/logs/vzlog"
    27  	"go.uber.org/zap"
    28  	corev1 "k8s.io/api/core/v1"
    29  	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/apimachinery/pkg/util/runtime"
    33  	"k8s.io/apimachinery/pkg/util/wait"
    34  	kubeinformers "k8s.io/client-go/informers"
    35  	"k8s.io/client-go/kubernetes"
    36  	"k8s.io/client-go/kubernetes/scheme"
    37  	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    38  	appslistersv1 "k8s.io/client-go/listers/apps/v1"
    39  	corelistersv1 "k8s.io/client-go/listers/core/v1"
    40  	netlistersv1 "k8s.io/client-go/listers/networking/v1"
    41  	rbacv1listers1 "k8s.io/client-go/listers/rbac/v1"
    42  	storagelisters1 "k8s.io/client-go/listers/storage/v1"
    43  	"k8s.io/client-go/tools/cache"
    44  	"k8s.io/client-go/tools/clientcmd"
    45  	"k8s.io/client-go/tools/record"
    46  	"k8s.io/client-go/util/workqueue"
    47  )
    48  
    49  const controllerAgentName = "vmo-controller"
    50  
    51  // Controller is the controller implementation for VMO resources
    52  type Controller struct {
    53  	// kubeclientset is a standard kubernetes clientset
    54  	kubeclientset kubernetes.Interface
    55  	// vmoclientset is a clientset for our own API group
    56  	vmoclientset     clientset.Interface
    57  	kubeextclientset apiextensionsclient.Interface
    58  
    59  	// listers and syncs
    60  	clusterRoleLister    rbacv1listers1.ClusterRoleLister
    61  	clusterRolesSynced   cache.InformerSynced
    62  	configMapLister      corelistersv1.ConfigMapLister
    63  	configMapsSynced     cache.InformerSynced
    64  	deploymentLister     appslistersv1.DeploymentLister
    65  	deploymentsSynced    cache.InformerSynced
    66  	ingressLister        netlistersv1.IngressLister
    67  	ingressesSynced      cache.InformerSynced
    68  	nodeLister           corelistersv1.NodeLister
    69  	nodesSynced          cache.InformerSynced
    70  	pvcLister            corelistersv1.PersistentVolumeClaimLister
    71  	pvcsSynced           cache.InformerSynced
    72  	roleBindingLister    rbacv1listers1.RoleBindingLister
    73  	roleBindingsSynced   cache.InformerSynced
    74  	secretLister         corelistersv1.SecretLister
    75  	secretsSynced        cache.InformerSynced
    76  	serviceLister        corelistersv1.ServiceLister
    77  	servicesSynced       cache.InformerSynced
    78  	statefulSetLister    appslistersv1.StatefulSetLister
    79  	statefulSetsSynced   cache.InformerSynced
    80  	vmoLister            listers.VerrazzanoMonitoringInstanceLister
    81  	vmosSynced           cache.InformerSynced
    82  	storageClassLister   storagelisters1.StorageClassLister
    83  	storageClassesSynced cache.InformerSynced
    84  
    85  	// misc
    86  	namespace      string
    87  	watchNamespace string
    88  	watchVmi       string
    89  	buildVersion   string
    90  	stopCh         <-chan struct{}
    91  
    92  	// multi-cluster
    93  	clusterInfo ClusterInfo
    94  
    95  	// config
    96  	operatorConfigMapName string
    97  	operatorConfig        *config.OperatorConfig
    98  	latestConfigMap       *corev1.ConfigMap
    99  
   100  	// workqueue is a rate limited work queue. This is used to queue work to be
   101  	// processed instead of performing it as soon as a change happens. This
   102  	// means we can ensure we only process a fixed amount of resources at a
   103  	// time, and makes it easy to ensure we are never processing the same item
   104  	// simultaneously in two different workers.
   105  	workqueue workqueue.RateLimitingInterface
   106  	// lastEnqueue is the timestamp of when the last element was added to the queue
   107  	lastEnqueue time.Time
   108  	// recorder is an event recorder for recording Event resources to the
   109  	// Kubernetes API.
   110  	recorder record.EventRecorder
   111  
   112  	// VerrazzanoLogger is used to log
   113  	log vzlog.VerrazzanoLogger
   114  
   115  	// OpenSearch Client
   116  	osClient *opensearch.OSClient
   117  
   118  	// OpenSearchDashboards Client
   119  	osDashboardsClient *dashboards.OSDashboardsClient
   120  
   121  	indexUpgradeMonitor *upgrade.Monitor
   122  }
   123  
   124  // ClusterInfo has info like ContainerRuntime and managed cluster name
   125  type ClusterInfo struct {
   126  	clusterName      string
   127  	KeycloakURL      string
   128  	KeycloakCABundle []byte
   129  }
   130  
   131  // NewController returns a new vmo controller
   132  func NewController(namespace string, configmapName string, buildVersion string, kubeconfig string, masterURL string, watchNamespace string, watchVmi string) (*Controller, error) {
   133  
   134  	zap.S().Debugw("Building config")
   135  	cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
   136  	if err != nil {
   137  		zap.S().Fatalf("Error building kubeconfig: %v", err)
   138  	}
   139  
   140  	zap.S().Debugw("Building kubernetes clientset")
   141  	kubeclientset, err := kubernetes.NewForConfig(cfg)
   142  	if err != nil {
   143  		zap.S().Fatalf("Error building kubernetes clientset: %v", err)
   144  	}
   145  
   146  	zap.S().Debugw("Building vmo clientset")
   147  	vmoclientset, err := clientset.NewForConfig(cfg)
   148  	if err != nil {
   149  		zap.S().Fatalf("Error building vmo clientset: %v", err)
   150  	}
   151  
   152  	zap.S().Debugw("Building api extensions clientset")
   153  	kubeextclientset, err := apiextensionsclient.NewForConfig(cfg)
   154  	if err != nil {
   155  		zap.S().Fatalf("Error building apiextensions-apiserver clientset: %v", err)
   156  	}
   157  
   158  	// Get the config from the ConfigMap
   159  	zap.S().Debugw("Loading ConfigMap ", configmapName)
   160  
   161  	operatorConfigMap, err := kubeclientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configmapName, metav1.GetOptions{})
   162  	if err != nil {
   163  		zap.S().Fatalf("No configuration ConfigMap called %s found in namespace %s.", configmapName, namespace)
   164  	}
   165  	zap.S().Debugf("Building config from ConfigMap %s", configmapName)
   166  	operatorConfig, err := config.NewConfigFromConfigMap(operatorConfigMap)
   167  	if err != nil {
   168  		zap.S().Fatalf("Error building verrazzano-monitoring-operator config from config map: %s", err.Error())
   169  	}
   170  
   171  	var kubeInformerFactory kubeinformers.SharedInformerFactory
   172  	var vmoInformerFactory informers.SharedInformerFactory
   173  	if watchNamespace == "" {
   174  		// Consider all namespaces if our namespace is left wide open our set to default
   175  		kubeInformerFactory = kubeinformers.NewSharedInformerFactory(kubeclientset, constants.ResyncPeriod)
   176  		vmoInformerFactory = informers.NewSharedInformerFactory(vmoclientset, constants.ResyncPeriod)
   177  	} else {
   178  		// Otherwise, restrict to a specific namespace
   179  		kubeInformerFactory = kubeinformers.NewSharedInformerFactoryWithOptions(kubeclientset, constants.ResyncPeriod, kubeinformers.WithNamespace(watchNamespace), kubeinformers.WithTweakListOptions(nil))
   180  		vmoInformerFactory = informers.NewSharedInformerFactoryWithOptions(vmoclientset, constants.ResyncPeriod, informers.WithNamespace(watchNamespace), informers.WithTweakListOptions(nil))
   181  	}
   182  
   183  	// obtain references to shared index informers for the Deployment and VMO
   184  	// types.
   185  	clusterRoleInformer := kubeInformerFactory.Rbac().V1().ClusterRoles()
   186  	configmapInformer := kubeInformerFactory.Core().V1().ConfigMaps()
   187  	deploymentInformer := kubeInformerFactory.Apps().V1().Deployments()
   188  	ingressInformer := kubeInformerFactory.Networking().V1().Ingresses()
   189  	nodeInformer := kubeInformerFactory.Core().V1().Nodes()
   190  	pvcInformer := kubeInformerFactory.Core().V1().PersistentVolumeClaims()
   191  	roleBindingInformer := kubeInformerFactory.Rbac().V1().RoleBindings()
   192  	secretsInformer := kubeInformerFactory.Core().V1().Secrets()
   193  	serviceInformer := kubeInformerFactory.Core().V1().Services()
   194  	statefulSetInformer := kubeInformerFactory.Apps().V1().StatefulSets()
   195  	vmoInformer := vmoInformerFactory.Verrazzano().V1().VerrazzanoMonitoringInstances()
   196  	storageClassInformer := kubeInformerFactory.Storage().V1().StorageClasses()
   197  	// Create event broadcaster
   198  	// Add vmo-controller types to the default Kubernetes Scheme so Events can be
   199  	// logged for vmo-controller types.
   200  	if err := clientsetscheme.AddToScheme(scheme.Scheme); err != nil {
   201  		zap.S().Warnf("error adding scheme: %+v", err)
   202  	}
   203  	zap.S().Infow("Creating event broadcaster")
   204  	eventBroadcaster := record.NewBroadcaster()
   205  	eventBroadcaster.StartLogging(zap.S().Infof)
   206  	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
   207  	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
   208  	statefulSetLister := statefulSetInformer.Lister()
   209  
   210  	zap.S().Infow("Creating OpenSearch client")
   211  	osClient := opensearch.NewOSClient(statefulSetLister)
   212  
   213  	zap.S().Infow("Creating OpenSearchDashboards client")
   214  	osDashboardsClient := dashboards.NewOSDashboardsClient()
   215  
   216  	controller := &Controller{
   217  		namespace:        namespace,
   218  		watchNamespace:   watchNamespace,
   219  		watchVmi:         watchVmi,
   220  		kubeclientset:    kubeclientset,
   221  		vmoclientset:     vmoclientset,
   222  		kubeextclientset: kubeextclientset,
   223  
   224  		clusterRoleLister:     clusterRoleInformer.Lister(),
   225  		clusterRolesSynced:    clusterRoleInformer.Informer().HasSynced,
   226  		configMapLister:       configmapInformer.Lister(),
   227  		configMapsSynced:      configmapInformer.Informer().HasSynced,
   228  		deploymentLister:      deploymentInformer.Lister(),
   229  		deploymentsSynced:     deploymentInformer.Informer().HasSynced,
   230  		ingressLister:         ingressInformer.Lister(),
   231  		ingressesSynced:       ingressInformer.Informer().HasSynced,
   232  		nodeLister:            nodeInformer.Lister(),
   233  		nodesSynced:           nodeInformer.Informer().HasSynced,
   234  		pvcLister:             pvcInformer.Lister(),
   235  		pvcsSynced:            pvcInformer.Informer().HasSynced,
   236  		roleBindingLister:     roleBindingInformer.Lister(),
   237  		roleBindingsSynced:    roleBindingInformer.Informer().HasSynced,
   238  		secretLister:          secretsInformer.Lister(),
   239  		secretsSynced:         secretsInformer.Informer().HasSynced,
   240  		serviceLister:         serviceInformer.Lister(),
   241  		servicesSynced:        serviceInformer.Informer().HasSynced,
   242  		statefulSetLister:     statefulSetLister,
   243  		statefulSetsSynced:    statefulSetInformer.Informer().HasSynced,
   244  		vmoLister:             vmoInformer.Lister(),
   245  		vmosSynced:            vmoInformer.Informer().HasSynced,
   246  		storageClassLister:    storageClassInformer.Lister(),
   247  		storageClassesSynced:  storageClassInformer.Informer().HasSynced,
   248  		workqueue:             workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "VMOs"),
   249  		recorder:              recorder,
   250  		buildVersion:          buildVersion,
   251  		operatorConfigMapName: configmapName,
   252  		operatorConfig:        operatorConfig,
   253  		latestConfigMap:       operatorConfigMap,
   254  		clusterInfo:           ClusterInfo{},
   255  		log:                   vzlog.DefaultLogger(),
   256  		osClient:              osClient,
   257  		osDashboardsClient:    osDashboardsClient,
   258  		indexUpgradeMonitor:   &upgrade.Monitor{},
   259  	}
   260  
   261  	zap.S().Infow("Setting up event handlers")
   262  
   263  	// Set up an event handler for when VMO resources change
   264  	vmoInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   265  		AddFunc: controller.enqueueVMO,
   266  		UpdateFunc: func(old, new interface{}) {
   267  			controller.enqueueVMO(new)
   268  		},
   269  	})
   270  
   271  	// Create watchers on the operator ConfigMap, which may signify a need to reload our config
   272  	configMapInformer := kubeInformerFactory.Core().V1().ConfigMaps()
   273  	configMapInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   274  		UpdateFunc: func(old, new interface{}) {
   275  			newConfigMap := new.(*corev1.ConfigMap)
   276  			// If the configMap has changed from our last known copy, process it
   277  			if newConfigMap.Name == controller.operatorConfigMapName && !reflect.DeepEqual(newConfigMap.Data, controller.latestConfigMap.Data) {
   278  				zap.S().Infof("Reloading config...")
   279  				newOperatorConfig, err := config.NewConfigFromConfigMap(newConfigMap)
   280  				if err != nil {
   281  					zap.S().Errorf("Errors processing config updates - so we're staying at current configuration: %s", err)
   282  				} else {
   283  					zap.S().Infof("Successfully reloaded config")
   284  					controller.operatorConfig = newOperatorConfig
   285  					controller.latestConfigMap = newConfigMap
   286  				}
   287  			}
   288  		},
   289  	})
   290  
   291  	// set up signals so we handle the first shutdown signal gracefully
   292  	zap.S().Debugw("Setting up signals")
   293  	controller.stopCh = signals.SetupSignalHandler()
   294  
   295  	go kubeInformerFactory.Start(controller.stopCh)
   296  	go vmoInformerFactory.Start(controller.stopCh)
   297  
   298  	return controller, nil
   299  }
   300  
   301  // Run will set up the event handlers for types we are interested in, as well
   302  // as syncing informer caches and starting workers. It will block until stopCh
   303  // is closed, at which point it will shutdown the workqueue and wait for
   304  // workers to finish processing their current work items.
   305  func (c *Controller) Run(threadiness int) error {
   306  	defer runtime.HandleCrash()
   307  	defer c.workqueue.ShutDown()
   308  
   309  	// Start the informer factories to begin populating the informer caches
   310  	zap.S().Infow("Starting VMO controller")
   311  
   312  	// Wait for the caches to be synced before starting workers
   313  	zap.S().Infow("Waiting for informer caches to sync")
   314  	if ok := cache.WaitForCacheSync(c.stopCh, c.clusterRolesSynced, c.configMapsSynced,
   315  		c.deploymentsSynced, c.ingressesSynced, c.nodesSynced, c.pvcsSynced, c.roleBindingsSynced, c.secretsSynced,
   316  		c.servicesSynced, c.statefulSetsSynced, c.vmosSynced, c.storageClassesSynced); !ok {
   317  		return errors.New("failed to wait for caches to sync")
   318  	}
   319  
   320  	zap.S().Infow("Starting workers")
   321  	// Launch two workers to process VMO resources
   322  	for i := 0; i < threadiness; i++ {
   323  		go wait.Until(c.runWorker, time.Second, c.stopCh)
   324  	}
   325  
   326  	zap.S().Infow("Started workers")
   327  	<-c.stopCh
   328  	zap.S().Infow("Shutting down workers")
   329  
   330  	return nil
   331  }
   332  
   333  // runWorker is a long-running function that will continually call the
   334  // processNextWorkItem function in order to read and process a message on the
   335  // workqueue.
   336  func (c *Controller) runWorker() {
   337  	for c.processNextWorkItem() {
   338  	}
   339  }
   340  
   341  // processNextWorkItem will read a single work item off the workqueue and
   342  // attempt to process it, by calling the syncHandler.
   343  func (c *Controller) processNextWorkItem() bool {
   344  	obj, shutdown := c.workqueue.Get()
   345  
   346  	if shutdown {
   347  		return false
   348  	}
   349  
   350  	// We wrap this block in a func so we can defer c.workqueue.Done.
   351  	err := func(obj interface{}) error {
   352  		// We call Done here so the workqueue knows we have finished
   353  		// processing this item. We also must remember to call Forget if we
   354  		// do not want this work item being re-queued. For example, we do
   355  		// not call Forget if a transient error occurs, instead the item is
   356  		// put back on the workqueue and attempted again after a back-off
   357  		// period.
   358  		defer c.workqueue.Done(obj)
   359  		var key string
   360  		var ok bool
   361  		// We expect strings to come off the workqueue. These are of the
   362  		// form namespace/name. We do this as the delayed nature of the
   363  		// workqueue means the items in the informer cache may actually be
   364  		// more up to date that when the item was initially put onto the
   365  		// workqueue.
   366  		if key, ok = obj.(string); !ok {
   367  			// As the item in the workqueue is actually invalid, we call
   368  			// Forget here else we'd go into a loop of attempting to
   369  			// process a work item that is invalid.
   370  			c.workqueue.Forget(obj)
   371  			runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
   372  			return nil
   373  		}
   374  		// Run the syncHandler, passing it the namespace/name string of the
   375  		// VMO resource to be synced.
   376  		if err := c.syncHandler(key); err != nil {
   377  			return fmt.Errorf("error syncing '%s': %s", key, err.Error())
   378  		}
   379  		// Finally, if no error occurs we Forget this item so it does not
   380  		// get queued again until another change happens.
   381  		c.workqueue.Forget(obj)
   382  		return nil
   383  	}(obj)
   384  
   385  	if err != nil {
   386  		runtime.HandleError(err)
   387  		return true
   388  	}
   389  
   390  	return true
   391  }
   392  
   393  // Process an update to a VMO
   394  func (c *Controller) syncHandler(key string) error {
   395  	// Convert the namespace/name string into a distinct namespace and name
   396  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   397  	if err != nil {
   398  		runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))
   399  		return err
   400  	}
   401  	if c.watchVmi != "" && c.watchVmi != name {
   402  		return nil
   403  	}
   404  
   405  	// Get the VMO resource with this namespace/name
   406  	vmo, err := c.vmoLister.VerrazzanoMonitoringInstances(namespace).Get(name)
   407  	if err != nil {
   408  		runtime.HandleError(fmt.Errorf("error getting VMO %s in namespace %s: %v", name, namespace, err))
   409  		return err
   410  	}
   411  
   412  	// Get the resource logger needed to log message using 'progress' and 'once' methods
   413  	log, err := vzlog.EnsureResourceLogger(&vzlog.ResourceConfig{
   414  		Name:           vmo.Name,
   415  		Namespace:      vmo.Namespace,
   416  		ID:             string(vmo.UID),
   417  		Generation:     vmo.Generation,
   418  		ControllerName: "vmi",
   419  	})
   420  	if err != nil {
   421  		zap.S().Errorf("Failed to create controller logger for VMO controller", err)
   422  	}
   423  	c.log = log
   424  
   425  	log.Progressf("Reconciling vmi resource %v, generation %v", types.NamespacedName{Namespace: vmo.Namespace, Name: vmo.Name}, vmo.Generation)
   426  	return c.syncHandlerStandardMode(vmo)
   427  }
   428  
   429  // In Standard Mode, we compare the actual state with the desired, and attempt to
   430  // converge the two.  We then update the Status block of the VMO resource
   431  // with the current status.
   432  func (c *Controller) syncHandlerStandardMode(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error {
   433  	var errorObserved bool
   434  	functionMetric, functionError := metricsexporter.GetFunctionMetrics(metricsexporter.NamesReconcile)
   435  	if functionError == nil {
   436  		functionMetric.LogStart()
   437  		defer func() { functionMetric.LogEnd(errorObserved) }()
   438  	} else {
   439  		return functionError
   440  	}
   441  
   442  	originalVMO := vmo.DeepCopy()
   443  
   444  	// populate clusterInfo
   445  	clusterSecret, err := c.secretLister.Secrets(constants.VerrazzanoSystemNamespace).Get(constants.MCRegistrationSecret)
   446  	if err == nil {
   447  		c.clusterInfo.clusterName = string(clusterSecret.Data[constants.ClusterNameData])
   448  		c.clusterInfo.KeycloakURL = string(clusterSecret.Data[constants.KeycloakURLData])
   449  		c.clusterInfo.KeycloakCABundle = clusterSecret.Data[constants.KeycloakCABundleData]
   450  	}
   451  
   452  	// If lock, controller will not sync/process the VMO env
   453  	if vmo.Spec.Lock {
   454  		c.log.Progressf("[%s/%s] Lock is set to true, this VMO env will not be synced/processed.", vmo.Name, vmo.Namespace)
   455  		return nil
   456  	}
   457  
   458  	/*********************
   459  	 * Initialize VMO Spec
   460  	 **********************/
   461  	InitializeVMOSpec(c, vmo)
   462  
   463  	errorObserved = false
   464  
   465  	/***************************************
   466  	 * Configure Index AutoExpand settings
   467  	 ****************************************/
   468  	autoExpandIndexChannel := c.osClient.SetAutoExpandIndices(vmo)
   469  
   470  	/*********************
   471  	 * Configure ISM
   472  	 **********************/
   473  	ismChannel := c.osClient.ConfigureISM(vmo)
   474  
   475  	/********************************************
   476  	 * Migrate old indices if any to data streams
   477  	*********************************************/
   478  	err = c.indexUpgradeMonitor.MigrateOldIndices(c.log, vmo, c.osClient, c.osDashboardsClient)
   479  	if err != nil {
   480  		c.log.ErrorfThrottled("Failed to migrate old indices to data stream: %v", err)
   481  		errorObserved = true
   482  	}
   483  
   484  	/*********************
   485  	 * Create RoleBindings
   486  	 **********************/
   487  	err = CreateRoleBindings(c, vmo)
   488  	if err != nil {
   489  		c.log.ErrorfThrottled("Failed to create Role Bindings for VMI %s: %v", vmo.Name, err)
   490  		errorObserved = true
   491  	}
   492  
   493  	/*********************
   494  	* Create configmaps
   495  	**********************/
   496  	err = CreateConfigmaps(c, vmo)
   497  	if err != nil {
   498  		c.log.ErrorfThrottled("Failed to create configmaps for VMI %s: %v", vmo.Name, err)
   499  		errorObserved = true
   500  	}
   501  
   502  	/*********************
   503  	 * Create Services
   504  	 **********************/
   505  	err = CreateServices(c, vmo)
   506  	if err != nil {
   507  		c.log.ErrorfThrottled("Failed to create Services for VMI %s: %v", vmo.Name, err)
   508  		errorObserved = true
   509  	}
   510  
   511  	/*********************
   512  	 * Create Persistent Volume Claims
   513  	 **********************/
   514  	pvcToAdMap, err := CreatePersistentVolumeClaims(c, vmo)
   515  	if err != nil {
   516  		c.log.ErrorfThrottled("Failed to create/update PVCs for VMI %s: %v", vmo.Name, err)
   517  		errorObserved = true
   518  	}
   519  
   520  	/*********************
   521  	 * Create StatefulSets
   522  	 **********************/
   523  	existingCluster, err := CreateStatefulSets(c, vmo)
   524  	if err != nil {
   525  		c.log.ErrorfThrottled("Failed to create/update statefulsets for VMI %s: %v", vmo.Name, err)
   526  		errorObserved = true
   527  	}
   528  
   529  	/*********************
   530  	 * Create Deployments
   531  	 **********************/
   532  	var deploymentsDirty bool
   533  	if !errorObserved {
   534  		deploymentsDirty, err = CreateDeployments(c, vmo, pvcToAdMap, existingCluster)
   535  		if err != nil {
   536  			c.log.ErrorfThrottled("Failed to create/update deployments for VMI %s: %v", vmo.Name, err)
   537  			functionMetric.IncError()
   538  			errorObserved = true
   539  		}
   540  	}
   541  	/*********************
   542  	 * Create Ingresses
   543  	 **********************/
   544  	err = CreateIngresses(c, vmo)
   545  	if err != nil {
   546  		c.log.ErrorfThrottled("Failed to create Ingresses for VMI %s: %v", vmo.Name, err)
   547  		errorObserved = true
   548  	}
   549  
   550  	/*********************
   551  	* Update VMO itself (if necessary, if anything has changed)
   552  	**********************/
   553  	specDiffs := diff.Diff(originalVMO, vmo)
   554  	if specDiffs != "" {
   555  		c.log.Debugf("Acquired lock in namespace: %s", vmo.Namespace)
   556  		c.log.Debugf("VMO %s : Spec differences %s", vmo.Name, specDiffs)
   557  		c.log.Oncef("Updating VMO")
   558  		metric, err := metricsexporter.GetCounterMetrics(metricsexporter.NamesVMOUpdate)
   559  		if err != nil {
   560  			return err
   561  		}
   562  		metric.Inc()
   563  		_, err = c.vmoclientset.VerrazzanoV1().VerrazzanoMonitoringInstances(vmo.Namespace).Update(context.TODO(), vmo, metav1.UpdateOptions{})
   564  		if err != nil {
   565  			c.log.Errorf("Failed to update status for VMI %s: %v", vmo.Name, err)
   566  			errorObserved = true
   567  		}
   568  	}
   569  
   570  	autoExpandIndexErr := <-autoExpandIndexChannel
   571  	if autoExpandIndexErr != nil {
   572  		c.log.ErrorfThrottled("Failed to update auto expand settings for indices: %v", autoExpandIndexErr)
   573  		errorObserved = true
   574  	}
   575  
   576  	ismErr := <-ismChannel
   577  	if ismErr != nil {
   578  		c.log.ErrorfThrottled("Failed to configure ISM Policies: %v", ismErr)
   579  		errorObserved = true
   580  	}
   581  
   582  	if !errorObserved && !deploymentsDirty && len(c.buildVersion) > 0 && vmo.Spec.Versioning.CurrentVersion != c.buildVersion {
   583  		// The spec.versioning.currentVersion field should not be updated to the new value until a sync produces no
   584  		// changes.  This allows observers (e.g. the controlled rollout scripts used to put new versions of operator
   585  		// into production) to know when a given vmo has been (mostly) updated, and thus when it's relatively safe to
   586  		// start checking various aspects of the vmo for health.
   587  		vmo.Spec.Versioning.CurrentVersion = c.buildVersion
   588  		_, err = c.vmoclientset.VerrazzanoV1().VerrazzanoMonitoringInstances(vmo.Namespace).Update(context.TODO(), vmo, metav1.UpdateOptions{})
   589  		if err != nil {
   590  			c.log.Errorf("Failed to update currentVersion for VMI %s: %v", vmo.Name, err)
   591  		} else {
   592  			c.log.Oncef("Updated VMI currentVersion to %s", c.buildVersion)
   593  			timeMetric, timeErr := metricsexporter.GetTimestampMetrics(metricsexporter.NamesVMOUpdate)
   594  			if timeErr != nil {
   595  				return timeErr
   596  			}
   597  			timeMetric.SetLastTime()
   598  		}
   599  	}
   600  
   601  	// Create a Hash on vmo/Status object to identify changes to vmo spec
   602  	hash, err := vmo.Hash()
   603  	if err != nil {
   604  		c.log.Errorf("Error getting VMO hash: %v", err)
   605  	}
   606  	if vmo.Status.Hash != hash {
   607  		vmo.Status.Hash = hash
   608  	}
   609  
   610  	c.log.Oncef("Successfully synced VMI'%s/%s'", vmo.Namespace, vmo.Name)
   611  	return nil
   612  }
   613  
   614  // enqueueVMO takes a VMO resource and converts it into a namespace/name
   615  // string which is then put onto the work queue. This method should *not* be
   616  // passed resources of any type other than VMO.
   617  func (c *Controller) enqueueVMO(obj interface{}) {
   618  	var key string
   619  	var err error
   620  	if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
   621  		runtime.HandleError(err)
   622  		return
   623  	}
   624  
   625  	c.workqueue.AddRateLimited(key)
   626  	c.lastEnqueue = time.Now()
   627  }
   628  
   629  // IsHealthy returns true if this controller is healthy, false otherwise. It's health is determined based on: (1) its
   630  // workqueue is 0 or decreasing in a timely manner, (2) it can communicate with API server, and (3) the CRD exists.
   631  func (c *Controller) IsHealthy() bool {
   632  	metric, err := metricsexporter.GetGaugeMetrics(metricsexporter.NamesQueue)
   633  	if err != nil {
   634  		zap.S().Error("Unable to retrieve simple gauge metric in isHealthy function")
   635  	} else {
   636  		metric.Set(float64(c.workqueue.Len()))
   637  	}
   638  	// Make sure if workqueue > 0, make sure it hasn't remained for longer than 60 seconds.
   639  	if startQueueLen := c.workqueue.Len(); startQueueLen > 0 {
   640  		if time.Since(c.lastEnqueue).Seconds() > float64(60) {
   641  			return false
   642  		}
   643  	}
   644  
   645  	// Make sure the controller can talk to the API server and its CRD is defined.
   646  	crds, err := c.kubeextclientset.ApiextensionsV1().CustomResourceDefinitions().List(context.TODO(), metav1.ListOptions{})
   647  	// Error getting CRD from API server
   648  	if err != nil {
   649  		return false
   650  	}
   651  	// No CRDs defined
   652  	if len(crds.Items) == 0 {
   653  		return false
   654  	}
   655  	crdExists := false
   656  	for _, crd := range crds.Items {
   657  		if crd.Name == constants.VMOFullname {
   658  			crdExists = true
   659  		}
   660  	}
   661  	return crdExists
   662  }