Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions controlplane/kubeadm/api/v1alpha3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,44 @@ limitations under the License.
package v1alpha3

import (
apiconversion "k8s.io/apimachinery/pkg/conversion"
"sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha4"

utilconversion "sigs.k8s.io/cluster-api/util/conversion"
"sigs.k8s.io/controller-runtime/pkg/conversion"
)

func (src *KubeadmControlPlane) ConvertTo(destRaw conversion.Hub) error {
dest := destRaw.(*v1alpha4.KubeadmControlPlane)
return Convert_v1alpha3_KubeadmControlPlane_To_v1alpha4_KubeadmControlPlane(src, dest, nil)

if err := Convert_v1alpha3_KubeadmControlPlane_To_v1alpha4_KubeadmControlPlane(src, dest, nil); err != nil {
return err
}

// Manually restore data.
restored := &v1alpha4.KubeadmControlPlane{}
if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
return err
}

dest.Spec.RolloutStrategy = restored.Spec.RolloutStrategy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we do backport to v1alpha3 wouldn't this be a problem?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do backport, we need to remove this part of the code and say something like Required minimum v0.3.x version before upgrading is ...


return nil
}

func (dest *KubeadmControlPlane) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*v1alpha4.KubeadmControlPlane)
return Convert_v1alpha4_KubeadmControlPlane_To_v1alpha3_KubeadmControlPlane(src, dest, nil)

if err := Convert_v1alpha4_KubeadmControlPlane_To_v1alpha3_KubeadmControlPlane(src, dest, nil); err != nil {
return err
}

// Preserve Hub data on down-conversion except for metadata
if err := utilconversion.MarshalData(src, dest); err != nil {
return err
}

return nil
}

func (src *KubeadmControlPlaneList) ConvertTo(destRaw conversion.Hub) error {
Expand All @@ -40,3 +66,11 @@ func (dest *KubeadmControlPlaneList) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*v1alpha4.KubeadmControlPlaneList)
return Convert_v1alpha4_KubeadmControlPlaneList_To_v1alpha3_KubeadmControlPlaneList(src, dest, nil)
}

func Convert_v1alpha4_KubeadmControlPlaneSpec_To_v1alpha3_KubeadmControlPlaneSpec(in *v1alpha4.KubeadmControlPlaneSpec, out *KubeadmControlPlaneSpec, s apiconversion.Scope) error {
return autoConvert_v1alpha4_KubeadmControlPlaneSpec_To_v1alpha3_KubeadmControlPlaneSpec(in, out, s)
}

func Convert_v1alpha3_KubeadmControlPlaneSpec_To_v1alpha4_KubeadmControlPlaneSpec(in *KubeadmControlPlaneSpec, out *v1alpha4.KubeadmControlPlaneSpec, s apiconversion.Scope) error { //nolint
return autoConvert_v1alpha3_KubeadmControlPlaneSpec_To_v1alpha4_KubeadmControlPlaneSpec(in, out, s)
}
25 changes: 24 additions & 1 deletion controlplane/kubeadm/api/v1alpha3/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ package v1alpha3
import (
"testing"

fuzz "github.com/google/gofuzz"
. "github.com/onsi/gomega"

"k8s.io/apimachinery/pkg/runtime"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
"sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha4"
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
)
Expand All @@ -32,5 +35,25 @@ func TestFuzzyConversion(t *testing.T) {
g.Expect(AddToScheme(scheme)).To(Succeed())
g.Expect(v1alpha4.AddToScheme(scheme)).To(Succeed())

t.Run("for KubeadmControlPLane", utilconversion.FuzzTestFunc(scheme, &v1alpha4.KubeadmControlPlane{}, &KubeadmControlPlane{}))
t.Run("for KubeadmControlPLane", utilconversion.FuzzTestFunc(
scheme, &v1alpha4.KubeadmControlPlane{}, &KubeadmControlPlane{},
func(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
// This custom function is needed when ConvertTo/ConvertFrom functions
// uses the json package to unmarshal the bootstrap token string.
//
// The Kubeadm v1beta1.BootstrapTokenString type ships with a custom
// json string representation, in particular it supplies a customized
// UnmarshalJSON function that can return an error if the string
// isn't in the correct form.
//
// This function effectively disables any fuzzing for the token by setting
// the values for ID and Secret to working alphanumeric values.
func(in *kubeadmv1.BootstrapTokenString, c fuzz.Continue) {
in.ID = "abcdef"
in.Secret = "abcdef0123456789"
},
}
},
))
}
27 changes: 9 additions & 18 deletions controlplane/kubeadm/api/v1alpha3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ package v1alpha4
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"

cabpkv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha4"
"sigs.k8s.io/cluster-api/errors"
)

type RolloutStrategyType string

const (
// Replace the old control planes by new one using rolling update
// i.e. gradually scale up or down the old control planes and scale up or down the new one.
RollingUpdateStrategyType RolloutStrategyType = "RollingUpdate"
)

