diff --git a/pkg/cluster/internal/create/actions/createworker/createworker.go b/pkg/cluster/internal/create/actions/createworker/createworker.go index ba416930fa..001661bfe5 100644 --- a/pkg/cluster/internal/create/actions/createworker/createworker.go +++ b/pkg/cluster/internal/create/actions/createworker/createworker.go @@ -544,7 +544,7 @@ func (a *action) Execute(ctx *actions.ActionContext) error { ctx.Status.Start("Enabling workload cluster's self-healing 🏥") defer ctx.Status.End(false) - err = enableSelfHealing(n, a.keosCluster, capiClustersNamespace) + err = enableSelfHealing(n, a.keosCluster, capiClustersNamespace, a.clusterConfig) if err != nil { return errors.Wrap(err, "failed to enable workload cluster's self-healing") } diff --git a/pkg/cluster/internal/create/actions/createworker/provider.go b/pkg/cluster/internal/create/actions/createworker/provider.go index e4e3a2e57e..32000a1cb2 100644 --- a/pkg/cluster/internal/create/actions/createworker/provider.go +++ b/pkg/cluster/internal/create/actions/createworker/provider.go @@ -369,19 +369,18 @@ func (p *Provider) deployClusterOperator(n nodes.Node, privateParams PrivatePara } keosCluster.Spec.Keos = commons.Keos{} - if clusterConfig != nil { - clusterConfigYAML, err := yaml.Marshal(clusterConfig) - if err != nil { - return err - } - // Write keoscluster file - c = "echo '" + string(clusterConfigYAML) + "' > " + manifestsPath + "/clusterconfig.yaml" - _, err = commons.ExecuteCommand(n, c, 5) - if err != nil { - return errors.Wrap(err, "failed to write the keoscluster file") - } - keosCluster.Spec.ClusterConfigRef.Name = clusterConfig.Metadata.Name + clusterConfigYAML, err := yaml.Marshal(clusterConfig) + if err != nil { + return err + } + // Write keoscluster file + c = "echo '" + string(clusterConfigYAML) + "' > " + manifestsPath + "/clusterconfig.yaml" + _, err = commons.ExecuteCommand(n, c, 5) + if err != nil { + return errors.Wrap(err, "failed to write the keoscluster file") } + keosCluster.Spec.ClusterConfigRef.Name = clusterConfig.Metadata.Name + keosClusterYAML, err := yaml.Marshal(keosCluster) if err != nil { return err @@ -860,13 +859,20 @@ func (p *Provider) installCAPXLocal(n nodes.Node) error { return nil } -func enableSelfHealing(n nodes.Node, keosCluster commons.KeosCluster, namespace string) error { +func enableSelfHealing(n nodes.Node, keosCluster commons.KeosCluster, namespace string, clusterConfig *commons.ClusterConfig) error { var c string var err error if !keosCluster.Spec.ControlPlane.Managed { machineRole := "-control-plane-node" - generateMHCManifest(n, keosCluster.Metadata.Name, namespace, machineHealthCheckControlPlaneNodePath, machineRole) + controlplane_maxunhealty := 34 + if clusterConfig != nil { + if clusterConfig.Spec.ControlplaneConfig.MaxUnhealthy != nil { + controlplane_maxunhealty = *clusterConfig.Spec.ControlplaneConfig.MaxUnhealthy + } + } + + generateMHCManifest(n, keosCluster.Metadata.Name, namespace, machineHealthCheckControlPlaneNodePath, machineRole, controlplane_maxunhealty) c = "kubectl -n " + namespace + " apply -f " + machineHealthCheckControlPlaneNodePath _, err = commons.ExecuteCommand(n, c, 5) @@ -876,7 +882,13 @@ func enableSelfHealing(n nodes.Node, keosCluster commons.KeosCluster, namespace } machineRole := "-worker-node" - generateMHCManifest(n, keosCluster.Metadata.Name, namespace, machineHealthCheckWorkerNodePath, machineRole) + workernode_maxunhealty := 34 + if clusterConfig != nil { + if clusterConfig.Spec.WorkersConfig.MaxUnhealthy != nil { + workernode_maxunhealty = *clusterConfig.Spec.WorkersConfig.MaxUnhealthy + } + } + generateMHCManifest(n, keosCluster.Metadata.Name, namespace, machineHealthCheckWorkerNodePath, machineRole, workernode_maxunhealty) c = "kubectl -n " + namespace + " apply -f " + machineHealthCheckWorkerNodePath _, err = commons.ExecuteCommand(n, c, 5) @@ -887,14 +899,11 @@ func enableSelfHealing(n nodes.Node, keosCluster commons.KeosCluster, namespace return nil } -func generateMHCManifest(n nodes.Node, clusterID string, namespace string, manifestPath string, machineRole string) error { +func generateMHCManifest(n nodes.Node, clusterID string, namespace string, manifestPath string, machineRole string, maxunhealthy int) error { var c string var err error - var maxUnhealthy = "100%" + var maxUnhealthy = strconv.Itoa(maxunhealthy) + "%" - if strings.Contains(machineRole, "control-plane-node") { - maxUnhealthy = "34%" - } var machineHealthCheck = ` apiVersion: cluster.x-k8s.io/v1beta1 kind: MachineHealthCheck diff --git a/pkg/cluster/internal/validate/common.go b/pkg/cluster/internal/validate/common.go index 9efaf9a238..f220f6ee62 100644 --- a/pkg/cluster/internal/validate/common.go +++ b/pkg/cluster/internal/validate/common.go @@ -34,7 +34,7 @@ const ( var k8sVersionSupported = []string{"1.24", "1.25", "1.26", "1.27", "1.28"} -func validateCommon(spec commons.KeosSpec) error { +func validateCommon(spec commons.KeosSpec, clusterConfigSpec commons.ClusterConfigSpec) error { var err error if err = validateK8SVersion(spec.K8SVersion); err != nil { return err @@ -45,6 +45,18 @@ func validateCommon(spec commons.KeosSpec) error { if err = validateVolumes(spec); err != nil { return err } + if err = validateClusterConfig(spec, clusterConfigSpec); err != nil { + return err + } + return nil +} + +func validateClusterConfig(spec commons.KeosSpec, clusterConfigSpec commons.ClusterConfigSpec) error { + if spec.ControlPlane.Managed { + if clusterConfigSpec.ControlplaneConfig.MaxUnhealthy != nil { + return errors.New("spec: Invalid value: \"controlplane_config.max_unhealthy\" in clusterConfig: This field cannot be set with managed cluster") + } + } return nil } diff --git a/pkg/cluster/internal/validate/validate.go b/pkg/cluster/internal/validate/validate.go index 135438a0d1..6195920013 100644 --- a/pkg/cluster/internal/validate/validate.go +++ b/pkg/cluster/internal/validate/validate.go @@ -22,6 +22,7 @@ import ( type ValidateParams struct { KeosCluster commons.KeosCluster + ClusterConfig *commons.ClusterConfig SecretsPath string VaultPassword string } @@ -34,8 +35,11 @@ func Cluster(params *ValidateParams) (commons.ClusterCredentials, error) { if err != nil { return commons.ClusterCredentials{}, err } - - if err := validateCommon(params.KeosCluster.Spec); err != nil { + clusterConfigSpec := commons.ClusterConfigSpec{} + if params.ClusterConfig != nil { + clusterConfigSpec = params.ClusterConfig.Spec + } + if err := validateCommon(params.KeosCluster.Spec, clusterConfigSpec); err != nil { return commons.ClusterCredentials{}, err } diff --git a/pkg/cluster/provider.go b/pkg/cluster/provider.go index 7f1767008f..cb1a847249 100644 --- a/pkg/cluster/provider.go +++ b/pkg/cluster/provider.go @@ -255,9 +255,10 @@ func (p *Provider) CollectLogs(name, dir string) error { return p.provider.CollectLogs(dir, n) } -func (p *Provider) Validate(keosCluster commons.KeosCluster, secretsPath string, vaultPassword string) (commons.ClusterCredentials, error) { +func (p *Provider) Validate(keosCluster commons.KeosCluster, clusterConfig *commons.ClusterConfig, secretsPath string, vaultPassword string) (commons.ClusterCredentials, error) { params := &internalvalidate.ValidateParams{ KeosCluster: keosCluster, + ClusterConfig: clusterConfig, SecretsPath: secretsPath, VaultPassword: vaultPassword, } diff --git a/pkg/cmd/kind/create/cluster/createcluster.go b/pkg/cmd/kind/create/cluster/createcluster.go index e1d27f8ed1..f3e5920d9f 100644 --- a/pkg/cmd/kind/create/cluster/createcluster.go +++ b/pkg/cmd/kind/create/cluster/createcluster.go @@ -180,6 +180,7 @@ func runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error { clusterCredentials, err := provider.Validate( *keosCluster, + clusterConfig, secretsDefaultPath, flags.VaultPassword, ) diff --git a/pkg/commons/cluster.go b/pkg/commons/cluster.go index 472e2f4723..03aabd3b0c 100644 --- a/pkg/commons/cluster.go +++ b/pkg/commons/cluster.go @@ -58,9 +58,19 @@ type Metadata struct { } type ClusterConfigSpec struct { - Private bool `yaml:"private_registry"` - ClusterOperatorVersion string `yaml:"cluster_operator_version,omitempty"` - ClusterOperatorImageVersion string `yaml:"cluster_operator_image_version,omitempty"` + Private bool `yaml:"private_registry"` + ControlplaneConfig ControlplaneConfig `yaml:"controlplane_config"` + WorkersConfig WorkersConfig `yaml:"workers_config"` + ClusterOperatorVersion string `yaml:"cluster_operator_version,omitempty"` + ClusterOperatorImageVersion string `yaml:"cluster_operator_image_version,omitempty"` +} + +type ControlplaneConfig struct { + MaxUnhealthy *int `yaml:"max_unhealthy,omitempty" validate:"omitempty,numeric,gte=0,lte=100"` +} + +type WorkersConfig struct { + MaxUnhealthy *int `yaml:"max_unhealthy,omitempty" validate:"omitempty,numeric,gte=0,lte=100"` } type ClusterConfigRef struct { @@ -347,6 +357,7 @@ type SCParameters struct { func (s ClusterConfigSpec) Init() ClusterConfigSpec { s.Private = false + s.WorkersConfig.MaxUnhealthy = ToPtr[int](100) return s } @@ -458,6 +469,9 @@ func GetClusterDescriptor(descriptorPath string) (*KeosCluster, *ClusterConfig, clusterConfig.Metadata.Name = keosCluster.Spec.InfraProvider + "-config" clusterConfig.Metadata.Namespace = "cluster-" + keosCluster.Metadata.Name clusterConfig.Spec = new(ClusterConfigSpec).Init() + if !keosCluster.Spec.ControlPlane.Managed { + clusterConfig.Spec.ControlplaneConfig.MaxUnhealthy = ToPtr[int](34) + } } return &keosCluster, &clusterConfig, nil @@ -592,3 +606,8 @@ func requireCheckFieldValue(fl validator.FieldLevel, param string, value string, return false } + +// Ptr returns a pointer to the provided value. +func ToPtr[T any](v T) *T { + return &v +}