Update workload examples

Signed-off-by: Illya Chekrygin <illya.chekrygin@gmail.com>
This commit is contained in:
Illya Chekrygin 2019-01-31 11:08:10 -08:00
parent 5874c59f2d
commit 641d2497d7
No known key found for this signature in database
GPG Key ID: EB1222EF859CB6A9
12 changed files with 348 additions and 46 deletions

2
Gopkg.lock generated
View File

@ -1247,8 +1247,10 @@
"sigs.k8s.io/controller-runtime/pkg/client/fake",
"sigs.k8s.io/controller-runtime/pkg/controller",
"sigs.k8s.io/controller-runtime/pkg/envtest",
"sigs.k8s.io/controller-runtime/pkg/event",
"sigs.k8s.io/controller-runtime/pkg/handler",
"sigs.k8s.io/controller-runtime/pkg/manager",
"sigs.k8s.io/controller-runtime/pkg/predicate",
"sigs.k8s.io/controller-runtime/pkg/reconcile",
"sigs.k8s.io/controller-runtime/pkg/runtime/scheme",
"sigs.k8s.io/controller-runtime/pkg/runtime/signals",

View File

@ -27,6 +27,8 @@ spec:
kind: Workload
plural: workloads
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
properties:

View File

@ -3,6 +3,8 @@ kind: KubernetesCluster
metadata:
name: demo-cluster
namespace: crossplane-system
labels:
provider: aws
spec:
classReference:
name: standard-cluster

View File

@ -18,12 +18,11 @@ metadata:
name: test-workload
namespace: default
spec:
clusterSelector:
provider: aws
resources:
- name: demo-mysql
secretName: demo-mysql
targetCluster:
name: demo-cluster
namespace: crossplane-system
targetDeployment:
apiVersion: extensions/v1beta1
kind: Deployment

View File

@ -5,6 +5,8 @@ kind: KubernetesCluster
metadata:
name: demo-cluster
namespace: crossplane-system
labels:
provider: azure
spec:
classReference:
name: standard-cluster

View File

@ -18,12 +18,11 @@ metadata:
name: test-workload
namespace: default
spec:
clusterSelector:
provider: azure
resources:
- name: demo-mysql
secretName: demo-mysql
targetCluster:
name: demo-cluster
namespace: crossplane-system
targetDeployment:
apiVersion: extensions/v1beta1
kind: Deployment

View File

@ -4,6 +4,8 @@ kind: KubernetesCluster
metadata:
name: demo-gke-cluster
namespace: crossplane-system
labels:
provider: gcp
spec:
classReference:
name: standard-cluster

View File

@ -17,12 +17,11 @@ metadata:
name: demo
namespace: default
spec:
clusterSelector:
provider: gcp
resources:
- name: demo
secretName: demo
targetCluster:
name: demo-gke-cluster
namespace: crossplane-system
targetDeployment:
apiVersion: extensions/v1beta1
kind: Deployment

View File

