pkg/providers/vsphere/vsphere.go (1,104 lines of code) (raw):

package vsphere import ( "bytes" "context" _ "embed" "fmt" "os" "reflect" "text/template" "time" "github.com/Masterminds/sprig" etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1" corev1 "k8s.io/api/core/v1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/bootstrapper" "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/config" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/executables" "github.com/aws/eks-anywhere/pkg/filewriter" "github.com/aws/eks-anywhere/pkg/govmomi" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/providers" "github.com/aws/eks-anywhere/pkg/providers/common" "github.com/aws/eks-anywhere/pkg/retrier" "github.com/aws/eks-anywhere/pkg/types" "github.com/aws/eks-anywhere/pkg/validations" releasev1alpha1 "github.com/aws/eks-anywhere/release/api/v1alpha1" ) const ( CredentialsObjectName = "vsphere-credentials" eksaLicense = "EKSA_LICENSE" vSphereUsernameKey = "VSPHERE_USERNAME" vSpherePasswordKey = "VSPHERE_PASSWORD" vSphereServerKey = "VSPHERE_SERVER" govcDatacenterKey = "GOVC_DATACENTER" govcInsecure = "GOVC_INSECURE" expClusterResourceSetKey = "EXP_CLUSTER_RESOURCE_SET" defaultTemplateLibrary = "eks-a-templates" defaultTemplatesFolder = "vm/Templates" maxRetries = 30 backOffPeriod = 5 * time.Second disk1 = "Hard disk 1" disk2 = "Hard disk 2" MemoryAvailable = "Memory_Available" ) const ( // Documentation URLs. vSpherePermissionDoc = "https://anywhere.eks.amazonaws.com/docs/getting-started/vsphere/vsphere-preparation/" ) //go:embed config/template-cp.yaml var defaultCAPIConfigCP string //go:embed config/template-md.yaml var defaultClusterConfigMD string //go:embed config/secret.yaml var defaultSecretObject string //go:embed config/template-failuredomain.yaml var defaultFailureDomainConfig string var ( eksaVSphereDatacenterResourceType = fmt.Sprintf("vspheredatacenterconfigs.%s", v1alpha1.GroupVersion.Group) eksaVSphereMachineResourceType = fmt.Sprintf("vspheremachineconfigs.%s", v1alpha1.GroupVersion.Group) ) var requiredEnvs = []string{vSphereUsernameKey, vSpherePasswordKey, expClusterResourceSetKey} type vsphereProvider struct { clusterConfig *v1alpha1.Cluster providerGovcClient ProviderGovcClient providerKubectlClient ProviderKubectlClient writer filewriter.FileWriter templateBuilder *VsphereTemplateBuilder skipIPCheck bool Retrier *retrier.Retrier validator *Validator defaulter *Defaulter ipValidator IPValidator skippedValidations map[string]bool } type ProviderGovcClient interface { SearchTemplate(ctx context.Context, datacenter, template string) (string, error) LibraryElementExists(ctx context.Context, library string) (bool, error) GetLibraryElementContentVersion(ctx context.Context, element string) (string, error) DeleteLibraryElement(ctx context.Context, element string) error TemplateHasSnapshot(ctx context.Context, template string) (bool, error) GetWorkloadAvailableSpace(ctx context.Context, datastore string) (float64, error) ValidateVCenterSetupMachineConfig(ctx context.Context, datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfig *v1alpha1.VSphereMachineConfig, selfSigned *bool) error ValidateFailureDomainConfig(ctx context.Context, datacenterConfig *v1alpha1.VSphereDatacenterConfig, failureDomain *v1alpha1.FailureDomain) error ValidateVCenterConnection(ctx context.Context, server string) error ValidateVCenterAuthentication(ctx context.Context) error IsCertSelfSigned(ctx context.Context) bool GetCertThumbprint(ctx context.Context) (string, error) ConfigureCertThumbprint(ctx context.Context, server, thumbprint string) error DatacenterExists(ctx context.Context, datacenter string) (bool, error) NetworkExists(ctx context.Context, network string) (bool, error) GetFolderPath(ctx context.Context, datacenter string, folder string, envMap map[string]string) (string, error) GetDatastorePath(ctx context.Context, datacenter string, datastorePath string, envMap map[string]string) (string, error) GetResourcePoolPath(ctx context.Context, datacenter string, resourcePool string, envMap map[string]string) (string, error) GetComputeClusterPath(ctx context.Context, datacenter string, computeCluster string, envMap map[string]string) (string, error) CreateLibrary(ctx context.Context, datastore, library string) error DeployTemplateFromLibrary(ctx context.Context, templateDir, templateName, library, datacenter, datastore, network, resourcePool string, resizeDisk2 bool) error ImportTemplate(ctx context.Context, library, ovaURL, name string) error GetVMDiskSizeInGB(ctx context.Context, vm, datacenter string) (int, error) GetTags(ctx context.Context, path string) (tags []string, err error) ListTags(ctx context.Context) ([]executables.Tag, error) CreateTag(ctx context.Context, tag, category string) error AddTag(ctx context.Context, path, tag string) error ListCategories(ctx context.Context) ([]string, error) CreateCategoryForVM(ctx context.Context, name string) error CreateUser(ctx context.Context, username, password string) error UserExists(ctx context.Context, username string) (bool, error) CreateGroup(ctx context.Context, name string) error GroupExists(ctx context.Context, name string) (bool, error) AddUserToGroup(ctx context.Context, name, username string) error RoleExists(ctx context.Context, name string) (bool, error) CreateRole(ctx context.Context, name string, privileges []string) error SetGroupRoleOnObject(ctx context.Context, principal, role, object, domain string) error GetHardDiskSize(ctx context.Context, vm, datacenter string) (map[string]float64, error) GetResourcePoolInfo(ctx context.Context, datacenter, resourcepool string, args ...string) (map[string]int, error) } type ProviderKubectlClient interface { ApplyKubeSpecFromBytes(ctx context.Context, cluster *types.Cluster, data []byte) error CreateNamespaceIfNotPresent(ctx context.Context, kubeconfig, namespace string) error LoadSecret(ctx context.Context, secretObject, secretObjType, secretObjectName, kubeConfFile string) error GetEksaCluster(ctx context.Context, cluster *types.Cluster, clusterName string) (*v1alpha1.Cluster, error) GetEksaVSphereDatacenterConfig(ctx context.Context, vsphereDatacenterConfigName, kubeconfigFile, namespace string) (*v1alpha1.VSphereDatacenterConfig, error) GetEksaVSphereMachineConfig(ctx context.Context, vsphereMachineConfigName, kubeconfigFile, namespace string) (*v1alpha1.VSphereMachineConfig, error) GetMachineDeployment(ctx context.Context, machineDeploymentName string, opts ...executables.KubectlOpt) (*clusterv1.MachineDeployment, error) GetKubeadmControlPlane(ctx context.Context, cluster *types.Cluster, clusterName string, opts ...executables.KubectlOpt) (*controlplanev1.KubeadmControlPlane, error) GetEtcdadmCluster(ctx context.Context, cluster *types.Cluster, clusterName string, opts ...executables.KubectlOpt) (*etcdv1.EtcdadmCluster, error) GetSecretFromNamespace(ctx context.Context, kubeconfigFile, name, namespace string) (*corev1.Secret, error) UpdateAnnotation(ctx context.Context, resourceType, objectName string, annotations map[string]string, opts ...executables.KubectlOpt) error RemoveAnnotationInNamespace(ctx context.Context, resourceType, objectName, key string, cluster *types.Cluster, namespace string) error SearchVsphereMachineConfig(ctx context.Context, name, kubeconfigFile, namespace string) ([]*v1alpha1.VSphereMachineConfig, error) SearchVsphereDatacenterConfig(ctx context.Context, name, kubeconfigFile, namespace string) ([]*v1alpha1.VSphereDatacenterConfig, error) SetDaemonSetImage(ctx context.Context, kubeconfigFile, name, namespace, container, image string) error DeleteEksaDatacenterConfig(ctx context.Context, vsphereDatacenterResourceType, vsphereDatacenterConfigName, kubeconfigFile, namespace string) error DeleteEksaMachineConfig(ctx context.Context, vsphereMachineResourceType, vsphereMachineConfigName, kubeconfigFile, namespace string) error ApplyTolerationsFromTaintsToDaemonSet(ctx context.Context, oldTaints, newTaints []corev1.Taint, dsName, kubeconfigFile string) error } // IPValidator is an interface that defines methods to validate the control plane IP. type IPValidator interface { ValidateControlPlaneIPUniqueness(cluster *v1alpha1.Cluster) error } // NewProvider initializes and returns a new vsphereProvider. func NewProvider( datacenterConfig *v1alpha1.VSphereDatacenterConfig, clusterConfig *v1alpha1.Cluster, providerGovcClient ProviderGovcClient, providerKubectlClient ProviderKubectlClient, writer filewriter.FileWriter, ipValidator IPValidator, now types.NowFunc, skipIPCheck bool, skippedValidations map[string]bool, ) *vsphereProvider { //nolint:revive // TODO(g-gaston): ignoring linter error for exported function returning unexported member // We should make it exported, but that would involve a bunch of changes, so will do it separately vcb := govmomi.NewVMOMIClientBuilder() v := NewValidator( providerGovcClient, vcb, ) return NewProviderCustomNet( datacenterConfig, clusterConfig, providerGovcClient, providerKubectlClient, writer, ipValidator, now, skipIPCheck, v, skippedValidations, ) } // NewProviderCustomNet initializes and returns a new vsphereProvider. func NewProviderCustomNet( datacenterConfig *v1alpha1.VSphereDatacenterConfig, clusterConfig *v1alpha1.Cluster, providerGovcClient ProviderGovcClient, providerKubectlClient ProviderKubectlClient, writer filewriter.FileWriter, ipValidator IPValidator, now types.NowFunc, skipIPCheck bool, v *Validator, skippedValidations map[string]bool, ) *vsphereProvider { //nolint:revive // TODO(g-gaston): ignoring linter error for exported function returning unexported member // We should make it exported, but that would involve a bunch of changes, so will do it separately retrier := retrier.NewWithMaxRetries(maxRetries, backOffPeriod) return &vsphereProvider{ clusterConfig: clusterConfig, providerGovcClient: providerGovcClient, providerKubectlClient: providerKubectlClient, writer: writer, templateBuilder: NewVsphereTemplateBuilder( now, ), skipIPCheck: skipIPCheck, Retrier: retrier, validator: v, defaulter: NewDefaulter(providerGovcClient), ipValidator: ipValidator, skippedValidations: skippedValidations, } } func (p *vsphereProvider) UpdateKubeConfig(_ *[]byte, _ string) error { // customize generated kube config return nil } func (p *vsphereProvider) machineConfigsSpecChanged(ctx context.Context, cc *v1alpha1.Cluster, cluster *types.Cluster, newClusterSpec *cluster.Spec) (bool, error) { for _, oldMcRef := range cc.MachineConfigRefs() { existingVmc, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, oldMcRef.Name, cluster.KubeconfigFile, newClusterSpec.Cluster.Namespace) if err != nil { return false, err } csmc, ok := newClusterSpec.VSphereMachineConfigs[oldMcRef.Name] if !ok { logger.V(3).Info(fmt.Sprintf("Old machine config spec %s not found in the existing spec", oldMcRef.Name)) return true, nil } if !reflect.DeepEqual(existingVmc.Spec, csmc.Spec) { logger.V(3).Info(fmt.Sprintf("New machine config spec %s is different from the existing spec", oldMcRef.Name)) return true, nil } } return false, nil } func (p *vsphereProvider) BootstrapClusterOpts(spec *cluster.Spec) ([]bootstrapper.BootstrapClusterOption, error) { return common.BootstrapClusterOpts(p.clusterConfig, spec.VSphereDatacenter.Spec.Server) } func (p *vsphereProvider) Name() string { return constants.VSphereProviderName } func (p *vsphereProvider) DatacenterResourceType() string { return eksaVSphereDatacenterResourceType } func (p *vsphereProvider) MachineResourceType() string { return eksaVSphereMachineResourceType } func (p *vsphereProvider) generateSSHKeysIfNotSet(machineConfigs map[string]*v1alpha1.VSphereMachineConfig) error { var generatedKey string for _, machineConfig := range machineConfigs { user := machineConfig.Spec.Users[0] if user.SshAuthorizedKeys[0] == "" { if generatedKey != "" { // use the same key user.SshAuthorizedKeys[0] = generatedKey } else { logger.Info("Provided sshAuthorizedKey is not set or is empty, auto-generating new key pair...", "vSphereMachineConfig", machineConfig.Name) var err error generatedKey, err = common.GenerateSSHAuthKey(p.writer) if err != nil { return err } user.SshAuthorizedKeys[0] = generatedKey } } } return nil } func (p *vsphereProvider) DeleteResources(ctx context.Context, clusterSpec *cluster.Spec) error { for _, mc := range clusterSpec.VSphereMachineConfigs { if err := p.providerKubectlClient.DeleteEksaMachineConfig(ctx, eksaVSphereMachineResourceType, mc.Name, clusterSpec.ManagementCluster.KubeconfigFile, mc.Namespace); err != nil { return err } } return p.providerKubectlClient.DeleteEksaDatacenterConfig(ctx, eksaVSphereDatacenterResourceType, clusterSpec.VSphereDatacenter.Name, clusterSpec.ManagementCluster.KubeconfigFile, clusterSpec.VSphereDatacenter.Namespace, ) } func (p *vsphereProvider) PostClusterDeleteValidate(_ context.Context, _ *types.Cluster) error { // No validations return nil } func (p *vsphereProvider) PostMoveManagementToBootstrap(_ context.Context, _ *types.Cluster) error { // NOOP return nil } func (p *vsphereProvider) SetupAndValidateCreateCluster(ctx context.Context, clusterSpec *cluster.Spec) error { if err := SetupEnvVars(clusterSpec.VSphereDatacenter); err != nil { return fmt.Errorf("failed setup and validations: %v", err) } vSphereClusterSpec := NewSpec(clusterSpec) if err := p.defaulter.SetDefaultsForDatacenterConfig(ctx, vSphereClusterSpec.VSphereDatacenter); err != nil { return fmt.Errorf("failed setting default values for vsphere datacenter config: %v", err) } if err := vSphereClusterSpec.VSphereDatacenter.Validate(); err != nil { return err } if err := p.validator.ValidateVCenterConfig(ctx, vSphereClusterSpec.VSphereDatacenter); err != nil { return err } if err := p.validator.ValidateFailureDomains(ctx, vSphereClusterSpec); err != nil { return err } if err := p.defaulter.setDefaultsForMachineConfig(ctx, vSphereClusterSpec); err != nil { return fmt.Errorf("failed setting default values for vsphere machine configs: %v", err) } if err := p.validator.ValidateClusterMachineConfigs(ctx, vSphereClusterSpec); err != nil { return err } if err := p.validateDatastoreUsageForCreate(ctx, vSphereClusterSpec); err != nil { return fmt.Errorf("validating vsphere machine configs datastore usage: %v", err) } if err := p.validateMemoryUsage(ctx, vSphereClusterSpec, nil); err != nil { return fmt.Errorf("validating vsphere machine configs resource pool memory usage: %v", err) } if err := p.generateSSHKeysIfNotSet(clusterSpec.VSphereMachineConfigs); err != nil { return fmt.Errorf("failed setup and validations: %v", err) } // TODO: move this to validator if clusterSpec.Cluster.IsManaged() { for _, mc := range clusterSpec.VSphereMachineConfigs { em, err := p.providerKubectlClient.SearchVsphereMachineConfig(ctx, mc.GetName(), clusterSpec.ManagementCluster.KubeconfigFile, mc.GetNamespace()) if err != nil { return err } if len(em) > 0 { return fmt.Errorf("VSphereMachineConfig %s already exists", mc.GetName()) } } existingDatacenter, err := p.providerKubectlClient.SearchVsphereDatacenterConfig(ctx, clusterSpec.VSphereDatacenter.Name, clusterSpec.ManagementCluster.KubeconfigFile, clusterSpec.Cluster.Namespace) if err != nil { return err } if len(existingDatacenter) > 0 { return fmt.Errorf("VSphereDatacenter %s already exists", clusterSpec.VSphereDatacenter.Name) } for _, identityProviderRef := range clusterSpec.Cluster.Spec.IdentityProviderRefs { if identityProviderRef.Kind == v1alpha1.OIDCConfigKind { clusterSpec.OIDCConfig.SetManagedBy(p.clusterConfig.ManagedBy()) } } } if !p.skipIPCheck { if err := p.ipValidator.ValidateControlPlaneIPUniqueness(clusterSpec.Cluster); err != nil { return err } } else { logger.Info("Skipping check for whether control plane ip is in use") } if !p.skippedValidations[validations.VSphereUserPriv] { if err := p.validator.validateVsphereUserPrivs(ctx, vSphereClusterSpec); err != nil { return fmt.Errorf("validating vsphere user privileges: %w, please refer to %s for required permissions or use -v 3 for full missing permissions", err, vSpherePermissionDoc) } } return nil } func (p *vsphereProvider) SetupAndValidateUpgradeCluster(ctx context.Context, cluster *types.Cluster, clusterSpec, _ *cluster.Spec) error { if err := SetupEnvVars(clusterSpec.VSphereDatacenter); err != nil { return fmt.Errorf("failed setup and validations: %v", err) } vSphereClusterSpec := NewSpec(clusterSpec) if err := p.defaulter.SetDefaultsForDatacenterConfig(ctx, vSphereClusterSpec.VSphereDatacenter); err != nil { return fmt.Errorf("failed setting default values for vsphere datacenter config: %v", err) } if err := vSphereClusterSpec.VSphereDatacenter.Validate(); err != nil { return err } if err := p.validator.ValidateVCenterConfig(ctx, vSphereClusterSpec.VSphereDatacenter); err != nil { return err } if err := p.validator.ValidateFailureDomains(ctx, vSphereClusterSpec); err != nil { return err } if err := p.defaulter.setDefaultsForMachineConfig(ctx, vSphereClusterSpec); err != nil { return fmt.Errorf("failed setting default values for vsphere machine configs: %v", err) } if err := p.validator.ValidateClusterMachineConfigs(ctx, vSphereClusterSpec); err != nil { return err } if err := p.validateDatastoreUsageForUpgrade(ctx, vSphereClusterSpec, cluster); err != nil { return fmt.Errorf("validating vsphere machine configs datastore usage: %v", err) } if err := p.validateMemoryUsage(ctx, vSphereClusterSpec, cluster); err != nil { return fmt.Errorf("validating vsphere machine configs resource pool memory usage: %v", err) } if !p.skippedValidations[validations.VSphereUserPriv] { if err := p.validator.validateVsphereUserPrivs(ctx, vSphereClusterSpec); err != nil { return fmt.Errorf("validating vsphere user privileges: %w, please refer to %s for required permissions or use -v 3 for full missing permissions", err, vSpherePermissionDoc) } } err := p.validateMachineConfigsNameUniqueness(ctx, cluster, clusterSpec) if err != nil { return fmt.Errorf("failed validate machineconfig uniqueness: %v", err) } return nil } // SetupAndValidateUpgradeManagementComponents performs necessary setup for upgrade management components operation. func (p *vsphereProvider) SetupAndValidateUpgradeManagementComponents(ctx context.Context, clusterSpec *cluster.Spec) error { if err := SetupEnvVars(clusterSpec.VSphereDatacenter); err != nil { return fmt.Errorf("failed environment variable setup: %v", err) } return nil } func (p *vsphereProvider) validateMachineConfigsNameUniqueness(ctx context.Context, cluster *types.Cluster, clusterSpec *cluster.Spec) error { prevSpec, err := p.providerKubectlClient.GetEksaCluster(ctx, cluster, clusterSpec.Cluster.GetName()) if err != nil { return err } cpMachineConfigName := clusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name if prevSpec.Spec.ControlPlaneConfiguration.MachineGroupRef.Name != cpMachineConfigName { em, err := p.providerKubectlClient.SearchVsphereMachineConfig(ctx, cpMachineConfigName, cluster.KubeconfigFile, clusterSpec.Cluster.GetNamespace()) if err != nil { return err } if len(em) > 0 { return fmt.Errorf("control plane VSphereMachineConfig %s already exists", cpMachineConfigName) } } if clusterSpec.Cluster.Spec.ExternalEtcdConfiguration != nil && prevSpec.Spec.ExternalEtcdConfiguration != nil { etcdMachineConfigName := clusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name if prevSpec.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name != etcdMachineConfigName { em, err := p.providerKubectlClient.SearchVsphereMachineConfig(ctx, etcdMachineConfigName, cluster.KubeconfigFile, clusterSpec.Cluster.GetNamespace()) if err != nil { return err } if len(em) > 0 { return fmt.Errorf("external etcd machineconfig %s already exists", etcdMachineConfigName) } } } return nil } type datastoreUsage struct { availableSpace float64 needGiBSpace int } func (p *vsphereProvider) getPrevMachineConfigDatastoreUsage(ctx context.Context, machineConfig *v1alpha1.VSphereMachineConfig, cluster *types.Cluster, count int) (diskGiB float64, err error) { if count > 0 { em, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, machineConfig.Name, cluster.KubeconfigFile, machineConfig.GetNamespace()) if err != nil { return 0, err } if em != nil { return float64(em.Spec.DiskGiB * count), nil } } return 0, nil } func (p *vsphereProvider) getMachineConfigDatastoreRequirements(ctx context.Context, machineConfig *v1alpha1.VSphereMachineConfig, count int) (available float64, need int, err error) { availableSpace, err := p.providerGovcClient.GetWorkloadAvailableSpace(ctx, machineConfig.Spec.Datastore) // TODO: remove dependency on machineConfig if err != nil { return 0, 0, fmt.Errorf("getting datastore details: %v", err) } needGiB := machineConfig.Spec.DiskGiB * count return availableSpace, needGiB, nil } func (p *vsphereProvider) calculateDatastoreUsage(ctx context.Context, machineConfig *v1alpha1.VSphereMachineConfig, cluster *types.Cluster, usage map[string]*datastoreUsage, prevCount, newCount int) error { availableSpace, needGiB, err := p.getMachineConfigDatastoreRequirements(ctx, machineConfig, newCount) if err != nil { return err } prevUsage, err := p.getPrevMachineConfigDatastoreUsage(ctx, machineConfig, cluster, prevCount) if err != nil { return err } availableSpace += prevUsage updateDatastoreUsageMap(machineConfig, needGiB, availableSpace, prevUsage, usage) return nil } func updateDatastoreUsageMap(machineConfig *v1alpha1.VSphereMachineConfig, needGiB int, availableSpace, prevUsage float64, usage map[string]*datastoreUsage) { if _, ok := usage[machineConfig.Spec.Datastore]; ok { usage[machineConfig.Spec.Datastore].needGiBSpace += needGiB usage[machineConfig.Spec.Datastore].availableSpace += prevUsage } else { usage[machineConfig.Spec.Datastore] = &datastoreUsage{ availableSpace: availableSpace, needGiBSpace: needGiB, } } } func (p *vsphereProvider) validateDatastoreUsageForUpgrade(ctx context.Context, currentClusterSpec *Spec, cluster *types.Cluster) error { usage := make(map[string]*datastoreUsage) prevEksaCluster, err := p.providerKubectlClient.GetEksaCluster(ctx, cluster, currentClusterSpec.Cluster.GetName()) if err != nil { return err } cpMachineConfig := currentClusterSpec.controlPlaneMachineConfig() if err := p.calculateDatastoreUsage(ctx, cpMachineConfig, cluster, usage, prevEksaCluster.Spec.ControlPlaneConfiguration.Count, currentClusterSpec.Cluster.Spec.ControlPlaneConfiguration.Count); err != nil { return fmt.Errorf("calculating datastore usage: %v", err) } prevMachineConfigRefs := machineRefSliceToMap(prevEksaCluster.MachineConfigRefs()) for _, workerNodeGroupConfiguration := range currentClusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations { prevCount := 0 workerMachineConfig := currentClusterSpec.workerMachineConfig(workerNodeGroupConfiguration) if _, ok := prevMachineConfigRefs[workerNodeGroupConfiguration.MachineGroupRef.Name]; ok { prevCount = *workerNodeGroupConfiguration.Count } if err := p.calculateDatastoreUsage(ctx, workerMachineConfig, cluster, usage, prevCount, *workerNodeGroupConfiguration.Count); err != nil { return fmt.Errorf("calculating datastore usage: %v", err) } } etcdMachineConfig := currentClusterSpec.etcdMachineConfig() if etcdMachineConfig != nil { prevCount := 0 if prevEksaCluster.Spec.ExternalEtcdConfiguration != nil { prevCount = prevEksaCluster.Spec.ExternalEtcdConfiguration.Count } if err := p.calculateDatastoreUsage(ctx, etcdMachineConfig, cluster, usage, prevCount, currentClusterSpec.Cluster.Spec.ExternalEtcdConfiguration.Count); err != nil { return fmt.Errorf("calculating datastore usage: %v", err) } } for datastore, usage := range usage { if float64(usage.needGiBSpace) > usage.availableSpace { return fmt.Errorf("not enough space in datastore %v for given diskGiB and count for respective machine groups", datastore) } } return nil } func (p *vsphereProvider) validateDatastoreUsageForCreate(ctx context.Context, vsphereClusterSpec *Spec) error { usage := make(map[string]*datastoreUsage) cpMachineConfig := vsphereClusterSpec.controlPlaneMachineConfig() controlPlaneAvailableSpace, controlPlaneNeedGiB, err := p.getMachineConfigDatastoreRequirements(ctx, cpMachineConfig, vsphereClusterSpec.Cluster.Spec.ControlPlaneConfiguration.Count) if err != nil { return err } updateDatastoreUsageMap(cpMachineConfig, controlPlaneNeedGiB, controlPlaneAvailableSpace, 0, usage) for _, workerNodeGroupConfiguration := range vsphereClusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations { workerMachineConfig := vsphereClusterSpec.workerMachineConfig(workerNodeGroupConfiguration) workerAvailableSpace, workerNeedGiB, err := p.getMachineConfigDatastoreRequirements(ctx, workerMachineConfig, *workerNodeGroupConfiguration.Count) if err != nil { return err } updateDatastoreUsageMap(workerMachineConfig, workerNeedGiB, workerAvailableSpace, 0, usage) } etcdMachineConfig := vsphereClusterSpec.etcdMachineConfig() if etcdMachineConfig != nil { etcdAvailableSpace, etcdNeedGiB, err := p.getMachineConfigDatastoreRequirements(ctx, etcdMachineConfig, vsphereClusterSpec.Cluster.Spec.ExternalEtcdConfiguration.Count) if err != nil { return err } updateDatastoreUsageMap(etcdMachineConfig, etcdNeedGiB, etcdAvailableSpace, 0, usage) } for datastore, usage := range usage { if float64(usage.needGiBSpace) > usage.availableSpace { return fmt.Errorf("not enough space in datastore %v for given diskGiB and count for respective machine groups", datastore) } } return nil } // getPrevMachineConfigMemoryUsage returns the memoryMiB freed up from the given machineConfig based on the count. func (p *vsphereProvider) getPrevMachineConfigMemoryUsage(ctx context.Context, mc *v1alpha1.VSphereMachineConfig, cluster *types.Cluster, machineConfigCount int) (memoryMiB int, err error) { em, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, mc.Name, cluster.KubeconfigFile, mc.GetNamespace()) if err != nil { return 0, err } if em != nil && em.Spec.ResourcePool == mc.Spec.ResourcePool { return em.Spec.MemoryMiB * machineConfigCount, nil } return 0, nil } // getMachineConfigMemoryAvailability accepts a machine config and returns available memory in the config's resource pool along with needed memory for the machine config. func (p *vsphereProvider) getMachineConfigMemoryAvailability(ctx context.Context, datacenter string, mc *v1alpha1.VSphereMachineConfig, machineConfigCount int) (availableMemoryMiB, needMemoryMiB int, err error) { poolInfo, err := p.providerGovcClient.GetResourcePoolInfo(ctx, datacenter, mc.Spec.ResourcePool) if err != nil { return 0, 0, err } needMemoryMiB = mc.Spec.MemoryMiB * machineConfigCount return poolInfo[MemoryAvailable], needMemoryMiB, nil } // updateMemoryUsageMap updates the memory availability for the machine config's resource pool. func updateMemoryUsageMap(mc *v1alpha1.VSphereMachineConfig, needMiB, availableMiB int, mu map[string]int) { if _, ok := mu[mc.Spec.ResourcePool]; !ok { mu[mc.Spec.ResourcePool] = availableMiB } // needMiB can be ignored when the resource pool memory limit is unset if availableMiB != -1 { mu[mc.Spec.ResourcePool] -= needMiB } } func addPrevMachineConfigMemoryUsage(mc *v1alpha1.VSphereMachineConfig, prevUsage int, memoryUsage map[string]int) { // when the memory limit for the respective resource pool is unset, skip accounting for previous usage and validating the needed memory if _, ok := memoryUsage[mc.Spec.ResourcePool]; ok && memoryUsage[mc.Spec.ResourcePool] != -1 { memoryUsage[mc.Spec.ResourcePool] += prevUsage } } func (p *vsphereProvider) validateMemoryUsage(ctx context.Context, clusterSpec *Spec, cluster *types.Cluster) error { memoryUsage := make(map[string]int) datacenter := clusterSpec.VSphereDatacenter.Spec.Datacenter for _, mc := range clusterSpec.machineConfigsWithCount() { availableMemoryMiB, needMemoryMiB, err := p.getMachineConfigMemoryAvailability(ctx, datacenter, mc.VSphereMachineConfig, mc.Count) if err != nil { return fmt.Errorf("calculating memory usage for machine config %v: %v", mc.VSphereMachineConfig.ObjectMeta.Name, err) } updateMemoryUsageMap(mc.VSphereMachineConfig, needMemoryMiB, availableMemoryMiB, memoryUsage) } // account for previous cluster resources that are freed up during upgrade. if cluster != nil { err := p.updatePrevClusterMemoryUsage(ctx, clusterSpec, cluster, memoryUsage) if err != nil { return err } } for resourcePool, remaniningMiB := range memoryUsage { if remaniningMiB != -1 && remaniningMiB < 0 { return fmt.Errorf("not enough memory available in resource pool %v for given memoryMiB and count for respective machine groups", resourcePool) } } logger.V(5).Info("Memory availability for machine configs in requested resource pool validated") return nil } // updatePrevClusterMemoryUsage calculates memory freed up from previous CP and worker nodes during upgrade and adds up the memory usage for the specific resource pool. func (p *vsphereProvider) updatePrevClusterMemoryUsage(ctx context.Context, clusterSpec *Spec, cluster *types.Cluster, memoryUsage map[string]int) error { prevEksaCluster, err := p.providerKubectlClient.GetEksaCluster(ctx, cluster, clusterSpec.Cluster.GetName()) if err != nil { return err } prevMachineConfigRefs := machineRefSliceToMap(prevEksaCluster.MachineConfigRefs()) if _, ok := prevMachineConfigRefs[clusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name]; ok { cpMachineConfig := clusterSpec.controlPlaneMachineConfig() // The last CP machine is deleted only after the desired number of new worker machines are rolled out, so don't add it's memory prevCPusage, err := p.getPrevMachineConfigMemoryUsage(ctx, cpMachineConfig, cluster, prevEksaCluster.Spec.ControlPlaneConfiguration.Count-1) if err != nil { return fmt.Errorf("calculating previous memory usage for control plane: %v", err) } addPrevMachineConfigMemoryUsage(cpMachineConfig, prevCPusage, memoryUsage) } for _, workerNodeGroupConfiguration := range clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations { workerMachineConfig := clusterSpec.workerMachineConfig(workerNodeGroupConfiguration) if _, ok := prevMachineConfigRefs[workerNodeGroupConfiguration.MachineGroupRef.Name]; ok { prevCount := *workerNodeGroupConfiguration.Count // The last worker machine is deleted only after the desired number of new worker machines are rolled out, so don't add it's memory prevWorkerUsage, err := p.getPrevMachineConfigMemoryUsage(ctx, workerMachineConfig, cluster, prevCount-1) if err != nil { return fmt.Errorf("calculating previous memory usage for worker node group - %v: %v", workerMachineConfig.Name, err) } addPrevMachineConfigMemoryUsage(workerMachineConfig, prevWorkerUsage, memoryUsage) } } return nil } func (p *vsphereProvider) UpdateSecrets(ctx context.Context, cluster *types.Cluster, _ *cluster.Spec) error { var contents bytes.Buffer err := p.createSecret(ctx, cluster, &contents) if err != nil { return err } err = p.providerKubectlClient.ApplyKubeSpecFromBytes(ctx, cluster, contents.Bytes()) if err != nil { return fmt.Errorf("loading secrets object: %v", err) } return nil } func (p *vsphereProvider) SetupAndValidateDeleteCluster(ctx context.Context, _ *types.Cluster, spec *cluster.Spec) error { if err := SetupEnvVars(spec.VSphereDatacenter); err != nil { return fmt.Errorf("failed setup and validations: %v", err) } return nil } func NeedsNewControlPlaneTemplate(oldSpec, newSpec *cluster.Spec, oldVdc, newVdc *v1alpha1.VSphereDatacenterConfig, oldVmc, newVmc *v1alpha1.VSphereMachineConfig) bool { // Another option is to generate MachineTemplates based on the old and new eksa spec, // remove the name field and compare them with DeepEqual // We plan to approach this way since it's more flexible to add/remove fields and test out for validation if oldSpec.Cluster.Spec.KubernetesVersion != newSpec.Cluster.Spec.KubernetesVersion { return true } if oldSpec.Bundles.Spec.Number != newSpec.Bundles.Spec.Number { return true } return AnyImmutableFieldChanged(oldVdc, newVdc, oldVmc, newVmc) } // NeedsNewWorkloadTemplate determines if a new workload template is needed. func NeedsNewWorkloadTemplate(oldSpec, newSpec *cluster.Spec, oldVdc, newVdc *v1alpha1.VSphereDatacenterConfig, oldVmc, newVmc *v1alpha1.VSphereMachineConfig, oldWorker, newWorker v1alpha1.WorkerNodeGroupConfiguration) bool { if oldSpec.Bundles.Spec.Number != newSpec.Bundles.Spec.Number { return true } if !v1alpha1.TaintsSliceEqual(oldWorker.Taints, newWorker.Taints) || !v1alpha1.MapEqual(oldWorker.Labels, newWorker.Labels) || !v1alpha1.WorkerNodeGroupConfigurationKubeVersionUnchanged(&oldWorker, &newWorker, oldSpec.Cluster, newSpec.Cluster) { return true } return AnyImmutableFieldChanged(oldVdc, newVdc, oldVmc, newVmc) } func NeedsNewKubeadmConfigTemplate(newWorkerNodeGroup, oldWorkerNodeGroup *v1alpha1.WorkerNodeGroupConfiguration, oldWorkerNodeVmc, newWorkerNodeVmc *v1alpha1.VSphereMachineConfig) bool { return !v1alpha1.TaintsSliceEqual(newWorkerNodeGroup.Taints, oldWorkerNodeGroup.Taints) || !v1alpha1.MapEqual(newWorkerNodeGroup.Labels, oldWorkerNodeGroup.Labels) || !v1alpha1.UsersSliceEqual(oldWorkerNodeVmc.Spec.Users, newWorkerNodeVmc.Spec.Users) } func NeedsNewEtcdTemplate(oldSpec, newSpec *cluster.Spec, oldVdc, newVdc *v1alpha1.VSphereDatacenterConfig, oldVmc, newVmc *v1alpha1.VSphereMachineConfig) bool { if oldSpec.Cluster.Spec.KubernetesVersion != newSpec.Cluster.Spec.KubernetesVersion { return true } if oldSpec.Bundles.Spec.Number != newSpec.Bundles.Spec.Number { return true } return AnyImmutableFieldChanged(oldVdc, newVdc, oldVmc, newVmc) } func AnyImmutableFieldChanged(oldVdc, newVdc *v1alpha1.VSphereDatacenterConfig, oldVmc, newVmc *v1alpha1.VSphereMachineConfig) bool { if oldVmc.Spec.NumCPUs != newVmc.Spec.NumCPUs { return true } if oldVmc.Spec.MemoryMiB != newVmc.Spec.MemoryMiB { return true } if oldVmc.Spec.DiskGiB != newVmc.Spec.DiskGiB { return true } if oldVmc.Spec.Datastore != newVmc.Spec.Datastore { return true } if oldVmc.Spec.Folder != newVmc.Spec.Folder { return true } if oldVdc.Spec.Network != newVdc.Spec.Network { return true } if oldVmc.Spec.ResourcePool != newVmc.Spec.ResourcePool { return true } if oldVdc.Spec.Thumbprint != newVdc.Spec.Thumbprint { return true } if oldVmc.Spec.Template != newVmc.Spec.Template { return true } return false } func (p *vsphereProvider) generateCAPISpecForUpgrade(ctx context.Context, bootstrapCluster, workloadCluster *types.Cluster, currentSpec, newClusterSpec *cluster.Spec) (controlPlaneSpec, workersSpec []byte, err error) { clusterName := newClusterSpec.Cluster.Name var controlPlaneTemplateName, workloadTemplateName, kubeadmconfigTemplateName, etcdTemplateName string var needsNewEtcdTemplate bool c, err := p.providerKubectlClient.GetEksaCluster(ctx, workloadCluster, newClusterSpec.Cluster.Name) if err != nil { return nil, nil, err } vdc, err := p.providerKubectlClient.GetEksaVSphereDatacenterConfig(ctx, newClusterSpec.VSphereDatacenter.Name, workloadCluster.KubeconfigFile, newClusterSpec.Cluster.Namespace) if err != nil { return nil, nil, err } controlPlaneMachineConfig := newClusterSpec.VSphereMachineConfigs[newClusterSpec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name] controlPlaneVmc, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, c.Spec.ControlPlaneConfiguration.MachineGroupRef.Name, workloadCluster.KubeconfigFile, newClusterSpec.Cluster.Namespace) if err != nil { return nil, nil, err } needsNewControlPlaneTemplate := NeedsNewControlPlaneTemplate(currentSpec, newClusterSpec, vdc, newClusterSpec.VSphereDatacenter, controlPlaneVmc, controlPlaneMachineConfig) if !needsNewControlPlaneTemplate { cp, err := p.providerKubectlClient.GetKubeadmControlPlane(ctx, workloadCluster, c.Name, executables.WithCluster(bootstrapCluster), executables.WithNamespace(constants.EksaSystemNamespace)) if err != nil { return nil, nil, err } controlPlaneTemplateName = cp.Spec.MachineTemplate.InfrastructureRef.Name } else { controlPlaneTemplateName = common.CPMachineTemplateName(clusterName, p.templateBuilder.now) } previousWorkerNodeGroupConfigs := cluster.BuildMapForWorkerNodeGroupsByName(currentSpec.Cluster.Spec.WorkerNodeGroupConfigurations) workloadTemplateNames := make(map[string]string, len(newClusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations)) kubeadmconfigTemplateNames := make(map[string]string, len(newClusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations)) for _, workerNodeGroupConfiguration := range newClusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations { oldWorkerNodeVmc, newWorkerNodeVmc, err := p.getWorkerNodeMachineConfigs(ctx, workloadCluster, newClusterSpec, workerNodeGroupConfiguration, previousWorkerNodeGroupConfigs) if err != nil { return nil, nil, err } needsNewWorkloadTemplate, err := p.needsNewMachineTemplate(currentSpec, newClusterSpec, workerNodeGroupConfiguration, vdc, previousWorkerNodeGroupConfigs, oldWorkerNodeVmc, newWorkerNodeVmc) if err != nil { return nil, nil, err } needsNewKubeadmConfigTemplate, err := p.needsNewKubeadmConfigTemplate(workerNodeGroupConfiguration, previousWorkerNodeGroupConfigs, oldWorkerNodeVmc, newWorkerNodeVmc) if err != nil { return nil, nil, err } if !needsNewKubeadmConfigTemplate { mdName := machineDeploymentName(newClusterSpec.Cluster.Name, workerNodeGroupConfiguration.Name) md, err := p.providerKubectlClient.GetMachineDeployment(ctx, mdName, executables.WithCluster(bootstrapCluster), executables.WithNamespace(constants.EksaSystemNamespace)) if err != nil { return nil, nil, err } kubeadmconfigTemplateName = md.Spec.Template.Spec.Bootstrap.ConfigRef.Name kubeadmconfigTemplateNames[workerNodeGroupConfiguration.Name] = kubeadmconfigTemplateName } else { kubeadmconfigTemplateName = common.KubeadmConfigTemplateName(clusterName, workerNodeGroupConfiguration.Name, p.templateBuilder.now) kubeadmconfigTemplateNames[workerNodeGroupConfiguration.Name] = kubeadmconfigTemplateName } if !needsNewWorkloadTemplate { mdName := machineDeploymentName(newClusterSpec.Cluster.Name, workerNodeGroupConfiguration.Name) md, err := p.providerKubectlClient.GetMachineDeployment(ctx, mdName, executables.WithCluster(bootstrapCluster), executables.WithNamespace(constants.EksaSystemNamespace)) if err != nil { return nil, nil, err } workloadTemplateName = md.Spec.Template.Spec.InfrastructureRef.Name workloadTemplateNames[workerNodeGroupConfiguration.Name] = workloadTemplateName } else { workloadTemplateName = common.WorkerMachineTemplateName(clusterName, workerNodeGroupConfiguration.Name, p.templateBuilder.now) workloadTemplateNames[workerNodeGroupConfiguration.Name] = workloadTemplateName } } if newClusterSpec.Cluster.Spec.ExternalEtcdConfiguration != nil { etcdMachineConfig := newClusterSpec.VSphereMachineConfigs[newClusterSpec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name] etcdMachineVmc, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, c.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name, workloadCluster.KubeconfigFile, newClusterSpec.Cluster.Namespace) if err != nil { return nil, nil, err } needsNewEtcdTemplate = NeedsNewEtcdTemplate(currentSpec, newClusterSpec, vdc, newClusterSpec.VSphereDatacenter, etcdMachineVmc, etcdMachineConfig) if !needsNewEtcdTemplate { etcdadmCluster, err := p.providerKubectlClient.GetEtcdadmCluster(ctx, workloadCluster, clusterName, executables.WithCluster(bootstrapCluster), executables.WithNamespace(constants.EksaSystemNamespace)) if err != nil { return nil, nil, err } etcdTemplateName = etcdadmCluster.Spec.InfrastructureTemplate.Name } else { /* During a cluster upgrade, etcd machines need to be upgraded first, so that the etcd machines with new spec get created and can be used by controlplane machines as etcd endpoints. KCP rollout should not start until then. As a temporary solution in the absence of static etcd endpoints, we annotate the etcd cluster as "upgrading", so that KCP checks this annotation and does not proceed if etcd cluster is upgrading. The etcdadm controller removes this annotation once the etcd upgrade is complete. */ err = p.providerKubectlClient.UpdateAnnotation(ctx, "etcdadmcluster", fmt.Sprintf("%s-etcd", clusterName), map[string]string{etcdv1.UpgradeInProgressAnnotation: "true"}, executables.WithCluster(bootstrapCluster), executables.WithNamespace(constants.EksaSystemNamespace)) if err != nil { return nil, nil, err } etcdTemplateName = common.EtcdMachineTemplateName(clusterName, p.templateBuilder.now) } } cpOpt := func(values map[string]interface{}) { values["controlPlaneTemplateName"] = controlPlaneTemplateName values["etcdTemplateName"] = etcdTemplateName } controlPlaneSpec, err = p.templateBuilder.GenerateCAPISpecControlPlane(newClusterSpec, cpOpt) if err != nil { return nil, nil, err } workersSpec, err = p.templateBuilder.GenerateCAPISpecWorkers(newClusterSpec, workloadTemplateNames, kubeadmconfigTemplateNames) if err != nil { return nil, nil, err } return controlPlaneSpec, workersSpec, nil } func (p *vsphereProvider) generateCAPISpecForCreate(ctx context.Context, clusterSpec *cluster.Spec) (controlPlaneSpec, workersSpec []byte, err error) { clusterName := clusterSpec.Cluster.Name cpOpt := func(values map[string]interface{}) { values["controlPlaneTemplateName"] = common.CPMachineTemplateName(clusterName, p.templateBuilder.now) values["etcdTemplateName"] = common.EtcdMachineTemplateName(clusterName, p.templateBuilder.now) } controlPlaneSpec, err = p.templateBuilder.GenerateCAPISpecControlPlane(clusterSpec, cpOpt) if err != nil { return nil, nil, err } // TODO(g-gaston): update this to use the new method CAPIWorkersSpecWithInitialNames. // That implies moving to monotonically increasing names instead of based on timestamp. // Upgrades should also be moved to that naming scheme for consistency. That requires bigger changes. workloadTemplateNames := make(map[string]string, len(clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations)) kubeadmconfigTemplateNames := make(map[string]string, len(clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations)) for _, workerNodeGroupConfiguration := range clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations { workloadTemplateNames[workerNodeGroupConfiguration.Name] = common.WorkerMachineTemplateName(clusterSpec.Cluster.Name, workerNodeGroupConfiguration.Name, p.templateBuilder.now) kubeadmconfigTemplateNames[workerNodeGroupConfiguration.Name] = common.KubeadmConfigTemplateName(clusterSpec.Cluster.Name, workerNodeGroupConfiguration.Name, p.templateBuilder.now) } workersSpec, err = p.templateBuilder.GenerateCAPISpecWorkers(clusterSpec, workloadTemplateNames, kubeadmconfigTemplateNames) if err != nil { return nil, nil, err } return controlPlaneSpec, workersSpec, nil } func (p *vsphereProvider) GenerateCAPISpecForUpgrade(ctx context.Context, bootstrapCluster, workloadCluster *types.Cluster, currentSpec, clusterSpec *cluster.Spec) (controlPlaneSpec, workersSpec []byte, err error) { controlPlaneSpec, workersSpec, err = p.generateCAPISpecForUpgrade(ctx, bootstrapCluster, workloadCluster, currentSpec, clusterSpec) if err != nil { return nil, nil, fmt.Errorf("generating cluster api spec contents: %v", err) } return controlPlaneSpec, workersSpec, nil } func (p *vsphereProvider) GenerateCAPISpecForCreate(ctx context.Context, _ *types.Cluster, clusterSpec *cluster.Spec) (controlPlaneSpec, workersSpec []byte, err error) { controlPlaneSpec, workersSpec, err = p.generateCAPISpecForCreate(ctx, clusterSpec) if err != nil { return nil, nil, fmt.Errorf("generating cluster api spec contents: %v", err) } return controlPlaneSpec, workersSpec, nil } func (p *vsphereProvider) createSecret(ctx context.Context, cluster *types.Cluster, contents *bytes.Buffer) error { t, err := template.New("tmpl").Funcs(sprig.TxtFuncMap()).Parse(defaultSecretObject) if err != nil { return fmt.Errorf("creating secret object template: %v", err) } vuc := config.NewVsphereUserConfig() values := map[string]string{ "vspherePassword": os.Getenv(vSpherePasswordKey), "vsphereUsername": os.Getenv(vSphereUsernameKey), "eksaCloudProviderUsername": vuc.EksaVsphereCPUsername, "eksaCloudProviderPassword": vuc.EksaVsphereCPPassword, "eksaLicense": os.Getenv(eksaLicense), "eksaSystemNamespace": constants.EksaSystemNamespace, "vsphereCredentialsName": constants.VSphereCredentialsName, "eksaLicenseName": constants.EksaLicenseName, } err = t.Execute(contents, values) if err != nil { return fmt.Errorf("substituting values for secret object template: %v", err) } return nil } func (p *vsphereProvider) PreCAPIInstallOnBootstrap(ctx context.Context, cluster *types.Cluster, clusterSpec *cluster.Spec) error { return p.UpdateSecrets(ctx, cluster, nil) } func (p *vsphereProvider) PostBootstrapSetup(ctx context.Context, clusterConfig *v1alpha1.Cluster, cluster *types.Cluster) error { return nil } func (p *vsphereProvider) PostBootstrapSetupUpgrade(ctx context.Context, clusterConfig *v1alpha1.Cluster, cluster *types.Cluster) error { return nil } func (p *vsphereProvider) PostWorkloadInit(ctx context.Context, cluster *types.Cluster, clusterSpec *cluster.Spec) error { return nil } // EnvMap returns a map of environment variables required for the vsphere provider. func (p *vsphereProvider) EnvMap(_ *cluster.ManagementComponents, _ *cluster.Spec) (map[string]string, error) { envMap := make(map[string]string) for _, key := range requiredEnvs { if env, ok := os.LookupEnv(key); ok && len(env) > 0 { envMap[key] = env } else { return envMap, fmt.Errorf("warning required env not set %s", key) } } return envMap, nil } func (p *vsphereProvider) GetDeployments() map[string][]string { return map[string][]string{ "capv-system": {"capv-controller-manager"}, } } func (p *vsphereProvider) DatacenterConfig(spec *cluster.Spec) providers.DatacenterConfig { return spec.VSphereDatacenter } func (p *vsphereProvider) MachineConfigs(spec *cluster.Spec) []providers.MachineConfig { annotateMachineConfig( spec, spec.Cluster.Spec.ControlPlaneConfiguration.MachineGroupRef.Name, spec.Cluster.ControlPlaneAnnotation(), "true", ) if spec.Cluster.Spec.ExternalEtcdConfiguration != nil { annotateMachineConfig( spec, spec.Cluster.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name, spec.Cluster.EtcdAnnotation(), "true", ) } for _, workerNodeGroupConfiguration := range p.clusterConfig.Spec.WorkerNodeGroupConfigurations { setMachineConfigManagedBy( spec, workerNodeGroupConfiguration.MachineGroupRef.Name, ) } machineConfigs := make([]providers.MachineConfig, 0, len(spec.VSphereMachineConfigs)) for _, m := range spec.VSphereMachineConfigs { machineConfigs = append(machineConfigs, m) } return machineConfigs } func annotateMachineConfig(spec *cluster.Spec, machineConfigName, annotationKey, annotationValue string) { machineConfig := spec.VSphereMachineConfigs[machineConfigName] if machineConfig.Annotations == nil { machineConfig.Annotations = make(map[string]string, 1) } machineConfig.Annotations[annotationKey] = annotationValue setMachineConfigManagedBy(spec, machineConfigName) } func setMachineConfigManagedBy(spec *cluster.Spec, machineConfigName string) { machineConfig := spec.VSphereMachineConfigs[machineConfigName] if machineConfig.Annotations == nil { machineConfig.Annotations = make(map[string]string, 1) } if spec.Cluster.IsManaged() { machineConfig.SetManagedBy(spec.Cluster.ManagedBy()) } } func (p *vsphereProvider) ValidateNewSpec(ctx context.Context, cluster *types.Cluster, clusterSpec *cluster.Spec) error { prevSpec, err := p.providerKubectlClient.GetEksaCluster(ctx, cluster, clusterSpec.Cluster.Name) if err != nil { return err } prevDatacenter, err := p.providerKubectlClient.GetEksaVSphereDatacenterConfig(ctx, prevSpec.Spec.DatacenterRef.Name, cluster.KubeconfigFile, prevSpec.Namespace) if err != nil { return err } datacenter := clusterSpec.VSphereDatacenter oSpec := prevDatacenter.Spec nSpec := datacenter.Spec prevMachineConfigRefs := machineRefSliceToMap(prevSpec.MachineConfigRefs()) for _, machineConfigRef := range clusterSpec.Cluster.MachineConfigRefs() { machineConfig, ok := clusterSpec.VSphereMachineConfigs[machineConfigRef.Name] if !ok { return fmt.Errorf("cannot find machine config %s in vsphere provider machine configs", machineConfigRef.Name) } if _, ok = prevMachineConfigRefs[machineConfig.Name]; ok { err = p.validateMachineConfigImmutability(ctx, cluster, machineConfig, clusterSpec) if err != nil { return err } } } if nSpec.Server != oSpec.Server { return fmt.Errorf("spec.server is immutable. Previous value %s, new value %s", oSpec.Server, nSpec.Server) } if nSpec.Datacenter != oSpec.Datacenter { return fmt.Errorf("spec.datacenter is immutable. Previous value %s, new value %s", oSpec.Datacenter, nSpec.Datacenter) } if nSpec.Network != oSpec.Network { return fmt.Errorf("spec.network is immutable. Previous value %s, new value %s", oSpec.Network, nSpec.Network) } secretChanged, err := p.secretContentsChanged(ctx, cluster) if err != nil { return err } if secretChanged { return fmt.Errorf("the VSphere credentials derived from %s and %s are immutable; please use the same credentials for the upgraded cluster", vSpherePasswordKey, vSphereUsernameKey) } return nil } func (p *vsphereProvider) getWorkerNodeMachineConfigs(ctx context.Context, workloadCluster *types.Cluster, newClusterSpec *cluster.Spec, workerNodeGroupConfiguration v1alpha1.WorkerNodeGroupConfiguration, prevWorkerNodeGroupConfigs map[string]v1alpha1.WorkerNodeGroupConfiguration) (*v1alpha1.VSphereMachineConfig, *v1alpha1.VSphereMachineConfig, error) { if oldWorkerNodeGroup, ok := prevWorkerNodeGroupConfigs[workerNodeGroupConfiguration.Name]; ok { newWorkerMachineConfig := newClusterSpec.VSphereMachineConfigs[workerNodeGroupConfiguration.MachineGroupRef.Name] oldWorkerMachineConfig, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, oldWorkerNodeGroup.MachineGroupRef.Name, workloadCluster.KubeconfigFile, newClusterSpec.Cluster.Namespace) if err != nil { return nil, newWorkerMachineConfig, err } return oldWorkerMachineConfig, newWorkerMachineConfig, nil } return nil, nil, nil } func (p *vsphereProvider) needsNewMachineTemplate(currentSpec, newClusterSpec *cluster.Spec, workerNodeGroupConfiguration v1alpha1.WorkerNodeGroupConfiguration, vdc *v1alpha1.VSphereDatacenterConfig, prevWorkerNodeGroupConfigs map[string]v1alpha1.WorkerNodeGroupConfiguration, oldWorkerMachineConfig, newWorkerMachineConfig *v1alpha1.VSphereMachineConfig) (bool, error) { if prevWorkerNode, ok := prevWorkerNodeGroupConfigs[workerNodeGroupConfiguration.Name]; ok { needsNewWorkloadTemplate := NeedsNewWorkloadTemplate(currentSpec, newClusterSpec, vdc, newClusterSpec.VSphereDatacenter, oldWorkerMachineConfig, newWorkerMachineConfig, prevWorkerNode, workerNodeGroupConfiguration) return needsNewWorkloadTemplate, nil } return true, nil } func (p *vsphereProvider) needsNewKubeadmConfigTemplate(workerNodeGroupConfiguration v1alpha1.WorkerNodeGroupConfiguration, prevWorkerNodeGroupConfigs map[string]v1alpha1.WorkerNodeGroupConfiguration, oldWorkerNodeVmc, newWorkerNodeVmc *v1alpha1.VSphereMachineConfig) (bool, error) { if _, ok := prevWorkerNodeGroupConfigs[workerNodeGroupConfiguration.Name]; ok { existingWorkerNodeGroupConfig := prevWorkerNodeGroupConfigs[workerNodeGroupConfiguration.Name] return NeedsNewKubeadmConfigTemplate(&workerNodeGroupConfiguration, &existingWorkerNodeGroupConfig, oldWorkerNodeVmc, newWorkerNodeVmc), nil } return true, nil } func (p *vsphereProvider) validateMachineConfigImmutability(ctx context.Context, cluster *types.Cluster, newConfig *v1alpha1.VSphereMachineConfig, clusterSpec *cluster.Spec) error { prevMachineConfig, err := p.providerKubectlClient.GetEksaVSphereMachineConfig(ctx, newConfig.Name, cluster.KubeconfigFile, clusterSpec.Cluster.Namespace) if err != nil { return err } if newConfig.Spec.StoragePolicyName != prevMachineConfig.Spec.StoragePolicyName { return fmt.Errorf("spec.storagePolicyName is immutable. Previous value %s, new value %s", prevMachineConfig.Spec.StoragePolicyName, newConfig.Spec.StoragePolicyName) } if newConfig.Spec.OSFamily != prevMachineConfig.Spec.OSFamily { return fmt.Errorf("spec.osFamily is immutable. Previous value %v, new value %v", prevMachineConfig.Spec.OSFamily, newConfig.Spec.OSFamily) } return nil } func (p *vsphereProvider) secretContentsChanged(ctx context.Context, workloadCluster *types.Cluster) (bool, error) { nPassword := os.Getenv(vSpherePasswordKey) oSecret, err := p.providerKubectlClient.GetSecretFromNamespace(ctx, workloadCluster.KubeconfigFile, CredentialsObjectName, constants.EksaSystemNamespace) if err != nil { return false, fmt.Errorf("obtaining VSphere secret %s from workload cluster: %v", CredentialsObjectName, err) } if string(oSecret.Data["password"]) != nPassword { return true, nil } nUser := os.Getenv(vSphereUsernameKey) if string(oSecret.Data["username"]) != nUser { return true, nil } return false, nil } // ChangeDiff returns the component change diff for the provider. func (p *vsphereProvider) ChangeDiff(currentComponents, newComponents *cluster.ManagementComponents) *types.ComponentChangeDiff { if currentComponents.VSphere.Version == newComponents.VSphere.Version { return nil } return &types.ComponentChangeDiff{ ComponentName: constants.VSphereProviderName, NewVersion: newComponents.VSphere.Version, OldVersion: currentComponents.VSphere.Version, } } // GetInfrastructureBundle returns the infrastructure bundle for the provider. func (p *vsphereProvider) GetInfrastructureBundle(components *cluster.ManagementComponents) *types.InfrastructureBundle { folderName := fmt.Sprintf("infrastructure-vsphere/%s/", components.VSphere.Version) infraBundle := types.InfrastructureBundle{ FolderName: folderName, Manifests: []releasev1alpha1.Manifest{ components.VSphere.Components, components.VSphere.Metadata, components.VSphere.ClusterTemplate, }, } return &infraBundle } // Version returns the version of the provider. func (p *vsphereProvider) Version(components *cluster.ManagementComponents) string { return components.VSphere.Version } func (p *vsphereProvider) RunPostControlPlaneUpgrade(_ context.Context, _, _ *cluster.Spec, _, _ *types.Cluster) error { return nil } func cpiResourceSetName(clusterSpec *cluster.Spec) string { return fmt.Sprintf("%s-cpi", clusterSpec.Cluster.Name) } func (p *vsphereProvider) UpgradeNeeded(ctx context.Context, newSpec, currentSpec *cluster.Spec, c *types.Cluster) (bool, error) { currentVersionsBundle := currentSpec.RootVersionsBundle() newVersionsBundle := newSpec.RootVersionsBundle() newV, oldV := newVersionsBundle.VSphere, currentVersionsBundle.VSphere if newV.Manager.ImageDigest != oldV.Manager.ImageDigest || newV.KubeVip.ImageDigest != oldV.KubeVip.ImageDigest { return true, nil } cc := currentSpec.Cluster existingVdc, err := p.providerKubectlClient.GetEksaVSphereDatacenterConfig(ctx, cc.Spec.DatacenterRef.Name, c.KubeconfigFile, newSpec.Cluster.Namespace) if err != nil { return false, err } if !reflect.DeepEqual(existingVdc.Spec, newSpec.VSphereDatacenter.Spec) { logger.V(3).Info("New provider spec is different from the new spec") return true, nil } machineConfigsSpecChanged, err := p.machineConfigsSpecChanged(ctx, cc, c, newSpec) if err != nil { return false, err } return machineConfigsSpecChanged, nil } func machineRefSliceToMap(machineRefs []v1alpha1.Ref) map[string]v1alpha1.Ref { refMap := make(map[string]v1alpha1.Ref, len(machineRefs)) for _, ref := range machineRefs { refMap[ref.Name] = ref } return refMap } func machineDeploymentName(clusterName, nodeGroupName string) string { return fmt.Sprintf("%s-%s", clusterName, nodeGroupName) } func (p *vsphereProvider) InstallCustomProviderComponents(ctx context.Context, kubeconfigFile string) error { return nil } // PostBootstrapDeleteForUpgrade runs any provider-specific operations after bootstrap cluster has been deleted. func (p *vsphereProvider) PostBootstrapDeleteForUpgrade(ctx context.Context, cluster *types.Cluster) error { return nil } // PreCoreComponentsUpgrade satisfies the Provider interface. func (p *vsphereProvider) PreCoreComponentsUpgrade( ctx context.Context, cluster *types.Cluster, managementComponents *cluster.ManagementComponents, clusterSpec *cluster.Spec, ) error { return nil }