const (
KubeadmControlPlaneFinalizer = "kubeadm.controlplane.cluster.x-k8s.io"

Expand Down Expand Up @@ -69,6 +78,38 @@ type KubeadmControlPlaneSpec struct {
// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
// +optional
NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout,omitempty"`

// The RolloutStrategy to use to replace control plane machines with
// new ones.
// +optional
RolloutStrategy *RolloutStrategy `json:"rolloutStrategy,omitempty"`
}

// RolloutStrategy describes how to replace existing machines
// with new ones.
type RolloutStrategy struct {
// Type of rollout. Currently the only supported strategy is
// "RollingUpdate".
// Default is RollingUpdate.
// +optional
Type RolloutStrategyType `json:"type,omitempty"`

// Rolling update config params. Present only if
// RolloutStrategyType = RollingUpdate.
// +optional
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
}

// RollingUpdate is used to control the desired behavior of rolling update.
type RollingUpdate struct {
// The maximum number of control planes that can be scheduled above or under the
// desired number of control planes.
// Value can be an absolute number 1 or 0.
// Defaults to 1.
// Example: when this is set to 1, the control plane can be scaled
// up immediately when the rolling update starts.
// +optional
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty"`
}

// KubeadmControlPlaneStatus defines the observed state of KubeadmControlPlane.
Expand Down
57 changes: 57 additions & 0 deletions controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
"sigs.k8s.io/cluster-api/util/container"
Expand Down Expand Up @@ -61,6 +62,25 @@ func (in *KubeadmControlPlane) Default() {
if !strings.HasPrefix(in.Spec.Version, "v") {
in.Spec.Version = "v" + in.Spec.Version
}

ios1 := intstr.FromInt(1)

if in.Spec.RolloutStrategy == nil {
in.Spec.RolloutStrategy = &RolloutStrategy{}
}

// Enforce RollingUpdate strategy and default MaxSurge if not set.
if in.Spec.RolloutStrategy != nil {
if len(in.Spec.RolloutStrategy.Type) == 0 {
in.Spec.RolloutStrategy.Type = RollingUpdateStrategyType
}
if in.Spec.RolloutStrategy.Type == RollingUpdateStrategyType {
if in.Spec.RolloutStrategy.RollingUpdate == nil {
in.Spec.RolloutStrategy.RollingUpdate = &RollingUpdate{}
}
in.Spec.RolloutStrategy.RollingUpdate.MaxSurge = intstr.ValueOrDefault(in.Spec.RolloutStrategy.RollingUpdate.MaxSurge, ios1)
}
}
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
Expand Down Expand Up @@ -116,6 +136,7 @@ func (in *KubeadmControlPlane) ValidateUpdate(old runtime.Object) error {
{spec, "version"},
{spec, "upgradeAfter"},
{spec, "nodeDrainTimeout"},
{spec, "rolloutStrategy"},
}

allErrs := in.validateCommon()
Expand Down Expand Up @@ -269,6 +290,42 @@ func (in *KubeadmControlPlane) validateCommon() (allErrs field.ErrorList) {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "version"), in.Spec.Version, "must be a valid semantic version"))
}

if in.Spec.RolloutStrategy != nil {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we error out in case in.Spec.RolloutStrategy == nil?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can if in.Spec.RolloutStrategy == nil ever be true? I assumed that defaulting on line 68 makes sure that the value is never nil. Or have I misunderstood?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the defaulting function could change at some point, I personally prefer to have each func self consistent (or at least to explicitly document the assumption each function relies on, especially if those assumptions depends on something somewhere else in the codebase), but this is just a nit, feel free to ignore it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification @fabriziopandini.


Comment thread
jan-est marked this conversation as resolved.
if in.Spec.RolloutStrategy.Type != RollingUpdateStrategyType {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutStrategy", "type"),
"only RollingUpdateStrategyType is supported",
),
)
}

ios1 := intstr.FromInt(1)
ios0 := intstr.FromInt(0)

if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge == ios0 && *in.Spec.Replicas < int32(3) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check for RollingUpdate != nil and error out in case this is not true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabriziopandini do you see any cases where RollingUpdate != nil is not true? I assumed that defaulting on line 68 makes sure that the value is never nil. Or have I misunderstood?

allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutStrategy", "rollingUpdate"),
"when KubeadmControlPlane is configured to scale-in, replica count needs to be at least 3",
),
)
}

if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios1 && *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios0 {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutStrategy", "rollingUpdate", "maxSurge"),
"value must be 1 or 0",
),
)
}
}

allErrs = append(allErrs, in.validateCoreDNSImage()...)

return allErrs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha4"
kubeadmv1beta1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
Expand All @@ -37,14 +38,18 @@ func TestKubeadmControlPlaneDefault(t *testing.T) {
Namespace: "foo",
},
Spec: KubeadmControlPlaneSpec{
InfrastructureTemplate: corev1.ObjectReference{},
Version: "1.18.3",
InfrastructureTemplate: corev1.ObjectReference{},
RolloutStrategy: &RolloutStrategy{},
},
}
kcp.Default()

g.Expect(kcp.Spec.InfrastructureTemplate.Namespace).To(Equal(kcp.Namespace))
g.Expect(kcp.Spec.Version).To(Equal("v1.18.3"))
g.Expect(kcp.Spec.RolloutStrategy.Type).To(Equal(RollingUpdateStrategyType))
g.Expect(kcp.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal).To(Equal(int32(1)))

}

func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
Expand All @@ -60,8 +65,20 @@ func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
},
Replicas: pointer.Int32Ptr(1),
Version: "v1.19.0",
RolloutStrategy: &RolloutStrategy{
Type: RollingUpdateStrategyType,
RollingUpdate: &RollingUpdate{
MaxSurge: &intstr.IntOrString{
IntVal: 1,
},
},
},
},
}

invalidMaxSurge := valid.DeepCopy()
invalidMaxSurge.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal = int32(3)

invalidNamespace := valid.DeepCopy()
invalidNamespace.Spec.InfrastructureTemplate.Namespace = "bar"

Expand Down Expand Up @@ -142,6 +159,11 @@ func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
expectErr: true,
kcp: invalidVersion1,
},
{
name: "should return error when maxSurge is not 1",
expectErr: true,
kcp: invalidMaxSurge,
},
}

for _, tt := range tests {
Expand Down
Loading