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 moves shared helper functions from each controller test files to a common file.

Design doc: http://bit.ly/hnc-type-configuration
Issue: kubernetes-retired#411
  • Loading branch information
sophieliu15 committed Feb 19, 2020
1 parent 0b38160 commit 8551f06
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 111 deletions.
1 change: 1 addition & 0 deletions incubator/hnc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kubernetes-sigs/multi-tenancy v0.0.0-20200219071425-0b3816093ff1 h1:eX1j6qGAr6OB68yt5YZTfPZa/0IXASQUPEZviQU92aA=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
Expand Down
51 changes: 51 additions & 0 deletions incubator/hnc/pkg/controllers/config_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package controllers_test

import (
"context"
"time"

"github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

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 singleton back to the default value.
// Wait 1 second to make sure the latest version of the singleton has been successfully updated
// by the client.
time.Sleep(1 * time.Second)
c := getHNCConfig(ctx)
c.Spec = config.GetDefaultConfigSpec()
updateHNCConfig(ctx, c)
})

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-sec" is not propagated to bar because Secret hasn't been configured in HNCConfiguration.
Eventually(hasSecret(ctx, barName, "foo-sec")).Should(BeFalse())

c := getHNCConfig(ctx)
addSecretToHNCConfig(ctx, c)

// "foo-sec" is now propagated to bar.
Eventually(hasSecret(ctx, barName, "foo-sec")).Should(BeTrue())
Expect(secretInheritedFrom(ctx, barName, "foo-sec")).Should(Equal(fooName))
})
})
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
90 changes: 15 additions & 75 deletions incubator/hnc/pkg/controllers/object_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import (
var _ = Describe("Secret", func() {
ctx := context.Background()

// Setup HNCConfiguration.
hncConfig := newHNCConfig()

var (
fooName string
barName string
Expand All @@ -28,7 +25,11 @@ var _ = Describe("Secret", func() {

BeforeEach(func() {
// Add secret to HNCConfiguration so that an ObjectReconciler will be created for secret.
addSecretToHNCConfig(ctx, hncConfig)
// Wait 1 second to make sure the latest version of the singleton has been successfully updated
// by the client.
time.Sleep(1 * time.Second)
c := getHNCConfig(ctx)
addSecretToHNCConfig(ctx, c)

fooName = createNS(ctx, "foo")
barName = createNS(ctx, "bar")
Expand All @@ -40,6 +41,16 @@ var _ = Describe("Secret", func() {
makeSecret(ctx, bazName, "baz-sec")
})

AfterEach(func() {
// Change current singleton back to the default value.
// Wait 1 second to make sure the latest version of the singleton has been successfully updated
// by the client.
time.Sleep(1 * time.Second)
c := getHNCConfig(ctx)
c.Spec = config.GetDefaultConfigSpec()
updateHNCConfig(ctx, c)
})

It("should be copied to descendents", func() {
setParent(ctx, barName, fooName)
setParent(ctx, bazName, barName)
Expand Down Expand Up @@ -219,56 +230,6 @@ var _ = Describe("Secret", func() {
})
})

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
}

func setParent(ctx context.Context, nm string, pnm string) {
hier := newOrGetHierarchy(ctx, nm)
oldPNM := hier.Spec.Parent
hier.Spec.Parent = pnm
updateHierarchy(ctx, hier)
if oldPNM != "" {
EventuallyWithOffset(1, func() []string {
pHier := getHierarchyWithOffset(1, ctx, oldPNM)
return pHier.Status.Children
}).ShouldNot(ContainElement(nm))
}
if pnm != "" {
EventuallyWithOffset(1, func() []string {
pHier := getHierarchyWithOffset(1, ctx, pnm)
return pHier.Status.Children
}).Should(ContainElement(nm))
}
}

func newOrGetHierarchy(ctx context.Context, nm string) *api.HierarchyConfiguration {
hier := &api.HierarchyConfiguration{}
hier.ObjectMeta.Namespace = nm
Expand Down Expand Up @@ -322,24 +283,3 @@ func removeSecret(ctx context.Context, nsName, secretName string) {
sec.Namespace = nsName
ExpectWithOffset(1, k8sClient.Delete(ctx, sec)).Should(Succeed())
}

func newHNCConfig() *api.HNCConfiguration {
hncConfig := &api.HNCConfiguration{}
hncConfig.ObjectMeta.Name = api.HNCConfigSingleton
hncConfig.Spec = config.GetDefaultConfigSpec()
return hncConfig
}

func updateHNCConfig(ctx context.Context, c *api.HNCConfiguration) {
if c.CreationTimestamp.IsZero() {
ExpectWithOffset(1, k8sClient.Create(ctx, c)).Should(Succeed())
} else {
ExpectWithOffset(1, k8sClient.Update(ctx, c)).Should(Succeed())
}
}

func addSecretToHNCConfig(ctx context.Context, c *api.HNCConfiguration) {
secSpec := api.TypeSynchronizationSpec{APIVersion: "v1", Kind: "Secret", Mode: api.Propagate}
c.Spec.Types = append(c.Spec.Types, secSpec)
updateHNCConfig(ctx, c)
}
7 changes: 7 additions & 0 deletions incubator/hnc/pkg/controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package controllers_test

import (
"context"
"path/filepath"
"testing"
"time"
Expand Down Expand Up @@ -90,6 +91,12 @@ var _ = BeforeSuite(func(done Done) {
k8sClient = k8sManager.GetClient()
Expect(k8sClient).ToNot(BeNil())

// Setup HNCConfiguration object here because it is a cluster-wide singleton shared by all reconcilers.
hncConfig := newHNCConfig()
Expect(hncConfig).ToNot(BeNil())
ctx := context.Background()
updateHNCConfig(ctx, hncConfig)

go func() {
err = k8sManager.Start(ctrl.SetupSignalHandler())
Expect(err).ToNot(HaveOccurred())
Expand Down
133 changes: 133 additions & 0 deletions incubator/hnc/pkg/controllers/test_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package controllers_test

import (
"context"
"crypto/rand"
"fmt"

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

api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1"
)

func setParent(ctx context.Context, nm string, pnm string) {
hier := newOrGetHierarchy(ctx, nm)
oldPNM := hier.Spec.Parent
hier.Spec.Parent = pnm
updateHierarchy(ctx, hier)
if oldPNM != "" {
EventuallyWithOffset(1, func() []string {
pHier := getHierarchyWithOffset(1, ctx, oldPNM)
return pHier.Status.Children
}).ShouldNot(ContainElement(nm))
}
if pnm != "" {
EventuallyWithOffset(1, func() []string {
pHier := getHierarchyWithOffset(1, ctx, pnm)
return pHier.Status.Children
}).Should(ContainElement(nm))
}
}

// 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)
}

func newHNCConfig() *api.HNCConfiguration {
hncConfig := &api.HNCConfiguration{}
hncConfig.ObjectMeta.Name = api.HNCConfigSingleton
hncConfig.Spec = config.GetDefaultConfigSpec()
return hncConfig
}

func updateHNCConfig(ctx context.Context, c *api.HNCConfiguration) {
if c.CreationTimestamp.IsZero() {
ExpectWithOffset(1, k8sClient.Create(ctx, c)).Should(Succeed())
} else {
ExpectWithOffset(1, k8sClient.Update(ctx, c)).Should(Succeed())
}
}

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
}

// 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 addSecretToHNCConfig(ctx context.Context, c *api.HNCConfiguration) {
secSpec := api.TypeSynchronizationSpec{APIVersion: "v1", Kind: "Secret", Mode: api.Propagate}
c.Spec.Types = append(c.Spec.Types, secSpec)
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
}

0 comments on commit 8551f06

Please sign in to comment.