fed: Add integration test for secrets
This commit is contained in:
@@ -29,6 +29,7 @@ filegroup(
|
||||
"//federation/pkg/dnsprovider:all-srcs",
|
||||
"//federation/pkg/federation-controller:all-srcs",
|
||||
"//federation/pkg/kubefed:all-srcs",
|
||||
"//federation/pkg/typeadapters:all-srcs",
|
||||
"//federation/registry/cluster:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
|
42
federation/pkg/typeadapters/BUILD
Normal file
42
federation/pkg/typeadapters/BUILD
Normal file
@@ -0,0 +1,42 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"adapter.go",
|
||||
"secret.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/client/clientset_generated/federation_clientset:go_default_library",
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//federation/pkg/typeadapters/crudtester:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
48
federation/pkg/typeadapters/adapter.go
Normal file
48
federation/pkg/typeadapters/adapter.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package typeadapters
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
)
|
||||
|
||||
// FederatedTypeAdapter defines operations for interacting with a
|
||||
// federated type. Code written to this interface can then target any
|
||||
// type for which an implementation of this interface exists.
|
||||
type FederatedTypeAdapter interface {
|
||||
SetClient(client federationclientset.Interface)
|
||||
|
||||
Kind() string
|
||||
Equivalent(obj1, obj2 pkgruntime.Object) bool
|
||||
ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta
|
||||
NamespacedName(obj pkgruntime.Object) types.NamespacedName
|
||||
|
||||
// Fed* operations target the federation control plane
|
||||
FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error)
|
||||
FedGet(namespacedName types.NamespacedName) (pkgruntime.Object, error)
|
||||
FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error)
|
||||
FedDelete(namespacedName types.NamespacedName, options *metav1.DeleteOptions) error
|
||||
|
||||
// The following operations are intended to target a cluster that is a member of a federation
|
||||
ClusterGet(client clientset.Interface, namespacedName types.NamespacedName) (pkgruntime.Object, error)
|
||||
|
||||
NewTestObject(namespace string) pkgruntime.Object
|
||||
}
|
35
federation/pkg/typeadapters/crudtester/BUILD
Normal file
35
federation/pkg/typeadapters/crudtester/BUILD
Normal file
@@ -0,0 +1,35 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["crudtester.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/pkg/typeadapters:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
217
federation/pkg/typeadapters/crudtester/crudtester.go
Normal file
217
federation/pkg/typeadapters/crudtester/crudtester.go
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package crudtester
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/federation/pkg/typeadapters"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
)
|
||||
|
||||
const (
|
||||
AnnotationTestFederationCRUDUpdate string = "federation.kubernetes.io/test-federation-crud-update"
|
||||
)
|
||||
|
||||
// TestLogger defines operations common across different types of testing
|
||||
type TestLogger interface {
|
||||
Fatalf(format string, args ...interface{})
|
||||
Fatal(msg string)
|
||||
Logf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// FederatedTypeCRUDTester exercises Create/Read/Update/Delete operations for
|
||||
// federated types via the Federation API and validates that the
|
||||
// results of those operations are propagated to clusters that are
|
||||
// members of a federation.
|
||||
type FederatedTypeCRUDTester struct {
|
||||
tl TestLogger
|
||||
adapter typeadapters.FederatedTypeAdapter
|
||||
kind string
|
||||
clusterClients []clientset.Interface
|
||||
waitInterval time.Duration
|
||||
// Federation operations will use wait.ForeverTestTimeout. Any
|
||||
// operation that involves member clusters may take longer due to
|
||||
// propagation latency.
|
||||
clusterWaitTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewFederatedTypeCRUDTester(testLogger TestLogger, adapter typeadapters.FederatedTypeAdapter, clusterClients []clientset.Interface, waitInterval, clusterWaitTimeout time.Duration) *FederatedTypeCRUDTester {
|
||||
return &FederatedTypeCRUDTester{
|
||||
tl: testLogger,
|
||||
adapter: adapter,
|
||||
kind: adapter.Kind(),
|
||||
clusterClients: clusterClients,
|
||||
waitInterval: waitInterval,
|
||||
clusterWaitTimeout: clusterWaitTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) CheckLifecycle(desiredObject pkgruntime.Object) {
|
||||
obj := c.CheckCreate(desiredObject)
|
||||
c.CheckUpdate(obj)
|
||||
|
||||
// Validate the golden path - removal of dependents
|
||||
orphanDependents := false
|
||||
c.CheckDelete(obj, &orphanDependents)
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) CheckCreate(desiredObject pkgruntime.Object) pkgruntime.Object {
|
||||
namespace := c.adapter.ObjectMeta(desiredObject).Namespace
|
||||
c.tl.Logf("Creating new federated %s in namespace %q", c.kind, namespace)
|
||||
|
||||
obj, err := c.adapter.FedCreate(desiredObject)
|
||||
if err != nil {
|
||||
c.tl.Fatalf("Error creating federated %s in namespace %q : %v", c.kind, namespace, err)
|
||||
}
|
||||
|
||||
namespacedName := c.adapter.NamespacedName(obj)
|
||||
c.tl.Logf("Created new federated %s %q", c.kind, namespacedName)
|
||||
|
||||
c.CheckPropagation(obj)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) CheckUpdate(obj pkgruntime.Object) {
|
||||
namespacedName := c.adapter.NamespacedName(obj)
|
||||
|
||||
var initialAnnotation string
|
||||
meta := c.adapter.ObjectMeta(obj)
|
||||
if meta.Annotations != nil {
|
||||
initialAnnotation = meta.Annotations[AnnotationTestFederationCRUDUpdate]
|
||||
}
|
||||
|
||||
c.tl.Logf("Updating federated %s %q", c.kind, namespacedName)
|
||||
updatedObj, err := c.updateFedObject(obj)
|
||||
if err != nil {
|
||||
c.tl.Fatalf("Error updating federated %s %q: %v", c.kind, namespacedName, err)
|
||||
}
|
||||
|
||||
// updateFedObject is expected to have changed the value of the annotation
|
||||
meta = c.adapter.ObjectMeta(updatedObj)
|
||||
updatedAnnotation := meta.Annotations[AnnotationTestFederationCRUDUpdate]
|
||||
if updatedAnnotation == initialAnnotation {
|
||||
c.tl.Fatalf("Federated %s %q not mutated", c.kind, namespacedName)
|
||||
}
|
||||
|
||||
c.CheckPropagation(updatedObj)
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) CheckDelete(obj pkgruntime.Object, orphanDependents *bool) {
|
||||
namespacedName := c.adapter.NamespacedName(obj)
|
||||
|
||||
c.tl.Logf("Deleting federated %s %q", c.kind, namespacedName)
|
||||
err := c.adapter.FedDelete(namespacedName, &metav1.DeleteOptions{OrphanDependents: orphanDependents})
|
||||
if err != nil {
|
||||
c.tl.Fatalf("Error deleting federated %s %q: %v", c.kind, namespacedName, err)
|
||||
}
|
||||
|
||||
deletingInCluster := (orphanDependents != nil && *orphanDependents == false)
|
||||
|
||||
waitTimeout := wait.ForeverTestTimeout
|
||||
if deletingInCluster {
|
||||
// May need extra time to delete both federation and cluster resources
|
||||
waitTimeout = c.clusterWaitTimeout
|
||||
}
|
||||
|
||||
// Wait for deletion. The federation resource will only be removed once orphan deletion has been
|
||||
// completed or deemed unnecessary.
|
||||
err = wait.PollImmediate(c.waitInterval, waitTimeout, func() (bool, error) {
|
||||
_, err := c.adapter.FedGet(namespacedName)
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
})
|
||||
if err != nil {
|
||||
c.tl.Fatalf("Error deleting federated %s %q: %v", c.kind, namespacedName, err)
|
||||
}
|
||||
|
||||
var stateMsg string = "present"
|
||||
if deletingInCluster {
|
||||
stateMsg = "not present"
|
||||
}
|
||||
for _, client := range c.clusterClients {
|
||||
_, err := c.adapter.ClusterGet(client, namespacedName)
|
||||
switch {
|
||||
case !deletingInCluster && errors.IsNotFound(err):
|
||||
c.tl.Fatalf("Federated %s %q was unexpectedly deleted from a member cluster", c.kind, namespacedName)
|
||||
case deletingInCluster && err == nil:
|
||||
c.tl.Fatalf("Federated %s %q was unexpectedly orphaned in a member cluster", c.kind, namespacedName)
|
||||
case err != nil && !errors.IsNotFound(err):
|
||||
c.tl.Fatalf("Error while checking whether %s %q is %s in member clusters: %v", c.kind, namespacedName, stateMsg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) CheckPropagation(obj pkgruntime.Object) {
|
||||
namespacedName := c.adapter.NamespacedName(obj)
|
||||
|
||||
c.tl.Logf("Waiting for %s %q in %d clusters", c.kind, namespacedName, len(c.clusterClients))
|
||||
for _, client := range c.clusterClients {
|
||||
err := c.waitForResource(client, obj)
|
||||
if err != nil {
|
||||
c.tl.Fatalf("Failed to verify %s %q in a member cluster: %v", c.kind, namespacedName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) waitForResource(client clientset.Interface, obj pkgruntime.Object) error {
|
||||
namespacedName := c.adapter.NamespacedName(obj)
|
||||
err := wait.PollImmediate(c.waitInterval, c.clusterWaitTimeout, func() (bool, error) {
|
||||
clusterObj, err := c.adapter.ClusterGet(client, namespacedName)
|
||||
if err == nil && c.adapter.Equivalent(clusterObj, obj) {
|
||||
return true, nil
|
||||
}
|
||||
if errors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *FederatedTypeCRUDTester) updateFedObject(obj pkgruntime.Object) (pkgruntime.Object, error) {
|
||||
err := wait.PollImmediate(c.waitInterval, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
// Target the metadata for simplicity (it's type-agnostic)
|
||||
meta := c.adapter.ObjectMeta(obj)
|
||||
if meta.Annotations == nil {
|
||||
meta.Annotations = make(map[string]string)
|
||||
}
|
||||
meta.Annotations[AnnotationTestFederationCRUDUpdate] = "updated"
|
||||
|
||||
_, err := c.adapter.FedUpdate(obj)
|
||||
if errors.IsConflict(err) {
|
||||
// The resource was updated by the federation controller.
|
||||
// Get the latest version and retry.
|
||||
namespacedName := c.adapter.NamespacedName(obj)
|
||||
obj, err = c.adapter.FedGet(namespacedName)
|
||||
return false, err
|
||||
}
|
||||
// Be tolerant of a slow server
|
||||
if errors.IsServerTimeout(err) {
|
||||
return false, nil
|
||||
}
|
||||
return (err == nil), err
|
||||
})
|
||||
return obj, err
|
||||
}
|
93
federation/pkg/typeadapters/secret.go
Normal file
93
federation/pkg/typeadapters/secret.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package typeadapters
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
)
|
||||
|
||||
type SecretAdapter struct {
|
||||
client federationclientset.Interface
|
||||
}
|
||||
|
||||
func NewSecretAdapter(client federationclientset.Interface) *SecretAdapter {
|
||||
return &SecretAdapter{client: client}
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) SetClient(client federationclientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) Kind() string {
|
||||
return "secret"
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
|
||||
secret1 := obj1.(*apiv1.Secret)
|
||||
secret2 := obj2.(*apiv1.Secret)
|
||||
return util.SecretEquivalent(*secret1, *secret2)
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
|
||||
return &obj.(*apiv1.Secret).ObjectMeta
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) NamespacedName(obj pkgruntime.Object) types.NamespacedName {
|
||||
secret := obj.(*apiv1.Secret)
|
||||
return types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name}
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
|
||||
secret := obj.(*apiv1.Secret)
|
||||
return a.client.CoreV1().Secrets(secret.Namespace).Create(secret)
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) FedGet(namespacedName types.NamespacedName) (pkgruntime.Object, error) {
|
||||
return a.client.CoreV1().Secrets(namespacedName.Namespace).Get(namespacedName.Name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
|
||||
secret := obj.(*apiv1.Secret)
|
||||
return a.client.CoreV1().Secrets(secret.Namespace).Update(secret)
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) FedDelete(namespacedName types.NamespacedName, options *metav1.DeleteOptions) error {
|
||||
return a.client.CoreV1().Secrets(namespacedName.Namespace).Delete(namespacedName.Name, options)
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) ClusterGet(client clientset.Interface, namespacedName types.NamespacedName) (pkgruntime.Object, error) {
|
||||
return client.CoreV1().Secrets(namespacedName.Namespace).Get(namespacedName.Name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func (a *SecretAdapter) NewTestObject(namespace string) pkgruntime.Object {
|
||||
return &apiv1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "test-secret-",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"A": []byte("ala ma kota"),
|
||||
},
|
||||
Type: apiv1.SecretTypeOpaque,
|
||||
}
|
||||
}
|
@@ -9,7 +9,10 @@ load(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["api_test.go"],
|
||||
srcs = [
|
||||
"api_test.go",
|
||||
"crud_test.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
@@ -18,6 +21,7 @@ go_test(
|
||||
"//pkg/apis/batch/v1:go_default_library",
|
||||
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||
"//test/integration/federation/framework:go_default_library",
|
||||
"//vendor:github.com/pborman/uuid",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
|
@@ -46,8 +46,8 @@ type apiTestFunc func(t *testing.T, host string)
|
||||
|
||||
func TestFederationAPI(t *testing.T) {
|
||||
f := &framework.FederationAPIFixture{}
|
||||
f.Setup(t)
|
||||
defer f.Teardown(t)
|
||||
f.SetUp(t)
|
||||
defer f.TearDown(t)
|
||||
|
||||
testCases := map[string]apiTestFunc{
|
||||
"swaggerSpec": testSwaggerSpec,
|
||||
|
68
test/integration/federation/crud_test.go
Normal file
68
test/integration/federation/crud_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package federation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"k8s.io/kubernetes/test/integration/federation/framework"
|
||||
)
|
||||
|
||||
// TestFederationCRUD validates create/read/update/delete operations for federated resource types.
|
||||
func TestFederationCRUD(t *testing.T) {
|
||||
fedFixture := framework.FederationFixture{DesiredClusterCount: 2}
|
||||
fedFixture.SetUp(t)
|
||||
defer fedFixture.TearDown(t)
|
||||
|
||||
controllerFixtures := []framework.ControllerFixture{
|
||||
&framework.SecretFixture{},
|
||||
}
|
||||
for _, fixture := range controllerFixtures {
|
||||
t.Run(fixture.Kind(), func(t *testing.T) {
|
||||
framework.SetUpControllerFixture(t, fedFixture.APIFixture, fixture)
|
||||
defer fixture.TearDown(t)
|
||||
|
||||
adapter := fixture.Adapter()
|
||||
crudtester := framework.NewFederatedTypeCRUDTester(t, adapter, fedFixture.ClusterClients)
|
||||
obj := adapter.NewTestObject(uuid.New())
|
||||
crudtester.CheckLifecycle(obj)
|
||||
})
|
||||
}
|
||||
|
||||
// Validate deletion handling where orphanDependents is true or nil for a single resource type since the
|
||||
// underlying logic is common across all types.
|
||||
orphanedDependents := true
|
||||
testCases := map[string]*bool{
|
||||
"Resources should not be deleted from underlying clusters when OrphanDependents is true": &orphanedDependents,
|
||||
"Resources should not be deleted from underlying clusters when OrphanDependents is nil": nil,
|
||||
}
|
||||
for testName, orphanDependents := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
fixture := &framework.SecretFixture{}
|
||||
framework.SetUpControllerFixture(t, fedFixture.APIFixture, fixture)
|
||||
defer fixture.TearDown(t)
|
||||
|
||||
adapter := fixture.Adapter()
|
||||
crudtester := framework.NewFederatedTypeCRUDTester(t, adapter, fedFixture.ClusterClients)
|
||||
obj := adapter.NewTestObject(uuid.New())
|
||||
updatedObj := crudtester.CheckCreate(obj)
|
||||
crudtester.CheckDelete(updatedObj, orphanDependents)
|
||||
})
|
||||
}
|
||||
}
|
@@ -11,15 +11,29 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"api.go",
|
||||
"controller.go",
|
||||
"crudtester.go",
|
||||
"federation.go",
|
||||
"secret.go",
|
||||
"util.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/client/clientset_generated/federation_clientset:go_default_library",
|
||||
"//federation/cmd/federation-apiserver/app:go_default_library",
|
||||
"//federation/cmd/federation-apiserver/app/options:go_default_library",
|
||||
"//federation/pkg/federation-controller/cluster:go_default_library",
|
||||
"//federation/pkg/federation-controller/secret:go_default_library",
|
||||
"//federation/pkg/typeadapters:go_default_library",
|
||||
"//federation/pkg/typeadapters/crudtester:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//pkg/master:go_default_library",
|
||||
"//test/integration/framework:go_default_library",
|
||||
"//vendor:github.com/pborman/uuid",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/client-go/rest",
|
||||
],
|
||||
)
|
||||
|
||||
|
@@ -20,20 +20,18 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app"
|
||||
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
const (
|
||||
apiNoun = "federation apiserver"
|
||||
waitInterval = 50 * time.Millisecond
|
||||
)
|
||||
const apiNoun = "federation apiserver"
|
||||
|
||||
func getRunOptions() *options.ServerRunOptions {
|
||||
r := options.NewServerRunOptions()
|
||||
@@ -51,11 +49,11 @@ type FederationAPIFixture struct {
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (f *FederationAPIFixture) Setup(t *testing.T) {
|
||||
func (f *FederationAPIFixture) SetUp(t *testing.T) {
|
||||
if f.stopChan != nil {
|
||||
t.Fatal("Setup() already called")
|
||||
t.Fatal("SetUp() already called")
|
||||
}
|
||||
defer TeardownOnPanic(t, f)
|
||||
defer TearDownOnPanic(t, f)
|
||||
|
||||
f.stopChan = make(chan struct{})
|
||||
|
||||
@@ -74,15 +72,25 @@ func (f *FederationAPIFixture) Setup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FederationAPIFixture) Teardown(t *testing.T) {
|
||||
func (f *FederationAPIFixture) TearDown(t *testing.T) {
|
||||
if f.stopChan != nil {
|
||||
close(f.stopChan)
|
||||
f.stopChan = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FederationAPIFixture) NewConfig() *restclient.Config {
|
||||
return &restclient.Config{Host: f.Host}
|
||||
}
|
||||
|
||||
func (f *FederationAPIFixture) NewClient(userAgent string) federationclientset.Interface {
|
||||
config := f.NewConfig()
|
||||
restclient.AddUserAgent(config, userAgent)
|
||||
return federationclientset.NewForConfigOrDie(config)
|
||||
}
|
||||
|
||||
func startServer(t *testing.T, runOptions *options.ServerRunOptions, stopChan <-chan struct{}) error {
|
||||
err := wait.PollImmediate(waitInterval, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
err := wait.PollImmediate(DefaultWaitInterval, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
port, err := framework.FindFreeLocalPort()
|
||||
if err != nil {
|
||||
t.Logf("Error allocating an ephemeral port: %v", err)
|
||||
@@ -105,7 +113,7 @@ func startServer(t *testing.T, runOptions *options.ServerRunOptions, stopChan <-
|
||||
}
|
||||
|
||||
func waitForServer(t *testing.T, host string) error {
|
||||
err := wait.PollImmediate(waitInterval, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
err := wait.PollImmediate(DefaultWaitInterval, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
_, err := http.Get(host)
|
||||
if err != nil {
|
||||
t.Logf("Error when trying to contact the API: %v", err)
|
||||
|
46
test/integration/federation/framework/controller.go
Normal file
46
test/integration/federation/framework/controller.go
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
"k8s.io/kubernetes/federation/pkg/typeadapters"
|
||||
)
|
||||
|
||||
// ControllerFixture defines operations for managing a federation
|
||||
// controller. Tests written to this interface can then target any
|
||||
// controller for which an implementation of this interface exists.
|
||||
type ControllerFixture interface {
|
||||
TestFixture
|
||||
|
||||
SetUp(t *testing.T, testClient federationclientset.Interface, config *restclient.Config)
|
||||
|
||||
Kind() string
|
||||
|
||||
Adapter() typeadapters.FederatedTypeAdapter
|
||||
}
|
||||
|
||||
// SetUpControllerFixture configures the given resource fixture to target the provided api fixture
|
||||
func SetUpControllerFixture(t *testing.T, apiFixture *FederationAPIFixture, controllerFixture ControllerFixture) {
|
||||
client := apiFixture.NewClient(fmt.Sprintf("test-%s", controllerFixture.Kind()))
|
||||
config := apiFixture.NewConfig()
|
||||
controllerFixture.SetUp(t, client, config)
|
||||
}
|
47
test/integration/federation/framework/crudtester.go
Normal file
47
test/integration/federation/framework/crudtester.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package framework
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/federation/pkg/typeadapters"
|
||||
"k8s.io/kubernetes/federation/pkg/typeadapters/crudtester"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
)
|
||||
|
||||
type IntegrationLogger struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (l *IntegrationLogger) Logf(format string, args ...interface{}) {
|
||||
l.t.Logf(format, args...)
|
||||
}
|
||||
|
||||
func (l *IntegrationLogger) Fatalf(format string, args ...interface{}) {
|
||||
l.t.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (l *IntegrationLogger) Fatal(msg string) {
|
||||
l.t.Fatal(msg)
|
||||
}
|
||||
|
||||
func NewFederatedTypeCRUDTester(t *testing.T, adapter typeadapters.FederatedTypeAdapter, clusterClients []clientset.Interface) *crudtester.FederatedTypeCRUDTester {
|
||||
logger := &IntegrationLogger{t}
|
||||
return crudtester.NewFederatedTypeCRUDTester(logger, adapter, clusterClients, DefaultWaitInterval, wait.ForeverTestTimeout)
|
||||
}
|
123
test/integration/federation/framework/federation.go
Normal file
123
test/integration/federation/framework/federation.go
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
"k8s.io/kubernetes/pkg/master"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
type MemberCluster struct {
|
||||
Server *httptest.Server
|
||||
Config *master.Config
|
||||
Client clientset.Interface
|
||||
Host string
|
||||
}
|
||||
|
||||
// FederationFixture manages a federation api server and a set of member clusters
|
||||
type FederationFixture struct {
|
||||
APIFixture *FederationAPIFixture
|
||||
DesiredClusterCount int
|
||||
Clusters []*MemberCluster
|
||||
ClusterClients []clientset.Interface
|
||||
ClusterController *clustercontroller.ClusterController
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (f *FederationFixture) SetUp(t *testing.T) {
|
||||
if f.APIFixture != nil {
|
||||
t.Fatal("Fixture already started")
|
||||
}
|
||||
if f.DesiredClusterCount < 1 {
|
||||
f.DesiredClusterCount = 1
|
||||
}
|
||||
defer TearDownOnPanic(t, f)
|
||||
|
||||
t.Logf("Starting a federation of %d clusters", f.DesiredClusterCount)
|
||||
|
||||
f.APIFixture = &FederationAPIFixture{}
|
||||
f.APIFixture.SetUp(t)
|
||||
|
||||
f.stopChan = make(chan struct{})
|
||||
monitorPeriod := 1 * time.Second
|
||||
clustercontroller.StartClusterController(f.APIFixture.NewConfig(), f.stopChan, monitorPeriod)
|
||||
|
||||
f.startClusters()
|
||||
}
|
||||
|
||||
func (f *FederationFixture) startClusters() {
|
||||
fedClient := f.APIFixture.NewClient("federation-fixture")
|
||||
for i := 0; i < f.DesiredClusterCount; i++ {
|
||||
config := framework.NewMasterConfig()
|
||||
_, server := framework.RunAMaster(config)
|
||||
host := config.GenericConfig.LoopbackClientConfig.Host
|
||||
|
||||
// Use fmt to ensure the output will be visible when run with go test -v
|
||||
fmt.Printf("Federated cluster %d serving on %s", i, host)
|
||||
|
||||
clusterClient := clientset.NewForConfigOrDie(config.GenericConfig.LoopbackClientConfig)
|
||||
f.Clusters = append(f.Clusters, &MemberCluster{
|
||||
Server: server,
|
||||
Config: config,
|
||||
Client: clusterClient,
|
||||
Host: host,
|
||||
})
|
||||
|
||||
f.ClusterClients = append(f.ClusterClients, clusterClient)
|
||||
|
||||
cluster := &federationapi.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("cluster-%d", i),
|
||||
},
|
||||
Spec: federationapi.ClusterSpec{
|
||||
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
|
||||
{
|
||||
ClientCIDR: "0.0.0.0/0",
|
||||
ServerAddress: host,
|
||||
},
|
||||
},
|
||||
// Use insecure access
|
||||
SecretRef: nil,
|
||||
},
|
||||
}
|
||||
fedClient.FederationV1beta1().Clusters().Create(cluster)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FederationFixture) TearDown(t *testing.T) {
|
||||
if f.stopChan != nil {
|
||||
close(f.stopChan)
|
||||
f.stopChan = nil
|
||||
}
|
||||
for _, cluster := range f.Clusters {
|
||||
cluster.Server.Close()
|
||||
}
|
||||
f.Clusters = nil
|
||||
if f.APIFixture != nil {
|
||||
f.APIFixture.TearDown(t)
|
||||
f.APIFixture = nil
|
||||
}
|
||||
}
|
49
test/integration/federation/framework/secret.go
Normal file
49
test/integration/federation/framework/secret.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package framework
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
secretcontroller "k8s.io/kubernetes/federation/pkg/federation-controller/secret"
|
||||
"k8s.io/kubernetes/federation/pkg/typeadapters"
|
||||
)
|
||||
|
||||
type SecretFixture struct {
|
||||
adapter *typeadapters.SecretAdapter
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (f *SecretFixture) SetUp(t *testing.T, client federationclientset.Interface, config *restclient.Config) {
|
||||
f.adapter = typeadapters.NewSecretAdapter(client)
|
||||
f.stopChan = make(chan struct{})
|
||||
secretcontroller.StartSecretController(config, f.stopChan, true)
|
||||
}
|
||||
|
||||
func (f *SecretFixture) TearDown(t *testing.T) {
|
||||
close(f.stopChan)
|
||||
}
|
||||
func (f *SecretFixture) Kind() string {
|
||||
adapter := &typeadapters.SecretAdapter{}
|
||||
return adapter.Kind()
|
||||
}
|
||||
|
||||
func (f *SecretFixture) Adapter() typeadapters.FederatedTypeAdapter {
|
||||
return f.adapter
|
||||
}
|
@@ -18,18 +18,23 @@ package framework
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Setup is likely to be fixture-specific, but Teardown needs to be
|
||||
// consistent to enable TeardownOnPanic.
|
||||
const (
|
||||
DefaultWaitInterval = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
// SetUp is likely to be fixture-specific, but TearDown needs to be
|
||||
// consistent to enable TearDownOnPanic.
|
||||
type TestFixture interface {
|
||||
Teardown(t *testing.T)
|
||||
TearDown(t *testing.T)
|
||||
}
|
||||
|
||||
// TeardownOnPanic can be used to ensure cleanup on setup failure.
|
||||
func TeardownOnPanic(t *testing.T, f TestFixture) {
|
||||
// TearDownOnPanic can be used to ensure cleanup on setup failure.
|
||||
func TearDownOnPanic(t *testing.T, f TestFixture) {
|
||||
if r := recover(); r != nil {
|
||||
f.Teardown(t)
|
||||
f.TearDown(t)
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user