Skip to content

Commit

Permalink
Merge pull request #2728 from chuckha/control-plane
Browse files Browse the repository at this point in the history
🏃 Adds a control plane struct to clean up the reconciler
  • Loading branch information
k8s-ci-robot authored Mar 19, 2020
2 parents 67bae89 + 1bfdc02 commit 867b2af
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 0 deletions.
204 changes: 204 additions & 0 deletions controlplane/kubeadm/internal/control_plane.go
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
}
82 changes: 82 additions & 0 deletions controlplane/kubeadm/internal/control_plane_test.go
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() {})
})
})
})
})
})

})
})

0 comments on commit 867b2af

Please sign in to comment.