scotty.scott 2 anni fa
parent
commit
00dc538fed

+ 2 - 0
.gitignore

@@ -23,3 +23,5 @@ testbin/*
 *.swp
 *.swo
 *~
+
+.vscode

+ 3 - 10
controllers/bluegreen/client.go

@@ -12,7 +12,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
 //go:generate go run github.com/golang/mock/mockgen@v1.6.0 -package bluegreen -destination mock.gen.go . Client
@@ -22,7 +21,7 @@ type Client interface {
 	CreateOrUpdateService(ctx context.Context, owner *v1.BlueGreen) error
 	CreateOrUpdateDeployment(ctx context.Context, owner *v1.BlueGreen, phase v1.BlueOrGreen, podSpec corev1.PodSpec) error
 
-	UpdateStatus(ctx context.Context, req ctrl.Request, status v1.BlueGreenStatus) error
+	UpdateStatus(ctx context.Context, bg *v1.BlueGreen) error
 }
 
 var _ Client = &BlueGreenClient{}
@@ -31,14 +30,8 @@ type BlueGreenClient struct {
 	client.Client
 }
 
-func (r *BlueGreenClient) UpdateStatus(ctx context.Context, req reconcile.Request, status v1.BlueGreenStatus) error {
-	return r.Status().Update(ctx, &v1.BlueGreen{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      req.Name,
-			Namespace: req.Namespace,
-		},
-		Status: status,
-	})
+func (r *BlueGreenClient) UpdateStatus(ctx context.Context, bg *v1.BlueGreen) error {
+	return r.Status().Update(ctx, bg)
 }
 
 func (r *BlueGreenClient) CreateOrUpdateDeployment(ctx context.Context, owner *v1.BlueGreen, phase v1.BlueOrGreen, podSpec corev1.PodSpec) error {

+ 4 - 5
controllers/bluegreen/mock.gen.go

@@ -13,7 +13,6 @@ import (
 	v10 "k8s.io/api/core/v1"
 	types "k8s.io/apimachinery/pkg/types"
 	client "sigs.k8s.io/controller-runtime/pkg/client"
-	reconcile "sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
 // MockClient is a mock of Client interface.
@@ -101,15 +100,15 @@ func (mr *MockClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface
 }
 
 // UpdateStatus mocks base method.
-func (m *MockClient) UpdateStatus(arg0 context.Context, arg1 reconcile.Request, arg2 v1.BlueGreenStatus) error {
+func (m *MockClient) UpdateStatus(arg0 context.Context, arg1 *v1.BlueGreen) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "UpdateStatus", arg0, arg1, arg2)
+	ret := m.ctrl.Call(m, "UpdateStatus", arg0, arg1)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // UpdateStatus indicates an expected call of UpdateStatus.
-func (mr *MockClientMockRecorder) UpdateStatus(arg0, arg1, arg2 interface{}) *gomock.Call {
+func (mr *MockClientMockRecorder) UpdateStatus(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockClient)(nil).UpdateStatus), arg0, arg1, arg2)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockClient)(nil).UpdateStatus), arg0, arg1)
 }

+ 8 - 6
controllers/bluegreen_controller.go

@@ -18,6 +18,7 @@ package controllers
 
 import (
 	"context"
+	"fmt"
 
 	v1 "github.com/kakao/bluegreen/api/v1"
 	"github.com/kakao/bluegreen/controllers/bluegreen"
@@ -52,27 +53,28 @@ func (r *BlueGreenReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 
 	// Create or Update Service
 	if err := r.CreateOrUpdateService(ctx, bluegreen); err != nil {
-		return ctrl.Result{}, err
+		return ctrl.Result{}, fmt.Errorf("fail to reconcile service: %w", err)
 	}
 
 	// Create or Update BlueDeployment
 	if spec := bluegreen.Spec.BlueSpec; spec != nil {
 		if err := r.CreateOrUpdateDeployment(ctx, bluegreen, v1.Blue, *spec); err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("fail to reconcile blue deployment: %w", err)
 		}
 	}
 
 	// Create or Update GreenDeployment
 	if spec := bluegreen.Spec.GreenSpec; spec != nil {
 		if err := r.CreateOrUpdateDeployment(ctx, bluegreen, v1.Green, *spec); err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("fail to reconcile green deployment: %w", err)
 		}
 	}
 
-	bluegreen.Status = v1.BlueGreenStatus{
-		RouteTo: &bluegreen.Spec.RouteTo,
+	bluegreen.Status.RouteTo = &bluegreen.Spec.RouteTo
+	if err := r.Client.UpdateStatus(ctx, bluegreen); err != nil {
+		return ctrl.Result{}, fmt.Errorf("fail to update status: %w", err)
 	}
-	return ctrl.Result{}, r.Client.UpdateStatus(ctx, req, bluegreen.Status)
+	return ctrl.Result{}, nil
 }
 
 // SetupWithManager sets up the controller with the Manager.

+ 112 - 0
controllers/bluegreen_reconciler_test.go

@@ -0,0 +1,112 @@
+package controllers
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	appv1 "github.com/kakao/bluegreen/api/v1"
+	"github.com/kakao/bluegreen/controllers/bluegreen"
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+)
+
+var (
+	testScheme = runtime.NewScheme()
+)
+
+func init() {
+	utilruntime.Must(clientgoscheme.AddToScheme(testScheme))
+	utilruntime.Must(appv1.AddToScheme(testScheme))
+}
+
+func TestBlueGreenResconciler(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+	c := bluegreen.NewMockClient(ctrl)
+	r := &BlueGreenReconciler{
+		Client: c,
+		Scheme: testScheme,
+	}
+
+	req := reconcile.Request{NamespacedName: types.NamespacedName{
+		Namespace: "test",
+		Name:      "bluegreen",
+	}}
+	bg := appv1.BlueGreen{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: req.Namespace,
+			Name:      req.Name,
+		},
+		Spec: appv1.BlueGreenSpec{
+			RouteTo: appv1.Blue,
+			BlueSpec: &v1.PodSpec{
+				Containers: []v1.Container{{Name: "test"}},
+			},
+			GreenSpec: nil,
+		},
+	}
+	blugreenMatcher := &GenericMatcher[*appv1.BlueGreen]{func(in *appv1.BlueGreen) bool { return reflect.DeepEqual(bg, *in) }}
+
+	// EXPECTING
+	c.EXPECT().Get(gomock.Any(), gomock.Eq(req.NamespacedName), gomock.Any()).
+		SetArg(2, bg).
+		Return(nil)
+	c.EXPECT().CreateOrUpdateService(gomock.Any(), blugreenMatcher).
+		Return(nil)
+	c.EXPECT().CreateOrUpdateDeployment(gomock.Any(), blugreenMatcher, gomock.Eq(appv1.Blue), gomock.Eq(*bg.Spec.BlueSpec)).
+		Return(nil)
+
+	c.EXPECT().UpdateStatus(gomock.Any(),
+		&GenericMatcher[*appv1.BlueGreen]{func(bg *appv1.BlueGreen) bool { return *bg.Status.RouteTo == appv1.Blue }},
+	).Return(nil)
+
+	// Reconciling
+	result, err := r.Reconcile(ctx, req)
+
+	// Validation
+	assert.NoError(t, err)
+	assert.Equal(t, reconcile.Result{Requeue: false}, result)
+}
+
+var _ gomock.Matcher = &GenericMatcher[interface{}]{}
+
+type GenericMatcher[T interface{}] struct {
+	F func(T) bool
+}
+
+func (m *GenericMatcher[T]) Matches(x interface{}) bool {
+	if v, ok := x.(T); ok {
+		return m.F(v)
+	} else {
+		return false
+	}
+}
+
+func (m *GenericMatcher[T]) String() string {
+	return fmt.Sprintf("matching %v", reflect.TypeOf(new(T)))
+}
+
+var _ gomock.Matcher = &RuntimeObjectMatcher{}
+
+type RuntimeObjectMatcher struct {
+	object runtime.Object
+}
+
+// Matches implements gomock.Matcher
+func (r *RuntimeObjectMatcher) Matches(x interface{}) bool {
+	return reflect.DeepEqual(r.object, x)
+}
+
+// String implements gomock.Matcher
+func (r *RuntimeObjectMatcher) String() string {
+	gok := r.object.GetObjectKind().GroupVersionKind()
+	return fmt.Sprintf("should match %s/%s/%s", gok.Group, gok.Version, gok.Kind)
+}