Skip to content

Commit 867b2af

Browse files
authored
Merge pull request kubernetes-sigs#2728 from chuckha/control-plane
🏃 Adds a control plane struct to clean up the reconciler
2 parents 67bae89 + 1bfdc02 commit 867b2af

File tree

2 files changed

+286
-0
lines changed

2 files changed

+286
-0
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internal
18+
19+
import (
20+
"github.com/go-logr/logr"
21+
corev1 "k8s.io/api/core/v1"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apiserver/pkg/storage/names"
24+
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
25+
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
26+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
27+
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/hash"
28+
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/machinefilters"
29+
)
30+
31+
// ControlPlane holds business logic around control planes.
32+
// It should never need to connect to a service, that responsibility lies outside of this struct.
33+
type ControlPlane struct {
34+
KCP *controlplanev1.KubeadmControlPlane
35+
Cluster *clusterv1.Cluster
36+
Machines FilterableMachineCollection
37+
}
38+
39+
// NewControlPlane returns an instantiated ControlPlane.
40+
func NewControlPlane(cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, ownedMachines FilterableMachineCollection) *ControlPlane {
41+
return &ControlPlane{
42+
KCP: kcp,
43+
Cluster: cluster,
44+
Machines: ownedMachines,
45+
}
46+
}
47+
48+
// Logger returns a logger with useful context.
49+
func (c *ControlPlane) Logger() logr.Logger {
50+
return Log.WithValues("namespace", c.KCP.Namespace, "name", c.KCP.Name, "cluster-nanme", c.Cluster.Name)
51+
}
52+
53+
// Version returns the KubeadmControlPlane's version.
54+
func (c *ControlPlane) Version() *string {
55+
return &c.KCP.Spec.Version
56+
}
57+
58+
// InfrastructureTemplate returns the KubeadmControlPlane's infrastructure template.
59+
func (c *ControlPlane) InfrastructureTemplate() *corev1.ObjectReference {
60+
return &c.KCP.Spec.InfrastructureTemplate
61+
}
62+
63+
// ConfigurationHash returns the hash of the KubeadmControlPlane spec.
64+
func (c *ControlPlane) ConfigurationHash() string {
65+
return hash.Compute(&c.KCP.Spec)
66+
}
67+
68+
// AsOwnerReference returns an owner reference to the KubeadmControlPlane.
69+
func (c *ControlPlane) AsOwnerReference() *metav1.OwnerReference {
70+
return &metav1.OwnerReference{
71+
APIVersion: controlplanev1.GroupVersion.String(),
72+
Kind: "KubeadmControlPlane",
73+
Name: c.KCP.Name,
74+
UID: c.KCP.UID,
75+
}
76+
}
77+
78+
// EtcdImageData returns the etcd image data embedded in the ClusterConfiguration or empty strings if none are defined.
79+
func (c *ControlPlane) EtcdImageData() (string, string) {
80+
if c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration != nil && c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil {
81+
meta := c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local.ImageMeta
82+
return meta.ImageRepository, meta.ImageTag
83+
}
84+
return "", ""
85+
}
86+
87+
// MachinesNeedingUpgrade return a list of machines that need to be upgraded.
88+
func (c *ControlPlane) MachinesNeedingUpgrade() FilterableMachineCollection {
89+
now := metav1.Now()
90+
var requireUpgrade FilterableMachineCollection
91+
if c.KCP.Spec.UpgradeAfter != nil && c.KCP.Spec.UpgradeAfter.Before(&now) {
92+
requireUpgrade = c.Machines.AnyFilter(
93+
machinefilters.Not(machinefilters.MatchesConfigurationHash(hash.Compute(&c.KCP.Spec))),
94+
machinefilters.OlderThan(c.KCP.Spec.UpgradeAfter),
95+
)
96+
} else {
97+
requireUpgrade = c.Machines.Filter(
98+
machinefilters.Not(machinefilters.MatchesConfigurationHash(hash.Compute(&c.KCP.Spec))),
99+
)
100+
}
101+
return requireUpgrade
102+
}
103+
104+
// FailureDomainWithMost returns the failure domain with the most number of machines.
105+
// Used when scaling down.
106+
func (c *ControlPlane) FailureDomainWithMost() *string {
107+
// See if there are any Machines that are not in currently defined failure domains first.
108+
notInFailureDomains := c.Machines.Filter(
109+
machinefilters.Not(machinefilters.InFailureDomains(c.Cluster.Status.FailureDomains.FilterControlPlane().GetIDs()...)),
110+
)
111+
if len(notInFailureDomains) > 0 {
112+
// return the failure domain for the oldest Machine not in the current list of failure domains
113+
// this could be either nil (no failure domain defined) or a failure domain that is no longer defined
114+
// in the cluster status.
115+
return notInFailureDomains.Oldest().Spec.FailureDomain
116+
}
117+
118+
// Otherwise pick the currently known failure domain with the most Machines
119+
return PickMost(c.Cluster.Status.FailureDomains.FilterControlPlane(), c.Machines)
120+
}
121+
122+
// FailureDomainWithFewest returns the failure domain with the fewest number of machines.
123+
// Used when scaling up.
124+
func (c *ControlPlane) FailureDomainWithFewest() *string {
125+
if len(c.Cluster.Status.FailureDomains.FilterControlPlane()) == 0 {
126+
return nil
127+
}
128+
return PickFewest(c.Cluster.Status.FailureDomains.FilterControlPlane(), c.Machines)
129+
}
130+
131+
// InitialControlPlaneConfig returns a new KubeadmConfigSpec that is to be used for an initializing control plane.
132+
func (c *ControlPlane) InitialControlPlaneConfig() *bootstrapv1.KubeadmConfigSpec {
133+
bootstrapSpec := c.KCP.Spec.KubeadmConfigSpec.DeepCopy()
134+
bootstrapSpec.JoinConfiguration = nil
135+
return bootstrapSpec
136+
}
137+
138+
// JoinControlPlaneConfig returns a new KubeadmConfigSpec that is to be used for joining control planes.
139+
func (c *ControlPlane) JoinControlPlaneConfig() *bootstrapv1.KubeadmConfigSpec {
140+
bootstrapSpec := c.KCP.Spec.KubeadmConfigSpec.DeepCopy()
141+
bootstrapSpec.InitConfiguration = nil
142+
bootstrapSpec.ClusterConfiguration = nil
143+
return bootstrapSpec
144+
}
145+
146+
// GenerateKubeadmConfig generates a new kubeadm config for creating new control plane nodes.
147+
func (c *ControlPlane) GenerateKubeadmConfig(spec *bootstrapv1.KubeadmConfigSpec) *bootstrapv1.KubeadmConfig {
148+
// Create an owner reference without a controller reference because the owning controller is the machine controller
149+
owner := metav1.OwnerReference{
150+
APIVersion: controlplanev1.GroupVersion.String(),
151+
Kind: "KubeadmControlPlane",
152+
Name: c.KCP.Name,
153+
UID: c.KCP.UID,
154+
}
155+
156+
bootstrapConfig := &bootstrapv1.KubeadmConfig{
157+
ObjectMeta: metav1.ObjectMeta{
158+
Name: names.SimpleNameGenerator.GenerateName(c.KCP.Name + "-"),
159+
Namespace: c.KCP.Namespace,
160+
Labels: ControlPlaneLabelsForClusterWithHash(c.Cluster.Name, c.ConfigurationHash()),
161+
OwnerReferences: []metav1.OwnerReference{owner},
162+
},
163+
Spec: *spec,
164+
}
165+
return bootstrapConfig
166+
}
167+
168+
// NewMachine returns a machine configured to be a part of the control plane.
169+
func (c *ControlPlane) NewMachine(infraRef, bootstrapRef *corev1.ObjectReference, failureDomain *string) *clusterv1.Machine {
170+
return &clusterv1.Machine{
171+
ObjectMeta: metav1.ObjectMeta{
172+
Name: names.SimpleNameGenerator.GenerateName(c.KCP.Name + "-"),
173+
Namespace: c.KCP.Namespace,
174+
Labels: ControlPlaneLabelsForClusterWithHash(c.Cluster.Name, c.ConfigurationHash()),
175+
OwnerReferences: []metav1.OwnerReference{
176+
*metav1.NewControllerRef(c.KCP, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane")),
177+
},
178+
},
179+
Spec: clusterv1.MachineSpec{
180+
ClusterName: c.Cluster.Name,
181+
Version: c.Version(),
182+
InfrastructureRef: *infraRef,
183+
Bootstrap: clusterv1.Bootstrap{
184+
ConfigRef: bootstrapRef,
185+
},
186+
FailureDomain: failureDomain,
187+
},
188+
}
189+
}
190+
191+
// NeedsReplacementNode determines if the control plane needs to create a replacement node during upgrade.
192+
func (c *ControlPlane) NeedsReplacementNode() bool {
193+
// Can't do anything with an unknown number of desired replicas.
194+
if c.KCP.Spec.Replicas == nil {
195+
return false
196+
}
197+
// if the number of existing machines is exactly 1 > than the number of replicas.
198+
return len(c.Machines)+1 == int(*c.KCP.Spec.Replicas)
199+
}
200+
201+
// HasDeletingMachine returns true if any machine in the control plane is in the process of being deleted.
202+
func (c *ControlPlane) HasDeletingMachine() bool {
203+
return len(c.Machines.Filter(machinefilters.HasDeletionTimestamp)) > 0
204+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internal
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
25+
)
26+
27+
func TestControlPlane(t *testing.T) {
28+
RegisterFailHandler(Fail)
29+
RunSpecs(t, "Control Plane Suite")
30+
}
31+
32+
var _ = Describe("Control Plane", func() {
33+
Describe("MachinesNeedingUpgrade", func() {
34+
var controlPlane *ControlPlane
35+
BeforeEach(func() {
36+
controlPlane = &ControlPlane{
37+
KCP: &controlplanev1.KubeadmControlPlane{},
38+
}
39+
})
40+
41+
Context("With no machines", func() {
42+
It("should return no machines", func() {
43+
Expect(controlPlane.MachinesNeedingUpgrade()).To(HaveLen(0))
44+
})
45+
})
46+
47+
Context("With machines", func() {
48+
BeforeEach(func() {
49+
controlPlane.Machines = FilterableMachineCollection{
50+
"machine-1": machine("machine-1"),
51+
}
52+
})
53+
Context("That have an old configuration", func() {
54+
It("should return some machines", func() {
55+
Expect(controlPlane.MachinesNeedingUpgrade()).ToNot(HaveLen(0))
56+
})
57+
})
58+
59+
Context("That have an up-to-date configuration", func() {
60+
Context("That has no upgradeAfter value set", func() {
61+
PIt("should return no machines", func() {})
62+
})
63+
64+
Context("That has an upgradeAfter value set", func() {
65+
Context("That is in the future", func() {
66+
PIt("should return no machines", func() {})
67+
})
68+
69+
Context("That is in the past", func() {
70+
Context("That is before machine creation time", func() {
71+
PIt("should return no machines", func() {})
72+
})
73+
Context("That is after machine creation time", func() {
74+
PIt("should return no machines", func() {})
75+
})
76+
})
77+
})
78+
})
79+
})
80+
81+
})
82+
})

0 commit comments

Comments
 (0)