@ -109,10 +109,6 @@ func TestWorkload(t *testing.T) {
created := &Workload{
ObjectMeta: om,
Spec: WorkloadSpec{
TargetCluster: corev1.ObjectReference{
Namespace: "default",
Name: "test-cluster",
},
TargetNamespace: namespace,
TargetDeployment: &appsv1.Deployment{
ObjectMeta: om,

View File

@ -92,12 +92,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
}
// Watch for changes to Instance
err = c.Watch(&source.Kind{Type: &computev1alpha1.Workload{}}, &handler.EnqueueRequestForObject{}, &predicate.Funcs{CreateFunc: CreatePredicate})
if err != nil {
return err
}
return nil
return c.Watch(&source.Kind{Type: &computev1alpha1.Workload{}}, &handler.EnqueueRequestForObject{}, &predicate.Funcs{CreateFunc: CreatePredicate})
}
// fail - helper function to set fail condition with reason and message
@ -113,8 +108,7 @@ func (r *Reconciler) _schedule(instance *computev1alpha1.Workload) (reconcile.Re
clusters := &computev1alpha1.KubernetesClusterList{}
err := r.List(context.Background(), client.MatchingLabels(instance.Spec.ClusterSelector), clusters)
if err != nil {
if err := r.List(context.Background(), client.MatchingLabels(instance.Spec.ClusterSelector), clusters); err != nil {
return resultDone, err
}

View File

@ -25,7 +25,6 @@ import (
computev1alpha1 "github.com/crossplaneio/crossplane/pkg/apis/compute/v1alpha1"
corev1alpha1 "github.com/crossplaneio/crossplane/pkg/apis/core/v1alpha1"
"github.com/crossplaneio/crossplane/pkg/util"
errs "github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@ -139,7 +138,7 @@ func (r *Reconciler) fail(instance *computev1alpha1.Workload, reason, msg string
func (r *Reconciler) _connect(instance *computev1alpha1.Workload) (kubernetes.Interface, error) {
ref := instance.Status.Cluster
if ref == nil {
return nil, errs.New("workload is not scheduled")
return nil, fmt.Errorf("workload is not scheduled")
}
k := &computev1alpha1.KubernetesCluster{}
@ -321,7 +320,7 @@ func (r *Reconciler) _create(instance *computev1alpha1.Workload, client kubernet
instance.Status.State = computev1alpha1.WorkloadStateCreating
// update instance
return resultDone, r.Status().Update(ctx, instance)
return resultDone, r.Update(ctx, instance)
}
// _sync Workload status
@ -380,7 +379,7 @@ func (r *Reconciler) _delete(instance *computev1alpha1.Workload, client kubernet
instance.Status.SetCondition(corev1alpha1.NewCondition(corev1alpha1.Deleting, "", ""))
util.RemoveFinalizer(&instance.ObjectMeta, finalizer)
return reconcile.Result{}, r.Status().Update(ctx, instance)
return resultDone, r.Status().Update(ctx, instance)
}
// Reconcile reads that state of the cluster for a Instance object and makes changes based on the state read

View File

@ -11,26 +11,30 @@ 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
limitations under the License.
*/
package workload
import (
"fmt"
"testing"
"time"
"github.com/crossplaneio/crossplane/pkg/apis/compute"
. "github.com/crossplaneio/crossplane/pkg/apis/compute/v1alpha1"
computev1alpha1 "github.com/crossplaneio/crossplane/pkg/apis/compute/v1alpha1"
corev1alpha1 "github.com/crossplaneio/crossplane/pkg/apis/core/v1alpha1"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
. "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
. "k8s.io/client-go/testing"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@ -65,17 +69,45 @@ func testSecret() *corev1.Secret {
}
}
func testCluster() *KubernetesCluster {
return &KubernetesCluster{
func testCluster() *computev1alpha1.KubernetesCluster {
return &computev1alpha1.KubernetesCluster{
ObjectMeta: objectMeta,
Spec: KubernetesClusterSpec{},
Spec: computev1alpha1.KubernetesClusterSpec{},
}
}
func testWorkload() *Workload {
return &Workload{
func testWorkload() *computev1alpha1.Workload {
return &computev1alpha1.Workload{
ObjectMeta: objectMeta,
Spec: WorkloadSpec{},
Spec: computev1alpha1.WorkloadSpec{},
}
}
func testDeployment() *appsv1.Deployment {
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "deployment",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "test-deployment",
UID: "test-deployment-uid",
},
}
}
func testService() *corev1.Service {
return &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "test-service",
UID: "test-service-uid",
},
}
}
@ -83,9 +115,9 @@ func TestWorkloadCreatePredicate(t *testing.T) {
g := NewGomegaWithT(t)
g.Expect(CreatePredicate(event.CreateEvent{})).To(BeFalse())
g.Expect(CreatePredicate(event.CreateEvent{Object: &Workload{}})).To(BeFalse())
g.Expect(CreatePredicate(event.CreateEvent{Object: &Workload{
Status: WorkloadStatus{Cluster: &corev1.ObjectReference{}}}})).To(BeTrue())
g.Expect(CreatePredicate(event.CreateEvent{Object: &computev1alpha1.Workload{}})).To(BeFalse())
g.Expect(CreatePredicate(event.CreateEvent{Object: &computev1alpha1.Workload{
Status: computev1alpha1.WorkloadStatus{Cluster: &corev1.ObjectReference{}}}})).To(BeTrue())
}
func TestReconcileNotScheduled(t *testing.T) {
@ -113,8 +145,8 @@ func TestReconcileClientError(t *testing.T) {
expCondition.SetFailed(errorClusterClient, testError)
r := &Reconciler{
Client: fake.NewFakeClient(w),
connect: func(*Workload) (i kubernetes.Interface, e error) {
return nil, errors.New(testError)
connect: func(*computev1alpha1.Workload) (i kubernetes.Interface, e error) {
return nil, fmt.Errorf(testError)
},
}
rs, err := r.Reconcile(request)
@ -133,8 +165,8 @@ func TestReconcileDelete(t *testing.T) {
w.Status.Cluster = &corev1.ObjectReference{}
r := &Reconciler{
Client: fake.NewFakeClient(w),
connect: func(*Workload) (i kubernetes.Interface, e error) { return nil, nil },
delete: func(workload *Workload, i kubernetes.Interface) (result reconcile.Result, e error) {
connect: func(*computev1alpha1.Workload) (i kubernetes.Interface, e error) { return nil, nil },
delete: func(workload *computev1alpha1.Workload, i kubernetes.Interface) (result reconcile.Result, e error) {
return resultDone, nil
},
}
@ -150,8 +182,8 @@ func TestReconcileCreate(t *testing.T) {
w.Status.Cluster = &corev1.ObjectReference{}
r := &Reconciler{
Client: fake.NewFakeClient(w),
connect: func(*Workload) (i kubernetes.Interface, e error) { return nil, nil },
create: func(workload *Workload, i kubernetes.Interface) (result reconcile.Result, e error) {
connect: func(*computev1alpha1.Workload) (i kubernetes.Interface, e error) { return nil, nil },
create: func(workload *computev1alpha1.Workload, i kubernetes.Interface) (result reconcile.Result, e error) {
return resultDone, nil
},
}
@ -165,11 +197,11 @@ func TestReconcileSync(t *testing.T) {
w := testWorkload()
w.Status.Cluster = &corev1.ObjectReference{}
w.Status.State = WorkloadStateRunning
w.Status.State = computev1alpha1.WorkloadStateRunning
r := &Reconciler{
Client: fake.NewFakeClient(w),
connect: func(*Workload) (i kubernetes.Interface, e error) { return nil, nil },
sync: func(workload *Workload, i kubernetes.Interface) (result reconcile.Result, e error) {
connect: func(*computev1alpha1.Workload) (i kubernetes.Interface, e error) { return nil, nil },
sync: func(workload *computev1alpha1.Workload, i kubernetes.Interface) (result reconcile.Result, e error) {
return resultDone, nil
},
}
@ -285,3 +317,277 @@ func TestConnect(t *testing.T) {
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(k).ShouldNot(BeNil())
}
func Test_addWorkloadReferenceLabel(t *testing.T) {
g := NewGomegaWithT(t)
// test workload with test testUid value
testUid := "test-testUid"
type args struct {
m *metav1.ObjectMeta
uid string
}
tests := []struct {
name string
args args
}{
{"Nil labels", args{&metav1.ObjectMeta{}, testUid}},
{"Empty labels", args{&metav1.ObjectMeta{Labels: make(map[string]string)}, testUid}},
{"Label added", args{&metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, testUid}},
{"Label updated", args{&metav1.ObjectMeta{Labels: map[string]string{workloadReferenceLabelKey: "foo-bar"}}, testUid}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
addWorkloadReferenceLabel(tt.args.m, tt.args.uid)
})
g.Expect(tt.args.m.Labels).ShouldNot(BeNil())
g.Expect(tt.args.m.Labels).Should(HaveKeyWithValue(workloadReferenceLabelKey, string(tt.args.uid)))
}
}
func Test_getWorkloadReferenceLabel(t *testing.T) {
g := NewGomegaWithT(t)
type args struct {
m metav1.ObjectMeta
}
tests := []struct {
name string
args args
want string
}{
{"Nil labels", args{metav1.ObjectMeta{}}, ""},
{"Empty labels", args{metav1.ObjectMeta{Labels: make(map[string]string)}}, ""},
{"Label not found", args{metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}}, ""},
{"Label found", args{metav1.ObjectMeta{Labels: map[string]string{workloadReferenceLabelKey: "test-uid"}}}, "test-uid"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g.Expect(getWorkloadReferenceLabel(tt.args.m)).Should(Equal(tt.want))
})
}
}
func Test_propagateDeployment(t *testing.T) {
g := NewGomegaWithT(t)
testName := "test-name"
targetNamespace := "test-ns"
workloadUID := "test-uid"
td := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: testName,
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"}, // to test selector update
},
},
},
}
// propagate create deployment without namespace value
client := NewSimpleClientset()
rd, err := propagateDeployment(client, td, targetNamespace, workloadUID)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rd).ShouldNot(BeNil())
g.Expect(rd.Labels).Should(HaveKeyWithValue(workloadReferenceLabelKey, workloadUID))
g.Expect(rd.Spec.Selector).ShouldNot(BeNil())
g.Expect(rd.Spec.Selector.MatchLabels).Should(HaveKeyWithValue("foo", "bar"))
// propagate create deployment with name collision
_, err = propagateDeployment(client, td, targetNamespace, workloadUID+"-2")
g.Expect(err).Should(MatchError(fmt.Errorf("cannot propagate, deployment %s/%s already exists", td.Namespace, td.Name)))
// propagate update deployment: add a new label to target deployment to test the update
td.Labels["foo"] = "bar"
rd, err = propagateDeployment(client, td, targetNamespace, workloadUID)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rd.Labels).Should(HaveKeyWithValue("foo", "bar"))
// propagate create deployment with the namespace value different from the workload's target namespace
td.Namespace = "default"
client = NewSimpleClientset()
rd, err = propagateDeployment(client, td, targetNamespace, workloadUID)
g.Expect(err).ShouldNot(HaveOccurred())
_, err = client.AppsV1().Deployments(targetNamespace).Get(td.Name, metav1.GetOptions{})
g.Expect(err).Should(HaveOccurred())
g.Expect(errors.IsNotFound(err)).Should(BeTrue())
g.Expect(rd.Labels).Should(HaveKeyWithValue(workloadReferenceLabelKey, workloadUID))
// test client errors
// GET deployment error
client = NewSimpleClientset()
client.PrependReactor("get", "deployments", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("test client get error")
})
_, err = propagateDeployment(client, td, targetNamespace, workloadUID)
g.Expect(err).Should(MatchError("test client get error"))
// CREATE deployment error
client = NewSimpleClientset()
client.PrependReactor("create", "deployments", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("test client create error")
})
_, err = propagateDeployment(client, td, targetNamespace, workloadUID)
g.Expect(err).Should(MatchError("test client create error"))
// UPDATE deployment error
client = NewSimpleClientset(td)
client.PrependReactor("update", "deployments", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("test client update error")
})
_, err = propagateDeployment(client, td, targetNamespace, workloadUID)
g.Expect(err).Should(MatchError("test client update error"))
}
func Test_propagateService(t *testing.T) {
g := NewGomegaWithT(t)
testName := "test-name"
targetNamespace := "test-ns"
workloadUID := "test-uid"
ts := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: testName,
},
}
// propagate create service without namespace value
client := NewSimpleClientset()
rs, err := propagateService(client, ts, targetNamespace, workloadUID)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs.Labels).Should(HaveKeyWithValue(workloadReferenceLabelKey, workloadUID))
// propagate create service with name collision
_, err = propagateService(client, ts, targetNamespace, workloadUID+"-2")
g.Expect(err).Should(MatchError(fmt.Errorf("cannot propagate, service %s/%s already exists", ts.Namespace, ts.Name)))
// propagate update service: add a new label to target service to test the update
ts.Labels["foo"] = "bar"
rs, err = propagateService(client, ts, targetNamespace, workloadUID)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs.Labels).Should(HaveKeyWithValue("foo", "bar"))
// propagate create service with the namespace value different from the workload's target namespace
ts.Namespace = "default"
client = NewSimpleClientset()
rs, err = propagateService(client, ts, targetNamespace, workloadUID)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs.Namespace).ShouldNot(Equal(targetNamespace))
g.Expect(rs.Labels).Should(HaveKeyWithValue(workloadReferenceLabelKey, workloadUID))
// test client errors
// GET service error
client = NewSimpleClientset()
client.PrependReactor("get", "services", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("test client get error")
})
_, err = propagateService(client, ts, targetNamespace, workloadUID)
g.Expect(err).Should(MatchError("test client get error"))
// CREATE service error
client = NewSimpleClientset()
client.PrependReactor("create", "services", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("test client create error")
})
_, err = propagateService(client, ts, targetNamespace, workloadUID)
g.Expect(err).Should(MatchError("test client create error"))
// UPDATE service error
client = NewSimpleClientset(ts)
client.PrependReactor("update", "services", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("test client update error")
})
_, err = propagateService(client, ts, targetNamespace, workloadUID)
g.Expect(err).Should(MatchError("test client update error"))
}
func Test_create(t *testing.T) {
g := NewGomegaWithT(t)
tw := testWorkload()
td := testDeployment()
ts := testService()
client := NewSimpleClientset()
r := &Reconciler{
Client: fake.NewFakeClient(tw),
propagateDeployment: func(i kubernetes.Interface, deployment *appsv1.Deployment, s string, s2 string) (*appsv1.Deployment, error) {
return td, nil
},
propagateService: func(i kubernetes.Interface, service *corev1.Service, s string, s2 string) (*corev1.Service, error) {
return ts, nil
},
}
expStatus := tw.Status.ConditionedStatus
expStatus.SetCreating()
rs, err := r._create(tw, client)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs).Should(Equal(resultDone))
g.Expect(tw.Status.ConditionedStatus).Should(corev1alpha1.MatchConditionedStatus(expStatus))
g.Expect(tw.Status.Deployment.UID).Should(Equal(td.UID))
g.Expect(tw.Status.Service.UID).Should(Equal(ts.UID))
}
func Test_create_Failures(t *testing.T) {
g := NewGomegaWithT(t)
tw := testWorkload()
client := NewSimpleClientset()
expStatus := tw.Status.ConditionedStatus
expStatus.SetCreating()
// Target namespace error
testError := "test error creating target namespace"
client.PrependReactor("create", "namespaces", func(action Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf(testError)
})
expStatus.SetFailed(errorCreating, testError)
r := &Reconciler{
Client: fake.NewFakeClient(tw),
}
rs, err := r._create(tw, client)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs).Should(Equal(resultRequeue))
g.Expect(tw.Status.ConditionedStatus).Should(corev1alpha1.MatchConditionedStatus(expStatus))
client.ReactionChain = client.ReactionChain[:0]
// Deployment propagation failure
testError = "test deployment propagation error"
r = &Reconciler{
Client: fake.NewFakeClient(tw),
propagateDeployment: func(i kubernetes.Interface, deployment *appsv1.Deployment, s string, s2 string) (*appsv1.Deployment, error) {
return nil, fmt.Errorf(testError)
},
}
expStatus.SetFailed(errorCreating, "test deployment propagation error")
rs, err = r._create(tw, client)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs).Should(Equal(resultRequeue))
g.Expect(tw.Status.ConditionedStatus).Should(corev1alpha1.MatchConditionedStatus(expStatus))
// Service propagation failure
testError = "test service propagation error"
r.propagateDeployment = func(i kubernetes.Interface, deployment *appsv1.Deployment, s string, s2 string) (*appsv1.Deployment, error) {
return testDeployment(), nil
}
r.propagateService = func(i kubernetes.Interface, deployment *corev1.Service, s string, s2 string) (*corev1.Service, error) {
return nil, fmt.Errorf(testError)
}
expStatus.SetFailed(errorCreating, "test deployment propagation error")
rs, err = r._create(tw, client)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(rs).Should(Equal(resultRequeue))
g.Expect(tw.Status.ConditionedStatus).Should(corev1alpha1.MatchConditionedStatus(expStatus))
}