github.com/verrazzano/verrazzano@v1.7.0/platform-operator/internal/operatorinit/run_operator.go (about)

     1  // Copyright (c) 2022, 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 operatorinit
     5  
     6  import (
     7  	"context"
     8  	"github.com/pkg/errors"
     9  	moduleapi "github.com/verrazzano/verrazzano-modules/module-operator/apis/platform/v1alpha1"
    10  	"github.com/verrazzano/verrazzano-modules/module-operator/controllers/module"
    11  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    12  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    13  	"github.com/verrazzano/verrazzano/pkg/nginxutil"
    14  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    15  	"github.com/verrazzano/verrazzano/platform-operator/controllers/configmaps/components"
    16  	"github.com/verrazzano/verrazzano/platform-operator/controllers/configmaps/overrides"
    17  	opensearchcontroller "github.com/verrazzano/verrazzano/platform-operator/controllers/integration/opensearch"
    18  	modulehandlerfactory "github.com/verrazzano/verrazzano/platform-operator/controllers/module/component-handler/factory"
    19  	"github.com/verrazzano/verrazzano/platform-operator/controllers/secrets"
    20  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/registry"
    21  	verrazzancontroller "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/controller"
    22  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/healthcheck"
    23  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/mysqlcheck"
    24  	"github.com/verrazzano/verrazzano/platform-operator/namespacewatch"
    25  	"sigs.k8s.io/controller-runtime/pkg/controller"
    26  
    27  	"github.com/verrazzano/verrazzano/platform-operator/internal/config"
    28  	"github.com/verrazzano/verrazzano/platform-operator/metricsexporter"
    29  	"go.uber.org/zap"
    30  	corev1 "k8s.io/api/core/v1"
    31  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/client-go/kubernetes"
    35  	"os"
    36  	controllerruntime "sigs.k8s.io/controller-runtime"
    37  	"strings"
    38  	"time"
    39  )
    40  
    41  const vpoHelmChartConfigMapName = "vpo-helm-chart"
    42  
    43  // StartPlatformOperator Platform operator execution entry point
    44  func StartPlatformOperator(vzconfig config.OperatorConfig, log *zap.SugaredLogger, scheme *runtime.Scheme) error {
    45  	// Determine NGINX namespace before initializing components
    46  	ingressNGINXNamespace, err := nginxutil.DetermineNamespaceForIngressNGINX(vzlog.DefaultLogger())
    47  	if err != nil {
    48  		return err
    49  	}
    50  	nginxutil.SetIngressNGINXNamespace(ingressNGINXNamespace)
    51  
    52  	if err := CreateVerrazzanoVersionsConfigMap(context.Background()); err != nil {
    53  		return err
    54  	}
    55  
    56  	registry.InitRegistry()
    57  	metricsexporter.Init()
    58  
    59  	chartDir := config.GetHelmVPOChartsDir()
    60  	files, err := os.ReadDir(chartDir)
    61  	if err != nil {
    62  		return errors.Wrap(err, "Failed to read the verrazzano-platform-operator helm chart directory")
    63  	}
    64  	vpoHelmChartConfigMap := &corev1.ConfigMap{
    65  		ObjectMeta: metav1.ObjectMeta{
    66  			Name:      vpoHelmChartConfigMapName,
    67  			Namespace: constants.VerrazzanoInstallNamespace,
    68  		},
    69  	}
    70  	err = generateConfigMapFromHelmChartFiles(chartDir, "", files, vpoHelmChartConfigMap)
    71  	if err != nil {
    72  		return errors.Wrap(err, "Failed to generate config map containing the verrazzano-platform-operator helm chart")
    73  	}
    74  	kubeClient, err := k8sutil.GetKubernetesClientset()
    75  	if err != nil {
    76  		return err
    77  	}
    78  	err = createVPOHelmChartConfigMap(kubeClient, vpoHelmChartConfigMap)
    79  	if err != nil {
    80  		return errors.Wrap(err, "Failed to create/update config map containing the verrazzano-platform-operator helm chart")
    81  	}
    82  
    83  	mgr, err := controllerruntime.NewManager(k8sutil.GetConfigOrDieFromController(), controllerruntime.Options{
    84  		Scheme:             scheme,
    85  		MetricsBindAddress: vzconfig.MetricsAddr,
    86  		Port:               8080,
    87  		LeaderElection:     vzconfig.LeaderElectionEnabled,
    88  		LeaderElectionID:   "3ec4d290.verrazzano.io",
    89  	})
    90  	if err != nil {
    91  		return errors.Wrap(err, "Failed to create a controller-runtime manager")
    92  	}
    93  
    94  	metricsexporter.StartMetricsServer(log)
    95  
    96  	// Set up the reconciler
    97  	statusUpdater := healthcheck.NewStatusUpdater(mgr.GetClient())
    98  
    99  	// Init the module controllers
   100  	if err := initModuleControllers(log, mgr); err != nil {
   101  		log.Errorf("Failed to start all module controllers", err)
   102  		return errors.Wrap(err, "Failed to initialize modules controllers for the components")
   103  	}
   104  
   105  	// init verrazzano  controller
   106  	if err := verrazzancontroller.InitController(mgr); err != nil {
   107  		log.Errorf("Failed to start module-based Verrazzano controller", err)
   108  		return errors.Wrap(err, "Failed to initialize controller for module-based Verrazzano controller")
   109  	}
   110  
   111  	if err := opensearchcontroller.InitController(mgr); err != nil {
   112  		log.Errorf("Failed to start module-based opensearch controller", err)
   113  		return errors.Wrap(err, "Failed to initialize controller for module-based opensearch controller")
   114  	}
   115  
   116  	// Setup health checker
   117  	healthCheck := healthcheck.NewHealthChecker(statusUpdater, mgr.GetClient(), time.Duration(vzconfig.HealthCheckPeriodSeconds)*time.Second)
   118  	if vzconfig.HealthCheckPeriodSeconds > 0 {
   119  		healthCheck.Start()
   120  	}
   121  
   122  	dynamicClient, err := k8sutil.GetDynamicClient()
   123  	if err != nil {
   124  		return errors.Wrapf(err, "Failed to get Dynamic Client")
   125  	}
   126  	// Setup secrets reconciler
   127  	if err = (&secrets.VerrazzanoSecretsReconciler{
   128  		Client:        mgr.GetClient(),
   129  		DynamicClient: dynamicClient,
   130  		Scheme:        mgr.GetScheme(),
   131  		StatusUpdater: statusUpdater,
   132  	}).SetupWithManager(mgr); err != nil {
   133  		return errors.Wrapf(err, "Failed to setup controller VerrazzanoSecrets")
   134  	}
   135  
   136  	// Setup configMaps reconciler
   137  	if err = (&overrides.OverridesConfigMapsReconciler{
   138  		Client:        mgr.GetClient(),
   139  		Scheme:        mgr.GetScheme(),
   140  		StatusUpdater: statusUpdater,
   141  	}).SetupWithManager(mgr); err != nil {
   142  		return errors.Wrap(err, "Failed to setup controller VerrazzanoConfigMaps")
   143  	}
   144  
   145  	// Setup MySQL checker
   146  	mysqlCheck, err := mysqlcheck.NewMySQLChecker(mgr.GetClient(), time.Duration(vzconfig.MySQLCheckPeriodSeconds)*time.Second, time.Duration(vzconfig.MySQLRepairTimeoutSeconds)*time.Second)
   147  	if err != nil {
   148  		return errors.Wrap(err, "Failed starting MySQLChecker")
   149  	}
   150  	mysqlCheck.Start()
   151  
   152  	// Setup Namespaces watcher
   153  	watchNamespace := namespacewatch.NewNamespaceWatcher(mgr.GetClient(), time.Duration(vzconfig.NamespacePeriodSeconds)*time.Second)
   154  	if vzconfig.NamespacePeriodSeconds > 0 {
   155  		watchNamespace.Start()
   156  	}
   157  
   158  	// Setup stacks reconciler
   159  	if err = (&components.ComponentConfigMapReconciler{
   160  		Client: mgr.GetClient(),
   161  		Scheme: mgr.GetScheme(),
   162  		DryRun: vzconfig.DryRun,
   163  	}).SetupWithManager(mgr); err != nil {
   164  		return errors.Wrap(err, "Failed to setup controller for Verrazzano Stacks")
   165  	}
   166  
   167  	// +kubebuilder:scaffold:builder
   168  	log.Info("Starting controller-runtime manager")
   169  	if err := mgr.Start(controllerruntime.SetupSignalHandler()); err != nil {
   170  		return errors.Wrap(err, "Failed starting controller-runtime manager: %v")
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  // generateConfigMapFromHelmChartFiles generates a config map with the files from a given helm chart directory
   177  func generateConfigMapFromHelmChartFiles(dir string, key string, files []os.DirEntry, configMap *corev1.ConfigMap) error {
   178  	for _, file := range files {
   179  		newKey := file.Name()
   180  		if len(key) != 0 {
   181  			newKey = key + "/" + file.Name()
   182  		}
   183  		if file.IsDir() {
   184  			files2, err := os.ReadDir(dir + "/" + newKey)
   185  			if err != nil {
   186  				return err
   187  			}
   188  			err = generateConfigMapFromHelmChartFiles(dir, newKey, files2, configMap)
   189  			if err != nil {
   190  				return err
   191  			}
   192  		} else {
   193  			err := addKeyForFileToConfigMap(dir, newKey, configMap)
   194  			if err != nil {
   195  				return nil
   196  			}
   197  		}
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  // addKeyForFileToConfigMap adds a key for a file to a config map
   204  func addKeyForFileToConfigMap(dir string, key string, configMap *corev1.ConfigMap) error {
   205  	data, err := os.ReadFile(dir + "/" + key)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	if configMap.Data == nil {
   210  		configMap.Data = make(map[string]string)
   211  	}
   212  	// Use "..." as a path separator since "/" is an invalid character for a config map data key
   213  	keyName := strings.ReplaceAll(key, "/", "...")
   214  	configMap.Data[keyName] = string(data)
   215  
   216  	return nil
   217  }
   218  
   219  // createVPOHelmChartConfigMap creates/updates a config map containing the VPO helm chart
   220  func createVPOHelmChartConfigMap(kubeClient kubernetes.Interface, configMap *corev1.ConfigMap) error {
   221  	_, err := kubeClient.CoreV1().ConfigMaps(constants.VerrazzanoInstallNamespace).Get(context.TODO(), configMap.Name, metav1.GetOptions{})
   222  	if err != nil {
   223  		if k8serrors.IsNotFound(err) {
   224  			_, err = kubeClient.CoreV1().ConfigMaps(constants.VerrazzanoInstallNamespace).Create(context.TODO(), configMap, metav1.CreateOptions{})
   225  		}
   226  	} else {
   227  		_, err = kubeClient.CoreV1().ConfigMaps(constants.VerrazzanoInstallNamespace).Update(context.TODO(), configMap, metav1.UpdateOptions{})
   228  	}
   229  
   230  	return err
   231  }
   232  
   233  // initModuleControllers creates a controller for every module
   234  // The controller uses the module name (i.e. component name) as a predicate,
   235  // so that each controller only processes Module CRs for the respective component.
   236  func initModuleControllers(log *zap.SugaredLogger, mgr controllerruntime.Manager) error {
   237  	// Run 50 controllers max concurrently.  Even though there is a controller for each
   238  	// module, the controller-runtime serializes reconciles if the MaxConcurrentReconciles is 1.
   239  	// This is because it is the same resource type being reconcile, but with a
   240  	// different predicate per controller.  Also, with the MaxConcurrentReconciles > 1, there
   241  	// is a chance that the controller-runtime will call the same controller (e.g. istio) to
   242  	// reconcile, while it is already reconciling a given CR.  There is a check in the
   243  	// module-operator package to catch this and requeue, thereby never allowing more
   244  	// than 1 instance of the same module from having more than 1 outstanding reconcile happening.
   245  	// See https://github.com/verrazzano/verrazzano-modules/blob/main/module-operator/controllers/module/reconciler.go#L225
   246  	// for details.
   247  	const maxReconciles = 50
   248  
   249  	// Temp hack to prevent module controller from looking up helm info
   250  	module.IgnoreHelmInfo()
   251  
   252  	for _, comp := range registry.GetComponents() {
   253  		// Create and initialize the module controller.  Note that the implementation of the module controller
   254  		// is in the module-operator package (in the module-operator repo).  This is the exact
   255  		// same controller that is used by the module-operator used by OCNE.  The only difference
   256  		// is the set of handlers.  This code uses component shim handlers in the VPO code, whereas
   257  		// the OCNE module-operator uses OCNE handlers in the module-operator controller.  See
   258  		// ModuleHandlerInfo param below.  Also see the module-operator where the OCNE handlers are used
   259  		// for comparison: https://github.com/verrazzano/verrazzano-modules/blob/main/module-operator/main.go
   260  		if err := module.InitController(module.ModuleControllerConfig{
   261  			ControllerManager: mgr,
   262  			ModuleHandlerInfo: modulehandlerfactory.NewModuleHandlerInfo(),
   263  			ModuleClass:       moduleapi.ModuleClassType(comp.Name()),
   264  			WatchDescriptors:  comp.GetWatchDescriptors(),
   265  			ControllerOptions: &controller.Options{MaxConcurrentReconciles: maxReconciles},
   266  		}); err != nil {
   267  			log.Errorf("Failed to start the controller for module %s:%v", comp.Name(), err)
   268  			return err
   269  		}
   270  	}
   271  	return nil
   272  }