Skip to content

Commit

Permalink
Add a unit test for config_controller.go
Browse files Browse the repository at this point in the history
Add a unit test for config_controller.go to test the case that when a new type is added to HNCConfiguration singleton, the corresponding object reconciler can be created correctly.

This PR also does the following refactoring:
   -  Moves shared helper functions from each controller test files to a common file: test_helpers_test.go
   -  Switches the object type in object_controller_test.go from Secret to Role because:
       -  The behaviors that are tested in the object_controller_test.go are irrelevant to the object type or the creation of a new object reconciler.
       -  Since the test suite creates the object reconciler for Role by default, object_controller_test.go can use the existing Role object reconciler
       for the tests instead of creating a new object reconciler.
       -  The tests inside controllers_test package share the same test environment, which means if the test in one Describe container adds a type to
       HNCConfiguration singleton, the corresponding object reconciler for that type will be created and will still be there when running subsequent Describe
       containers in the package. Until we find a way to "cleanup" object reconcilers, it is better to only create new object reconcilers when necessary.

Design doc: http://bit.ly/hnc-type-configuration
Issue: kubernetes-retired#411
  • Loading branch information
sophieliu15 committed Feb 20, 2020
1 parent 483fdfa commit 0414e0e
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 217 deletions.
111 changes: 111 additions & 0 deletions incubator/hnc/pkg/controllers/config_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package controllers_test

import (
"context"
"time"

api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1"
"github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)

var _ = Describe("HNCConfiguration", func() {
ctx := context.Background()

var (
fooName string
barName string
)

BeforeEach(func() {
fooName = createNS(ctx, "foo")
barName = createNS(ctx, "bar")
})

AfterEach(func() {
// Change current config back to the default value.
Eventually(func() error {
return resetHNCConfigToDefault(ctx)
}).Should(Succeed())
})

It("should propagate objects whose types have been added to HNCConfiguration", func() {
setParent(ctx, barName, fooName)
makeSecret(ctx, fooName, "foo-sec")

// Wait 1 second to give "foo-sec" a chance to be propagated to bar, if it can be propagated.
time.Sleep(1 * time.Second)
// Foo should have "foo-sec" since we created it there.
Eventually(hasSecret(ctx, fooName, "foo-sec")).Should(BeTrue())
// "foo-sec" is not propagated to bar because Secret hasn't been configured in HNCConfiguration.
Eventually(hasSecret(ctx, barName, "foo-sec")).Should(BeFalse())

Eventually(func() error {
c := getHNCConfig(ctx)
return addSecretToHNCConfig(ctx, c)
}).Should(Succeed())

// "foo-sec" should now be propagated from foo to bar.
Eventually(hasSecret(ctx, barName, "foo-sec")).Should(BeTrue())
Expect(secretInheritedFrom(ctx, barName, "foo-sec")).Should(Equal(fooName))
})
})

func resetHNCConfigToDefault(ctx context.Context) error {
c := getHNCConfig(ctx)
c.Spec = config.GetDefaultConfigSpec()
return k8sClient.Update(ctx, c)
}

func getHNCConfig(ctx context.Context) *api.HNCConfiguration {
return getHNCConfigWithOffset(1, ctx)
}

func getHNCConfigWithOffset(offset int, ctx context.Context) *api.HNCConfiguration {
snm := types.NamespacedName{Name: api.HNCConfigSingleton}
config := &api.HNCConfiguration{}
EventuallyWithOffset(offset+1, func() error {
return k8sClient.Get(ctx, snm, config)
}).Should(Succeed())
return config
}

func addSecretToHNCConfig(ctx context.Context, c *api.HNCConfiguration) error {
secSpec := api.TypeSynchronizationSpec{APIVersion: "v1", Kind: "Secret", Mode: api.Propagate}
c.Spec.Types = append(c.Spec.Types, secSpec)
return updateHNCConfig(ctx, c)
}

func makeSecret(ctx context.Context, nsName, secretName string) {
sec := &corev1.Secret{}
sec.Name = secretName
sec.Namespace = nsName
ExpectWithOffset(1, k8sClient.Create(ctx, sec)).Should(Succeed())
}

func hasSecret(ctx context.Context, nsName, secretName string) func() bool {
// `Eventually` only works with a fn that doesn't take any args
return func() bool {
nnm := types.NamespacedName{Namespace: nsName, Name: secretName}
sec := &corev1.Secret{}
err := k8sClient.Get(ctx, nnm, sec)
return err == nil
}
}

func secretInheritedFrom(ctx context.Context, nsName, secretName string) string {
nnm := types.NamespacedName{Namespace: nsName, Name: secretName}
sec := &corev1.Secret{}
if err := k8sClient.Get(ctx, nnm, sec); err != nil {
// should have been caught above
return err.Error()
}
if sec.ObjectMeta.Labels == nil {
return ""
}
lif, _ := sec.ObjectMeta.Labels["hnc.x-k8s.io/inheritedFrom"]
return lif
}
36 changes: 0 additions & 36 deletions incubator/hnc/pkg/controllers/hierarchy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package controllers_test
import (
"context"
"fmt"
"math/rand"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -386,41 +385,6 @@ func updateHierarchy(ctx context.Context, h *api.HierarchyConfiguration) {
}
}

// createNSName generates random namespace names. Namespaces are never deleted in test-env because
// the building Namespace controller (which finalizes namespaces) doesn't run; I searched Github and
// found that everyone who was deleting namespaces was *also* very intentionally generating random
// names, so I guess this problem is widespread.
func createNSName(prefix string) string {
suffix := make([]byte, 10)
rand.Read(suffix)
return fmt.Sprintf("%s-%x", prefix, suffix)
}

// createNSWithLabel has similar function to createNS with label as additional parameter
func createNSWithLabel(ctx context.Context, prefix string, label map[string]string) string {
nm := createNSName(prefix)

// Create the namespace
ns := &corev1.Namespace{}
ns.SetLabels(label)
ns.Name = nm
Expect(k8sClient.Create(ctx, ns)).Should(Succeed())
return nm
}

// createNS is a convenience function to create a namespace and wait for its singleton to be
// created. It's used in other tests in this package, but basically duplicates the code in this test
// (it didn't originally). TODO: refactor.
func createNS(ctx context.Context, prefix string) string {
nm := createNSName(prefix)

// Create the namespace
ns := &corev1.Namespace{}
ns.Name = nm
Expect(k8sClient.Create(ctx, ns)).Should(Succeed())
return nm
}

func getLabel(ctx context.Context, from, label string) func() string {
return func() string {
ns := getNamespace(ctx, from)
Expand Down
Loading

0 comments on commit 0414e0e

Please sign in to comment.