Skip to content

Commit

Permalink
Add templating support to MachineSet (bootstrap & infrastructure) (ku…
Browse files Browse the repository at this point in the history
…bernetes-sigs#1098)

Signed-off-by: Vince Prignano <[email protected]>
  • Loading branch information
vincepri authored and k8s-ci-robot committed Jul 2, 2019
1 parent b37cf11 commit 0625970
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 30 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/spf13/pflag v1.0.3
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
Expand Down
19 changes: 19 additions & 0 deletions pkg/controller/external/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = [
"testing.go",
"util.go",
],
importpath = "sigs.k8s.io/cluster-api/pkg/controller/external",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
],
)
49 changes: 49 additions & 0 deletions pkg/controller/external/testing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2019 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 external

import (
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
TestGenericInfrastructureCRD = &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "generic.infrastructure.cluster.sigs.k8s.io",
},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: "infrastructure.cluster.sigs.k8s.io",
Scope: apiextensionsv1beta1.NamespaceScoped,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Kind: "InfrastructureRef",
Plural: "generic",
},
Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
},
Validation: &apiextensionsv1beta1.CustomResourceValidation{},
Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
{
Name: "v1alpha1",
Served: true,
Storage: true,
},
},
},
}
)
1 change: 1 addition & 0 deletions pkg/controller/machine/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ go_library(
"//pkg/apis/cluster/common:go_default_library",
"//pkg/apis/cluster/v1alpha2:go_default_library",
"//pkg/controller/error:go_default_library",
"//pkg/controller/external:go_default_library",
"//pkg/controller/remote:go_default_library",
"//pkg/util:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
Expand Down
5 changes: 5 additions & 0 deletions pkg/controller/machineset/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/cluster/v1alpha2:go_default_library",
"//pkg/controller/external:go_default_library",
"//pkg/controller/noderefutil:go_default_library",
"//pkg/controller/remote:go_default_library",
"//pkg/util:go_default_library",
Expand Down Expand Up @@ -47,9 +48,13 @@ go_test(
"//pkg/apis:go_default_library",
"//pkg/apis/cluster/common:go_default_library",
"//pkg/apis/cluster/v1alpha2:go_default_library",
"//pkg/controller/external:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
Expand Down
44 changes: 43 additions & 1 deletion pkg/controller/machineset/machineset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"sync"
"time"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -33,6 +35,7 @@ import (
"k8s.io/klog"
"k8s.io/utils/pointer"
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha2"
"sigs.k8s.io/cluster-api/pkg/controller/external"
"sigs.k8s.io/cluster-api/pkg/util"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
Expand Down Expand Up @@ -301,10 +304,49 @@ func (r *ReconcileMachineSet) syncReplicas(ms *clusterv1.MachineSet, machines []
i+1, diff, *(ms.Spec.Replicas), len(machines))

machine := r.getNewMachine(ms)
if err := r.Client.Create(context.Background(), machine); err != nil {

// Clone and set the infrastructure and bootstrap references.
var (
infraConfig, bootstrapConfig *unstructured.Unstructured
err error
)

infraConfig, err = external.Clone(r.Client, &machine.Spec.InfrastructureRef, machine.Namespace)
if err != nil {
return errors.Wrapf(err, "failed to clone infrastructure configuration for MachineSet %q in namespace %q", ms.Name, ms.Namespace)
}
machine.Spec.InfrastructureRef = corev1.ObjectReference{
APIVersion: infraConfig.GetAPIVersion(),
Kind: infraConfig.GetKind(),
Namespace: infraConfig.GetNamespace(),
Name: infraConfig.GetName(),
}

if machine.Spec.Bootstrap.ConfigRef != nil {
bootstrapConfig, err = external.Clone(r.Client, machine.Spec.Bootstrap.ConfigRef, machine.Namespace)
if err != nil {
return errors.Wrapf(err, "failed to clone bootstrap configuration for MachineSet %q in namespace %q", ms.Name, ms.Namespace)
}
machine.Spec.Bootstrap.ConfigRef = &corev1.ObjectReference{
APIVersion: bootstrapConfig.GetAPIVersion(),
Kind: bootstrapConfig.GetKind(),
Namespace: bootstrapConfig.GetNamespace(),
Name: bootstrapConfig.GetName(),
}
}

if err := r.Client.Create(context.TODO(), machine); err != nil {
klog.Errorf("Unable to create Machine %q: %v", machine.Name, err)
r.recorder.Eventf(ms, corev1.EventTypeWarning, "FailedCreate", "Failed to create machine %q: %v", machine.Name, err)
errstrings = append(errstrings, err.Error())
if err := r.Client.Delete(context.TODO(), infraConfig); !apierrors.IsNotFound(err) {
klog.Errorf("Failed to cleanup infrastructure configuration object after Machine creation error: %v", err)
}
if bootstrapConfig != nil {
if err := r.Client.Delete(context.TODO(), bootstrapConfig); !apierrors.IsNotFound(err) {
klog.Errorf("Failed to cleanup bootstrap configuration object after Machine creation error: %v", err)
}
}
continue
}
klog.Infof("Created machine %d of %d with name %q", i+1, diff, machine.Name)
Expand Down
4 changes: 4 additions & 0 deletions pkg/controller/machineset/machineset_reconciler_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import (
"path/filepath"
"testing"

apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/cluster-api/pkg/apis"
"sigs.k8s.io/cluster-api/pkg/controller/external"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand All @@ -34,9 +36,11 @@ var cfg *rest.Config

func TestMain(m *testing.M) {
t := &envtest.Environment{
CRDs: []*apiextensionsv1beta1.CustomResourceDefinition{external.TestGenericInfrastructureCRD},
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
}
apis.AddToScheme(scheme.Scheme)
apiextensionsv1beta1.AddToScheme(scheme.Scheme)

var err error
if cfg, err = t.Start(); err != nil {
Expand Down
74 changes: 46 additions & 28 deletions pkg/controller/machineset/machineset_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
"testing"
"time"

"github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"golang.org/x/net/context"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -37,6 +40,14 @@ var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Nam
const timeout = time.Second * 5

func TestReconcile(t *testing.T) {
g := gomega.NewGomegaWithT(t)

infraResource := new(unstructured.Unstructured)
infraResource.SetKind("InfrastructureRef")
infraResource.SetAPIVersion("infrastructure.cluster.sigs.k8s.io/v1alpha1")
infraResource.SetName("foo-template")
infraResource.SetNamespace("default")

replicas := int32(2)
version := "1.14.2"
instance := &clusterv1alpha2.MachineSet{
Expand All @@ -49,7 +60,7 @@ func TestReconcile(t *testing.T) {
InfrastructureRef: corev1.ObjectReference{
APIVersion: "infrastructure.cluster.sigs.k8s.io/v1alpha1",
Kind: "InfrastructureRef",
Name: "machine-infrastructure",
Name: "foo-template",
},
},
},
Expand All @@ -59,10 +70,10 @@ func TestReconcile(t *testing.T) {
// Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
// channel when it is finished.
mgr, err := manager.New(cfg, manager.Options{})
if err != nil {
t.Errorf("error creating new manager: %v", err)
}
g.Expect(err).To(gomega.BeNil())

c = mgr.GetClient()
g.Expect(c.Create(context.TODO(), infraResource)).To(gomega.BeNil())

r := newReconciler(mgr)
recFn, requests := SetupTestReconcile(r)
Expand All @@ -72,9 +83,7 @@ func TestReconcile(t *testing.T) {
defer close(StartTestManager(mgr, t))

// Create the MachineSet object and expect Reconcile to be called and the Machines to be created.
if err := c.Create(context.TODO(), instance); err != nil {
t.Errorf("error creating instance: %v", err)
}
g.Expect(c.Create(context.TODO(), instance)).To(gomega.BeNil())
defer c.Delete(context.TODO(), instance)
select {
case recv := <-requests:
Expand All @@ -90,7 +99,7 @@ func TestReconcile(t *testing.T) {
// TODO(joshuarubin) there seems to be a race here. If expectInt sleeps
// briefly, even 10ms, the number of replicas is 4 and not 2 as expected
expectInt(t, int(replicas), func(ctx context.Context) int {
if err := c.List(ctx, machines); err != nil {
if err := c.List(ctx, machines, client.InNamespace("default")); err != nil {
return -1
}
return len(machines.Items)
Expand All @@ -103,29 +112,38 @@ func TestReconcile(t *testing.T) {
}
}

// Delete a Machine and expect Reconcile to be called to replace it.
m := machines.Items[0]
if err := c.Delete(context.TODO(), &m); err != nil {
t.Errorf("error deleting machine: %v", err)
}
select {
case recv := <-requests:
if recv != expectedRequest {
t.Error("received request does not match expected request")
// Verify that we have 3 infrastructure references: 1 template + 2 machines.
infraConfigs := &unstructured.UnstructuredList{}
infraConfigs.SetKind("InfrastructureRef")
infraConfigs.SetAPIVersion("infrastructure.cluster.sigs.k8s.io/v1alpha1")
expectInt(t, 3, func(ctx context.Context) int {
if err := c.List(ctx, infraConfigs, client.InNamespace("default")); err != nil {
return -1
}
case <-time.After(timeout):
t.Error("timed out waiting for request")
}
return len(infraConfigs.Items)
})

// TODO (robertbailey): Figure out why the control loop isn't working as expected.
/*
g.Eventually(func() int {
if err := c.List(context.TODO(), &client.ListOptions{}, machines); err != nil {
return -1
}
return len(machines.Items)
}, timeout).Should(gomega.BeEquivalentTo(replicas))
*/
// Delete a Machine and expect Reconcile to be called to replace it.
//
// More information: https://github.com/kubernetes-sigs/cluster-api/issues/1099
//
// m := machines.Items[0]
// g.Expect(c.Delete(context.TODO(), &m)).To(gomega.BeNil())
// select {
// case recv := <-requests:
// if recv != expectedRequest {
// t.Error("received request does not match expected request")
// }
// case <-time.After(timeout):
// t.Error("timed out waiting for request")
// }
// g.Eventually(func() int {
// if err := c.List(context.TODO(), machines); err != nil {
// return -1
// }
// return len(machines.Items)
// }, timeout).Should(gomega.BeEquivalentTo(replicas))
}

func expectInt(t *testing.T, expect int, fn func(context.Context) int) {
Expand Down
2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ k8s.io/api/storage/v1beta1
k8s.io/api/admission/v1beta1
# k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme
# k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
Expand Down

0 comments on commit 0625970

Please sign in to comment.