Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Changes to controlplane spec should trigger a rollout #114

Merged
merged 5 commits into from
May 20, 2024
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
28 changes: 27 additions & 1 deletion pkg/machinefilters/machine_filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package machinefilters

import (
"reflect"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util/collections"
Expand Down Expand Up @@ -83,6 +85,30 @@ func MatchesKubernetesVersion(kubernetesVersion string) Func {
// MatchesKThreesBootstrapConfig checks if machine's KThreesConfigSpec is equivalent with KCP's KThreesConfigSpec.
func MatchesKThreesBootstrapConfig(machineConfigs map[string]*bootstrapv1.KThreesConfig, kcp *controlplanev1.KThreesControlPlane) Func {
return func(machine *clusterv1.Machine) bool {
return true
if machine == nil {
return false
}

bootstrapRef := machine.Spec.Bootstrap.ConfigRef
if bootstrapRef == nil {
// Missing bootstrap reference should not be considered as unmatching.
// This is a safety precaution to avoid selecting machines that are broken, which in the future should be remediated separately.
return true
}

machineConfig, found := machineConfigs[machine.Name]
if !found {
// Return true here because failing to get KThreesConfig should not be considered as unmatching.
// This is a safety precaution to avoid rolling out machines if the client or the api-server is misbehaving.
return true
}

kcpConfig := kcp.Spec.KThreesConfigSpec.DeepCopy()

// KCP version check is handled elsewhere
kcpConfig.Version = ""
machineConfig.Spec.Version = ""

return reflect.DeepEqual(&machineConfig.Spec, kcpConfig)
}
}
310 changes: 310 additions & 0 deletions pkg/machinefilters/machine_filters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
package machinefilters

import (
"testing"

. "github.com/onsi/gomega"
"google.golang.org/protobuf/proto"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"

bootstrapv1 "github.com/k3s-io/cluster-api-k3s/bootstrap/api/v1beta2"
controlplanev1 "github.com/k3s-io/cluster-api-k3s/controlplane/api/v1beta2"
)

func TestMatchesKThreesBootstrapConfig(t *testing.T) {
t.Run("returns true if ClusterConfiguration is equal", func(t *testing.T) {
g := NewWithT(t)
kcp := &controlplanev1.KThreesControlPlane{
Spec: controlplanev1.KThreesControlPlaneSpec{
KThreesConfigSpec: bootstrapv1.KThreesConfigSpec{
ServerConfig: bootstrapv1.KThreesServerConfig{
ClusterDomain: "foo",
},
},
},
}
m := &clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
controlplanev1.KThreesServerConfigurationAnnotation: "{\n \"clusterDomain\": \"foo\"\n}",
},
},
}
machineConfigs := map[string]*bootstrapv1.KThreesConfig{
m.Name: {},
}
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeTrue())
})
t.Run("returns false if ClusterConfiguration is NOT equal", func(t *testing.T) {
g := NewWithT(t)
kcp := &controlplanev1.KThreesControlPlane{
Spec: controlplanev1.KThreesControlPlaneSpec{
KThreesConfigSpec: bootstrapv1.KThreesConfigSpec{
ServerConfig: bootstrapv1.KThreesServerConfig{
ClusterDomain: "foo",
},
},
},
}
m := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: clusterv1.MachineSpec{
Bootstrap: clusterv1.Bootstrap{
ConfigRef: &corev1.ObjectReference{
Kind: "KThreesConfig",
Namespace: "default",
Name: "test",
APIVersion: bootstrapv1.GroupVersion.String(),
},
},
},
}
machineConfigs := map[string]*bootstrapv1.KThreesConfig{
m.Name: {
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: bootstrapv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: bootstrapv1.KThreesConfigSpec{
ServerConfig: bootstrapv1.KThreesServerConfig{
ClusterDomain: "bar",
},
},
},
}
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeFalse())
})

t.Run("returns false if some other configurations are not equal", func(t *testing.T) {
g := NewWithT(t)
kcp := &controlplanev1.KThreesControlPlane{
Spec: controlplanev1.KThreesControlPlaneSpec{
KThreesConfigSpec: bootstrapv1.KThreesConfigSpec{
Files: []bootstrapv1.File{}, // This is a change
},
},
}

m := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: clusterv1.MachineSpec{
Bootstrap: clusterv1.Bootstrap{
ConfigRef: &corev1.ObjectReference{
Kind: "KThreesConfig",
Namespace: "default",
Name: "test",
APIVersion: bootstrapv1.GroupVersion.String(),
},
},
},
}
machineConfigs := map[string]*bootstrapv1.KThreesConfig{
m.Name: {
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: bootstrapv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: bootstrapv1.KThreesConfigSpec{},
},
}
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeFalse())
})

