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 }