-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2728 from chuckha/control-plane
🏃 Adds a control plane struct to clean up the reconciler
- Loading branch information
Showing
2 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* | ||
Copyright 2020 The Kubernetes Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package internal | ||
|
||
import ( | ||
"github.com/go-logr/logr" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apiserver/pkg/storage/names" | ||
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" | ||
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3" | ||
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3" | ||
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/hash" | ||
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/machinefilters" | ||
) | ||
|
||
// ControlPlane holds business logic around control planes. | ||
// It should never need to connect to a service, that responsibility lies outside of this struct. | ||
type ControlPlane struct { | ||
KCP *controlplanev1.KubeadmControlPlane | ||
Cluster *clusterv1.Cluster | ||
Machines FilterableMachineCollection | ||
} | ||
|
||
// NewControlPlane returns an instantiated ControlPlane. | ||
func NewControlPlane(cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, ownedMachines FilterableMachineCollection) *ControlPlane { | ||
return &ControlPlane{ | ||
KCP: kcp, | ||
Cluster: cluster, | ||
Machines: ownedMachines, | ||
} | ||
} | ||
|
||
// Logger returns a logger with useful context. | ||
func (c *ControlPlane) Logger() logr.Logger { | ||
return Log.WithValues("namespace", c.KCP.Namespace, "name", c.KCP.Name, "cluster-nanme", c.Cluster.Name) | ||
} | ||
|
||
// Version returns the KubeadmControlPlane's version. | ||
func (c *ControlPlane) Version() *string { | ||
return &c.KCP.Spec.Version | ||
} | ||
|
||
// InfrastructureTemplate returns the KubeadmControlPlane's infrastructure template. | ||
func (c *ControlPlane) InfrastructureTemplate() *corev1.ObjectReference { | ||
return &c.KCP.Spec.InfrastructureTemplate | ||
} | ||
|
||
// ConfigurationHash returns the hash of the KubeadmControlPlane spec. | ||
func (c *ControlPlane) ConfigurationHash() string { | ||
return hash.Compute(&c.KCP.Spec) | ||
} | ||
|
||
// AsOwnerReference returns an owner reference to the KubeadmControlPlane. | ||
func (c *ControlPlane) AsOwnerReference() *metav1.OwnerReference { | ||
return &metav1.OwnerReference{ | ||
APIVersion: controlplanev1.GroupVersion.String(), | ||
Kind: "KubeadmControlPlane", | ||
Name: c.KCP.Name, | ||
UID: c.KCP.UID, | ||
} | ||
} | ||
|
||
// EtcdImageData returns the etcd image data embedded in the ClusterConfiguration or empty strings if none are defined. | ||
func (c *ControlPlane) EtcdImageData() (string, string) { | ||
if c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration != nil && c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil { | ||
meta := c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local.ImageMeta | ||
return meta.ImageRepository, meta.ImageTag | ||
} | ||
return "", "" | ||
} | ||
|
||
// MachinesNeedingUpgrade return a list of machines that need to be upgraded. | ||
func (c *ControlPlane) MachinesNeedingUpgrade() FilterableMachineCollection { | ||
now := metav1.Now() | ||
var requireUpgrade FilterableMachineCollection | ||
if c.KCP.Spec.UpgradeAfter != nil && c.KCP.Spec.UpgradeAfter.Before(&now) { | ||
requireUpgrade = c.Machines.AnyFilter( | ||
machinefilters.Not(machinefilters.MatchesConfigurationHash(hash.Compute(&c.KCP.Spec))), | ||
machinefilters.OlderThan(c.KCP.Spec.UpgradeAfter), | ||
) | ||
} else { | ||
requireUpgrade = c.Machines.Filter( | ||
machinefilters.Not(machinefilters.MatchesConfigurationHash(hash.Compute(&c.KCP.Spec))), | ||
) | ||
} | ||
return requireUpgrade | ||
} | ||
|
||
// FailureDomainWithMost returns the failure domain with the most number of machines. | ||
// Used when scaling down. | ||
func (c *ControlPlane) FailureDomainWithMost() *string { | ||
// See if there are any Machines that are not in currently defined failure domains first. | ||
notInFailureDomains := c.Machines.Filter( | ||
machinefilters.Not(machinefilters.InFailureDomains(c.Cluster.Status.FailureDomains.FilterControlPlane().GetIDs()...)), | ||
) | ||
if len(notInFailureDomains) > 0 { | ||
// return the failure domain for the oldest Machine not in the current list of failure domains | ||
// this could be either nil (no failure domain defined) or a failure domain that is no longer defined | ||
// in the cluster status. | ||
return notInFailureDomains.Oldest().Spec.FailureDomain | ||
} | ||
|
||
// Otherwise pick the currently known failure domain with the most Machines | ||
return PickMost(c.Cluster.Status.FailureDomains.FilterControlPlane(), c.Machines) | ||
} | ||
|
||
// FailureDomainWithFewest returns the failure domain with the fewest number of machines. | ||
// Used when scaling up. | ||
func (c *ControlPlane) FailureDomainWithFewest() *string { | ||
if len(c.Cluster.Status.FailureDomains.FilterControlPlane()) == 0 { | ||
return nil | ||
} | ||
return PickFewest(c.Cluster.Status.FailureDomains.FilterControlPlane(), c.Machines) | ||
} | ||
|
||
// InitialControlPlaneConfig returns a new KubeadmConfigSpec that is to be used for an initializing control plane. | ||
func (c *ControlPlane) InitialControlPlaneConfig() *bootstrapv1.KubeadmConfigSpec { | ||
bootstrapSpec := c.KCP.Spec.KubeadmConfigSpec.DeepCopy() | ||
bootstrapSpec.JoinConfiguration = nil | ||
return bootstrapSpec | ||
} | ||
|
||
// JoinControlPlaneConfig returns a new KubeadmConfigSpec that is to be used for joining control planes. | ||
func (c *ControlPlane) JoinControlPlaneConfig() *bootstrapv1.KubeadmConfigSpec { | ||
bootstrapSpec := c.KCP.Spec.KubeadmConfigSpec.DeepCopy() | ||
bootstrapSpec.InitConfiguration = nil | ||
bootstrapSpec.ClusterConfiguration = nil | ||
return bootstrapSpec | ||
} | ||
|
||
// GenerateKubeadmConfig generates a new kubeadm config for creating new control plane nodes. | ||
func (c *ControlPlane) GenerateKubeadmConfig(spec *bootstrapv1.KubeadmConfigSpec) *bootstrapv1.KubeadmConfig { | ||
// Create an owner reference without a controller reference because the owning controller is the machine controller | ||
owner := metav1.OwnerReference{ | ||
APIVersion: controlplanev1.GroupVersion.String(), | ||
Kind: "KubeadmControlPlane", | ||
Name: c.KCP.Name, | ||
UID: c.KCP.UID, | ||
} | ||
|
||
bootstrapConfig := &bootstrapv1.KubeadmConfig{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: names.SimpleNameGenerator.GenerateName(c.KCP.Name + "-"), | ||
Namespace: c.KCP.Namespace, | ||
Labels: ControlPlaneLabelsForClusterWithHash(c.Cluster.Name, c.ConfigurationHash()), | ||
OwnerReferences: []metav1.OwnerReference{owner}, | ||
}, | ||
Spec: *spec, | ||
} | ||
return bootstrapConfig | ||
} | ||
|
||
// NewMachine returns a machine configured to be a part of the control plane. | ||
func (c *ControlPlane) NewMachine(infraRef, bootstrapRef *corev1.ObjectReference, failureDomain *string) *clusterv1.Machine { | ||
return &clusterv1.Machine{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: names.SimpleNameGenerator.GenerateName(c.KCP.Name + "-"), | ||
Namespace: c.KCP.Namespace, | ||
Labels: ControlPlaneLabelsForClusterWithHash(c.Cluster.Name, c.ConfigurationHash()), | ||
OwnerReferences: []metav1.OwnerReference{ | ||
*metav1.NewControllerRef(c.KCP, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane")), | ||
}, | ||
}, | ||
Spec: clusterv1.MachineSpec{ | ||
ClusterName: c.Cluster.Name, | ||
Version: c.Version(), | ||
InfrastructureRef: *infraRef, | ||
Bootstrap: clusterv1.Bootstrap{ | ||
ConfigRef: bootstrapRef, | ||
}, | ||
FailureDomain: failureDomain, | ||
}, | ||
} | ||
} | ||
|
||
// NeedsReplacementNode determines if the control plane needs to create a replacement node during upgrade. | ||
func (c *ControlPlane) NeedsReplacementNode() bool { | ||
// Can't do anything with an unknown number of desired replicas. | ||
if c.KCP.Spec.Replicas == nil { | ||
return false | ||
} | ||
// if the number of existing machines is exactly 1 > than the number of replicas. | ||
return len(c.Machines)+1 == int(*c.KCP.Spec.Replicas) | ||
} | ||
|
||
// HasDeletingMachine returns true if any machine in the control plane is in the process of being deleted. | ||
func (c *ControlPlane) HasDeletingMachine() bool { | ||
return len(c.Machines.Filter(machinefilters.HasDeletionTimestamp)) > 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
Copyright 2020 The Kubernetes Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package internal | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3" | ||
) | ||
|
||
func TestControlPlane(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "Control Plane Suite") | ||
} | ||
|
||
var _ = Describe("Control Plane", func() { | ||
Describe("MachinesNeedingUpgrade", func() { | ||
var controlPlane *ControlPlane | ||
BeforeEach(func() { | ||
controlPlane = &ControlPlane{ | ||
KCP: &controlplanev1.KubeadmControlPlane{}, | ||
} | ||
}) | ||
|
||
Context("With no machines", func() { | ||
It("should return no machines", func() { | ||
Expect(controlPlane.MachinesNeedingUpgrade()).To(HaveLen(0)) | ||
}) | ||
}) | ||
|
||
Context("With machines", func() { | ||
BeforeEach(func() { | ||
controlPlane.Machines = FilterableMachineCollection{ | ||
"machine-1": machine("machine-1"), | ||
} | ||
}) | ||
Context("That have an old configuration", func() { | ||
It("should return some machines", func() { | ||
Expect(controlPlane.MachinesNeedingUpgrade()).ToNot(HaveLen(0)) | ||
}) | ||
}) | ||
|
||
Context("That have an up-to-date configuration", func() { | ||
Context("That has no upgradeAfter value set", func() { | ||
PIt("should return no machines", func() {}) | ||
}) | ||
|
||
Context("That has an upgradeAfter value set", func() { | ||
Context("That is in the future", func() { | ||
PIt("should return no machines", func() {}) | ||
}) | ||
|
||
Context("That is in the past", func() { | ||
Context("That is before machine creation time", func() { | ||
PIt("should return no machines", func() {}) | ||
}) | ||
Context("That is after machine creation time", func() { | ||
PIt("should return no machines", func() {}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
}) | ||
}) |