t.Run("Should match on other configurations", func(t *testing.T) {
kThreesConfigSpec := bootstrapv1.KThreesConfigSpec{
Files: []bootstrapv1.File{},
PreK3sCommands: []string{"test"},
PostK3sCommands: []string{"test"},
AgentConfig: bootstrapv1.KThreesAgentConfig{
NodeName: "test-node",
NodeTaints: []string{"node-role.kubernetes.io/control-plane:NoSchedule"},
KubeProxyArgs: []string{"metrics-bind-address=0.0.0.0"},
},
ServerConfig: bootstrapv1.KThreesServerConfig{
DisableComponents: []string{"traefik"},
KubeControllerManagerArgs: []string{"bind-address=0.0.0.0"},
KubeSchedulerArgs: []string{"bind-address=0.0.0.0"},
},
}
kcp := &controlplanev1.KThreesControlPlane{
Spec: controlplanev1.KThreesControlPlaneSpec{
Replicas: proto.Int32(3),
Version: "v1.13.14+k3s1",
MachineTemplate: controlplanev1.KThreesControlPlaneMachineTemplate{
ObjectMeta: clusterv1.ObjectMeta{
Labels: map[string]string{"test-label": "test-value"},
},
},
KThreesConfigSpec: kThreesConfigSpec,
},
}

m := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: clusterv1.MachineSpec{
Bootstrap: clusterv1.Bootstrap{
ConfigRef: &corev1.ObjectReference{
Kind: "KThreesConfig",
Namespace: "default",
Name: "test",
APIVersion: bootstrapv1.GroupVersion.String(),
},
},
},
}
machineConfigs := map[string]*bootstrapv1.KThreesConfig{
m.Name: {
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: bootstrapv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: kThreesConfigSpec,
},
}

t.Run("by returning true if all configs match", func(t *testing.T) {
g := NewWithT(t)
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeTrue())
})

t.Run("by returning false if post commands don't match", func(t *testing.T) {
g := NewWithT(t)
machineConfigs[m.Name].Spec.PostK3sCommands = []string{"new-test"}
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeFalse())
})

t.Run("by returning false if agent configs don't match", func(t *testing.T) {
g := NewWithT(t)
machineConfigs[m.Name].Spec.AgentConfig.KubeletArgs = []string{"test-arg"}
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeFalse())
})
})

t.Run("should match on labels and annotations", func(t *testing.T) {
kcp := &controlplanev1.KThreesControlPlane{
Spec: controlplanev1.KThreesControlPlaneSpec{
MachineTemplate: controlplanev1.KThreesControlPlaneMachineTemplate{
ObjectMeta: clusterv1.ObjectMeta{
Annotations: map[string]string{
"test": "annotation",
},
Labels: map[string]string{
"test": "labels",
},
},
},
KThreesConfigSpec: bootstrapv1.KThreesConfigSpec{
ServerConfig: bootstrapv1.KThreesServerConfig{},
},
},
}
m := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: clusterv1.MachineSpec{
Bootstrap: clusterv1.Bootstrap{
ConfigRef: &corev1.ObjectReference{
Kind: "KThreesConfig",
Namespace: "default",
Name: "test",
APIVersion: bootstrapv1.GroupVersion.String(),
},
},
},
}
machineConfigs := map[string]*bootstrapv1.KThreesConfig{
m.Name: {
TypeMeta: metav1.TypeMeta{
Kind: "KThreesConfig",
APIVersion: bootstrapv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: bootstrapv1.KThreesConfigSpec{
ServerConfig: bootstrapv1.KThreesServerConfig{},
},
},
}

t.Run("by returning true if neither labels or annotations match", func(t *testing.T) {
g := NewWithT(t)
machineConfigs[m.Name].Annotations = nil
machineConfigs[m.Name].Labels = nil
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeTrue())
})

t.Run("by returning true if only labels don't match", func(t *testing.T) {
g := NewWithT(t)
machineConfigs[m.Name].Annotations = kcp.Spec.MachineTemplate.ObjectMeta.Annotations
machineConfigs[m.Name].Labels = nil
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeTrue())
})

t.Run("by returning true if only annotations don't match", func(t *testing.T) {
g := NewWithT(t)
machineConfigs[m.Name].Annotations = nil
machineConfigs[m.Name].Labels = kcp.Spec.MachineTemplate.ObjectMeta.Labels
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeTrue())
})

t.Run("by returning true if both labels and annotations match", func(t *testing.T) {
g := NewWithT(t)
machineConfigs[m.Name].Labels = kcp.Spec.MachineTemplate.ObjectMeta.Labels
machineConfigs[m.Name].Annotations = kcp.Spec.MachineTemplate.ObjectMeta.Annotations
match := MatchesKThreesBootstrapConfig(machineConfigs, kcp)(m)
g.Expect(match).To(BeTrue())
})
})
}
Loading