Skip to content

Commit

Permalink
update fsm and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
m00g3n committed Aug 12, 2024
1 parent 98cb471 commit 5def1a1
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 49 deletions.
7 changes: 7 additions & 0 deletions api/v1/runtime_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,13 @@ func (k *Runtime) IsConditionSetWithStatus(c RuntimeConditionType, r RuntimeCond
return false
}

func (k *Runtime) IsConditionConfiguredSuccess() bool {
return k.IsConditionSetWithStatus(
ConditionTypeRuntimeConfigured,
ConditionReasonShootCreationCompleted,
metav1.ConditionTrue)
}

func (k *Runtime) ValidateRequiredLabels() error {
var requiredLabelKeys = []string{
LabelKymaInstanceID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,42 @@ var (
}
)

func sFnApplyClusterRoleBindings(ctx context.Context, m *fsm, s *systemState) (stateFn, *ctrl.Result, error) {
// prepare subresource client to request admin kubeconfig
srscClient := m.ShootClient.SubResource("adminkubeconfig")
shootAdminClient, err := GetShootClient(ctx, srscClient, s.shoot)
if err != nil {
updateCRBApplyFailed(&s.instance)
return updateStatusAndStopWithError(err)
}
// list existing cluster role bindings
var crbList rbacv1.ClusterRoleBindingList
if err := shootAdminClient.List(ctx, &crbList); err != nil {
updateCRBApplyFailed(&s.instance)
return updateStatusAndStopWithError(err)
}

removed := getRemoved(crbList.Items, s.instance.Spec.Security.Administrators)
missing := getMissing(crbList.Items, s.instance.Spec.Security.Administrators)

for _, fn := range []func() error{
newDelCRBs(ctx, shootAdminClient, removed),
newAddCRBs(ctx, shootAdminClient, missing),
} {
if err := fn(); err != nil {
updateCRBApplyFailed(&s.instance)
return updateStatusAndStopWithError(err)
}
}

s.instance.UpdateStateReady(
imv1.ConditionTypeRuntimeConfigured,
imv1.ConditionReasonConfigurationCompleted,
"kubeconfig admin access updated",
)
return updateStatusAndStop()
}

//nolint:gochecknoglobals
var GetShootClient = func(ctx context.Context,
adminKubeconfigClient client.SubResourceClient, shoot *gardener_api.Shoot) (client.Client, error) {
Expand Down Expand Up @@ -150,39 +186,3 @@ func updateCRBApplyFailed(rt *imv1.Runtime) {
"failed to update kubeconfig admin access",
)
}

func sFnApplyClusterRoleBindings(ctx context.Context, m *fsm, s *systemState) (stateFn, *ctrl.Result, error) {
// prepare subresource client to request admin kubeconfig
srscClient := m.ShootClient.SubResource("adminkubeconfig")
shootAdminClient, err := GetShootClient(ctx, srscClient, s.shoot)
if err != nil {
updateCRBApplyFailed(&s.instance)
return updateStatusAndStopWithError(err)
}
// list existing cluster role bindings
var crbList rbacv1.ClusterRoleBindingList
if err := shootAdminClient.List(ctx, &crbList); err != nil {
updateCRBApplyFailed(&s.instance)
return updateStatusAndStopWithError(err)
}

removed := getRemoved(crbList.Items, s.instance.Spec.Security.Administrators)
missing := getMissing(crbList.Items, s.instance.Spec.Security.Administrators)

// FIXME add status check
if len(removed) == 0 && len(missing) == 0 {
stop()
}

for _, fn := range []func() error{
newDelCRBs(ctx, shootAdminClient, removed),
newAddCRBs(ctx, shootAdminClient, missing),
} {
if err := fn(); err != nil {
updateCRBApplyFailed(&s.instance)
return updateStatusAndStopWithError(err)
}
}

return updateStatusAndRequeue()
}
236 changes: 236 additions & 0 deletions internal/controller/runtime/fsm/runtime_fsm_apply_crb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package fsm

import (
"context"
"fmt"
"time"

gardener_api "github.com/gardener/gardener/pkg/apis/core/v1beta1"
imv1 "github.com/kyma-project/infrastructure-manager/api/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

var _ = Describe(`runtime_fsm_apply_crb`, Label("applyCRB"), func() {

var testErr = fmt.Errorf("test error")

DescribeTable("isRBACUserKind",
func(s rbacv1.Subject, expected bool) {
actual := isRBACUserKind(s)
Expect(actual).To(Equal(expected))
},
Entry("shoud detect if subject is not user kind", rbacv1.Subject{}, false),
Entry("shoud detect if subject is from invalid group",
rbacv1.Subject{
Kind: rbacv1.UserKind,
}, false),
Entry("shoud detect if subject user from valid group",
rbacv1.Subject{
APIGroup: rbacv1.GroupName,
Kind: rbacv1.UserKind,
}, true),
)

DescribeTable("getMissing",
func(tc tcGetCRB) {
actual := getMissing(tc.crbs, tc.admins)
Expect(actual).To(BeComparableTo(tc.expected))
},
Entry("should return a list with CRBs to be created", tcGetCRB{
admins: []string{"test1", "test2"},
crbs: nil,
expected: []rbacv1.ClusterRoleBinding{
toAdminClusterRoleBinding("test1"),
toAdminClusterRoleBinding("test2"),
},
}),
Entry("should return nil list if no admins missing", tcGetCRB{
admins: []string{"test1"},
crbs: []rbacv1.ClusterRoleBinding{
toAdminClusterRoleBinding("test1"),
},
expected: nil,
}),
)

DescribeTable("getRemoved",
func(tc tcGetCRB) {
actual := getRemoved(tc.crbs, tc.admins)
Expect(actual).To(BeComparableTo(tc.expected))
},
Entry("should return nil list if CRB list is nil", tcGetCRB{
admins: []string{"test1"},
crbs: nil,
expected: nil,
}),
Entry("should return nil list if CRB list is empty", tcGetCRB{
admins: []string{"test1"},
crbs: []rbacv1.ClusterRoleBinding{},
expected: nil,
}),
Entry("should return nil list if no admins to remove", tcGetCRB{
admins: []string{"test1"},
crbs: []rbacv1.ClusterRoleBinding{toAdminClusterRoleBinding("test1")},
expected: nil,
}),
Entry("should return list if with CRBs to remove", tcGetCRB{
admins: []string{"test2"},
crbs: []rbacv1.ClusterRoleBinding{
toAdminClusterRoleBinding("test1"),
toAdminClusterRoleBinding("test2"),
toAdminClusterRoleBinding("test3"),
},
expected: []rbacv1.ClusterRoleBinding{
toAdminClusterRoleBinding("test1"),
toAdminClusterRoleBinding("test3"),
},
}),
)

testRuntime := imv1.Runtime{
ObjectMeta: metav1.ObjectMeta{
Name: "testme1",
Namespace: "default",
},
}

testRuntimeWithAdmin := imv1.Runtime{
ObjectMeta: metav1.ObjectMeta{
Name: "testme1",
Namespace: "default",
},
Spec: imv1.RuntimeSpec{
Security: imv1.Security{
Administrators: []string{
"test-admin1",
},
},
},
}

testScheme, err := newTestScheme()
Expect(err).ShouldNot(HaveOccurred())

defaultSetup := func(f *fsm) error {
GetShootClient = func(
_ context.Context,
_ client.SubResourceClient,
_ *gardener_api.Shoot) (client.Client, error) {
return f.Client, nil
}
return nil
}

DescribeTable("sFnAppluClusterRoleBindings",
func(tc tcApplySfn) {
// initialize test data if required
Expect(tc.init()).ShouldNot(HaveOccurred())

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10000)
defer cancel()

actualResult, actualErr := tc.fsm.Run(ctx, tc.instance)
Expect(actualResult).Should(BeComparableTo(tc.expected.result))

matchErr := BeNil()
if tc.expected.err != nil {
matchErr = MatchError(tc.expected.err)
}
Expect(actualErr).Should(matchErr)
},

Entry("add admin", tcApplySfn{
instance: testRuntimeWithAdmin,
expected: tcSfnExpected{
err: nil,
},
fsm: must(
newFakeFSM,
withFakedK8sClient(testScheme, &testRuntimeWithAdmin),
withFn(sFnApplyClusterRoleBindings),
withFakeEventRecorder(1),
),
setup: defaultSetup,
}),

Entry("nothing change", tcApplySfn{
instance: testRuntime,
expected: tcSfnExpected{
err: nil,
},
fsm: must(
newFakeFSM,
withFakedK8sClient(testScheme, &testRuntime),
withFn(sFnApplyClusterRoleBindings),
withFakeEventRecorder(1),
),
setup: defaultSetup,
}),

Entry("error getting client", tcApplySfn{
expected: tcSfnExpected{
err: testErr,
},
fsm: must(
newFakeFSM,
withFakedK8sClient(testScheme, &testRuntime),
withFn(sFnApplyClusterRoleBindings),
withFakeEventRecorder(1),
),
setup: defaultSetup,
}),
)
})

type tcGetCRB struct {
crbs []rbacv1.ClusterRoleBinding
admins []string
expected []rbacv1.ClusterRoleBinding
}

type tcSfnExpected struct {
result ctrl.Result
err error
}

type tcApplySfn struct {
expected tcSfnExpected
setup func(m *fsm) error
fsm *fsm
instance imv1.Runtime
}

func (c *tcApplySfn) init() error {
if c.setup != nil {
return c.setup(c.fsm)
}
return nil
}

func toCRBs(admins []string) (result []rbacv1.ClusterRoleBinding) {
for _, crb := range admins {
result = append(result, toAdminClusterRoleBinding(crb))
}
return result
}

func newTestScheme() (*runtime.Scheme, error) {
schema := runtime.NewScheme()

for _, fn := range []func(*runtime.Scheme) error{
imv1.AddToScheme,
rbacv1.AddToScheme,
} {
if err := fn(schema); err != nil {
return nil, err
}
}
return schema, nil
}
14 changes: 12 additions & 2 deletions internal/controller/runtime/fsm/runtime_fsm_create_kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,25 @@ func sFnCreateKubeconfig(ctx context.Context, m *fsm, s *systemState) (stateFn,
if err != nil {
if !k8serrors.IsNotFound(err) {
m.log.Error(err, "GardenerCluster CR read error", "name", runtimeID)
s.instance.UpdateStatePending(imv1.ConditionTypeRuntimeKubeconfigReady, imv1.ConditionReasonKubernetesAPIErr, "False", err.Error())
s.instance.UpdateStatePending(
imv1.ConditionTypeRuntimeKubeconfigReady,
imv1.ConditionReasonKubernetesAPIErr,
"False",
err.Error(),
)
return updateStatusAndStop()
}

m.log.Info("GardenerCluster CR not found, creating a new one", "Name", runtimeID)
err = m.Create(ctx, makeGardenerClusterForRuntime(s.instance, s.shoot))
if err != nil {
m.log.Error(err, "GardenerCluster CR create error", "name", runtimeID)
s.instance.UpdateStatePending(imv1.ConditionTypeRuntimeKubeconfigReady, imv1.ConditionReasonKubernetesAPIErr, "False", err.Error())
s.instance.UpdateStatePending(
imv1.ConditionTypeRuntimeKubeconfigReady,
imv1.ConditionReasonKubernetesAPIErr,
"False",
err.Error(),
)
return updateStatusAndStop()
}

Expand Down
13 changes: 11 additions & 2 deletions internal/controller/runtime/fsm/runtime_fsm_create_shoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ func sFnCreateShoot(ctx context.Context, m *fsm, s *systemState) (stateFn, *ctrl
newShoot, err := convertShoot(&s.instance, m.ConverterConfig)
if err != nil {
m.log.Error(err, "Failed to convert Runtime instance to shoot object")
return updateStatePendingWithErrorAndStop(&s.instance, imv1.ConditionTypeRuntimeProvisioned, imv1.ConditionReasonConversionError, "Runtime conversion error")
return updateStatePendingWithErrorAndStop(
&s.instance,
imv1.ConditionTypeRuntimeProvisioned,
imv1.ConditionReasonConversionError,
"Runtime conversion error")
}

err = m.ShootClient.Create(ctx, &newShoot)
Expand All @@ -29,7 +33,12 @@ func sFnCreateShoot(ctx context.Context, m *fsm, s *systemState) (stateFn, *ctrl
)
return updateStatusAndRequeueAfter(gardenerRequeueDuration)
}
m.log.Info("Gardener shoot for runtime initialised successfully", "Name", newShoot.Name, "Namespace", newShoot.Namespace)

m.log.Info(
"Gardener shoot for runtime initialised successfully",
"Name", newShoot.Name,
"Namespace", newShoot.Namespace,
)

s.instance.UpdateStatePending(
imv1.ConditionTypeRuntimeProvisioned,
Expand Down
Loading

0 comments on commit 5def1a1

Please sign in to comment.