fed: Add integration test for secrets

This commit is contained in:
Maru Newby
2017-03-14 12:10:32 -07:00
parent bc67565070
commit 304030a61c
16 changed files with 820 additions and 20 deletions

View File

@@ -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"],

View 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"],
)

View 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
}

View 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"],
)

View 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
}

View 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,
}
}

View File

@@ -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",

View File

@@ -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,

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

View File

@@ -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",
],
)

View File

@@ -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)

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

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

View 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
}
}

View 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
}

View File

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