Add generated file

This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
xing-yang
2018-07-12 10:55:15 -07:00
parent 36b1de0341
commit e213d1890d
17729 changed files with 5090889 additions and 0 deletions

73
vendor/k8s.io/kubernetes/test/integration/BUILD generated vendored Normal file
View File

@@ -0,0 +1,73 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"utils.go",
],
importpath = "k8s.io/kubernetes/test/integration",
deps = [
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//test/integration/apiserver:all-srcs",
"//test/integration/auth:all-srcs",
"//test/integration/benchmark/jsonify:all-srcs",
"//test/integration/client:all-srcs",
"//test/integration/configmap:all-srcs",
"//test/integration/daemonset:all-srcs",
"//test/integration/defaulttolerationseconds:all-srcs",
"//test/integration/deployment:all-srcs",
"//test/integration/etcd:all-srcs",
"//test/integration/evictions:all-srcs",
"//test/integration/examples:all-srcs",
"//test/integration/framework:all-srcs",
"//test/integration/garbagecollector:all-srcs",
"//test/integration/ipamperf:all-srcs",
"//test/integration/master:all-srcs",
"//test/integration/metrics:all-srcs",
"//test/integration/objectmeta:all-srcs",
"//test/integration/openshift:all-srcs",
"//test/integration/pods:all-srcs",
"//test/integration/quota:all-srcs",
"//test/integration/replicaset:all-srcs",
"//test/integration/replicationcontroller:all-srcs",
"//test/integration/scale:all-srcs",
"//test/integration/scheduler:all-srcs",
"//test/integration/scheduler_perf:all-srcs",
"//test/integration/secrets:all-srcs",
"//test/integration/serviceaccount:all-srcs",
"//test/integration/statefulset:all-srcs",
"//test/integration/storageclasses:all-srcs",
"//test/integration/tls:all-srcs",
"//test/integration/ttlcontroller:all-srcs",
"//test/integration/util:all-srcs",
"//test/integration/volume:all-srcs",
],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,70 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"apiserver_test.go",
"main_test.go",
"patch_test.go",
"print_test.go",
],
tags = [
"etcd",
"integration",
],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/master:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/k8s.io/api/batch/v2alpha1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1alpha1:go_default_library",
"//vendor/k8s.io/api/scheduling/v1beta1:go_default_library",
"//vendor/k8s.io/api/settings/v1alpha1:go_default_library",
"//vendor/k8s.io/api/storage/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/tools/pager:go_default_library",
"//vendor/k8s.io/gengo/examples/set-gen/sets:go_default_library",
],
)
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,323 @@
/*
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 apiserver
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/pager"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/test/integration/framework"
)
func setup(t *testing.T, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
masterConfig := framework.NewIntegrationTestMasterConfig()
if len(groupVersions) > 0 {
resourceConfig := master.DefaultAPIResourceConfigSource()
resourceConfig.EnableVersions(groupVersions...)
masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig
}
_, s, closeFn := framework.RunAMaster(masterConfig)
clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
return s, clientSet, closeFn
}
func verifyStatusCode(t *testing.T, verb, URL, body string, expectedStatusCode int) {
// We dont use the typed Go client to send this request to be able to verify the response status code.
bodyBytes := bytes.NewReader([]byte(body))
req, err := http.NewRequest(verb, URL, bodyBytes)
if err != nil {
t.Fatalf("unexpected error: %v in sending req with verb: %s, URL: %s and body: %s", err, verb, URL, body)
}
transport := http.DefaultTransport
glog.Infof("Sending request: %v", req)
resp, err := transport.RoundTrip(req)
if err != nil {
t.Fatalf("unexpected error: %v in req: %v", err, req)
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != expectedStatusCode {
t.Errorf("Expected status %v, but got %v", expectedStatusCode, resp.StatusCode)
t.Errorf("Body: %v", string(b))
}
}
func path(resource, namespace, name string) string {
return testapi.Extensions.ResourcePath(resource, namespace, name)
}
func newRS(namespace string) *v1beta1.ReplicaSet {
return &v1beta1.ReplicaSet{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicaSet",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
GenerateName: "apiserver-test",
},
Spec: v1beta1.ReplicaSetSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"name": "test"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
},
},
},
},
},
}
}
var cascDel = `
{
"kind": "DeleteOptions",
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
"orphanDependents": false
}
`
// Tests that the apiserver returns 202 status code as expected.
func Test202StatusCode(t *testing.T) {
s, clientSet, closeFn := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("status-code", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rsClient := clientSet.ExtensionsV1beta1().ReplicaSets(ns.Name)
// 1. Create the resource without any finalizer and then delete it without setting DeleteOptions.
// Verify that server returns 200 in this case.
rs, err := rsClient.Create(newRS(ns.Name))
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), "", 200)
// 2. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it without setting DeleteOptions.
// Verify that the apiserver still returns 200 since DeleteOptions.OrphanDependents is not set.
rs = newRS(ns.Name)
rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
rs, err = rsClient.Create(rs)
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), "", 200)
// 3. Create the resource and then delete it with DeleteOptions.OrphanDependents=false.
// Verify that the server still returns 200 since the resource is immediately deleted.
rs = newRS(ns.Name)
rs, err = rsClient.Create(rs)
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), cascDel, 200)
// 4. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it with DeleteOptions.OrphanDependents=false.
// Verify that the server returns 202 in this case.
rs = newRS(ns.Name)
rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
rs, err = rsClient.Create(rs)
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), cascDel, 202)
}
func TestAPIListChunking(t *testing.T) {
if err := utilfeature.DefaultFeatureGate.Set(string(genericfeatures.APIListChunking) + "=true"); err != nil {
t.Fatal(err)
}
s, clientSet, closeFn := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("list-paging", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rsClient := clientSet.ExtensionsV1beta1().ReplicaSets(ns.Name)
for i := 0; i < 4; i++ {
rs := newRS(ns.Name)
rs.Name = fmt.Sprintf("test-%d", i)
if _, err := rsClient.Create(rs); err != nil {
t.Fatal(err)
}
}
calls := 0
firstRV := ""
p := &pager.ListPager{
PageSize: 1,
PageFn: pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
calls++
list, err := rsClient.List(opts)
if err != nil {
return nil, err
}
if calls == 1 {
firstRV = list.ResourceVersion
}
if calls == 2 {
rs := newRS(ns.Name)
rs.Name = "test-5"
if _, err := rsClient.Create(rs); err != nil {
t.Fatal(err)
}
}
return list, err
}),
}
listObj, err := p.List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
if calls != 4 {
t.Errorf("unexpected list invocations: %d", calls)
}
list := listObj.(metav1.ListInterface)
if len(list.GetContinue()) != 0 {
t.Errorf("unexpected continue: %s", list.GetContinue())
}
if list.GetResourceVersion() != firstRV {
t.Errorf("unexpected resource version: %s instead of %s", list.GetResourceVersion(), firstRV)
}
var names []string
if err := meta.EachListItem(listObj, func(obj runtime.Object) error {
rs := obj.(*v1beta1.ReplicaSet)
names = append(names, rs.Name)
return nil
}); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(names, []string{"test-0", "test-1", "test-2", "test-3"}) {
t.Errorf("unexpected items: %#v", list)
}
}
func makeSecret(name string) *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"key": []byte("value"),
},
}
}
func TestNameInFieldSelector(t *testing.T) {
s, clientSet, closeFn := setup(t)
defer closeFn()
numNamespaces := 3
namespaces := make([]*v1.Namespace, 0, numNamespaces)
for i := 0; i < 3; i++ {
ns := framework.CreateTestingNamespace(fmt.Sprintf("ns%d", i), s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
namespaces = append(namespaces, ns)
_, err := clientSet.CoreV1().Secrets(ns.Name).Create(makeSecret("foo"))
if err != nil {
t.Errorf("Couldn't create secret: %v", err)
}
_, err = clientSet.CoreV1().Secrets(ns.Name).Create(makeSecret("bar"))
if err != nil {
t.Errorf("Couldn't create secret: %v", err)
}
}
testcases := []struct {
namespace string
selector string
expectedSecrets int
}{
{
namespace: "",
selector: "metadata.name=foo",
expectedSecrets: numNamespaces,
},
{
namespace: "",
selector: "metadata.name=foo,metadata.name=bar",
expectedSecrets: 0,
},
{
namespace: "",
selector: "metadata.name=foo,metadata.namespace=ns1",
expectedSecrets: 1,
},
{
namespace: "ns1",
selector: "metadata.name=foo,metadata.namespace=ns1",
expectedSecrets: 1,
},
{
namespace: "ns1",
selector: "metadata.name=foo,metadata.namespace=ns2",
expectedSecrets: 0,
},
{
namespace: "ns1",
selector: "metadata.name=foo,metadata.namespace=",
expectedSecrets: 0,
},
}
for _, tc := range testcases {
opts := metav1.ListOptions{
FieldSelector: tc.selector,
}
secrets, err := clientSet.CoreV1().Secrets(tc.namespace).List(opts)
if err != nil {
t.Errorf("%s: Unexpected error: %v", tc.selector, err)
}
if len(secrets.Items) != tc.expectedSecrets {
t.Errorf("%s: Unexpected number of secrets: %d, expected: %d", tc.selector, len(secrets.Items), tc.expectedSecrets)
}
}
}

View File

@@ -0,0 +1,27 @@
/*
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 apiserver
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,138 @@
/*
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 apiserver
import (
"fmt"
"sync"
"sync/atomic"
"testing"
"github.com/pborman/uuid"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/test/integration/framework"
)
// Tests that the apiserver retries patches
func TestPatchConflicts(t *testing.T) {
s, clientSet, closeFn := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("status-code", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
numOfConcurrentPatches := 100
UIDs := make([]types.UID, numOfConcurrentPatches)
ownerRefs := []metav1.OwnerReference{}
for i := 0; i < numOfConcurrentPatches; i++ {
uid := types.UID(uuid.NewRandom().String())
ownerName := fmt.Sprintf("owner-%d", i)
UIDs[i] = uid
ownerRefs = append(ownerRefs, metav1.OwnerReference{
APIVersion: "example.com/v1",
Kind: "Foo",
Name: ownerName,
UID: uid,
})
}
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
OwnerReferences: ownerRefs,
},
}
// Create the object we're going to conflict on
clientSet.CoreV1().Secrets(ns.Name).Create(secret)
client := clientSet.CoreV1().RESTClient()
successes := int32(0)
// Run a lot of simultaneous patch operations to exercise internal API server retry of application of patches that do not specify resourceVersion.
// They should all succeed.
wg := sync.WaitGroup{}
for i := 0; i < numOfConcurrentPatches; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
labelName := fmt.Sprintf("label-%d", i)
value := uuid.NewRandom().String()
obj, err := client.Patch(types.StrategicMergePatchType).
Namespace(ns.Name).
Resource("secrets").
Name("test").
Body([]byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}, "ownerReferences":[{"$patch":"delete","uid":"%s"}]}}`, labelName, value, UIDs[i]))).
Do().
Get()
if errors.IsConflict(err) {
t.Logf("tolerated conflict error patching %s: %v", "secrets", err)
return
}
if err != nil {
t.Errorf("error patching %s: %v", "secrets", err)
return
}
accessor, err := meta.Accessor(obj)
if err != nil {
t.Errorf("error getting object from %s: %v", "secrets", err)
return
}
// make sure the label we wanted was effective
if accessor.GetLabels()[labelName] != value {
t.Errorf("patch of %s was ineffective, expected %s=%s, got labels %#v", "secrets", labelName, value, accessor.GetLabels())
return
}
// make sure the patch directive didn't get lost, and that an entry in the ownerReference list was deleted.
found := findOwnerRefByUID(accessor.GetOwnerReferences(), UIDs[i])
if err != nil {
t.Errorf("%v", err)
return
}
if found {
t.Errorf("patch of %s with $patch directive was ineffective, didn't delete the entry in the ownerReference slice: %#v", "secrets", UIDs[i])
}
atomic.AddInt32(&successes, 1)
}(i)
}
wg.Wait()
if successes < int32(numOfConcurrentPatches) {
t.Errorf("Expected at least %d successful patches for %s, got %d", numOfConcurrentPatches, "secrets", successes)
} else {
t.Logf("Got %d successful patches for %s", successes, "secrets")
}
}
func findOwnerRefByUID(ownerRefs []metav1.OwnerReference, uid types.UID) bool {
for _, of := range ownerRefs {
if of.UID == uid {
return true
}
}
return false
}

View File

@@ -0,0 +1,304 @@
/*
Copyright 2018 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 apiserver
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
"time"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
schedulerapi "k8s.io/api/scheduling/v1beta1"
settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
storagev1alpha1 "k8s.io/api/storage/v1alpha1"
"k8s.io/apimachinery/pkg/api/meta"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/gengo/examples/set-gen/sets"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
"k8s.io/kubernetes/test/integration/framework"
)
var kindWhiteList = sets.NewString(
// k8s.io/api/core
"APIGroup",
"APIVersions",
"Binding",
"DeleteOptions",
"ExportOptions",
"GetOptions",
"ListOptions",
"NodeProxyOptions",
"PodAttachOptions",
"PodExecOptions",
"PodPortForwardOptions",
"PodLogOptions",
"PodProxyOptions",
"PodStatusResult",
"RangeAllocation",
"ServiceProxyOptions",
"SerializedReference",
// --
// k8s.io/api/admission
"AdmissionReview",
// --
// k8s.io/api/admissionregistration
"InitializerConfiguration",
// --
// k8s.io/api/authentication
"TokenRequest",
"TokenReview",
// --
// k8s.io/api/authorization
"LocalSubjectAccessReview",
"SelfSubjectAccessReview",
"SelfSubjectRulesReview",
"SubjectAccessReview",
// --
// k8s.io/api/autoscaling
"Scale",
// --
// k8s.io/api/apps
"DeploymentRollback",
// --
// k8s.io/api/batch
"JobTemplate",
// --
// k8s.io/api/extensions
"ReplicationControllerDummy",
// --
// k8s.io/api/imagepolicy
"ImageReview",
// --
// k8s.io/api/policy
"Eviction",
// --
// k8s.io/kubernetes/pkg/apis/componentconfig
"KubeSchedulerConfiguration",
// --
// k8s.io/apimachinery/pkg/apis/meta
"WatchEvent",
"Status",
// --
)
// TODO (soltysh): this list has to go down to 0!
var missingHanlders = sets.NewString(
"ClusterRole",
"LimitRange",
"MutatingWebhookConfiguration",
"ResourceQuota",
"Role",
"ValidatingWebhookConfiguration",
"VolumeAttachment",
"PriorityClass",
"PodPreset",
)
func TestServerSidePrint(t *testing.T) {
s, _, closeFn := setup(t,
// additional groupversions needed for the test to run
batchv2alpha1.SchemeGroupVersion,
rbacv1alpha1.SchemeGroupVersion,
settingsv1alpha1.SchemeGroupVersion,
schedulerapi.SchemeGroupVersion,
storagev1alpha1.SchemeGroupVersion)
defer closeFn()
ns := framework.CreateTestingNamespace("server-print", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
tableParam := fmt.Sprintf("application/json;as=Table;g=%s;v=%s, application/json", metav1beta1.GroupName, metav1beta1.SchemeGroupVersion.Version)
printer := newFakePrinter(printersinternal.AddHandlers)
configFlags := genericclioptions.NewTestConfigFlags().
WithClientConfig(clientcmd.NewDefaultClientConfig(*createKubeConfig(s.URL), &clientcmd.ConfigOverrides{}))
restConfig, err := configFlags.ToRESTConfig()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
cacheDir, err := ioutil.TempDir(os.TempDir(), "test-integration-apiserver-print")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
defer func() {
os.Remove(cacheDir)
}()
cachedClient, err := discovery.NewCachedDiscoveryClientForConfig(restConfig, cacheDir, "", time.Duration(10*time.Minute))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
configFlags.WithDiscoveryClient(cachedClient)
factory := util.NewFactory(configFlags)
mapper, err := factory.ToRESTMapper()
if err != nil {
t.Errorf("unexpected error getting mapper: %v", err)
return
}
for gvk, apiType := range legacyscheme.Scheme.AllKnownTypes() {
// we do not care about internal objects or lists // TODO make sure this is always true
if gvk.Version == runtime.APIVersionInternal || strings.HasSuffix(apiType.Name(), "List") {
continue
}
if kindWhiteList.Has(gvk.Kind) || missingHanlders.Has(gvk.Kind) {
continue
}
t.Logf("Checking %s", gvk)
// read table definition as returned by the server
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
t.Errorf("unexpected error getting mapping for GVK %s: %v", gvk, err)
continue
}
client, err := factory.ClientForMapping(mapping)
if err != nil {
t.Errorf("unexpected error getting client for GVK %s: %v", gvk, err)
continue
}
req := client.Get()
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
req = req.Namespace(ns.Name)
}
body, err := req.Resource(mapping.Resource.Resource).SetHeader("Accept", tableParam).Do().Raw()
if err != nil {
t.Errorf("unexpected error getting %s: %v", gvk, err)
continue
}
actual, err := decodeIntoTable(body)
if err != nil {
t.Errorf("unexpected error decoding %s: %v", gvk, err)
continue
}
// get table definition used in printers
obj, err := legacyscheme.Scheme.New(gvk)
if err != nil {
t.Errorf("unexpected error creating %s: %v", gvk, err)
continue
}
intGV := gvk.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
intObj, err := legacyscheme.Scheme.ConvertToVersion(obj, intGV)
if err != nil {
t.Errorf("unexpected error converting %s to internal: %v", gvk, err)
continue
}
expectedColumnDefinitions, ok := printer.handlers[reflect.TypeOf(intObj)]
if !ok {
t.Errorf("missing handler for type %v", gvk)
continue
}
for _, e := range expectedColumnDefinitions {
for _, a := range actual.ColumnDefinitions {
if a.Name == e.Name && !reflect.DeepEqual(a, e) {
t.Errorf("unexpected difference in column definition %s for %s:\nexpected:\n%#v\nactual:\n%#v\n", e.Name, gvk, e, a)
}
}
}
}
}
type fakePrinter struct {
handlers map[reflect.Type][]metav1beta1.TableColumnDefinition
}
var _ printers.PrintHandler = &fakePrinter{}
func (f *fakePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
return nil
}
func (f *fakePrinter) TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
printFuncValue := reflect.ValueOf(printFunc)
objType := printFuncValue.Type().In(0)
f.handlers[objType] = columns
return nil
}
func (f *fakePrinter) DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
return nil
}
func newFakePrinter(fns ...func(printers.PrintHandler)) *fakePrinter {
handlers := make(map[reflect.Type][]metav1beta1.TableColumnDefinition, len(fns))
p := &fakePrinter{handlers: handlers}
for _, fn := range fns {
fn(p)
}
return p
}
func decodeIntoTable(body []byte) (*metav1beta1.Table, error) {
table := &metav1beta1.Table{}
err := json.Unmarshal(body, table)
if err != nil {
return nil, err
}
return table, nil
}
func createKubeConfig(url string) *clientcmdapi.Config {
clusterNick := "cluster"
userNick := "user"
contextNick := "context"
config := clientcmdapi.NewConfig()
cluster := clientcmdapi.NewCluster()
cluster.Server = url
cluster.InsecureSkipTLSVerify = true
config.Clusters[clusterNick] = cluster
context := clientcmdapi.NewContext()
context.Cluster = clusterNick
context.AuthInfo = userNick
config.Contexts[contextNick] = context
config.CurrentContext = contextNick
return config
}

101
vendor/k8s.io/kubernetes/test/integration/auth/BUILD generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"accessreview_test.go",
"auth_test.go",
"bootstraptoken_test.go",
"main_test.go",
"node_test.go",
"rbac_test.go",
"svcaccttoken_test.go",
],
tags = ["integration"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/authorization:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/auth/authorizer/abac:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller/serviceaccount:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/authorizer:go_default_library",
"//pkg/master:go_default_library",
"//pkg/registry/rbac/clusterrole:go_default_library",
"//pkg/registry/rbac/clusterrole/storage:go_default_library",
"//pkg/registry/rbac/clusterrolebinding:go_default_library",
"//pkg/registry/rbac/clusterrolebinding/storage:go_default_library",
"//pkg/registry/rbac/role:go_default_library",
"//pkg/registry/rbac/role/storage:go_default_library",
"//pkg/registry/rbac/rolebinding:go_default_library",
"//pkg/registry/rbac/rolebinding/storage:go_default_library",
"//pkg/serviceaccount:go_default_library",
"//plugin/pkg/admission/noderestriction:go_default_library",
"//plugin/pkg/auth/authenticator/token/bootstrap:go_default_library",
"//plugin/pkg/auth/authorizer/rbac:go_default_library",
"//test/e2e/lifecycle/bootstrap:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library",
"//vendor/k8s.io/api/authentication/v1:go_default_library",
"//vendor/k8s.io/api/authentication/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/storage/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/group:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
"//vendor/k8s.io/client-go/transport:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
],
)
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,343 @@
/*
Copyright 2016 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 auth
import (
"errors"
"net/http"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/testapi"
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
api "k8s.io/kubernetes/pkg/apis/core"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/test/integration/framework"
)
// Inject into master an authorizer that uses user info.
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
type sarAuthorizer struct{}
func (sarAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if a.GetUser().GetName() == "dave" {
return authorizer.DecisionNoOpinion, "no", errors.New("I'm sorry, Dave")
}
return authorizer.DecisionAllow, "you're not dave", nil
}
func alwaysAlice(req *http.Request) (user.Info, bool, error) {
return &user.DefaultInfo{
Name: "alice",
}, true, nil
}
func TestSubjectAccessReview(t *testing.T) {
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
masterConfig.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
tests := []struct {
name string
sar *authorizationapi.SubjectAccessReview
expectedError string
expectedStatus authorizationapi.SubjectAccessReviewStatus
}{
{
name: "simple allow",
sar: &authorizationapi.SubjectAccessReview{
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
},
User: "alice",
},
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: true,
Reason: "you're not dave",
},
},
{
name: "simple deny",
sar: &authorizationapi.SubjectAccessReview{
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
},
User: "dave",
},
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: false,
Reason: "no",
EvaluationError: "I'm sorry, Dave",
},
},
{
name: "simple error",
sar: &authorizationapi.SubjectAccessReview{
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
},
},
},
expectedError: "at least one of user or group must be specified",
},
}
for _, test := range tests {
response, err := clientset.Authorization().SubjectAccessReviews().Create(test.sar)
switch {
case err == nil && len(test.expectedError) == 0:
case err != nil && strings.Contains(err.Error(), test.expectedError):
continue
case err != nil && len(test.expectedError) != 0:
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
default:
t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
continue
}
if response.Status != test.expectedStatus {
t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
continue
}
}
}
func TestSelfSubjectAccessReview(t *testing.T) {
username := "alice"
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
return &user.DefaultInfo{Name: username}, true, nil
})
masterConfig.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
tests := []struct {
name string
username string
sar *authorizationapi.SelfSubjectAccessReview
expectedError string
expectedStatus authorizationapi.SubjectAccessReviewStatus
}{
{
name: "simple allow",
username: "alice",
sar: &authorizationapi.SelfSubjectAccessReview{
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
},
},
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: true,
Reason: "you're not dave",
},
},
{
name: "simple deny",
username: "dave",
sar: &authorizationapi.SelfSubjectAccessReview{
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
},
},
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: false,
Reason: "no",
EvaluationError: "I'm sorry, Dave",
},
},
}
for _, test := range tests {
username = test.username
response, err := clientset.Authorization().SelfSubjectAccessReviews().Create(test.sar)
switch {
case err == nil && len(test.expectedError) == 0:
case err != nil && strings.Contains(err.Error(), test.expectedError):
continue
case err != nil && len(test.expectedError) != 0:
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
default:
t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
continue
}
if response.Status != test.expectedStatus {
t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
continue
}
}
}
func TestLocalSubjectAccessReview(t *testing.T) {
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
masterConfig.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
tests := []struct {
name string
namespace string
sar *authorizationapi.LocalSubjectAccessReview
expectedError string
expectedStatus authorizationapi.SubjectAccessReviewStatus
}{
{
name: "simple allow",
namespace: "foo",
sar: &authorizationapi.LocalSubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
Namespace: "foo",
},
User: "alice",
},
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: true,
Reason: "you're not dave",
},
},
{
name: "simple deny",
namespace: "foo",
sar: &authorizationapi.LocalSubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
Namespace: "foo",
},
User: "dave",
},
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: false,
Reason: "no",
EvaluationError: "I'm sorry, Dave",
},
},
{
name: "conflicting namespace",
namespace: "foo",
sar: &authorizationapi.LocalSubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
Namespace: "bar",
},
User: "dave",
},
},
expectedError: "must match metadata.namespace",
},
{
name: "missing namespace",
namespace: "foo",
sar: &authorizationapi.LocalSubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
Spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Verb: "list",
Group: api.GroupName,
Version: "v1",
Resource: "pods",
},
User: "dave",
},
},
expectedError: "must match metadata.namespace",
},
}
for _, test := range tests {
response, err := clientset.Authorization().LocalSubjectAccessReviews(test.namespace).Create(test.sar)
switch {
case err == nil && len(test.expectedError) == 0:
case err != nil && strings.Contains(err.Error(), test.expectedError):
continue
case err != nil && len(test.expectedError) != 0:
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
default:
t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
continue
}
if response.Status != test.expectedStatus {
t.Errorf("%s: expected %#v, got %#v", test.name, test.expectedStatus, response.Status)
continue
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
/*
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 auth
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
bootstraputil "k8s.io/kubernetes/test/e2e/lifecycle/bootstrap"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework"
)
type bootstrapSecrets []*api.Secret
func (b bootstrapSecrets) List(selector labels.Selector) (ret []*api.Secret, err error) {
return b, nil
}
func (b bootstrapSecrets) Get(name string) (*api.Secret, error) {
return b[0], nil
}
// TestBootstrapTokenAuth tests the bootstrap token auth provider
func TestBootstrapTokenAuth(t *testing.T) {
tokenId, err := bootstraputil.GenerateTokenId()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
secret, err := bootstraputil.GenerateTokenSecret()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var bootstrapSecretValid = &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: metav1.NamespaceSystem,
Name: bootstrapapi.BootstrapTokenSecretPrefix,
},
Type: api.SecretTypeBootstrapToken,
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
bootstrapapi.BootstrapTokenSecretKey: []byte(secret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
}
var bootstrapSecretInvalid = &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: metav1.NamespaceSystem,
Name: bootstrapapi.BootstrapTokenSecretPrefix,
},
Type: api.SecretTypeBootstrapToken,
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
}
var expiredBootstrapToken = &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: metav1.NamespaceSystem,
Name: bootstrapapi.BootstrapTokenSecretPrefix,
},
Type: api.SecretTypeBootstrapToken,
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExpirationKey: []byte(bootstraputil.TimeStringFromNow(-time.Hour)),
},
}
type request struct {
verb string
URL string
body string
statusCodes map[int]bool // Set of expected resp.StatusCode if all goes well.
}
tests := []struct {
name string
request request
secret *api.Secret
}{
{
name: "valid token",
request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code200},
secret: bootstrapSecretValid,
},
{
name: "invalid token format",
request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401},
secret: bootstrapSecretInvalid,
},
{
name: "invalid token expired",
request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401},
secret: expiredBootstrapToken,
},
}
for _, test := range tests {
authenticator := bearertoken.New(bootstrap.NewTokenAuthenticator(bootstrapSecrets{test.secret}))
// Set up a master
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authentication.Authenticator = authenticator
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
ns := framework.CreateTestingNamespace("auth-bootstrap-token", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
previousResourceVersion := make(map[string]float64)
transport := http.DefaultTransport
token := tokenId + "." + secret
var bodyStr string
if test.request.body != "" {
sub := ""
if test.request.verb == "PUT" {
// For update operations, insert previous resource version
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(test.request.URL, "")]; resVersion != 0 {
sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
}
sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
}
bodyStr = fmt.Sprintf(test.request.body, sub)
}
test.request.body = bodyStr
bodyBytes := bytes.NewReader([]byte(bodyStr))
req, err := http.NewRequest(test.request.verb, s.URL+test.request.URL, bodyBytes)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
if test.request.verb == "PATCH" {
req.Header.Set("Content-Type", "application/merge-patch+json")
}
func() {
resp, err := transport.RoundTrip(req)
defer resp.Body.Close()
if err != nil {
t.Logf("case %v", test.name)
t.Fatalf("unexpected error: %v", err)
}
b, _ := ioutil.ReadAll(resp.Body)
if _, ok := test.request.statusCodes[resp.StatusCode]; !ok {
t.Logf("case %v", test.name)
t.Errorf("Expected status one of %v, but got %v", test.request.statusCodes, resp.StatusCode)
t.Errorf("Body: %v", string(b))
} else {
if test.request.verb == "POST" {
// For successful create operations, extract resourceVersion
id, currentResourceVersion, err := parseResourceVersion(b)
if err == nil {
key := getPreviousResourceVersionKey(test.request.URL, id)
previousResourceVersion[key] = currentResourceVersion
}
}
}
}()
}
}

View File

@@ -0,0 +1,27 @@
/*
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 auth
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,551 @@
/*
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 auth
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
storagev1beta1 "k8s.io/api/storage/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
"k8s.io/apiserver/pkg/authentication/user"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
versionedinformers "k8s.io/client-go/informers"
externalclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer"
"k8s.io/kubernetes/plugin/pkg/admission/noderestriction"
"k8s.io/kubernetes/test/integration/framework"
)
func TestNodeAuthorizer(t *testing.T) {
// Start the server so we know the address
h := &framework.MasterHolder{Initialized: make(chan struct{})}
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
<-h.Initialized
h.M.GenericAPIServer.Handler.ServeHTTP(w, req)
}))
const (
// Define credentials
tokenMaster = "master-token"
tokenNodeUnknown = "unknown-token"
tokenNode1 = "node1-token"
tokenNode2 = "node2-token"
)
authenticator := bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
tokenMaster: {Name: "admin", Groups: []string{"system:masters"}},
tokenNodeUnknown: {Name: "unknown", Groups: []string{"system:nodes"}},
tokenNode1: {Name: "system:node:node1", Groups: []string{"system:nodes"}},
tokenNode2: {Name: "system:node:node2", Groups: []string{"system:nodes"}},
}))
// Build client config, clientset, and informers
clientConfig := &restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
superuserClient, superuserClientExternal := clientsetForToken(tokenMaster, clientConfig)
informerFactory := informers.NewSharedInformerFactory(superuserClient, time.Minute)
versionedInformerFactory := versionedinformers.NewSharedInformerFactory(superuserClientExternal, time.Minute)
// Enabled CSIPersistentVolume feature at startup so volumeattachments get watched
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, true)()
// Enable DynamicKubeletConfig feature so that Node.Spec.ConfigSource can be set
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicKubeletConfig, true)()
// Set up Node+RBAC authorizer
authorizerConfig := &authorizer.AuthorizationConfig{
AuthorizationModes: []string{"Node", "RBAC"},
InformerFactory: informerFactory,
VersionedInformerFactory: versionedInformerFactory,
}
nodeRBACAuthorizer, _, err := authorizerConfig.New()
if err != nil {
t.Fatal(err)
}
// Set up NodeRestriction admission
nodeRestrictionAdmission := noderestriction.NewPlugin(nodeidentifier.NewDefaultNodeIdentifier())
nodeRestrictionAdmission.SetInternalKubeInformerFactory(informerFactory)
if err := nodeRestrictionAdmission.ValidateInitialization(); err != nil {
t.Fatal(err)
}
// Start the server
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authentication.Authenticator = authenticator
masterConfig.GenericConfig.Authorization.Authorizer = nodeRBACAuthorizer
masterConfig.GenericConfig.AdmissionControl = nodeRestrictionAdmission
_, _, closeFn := framework.RunAMasterUsingServer(masterConfig, apiServer, h)
defer closeFn()
// Start the informers
stopCh := make(chan struct{})
defer close(stopCh)
informerFactory.Start(stopCh)
versionedInformerFactory.Start(stopCh)
// Wait for a healthy server
for {
result := superuserClient.Core().RESTClient().Get().AbsPath("/healthz").Do()
_, err := result.Raw()
if err == nil {
break
}
t.Log(err)
time.Sleep(time.Second)
}
// Create objects
if _, err := superuserClient.Core().Secrets("ns").Create(&api.Secret{ObjectMeta: metav1.ObjectMeta{Name: "mysecret"}}); err != nil {
t.Fatal(err)
}
if _, err := superuserClient.Core().Secrets("ns").Create(&api.Secret{ObjectMeta: metav1.ObjectMeta{Name: "mypvsecret"}}); err != nil {
t.Fatal(err)
}
if _, err := superuserClient.Core().ConfigMaps("ns").Create(&api.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmap"}}); err != nil {
t.Fatal(err)
}
if _, err := superuserClient.Core().ConfigMaps("ns").Create(&api.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmapconfigsource"}}); err != nil {
t.Fatal(err)
}
pvName := "mypv"
if _, err := superuserClientExternal.StorageV1beta1().VolumeAttachments().Create(&storagev1beta1.VolumeAttachment{
ObjectMeta: metav1.ObjectMeta{Name: "myattachment"},
Spec: storagev1beta1.VolumeAttachmentSpec{
Attacher: "foo",
Source: storagev1beta1.VolumeAttachmentSource{PersistentVolumeName: &pvName},
NodeName: "node2",
},
}); err != nil {
t.Fatal(err)
}
if _, err := superuserClient.Core().PersistentVolumeClaims("ns").Create(&api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: "mypvc"},
Spec: api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceStorage: resource.MustParse("1")}},
},
}); err != nil {
t.Fatal(err)
}
if _, err := superuserClient.Core().PersistentVolumes().Create(&api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "mypv"},
Spec: api.PersistentVolumeSpec{
AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
Capacity: api.ResourceList{api.ResourceStorage: resource.MustParse("1")},
ClaimRef: &api.ObjectReference{Namespace: "ns", Name: "mypvc"},
PersistentVolumeSource: api.PersistentVolumeSource{AzureFile: &api.AzureFilePersistentVolumeSource{ShareName: "default", SecretName: "mypvsecret"}},
},
}); err != nil {
t.Fatal(err)
}
getSecret := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().Secrets("ns").Get("mysecret", metav1.GetOptions{})
return err
}
}
getPVSecret := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().Secrets("ns").Get("mypvsecret", metav1.GetOptions{})
return err
}
}
getConfigMap := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().ConfigMaps("ns").Get("myconfigmap", metav1.GetOptions{})
return err
}
}
getConfigMapConfigSource := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().ConfigMaps("ns").Get("myconfigmapconfigsource", metav1.GetOptions{})
return err
}
}
getPVC := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().PersistentVolumeClaims("ns").Get("mypvc", metav1.GetOptions{})
return err
}
}
getPV := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().PersistentVolumes().Get("mypv", metav1.GetOptions{})
return err
}
}
getVolumeAttachment := func(client externalclientset.Interface) func() error {
return func() error {
_, err := client.StorageV1beta1().VolumeAttachments().Get("myattachment", metav1.GetOptions{})
return err
}
}
createNode2NormalPod := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().Pods("ns").Create(&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "node2normalpod"},
Spec: api.PodSpec{
NodeName: "node2",
Containers: []api.Container{{Name: "image", Image: "busybox"}},
Volumes: []api.Volume{
{Name: "secret", VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "mysecret"}}},
{Name: "cm", VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: "myconfigmap"}}}},
{Name: "pvc", VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "mypvc"}}},
},
},
})
return err
}
}
updateNode2NormalPodStatus := func(client clientset.Interface) func() error {
return func() error {
startTime := metav1.NewTime(time.Now())
_, err := client.Core().Pods("ns").UpdateStatus(&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "node2normalpod"},
Status: api.PodStatus{StartTime: &startTime},
})
return err
}
}
deleteNode2NormalPod := func(client clientset.Interface) func() error {
return func() error {
zero := int64(0)
return client.Core().Pods("ns").Delete("node2normalpod", &metav1.DeleteOptions{GracePeriodSeconds: &zero})
}
}
createNode2MirrorPod := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().Pods("ns").Create(&api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node2mirrorpod",
Annotations: map[string]string{api.MirrorPodAnnotationKey: "true"},
},
Spec: api.PodSpec{
NodeName: "node2",
Containers: []api.Container{{Name: "image", Image: "busybox"}},
},
})
return err
}
}
deleteNode2MirrorPod := func(client clientset.Interface) func() error {
return func() error {
zero := int64(0)
return client.Core().Pods("ns").Delete("node2mirrorpod", &metav1.DeleteOptions{GracePeriodSeconds: &zero})
}
}
createNode2 := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().Nodes().Create(&api.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2"}})
return err
}
}
setNode2ConfigSource := func(client clientset.Interface) func() error {
return func() error {
node2, err := client.Core().Nodes().Get("node2", metav1.GetOptions{})
if err != nil {
return err
}
node2.Spec.ConfigSource = &api.NodeConfigSource{
ConfigMap: &api.ConfigMapNodeConfigSource{
Namespace: "ns",
Name: "myconfigmapconfigsource",
KubeletConfigKey: "kubelet",
},
}
_, err = client.Core().Nodes().Update(node2)
return err
}
}
unsetNode2ConfigSource := func(client clientset.Interface) func() error {
return func() error {
node2, err := client.Core().Nodes().Get("node2", metav1.GetOptions{})
if err != nil {
return err
}
node2.Spec.ConfigSource = nil
_, err = client.Core().Nodes().Update(node2)
return err
}
}
updateNode2Status := func(client clientset.Interface) func() error {
return func() error {
_, err := client.Core().Nodes().UpdateStatus(&api.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node2"},
Status: api.NodeStatus{},
})
return err
}
}
deleteNode2 := func(client clientset.Interface) func() error {
return func() error {
return client.Core().Nodes().Delete("node2", nil)
}
}
createNode2NormalPodEviction := func(client clientset.Interface) func() error {
return func() error {
return client.Policy().Evictions("ns").Evict(&policy.Eviction{
TypeMeta: metav1.TypeMeta{
APIVersion: "policy/v1beta1",
Kind: "Eviction",
},
ObjectMeta: metav1.ObjectMeta{
Name: "node2normalpod",
Namespace: "ns",
},
})
}
}
createNode2MirrorPodEviction := func(client clientset.Interface) func() error {
return func() error {
return client.Policy().Evictions("ns").Evict(&policy.Eviction{
TypeMeta: metav1.TypeMeta{
APIVersion: "policy/v1beta1",
Kind: "Eviction",
},
ObjectMeta: metav1.ObjectMeta{
Name: "node2mirrorpod",
Namespace: "ns",
},
})
}
}
capacity := 50
updatePVCCapacity := func(client clientset.Interface) func() error {
return func() error {
capacity++
statusString := fmt.Sprintf("{\"status\": {\"capacity\": {\"storage\": \"%dG\"}}}", capacity)
patchBytes := []byte(statusString)
_, err := client.Core().PersistentVolumeClaims("ns").Patch("mypvc", types.StrategicMergePatchType, patchBytes, "status")
return err
}
}
updatePVCPhase := func(client clientset.Interface) func() error {
return func() error {
patchBytes := []byte(`{"status":{"phase": "Bound"}}`)
_, err := client.Core().PersistentVolumeClaims("ns").Patch("mypvc", types.StrategicMergePatchType, patchBytes, "status")
return err
}
}
nodeanonClient, _ := clientsetForToken(tokenNodeUnknown, clientConfig)
node1Client, node1ClientExternal := clientsetForToken(tokenNode1, clientConfig)
node2Client, node2ClientExternal := clientsetForToken(tokenNode2, clientConfig)
// all node requests from node1 and unknown node fail
expectForbidden(t, getSecret(nodeanonClient))
expectForbidden(t, getPVSecret(nodeanonClient))
expectForbidden(t, getConfigMap(nodeanonClient))
expectForbidden(t, getPVC(nodeanonClient))
expectForbidden(t, getPV(nodeanonClient))
expectForbidden(t, createNode2NormalPod(nodeanonClient))
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
expectForbidden(t, createNode2(nodeanonClient))
expectForbidden(t, updateNode2Status(nodeanonClient))
expectForbidden(t, deleteNode2(nodeanonClient))
expectForbidden(t, getSecret(node1Client))
expectForbidden(t, getPVSecret(node1Client))
expectForbidden(t, getConfigMap(node1Client))
expectForbidden(t, getPVC(node1Client))
expectForbidden(t, getPV(node1Client))
expectForbidden(t, createNode2NormalPod(nodeanonClient))
expectForbidden(t, createNode2MirrorPod(node1Client))
expectNotFound(t, deleteNode2MirrorPod(node1Client))
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
expectForbidden(t, createNode2(node1Client))
expectForbidden(t, updateNode2Status(node1Client))
expectForbidden(t, deleteNode2(node1Client))
// related object requests from node2 fail
expectForbidden(t, getSecret(node2Client))
expectForbidden(t, getPVSecret(node2Client))
expectForbidden(t, getConfigMap(node2Client))
expectForbidden(t, getPVC(node2Client))
expectForbidden(t, getPV(node2Client))
expectForbidden(t, createNode2NormalPod(nodeanonClient))
// mirror pod and self node lifecycle is allowed
expectAllowed(t, createNode2MirrorPod(node2Client))
expectAllowed(t, deleteNode2MirrorPod(node2Client))
expectAllowed(t, createNode2MirrorPod(node2Client))
expectAllowed(t, createNode2MirrorPodEviction(node2Client))
expectAllowed(t, createNode2(node2Client))
expectAllowed(t, updateNode2Status(node2Client))
expectAllowed(t, deleteNode2(node2Client))
// create a pod as an admin to add object references
expectAllowed(t, createNode2NormalPod(superuserClient))
// unidentifiable node and node1 are still forbidden
expectForbidden(t, getSecret(nodeanonClient))
expectForbidden(t, getPVSecret(nodeanonClient))
expectForbidden(t, getConfigMap(nodeanonClient))
expectForbidden(t, getPVC(nodeanonClient))
expectForbidden(t, getPV(nodeanonClient))
expectForbidden(t, createNode2NormalPod(nodeanonClient))
expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient))
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
expectForbidden(t, createNode2NormalPodEviction(nodeanonClient))
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
expectForbidden(t, getSecret(node1Client))
expectForbidden(t, getPVSecret(node1Client))
expectForbidden(t, getConfigMap(node1Client))
expectForbidden(t, getPVC(node1Client))
expectForbidden(t, getPV(node1Client))
expectForbidden(t, createNode2NormalPod(node1Client))
expectForbidden(t, updateNode2NormalPodStatus(node1Client))
expectForbidden(t, deleteNode2NormalPod(node1Client))
expectForbidden(t, createNode2NormalPodEviction(node1Client))
expectForbidden(t, createNode2MirrorPod(node1Client))
expectNotFound(t, deleteNode2MirrorPod(node1Client))
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
// node2 can get referenced objects now
expectAllowed(t, getSecret(node2Client))
expectAllowed(t, getPVSecret(node2Client))
expectAllowed(t, getConfigMap(node2Client))
expectAllowed(t, getPVC(node2Client))
expectAllowed(t, getPV(node2Client))
expectForbidden(t, createNode2NormalPod(node2Client))
expectAllowed(t, updateNode2NormalPodStatus(node2Client))
expectAllowed(t, deleteNode2NormalPod(node2Client))
expectAllowed(t, createNode2MirrorPod(node2Client))
expectAllowed(t, deleteNode2MirrorPod(node2Client))
// recreate as an admin to test eviction
expectAllowed(t, createNode2NormalPod(superuserClient))
expectAllowed(t, createNode2MirrorPod(superuserClient))
expectAllowed(t, createNode2NormalPodEviction(node2Client))
expectAllowed(t, createNode2MirrorPodEviction(node2Client))
// re-create a pod as an admin to add object references
expectAllowed(t, createNode2NormalPod(superuserClient))
// ExpandPersistentVolumes feature disabled
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, false)()
expectForbidden(t, updatePVCCapacity(node1Client))
expectForbidden(t, updatePVCCapacity(node2Client))
// ExpandPersistentVolumes feature enabled
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, true)()
expectForbidden(t, updatePVCCapacity(node1Client))
expectAllowed(t, updatePVCCapacity(node2Client))
expectForbidden(t, updatePVCPhase(node2Client))
// Disabled CSIPersistentVolume feature
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, false)()
expectForbidden(t, getVolumeAttachment(node1ClientExternal))
expectForbidden(t, getVolumeAttachment(node2ClientExternal))
// Enabled CSIPersistentVolume feature
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, true)()
expectForbidden(t, getVolumeAttachment(node1ClientExternal))
expectAllowed(t, getVolumeAttachment(node2ClientExternal))
// create node2 again
expectAllowed(t, createNode2(node2Client))
// node2 can not set its own config source
expectForbidden(t, setNode2ConfigSource(node2Client))
// node2 can not access the configmap config source yet
expectForbidden(t, getConfigMapConfigSource(node2Client))
// superuser can access the configmap config source
expectAllowed(t, getConfigMapConfigSource(superuserClient))
// superuser can set node2's config source
expectAllowed(t, setNode2ConfigSource(superuserClient))
// node2 can now get the configmap assigned as its config source
expectAllowed(t, getConfigMapConfigSource(node2Client))
// superuser can unset node2's config source
expectAllowed(t, unsetNode2ConfigSource(superuserClient))
// node2 can no longer get the configmap after it is unassigned as its config source
expectForbidden(t, getConfigMapConfigSource(node2Client))
// clean up node2
expectAllowed(t, deleteNode2(node2Client))
//TODO(mikedanese): integration test node restriction of TokenRequest
}
// expect executes a function a set number of times until it either returns the
// expected error or executes too many times. It returns if the retries timed
// out and the last error returned by the method.
func expect(t *testing.T, f func() error, wantErr func(error) bool) (timeout bool, lastErr error) {
t.Helper()
err := wait.PollImmediate(time.Second, 30*time.Second, func() (bool, error) {
t.Helper()
lastErr = f()
if wantErr(lastErr) {
return true, nil
}
t.Logf("unexpected response, will retry: %v", lastErr)
return false, nil
})
return err == nil, lastErr
}
func expectForbidden(t *testing.T, f func() error) {
t.Helper()
if ok, err := expect(t, f, errors.IsForbidden); !ok {
t.Errorf("Expected forbidden error, got %v", err)
}
}
func expectNotFound(t *testing.T, f func() error) {
t.Helper()
if ok, err := expect(t, f, errors.IsNotFound); !ok {
t.Errorf("Expected notfound error, got %v", err)
}
}
func expectAllowed(t *testing.T, f func() error) {
t.Helper()
if ok, err := expect(t, f, func(e error) bool { return e == nil }); !ok {
t.Errorf("Expected no error, got %v", err)
}
}

View File

@@ -0,0 +1,564 @@
/*
Copyright 2016 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 auth
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"strings"
"testing"
"time"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
externalclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/transport"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
clusterrolestore "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage"
"k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
clusterrolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage"
"k8s.io/kubernetes/pkg/registry/rbac/role"
rolestore "k8s.io/kubernetes/pkg/registry/rbac/role/storage"
"k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
rolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/storage"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
"k8s.io/kubernetes/test/integration/framework"
)
func clientForToken(user string) *http.Client {
return &http.Client{
Transport: transport.NewBearerAuthRoundTripper(
user,
transport.DebugWrappers(http.DefaultTransport),
),
}
}
func clientsetForToken(user string, config *restclient.Config) (clientset.Interface, externalclientset.Interface) {
configCopy := *config
configCopy.BearerToken = user
return clientset.NewForConfigOrDie(&configCopy), externalclientset.NewForConfigOrDie(&configCopy)
}
type testRESTOptionsGetter struct {
config *master.Config
}
func (getter *testRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
storageConfig, err := getter.config.ExtraConfig.StorageFactory.NewConfig(resource)
if err != nil {
return generic.RESTOptions{}, fmt.Errorf("failed to get storage: %v", err)
}
return generic.RESTOptions{StorageConfig: storageConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: resource.Resource}, nil
}
func newRBACAuthorizer(config *master.Config) authorizer.Authorizer {
optsGetter := &testRESTOptionsGetter{config}
roleRegistry := role.AuthorizerAdapter{Registry: role.NewRegistry(rolestore.NewREST(optsGetter))}
roleBindingRegistry := rolebinding.AuthorizerAdapter{Registry: rolebinding.NewRegistry(rolebindingstore.NewREST(optsGetter))}
clusterRoleRegistry := clusterrole.AuthorizerAdapter{Registry: clusterrole.NewRegistry(clusterrolestore.NewREST(optsGetter))}
clusterRoleBindingRegistry := clusterrolebinding.AuthorizerAdapter{Registry: clusterrolebinding.NewRegistry(clusterrolebindingstore.NewREST(optsGetter))}
return rbac.New(roleRegistry, roleBindingRegistry, clusterRoleRegistry, clusterRoleBindingRegistry)
}
// bootstrapRoles are a set of RBAC roles which will be populated before the test.
type bootstrapRoles struct {
roles []rbacapi.Role
roleBindings []rbacapi.RoleBinding
clusterRoles []rbacapi.ClusterRole
clusterRoleBindings []rbacapi.ClusterRoleBinding
}
// bootstrap uses the provided client to create the bootstrap roles and role bindings.
//
// client should be authenticated as the RBAC super user.
func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
for _, r := range b.clusterRoles {
_, err := client.Rbac().ClusterRoles().Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
for _, r := range b.roles {
_, err := client.Rbac().Roles(r.Namespace).Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
for _, r := range b.clusterRoleBindings {
_, err := client.Rbac().ClusterRoleBindings().Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
for _, r := range b.roleBindings {
_, err := client.Rbac().RoleBindings(r.Namespace).Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
return nil
}
// request is a test case which can.
type request struct {
// The bearer token sent as part of the request
token string
// Resource metadata
verb string
apiGroup string
resource string
namespace string
name string
// The actual resource.
body string
// The expected return status of this request.
expectedStatus int
}
func (r request) String() string {
return fmt.Sprintf("%s %s %s", r.token, r.verb, r.resource)
}
type statusCode int
func (s statusCode) String() string {
return fmt.Sprintf("%d %s", int(s), http.StatusText(int(s)))
}
// Declare a set of raw objects to use.
var (
// Make a role binding with the version enabled in testapi.Rbac
// This assumes testapi is using rbac.authorization.k8s.io/v1beta1 or rbac.authorization.k8s.io/v1, which are identical in structure.
// TODO: rework or remove testapi usage to allow writing integration tests that don't depend on envvars
writeJobsRoleBinding = `
{
"apiVersion": "` + testapi.Rbac.GroupVersion().String() + `",
"kind": "RoleBinding",
"metadata": {
"name": "pi"%s
},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "write-jobs"
},
"subjects": [{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "User",
"name": "admin"
}]
}`
aJob = `
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "pi"%s
},
"spec": {
"template": {
"metadata": {
"name": "a",
"labels": {
"name": "pijob"
}
},
"spec": {
"containers": [
{
"name": "pi",
"image": "perl",
"command": [
"perl",
"-Mbignum=bpi",
"-wle",
"print bpi(2000)"
]
}
],
"restartPolicy": "Never"
}
}
}
}
`
podNamespace = `
{
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
"kind": "Namespace",
"metadata": {
"name": "pod-namespace"%s
}
}
`
jobNamespace = `
{
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
"kind": "Namespace",
"metadata": {
"name": "job-namespace"%s
}
}
`
forbiddenNamespace = `
{
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
"kind": "Namespace",
"metadata": {
"name": "forbidden-namespace"%s
}
}
`
)
// Declare some PolicyRules beforehand.
var (
ruleAllowAll = rbacapi.NewRule("*").Groups("*").Resources("*").RuleOrDie()
ruleReadPods = rbacapi.NewRule("list", "get", "watch").Groups("").Resources("pods").RuleOrDie()
ruleWriteJobs = rbacapi.NewRule("*").Groups("batch").Resources("*").RuleOrDie()
)
func TestRBAC(t *testing.T) {
superUser := "admin/system:masters"
tests := []struct {
bootstrapRoles bootstrapRoles
requests []request
}{
{
bootstrapRoles: bootstrapRoles{
clusterRoles: []rbacapi.ClusterRole{
{
ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
Rules: []rbacapi.PolicyRule{ruleAllowAll},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
Rules: []rbacapi.PolicyRule{ruleReadPods},
},
},
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
{
ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
Subjects: []rbacapi.Subject{
{Kind: "User", Name: "pod-reader"},
},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "read-pods"},
},
},
},
requests: []request{
// Create the namespace used later in the test
{superUser, "POST", "", "namespaces", "", "", podNamespace, http.StatusCreated},
{superUser, "GET", "", "pods", "", "", "", http.StatusOK},
{superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusNotFound},
{superUser, "POST", "", "pods", "pod-namespace", "", aPod, http.StatusCreated},
{superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusOK},
{"bob", "GET", "", "pods", "", "", "", http.StatusForbidden},
{"bob", "GET", "", "pods", "pod-namespace", "a", "", http.StatusForbidden},
{"pod-reader", "GET", "", "pods", "", "", "", http.StatusOK},
{"pod-reader", "POST", "", "pods", "pod-namespace", "", aPod, http.StatusForbidden},
},
},
{
bootstrapRoles: bootstrapRoles{
clusterRoles: []rbacapi.ClusterRole{
{
ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
Rules: []rbacapi.PolicyRule{ruleWriteJobs},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
Rules: []rbacapi.PolicyRule{
rbacapi.NewRule("create").Groups("rbac.authorization.k8s.io").Resources("rolebindings").RuleOrDie(),
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
Rules: []rbacapi.PolicyRule{
rbacapi.NewRule("bind").Groups("rbac.authorization.k8s.io").Resources("clusterroles").RuleOrDie(),
},
},
},
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
{
ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
Subjects: []rbacapi.Subject{
{Kind: "User", Name: "job-writer"},
{Kind: "User", Name: "nonescalating-rolebinding-writer"},
{Kind: "User", Name: "any-rolebinding-writer"},
},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
},
},
roleBindings: []rbacapi.RoleBinding{
{
ObjectMeta: metav1.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer-namespace"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings", Namespace: "job-namespace"},
Subjects: []rbacapi.Subject{
{Kind: "User", Name: "job-writer-namespace"},
{Kind: "User", Name: "any-rolebinding-writer-namespace"},
},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole", Namespace: "job-namespace"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer-namespace"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
},
},
},
requests: []request{
// Create the namespace used later in the test
{superUser, "POST", "", "namespaces", "", "", jobNamespace, http.StatusCreated},
{superUser, "POST", "", "namespaces", "", "", forbiddenNamespace, http.StatusCreated},
{"user-with-no-permissions", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusForbidden},
{"user-with-no-permissions", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusForbidden},
// job-writer-namespace cannot write to the "forbidden-namespace"
{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusForbidden},
{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
{"job-writer-namespace", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusForbidden},
{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
// job-writer can write to any namespace
{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusOK},
{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusNotFound},
{"job-writer", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusCreated},
{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusOK},
{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "", "", http.StatusOK},
{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusNotFound},
{"job-writer-namespace", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusCreated},
{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusOK},
// cannot bind role anywhere
{"user-with-no-permissions", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
// can only bind role in namespace where they have explicit bind permission
{"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
// can only bind role in namespace where they have covering permissions
{"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
{"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
// can bind role in any namespace where they have covering permissions
{"job-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusCreated},
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "pi", "", http.StatusOK},
// cannot bind role because they don't have covering permissions
{"nonescalating-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
// can bind role because they have explicit bind permission
{"any-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
{"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
},
},
}
for i, tc := range tests {
// Create an API Server.
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
superUser: {Name: "admin", Groups: []string{"system:masters"}},
"any-rolebinding-writer": {Name: "any-rolebinding-writer"},
"any-rolebinding-writer-namespace": {Name: "any-rolebinding-writer-namespace"},
"bob": {Name: "bob"},
"job-writer": {Name: "job-writer"},
"job-writer-namespace": {Name: "job-writer-namespace"},
"nonescalating-rolebinding-writer": {Name: "nonescalating-rolebinding-writer"},
"pod-reader": {Name: "pod-reader"},
"user-with-no-permissions": {Name: "user-with-no-permissions"},
}))
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
clientConfig := &restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
// Bootstrap the API Server with the test case's initial roles.
superuserClient, _ := clientsetForToken(superUser, clientConfig)
if err := tc.bootstrapRoles.bootstrap(superuserClient); err != nil {
t.Errorf("case %d: failed to apply initial roles: %v", i, err)
continue
}
previousResourceVersion := make(map[string]float64)
for j, r := range tc.requests {
testGroup, ok := testapi.Groups[r.apiGroup]
if !ok {
t.Errorf("case %d %d: unknown api group %q, %s", i, j, r.apiGroup, r)
continue
}
path := testGroup.ResourcePath(r.resource, r.namespace, r.name)
var body io.Reader
if r.body != "" {
sub := ""
if r.verb == "PUT" {
// For update operations, insert previous resource version
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(path, "")]; resVersion != 0 {
sub += fmt.Sprintf(",\"resourceVersion\": \"%v\"", resVersion)
}
}
body = strings.NewReader(fmt.Sprintf(r.body, sub))
}
req, err := http.NewRequest(r.verb, s.URL+path, body)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
func() {
reqDump, err := httputil.DumpRequest(req, true)
if err != nil {
t.Fatalf("failed to dump request: %v", err)
return
}
resp, err := clientForToken(r.token).Do(req)
if err != nil {
t.Errorf("case %d, req %d: failed to make request: %v", i, j, err)
return
}
defer resp.Body.Close()
respDump, err := httputil.DumpResponse(resp, true)
if err != nil {
t.Fatalf("failed to dump response: %v", err)
return
}
if resp.StatusCode != r.expectedStatus {
// When debugging is on, dump the entire request and response. Very helpful for
// debugging malformed test cases.
//
// To turn on debugging, use the '-args' flag.
//
// go test -v -tags integration -run RBAC -args -v 10
//
glog.V(8).Infof("case %d, req %d: %s\n%s\n", i, j, reqDump, respDump)
t.Errorf("case %d, req %d: %s expected %q got %q", i, j, r, statusCode(r.expectedStatus), statusCode(resp.StatusCode))
}
b, _ := ioutil.ReadAll(resp.Body)
if r.verb == "POST" && (resp.StatusCode/100) == 2 {
// For successful create operations, extract resourceVersion
id, currentResourceVersion, err := parseResourceVersion(b)
if err == nil {
key := getPreviousResourceVersionKey(path, id)
previousResourceVersion[key] = currentResourceVersion
} else {
t.Logf("error in trying to extract resource version: %s", err)
}
}
}()
}
}
}
func TestBootstrapping(t *testing.T) {
superUser := "admin/system:masters"
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
superUser: {Name: "admin", Groups: []string{"system:masters"}},
}))
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
clientset := clientset.NewForConfigOrDie(&restclient.Config{BearerToken: superUser, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
watcher, err := clientset.Rbac().ClusterRoles().Watch(metav1.ListOptions{ResourceVersion: "0"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = watch.Until(30*time.Second, watcher, func(event watch.Event) (bool, error) {
if event.Type != watch.Added {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
clusterRoles, err := clientset.Rbac().ClusterRoles().List(metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(clusterRoles.Items) == 0 {
t.Fatalf("missing cluster roles")
}
for _, clusterRole := range clusterRoles.Items {
if clusterRole.Name == "cluster-admin" {
return
}
}
t.Errorf("missing cluster-admin: %v", clusterRoles)
healthBytes, err := clientset.Discovery().RESTClient().Get().AbsPath("/healthz/poststarthook/rbac/bootstrap-roles").DoRaw()
if err != nil {
t.Error(err)
}
t.Errorf("error bootstrapping roles: %s", string(healthBytes))
}

View File

@@ -0,0 +1,558 @@
/*
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 auth
import (
"crypto/ecdsa"
"encoding/base64"
"encoding/json"
"strings"
"testing"
"time"
"gopkg.in/square/go-jose.v2/jwt"
authenticationv1 "k8s.io/api/authentication/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
clientset "k8s.io/client-go/kubernetes"
externalclientset "k8s.io/client-go/kubernetes"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/apis/core"
serviceaccountgetter "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/test/integration/framework"
)
const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49
AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0
/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
-----END EC PRIVATE KEY-----`
func TestServiceAccountTokenCreate(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TokenRequest, true)()
// Build client config, clientset, and informers
sk, err := certutil.ParsePrivateKeyPEM([]byte(ecdsaPrivateKey))
if err != nil {
t.Fatalf("err: %v", err)
}
pk := sk.(*ecdsa.PrivateKey).PublicKey
const iss = "https://foo.bar.example.com"
aud := []string{"api"}
gcs := &clientset.Clientset{}
// Start the server
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(
serviceaccount.JWTTokenAuthenticator(
iss,
[]interface{}{&pk},
serviceaccount.NewValidator(aud, serviceaccountgetter.NewGetterFromClient(gcs)),
),
)
masterConfig.ExtraConfig.ServiceAccountIssuer = serviceaccount.JWTTokenGenerator(iss, sk)
masterConfig.ExtraConfig.ServiceAccountAPIAudiences = aud
master, _, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
cs, err := clientset.NewForConfig(master.GenericAPIServer.LoopbackClientConfig)
if err != nil {
t.Fatalf("err: %v", err)
}
*gcs = *cs
var (
sa = &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "test-svcacct",
Namespace: "myns",
},
}
pod = &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: sa.Namespace,
},
Spec: v1.PodSpec{
ServiceAccountName: sa.Name,
Containers: []v1.Container{{Name: "test-container", Image: "nginx"}},
},
}
otherpod = &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "other-test-pod",
Namespace: sa.Namespace,
},
Spec: v1.PodSpec{
ServiceAccountName: "other-" + sa.Name,
Containers: []v1.Container{{Name: "test-container", Image: "nginx"}},
},
}
secret = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Namespace: sa.Namespace,
},
}
wrongUID = types.UID("wrong")
noUID = types.UID("")
)
t.Run("bound to service account", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
},
}
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
}
sa, delSvcAcct := createDeleteSvcAcct(t, cs, sa)
defer delSvcAcct()
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
if err != nil {
t.Fatalf("err: %v", err)
}
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "pod")
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
doTokenReview(t, cs, treq, false)
delSvcAcct()
doTokenReview(t, cs, treq, true)
})
t.Run("bound to service account and pod", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Pod",
APIVersion: "v1",
Name: pod.Name,
},
},
}
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token bound to nonexistant pod but got: %#v", resp)
}
pod, delPod := createDeletePod(t, cs, pod)
defer delPod()
// right uid
treq.Spec.BoundObjectRef.UID = pod.UID
if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
t.Fatalf("err: %v", err)
}
// wrong uid
treq.Spec.BoundObjectRef.UID = wrongUID
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token bound to pod with wrong uid but got: %#v", resp)
}
// no uid
treq.Spec.BoundObjectRef.UID = noUID
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
if err != nil {
t.Fatalf("err: %v", err)
}
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name")
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
doTokenReview(t, cs, treq, false)
delPod()
doTokenReview(t, cs, treq, true)
})
t.Run("bound to service account and secret", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Secret",
APIVersion: "v1",
Name: secret.Name,
UID: secret.UID,
},
},
}
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token bound to nonexistant secret but got: %#v", resp)
}
secret, delSecret := createDeleteSecret(t, cs, secret)
defer delSecret()
// right uid
treq.Spec.BoundObjectRef.UID = secret.UID
if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
t.Fatalf("err: %v", err)
}
// wrong uid
treq.Spec.BoundObjectRef.UID = wrongUID
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err creating token bound to secret with wrong uid but got: %#v", resp)
}
// no uid
treq.Spec.BoundObjectRef.UID = noUID
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
if err != nil {
t.Fatalf("err: %v", err)
}
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
doTokenReview(t, cs, treq, false)
delSecret()
doTokenReview(t, cs, treq, true)
})
t.Run("bound to service account and pod running as different service account", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Pod",
APIVersion: "v1",
Name: otherpod.Name,
},
},
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
_, del = createDeletePod(t, cs, otherpod)
defer del()
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
t.Fatalf("expected err but got: %#v", resp)
}
})
t.Run("expired token", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
},
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
if err != nil {
t.Fatalf("err: %v", err)
}
doTokenReview(t, cs, treq, false)
// backdate the token
then := time.Now().Add(-2 * time.Hour)
sc := &jwt.Claims{
Subject: apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name),
Audience: jwt.Audience([]string{"api"}),
IssuedAt: jwt.NewNumericDate(then),
NotBefore: jwt.NewNumericDate(then),
Expiry: jwt.NewNumericDate(then.Add(time.Duration(60*60) * time.Second)),
}
coresa := core.ServiceAccount{
ObjectMeta: sa.ObjectMeta,
}
_, pc := serviceaccount.Claims(coresa, nil, nil, 0, nil)
tok, err := masterConfig.ExtraConfig.ServiceAccountIssuer.GenerateToken(sc, pc)
if err != nil {
t.Fatalf("err signing expired token: %v", err)
}
treq.Status.Token = tok
doTokenReview(t, cs, treq, true)
})
t.Run("a token without an api audience is invalid", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"not-the-api"},
},
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
if err != nil {
t.Fatalf("err: %v", err)
}
doTokenReview(t, cs, treq, true)
})
t.Run("a tokenrequest without an audience is valid against the api", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{},
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
if err != nil {
t.Fatalf("err: %v", err)
}
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
doTokenReview(t, cs, treq, false)
})
t.Run("a token should be invalid after recreating same name pod", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Pod",
APIVersion: "v1",
Name: pod.Name,
},
},
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
originalPod, originalDelPod := createDeletePod(t, cs, pod)
defer originalDelPod()
treq.Spec.BoundObjectRef.UID = originalPod.UID
if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
t.Fatalf("err: %v", err)
}
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name")
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
doTokenReview(t, cs, treq, false)
originalDelPod()
doTokenReview(t, cs, treq, true)
_, recreateDelPod := createDeletePod(t, cs, pod)
defer recreateDelPod()
doTokenReview(t, cs, treq, true)
})
t.Run("a token should be invalid after recreating same name secret", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Secret",
APIVersion: "v1",
Name: secret.Name,
UID: secret.UID,
},
},
}
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret)
defer originalDelSecret()
treq.Spec.BoundObjectRef.UID = originalSecret.UID
if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
t.Fatalf("err: %v", err)
}
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
doTokenReview(t, cs, treq, false)
originalDelSecret()
doTokenReview(t, cs, treq, true)
_, recreateDelSecret := createDeleteSecret(t, cs, secret)
defer recreateDelSecret()
doTokenReview(t, cs, treq, true)
})
}
func doTokenReview(t *testing.T, cs externalclientset.Interface, treq *authenticationv1.TokenRequest, expectErr bool) {
t.Helper()
trev, err := cs.AuthenticationV1().TokenReviews().Create(&authenticationv1.TokenReview{
Spec: authenticationv1.TokenReviewSpec{
Token: treq.Status.Token,
},
})
if err != nil {
t.Fatalf("err: %v", err)
}
t.Logf("status: %+v", trev.Status)
if (trev.Status.Error != "") && !expectErr {
t.Fatalf("expected no error but got: %v", trev.Status.Error)
}
if (trev.Status.Error == "") && expectErr {
t.Fatalf("expected error but got: %+v", trev.Status)
}
if !trev.Status.Authenticated && !expectErr {
t.Fatal("expected token to be authenticated but it wasn't")
}
}
func checkPayload(t *testing.T, tok string, want string, parts ...string) {
t.Helper()
got := getSubObject(t, getPayload(t, tok), parts...)
if got != want {
t.Errorf("unexpected payload.\nsaw:\t%v\nwant:\t%v", got, want)
}
}
func getSubObject(t *testing.T, b string, parts ...string) string {
t.Helper()
var obj interface{}
obj = make(map[string]interface{})
if err := json.Unmarshal([]byte(b), &obj); err != nil {
t.Fatalf("err: %v", err)
}
for _, part := range parts {
obj = obj.(map[string]interface{})[part]
}
out, err := json.Marshal(obj)
if err != nil {
t.Fatalf("err: %v", err)
}
return string(out)
}
func getPayload(t *testing.T, b string) string {
t.Helper()
parts := strings.Split(b, ".")
if len(parts) != 3 {
t.Fatalf("token did not have three parts: %v", b)
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
t.Fatalf("failed to base64 decode token: %v", err)
}
return string(payload)
}
func createDeleteSvcAcct(t *testing.T, cs clientset.Interface, sa *v1.ServiceAccount) (*v1.ServiceAccount, func()) {
t.Helper()
sa, err := cs.CoreV1().ServiceAccounts(sa.Namespace).Create(sa)
if err != nil {
t.Fatalf("err: %v", err)
}
done := false
return sa, func() {
t.Helper()
if done {
return
}
done = true
if err := cs.CoreV1().ServiceAccounts(sa.Namespace).Delete(sa.Name, nil); err != nil {
t.Fatalf("err: %v", err)
}
}
}
func createDeletePod(t *testing.T, cs clientset.Interface, pod *v1.Pod) (*v1.Pod, func()) {
t.Helper()
pod, err := cs.CoreV1().Pods(pod.Namespace).Create(pod)
if err != nil {
t.Fatalf("err: %v", err)
}
done := false
return pod, func() {
t.Helper()
if done {
return
}
done = true
if err := cs.CoreV1().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil {
t.Fatalf("err: %v", err)
}
}
}
func createDeleteSecret(t *testing.T, cs clientset.Interface, sec *v1.Secret) (*v1.Secret, func()) {
t.Helper()
sec, err := cs.CoreV1().Secrets(sec.Namespace).Create(sec)
if err != nil {
t.Fatalf("err: %v", err)
}
done := false
return sec, func() {
t.Helper()
if done {
return
}
done = true
if err := cs.CoreV1().Secrets(sec.Namespace).Delete(sec.Name, nil); err != nil {
t.Fatalf("err: %v", err)
}
}
}

View File

@@ -0,0 +1,25 @@
{
"kind": "ReplicationController",
"apiVersion": "v1",
"metadata": {
"name": "test-controller",
"namespace": "test",
"labels": {"name": "test-controller"}
},
"spec": {
"replicas": 0,
"selector": {"name": "test-pod"},
"template": {
"metadata": {
"namespace": "test",
"labels": {"name": "test-pod"}
},
"spec": {
"containers": [{
"name": "test-container",
"image": "k8s.gcr.io/pause:3.1"
}]
}
}
}
}

View File

@@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "k8s.io/kubernetes/test/integration/benchmark/jsonify",
visibility = ["//visibility:private"],
deps = [
"//test/e2e/perftype:go_default_library",
"//vendor/golang.org/x/tools/benchmark/parse:go_default_library",
],
)
go_binary(
name = "jsonify",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,77 @@
/*
Copyright 2018 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 main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
benchparse "golang.org/x/tools/benchmark/parse"
"k8s.io/kubernetes/test/e2e/perftype"
)
func main() {
err := run()
if err != nil {
panic(err)
}
}
func run() error {
if len(os.Args) < 2 {
return fmt.Errorf("output filename is a required argument")
}
benchmarkSet, err := benchparse.ParseSet(os.Stdin)
if err != nil {
return err
}
data := perftype.PerfData{Version: "v1"}
for _, benchMarks := range benchmarkSet {
for _, benchMark := range benchMarks {
data.DataItems = appendIfMeasured(data.DataItems, benchMark, benchparse.NsPerOp, "time", "μs", benchMark.NsPerOp/1000.0)
data.DataItems = appendIfMeasured(data.DataItems, benchMark, benchparse.MBPerS, "throughput", "MBps", benchMark.MBPerS)
data.DataItems = appendIfMeasured(data.DataItems, benchMark, benchparse.AllocedBytesPerOp, "allocated", "bytes", float64(benchMark.AllocedBytesPerOp))
data.DataItems = appendIfMeasured(data.DataItems, benchMark, benchparse.AllocsPerOp, "allocations", "1", float64(benchMark.AllocsPerOp))
data.DataItems = appendIfMeasured(data.DataItems, benchMark, 0, "iterations", "1", float64(benchMark.N))
}
}
output := &bytes.Buffer{}
if err := json.NewEncoder(output).Encode(data); err != nil {
return err
}
formatted := &bytes.Buffer{}
if err := json.Indent(formatted, output.Bytes(), "", " "); err != nil {
return err
}
return ioutil.WriteFile(os.Args[1], formatted.Bytes(), 0664)
}
func appendIfMeasured(items []perftype.DataItem, benchmark *benchparse.Benchmark, metricType int, metricName string, unit string, value float64) []perftype.DataItem {
if metricType != 0 && (benchmark.Measured&metricType) == 0 {
return items
}
return append(items, perftype.DataItem{
Unit: unit,
Labels: map[string]string{
"benchmark": benchmark.Name,
"metricName": metricName},
Data: map[string]float64{
"value": value}})
}

51
vendor/k8s.io/kubernetes/test/integration/client/BUILD generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"client_test.go",
"dynamic_client_test.go",
"main_test.go",
],
tags = ["integration"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/version:go_default_library",
"//test/integration/framework:go_default_library",
"//test/utils/image:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,818 @@
/*
Copyright 2014 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 client
import (
"fmt"
"log"
"reflect"
rt "runtime"
"sync"
"testing"
"time"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/test/integration/framework"
imageutils "k8s.io/kubernetes/test/utils/image"
)
func TestClient(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := framework.CreateTestingNamespace("client", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
info, err := client.Discovery().ServerVersion()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if e, a := version.Get(), *info; !reflect.DeepEqual(e, a) {
t.Errorf("expected %#v, got %#v", e, a)
}
pods, err := client.Core().Pods(ns.Name).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(pods.Items) != 0 {
t.Errorf("expected no pods, got %#v", pods)
}
// get a validation error
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test",
Namespace: ns.Name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test",
},
},
},
}
got, err := client.Core().Pods(ns.Name).Create(pod)
if err == nil {
t.Fatalf("unexpected non-error: %v", got)
}
// get a created pod
pod.Spec.Containers[0].Image = "an-image"
got, err = client.Core().Pods(ns.Name).Create(pod)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.Name == "" {
t.Errorf("unexpected empty pod Name %v", got)
}
// pod is shown, but not scheduled
pods, err = client.Core().Pods(ns.Name).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(pods.Items) != 1 {
t.Errorf("expected one pod, got %#v", pods)
}
actual := pods.Items[0]
if actual.Name != got.Name {
t.Errorf("expected pod %#v, got %#v", got, actual)
}
if actual.Spec.NodeName != "" {
t.Errorf("expected pod to be unscheduled, got %#v", actual)
}
}
func TestAtomicPut(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
c := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := framework.CreateTestingNamespace("atomic-put", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rcBody := v1.ReplicationController{
TypeMeta: metav1.TypeMeta{
APIVersion: c.Core().RESTClient().APIVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "atomicrc",
Namespace: ns.Name,
Labels: map[string]string{
"name": "atomicrc",
},
},
Spec: v1.ReplicationControllerSpec{
Replicas: func(i int32) *int32 { return &i }(0),
Selector: map[string]string{
"foo": "bar",
},
Template: &v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "name", Image: "image"},
},
},
},
},
}
rcs := c.Core().ReplicationControllers(ns.Name)
rc, err := rcs.Create(&rcBody)
if err != nil {
t.Fatalf("Failed creating atomicRC: %v", err)
}
testLabels := labels.Set{
"foo": "bar",
}
for i := 0; i < 5; i++ {
// a: z, b: y, etc...
testLabels[string([]byte{byte('a' + i)})] = string([]byte{byte('z' - i)})
}
var wg sync.WaitGroup
wg.Add(len(testLabels))
for label, value := range testLabels {
go func(l, v string) {
defer wg.Done()
for {
tmpRC, err := rcs.Get(rc.Name, metav1.GetOptions{})
if err != nil {
t.Errorf("Error getting atomicRC: %v", err)
continue
}
if tmpRC.Spec.Selector == nil {
tmpRC.Spec.Selector = map[string]string{l: v}
tmpRC.Spec.Template.Labels = map[string]string{l: v}
} else {
tmpRC.Spec.Selector[l] = v
tmpRC.Spec.Template.Labels[l] = v
}
tmpRC, err = rcs.Update(tmpRC)
if err != nil {
if apierrors.IsConflict(err) {
// This is what we expect.
continue
}
t.Errorf("Unexpected error putting atomicRC: %v", err)
continue
}
return
}
}(label, value)
}
wg.Wait()
rc, err = rcs.Get(rc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed getting atomicRC after writers are complete: %v", err)
}
if !reflect.DeepEqual(testLabels, labels.Set(rc.Spec.Selector)) {
t.Errorf("Selector PUTs were not atomic: wanted %v, got %v", testLabels, rc.Spec.Selector)
}
}
func TestPatch(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
c := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := framework.CreateTestingNamespace("patch", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
name := "patchpod"
resource := "pods"
podBody := v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: c.Core().RESTClient().APIVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns.Name,
Labels: map[string]string{},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "name", Image: "image"},
},
},
}
pods := c.Core().Pods(ns.Name)
pod, err := pods.Create(&podBody)
if err != nil {
t.Fatalf("Failed creating patchpods: %v", err)
}
patchBodies := map[schema.GroupVersion]map[types.PatchType]struct {
AddLabelBody []byte
RemoveLabelBody []byte
RemoveAllLabelsBody []byte
}{
v1.SchemeGroupVersion: {
types.JSONPatchType: {
[]byte(`[{"op":"add","path":"/metadata/labels","value":{"foo":"bar","baz":"qux"}}]`),
[]byte(`[{"op":"remove","path":"/metadata/labels/foo"}]`),
[]byte(`[{"op":"remove","path":"/metadata/labels"}]`),
},
types.MergePatchType: {
[]byte(`{"metadata":{"labels":{"foo":"bar","baz":"qux"}}}`),
[]byte(`{"metadata":{"labels":{"foo":null}}}`),
[]byte(`{"metadata":{"labels":null}}`),
},
types.StrategicMergePatchType: {
[]byte(`{"metadata":{"labels":{"foo":"bar","baz":"qux"}}}`),
[]byte(`{"metadata":{"labels":{"foo":null}}}`),
[]byte(`{"metadata":{"labels":{"$patch":"replace"}}}`),
},
},
}
pb := patchBodies[c.Core().RESTClient().APIVersion()]
execPatch := func(pt types.PatchType, body []byte) error {
result := c.Core().RESTClient().Patch(pt).
Resource(resource).
Namespace(ns.Name).
Name(name).
Body(body).
Do()
if result.Error() != nil {
return result.Error()
}
// trying to chase flakes, this should give us resource versions of objects as we step through
jsonObj, err := result.Raw()
if err != nil {
t.Log(err)
} else {
t.Logf("%v", string(jsonObj))
}
return nil
}
for k, v := range pb {
// add label
err := execPatch(k, v.AddLabelBody)
if err != nil {
t.Fatalf("Failed updating patchpod with patch type %s: %v", k, err)
}
pod, err = pods.Get(name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed getting patchpod: %v", err)
}
if len(pod.Labels) != 2 || pod.Labels["foo"] != "bar" || pod.Labels["baz"] != "qux" {
t.Errorf("Failed updating patchpod with patch type %s: labels are: %v", k, pod.Labels)
}
// remove one label
err = execPatch(k, v.RemoveLabelBody)
if err != nil {
t.Fatalf("Failed updating patchpod with patch type %s: %v", k, err)
}
pod, err = pods.Get(name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed getting patchpod: %v", err)
}
if len(pod.Labels) != 1 || pod.Labels["baz"] != "qux" {
t.Errorf("Failed updating patchpod with patch type %s: labels are: %v", k, pod.Labels)
}
// remove all labels
err = execPatch(k, v.RemoveAllLabelsBody)
if err != nil {
t.Fatalf("Failed updating patchpod with patch type %s: %v", k, err)
}
pod, err = pods.Get(name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed getting patchpod: %v", err)
}
if pod.Labels != nil {
t.Errorf("Failed remove all labels from patchpod with patch type %s: %v", k, pod.Labels)
}
}
}
func TestPatchWithCreateOnUpdate(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
c := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := framework.CreateTestingNamespace("patch-with-create", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
endpointTemplate := &v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "patchendpoint",
Namespace: ns.Name,
},
Subsets: []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 80, Protocol: v1.ProtocolTCP}},
},
},
}
patchEndpoint := func(json []byte) (runtime.Object, error) {
return c.Core().RESTClient().Patch(types.MergePatchType).Resource("endpoints").Namespace(ns.Name).Name("patchendpoint").Body(json).Do().Get()
}
// Make sure patch doesn't get to CreateOnUpdate
{
endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
if err != nil {
t.Fatalf("Failed creating endpoint JSON: %v", err)
}
if obj, err := patchEndpoint(endpointJSON); !apierrors.IsNotFound(err) {
t.Errorf("Expected notfound creating from patch, got error=%v and object: %#v", err, obj)
}
}
// Create the endpoint (endpoints set AllowCreateOnUpdate=true) to get a UID and resource version
createdEndpoint, err := c.Core().Endpoints(ns.Name).Update(endpointTemplate)
if err != nil {
t.Fatalf("Failed creating endpoint: %v", err)
}
// Make sure identity patch is accepted
{
endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), createdEndpoint)
if err != nil {
t.Fatalf("Failed creating endpoint JSON: %v", err)
}
if _, err := patchEndpoint(endpointJSON); err != nil {
t.Errorf("Failed patching endpoint: %v", err)
}
}
// Make sure patch complains about a mismatched resourceVersion
{
endpointTemplate.Name = ""
endpointTemplate.UID = ""
endpointTemplate.ResourceVersion = "1"
endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
if err != nil {
t.Fatalf("Failed creating endpoint JSON: %v", err)
}
if _, err := patchEndpoint(endpointJSON); !apierrors.IsConflict(err) {
t.Errorf("Expected error, got %#v", err)
}
}
// Make sure patch complains about mutating the UID
{
endpointTemplate.Name = ""
endpointTemplate.UID = "abc"
endpointTemplate.ResourceVersion = ""
endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
if err != nil {
t.Fatalf("Failed creating endpoint JSON: %v", err)
}
if _, err := patchEndpoint(endpointJSON); !apierrors.IsInvalid(err) {
t.Errorf("Expected error, got %#v", err)
}
}
// Make sure patch complains about a mismatched name
{
endpointTemplate.Name = "changedname"
endpointTemplate.UID = ""
endpointTemplate.ResourceVersion = ""
endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
if err != nil {
t.Fatalf("Failed creating endpoint JSON: %v", err)
}
if _, err := patchEndpoint(endpointJSON); !apierrors.IsBadRequest(err) {
t.Errorf("Expected error, got %#v", err)
}
}
// Make sure patch containing originally submitted JSON is accepted
{
endpointTemplate.Name = ""
endpointTemplate.UID = ""
endpointTemplate.ResourceVersion = ""
endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
if err != nil {
t.Fatalf("Failed creating endpoint JSON: %v", err)
}
if _, err := patchEndpoint(endpointJSON); err != nil {
t.Errorf("Failed patching endpoint: %v", err)
}
}
}
func TestAPIVersions(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
c := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
clientVersion := c.Core().RESTClient().APIVersion().String()
g, err := c.Discovery().ServerGroups()
if err != nil {
t.Fatalf("Failed to get api versions: %v", err)
}
versions := metav1.ExtractGroupVersions(g)
// Verify that the server supports the API version used by the client.
for _, version := range versions {
if version == clientVersion {
return
}
}
t.Errorf("Server does not support APIVersion used by client. Server supported APIVersions: '%v', client APIVersion: '%v'", versions, clientVersion)
}
func TestSingleWatch(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
ns := framework.CreateTestingNamespace("single-watch", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
mkEvent := func(i int) *v1.Event {
name := fmt.Sprintf("event-%v", i)
return &v1.Event{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.Name,
Name: name,
},
InvolvedObject: v1.ObjectReference{
Namespace: ns.Name,
Name: name,
},
Reason: fmt.Sprintf("event %v", i),
}
}
rv1 := ""
for i := 0; i < 10; i++ {
event := mkEvent(i)
got, err := client.Core().Events(ns.Name).Create(event)
if err != nil {
t.Fatalf("Failed creating event %#q: %v", event, err)
}
if rv1 == "" {
rv1 = got.ResourceVersion
if rv1 == "" {
t.Fatal("did not get a resource version.")
}
}
t.Logf("Created event %#v", got.ObjectMeta)
}
w, err := client.Core().RESTClient().Get().
Namespace(ns.Name).
Resource("events").
VersionedParams(&metav1.ListOptions{
ResourceVersion: rv1,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "event-9").String(),
}, metav1.ParameterCodec).
Watch()
if err != nil {
t.Fatalf("Failed watch: %v", err)
}
defer w.Stop()
select {
case <-time.After(wait.ForeverTestTimeout):
t.Fatalf("watch took longer than %s", wait.ForeverTestTimeout.String())
case got, ok := <-w.ResultChan():
if !ok {
t.Fatal("Watch channel closed unexpectedly.")
}
// We expect to see an ADD of event-9 and only event-9. (This
// catches a bug where all the events would have been sent down
// the channel.)
if e, a := watch.Added, got.Type; e != a {
t.Errorf("Wanted %v, got %v", e, a)
}
switch o := got.Object.(type) {
case *v1.Event:
if e, a := "event-9", o.Name; e != a {
t.Errorf("Wanted %v, got %v", e, a)
}
default:
t.Fatalf("Unexpected watch event containing object %#q", got)
}
}
}
func TestMultiWatch(t *testing.T) {
// Disable this test as long as it demonstrates a problem.
// TODO: Re-enable this test when we get #6059 resolved.
t.Skip()
const watcherCount = 50
rt.GOMAXPROCS(watcherCount)
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
ns := framework.CreateTestingNamespace("multi-watch", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
dummyEvent := func(i int) *v1.Event {
name := fmt.Sprintf("unrelated-%v", i)
return &v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v.%x", name, time.Now().UnixNano()),
Namespace: ns.Name,
},
InvolvedObject: v1.ObjectReference{
Name: name,
Namespace: ns.Name,
},
Reason: fmt.Sprintf("unrelated change %v", i),
}
}
type timePair struct {
t time.Time
name string
}
receivedTimes := make(chan timePair, watcherCount*2)
watchesStarted := sync.WaitGroup{}
// make a bunch of pods and watch them
for i := 0; i < watcherCount; i++ {
watchesStarted.Add(1)
name := fmt.Sprintf("multi-watch-%v", i)
got, err := client.Core().Pods(ns.Name).Create(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels.Set{"watchlabel": name},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "pause",
Image: imageutils.GetPauseImageName(),
}},
},
})
if err != nil {
t.Fatalf("Couldn't make %v: %v", name, err)
}
go func(name, rv string) {
options := metav1.ListOptions{
LabelSelector: labels.Set{"watchlabel": name}.AsSelector().String(),
ResourceVersion: rv,
}
w, err := client.Core().Pods(ns.Name).Watch(options)
if err != nil {
panic(fmt.Sprintf("watch error for %v: %v", name, err))
}
defer w.Stop()
watchesStarted.Done()
e, ok := <-w.ResultChan() // should get the update (that we'll do below)
if !ok {
panic(fmt.Sprintf("%v ended early?", name))
}
if e.Type != watch.Modified {
panic(fmt.Sprintf("Got unexpected watch notification:\n%v: %+v %+v", name, e, e.Object))
}
receivedTimes <- timePair{time.Now(), name}
}(name, got.ObjectMeta.ResourceVersion)
}
log.Printf("%v: %v pods made and watchers started", time.Now(), watcherCount)
// wait for watches to start before we start spamming the system with
// objects below, otherwise we'll hit the watch window restriction.
watchesStarted.Wait()
const (
useEventsAsUnrelatedType = false
usePodsAsUnrelatedType = true
)
// make a bunch of unrelated changes in parallel
if useEventsAsUnrelatedType {
const unrelatedCount = 3000
var wg sync.WaitGroup
defer wg.Wait()
changeToMake := make(chan int, unrelatedCount*2)
changeMade := make(chan int, unrelatedCount*2)
go func() {
for i := 0; i < unrelatedCount; i++ {
changeToMake <- i
}
close(changeToMake)
}()
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
i, ok := <-changeToMake
if !ok {
return
}
if _, err := client.Core().Events(ns.Name).Create(dummyEvent(i)); err != nil {
panic(fmt.Sprintf("couldn't make an event: %v", err))
}
changeMade <- i
}
}()
}
for i := 0; i < 2000; i++ {
<-changeMade
if (i+1)%50 == 0 {
log.Printf("%v: %v unrelated changes made", time.Now(), i+1)
}
}
}
if usePodsAsUnrelatedType {
const unrelatedCount = 3000
var wg sync.WaitGroup
defer wg.Wait()
changeToMake := make(chan int, unrelatedCount*2)
changeMade := make(chan int, unrelatedCount*2)
go func() {
for i := 0; i < unrelatedCount; i++ {
changeToMake <- i
}
close(changeToMake)
}()
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
i, ok := <-changeToMake
if !ok {
return
}
name := fmt.Sprintf("unrelated-%v", i)
_, err := client.Core().Pods(ns.Name).Create(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "nothing",
Image: imageutils.GetPauseImageName(),
}},
},
})
if err != nil {
panic(fmt.Sprintf("couldn't make unrelated pod: %v", err))
}
changeMade <- i
}
}()
}
for i := 0; i < 2000; i++ {
<-changeMade
if (i+1)%50 == 0 {
log.Printf("%v: %v unrelated changes made", time.Now(), i+1)
}
}
}
// Now we still have changes being made in parallel, but at least 1000 have been made.
// Make some updates to send down the watches.
sentTimes := make(chan timePair, watcherCount*2)
for i := 0; i < watcherCount; i++ {
go func(i int) {
name := fmt.Sprintf("multi-watch-%v", i)
pod, err := client.Core().Pods(ns.Name).Get(name, metav1.GetOptions{})
if err != nil {
panic(fmt.Sprintf("Couldn't get %v: %v", name, err))
}
pod.Spec.Containers[0].Image = imageutils.GetPauseImageName()
sentTimes <- timePair{time.Now(), name}
if _, err := client.Core().Pods(ns.Name).Update(pod); err != nil {
panic(fmt.Sprintf("Couldn't make %v: %v", name, err))
}
}(i)
}
sent := map[string]time.Time{}
for i := 0; i < watcherCount; i++ {
tp := <-sentTimes
sent[tp.name] = tp.t
}
log.Printf("all changes made")
dur := map[string]time.Duration{}
for i := 0; i < watcherCount; i++ {
tp := <-receivedTimes
delta := tp.t.Sub(sent[tp.name])
dur[tp.name] = delta
log.Printf("%v: %v", tp.name, delta)
}
log.Printf("all watches ended")
t.Errorf("durations: %v", dur)
}
func runSelfLinkTestOnNamespace(t *testing.T, c clientset.Interface, namespace string) {
podBody := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "selflinktest",
Namespace: namespace,
Labels: map[string]string{
"name": "selflinktest",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "name", Image: "image"},
},
},
}
pod, err := c.CoreV1().Pods(namespace).Create(&podBody)
if err != nil {
t.Fatalf("Failed creating selflinktest pod: %v", err)
}
if err = c.CoreV1().RESTClient().Get().RequestURI(pod.SelfLink).Do().Into(pod); err != nil {
t.Errorf("Failed listing pod with supplied self link '%v': %v", pod.SelfLink, err)
}
podList, err := c.CoreV1().Pods(namespace).List(metav1.ListOptions{})
if err != nil {
t.Errorf("Failed listing pods: %v", err)
}
if err = c.CoreV1().RESTClient().Get().RequestURI(podList.SelfLink).Do().Into(podList); err != nil {
t.Errorf("Failed listing pods with supplied self link '%v': %v", podList.SelfLink, err)
}
found := false
for i := range podList.Items {
item := &podList.Items[i]
if item.Name != "selflinktest" {
continue
}
found = true
err = c.CoreV1().RESTClient().Get().RequestURI(item.SelfLink).Do().Into(pod)
if err != nil {
t.Errorf("Failed listing pod with supplied self link '%v': %v", item.SelfLink, err)
}
break
}
if !found {
t.Errorf("never found selflinktest pod in namespace %s", namespace)
}
}
func TestSelfLinkOnNamespace(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
ns := framework.CreateTestingNamespace("selflink", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
c := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
runSelfLinkTestOnNamespace(t, c, ns.Name)
}

View File

@@ -0,0 +1,136 @@
/*
Copyright 2016 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 client
import (
"reflect"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/test/integration/framework"
)
func TestDynamicClient(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
ns := framework.CreateTestingNamespace("dynamic-client", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
gv := &schema.GroupVersion{Group: "", Version: "v1"}
config := &restclient.Config{
Host: s.URL,
ContentConfig: restclient.ContentConfig{GroupVersion: gv},
}
client := clientset.NewForConfigOrDie(config)
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
t.Fatalf("unexpected error creating dynamic client: %v", err)
}
resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
// Create a Pod with the normal client
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test",
Image: "test-image",
},
},
},
}
actual, err := client.Core().Pods(ns.Name).Create(pod)
if err != nil {
t.Fatalf("unexpected error when creating pod: %v", err)
}
// check dynamic list
unstructuredList, err := dynamicClient.Resource(resource).Namespace(ns.Name).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected error when listing pods: %v", err)
}
if len(unstructuredList.Items) != 1 {
t.Fatalf("expected one pod, got %d", len(unstructuredList.Items))
}
got, err := unstructuredToPod(&unstructuredList.Items[0])
if err != nil {
t.Fatalf("unexpected error converting Unstructured to v1.Pod: %v", err)
}
if !reflect.DeepEqual(actual, got) {
t.Fatalf("unexpected pod in list. wanted %#v, got %#v", actual, got)
}
// check dynamic get
unstruct, err := dynamicClient.Resource(resource).Namespace(ns.Name).Get(actual.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("unexpected error when getting pod %q: %v", actual.Name, err)
}
got, err = unstructuredToPod(unstruct)
if err != nil {
t.Fatalf("unexpected error converting Unstructured to v1.Pod: %v", err)
}
if !reflect.DeepEqual(actual, got) {
t.Fatalf("unexpected pod in list. wanted %#v, got %#v", actual, got)
}
// delete the pod dynamically
err = dynamicClient.Resource(resource).Namespace(ns.Name).Delete(actual.Name, nil)
if err != nil {
t.Fatalf("unexpected error when deleting pod: %v", err)
}
list, err := client.Core().Pods(ns.Name).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected error when listing pods: %v", err)
}
if len(list.Items) != 0 {
t.Fatalf("expected zero pods, got %d", len(list.Items))
}
}
func unstructuredToPod(obj *unstructured.Unstructured) (*v1.Pod, error) {
json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
pod := new(v1.Pod)
err = runtime.DecodeInto(testapi.Default.Codec(), json, pod)
pod.Kind = ""
pod.APIVersion = ""
return pod, err
}

View File

@@ -0,0 +1,27 @@
/*
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 client
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"configmap_test.go",
"main_test.go",
],
tags = ["integration"],
deps = [
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,124 @@
/*
Copyright 2015 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 configmap
// This file tests use of the configMap API resource.
import (
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework"
)
// TestConfigMap tests apiserver-side behavior of creation of ConfigMaps and pods that consume them.
func TestConfigMap(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := framework.CreateTestingNamespace("config-map", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
DoTestConfigMap(t, client, ns)
}
func DoTestConfigMap(t *testing.T, client clientset.Interface, ns *v1.Namespace) {
cfg := v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "configmap",
Namespace: ns.Name,
},
Data: map[string]string{
"data-1": "value-1",
"data-2": "value-2",
"data-3": "value-3",
},
}
if _, err := client.CoreV1().ConfigMaps(cfg.Namespace).Create(&cfg); err != nil {
t.Errorf("unable to create test configMap: %v", err)
}
defer deleteConfigMapOrErrorf(t, client, cfg.Namespace, cfg.Name)
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "XXX",
Namespace: ns.Name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
Env: []v1.EnvVar{
{
Name: "CONFIG_DATA_1",
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "configmap",
},
Key: "data-1",
},
},
},
{
Name: "CONFIG_DATA_2",
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "configmap",
},
Key: "data-2",
},
},
}, {
Name: "CONFIG_DATA_3",
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "configmap",
},
Key: "data-3",
},
},
},
},
},
},
},
}
pod.ObjectMeta.Name = "uses-configmap"
if _, err := client.CoreV1().Pods(ns.Name).Create(pod); err != nil {
t.Errorf("Failed to create pod: %v", err)
}
defer integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
}
func deleteConfigMapOrErrorf(t *testing.T, c clientset.Interface, ns, name string) {
if err := c.CoreV1().ConfigMaps(ns).Delete(name, nil); err != nil {
t.Errorf("unable to delete ConfigMap %v: %v", name, err)
}
}

View File

@@ -0,0 +1,27 @@
/*
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 configmap
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"daemonset_test.go",
"main_test.go",
],
tags = ["integration"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/controller/daemon:go_default_library",
"//pkg/features:go_default_library",
"//pkg/scheduler:go_default_library",
"//pkg/scheduler/algorithm:go_default_library",
"//pkg/scheduler/algorithmprovider:go_default_library",
"//pkg/scheduler/factory:go_default_library",
"//pkg/util/metrics:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

9
vendor/k8s.io/kubernetes/test/integration/daemonset/OWNERS generated vendored Executable file
View File

@@ -0,0 +1,9 @@
approvers:
- mikedanese
- kow3ns
reviewers:
- mikedanese
- kargakis
- lukaszo
- janetkuo
- kow3ns

View File

@@ -0,0 +1,742 @@
/*
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 daemonset
import (
"fmt"
"net/http/httptest"
"testing"
"time"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
appstyped "k8s.io/client-go/kubernetes/typed/apps/v1"
clientv1core "k8s.io/client-go/kubernetes/typed/core/v1"
corev1typed "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api/legacyscheme"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/controller/daemon"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/scheduler"
"k8s.io/kubernetes/pkg/scheduler/algorithm"
"k8s.io/kubernetes/pkg/scheduler/algorithmprovider"
_ "k8s.io/kubernetes/pkg/scheduler/algorithmprovider"
"k8s.io/kubernetes/pkg/scheduler/factory"
"k8s.io/kubernetes/pkg/util/metrics"
"k8s.io/kubernetes/test/integration/framework"
)
var zero = int64(0)
func setup(t *testing.T) (*httptest.Server, framework.CloseFunc, *daemon.DaemonSetsController, informers.SharedInformerFactory, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
_, server, closeFn := framework.RunAMaster(masterConfig)
config := restclient.Config{Host: server.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("Error in creating clientset: %v", err)
}
resyncPeriod := 12 * time.Hour
informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "daemonset-informers")), resyncPeriod)
metrics.UnregisterMetricAndUntrackRateLimiterUsage("daemon_controller")
dc, err := daemon.NewDaemonSetsController(
informers.Apps().V1().DaemonSets(),
informers.Apps().V1().ControllerRevisions(),
informers.Core().V1().Pods(),
informers.Core().V1().Nodes(),
clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "daemonset-controller")),
)
if err != nil {
t.Fatalf("error creating DaemonSets controller: %v", err)
}
return server, closeFn, dc, informers, clientSet
}
func setupScheduler(
t *testing.T,
cs clientset.Interface,
informerFactory informers.SharedInformerFactory,
stopCh chan struct{},
) {
// If ScheduleDaemonSetPods is disabled, do not start scheduler.
if !utilfeature.DefaultFeatureGate.Enabled(features.ScheduleDaemonSetPods) {
return
}
schedulerConfigFactory := factory.NewConfigFactory(
v1.DefaultSchedulerName,
cs,
informerFactory.Core().V1().Nodes(),
informerFactory.Core().V1().Pods(),
informerFactory.Core().V1().PersistentVolumes(),
informerFactory.Core().V1().PersistentVolumeClaims(),
informerFactory.Core().V1().ReplicationControllers(),
informerFactory.Extensions().V1beta1().ReplicaSets(),
informerFactory.Apps().V1beta1().StatefulSets(),
informerFactory.Core().V1().Services(),
informerFactory.Policy().V1beta1().PodDisruptionBudgets(),
informerFactory.Storage().V1().StorageClasses(),
v1.DefaultHardPodAffinitySymmetricWeight,
true,
false,
)
schedulerConfig, err := schedulerConfigFactory.Create()
if err != nil {
t.Fatalf("Couldn't create scheduler config: %v", err)
}
schedulerConfig.StopEverything = stopCh
eventBroadcaster := record.NewBroadcaster()
schedulerConfig.Recorder = eventBroadcaster.NewRecorder(
legacyscheme.Scheme,
v1.EventSource{Component: v1.DefaultSchedulerName},
)
eventBroadcaster.StartRecordingToSink(&clientv1core.EventSinkImpl{
Interface: cs.CoreV1().Events(""),
})
sched, err := scheduler.NewFromConfigurator(
&scheduler.FakeConfigurator{Config: schedulerConfig}, nil...)
if err != nil {
t.Fatalf("error creating scheduler: %v", err)
}
algorithmprovider.ApplyFeatureGates()
go sched.Run()
}
func testLabels() map[string]string {
return map[string]string{"name": "test"}
}
func newDaemonSet(name, namespace string) *apps.DaemonSet {
two := int32(2)
return &apps.DaemonSet{
TypeMeta: metav1.TypeMeta{
Kind: "DaemonSet",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
Spec: apps.DaemonSetSpec{
RevisionHistoryLimit: &two,
Selector: &metav1.LabelSelector{MatchLabels: testLabels()},
UpdateStrategy: apps.DaemonSetUpdateStrategy{
Type: apps.OnDeleteDaemonSetStrategyType,
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: testLabels(),
},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "foo", Image: "bar"}},
TerminationGracePeriodSeconds: &zero,
},
},
},
}
}
func cleanupDaemonSets(t *testing.T, cs clientset.Interface, ds *apps.DaemonSet) {
ds, err := cs.AppsV1().DaemonSets(ds.Namespace).Get(ds.Name, metav1.GetOptions{})
if err != nil {
t.Errorf("Failed to get DaemonSet %s/%s: %v", ds.Namespace, ds.Name, err)
return
}
// We set the nodeSelector to a random label. This label is nearly guaranteed
// to not be set on any node so the DameonSetController will start deleting
// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
// the DaemonSet.
ds.Spec.Template.Spec.NodeSelector = map[string]string{
string(uuid.NewUUID()): string(uuid.NewUUID()),
}
// force update to avoid version conflict
ds.ResourceVersion = ""
if ds, err = cs.AppsV1().DaemonSets(ds.Namespace).Update(ds); err != nil {
t.Errorf("Failed to update DaemonSet %s/%s: %v", ds.Namespace, ds.Name, err)
return
}
// Wait for the daemon set controller to kill all the daemon pods.
if err := wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) {
updatedDS, err := cs.AppsV1().DaemonSets(ds.Namespace).Get(ds.Name, metav1.GetOptions{})
if err != nil {
return false, nil
}
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
}); err != nil {
t.Errorf("Failed to kill the pods of DaemonSet %s/%s: %v", ds.Namespace, ds.Name, err)
return
}
falseVar := false
deleteOptions := &metav1.DeleteOptions{OrphanDependents: &falseVar}
if err := cs.AppsV1().DaemonSets(ds.Namespace).Delete(ds.Name, deleteOptions); err != nil {
t.Errorf("Failed to delete DaemonSet %s/%s: %v", ds.Namespace, ds.Name, err)
}
}
func newRollbackStrategy() *apps.DaemonSetUpdateStrategy {
one := intstr.FromInt(1)
return &apps.DaemonSetUpdateStrategy{
Type: apps.RollingUpdateDaemonSetStrategyType,
RollingUpdate: &apps.RollingUpdateDaemonSet{MaxUnavailable: &one},
}
}
func newOnDeleteStrategy() *apps.DaemonSetUpdateStrategy {
return &apps.DaemonSetUpdateStrategy{
Type: apps.OnDeleteDaemonSetStrategyType,
}
}
func updateStrategies() []*apps.DaemonSetUpdateStrategy {
return []*apps.DaemonSetUpdateStrategy{newOnDeleteStrategy(), newRollbackStrategy()}
}
func featureGates() []utilfeature.Feature {
return []utilfeature.Feature{
features.ScheduleDaemonSetPods,
}
}
func allocatableResources(memory, cpu string) v1.ResourceList {
return v1.ResourceList{
v1.ResourceMemory: resource.MustParse(memory),
v1.ResourceCPU: resource.MustParse(cpu),
v1.ResourcePods: resource.MustParse("100"),
}
}
func resourcePodSpec(nodeName, memory, cpu string) v1.PodSpec {
return v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{
{
Name: "foo",
Image: "bar",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse(memory),
v1.ResourceCPU: resource.MustParse(cpu),
},
},
},
},
TerminationGracePeriodSeconds: &zero,
}
}
func newNode(name string, label map[string]string) *v1.Node {
return &v1.Node{
TypeMeta: metav1.TypeMeta{
Kind: "Node",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: label,
Namespace: metav1.NamespaceDefault,
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionTrue}},
Allocatable: v1.ResourceList{v1.ResourcePods: resource.MustParse("100")},
},
}
}
func addNodes(nodeClient corev1typed.NodeInterface, startIndex, numNodes int, label map[string]string, t *testing.T) {
for i := startIndex; i < startIndex+numNodes; i++ {
_, err := nodeClient.Create(newNode(fmt.Sprintf("node-%d", i), label))
if err != nil {
t.Fatalf("Failed to create node: %v", err)
}
}
}
func validateDaemonSetPodsAndMarkReady(
podClient corev1typed.PodInterface,
podInformer cache.SharedIndexInformer,
numberPods int,
t *testing.T) {
if err := wait.Poll(10*time.Second, 60*time.Second, func() (bool, error) {
objects := podInformer.GetIndexer().List()
if len(objects) != numberPods {
return false, nil
}
for _, object := range objects {
pod := object.(*v1.Pod)
ownerReferences := pod.ObjectMeta.OwnerReferences
if len(ownerReferences) != 1 {
return false, fmt.Errorf("Pod %s has %d OwnerReferences, expected only 1", pod.Name, len(ownerReferences))
}
controllerRef := ownerReferences[0]
if got, want := controllerRef.Kind, "DaemonSet"; got != want {
t.Errorf("controllerRef.Kind = %q, want %q", got, want)
}
if controllerRef.Controller == nil || *controllerRef.Controller != true {
t.Errorf("controllerRef.Controller is not set to true")
}
if !podutil.IsPodReady(pod) && len(pod.Spec.NodeName) != 0 {
podCopy := pod.DeepCopy()
podCopy.Status = v1.PodStatus{
Phase: v1.PodRunning,
Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue}},
}
_, err := podClient.UpdateStatus(podCopy)
if err != nil {
return false, err
}
}
}
return true, nil
}); err != nil {
t.Fatal(err)
}
}
// podUnschedulable returns a condition function that returns true if the given pod
// gets unschedulable status.
func podUnschedulable(c clientset.Interface, podNamespace, podName string) wait.ConditionFunc {
return func() (bool, error) {
pod, err := c.CoreV1().Pods(podNamespace).Get(podName, metav1.GetOptions{})
if errors.IsNotFound(err) {
return false, nil
}
if err != nil {
// This could be a connection error so we want to retry.
return false, nil
}
_, cond := podutil.GetPodCondition(&pod.Status, v1.PodScheduled)
return cond != nil && cond.Status == v1.ConditionFalse &&
cond.Reason == v1.PodReasonUnschedulable, nil
}
}
// waitForPodUnscheduleWithTimeout waits for a pod to fail scheduling and returns
// an error if it does not become unschedulable within the given timeout.
func waitForPodUnschedulableWithTimeout(cs clientset.Interface, pod *v1.Pod, timeout time.Duration) error {
return wait.Poll(100*time.Millisecond, timeout, podUnschedulable(cs, pod.Namespace, pod.Name))
}
// waitForPodUnschedule waits for a pod to fail scheduling and returns
// an error if it does not become unschedulable within the timeout duration (30 seconds).
func waitForPodUnschedulable(cs clientset.Interface, pod *v1.Pod) error {
return waitForPodUnschedulableWithTimeout(cs, pod, 10*time.Second)
}
// waitForPodsCreated waits for number of pods are created.
func waitForPodsCreated(podInformer cache.SharedIndexInformer, num int) error {
return wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) {
objects := podInformer.GetIndexer().List()
return len(objects) == num, nil
})
}
func validateDaemonSetStatus(
dsClient appstyped.DaemonSetInterface,
dsName string,
expectedNumberReady int32,
t *testing.T) {
if err := wait.Poll(5*time.Second, 60*time.Second, func() (bool, error) {
ds, err := dsClient.Get(dsName, metav1.GetOptions{})
if err != nil {
return false, err
}
return ds.Status.NumberReady == expectedNumberReady, nil
}); err != nil {
t.Fatal(err)
}
}
func validateFailedPlacementEvent(eventClient corev1typed.EventInterface, t *testing.T) {
if err := wait.Poll(5*time.Second, 60*time.Second, func() (bool, error) {
eventList, err := eventClient.List(metav1.ListOptions{})
if err != nil {
return false, err
}
if len(eventList.Items) == 0 {
return false, nil
}
if len(eventList.Items) > 1 {
t.Errorf("Expected 1 event got %d", len(eventList.Items))
}
event := eventList.Items[0]
if event.Type != v1.EventTypeWarning {
t.Errorf("Event type expected %s got %s", v1.EventTypeWarning, event.Type)
}
if event.Reason != daemon.FailedPlacementReason {
t.Errorf("Event reason expected %s got %s", daemon.FailedPlacementReason, event.Reason)
}
return true, nil
}); err != nil {
t.Fatal(err)
}
}
func forEachFeatureGate(t *testing.T, tf func(t *testing.T)) {
for _, fg := range featureGates() {
func() {
enabled := utilfeature.DefaultFeatureGate.Enabled(fg)
defer func() {
utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%v=%t", fg, enabled))
}()
for _, f := range []bool{true, false} {
utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%v=%t", fg, f))
t.Run(fmt.Sprintf("%v (%t)", fg, f), tf)
}
}()
}
}
func forEachStrategy(t *testing.T, tf func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy)) {
for _, strategy := range updateStrategies() {
t.Run(fmt.Sprintf("%s (%v)", t.Name(), strategy),
func(tt *testing.T) { tf(tt, strategy) })
}
}
func TestOneNodeDaemonLaunchesPod(t *testing.T) {
forEachFeatureGate(t, func(t *testing.T) {
forEachStrategy(t, func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy) {
server, closeFn, dc, informers, clientset := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("one-node-daemonset-test", server, t)
defer framework.DeleteTestingNamespace(ns, server, t)
dsClient := clientset.AppsV1().DaemonSets(ns.Name)
podClient := clientset.CoreV1().Pods(ns.Name)
nodeClient := clientset.CoreV1().Nodes()
podInformer := informers.Core().V1().Pods().Informer()
stopCh := make(chan struct{})
defer close(stopCh)
informers.Start(stopCh)
go dc.Run(5, stopCh)
// Start Scheduler
setupScheduler(t, clientset, informers, stopCh)
ds := newDaemonSet("foo", ns.Name)
ds.Spec.UpdateStrategy = *strategy
_, err := dsClient.Create(ds)
if err != nil {
t.Fatalf("Failed to create DaemonSet: %v", err)
}
defer cleanupDaemonSets(t, clientset, ds)
_, err = nodeClient.Create(newNode("single-node", nil))
if err != nil {
t.Fatalf("Failed to create node: %v", err)
}
validateDaemonSetPodsAndMarkReady(podClient, podInformer, 1, t)
validateDaemonSetStatus(dsClient, ds.Name, 1, t)
})
})
}
func TestSimpleDaemonSetLaunchesPods(t *testing.T) {
forEachFeatureGate(t, func(t *testing.T) {
forEachStrategy(t, func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy) {
server, closeFn, dc, informers, clientset := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("simple-daemonset-test", server, t)
defer framework.DeleteTestingNamespace(ns, server, t)
dsClient := clientset.AppsV1().DaemonSets(ns.Name)
podClient := clientset.CoreV1().Pods(ns.Name)
nodeClient := clientset.CoreV1().Nodes()
podInformer := informers.Core().V1().Pods().Informer()
stopCh := make(chan struct{})
defer close(stopCh)
informers.Start(stopCh)
go dc.Run(5, stopCh)
// Start Scheduler
setupScheduler(t, clientset, informers, stopCh)
ds := newDaemonSet("foo", ns.Name)
ds.Spec.UpdateStrategy = *strategy
_, err := dsClient.Create(ds)
if err != nil {
t.Fatalf("Failed to create DaemonSet: %v", err)
}
defer cleanupDaemonSets(t, clientset, ds)
addNodes(nodeClient, 0, 5, nil, t)
validateDaemonSetPodsAndMarkReady(podClient, podInformer, 5, t)
validateDaemonSetStatus(dsClient, ds.Name, 5, t)
})
})
}
func TestDaemonSetWithNodeSelectorLaunchesPods(t *testing.T) {
forEachFeatureGate(t, func(t *testing.T) {
forEachStrategy(t, func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy) {
server, closeFn, dc, informers, clientset := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("simple-daemonset-test", server, t)
defer framework.DeleteTestingNamespace(ns, server, t)
dsClient := clientset.AppsV1().DaemonSets(ns.Name)
podClient := clientset.CoreV1().Pods(ns.Name)
nodeClient := clientset.CoreV1().Nodes()
podInformer := informers.Core().V1().Pods().Informer()
stopCh := make(chan struct{})
defer close(stopCh)
informers.Start(stopCh)
go dc.Run(5, stopCh)
// Start Scheduler
setupScheduler(t, clientset, informers, stopCh)
ds := newDaemonSet("foo", ns.Name)
ds.Spec.UpdateStrategy = *strategy
ds.Spec.Template.Spec.Affinity = &v1.Affinity{
NodeAffinity: &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "zone",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test"},
},
},
},
{
MatchFields: []v1.NodeSelectorRequirement{
{
Key: algorithm.NodeFieldSelectorKeyNodeName,
Operator: v1.NodeSelectorOpIn,
Values: []string{"node-1"},
},
},
},
},
},
},
}
_, err := dsClient.Create(ds)
if err != nil {
t.Fatalf("Failed to create DaemonSet: %v", err)
}
defer cleanupDaemonSets(t, clientset, ds)
addNodes(nodeClient, 0, 2, nil, t)
// Two nodes with labels
addNodes(nodeClient, 2, 2, map[string]string{
"zone": "test",
}, t)
addNodes(nodeClient, 4, 2, nil, t)
validateDaemonSetPodsAndMarkReady(podClient, podInformer, 3, t)
validateDaemonSetStatus(dsClient, ds.Name, 3, t)
})
})
}
func TestNotReadyNodeDaemonDoesLaunchPod(t *testing.T) {
forEachStrategy(t, func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy) {
server, closeFn, dc, informers, clientset := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("simple-daemonset-test", server, t)
defer framework.DeleteTestingNamespace(ns, server, t)
dsClient := clientset.AppsV1().DaemonSets(ns.Name)
podClient := clientset.CoreV1().Pods(ns.Name)
nodeClient := clientset.CoreV1().Nodes()
podInformer := informers.Core().V1().Pods().Informer()
stopCh := make(chan struct{})
defer close(stopCh)
informers.Start(stopCh)
go dc.Run(5, stopCh)
// Start Scheduler
setupScheduler(t, clientset, informers, stopCh)
ds := newDaemonSet("foo", ns.Name)
ds.Spec.UpdateStrategy = *strategy
_, err := dsClient.Create(ds)
if err != nil {
t.Fatalf("Failed to create DaemonSet: %v", err)
}
defer cleanupDaemonSets(t, clientset, ds)
node := newNode("single-node", nil)
node.Status.Conditions = []v1.NodeCondition{
{Type: v1.NodeReady, Status: v1.ConditionFalse},
}
_, err = nodeClient.Create(node)
if err != nil {
t.Fatalf("Failed to create node: %v", err)
}
validateDaemonSetPodsAndMarkReady(podClient, podInformer, 1, t)
validateDaemonSetStatus(dsClient, ds.Name, 1, t)
})
}
func TestInsufficientCapacityNodeDaemonDoesNotLaunchPod(t *testing.T) {
forEachStrategy(t, func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy) {
server, closeFn, dc, informers, clientset := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("insufficient-capacity", server, t)
defer framework.DeleteTestingNamespace(ns, server, t)
dsClient := clientset.AppsV1().DaemonSets(ns.Name)
nodeClient := clientset.CoreV1().Nodes()
eventClient := clientset.CoreV1().Events(ns.Namespace)
stopCh := make(chan struct{})
defer close(stopCh)
informers.Start(stopCh)
go dc.Run(5, stopCh)
ds := newDaemonSet("foo", ns.Name)
ds.Spec.Template.Spec = resourcePodSpec("node-with-limited-memory", "120M", "75m")
ds.Spec.UpdateStrategy = *strategy
_, err := dsClient.Create(ds)
if err != nil {
t.Fatalf("Failed to create DaemonSet: %v", err)
}
defer cleanupDaemonSets(t, clientset, ds)
node := newNode("node-with-limited-memory", nil)
node.Status.Allocatable = allocatableResources("100M", "200m")
_, err = nodeClient.Create(node)
if err != nil {
t.Fatalf("Failed to create node: %v", err)
}
validateFailedPlacementEvent(eventClient, t)
})
}
// TestInsufficientCapacityNodeDaemonSetCreateButNotLaunchPod tests that when "ScheduleDaemonSetPods"
// feature is enabled, the DaemonSet should create Pods for all the nodes regardless of available resource
// on the nodes, and kube-scheduler should not schedule Pods onto the nodes with insufficient resource.
func TestInsufficientCapacityNodeWhenScheduleDaemonSetPodsEnabled(t *testing.T) {
enabled := utilfeature.DefaultFeatureGate.Enabled(features.ScheduleDaemonSetPods)
defer func() {
utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%t",
features.ScheduleDaemonSetPods, enabled))
}()
utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%t", features.ScheduleDaemonSetPods, true))
forEachStrategy(t, func(t *testing.T, strategy *apps.DaemonSetUpdateStrategy) {
server, closeFn, dc, informers, clientset := setup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("insufficient-capacity", server, t)
defer framework.DeleteTestingNamespace(ns, server, t)
dsClient := clientset.AppsV1().DaemonSets(ns.Name)
podClient := clientset.CoreV1().Pods(ns.Name)
podInformer := informers.Core().V1().Pods().Informer()
nodeClient := clientset.CoreV1().Nodes()
stopCh := make(chan struct{})
defer close(stopCh)
informers.Start(stopCh)
go dc.Run(5, stopCh)
// Start Scheduler
setupScheduler(t, clientset, informers, stopCh)
ds := newDaemonSet("foo", ns.Name)
ds.Spec.Template.Spec = resourcePodSpec("", "120M", "75m")
ds.Spec.UpdateStrategy = *strategy
ds, err := dsClient.Create(ds)
if err != nil {
t.Fatalf("Failed to create DaemonSet: %v", err)
}
defer cleanupDaemonSets(t, clientset, ds)
node := newNode("node-with-limited-memory", nil)
node.Status.Allocatable = allocatableResources("100M", "200m")
_, err = nodeClient.Create(node)
if err != nil {
t.Fatalf("Failed to create node: %v", err)
}
if err := waitForPodsCreated(podInformer, 1); err != nil {
t.Errorf("Failed to wait for pods created: %v", err)
}
objects := podInformer.GetIndexer().List()
for _, object := range objects {
pod := object.(*v1.Pod)
if err := waitForPodUnschedulable(clientset, pod); err != nil {
t.Errorf("Failed to wait for unschedulable status of pod %+v", pod)
}
}
node1 := newNode("node-with-enough-memory", nil)
node1.Status.Allocatable = allocatableResources("200M", "2000m")
_, err = nodeClient.Create(node1)
if err != nil {
t.Fatalf("Failed to create node: %v", err)
}
// When ScheduleDaemonSetPods enabled, 2 pods are created. But only one
// of two Pods is scheduled by default scheduler.
validateDaemonSetPodsAndMarkReady(podClient, podInformer, 2, t)
validateDaemonSetStatus(dsClient, ds.Name, 1, t)
})
}

View File

@@ -0,0 +1,27 @@
/*
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 daemonset
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"defaulttolerationseconds_test.go",
"main_test.go",
],
tags = [
"etcd",
"integration",
],
deps = [
"//pkg/apis/core/helper:go_default_library",
"//pkg/scheduler/algorithm:go_default_library",
"//plugin/pkg/admission/defaulttolerationseconds:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,103 @@
/*
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 defaulttolerationseconds
import (
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/scheduler/algorithm"
"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
"k8s.io/kubernetes/test/integration/framework"
)
func TestAdmission(t *testing.T) {
masterConfig := framework.NewMasterConfig()
masterConfig.GenericConfig.EnableProfiling = true
masterConfig.GenericConfig.AdmissionControl = defaulttolerationseconds.NewDefaultTolerationSeconds()
_, s, closeFn := framework.RunAMaster(masterConfig)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := framework.CreateTestingNamespace("default-toleration-seconds", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
pod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.Name,
Name: "foo",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test",
Image: "an-image",
},
},
},
}
updatedPod, err := client.Core().Pods(pod.Namespace).Create(&pod)
if err != nil {
t.Fatalf("error creating pod: %v", err)
}
var defaultSeconds int64 = 300
nodeNotReady := v1.Toleration{
Key: algorithm.TaintNodeNotReady,
Operator: v1.TolerationOpExists,
Effect: v1.TaintEffectNoExecute,
TolerationSeconds: &defaultSeconds,
}
nodeUnreachable := v1.Toleration{
Key: algorithm.TaintNodeUnreachable,
Operator: v1.TolerationOpExists,
Effect: v1.TaintEffectNoExecute,
TolerationSeconds: &defaultSeconds,
}
found := 0
tolerations := updatedPod.Spec.Tolerations
for i := range tolerations {
if found == 2 {
break
}
if tolerations[i].MatchToleration(&nodeNotReady) {
if helper.Semantic.DeepEqual(tolerations[i], nodeNotReady) {
found++
continue
}
}
if tolerations[i].MatchToleration(&nodeUnreachable) {
if helper.Semantic.DeepEqual(tolerations[i], nodeUnreachable) {
found++
continue
}
}
}
if found != 2 {
t.Fatalf("unexpected tolerations: %v\n", updatedPod.Spec.Tolerations)
}
}

View File

@@ -0,0 +1,27 @@
/*
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 defaulttolerationseconds
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,67 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"deployment_test.go",
"main_test.go",
],
embed = [":go_default_library"],
tags = ["integration"],
deps = [
"//pkg/controller/deployment/util:go_default_library",
"//pkg/util/pointer:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/util/retry:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["util.go"],
importpath = "k8s.io/kubernetes/test/integration/deployment",
deps = [
"//pkg/api/v1/pod:go_default_library",
"//pkg/controller/deployment:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/controller/replicaset:go_default_library",
"//pkg/util/metrics:go_default_library",
"//test/integration/framework:go_default_library",
"//test/utils:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,7 @@
reviewers:
- janetkuo
- kargakis
- tnozicka
approvers:
- janetkuo
- kargakis

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
/*
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 deployment
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,509 @@
/*
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 deployment
import (
"fmt"
"net/http/httptest"
"sync"
"testing"
"time"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/controller/deployment"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
"k8s.io/kubernetes/pkg/controller/replicaset"
"k8s.io/kubernetes/pkg/util/metrics"
"k8s.io/kubernetes/test/integration/framework"
testutil "k8s.io/kubernetes/test/utils"
)
const (
pollInterval = 100 * time.Millisecond
pollTimeout = 60 * time.Second
fakeContainerName = "fake-name"
fakeImage = "fakeimage"
)
var pauseFn = func(update *apps.Deployment) {
update.Spec.Paused = true
}
var resumeFn = func(update *apps.Deployment) {
update.Spec.Paused = false
}
type deploymentTester struct {
t *testing.T
c clientset.Interface
deployment *apps.Deployment
}
func testLabels() map[string]string {
return map[string]string{"name": "test"}
}
// newDeployment returns a RollingUpdate Deployment with with a fake container image
func newDeployment(name, ns string, replicas int32) *apps.Deployment {
return &apps.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
Spec: apps.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{MatchLabels: testLabels()},
Strategy: apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
RollingUpdate: new(apps.RollingUpdateDeployment),
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: testLabels(),
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: fakeContainerName,
Image: fakeImage,
},
},
},
},
},
}
}
func newReplicaSet(name, ns string, replicas int32) *apps.ReplicaSet {
return &apps.ReplicaSet{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicaSet",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
Labels: testLabels(),
},
Spec: apps.ReplicaSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: testLabels(),
},
Replicas: &replicas,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: testLabels(),
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: fakeContainerName,
Image: fakeImage,
},
},
},
},
},
}
}
func newDeploymentRollback(name string, annotations map[string]string, revision int64) *extensions.DeploymentRollback {
return &extensions.DeploymentRollback{
Name: name,
UpdatedAnnotations: annotations,
RollbackTo: extensions.RollbackConfig{Revision: revision},
}
}
// dcSetup sets up necessities for Deployment integration test, including master, apiserver, informers, and clientset
func dcSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, *replicaset.ReplicaSetController, *deployment.DeploymentController, informers.SharedInformerFactory, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
config := restclient.Config{Host: s.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("error in create clientset: %v", err)
}
resyncPeriod := 12 * time.Hour
informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "deployment-informers")), resyncPeriod)
metrics.UnregisterMetricAndUntrackRateLimiterUsage("deployment_controller")
dc, err := deployment.NewDeploymentController(
informers.Apps().V1().Deployments(),
informers.Apps().V1().ReplicaSets(),
informers.Core().V1().Pods(),
clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "deployment-controller")),
)
if err != nil {
t.Fatalf("error creating Deployment controller: %v", err)
}
rm := replicaset.NewReplicaSetController(
informers.Apps().V1().ReplicaSets(),
informers.Core().V1().Pods(),
clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "replicaset-controller")),
replicaset.BurstReplicas,
)
return s, closeFn, rm, dc, informers, clientSet
}
// dcSimpleSetup sets up necessities for Deployment integration test, including master, apiserver,
// and clientset, but not controllers and informers
func dcSimpleSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
config := restclient.Config{Host: s.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("error in create clientset: %v", err)
}
return s, closeFn, clientSet
}
// addPodConditionReady sets given pod status to ready at given time
func addPodConditionReady(pod *v1.Pod, time metav1.Time) {
pod.Status = v1.PodStatus{
Phase: v1.PodRunning,
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
LastTransitionTime: time,
},
},
}
}
func (d *deploymentTester) waitForDeploymentRevisionAndImage(revision, image string) error {
if err := testutil.WaitForDeploymentRevisionAndImage(d.c, d.deployment.Namespace, d.deployment.Name, revision, image, d.t.Logf, pollInterval, pollTimeout); err != nil {
return fmt.Errorf("failed to wait for Deployment revision %s: %v", d.deployment.Name, err)
}
return nil
}
func markPodReady(c clientset.Interface, ns string, pod *v1.Pod) error {
addPodConditionReady(pod, metav1.Now())
_, err := c.CoreV1().Pods(ns).UpdateStatus(pod)
return err
}
func intOrStrP(num int) *intstr.IntOrString {
intstr := intstr.FromInt(num)
return &intstr
}
// markUpdatedPodsReady manually marks updated Deployment pods status to ready,
// until the deployment is complete
func (d *deploymentTester) markUpdatedPodsReady(wg *sync.WaitGroup) {
defer wg.Done()
ns := d.deployment.Namespace
err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
// We're done when the deployment is complete
if completed, err := d.deploymentComplete(); err != nil {
return false, err
} else if completed {
return true, nil
}
// Otherwise, mark remaining pods as ready
pods, err := d.listUpdatedPods()
if err != nil {
d.t.Log(err)
return false, nil
}
d.t.Logf("%d/%d of deployment pods are created", len(pods), *d.deployment.Spec.Replicas)
for i := range pods {
pod := pods[i]
if podutil.IsPodReady(&pod) {
continue
}
if err = markPodReady(d.c, ns, &pod); err != nil {
d.t.Logf("failed to update Deployment pod %s, will retry later: %v", pod.Name, err)
}
}
return false, nil
})
if err != nil {
d.t.Fatalf("failed to mark updated Deployment pods to ready: %v", err)
}
}
func (d *deploymentTester) deploymentComplete() (bool, error) {
latest, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(d.deployment.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return deploymentutil.DeploymentComplete(d.deployment, &latest.Status), nil
}
// Waits for the deployment to complete, and check rolling update strategy isn't broken at any times.
// Rolling update strategy should not be broken during a rolling update.
func (d *deploymentTester) waitForDeploymentCompleteAndCheckRolling() error {
return testutil.WaitForDeploymentCompleteAndCheckRolling(d.c, d.deployment, d.t.Logf, pollInterval, pollTimeout)
}
// Waits for the deployment to complete, and don't check if rolling update strategy is broken.
// Rolling update strategy is used only during a rolling update, and can be violated in other situations,
// such as shortly after a scaling event or the deployment is just created.
func (d *deploymentTester) waitForDeploymentComplete() error {
return testutil.WaitForDeploymentComplete(d.c, d.deployment, d.t.Logf, pollInterval, pollTimeout)
}
// waitForDeploymentCompleteAndCheckRollingAndMarkPodsReady waits for the Deployment to complete
// while marking updated Deployment pods as ready at the same time.
// Uses hard check to make sure rolling update strategy is not violated at any times.
func (d *deploymentTester) waitForDeploymentCompleteAndCheckRollingAndMarkPodsReady() error {
var wg sync.WaitGroup
// Manually mark updated Deployment pods as ready in a separate goroutine
wg.Add(1)
go d.markUpdatedPodsReady(&wg)
// Wait for goroutine to finish, for all return paths.
defer wg.Wait()
// Wait for the Deployment status to complete while Deployment pods are becoming ready
err := d.waitForDeploymentCompleteAndCheckRolling()
if err != nil {
return fmt.Errorf("failed to wait for Deployment %s to complete: %v", d.deployment.Name, err)
}
return nil
}
// waitForDeploymentCompleteAndMarkPodsReady waits for the Deployment to complete
// while marking updated Deployment pods as ready at the same time.
func (d *deploymentTester) waitForDeploymentCompleteAndMarkPodsReady() error {
var wg sync.WaitGroup
// Manually mark updated Deployment pods as ready in a separate goroutine
wg.Add(1)
go d.markUpdatedPodsReady(&wg)
// Wait for the Deployment status to complete using soft check, while Deployment pods are becoming ready
err := d.waitForDeploymentComplete()
if err != nil {
return fmt.Errorf("failed to wait for Deployment status %s: %v", d.deployment.Name, err)
}
// Wait for goroutine to finish
wg.Wait()
return nil
}
func (d *deploymentTester) updateDeployment(applyUpdate testutil.UpdateDeploymentFunc) (*apps.Deployment, error) {
return testutil.UpdateDeploymentWithRetries(d.c, d.deployment.Namespace, d.deployment.Name, applyUpdate, d.t.Logf, pollInterval, pollTimeout)
}
func (d *deploymentTester) waitForObservedDeployment(desiredGeneration int64) error {
if err := testutil.WaitForObservedDeployment(d.c, d.deployment.Namespace, d.deployment.Name, desiredGeneration); err != nil {
return fmt.Errorf("failed waiting for ObservedGeneration of deployment %s to become %d: %v", d.deployment.Name, desiredGeneration, err)
}
return nil
}
func (d *deploymentTester) getNewReplicaSet() (*apps.ReplicaSet, error) {
deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(d.deployment.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed retrieving deployment %s: %v", d.deployment.Name, err)
}
rs, err := deploymentutil.GetNewReplicaSet(deployment, d.c.AppsV1())
if err != nil {
return nil, fmt.Errorf("failed retrieving new replicaset of deployment %s: %v", d.deployment.Name, err)
}
return rs, nil
}
func (d *deploymentTester) expectNoNewReplicaSet() error {
rs, err := d.getNewReplicaSet()
if err != nil {
return err
}
if rs != nil {
return fmt.Errorf("expected deployment %s not to create a new replicaset, got %v", d.deployment.Name, rs)
}
return nil
}
func (d *deploymentTester) expectNewReplicaSet() (*apps.ReplicaSet, error) {
rs, err := d.getNewReplicaSet()
if err != nil {
return nil, err
}
if rs == nil {
return nil, fmt.Errorf("expected deployment %s to create a new replicaset, got nil", d.deployment.Name)
}
return rs, nil
}
func (d *deploymentTester) updateReplicaSet(name string, applyUpdate testutil.UpdateReplicaSetFunc) (*apps.ReplicaSet, error) {
return testutil.UpdateReplicaSetWithRetries(d.c, d.deployment.Namespace, name, applyUpdate, d.t.Logf, pollInterval, pollTimeout)
}
func (d *deploymentTester) updateReplicaSetStatus(name string, applyStatusUpdate testutil.UpdateReplicaSetFunc) (*apps.ReplicaSet, error) {
return testutil.UpdateReplicaSetStatusWithRetries(d.c, d.deployment.Namespace, name, applyStatusUpdate, d.t.Logf, pollInterval, pollTimeout)
}
// waitForDeploymentRollbackCleared waits for deployment either started rolling back or doesn't need to rollback.
func (d *deploymentTester) waitForDeploymentRollbackCleared() error {
return testutil.WaitForDeploymentRollbackCleared(d.c, d.deployment.Namespace, d.deployment.Name, pollInterval, pollTimeout)
}
// checkDeploymentRevisionAndImage checks if the input deployment's and its new replica set's revision and image are as expected.
func (d *deploymentTester) checkDeploymentRevisionAndImage(revision, image string) error {
return testutil.CheckDeploymentRevisionAndImage(d.c, d.deployment.Namespace, d.deployment.Name, revision, image)
}
func (d *deploymentTester) waitForDeploymentUpdatedReplicasGTE(minUpdatedReplicas int32) error {
return testutil.WaitForDeploymentUpdatedReplicasGTE(d.c, d.deployment.Namespace, d.deployment.Name, minUpdatedReplicas, d.deployment.Generation, pollInterval, pollTimeout)
}
func (d *deploymentTester) waitForDeploymentWithCondition(reason string, condType apps.DeploymentConditionType) error {
return testutil.WaitForDeploymentWithCondition(d.c, d.deployment.Namespace, d.deployment.Name, reason, condType, d.t.Logf, pollInterval, pollTimeout)
}
func (d *deploymentTester) listUpdatedPods() ([]v1.Pod, error) {
selector, err := metav1.LabelSelectorAsSelector(d.deployment.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("failed to parse deployment selector: %v", err)
}
pods, err := d.c.CoreV1().Pods(d.deployment.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, fmt.Errorf("failed to list deployment pods, will retry later: %v", err)
}
newRS, err := d.getNewReplicaSet()
if err != nil {
return nil, fmt.Errorf("failed to get new replicaset of deployment %q: %v", d.deployment.Name, err)
}
if newRS == nil {
return nil, fmt.Errorf("unable to find new replicaset of deployment %q", d.deployment.Name)
}
var ownedPods []v1.Pod
for _, pod := range pods.Items {
rs := metav1.GetControllerOf(&pod)
if rs.UID == newRS.UID {
ownedPods = append(ownedPods, pod)
}
}
return ownedPods, nil
}
func (d *deploymentTester) waitRSStable(replicaset *apps.ReplicaSet) error {
return testutil.WaitRSStable(d.t, d.c, replicaset, pollInterval, pollTimeout)
}
func (d *deploymentTester) scaleDeployment(newReplicas int32) error {
var err error
d.deployment, err = d.updateDeployment(func(update *apps.Deployment) {
update.Spec.Replicas = &newReplicas
})
if err != nil {
return fmt.Errorf("failed updating deployment %q: %v", d.deployment.Name, err)
}
if err := d.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
return err
}
rs, err := d.expectNewReplicaSet()
if err != nil {
return err
}
if *rs.Spec.Replicas != newReplicas {
return fmt.Errorf("expected new replicaset replicas = %d, got %d", newReplicas, *rs.Spec.Replicas)
}
return nil
}
// waitForReadyReplicas waits for number of ready replicas to equal number of replicas.
func (d *deploymentTester) waitForReadyReplicas() error {
if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(d.deployment.Name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("failed to get deployment %q: %v", d.deployment.Name, err)
}
return deployment.Status.ReadyReplicas == *deployment.Spec.Replicas, nil
}); err != nil {
return fmt.Errorf("failed to wait for .readyReplicas to equal .replicas: %v", err)
}
return nil
}
// markUpdatedPodsReadyWithoutComplete marks updated Deployment pods as ready without waiting for deployment to complete.
func (d *deploymentTester) markUpdatedPodsReadyWithoutComplete() error {
if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
pods, err := d.listUpdatedPods()
if err != nil {
return false, err
}
for i := range pods {
pod := pods[i]
if podutil.IsPodReady(&pod) {
continue
}
if err = markPodReady(d.c, d.deployment.Namespace, &pod); err != nil {
d.t.Logf("failed to update Deployment pod %q, will retry later: %v", pod.Name, err)
return false, nil
}
}
return true, nil
}); err != nil {
return fmt.Errorf("failed to mark all updated pods as ready: %v", err)
}
return nil
}
// Verify all replicas fields of DeploymentStatus have desired count.
// Immediately return an error when found a non-matching replicas field.
func (d *deploymentTester) checkDeploymentStatusReplicasFields(replicas, updatedReplicas, readyReplicas, availableReplicas, unavailableReplicas int32) error {
deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(d.deployment.Name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get deployment %q: %v", d.deployment.Name, err)
}
if deployment.Status.Replicas != replicas {
return fmt.Errorf("unexpected .replicas: expect %d, got %d", replicas, deployment.Status.Replicas)
}
if deployment.Status.UpdatedReplicas != updatedReplicas {
return fmt.Errorf("unexpected .updatedReplicas: expect %d, got %d", updatedReplicas, deployment.Status.UpdatedReplicas)
}
if deployment.Status.ReadyReplicas != readyReplicas {
return fmt.Errorf("unexpected .readyReplicas: expect %d, got %d", readyReplicas, deployment.Status.ReadyReplicas)
}
if deployment.Status.AvailableReplicas != availableReplicas {
return fmt.Errorf("unexpected .replicas: expect %d, got %d", availableReplicas, deployment.Status.AvailableReplicas)
}
if deployment.Status.UnavailableReplicas != unavailableReplicas {
return fmt.Errorf("unexpected .replicas: expect %d, got %d", unavailableReplicas, deployment.Status.UnavailableReplicas)
}
return nil
}

19
vendor/k8s.io/kubernetes/test/integration/doc.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
/*
Copyright 2014 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 integration provides integration tests for Kubernetes.Some tests require a
// running etcd or Docker installation on the system.
package integration // import "k8s.io/kubernetes/test/integration"

59
vendor/k8s.io/kubernetes/test/integration/etcd/BUILD generated vendored Normal file
View File

@@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"etcd_storage_path_test.go",
"main_test.go",
],
tags = [
"etcd",
"integration",
],
deps = [
"//cmd/kube-apiserver/app:go_default_library",
"//cmd/kube-apiserver/app/options:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/master:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//vendor/k8s.io/client-go/discovery/cached:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/restmapper:go_default_library",
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
/*
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 etcd
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"evictions_test.go",
"main_test.go",
],
tags = ["integration"],
deps = [
"//pkg/controller/disruption:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)
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,280 @@
/*
Copyright 2015 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 evictions
import (
"fmt"
"net/http/httptest"
"sync"
"sync/atomic"
"testing"
"time"
"k8s.io/api/core/v1"
"k8s.io/api/policy/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/controller/disruption"
"k8s.io/kubernetes/test/integration/framework"
)
const (
numOfEvictions = 10
)
// TestConcurrentEvictionRequests is to make sure pod disruption budgets (PDB) controller is able to
// handle concurrent eviction requests. Original issue:#37605
func TestConcurrentEvictionRequests(t *testing.T) {
podNameFormat := "test-pod-%d"
s, closeFn, rm, informers, clientSet := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("concurrent-eviction-requests", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := make(chan struct{})
informers.Start(stopCh)
go rm.Run(stopCh)
defer close(stopCh)
config := restclient.Config{Host: s.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("Failed to create clientset: %v", err)
}
var gracePeriodSeconds int64 = 30
deleteOption := &metav1.DeleteOptions{
GracePeriodSeconds: &gracePeriodSeconds,
}
// Generate numOfEvictions pods to evict
for i := 0; i < numOfEvictions; i++ {
podName := fmt.Sprintf(podNameFormat, i)
pod := newPod(podName)
if _, err := clientSet.CoreV1().Pods(ns.Name).Create(pod); err != nil {
t.Errorf("Failed to create pod: %v", err)
}
addPodConditionReady(pod)
if _, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(pod); err != nil {
t.Fatal(err)
}
}
waitToObservePods(t, informers.Core().V1().Pods().Informer(), numOfEvictions)
pdb := newPDB()
if _, err := clientSet.Policy().PodDisruptionBudgets(ns.Name).Create(pdb); err != nil {
t.Errorf("Failed to create PodDisruptionBudget: %v", err)
}
waitPDBStable(t, clientSet, numOfEvictions, ns.Name, pdb.Name)
var numberPodsEvicted uint32 = 0
errCh := make(chan error, 3*numOfEvictions)
var wg sync.WaitGroup
// spawn numOfEvictions goroutines to concurrently evict the pods
for i := 0; i < numOfEvictions; i++ {
wg.Add(1)
go func(id int, errCh chan error) {
defer wg.Done()
podName := fmt.Sprintf(podNameFormat, id)
eviction := newEviction(ns.Name, podName, deleteOption)
err := wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) {
e := clientSet.Policy().Evictions(ns.Name).Evict(eviction)
switch {
case errors.IsTooManyRequests(e):
return false, nil
case errors.IsConflict(e):
return false, fmt.Errorf("Unexpected Conflict (409) error caused by failing to handle concurrent PDB updates: %v", e)
case e == nil:
return true, nil
default:
return false, e
}
})
if err != nil {
errCh <- err
// should not return here otherwise we would leak the pod
}
_, err = clientSet.CoreV1().Pods(ns.Name).Get(podName, metav1.GetOptions{})
switch {
case errors.IsNotFound(err):
atomic.AddUint32(&numberPodsEvicted, 1)
// pod was evicted and deleted so return from goroutine immediately
return
case err == nil:
// this shouldn't happen if the pod was evicted successfully
errCh <- fmt.Errorf("Pod %q is expected to be evicted", podName)
default:
errCh <- err
}
// delete pod which still exists due to error
e := clientSet.CoreV1().Pods(ns.Name).Delete(podName, deleteOption)
if e != nil {
errCh <- e
}
}(i, errCh)
}
wg.Wait()
close(errCh)
var errList []error
if err := clientSet.Policy().PodDisruptionBudgets(ns.Name).Delete(pdb.Name, deleteOption); err != nil {
errList = append(errList, fmt.Errorf("Failed to delete PodDisruptionBudget: %v", err))
}
for err := range errCh {
errList = append(errList, err)
}
if len(errList) > 0 {
t.Fatal(utilerrors.NewAggregate(errList))
}
if atomic.LoadUint32(&numberPodsEvicted) != numOfEvictions {
t.Fatalf("fewer number of successful evictions than expected : %d", numberPodsEvicted)
}
}
func newPod(podName string) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Labels: map[string]string{"app": "test-evictions"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
},
},
},
}
}
func addPodConditionReady(pod *v1.Pod) {
pod.Status = v1.PodStatus{
Phase: v1.PodRunning,
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
}
}
func newPDB() *v1beta1.PodDisruptionBudget {
return &v1beta1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pdb",
},
Spec: v1beta1.PodDisruptionBudgetSpec{
MinAvailable: &intstr.IntOrString{
Type: intstr.Int,
IntVal: 0,
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "test-evictions"},
},
},
}
}
func newEviction(ns, evictionName string, deleteOption *metav1.DeleteOptions) *v1beta1.Eviction {
return &v1beta1.Eviction{
TypeMeta: metav1.TypeMeta{
APIVersion: "Policy/v1beta1",
Kind: "Eviction",
},
ObjectMeta: metav1.ObjectMeta{
Name: evictionName,
Namespace: ns,
},
DeleteOptions: deleteOption,
}
}
func rmSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, *disruption.DisruptionController, informers.SharedInformerFactory, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
config := restclient.Config{Host: s.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
resyncPeriod := 12 * time.Hour
informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "pdb-informers")), resyncPeriod)
rm := disruption.NewDisruptionController(
informers.Core().V1().Pods(),
informers.Policy().V1beta1().PodDisruptionBudgets(),
informers.Core().V1().ReplicationControllers(),
informers.Extensions().V1beta1().ReplicaSets(),
informers.Extensions().V1beta1().Deployments(),
informers.Apps().V1beta1().StatefulSets(),
clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "disruption-controller")),
)
return s, closeFn, rm, informers, clientSet
}
// wait for the podInformer to observe the pods. Call this function before
// running the RS controller to prevent the rc manager from creating new pods
// rather than adopting the existing ones.
func waitToObservePods(t *testing.T, podInformer cache.SharedIndexInformer, podNum int) {
if err := wait.PollImmediate(2*time.Second, 60*time.Second, func() (bool, error) {
objects := podInformer.GetIndexer().List()
if len(objects) == podNum {
return true, nil
}
return false, nil
}); err != nil {
t.Fatal(err)
}
}
func waitPDBStable(t *testing.T, clientSet clientset.Interface, podNum int32, ns, pdbName string) {
if err := wait.PollImmediate(2*time.Second, 60*time.Second, func() (bool, error) {
pdb, err := clientSet.Policy().PodDisruptionBudgets(ns).Get(pdbName, metav1.GetOptions{})
if err != nil {
return false, err
}
if pdb.Status.CurrentHealthy != podNum {
return false, nil
}
return true, nil
}); err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,27 @@
/*
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 evictions
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"apiserver_test.go",
"main_test.go",
"setup_test.go",
"webhook_test.go",
],
tags = ["integration"],
deps = [
"//cmd/kube-apiserver/app:go_default_library",
"//cmd/kube-apiserver/app/options:go_default_library",
"//pkg/master:go_default_library",
"//pkg/master/reconcilers:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/cmd/server:go_default_library",
"//vendor/k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1:go_default_library",
"//vendor/k8s.io/sample-apiserver/pkg/apis/wardle/v1beta1:go_default_library",
"//vendor/k8s.io/sample-apiserver/pkg/cmd/server:go_default_library",
],
)
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,472 @@
/*
Copyright 2016 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 apiserver
import (
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
genericapiserver "k8s.io/apiserver/pkg/server"
genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
client "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/util/cert"
apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
kubeaggregatorserver "k8s.io/kube-aggregator/pkg/cmd/server"
"k8s.io/kubernetes/cmd/kube-apiserver/app"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/test/integration/framework"
wardlev1alpha1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1"
wardlev1beta1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1beta1"
sampleserver "k8s.io/sample-apiserver/pkg/cmd/server"
)
func TestAggregatedAPIServer(t *testing.T) {
stopCh := make(chan struct{})
defer close(stopCh)
certDir, _ := ioutil.TempDir("", "test-integration-apiserver")
defer os.RemoveAll(certDir)
_, defaultServiceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24")
proxySigningKey, err := cert.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
proxySigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "front-proxy-ca"}, proxySigningKey)
if err != nil {
t.Fatal(err)
}
proxyCACertFile, _ := ioutil.TempFile(certDir, "proxy-ca.crt")
if err := ioutil.WriteFile(proxyCACertFile.Name(), cert.EncodeCertPEM(proxySigningCert), 0644); err != nil {
t.Fatal(err)
}
clientSigningKey, err := cert.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
clientSigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "client-ca"}, clientSigningKey)
if err != nil {
t.Fatal(err)
}
clientCACertFile, _ := ioutil.TempFile(certDir, "client-ca.crt")
if err := ioutil.WriteFile(clientCACertFile.Name(), cert.EncodeCertPEM(clientSigningCert), 0644); err != nil {
t.Fatal(err)
}
kubeClientConfigValue := atomic.Value{}
go func() {
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
kubeAPIServerOptions := options.NewServerRunOptions()
kubeAPIServerOptions.SecureServing.Listener = listener
kubeAPIServerOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
kubeAPIServerOptions.SecureServing.ServerCert.CertDirectory = certDir
kubeAPIServerOptions.InsecureServing.BindPort = 0
kubeAPIServerOptions.Etcd.StorageConfig.ServerList = []string{framework.GetEtcdURL()}
kubeAPIServerOptions.ServiceClusterIPRange = *defaultServiceClusterIPRange
kubeAPIServerOptions.Authentication.RequestHeader.UsernameHeaders = []string{"X-Remote-User"}
kubeAPIServerOptions.Authentication.RequestHeader.GroupHeaders = []string{"X-Remote-Group"}
kubeAPIServerOptions.Authentication.RequestHeader.ExtraHeaderPrefixes = []string{"X-Remote-Extra-"}
kubeAPIServerOptions.Authentication.RequestHeader.AllowedNames = []string{"kube-aggregator"}
kubeAPIServerOptions.Authentication.RequestHeader.ClientCAFile = proxyCACertFile.Name()
kubeAPIServerOptions.Authentication.ClientCert.ClientCA = clientCACertFile.Name()
kubeAPIServerOptions.Authorization.Modes = []string{"RBAC"}
completedOptions, err := app.Complete(kubeAPIServerOptions)
if err != nil {
t.Fatal(err)
}
tunneler, proxyTransport, err := app.CreateNodeDialer(completedOptions)
if err != nil {
t.Fatal(err)
}
kubeAPIServerConfig, sharedInformers, versionedInformers, _, _, _, admissionPostStartHook, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport)
if err != nil {
t.Fatal(err)
}
// Adjust the loopback config for external use (external server name and CA)
kubeAPIServerClientConfig := rest.CopyConfig(kubeAPIServerConfig.GenericConfig.LoopbackClientConfig)
kubeAPIServerClientConfig.CAFile = path.Join(certDir, "apiserver.crt")
kubeAPIServerClientConfig.CAData = nil
kubeAPIServerClientConfig.ServerName = ""
kubeClientConfigValue.Store(kubeAPIServerClientConfig)
kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), sharedInformers, versionedInformers, admissionPostStartHook)
if err != nil {
t.Fatal(err)
}
if err := kubeAPIServer.GenericAPIServer.PrepareRun().Run(wait.NeverStop); err != nil {
t.Fatal(err)
}
}()
// just use json because everyone speaks it
err = wait.PollImmediate(time.Second, time.Minute, func() (done bool, err error) {
obj := kubeClientConfigValue.Load()
if obj == nil {
return false, nil
}
kubeClientConfig := kubeClientConfigValue.Load().(*rest.Config)
kubeClientConfig.ContentType = ""
kubeClientConfig.AcceptContentTypes = ""
kubeClient, err := client.NewForConfig(kubeClientConfig)
if err != nil {
// this happens because we race the API server start
t.Log(err)
return false, nil
}
healthStatus := 0
kubeClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
if healthStatus != http.StatusOK {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatal(err)
}
// after this point we won't be mutating, so the race detector will be fine
kubeClientConfig := kubeClientConfigValue.Load().(*rest.Config)
// write a kubeconfig out for starting other API servers with delegated auth. remember, no in-cluster config
adminKubeConfig := createKubeConfig(kubeClientConfig)
kubeconfigFile, _ := ioutil.TempFile("", "")
defer os.Remove(kubeconfigFile.Name())
clientcmd.WriteToFile(*adminKubeConfig, kubeconfigFile.Name())
wardleCertDir, _ := ioutil.TempDir("", "test-integration-wardle-server")
defer os.RemoveAll(wardleCertDir)
wardlePort := new(int32)
// start the wardle server to prove we can aggregate it
go func() {
listener, port, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
atomic.StoreInt32(wardlePort, int32(port))
o := sampleserver.NewWardleServerOptions(os.Stdout, os.Stderr)
o.RecommendedOptions.SecureServing.Listener = listener
o.RecommendedOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
wardleCmd := sampleserver.NewCommandStartWardleServer(o, stopCh)
wardleCmd.SetArgs([]string{
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
"--requestheader-client-ca-file=" + proxyCACertFile.Name(),
"--requestheader-allowed-names=kube-aggregator",
"--authentication-kubeconfig", kubeconfigFile.Name(),
"--authorization-kubeconfig", kubeconfigFile.Name(),
"--etcd-servers", framework.GetEtcdURL(),
"--cert-dir", wardleCertDir,
"--kubeconfig", kubeconfigFile.Name(),
})
if err := wardleCmd.Execute(); err != nil {
t.Fatal(err)
}
}()
wardleClientConfig := rest.AnonymousClientConfig(kubeClientConfig)
wardleClientConfig.CAFile = path.Join(wardleCertDir, "apiserver.crt")
wardleClientConfig.CAData = nil
wardleClientConfig.ServerName = ""
wardleClientConfig.BearerToken = kubeClientConfig.BearerToken
var wardleClient client.Interface
err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
wardleClientConfig.Host = fmt.Sprintf("https://127.0.0.1:%d", atomic.LoadInt32(wardlePort))
wardleClient, err = client.NewForConfig(wardleClientConfig)
if err != nil {
// this happens because we race the API server start
t.Log(err)
return false, nil
}
healthStatus := 0
wardleClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
if healthStatus != http.StatusOK {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatal(err)
}
// start the aggregator
aggregatorCertDir, _ := ioutil.TempDir("", "test-integration-aggregator")
defer os.RemoveAll(aggregatorCertDir)
proxyClientKey, err := cert.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
proxyClientCert, err := cert.NewSignedCert(
cert.Config{
CommonName: "kube-aggregator",
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
proxyClientKey, proxySigningCert, proxySigningKey,
)
proxyClientCertFile, _ := ioutil.TempFile(aggregatorCertDir, "proxy-client.crt")
proxyClientKeyFile, _ := ioutil.TempFile(aggregatorCertDir, "proxy-client.key")
if err := ioutil.WriteFile(proxyClientCertFile.Name(), cert.EncodeCertPEM(proxyClientCert), 0600); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(proxyClientKeyFile.Name(), cert.EncodePrivateKeyPEM(proxyClientKey), 0644); err != nil {
t.Fatal(err)
}
aggregatorPort := new(int32)
go func() {
listener, port, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
atomic.StoreInt32(aggregatorPort, int32(port))
o := kubeaggregatorserver.NewDefaultOptions(os.Stdout, os.Stderr)
o.RecommendedOptions.SecureServing.Listener = listener
o.RecommendedOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
aggregatorCmd := kubeaggregatorserver.NewCommandStartAggregator(o, stopCh)
aggregatorCmd.SetArgs([]string{
"--requestheader-username-headers", "",
"--proxy-client-cert-file", proxyClientCertFile.Name(),
"--proxy-client-key-file", proxyClientKeyFile.Name(),
"--kubeconfig", kubeconfigFile.Name(),
"--authentication-kubeconfig", kubeconfigFile.Name(),
"--authorization-kubeconfig", kubeconfigFile.Name(),
"--etcd-servers", framework.GetEtcdURL(),
"--cert-dir", aggregatorCertDir,
})
if err := aggregatorCmd.Execute(); err != nil {
t.Fatal(err)
}
}()
aggregatorClientConfig := rest.AnonymousClientConfig(kubeClientConfig)
aggregatorClientConfig.CAFile = path.Join(aggregatorCertDir, "apiserver.crt")
aggregatorClientConfig.CAData = nil
aggregatorClientConfig.ServerName = ""
aggregatorClientConfig.BearerToken = kubeClientConfig.BearerToken
var aggregatorDiscoveryClient client.Interface
err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
aggregatorClientConfig.Host = fmt.Sprintf("https://127.0.0.1:%d", atomic.LoadInt32(aggregatorPort))
aggregatorDiscoveryClient, err = client.NewForConfig(aggregatorClientConfig)
if err != nil {
// this happens if we race the API server for writing the cert
return false, nil
}
healthStatus := 0
aggregatorDiscoveryClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
if healthStatus != http.StatusOK {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatal(err)
}
// now we're finally ready to test. These are what's run by default now
testAPIGroupList(t, wardleClient.Discovery().RESTClient())
testAPIGroup(t, wardleClient.Discovery().RESTClient())
testAPIResourceList(t, wardleClient.Discovery().RESTClient())
wardleCA, err := ioutil.ReadFile(wardleClientConfig.CAFile)
if err != nil {
t.Fatal(err)
}
aggregatorClient := aggregatorclient.NewForConfigOrDie(aggregatorClientConfig)
_, err = aggregatorClient.ApiregistrationV1beta1().APIServices().Create(&apiregistrationv1beta1.APIService{
ObjectMeta: metav1.ObjectMeta{Name: "v1alpha1.wardle.k8s.io"},
Spec: apiregistrationv1beta1.APIServiceSpec{
Service: &apiregistrationv1beta1.ServiceReference{
Namespace: "kube-wardle",
Name: "api",
},
Group: "wardle.k8s.io",
Version: "v1alpha1",
CABundle: wardleCA,
GroupPriorityMinimum: 200,
VersionPriority: 200,
},
})
if err != nil {
t.Fatal(err)
}
// this is ugly, but sleep just a little bit so that the watch is probably observed. Since nothing will actually be added to discovery
// (the service is missing), we don't have an external signal.
time.Sleep(100 * time.Millisecond)
if _, err := aggregatorDiscoveryClient.Discovery().ServerResources(); err != nil {
t.Fatal(err)
}
_, err = aggregatorClient.ApiregistrationV1beta1().APIServices().Create(&apiregistrationv1beta1.APIService{
ObjectMeta: metav1.ObjectMeta{Name: "v1."},
Spec: apiregistrationv1beta1.APIServiceSpec{
// register this as a local service so it doesn't try to lookup the default kubernetes service
// which will have an unroutable IP address since it's fake.
Group: "",
Version: "v1",
GroupPriorityMinimum: 100,
VersionPriority: 100,
},
})
if err != nil {
t.Fatal(err)
}
// this is ugly, but sleep just a little bit so that the watch is probably observed. Since nothing will actually be added to discovery
// (the service is missing), we don't have an external signal.
time.Sleep(100 * time.Millisecond)
_, err = aggregatorDiscoveryClient.Discovery().ServerResources()
if err != nil {
t.Fatal(err)
}
// TODO figure out how to turn on enough of services and dns to run more
}
func createKubeConfig(clientCfg *rest.Config) *clientcmdapi.Config {
clusterNick := "cluster"
userNick := "user"
contextNick := "context"
config := clientcmdapi.NewConfig()
credentials := clientcmdapi.NewAuthInfo()
credentials.Token = clientCfg.BearerToken
credentials.ClientCertificate = clientCfg.TLSClientConfig.CertFile
if len(credentials.ClientCertificate) == 0 {
credentials.ClientCertificateData = clientCfg.TLSClientConfig.CertData
}
credentials.ClientKey = clientCfg.TLSClientConfig.KeyFile
if len(credentials.ClientKey) == 0 {
credentials.ClientKeyData = clientCfg.TLSClientConfig.KeyData
}
config.AuthInfos[userNick] = credentials
cluster := clientcmdapi.NewCluster()
cluster.Server = clientCfg.Host
cluster.CertificateAuthority = clientCfg.CAFile
if len(cluster.CertificateAuthority) == 0 {
cluster.CertificateAuthorityData = clientCfg.CAData
}
cluster.InsecureSkipTLSVerify = clientCfg.Insecure
config.Clusters[clusterNick] = cluster
context := clientcmdapi.NewContext()
context.Cluster = clusterNick
context.AuthInfo = userNick
config.Contexts[contextNick] = context
config.CurrentContext = contextNick
return config
}
func readResponse(client rest.Interface, location string) ([]byte, error) {
return client.Get().AbsPath(location).DoRaw()
}
func testAPIGroupList(t *testing.T, client rest.Interface) {
contents, err := readResponse(client, "/apis")
if err != nil {
t.Fatalf("%v", err)
}
t.Log(string(contents))
var apiGroupList metav1.APIGroupList
err = json.Unmarshal(contents, &apiGroupList)
if err != nil {
t.Fatalf("Error in unmarshalling response from server %s: %v", "/apis", err)
}
assert.Equal(t, 1, len(apiGroupList.Groups))
assert.Equal(t, wardlev1alpha1.GroupName, apiGroupList.Groups[0].Name)
assert.Equal(t, 2, len(apiGroupList.Groups[0].Versions))
v1alpha1 := metav1.GroupVersionForDiscovery{
GroupVersion: wardlev1alpha1.SchemeGroupVersion.String(),
Version: wardlev1alpha1.SchemeGroupVersion.Version,
}
v1beta1 := metav1.GroupVersionForDiscovery{
GroupVersion: wardlev1beta1.SchemeGroupVersion.String(),
Version: wardlev1beta1.SchemeGroupVersion.Version,
}
assert.Equal(t, v1beta1, apiGroupList.Groups[0].Versions[0])
assert.Equal(t, v1alpha1, apiGroupList.Groups[0].Versions[1])
assert.Equal(t, v1beta1, apiGroupList.Groups[0].PreferredVersion)
}
func testAPIGroup(t *testing.T, client rest.Interface) {
contents, err := readResponse(client, "/apis/wardle.k8s.io")
if err != nil {
t.Fatalf("%v", err)
}
t.Log(string(contents))
var apiGroup metav1.APIGroup
err = json.Unmarshal(contents, &apiGroup)
if err != nil {
t.Fatalf("Error in unmarshalling response from server %s: %v", "/apis/wardle.k8s.io", err)
}
assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.Group, apiGroup.Name)
assert.Equal(t, 2, len(apiGroup.Versions))
assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.String(), apiGroup.Versions[1].GroupVersion)
assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.Version, apiGroup.Versions[1].Version)
assert.Equal(t, apiGroup.PreferredVersion, apiGroup.Versions[0])
}
func testAPIResourceList(t *testing.T, client rest.Interface) {
contents, err := readResponse(client, "/apis/wardle.k8s.io/v1alpha1")
if err != nil {
t.Fatalf("%v", err)
}
t.Log(string(contents))
var apiResourceList metav1.APIResourceList
err = json.Unmarshal(contents, &apiResourceList)
if err != nil {
t.Fatalf("Error in unmarshalling response from server %s: %v", "/apis/wardle.k8s.io/v1alpha1", err)
}
assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.String(), apiResourceList.GroupVersion)
assert.Equal(t, 2, len(apiResourceList.APIResources))
assert.Equal(t, "fischers", apiResourceList.APIResources[0].Name)
assert.False(t, apiResourceList.APIResources[0].Namespaced)
assert.Equal(t, "flunders", apiResourceList.APIResources[1].Name)
assert.True(t, apiResourceList.APIResources[1].Namespaced)
}

View File

@@ -0,0 +1,27 @@
/*
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 apiserver
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,168 @@
/*
Copyright 2018 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 apiserver
import (
"io/ioutil"
"net"
"net/http"
"os"
"path"
"testing"
"time"
"github.com/pborman/uuid"
"k8s.io/apimachinery/pkg/util/wait"
genericapiserver "k8s.io/apiserver/pkg/server"
genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
client "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/cert"
"k8s.io/kubernetes/cmd/kube-apiserver/app"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/test/integration/framework"
)
type TestServerSetup struct {
ModifyServerRunOptions func(*options.ServerRunOptions)
ModifyServerConfig func(*master.Config)
}
// startTestServer runs a kube-apiserver, optionally calling out to the setup.ModifyServerRunOptions and setup.ModifyServerConfig functions
func startTestServer(t *testing.T, stopCh <-chan struct{}, setup TestServerSetup) (client.Interface, *rest.Config) {
certDir, _ := ioutil.TempDir("", "test-integration-"+t.Name())
go func() {
<-stopCh
os.RemoveAll(certDir)
}()
_, defaultServiceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24")
proxySigningKey, err := cert.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
proxySigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "front-proxy-ca"}, proxySigningKey)
if err != nil {
t.Fatal(err)
}
proxyCACertFile, _ := ioutil.TempFile(certDir, "proxy-ca.crt")
if err := ioutil.WriteFile(proxyCACertFile.Name(), cert.EncodeCertPEM(proxySigningCert), 0644); err != nil {
t.Fatal(err)
}
clientSigningKey, err := cert.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
clientSigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "client-ca"}, clientSigningKey)
if err != nil {
t.Fatal(err)
}
clientCACertFile, _ := ioutil.TempFile(certDir, "client-ca.crt")
if err := ioutil.WriteFile(clientCACertFile.Name(), cert.EncodeCertPEM(clientSigningCert), 0644); err != nil {
t.Fatal(err)
}
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
kubeAPIServerOptions := options.NewServerRunOptions()
kubeAPIServerOptions.SecureServing.Listener = listener
kubeAPIServerOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
kubeAPIServerOptions.SecureServing.ServerCert.CertDirectory = certDir
kubeAPIServerOptions.InsecureServing.BindPort = 0
kubeAPIServerOptions.Etcd.StorageConfig.Prefix = path.Join("/", uuid.New(), "registry")
kubeAPIServerOptions.Etcd.StorageConfig.ServerList = []string{framework.GetEtcdURL()}
kubeAPIServerOptions.ServiceClusterIPRange = *defaultServiceClusterIPRange
kubeAPIServerOptions.Authentication.RequestHeader.UsernameHeaders = []string{"X-Remote-User"}
kubeAPIServerOptions.Authentication.RequestHeader.GroupHeaders = []string{"X-Remote-Group"}
kubeAPIServerOptions.Authentication.RequestHeader.ExtraHeaderPrefixes = []string{"X-Remote-Extra-"}
kubeAPIServerOptions.Authentication.RequestHeader.AllowedNames = []string{"kube-aggregator"}
kubeAPIServerOptions.Authentication.RequestHeader.ClientCAFile = proxyCACertFile.Name()
kubeAPIServerOptions.Authentication.ClientCert.ClientCA = clientCACertFile.Name()
kubeAPIServerOptions.Authorization.Modes = []string{"Node", "RBAC"}
if setup.ModifyServerRunOptions != nil {
setup.ModifyServerRunOptions(kubeAPIServerOptions)
}
completedOptions, err := app.Complete(kubeAPIServerOptions)
if err != nil {
t.Fatal(err)
}
tunneler, proxyTransport, err := app.CreateNodeDialer(completedOptions)
if err != nil {
t.Fatal(err)
}
kubeAPIServerConfig, sharedInformers, versionedInformers, _, _, _, admissionPostStartHook, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport)
if err != nil {
t.Fatal(err)
}
if setup.ModifyServerConfig != nil {
setup.ModifyServerConfig(kubeAPIServerConfig)
}
kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), sharedInformers, versionedInformers, admissionPostStartHook)
if err != nil {
t.Fatal(err)
}
go func() {
if err := kubeAPIServer.GenericAPIServer.PrepareRun().Run(stopCh); err != nil {
t.Fatal(err)
}
}()
// Adjust the loopback config for external use (external server name and CA)
kubeAPIServerClientConfig := rest.CopyConfig(kubeAPIServerConfig.GenericConfig.LoopbackClientConfig)
kubeAPIServerClientConfig.CAFile = path.Join(certDir, "apiserver.crt")
kubeAPIServerClientConfig.CAData = nil
kubeAPIServerClientConfig.ServerName = ""
// wait for health
err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
healthzConfig := rest.CopyConfig(kubeAPIServerClientConfig)
healthzConfig.ContentType = ""
healthzConfig.AcceptContentTypes = ""
kubeClient, err := client.NewForConfig(healthzConfig)
if err != nil {
// this happens because we race the API server start
t.Log(err)
return false, nil
}
healthStatus := 0
kubeClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
if healthStatus != http.StatusOK {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatal(err)
}
kubeAPIServerClient, err := client.NewForConfig(kubeAPIServerClientConfig)
if err != nil {
t.Fatal(err)
}
return kubeAPIServerClient, kubeAPIServerClientConfig
}

View File

@@ -0,0 +1,118 @@
/*
Copyright 2018 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 apiserver
import (
"sync/atomic"
"testing"
"time"
admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/pkg/master/reconcilers"
)
func TestWebhookLoopback(t *testing.T) {
stopCh := make(chan struct{})
defer close(stopCh)
webhookPath := "/webhook-test"
called := int32(0)
client, _ := startTestServer(t, stopCh, TestServerSetup{
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
},
ModifyServerConfig: func(config *master.Config) {
// Avoid resolveable kubernetes service
config.ExtraConfig.EndpointReconcilerType = reconcilers.NoneEndpointReconcilerType
// Hook into audit to watch requests
config.GenericConfig.AuditBackend = auditSinkFunc(func(events ...*auditinternal.Event) {})
config.GenericConfig.AuditPolicyChecker = auditChecker(func(attrs authorizer.Attributes) (auditinternal.Level, []auditinternal.Stage) {
if attrs.GetPath() == webhookPath {
if attrs.GetUser().GetName() != "system:apiserver" {
t.Errorf("expected user %q, got %q", "system:apiserver", attrs.GetUser().GetName())
}
atomic.AddInt32(&called, 1)
}
return auditinternal.LevelNone, nil
})
},
})
fail := admissionv1beta1.Fail
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhooktest.example.com"},
Webhooks: []admissionv1beta1.Webhook{{
Name: "webhooktest.example.com",
ClientConfig: admissionv1beta1.WebhookClientConfig{
Service: &admissionv1beta1.ServiceReference{Namespace: "default", Name: "kubernetes", Path: &webhookPath},
},
Rules: []admissionv1beta1.RuleWithOperations{{
Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
Rule: admissionv1beta1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"configmaps"}},
}},
FailurePolicy: &fail,
}},
})
if err != nil {
t.Fatal(err)
}
err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (done bool, err error) {
_, err = client.CoreV1().ConfigMaps("default").Create(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "webhook-test"},
Data: map[string]string{"invalid key": "value"},
})
if err == nil {
t.Fatal("Unexpected success")
}
if called > 0 {
return true, nil
}
t.Logf("%v", err)
t.Logf("webhook not called yet, continuing...")
return false, nil
})
if err != nil {
t.Fatal(err)
}
}
type auditChecker func(authorizer.Attributes) (auditinternal.Level, []auditinternal.Stage)
func (f auditChecker) LevelAndStages(attrs authorizer.Attributes) (auditinternal.Level, []auditinternal.Stage) {
return f(attrs)
}
type auditSinkFunc func(events ...*auditinternal.Event)
func (f auditSinkFunc) ProcessEvents(events ...*auditinternal.Event) {
f(events...)
}
func (auditSinkFunc) Run(stopCh <-chan struct{}) error {
return nil
}
func (auditSinkFunc) Shutdown() {
}

View File

@@ -0,0 +1,78 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"etcd.go",
"master_utils.go",
"perf_utils.go",
"serializer.go",
"util.go",
],
data = [
"@com_coreos_etcd//:etcd",
],
importpath = "k8s.io/kubernetes/test/integration/framework",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/policy/v1beta1:go_default_library",
"//pkg/generated/openapi:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//pkg/master:go_default_library",
"//pkg/util/env:go_default_library",
"//pkg/version:go_default_library",
"//test/e2e/framework:go_default_library",
"//test/utils:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1alpha1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/request/union:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/union:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/openapi:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,109 @@
/*
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"
"hash/adler32"
"io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"sync"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/env"
)
var (
etcdSetup sync.Once
etcdURL = ""
)
func setupETCD() {
etcdSetup.Do(func() {
if os.Getenv("RUNFILES_DIR") == "" {
etcdURL = env.GetEnvAsStringOrFallback("KUBE_INTEGRATION_ETCD_URL", "http://127.0.0.1:2379")
return
}
etcdPath := filepath.Join(os.Getenv("RUNFILES_DIR"), "com_coreos_etcd/etcd")
// give every test the same random port each run
etcdPort := 20000 + rand.New(rand.NewSource(int64(adler32.Checksum([]byte(os.Args[0]))))).Intn(5000)
etcdURL = fmt.Sprintf("http://127.0.0.1:%d", etcdPort)
info, err := os.Stat(etcdPath)
if err != nil {
glog.Fatalf("Unable to stat etcd: %v", err)
}
if info.IsDir() {
glog.Fatalf("Did not expect %q to be a directory", etcdPath)
}
etcdDataDir, err := ioutil.TempDir(os.TempDir(), "integration_test_etcd_data")
if err != nil {
glog.Fatalf("Unable to make temp etcd data dir: %v", err)
}
glog.Infof("storing etcd data in: %v", etcdDataDir)
etcdCmd := exec.Command(
etcdPath,
"--data-dir",
etcdDataDir,
"--listen-client-urls",
GetEtcdURL(),
"--advertise-client-urls",
GetEtcdURL(),
"--listen-peer-urls",
"http://127.0.0.1:0",
)
stdout, err := etcdCmd.StdoutPipe()
if err != nil {
glog.Fatalf("Failed to run etcd: %v", err)
}
stderr, err := etcdCmd.StderrPipe()
if err != nil {
glog.Fatalf("Failed to run etcd: %v", err)
}
if err := etcdCmd.Start(); err != nil {
glog.Fatalf("Failed to run etcd: %v", err)
}
go io.Copy(os.Stdout, stdout)
go io.Copy(os.Stderr, stderr)
go func() {
if err := etcdCmd.Wait(); err != nil {
glog.Fatalf("Failed to run etcd: %v", err)
}
glog.Fatalf("etcd should not have succeeded")
}()
})
}
func EtcdMain(tests func() int) {
setupETCD()
os.Exit(tests())
}
// return the EtcdURL
func GetEtcdURL() string {
return etcdURL
}

View File

@@ -0,0 +1,331 @@
/*
Copyright 2014 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 (
"net"
"net/http"
"net/http/httptest"
"path"
"time"
"github.com/go-openapi/spec"
"github.com/golang/glog"
"github.com/pborman/uuid"
apps "k8s.io/api/apps/v1beta1"
autoscaling "k8s.io/api/autoscaling/v1"
certificates "k8s.io/api/certificates/v1beta1"
"k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
rbac "k8s.io/api/rbac/v1alpha1"
storage "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
authauthenticator "k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/batch"
policy "k8s.io/kubernetes/pkg/apis/policy/v1beta1"
"k8s.io/kubernetes/pkg/generated/openapi"
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/pkg/version"
)
// Config is a struct of configuration directives for NewMasterComponents.
type Config struct {
// If nil, a default is used, partially filled configs will not get populated.
MasterConfig *master.Config
StartReplicationManager bool
// Client throttling qps
QPS float32
// Client burst qps, also burst replicas allowed in rc manager
Burst int
// TODO: Add configs for endpoints controller, scheduler etc
}
// alwaysAllow always allows an action
type alwaysAllow struct{}
func (alwaysAllow) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
return authorizer.DecisionAllow, "always allow", nil
}
// alwaysEmpty simulates "no authentication" for old tests
func alwaysEmpty(req *http.Request) (user.Info, bool, error) {
return &user.DefaultInfo{
Name: "",
}, true, nil
}
// MasterReceiver can be used to provide the master to a custom incoming server function
type MasterReceiver interface {
SetMaster(m *master.Master)
}
// MasterHolder implements
type MasterHolder struct {
Initialized chan struct{}
M *master.Master
}
func (h *MasterHolder) SetMaster(m *master.Master) {
h.M = m
close(h.Initialized)
}
// startMasterOrDie starts a kubernetes master and an httpserver to handle api requests
func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Server, masterReceiver MasterReceiver) (*master.Master, *httptest.Server, CloseFunc) {
var m *master.Master
var s *httptest.Server
if incomingServer != nil {
s = incomingServer
} else {
s = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
m.GenericAPIServer.Handler.ServeHTTP(w, req)
}))
}
stopCh := make(chan struct{})
closeFn := func() {
close(stopCh)
s.Close()
}
if masterConfig == nil {
masterConfig = NewMasterConfig()
masterConfig.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme))
masterConfig.GenericConfig.OpenAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",
Version: "unversioned",
},
}
masterConfig.GenericConfig.OpenAPIConfig.DefaultResponse = &spec.Response{
ResponseProps: spec.ResponseProps{
Description: "Default Response.",
},
}
masterConfig.GenericConfig.OpenAPIConfig.GetDefinitions = openapi.GetOpenAPIDefinitions
masterConfig.GenericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()
}
// set the loopback client config
if masterConfig.GenericConfig.LoopbackClientConfig == nil {
masterConfig.GenericConfig.LoopbackClientConfig = &restclient.Config{QPS: 50, Burst: 100, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
}
masterConfig.GenericConfig.LoopbackClientConfig.Host = s.URL
privilegedLoopbackToken := uuid.NewRandom().String()
// wrap any available authorizer
tokens := make(map[string]*user.DefaultInfo)
tokens[privilegedLoopbackToken] = &user.DefaultInfo{
Name: user.APIServerUser,
UID: uuid.NewRandom().String(),
Groups: []string{user.SystemPrivilegedGroup},
}
tokenAuthenticator := authenticatorfactory.NewFromTokens(tokens)
if masterConfig.GenericConfig.Authentication.Authenticator == nil {
masterConfig.GenericConfig.Authentication.Authenticator = authenticatorunion.New(tokenAuthenticator, authauthenticator.RequestFunc(alwaysEmpty))
} else {
masterConfig.GenericConfig.Authentication.Authenticator = authenticatorunion.New(tokenAuthenticator, masterConfig.GenericConfig.Authentication.Authenticator)
}
if masterConfig.GenericConfig.Authorization.Authorizer != nil {
tokenAuthorizer := authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup)
masterConfig.GenericConfig.Authorization.Authorizer = authorizerunion.New(tokenAuthorizer, masterConfig.GenericConfig.Authorization.Authorizer)
} else {
masterConfig.GenericConfig.Authorization.Authorizer = alwaysAllow{}
}
masterConfig.GenericConfig.LoopbackClientConfig.BearerToken = privilegedLoopbackToken
clientset, err := clientset.NewForConfig(masterConfig.GenericConfig.LoopbackClientConfig)
if err != nil {
glog.Fatal(err)
}
sharedInformers := informers.NewSharedInformerFactory(clientset, masterConfig.GenericConfig.LoopbackClientConfig.Timeout)
m, err = masterConfig.Complete(sharedInformers).New(genericapiserver.NewEmptyDelegate())
if err != nil {
closeFn()
glog.Fatalf("error in bringing up the master: %v", err)
}
if masterReceiver != nil {
masterReceiver.SetMaster(m)
}
// TODO have this start method actually use the normal start sequence for the API server
// this method never actually calls the `Run` method for the API server
// fire the post hooks ourselves
m.GenericAPIServer.PrepareRun()
m.GenericAPIServer.RunPostStartHooks(stopCh)
cfg := *masterConfig.GenericConfig.LoopbackClientConfig
cfg.ContentConfig.GroupVersion = &schema.GroupVersion{}
privilegedClient, err := restclient.RESTClientFor(&cfg)
if err != nil {
closeFn()
glog.Fatal(err)
}
var lastHealthContent []byte
err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
result := privilegedClient.Get().AbsPath("/healthz").Do()
status := 0
result.StatusCode(&status)
if status == 200 {
return true, nil
}
lastHealthContent, _ = result.Raw()
return false, nil
})
if err != nil {
closeFn()
glog.Errorf("last health content: %q", string(lastHealthContent))
glog.Fatal(err)
}
return m, s, closeFn
}
// Returns the master config appropriate for most integration tests.
func NewIntegrationTestMasterConfig() *master.Config {
masterConfig := NewMasterConfig()
masterConfig.GenericConfig.PublicAddress = net.ParseIP("192.168.10.4")
masterConfig.ExtraConfig.APIResourceConfigSource = master.DefaultAPIResourceConfigSource()
return masterConfig
}
// Returns a basic master config.
func NewMasterConfig() *master.Config {
// This causes the integration tests to exercise the etcd
// prefix code, so please don't change without ensuring
// sufficient coverage in other ways.
etcdOptions := options.NewEtcdOptions(storagebackend.NewDefaultConfig(uuid.New(), nil))
etcdOptions.StorageConfig.ServerList = []string{GetEtcdURL()}
info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
ns := NewSingleContentTypeSerializer(legacyscheme.Scheme, info)
resourceEncoding := serverstorage.NewDefaultResourceEncodingConfig(legacyscheme.Scheme)
// FIXME (soltysh): this GroupVersionResource override should be configurable
// we need to set both for the whole group and for cronjobs, separately
resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: batch.GroupName, Resource: "cronjobs"}, schema.GroupVersion{Group: batch.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
// we also need to set both for the storage group and for volumeattachments, separately
resourceEncoding.SetVersionEncoding(storage.GroupName, *testapi.Storage.GroupVersion(), schema.GroupVersion{Group: storage.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: storage.GroupName, Resource: "volumeattachments"}, schema.GroupVersion{Group: storage.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: storage.GroupName, Version: runtime.APIVersionInternal})
storageFactory := serverstorage.NewDefaultStorageFactory(etcdOptions.StorageConfig, runtime.ContentTypeJSON, ns, resourceEncoding, master.DefaultAPIResourceConfigSource(), nil)
storageFactory.SetSerializer(
schema.GroupResource{Group: v1.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: autoscaling.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: batch.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: apps.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: extensions.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: policy.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: rbac.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: certificates.GroupName, Resource: serverstorage.AllResources},
"",
ns)
storageFactory.SetSerializer(
schema.GroupResource{Group: storage.GroupName, Resource: serverstorage.AllResources},
"",
ns)
genericConfig := genericapiserver.NewConfig(legacyscheme.Codecs)
kubeVersion := version.Get()
genericConfig.Version = &kubeVersion
genericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
err := etcdOptions.ApplyWithStorageFactoryTo(storageFactory, genericConfig)
if err != nil {
panic(err)
}
return &master.Config{
GenericConfig: genericConfig,
ExtraConfig: master.ExtraConfig{
APIResourceConfigSource: master.DefaultAPIResourceConfigSource(),
StorageFactory: storageFactory,
KubeletClientConfig: kubeletclient.KubeletClientConfig{Port: 10250},
APIServerServicePort: 443,
MasterCount: 1,
},
}
}
// CloseFunc can be called to cleanup the master
type CloseFunc func()
func RunAMaster(masterConfig *master.Config) (*master.Master, *httptest.Server, CloseFunc) {
if masterConfig == nil {
masterConfig = NewMasterConfig()
masterConfig.GenericConfig.EnableProfiling = true
}
return startMasterOrDie(masterConfig, nil, nil)
}
func RunAMasterUsingServer(masterConfig *master.Config, s *httptest.Server, masterReceiver MasterReceiver) (*master.Master, *httptest.Server, CloseFunc) {
return startMasterOrDie(masterConfig, s, masterReceiver)
}
// SharedEtcd creates a storage config for a shared etcd instance, with a unique prefix.
func SharedEtcd() *storagebackend.Config {
cfg := storagebackend.NewDefaultConfig(path.Join(uuid.New(), "registry"), nil)
cfg.ServerList = []string{GetEtcdURL()}
return cfg
}

View File

@@ -0,0 +1,107 @@
/*
Copyright 2016 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 (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
e2eframework "k8s.io/kubernetes/test/e2e/framework"
testutils "k8s.io/kubernetes/test/utils"
"github.com/golang/glog"
)
const (
retries = 5
)
type IntegrationTestNodePreparer struct {
client clientset.Interface
countToStrategy []testutils.CountToStrategy
nodeNamePrefix string
}
func NewIntegrationTestNodePreparer(client clientset.Interface, countToStrategy []testutils.CountToStrategy, nodeNamePrefix string) testutils.TestNodePreparer {
return &IntegrationTestNodePreparer{
client: client,
countToStrategy: countToStrategy,
nodeNamePrefix: nodeNamePrefix,
}
}
func (p *IntegrationTestNodePreparer) PrepareNodes() error {
numNodes := 0
for _, v := range p.countToStrategy {
numNodes += v.Count
}
glog.Infof("Making %d nodes", numNodes)
baseNode := &v1.Node{
ObjectMeta: metav1.ObjectMeta{
GenerateName: p.nodeNamePrefix,
},
Status: v1.NodeStatus{
Capacity: v1.ResourceList{
v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
v1.ResourceCPU: resource.MustParse("4"),
v1.ResourceMemory: resource.MustParse("32Gi"),
},
Phase: v1.NodeRunning,
Conditions: []v1.NodeCondition{
{Type: v1.NodeReady, Status: v1.ConditionTrue},
},
},
}
for i := 0; i < numNodes; i++ {
var err error
for retry := 0; retry < retries; retry++ {
_, err = p.client.CoreV1().Nodes().Create(baseNode)
if err == nil || !testutils.IsRetryableAPIError(err) {
break
}
}
if err != nil {
glog.Fatalf("Error creating node: %v", err)
}
}
nodes := e2eframework.GetReadySchedulableNodesOrDie(p.client)
index := 0
sum := 0
for _, v := range p.countToStrategy {
sum += v.Count
for ; index < sum; index++ {
if err := testutils.DoPrepareNode(p.client, &nodes.Items[index], v.Strategy); err != nil {
glog.Errorf("Aborting node preparation: %v", err)
return err
}
}
}
return nil
}
func (p *IntegrationTestNodePreparer) CleanupNodes() error {
nodes := e2eframework.GetReadySchedulableNodesOrDie(p.client)
for i := range nodes.Items {
if err := p.client.CoreV1().Nodes().Delete(nodes.Items[i].Name, &metav1.DeleteOptions{}); err != nil {
glog.Errorf("Error while deleting Node: %v", err)
}
}
return nil
}

View File

@@ -0,0 +1,53 @@
/*
Copyright 2016 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 (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
)
// NewSingleContentTypeSerializer wraps a serializer in a NegotiatedSerializer that handles one content type
func NewSingleContentTypeSerializer(scheme *runtime.Scheme, info runtime.SerializerInfo) runtime.StorageSerializer {
return &wrappedSerializer{
scheme: scheme,
info: info,
}
}
type wrappedSerializer struct {
scheme *runtime.Scheme
info runtime.SerializerInfo
}
var _ runtime.StorageSerializer = &wrappedSerializer{}
func (s *wrappedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{s.info}
}
func (s *wrappedSerializer) UniversalDeserializer() runtime.Decoder {
return s.info.Serializer
}
func (s *wrappedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return versioning.NewCodec(encoder, nil, s.scheme, s.scheme, s.scheme, s.scheme, gv, nil)
}
func (s *wrappedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return versioning.NewCodec(nil, decoder, s.scheme, s.scheme, s.scheme, s.scheme, nil, gv)
}

View File

@@ -0,0 +1,50 @@
/*
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.
*/
// TODO: This file can potentially be moved to a common place used by both e2e and integration tests.
package framework
import (
"net/http/httptest"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
// When these values are updated, also update cmd/kubelet/app/options/container_runtime.go
// A copy of these values exist in test/utils/image/manifest.go
currentPodInfraContainerImageName = "k8s.gcr.io/pause"
currentPodInfraContainerImageVersion = "3.1"
)
func CreateTestingNamespace(baseName string, apiserver *httptest.Server, t *testing.T) *v1.Namespace {
// TODO: Create a namespace with a given basename.
// Currently we neither create the namespace nor delete all of its contents at the end.
// But as long as tests are not using the same namespaces, this should work fine.
return &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
// TODO: Once we start creating namespaces, switch to GenerateName.
Name: baseName,
},
}
}
func DeleteTestingNamespace(ns *v1.Namespace, apiserver *httptest.Server, t *testing.T) {
// TODO: Remove all resources from a given namespace once we implement CreateTestingNamespace.
}

View File

@@ -0,0 +1,51 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
go_test(
name = "go_default_test",
size = "large",
srcs = [
"cluster_scoped_owner_test.go",
"garbage_collector_test.go",
"main_test.go",
],
tags = ["integration"],
deps = [
"//cmd/kube-apiserver/app/testing:go_default_library",
"//pkg/controller/garbagecollector:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/test/integration/testserver:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
"//vendor/k8s.io/client-go/discovery/cached:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/restmapper:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,147 @@
/*
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 garbagecollector
import (
"io"
"net/http"
"strings"
"testing"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/framework"
)
type roundTripFunc func(req *http.Request) (*http.Response, error)
func (w roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return w(req)
}
type readDelayer struct {
delay time.Duration
io.ReadCloser
}
func (b *readDelayer) Read(p []byte) (n int, err error) {
defer time.Sleep(b.delay)
return b.ReadCloser.Read(p)
}
func TestClusterScopedOwners(t *testing.T) {
// Start the test server and wrap the client to delay PV watch responses
server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
server.ClientConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return roundTripFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.Query().Get("watch") != "true" || !strings.Contains(req.URL.String(), "persistentvolumes") {
return rt.RoundTrip(req)
}
resp, err := rt.RoundTrip(req)
if err != nil {
return resp, err
}
resp.Body = &readDelayer{30 * time.Second, resp.Body}
return resp, err
})
}
ctx := setupWithServer(t, server, 5)
defer ctx.tearDown()
_, clientSet := ctx.gc, ctx.clientSet
ns := createNamespaceOrDie("gc-cluster-scope-deletion", clientSet, t)
defer deleteNamespaceOrDie(ns.Name, clientSet, t)
t.Log("Create a pair of objects")
pv, err := clientSet.CoreV1().PersistentVolumes().Create(&v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "pv-valid"},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/foo"}},
Capacity: v1.ResourceList{v1.ResourceStorage: resource.MustParse("1Gi")},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
},
})
if err != nil {
t.Fatal(err)
}
if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Create(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "cm-valid",
OwnerReferences: []metav1.OwnerReference{{Kind: "PersistentVolume", APIVersion: "v1", Name: pv.Name, UID: pv.UID}},
},
}); err != nil {
t.Fatal(err)
}
t.Log("Create a namespaced object with a missing parent")
if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Create(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "cm-missing",
Labels: map[string]string{"missing": "true"},
OwnerReferences: []metav1.OwnerReference{{Kind: "PersistentVolume", APIVersion: "v1", Name: "missing-name", UID: types.UID("missing-uid")}},
},
}); err != nil {
t.Fatal(err)
}
t.Log("Create a namespaced object with a missing type parent")
if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Create(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "cm-invalid",
OwnerReferences: []metav1.OwnerReference{{Kind: "UnknownType", APIVersion: "unknown.group/v1", Name: "invalid-name", UID: types.UID("invalid-uid")}},
},
}); err != nil {
t.Fatal(err)
}
// wait for deletable children to go away
if err := wait.Poll(5*time.Second, 300*time.Second, func() (bool, error) {
_, err := clientSet.CoreV1().ConfigMaps(ns.Name).Get("cm-missing", metav1.GetOptions{})
switch {
case errors.IsNotFound(err):
return true, nil
case err != nil:
return false, err
default:
t.Logf("cm with missing parent still exists, retrying")
return false, nil
}
}); err != nil {
t.Fatal(err)
}
t.Logf("deletable children removed")
// Give time for blocked children to be incorrectly cleaned up
time.Sleep(5 * time.Second)
// ensure children with unverifiable parents don't get reaped
if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Get("cm-invalid", metav1.GetOptions{}); err != nil {
t.Fatalf("child with invalid ownerRef is unexpectedly missing: %v", err)
}
// ensure children with present parents don't get reaped
if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Get("cm-valid", metav1.GetOptions{}); err != nil {
t.Fatalf("child with valid ownerRef is unexpectedly missing: %v", err)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
/*
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 garbagecollector
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,70 @@
package(default_visibility = ["//visibility:public"])
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_test(
name = "go_default_test",
size = "large",
srcs = [
"ipam_test.go",
"main_test.go",
],
embed = [":go_default_library"],
tags = ["integration"],
deps = [
"//pkg/controller/nodeipam:go_default_library",
"//pkg/controller/nodeipam/ipam:go_default_library",
"//test/integration/framework:go_default_library",
"//test/integration/util:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_library(
name = "go_default_library",
srcs = [
"cloud.go",
"results.go",
"util.go",
],
importpath = "k8s.io/kubernetes/test/integration/ipamperf",
deps = [
"//pkg/cloudprovider:go_default_library",
"//pkg/cloudprovider/providers/gce/cloud:go_default_library",
"//pkg/cloudprovider/providers/gce/cloud/meta:go_default_library",
"//pkg/controller/nodeipam/ipam:go_default_library",
"//pkg/controller/nodeipam/ipam/cidrset:go_default_library",
"//pkg/controller/util/node:go_default_library",
"//test/integration/util:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/google.golang.org/api/compute/v0.beta:go_default_library",
"//vendor/google.golang.org/api/compute/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)

View File

@@ -0,0 +1,85 @@
IPAM Performance Test
=====
Motivation
-----
We wanted to be able to test the behavior of the IPAM controller's under various scenarios,
by mocking and monitoring the edges that the controller interacts with. This has the following goals:
- Save time on testing
- To simulate various behaviors cheaply
- To observe and model the ideal behavior of the IPAM controller code
Currently the test runs through the 4 different IPAM controller modes for cases where the kube API QPS is a)
equal to and b) significantly less than the number of nodes being added to observe and quantify behavior.
How to run
-------
```shell
# In kubernetes root path
make generated_files
cd test/integration/ipamperf
./test-performance.sh
```
The runner scripts support a few different options:
```shell
./test-performance.sh -h
usage: ./test-performance.sh [-h] [-d] [-r <pattern>] [-o <filename>]
usage: ./test-performance.sh <options>
-h display this help message
-d enable debug logs in tests
-r <pattern> regex pattern to match for tests
-o <filename> file to write JSON formatted results to
-p <id> enable cpu and memory profiles, output written to mem-<id>.out and cpu-<id>.out
-c enable custom test configuration
-a <name> allocator name, one of RangeAllocator, CloudAllocator, IPAMFromCluster, IPAMFromCloud
-k <num> api server qps for allocator
-n <num> number of nodes to simulate
-m <num> api server qps for node creation
-l <num> gce cloud endpoint qps
```
The tests follow the pattern TestPerformance/{AllocatorType}-KubeQPS{X}-Nodes{Y}, where AllocatorType
is one of
- RangeAllocator
- IPAMFromCluster
- CloudAllocator
- IPAMFromCloud
and X represents the QPS configured for the kubernetes API client, and Y is the number of nodes to create.
The -d flags set the -v level for glog to 6, enabling nearly all of the debug logs in the code.
So to run the test for CloudAllocator with 10 nodes, one can run
```shell
./test-performance.sh -r /CloudAllocator.*Nodes10$
```
At the end of the test, a JSON format of the results for all the tests run is printed. Passing the -o option
allows for also saving this JSON to a named file.
### Profiling the code
It's possible to get the CPU and memory profiles of code during test execution by using the ```-p``` option.
The CPU and memory profiles are generated in the same directory with the file names set to ```cpu-<id>.out```
and ```cpu-<id>.out```, where ```<id>``` is the argument value. Typicall pattern is to put in the number
of nodes being simulated as the id, or 'all' in case running the full suite.
### Custom Test Configuration
It's also possible to run a custom test configuration by passing the -c option. With this option, it then
possible to specify the number of nodes to simulate and the API server qps values for creation,
IPAM allocation and cloud endpoint, along with the allocator name to run. The defaults values for the
qps parmeters are 30 for IPAM allocation, 100 for node creation and 30 for the cloud endpoint, and the
default allocator is the RangeAllocator.
Code Organization
-----
The core of the tests are defined in [ipam_test.go](ipam_test.go), using the t.Run() helper to control parallelism
as we want to able to start the master once. [cloud.go](cloud.go) contains the mock of the cloud server endpoint
and can be configured to behave differently as needed by the various modes. The tracking of the node behavior and
creation of the test results data is in [results.go](results.go).

View File

@@ -0,0 +1,154 @@
/*
Copyright 2018 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 ipamperf
import (
"context"
"net"
"sync"
beta "google.golang.org/api/compute/v0.beta"
ga "google.golang.org/api/compute/v1"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
"k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset"
"k8s.io/kubernetes/test/integration/util"
)
// implemntation note:
// ------------------
// cloud.go implements hooks and handler functions for the MockGCE cloud in order to meet expectations
// of cloud behavior from the IPAM controllers. The key constraint is that the IPAM code is spread
// across both GA and Beta instances, which are distinct objects in the mock. We need to solve for
//
// 1. When a GET is called on an instance, we lazy create the instance with or without an assigned
// ip alias as needed by the IPAM controller type
// 2. When we assign an IP alias for an instance, both the GA and Beta instance have to agree on the
// assigned alias range
//
// We solve both the problems by using a baseInstanceList which maintains a list of known instances,
// and their pre-assigned ip-alias ranges (if needed). We then create GetHook for GA and Beta GetInstance
// calls as closures over this betaInstanceList that can lookup base instance data.
//
// This has the advantage that once the Get hook pouplates the GCEMock with the base data, we then let the
// rest of the mock code run as is.
// baseInstance tracks basic instance data needed by the IPAM controllers
type baseInstance struct {
name string
zone string
aliasRange string
}
// baseInstanceList tracks a set of base instances
type baseInstanceList struct {
allocateCIDR bool
clusterCIDR *net.IPNet
subnetMaskSize int
cidrSet *cidrset.CidrSet
lock sync.Mutex // protect access to instances
instances map[meta.Key]*baseInstance
}
// toGA is an utility method to return the baseInstance data as a GA Instance object
func (bi *baseInstance) toGA() *ga.Instance {
inst := &ga.Instance{Name: bi.name, Zone: bi.zone, NetworkInterfaces: []*ga.NetworkInterface{{}}}
if bi.aliasRange != "" {
inst.NetworkInterfaces[0].AliasIpRanges = []*ga.AliasIpRange{
{IpCidrRange: bi.aliasRange, SubnetworkRangeName: util.TestSecondaryRangeName},
}
}
return inst
}
// toGA is an utility method to return the baseInstance data as a beta Instance object
func (bi *baseInstance) toBeta() *beta.Instance {
inst := &beta.Instance{Name: bi.name, Zone: bi.zone, NetworkInterfaces: []*beta.NetworkInterface{{}}}
if bi.aliasRange != "" {
inst.NetworkInterfaces[0].AliasIpRanges = []*beta.AliasIpRange{
{IpCidrRange: bi.aliasRange, SubnetworkRangeName: util.TestSecondaryRangeName},
}
}
return inst
}
// newBaseInstanceList is the baseInstanceList constructor
func newBaseInstanceList(allocateCIDR bool, clusterCIDR *net.IPNet, subnetMaskSize int) *baseInstanceList {
cidrSet, _ := cidrset.NewCIDRSet(clusterCIDR, subnetMaskSize)
return &baseInstanceList{
allocateCIDR: allocateCIDR,
clusterCIDR: clusterCIDR,
subnetMaskSize: subnetMaskSize,
cidrSet: cidrSet,
instances: make(map[meta.Key]*baseInstance),
}
}
// getOrCreateBaseInstance lazily creates a new base instance, assigning if allocateCIDR is true
func (bil *baseInstanceList) getOrCreateBaseInstance(key *meta.Key) *baseInstance {
bil.lock.Lock()
defer bil.lock.Unlock()
inst, found := bil.instances[*key]
if !found {
inst = &baseInstance{name: key.Name, zone: key.Zone}
if bil.allocateCIDR {
nextRange, _ := bil.cidrSet.AllocateNext()
inst.aliasRange = nextRange.String()
}
bil.instances[*key] = inst
}
return inst
}
// newGAGetHook creates a new closure with the current baseInstanceList to be used as a MockInstances.GetHook
func (bil *baseInstanceList) newGAGetHook() func(ctx context.Context, key *meta.Key, m *cloud.MockInstances) (bool, *ga.Instance, error) {
return func(ctx context.Context, key *meta.Key, m *cloud.MockInstances) (bool, *ga.Instance, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
if _, found := m.Objects[*key]; !found {
m.Objects[*key] = &cloud.MockInstancesObj{Obj: bil.getOrCreateBaseInstance(key).toGA()}
}
return false, nil, nil
}
}
// newBetaGetHook creates a new closure with the current baseInstanceList to be used as a MockBetaInstances.GetHook
func (bil *baseInstanceList) newBetaGetHook() func(ctx context.Context, key *meta.Key, m *cloud.MockBetaInstances) (bool, *beta.Instance, error) {
return func(ctx context.Context, key *meta.Key, m *cloud.MockBetaInstances) (bool, *beta.Instance, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
if _, found := m.Objects[*key]; !found {
m.Objects[*key] = &cloud.MockInstancesObj{Obj: bil.getOrCreateBaseInstance(key).toBeta()}
}
return false, nil, nil
}
}
// newMockCloud returns a mock GCE instance with the appropriate handlers hooks
func (bil *baseInstanceList) newMockCloud() cloud.Cloud {
c := cloud.NewMockGCE(nil)
// insert hooks to lazy create a instance when needed
c.MockInstances.GetHook = bil.newGAGetHook()
c.MockBetaInstances.GetHook = bil.newBetaGetHook()
return c
}

View File

@@ -0,0 +1,158 @@
/*
Copyright 2018 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 ipamperf
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"testing"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/controller/nodeipam"
"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
"k8s.io/kubernetes/test/integration/util"
)
func setupAllocator(apiURL string, config *Config, clusterCIDR, serviceCIDR *net.IPNet, subnetMaskSize int) (*clientset.Clientset, util.ShutdownFunc, error) {
controllerStopChan := make(chan struct{})
shutdownFunc := func() {
close(controllerStopChan)
}
clientSet := clientset.NewForConfigOrDie(&restclient.Config{
Host: apiURL,
ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}},
QPS: float32(config.KubeQPS),
Burst: config.KubeQPS,
})
sharedInformer := informers.NewSharedInformerFactory(clientSet, 1*time.Hour)
ipamController, err := nodeipam.NewNodeIpamController(
sharedInformer.Core().V1().Nodes(), config.Cloud, clientSet,
clusterCIDR, serviceCIDR, subnetMaskSize, config.AllocatorType,
)
if err != nil {
return nil, shutdownFunc, err
}
go ipamController.Run(controllerStopChan)
sharedInformer.Start(controllerStopChan)
return clientSet, shutdownFunc, nil
}
func runTest(t *testing.T, apiURL string, config *Config, clusterCIDR, serviceCIDR *net.IPNet, subnetMaskSize int) (*Results, error) {
t.Helper()
glog.Infof("Running test %s", t.Name())
defer deleteNodes(apiURL, config) // cleanup nodes on after controller shutdown
clientSet, shutdownFunc, err := setupAllocator(apiURL, config, clusterCIDR, serviceCIDR, subnetMaskSize)
if err != nil {
t.Fatalf("Error starting IPAM allocator: %v", err)
}
defer shutdownFunc()
o := NewObserver(clientSet, config.NumNodes)
if err := o.StartObserving(); err != nil {
t.Fatalf("Could not start test observer: %v", err)
}
if err := createNodes(apiURL, config); err != nil {
t.Fatalf("Could not create nodes: %v", err)
}
results := o.Results(t.Name(), config)
glog.Infof("Results: %s", results)
if !results.Succeeded {
t.Errorf("%s: Not allocations succeeded", t.Name())
}
return results, nil
}
func logResults(allResults []*Results) {
jStr, err := json.MarshalIndent(allResults, "", " ")
if err != nil {
glog.Errorf("Error formating results: %v", err)
return
}
if resultsLogFile != "" {
glog.Infof("Logging results to %s", resultsLogFile)
if err := ioutil.WriteFile(resultsLogFile, jStr, os.FileMode(0644)); err != nil {
glog.Errorf("Error logging results to %s: %v", resultsLogFile, err)
}
}
glog.Infof("AllResults:\n%s", string(jStr))
}
func TestPerformance(t *testing.T) {
if testing.Short() {
// TODO (#61854) find why flakiness is caused by etcd connectivity before enabling always
t.Skip("Skipping because we want to run short tests")
}
apiURL, masterShutdown := util.StartApiserver()
defer masterShutdown()
_, clusterCIDR, _ := net.ParseCIDR("10.96.0.0/11") // allows up to 8K nodes
_, serviceCIDR, _ := net.ParseCIDR("10.94.0.0/24") // does not matter for test - pick upto 250 services
subnetMaskSize := 24
var (
allResults []*Results
tests []*Config
)
if isCustom {
tests = append(tests, customConfig)
} else {
for _, numNodes := range []int{10, 100} {
for _, alloc := range []ipam.CIDRAllocatorType{ipam.RangeAllocatorType, ipam.CloudAllocatorType, ipam.IPAMFromClusterAllocatorType, ipam.IPAMFromCloudAllocatorType} {
tests = append(tests, &Config{AllocatorType: alloc, NumNodes: numNodes, CreateQPS: numNodes, KubeQPS: 10, CloudQPS: 10})
}
}
}
for _, test := range tests {
testName := fmt.Sprintf("%s-KubeQPS%d-Nodes%d", test.AllocatorType, test.KubeQPS, test.NumNodes)
t.Run(testName, func(t *testing.T) {
allocateCIDR := false
if test.AllocatorType == ipam.IPAMFromCloudAllocatorType || test.AllocatorType == ipam.CloudAllocatorType {
allocateCIDR = true
}
bil := newBaseInstanceList(allocateCIDR, clusterCIDR, subnetMaskSize)
cloud, err := util.NewMockGCECloud(bil.newMockCloud())
if err != nil {
t.Fatalf("Unable to create mock cloud: %v", err)
}
test.Cloud = cloud
if results, err := runTest(t, apiURL, test, clusterCIDR, serviceCIDR, subnetMaskSize); err == nil {
allResults = append(allResults, results)
}
})
}
logResults(allResults)
}

View File

@@ -0,0 +1,66 @@
/*
Copyright 2018 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 ipamperf
import (
"flag"
"testing"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
"k8s.io/kubernetes/test/integration/framework"
)
var (
resultsLogFile string
isCustom bool
customConfig = &Config{
NumNodes: 10,
KubeQPS: 30,
CloudQPS: 30,
CreateQPS: 100,
AllocatorType: ipam.RangeAllocatorType,
}
)
func TestMain(m *testing.M) {
allocator := string(ipam.RangeAllocatorType)
flag.StringVar(&resultsLogFile, "log", "", "log file to write JSON results to")
flag.BoolVar(&isCustom, "custom", false, "enable custom test configuration")
flag.StringVar(&allocator, "allocator", allocator, "allocator to use")
flag.IntVar(&customConfig.KubeQPS, "kube-qps", customConfig.KubeQPS, "API server qps for allocations")
flag.IntVar(&customConfig.NumNodes, "num-nodes", 10, "number of nodes to simulate")
flag.IntVar(&customConfig.CreateQPS, "create-qps", customConfig.CreateQPS, "API server qps for node creation")
flag.IntVar(&customConfig.CloudQPS, "cloud-qps", customConfig.CloudQPS, "GCE Cloud qps limit")
flag.Parse()
switch allocator {
case string(ipam.RangeAllocatorType):
customConfig.AllocatorType = ipam.RangeAllocatorType
case string(ipam.CloudAllocatorType):
customConfig.AllocatorType = ipam.CloudAllocatorType
case string(ipam.IPAMFromCloudAllocatorType):
customConfig.AllocatorType = ipam.IPAMFromCloudAllocatorType
case string(ipam.IPAMFromClusterAllocatorType):
customConfig.AllocatorType = ipam.IPAMFromClusterAllocatorType
default:
glog.Fatalf("Unknown allocator type: %s", allocator)
}
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,221 @@
/*
Copyright 2018 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 ipamperf
import (
"bytes"
"fmt"
"sort"
"sync"
"time"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
nodeutil "k8s.io/kubernetes/pkg/controller/util/node"
)
// Config represents the test configuration that is being run
type Config struct {
CreateQPS int // rate at which nodes are created
KubeQPS int // rate for communication with kubernetes API
CloudQPS int // rate for communication with cloud endpoint
NumNodes int // number of nodes to created and monitored
AllocatorType ipam.CIDRAllocatorType // type of allocator to run
Cloud cloudprovider.Interface // cloud provider
}
type nodeTime struct {
added time.Time // observed time for when node was added
allocated time.Time // observed time for when node was assigned podCIDR
podCIDR string // the allocated podCIDR range
}
// Observer represents the handle to test observer that watches for node changes
// and tracks behavior
type Observer struct {
numAdded int // number of nodes observed added
numAllocated int // number of nodes observed allocated podCIDR
timing map[string]*nodeTime // per node timing
numNodes int // the number of nodes to expect
stopChan chan struct{} // for the shared informer
wg sync.WaitGroup
clientSet *clientset.Clientset
}
// JSONDuration is an alias of time.Duration to support custom Marshal code
type JSONDuration time.Duration
// NodeDuration represents the CIDR allocation time for each node
type NodeDuration struct {
Name string // node name
PodCIDR string // the podCIDR that was assigned to the node
Duration JSONDuration // how long it took to assign podCIDR
}
// Results represents the observed test results.
type Results struct {
Name string // name for the test
Config *Config // handle to the test config
Succeeded bool // whether all nodes were assigned podCIDR
MaxAllocTime JSONDuration // the maximum time take for assignment per node
TotalAllocTime JSONDuration // duration between first addition and last assignment
NodeAllocTime []NodeDuration // assignment time by node name
}
// NewObserver creates a new observer given a handle to the Clientset
func NewObserver(clientSet *clientset.Clientset, numNodes int) *Observer {
o := &Observer{
timing: map[string]*nodeTime{},
numNodes: numNodes,
clientSet: clientSet,
stopChan: make(chan struct{}),
}
return o
}
// StartObserving starts an asynchronous loop to monitor for node changes.
// Call Results() to get the test results after starting observer.
func (o *Observer) StartObserving() error {
o.monitor()
glog.Infof("Test observer started")
return nil
}
// Results returns the test results. It waits for the observer to finish
// and returns the computed results of the observations.
func (o *Observer) Results(name string, config *Config) *Results {
var (
firstAdd time.Time // earliest time any node was added (first node add)
lastAssignment time.Time // latest time any node was assignged CIDR (last node assignment)
)
o.wg.Wait()
close(o.stopChan) // shutdown the shared informer
results := &Results{
Name: name,
Config: config,
Succeeded: o.numAdded == o.numNodes && o.numAllocated == o.numNodes,
MaxAllocTime: 0,
NodeAllocTime: []NodeDuration{},
}
for name, nTime := range o.timing {
addFound := !nTime.added.IsZero()
if addFound && (firstAdd.IsZero() || nTime.added.Before(firstAdd)) {
firstAdd = nTime.added
}
cidrFound := !nTime.allocated.IsZero()
if cidrFound && nTime.allocated.After(lastAssignment) {
lastAssignment = nTime.allocated
}
if addFound && cidrFound {
allocTime := nTime.allocated.Sub(nTime.added)
if allocTime > time.Duration(results.MaxAllocTime) {
results.MaxAllocTime = JSONDuration(allocTime)
}
results.NodeAllocTime = append(results.NodeAllocTime, NodeDuration{
Name: name, PodCIDR: nTime.podCIDR, Duration: JSONDuration(allocTime),
})
}
}
results.TotalAllocTime = JSONDuration(lastAssignment.Sub(firstAdd))
sort.Slice(results.NodeAllocTime, func(i, j int) bool {
return results.NodeAllocTime[i].Duration > results.NodeAllocTime[j].Duration
})
return results
}
func (o *Observer) monitor() {
o.wg.Add(1)
sharedInformer := informers.NewSharedInformerFactory(o.clientSet, 1*time.Second)
nodeInformer := sharedInformer.Core().V1().Nodes().Informer()
nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: nodeutil.CreateAddNodeHandler(func(node *v1.Node) (err error) {
name := node.GetName()
if node.Spec.PodCIDR != "" {
// ignore nodes that have PodCIDR (might be hold over from previous runs that did not get cleaned up)
return
}
nTime := &nodeTime{}
o.timing[name] = nTime
nTime.added = time.Now()
o.numAdded = o.numAdded + 1
return
}),
UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(oldNode, newNode *v1.Node) (err error) {
name := newNode.GetName()
nTime, found := o.timing[name]
if !found {
return // consistency check - ignore nodes we have not seen the add event for
}
// check if CIDR assigned and ignore redundant updates
if newNode.Spec.PodCIDR != "" && nTime.podCIDR == "" {
nTime.allocated = time.Now()
nTime.podCIDR = newNode.Spec.PodCIDR
o.numAllocated++
if o.numAllocated%10 == 0 {
glog.Infof("progress: %d/%d - %.2d%%", o.numAllocated, o.numNodes, (o.numAllocated * 100.0 / o.numNodes))
}
// do following check only if numAllocated is modified, as otherwise, redundant updates
// can cause wg.Done() to be called multiple times, causing a panic
if o.numAdded == o.numNodes && o.numAllocated == o.numNodes {
glog.Info("All nodes assigned podCIDR")
o.wg.Done()
}
}
return
}),
})
sharedInformer.Start(o.stopChan)
}
// String implements the Stringer interface and returns a multi-line representation
// of the test results.
func (results *Results) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "\n TestName: %s", results.Name)
fmt.Fprintf(&b, "\n NumNodes: %d, CreateQPS: %d, KubeQPS: %d, CloudQPS: %d, Allocator: %v",
results.Config.NumNodes, results.Config.CreateQPS, results.Config.KubeQPS,
results.Config.CloudQPS, results.Config.AllocatorType)
fmt.Fprintf(&b, "\n Succeeded: %v, TotalAllocTime: %v, MaxAllocTime: %v",
results.Succeeded, time.Duration(results.TotalAllocTime), time.Duration(results.MaxAllocTime))
fmt.Fprintf(&b, "\n %5s %-20s %-20s %s", "Num", "Node", "PodCIDR", "Duration (s)")
for i, d := range results.NodeAllocTime {
fmt.Fprintf(&b, "\n %5d %-20s %-20s %10.3f", i+1, d.Name, d.PodCIDR, time.Duration(d.Duration).Seconds())
}
return b.String()
}
// MarshalJSON implements the json.Marshaler interface
func (jDuration *JSONDuration) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", time.Duration(*jDuration).String())), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (jDuration *JSONDuration) UnmarshalJSON(b []byte) (err error) {
var d time.Duration
if d, err = time.ParseDuration(string(b[1 : len(b)-1])); err == nil {
*jDuration = JSONDuration(d)
}
return
}

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# Copyright 2018 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.
set -o errexit
set -o nounset
set -o pipefail
TEST_ARGS=""
RUN_PATTERN=".*"
PROFILE_OPTS=""
function usage() {
echo "usage: $0 <options>"
echo " -h display this help message"
echo " -d enable debug logs in tests"
echo " -r <pattern> regex pattern to match for tests"
echo " -o <filename> file to write JSON formatted results to"
echo " -p <id> enable cpu and memory profiles, output written to mem-<id>.out and cpu-<id>.out"
echo " -c enable custom test configuration"
echo " -a <name> allocator name, one of RangeAllocator, CloudAllocator, IPAMFromCluster, IPAMFromCloud"
echo " -k <num> api server qps for allocator"
echo " -n <num> number of nodes to simulate"
echo " -m <num> api server qps for node creation"
echo " -l <num> gce cloud endpoint qps"
exit 1
}
while getopts ":hdr:o:p:ca:k:n:m:l:" opt; do
case ${opt} in
d) TEST_ARGS="${TEST_ARGS} -v=6"
;;
r) RUN_PATTERN="${OPTARG}"
;;
o) TEST_ARGS="${TEST_ARGS} -log ${OPTARG}"
;;
p) PROFILE_OPTS="-memprofile mem-${OPTARG}.out -cpuprofile cpu-${OPTARG}.out"
;;
c) TEST_ARGS="${TEST_ARGS} -custom"
;;
a) TEST_ARGS="${TEST_ARGS} -allocator ${OPTARG}"
;;
k) TEST_ARGS="${TEST_ARGS} -kube-qps ${OPTARG}"
;;
n) TEST_ARGS="${TEST_ARGS} -num-nodes ${OPTARG}"
;;
m) TEST_ARGS="${TEST_ARGS} -create-qps ${OPTARG}"
;;
l) TEST_ARGS="${TEST_ARGS} -cloud-qps ${OPTARG}"
;;
h) usage
;;
\?) usage
;;
esac
done
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../../../
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::golang::setup_env
DIR_BASENAME=$(dirname "${BASH_SOURCE}")
pushd ${DIR_BASENAME}
cleanup() {
popd 2> /dev/null
kube::etcd::cleanup
kube::log::status "performance test cleanup complete"
}
trap cleanup EXIT
kube::etcd::start
# Running IPAM tests. It might take a long time.
kube::log::status "performance test (IPAM) start"
go test ${PROFILE_OPTS} -test.run=${RUN_PATTERN} -test.timeout=60m -test.short=false -v -args ${TEST_ARGS}
kube::log::status "... IPAM tests finished."

View File

@@ -0,0 +1,95 @@
/*
Copyright 2018 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 ipamperf
import (
"time"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
)
const (
maxCreateRetries = 10
retryDelay = 10 * time.Second
)
var (
baseNodeTemplate = &v1.Node{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "sample-node-",
},
Status: v1.NodeStatus{
Capacity: v1.ResourceList{
v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
v1.ResourceCPU: resource.MustParse("4"),
v1.ResourceMemory: resource.MustParse("32Gi"),
},
Phase: v1.NodeRunning,
Conditions: []v1.NodeCondition{
{Type: v1.NodeReady, Status: v1.ConditionTrue},
},
},
}
)
func deleteNodes(apiURL string, config *Config) {
glog.Info("Deleting nodes")
clientSet := clientset.NewForConfigOrDie(&restclient.Config{
Host: apiURL,
ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}},
QPS: float32(config.CreateQPS),
Burst: config.CreateQPS,
})
noGrace := int64(0)
if err := clientSet.CoreV1().Nodes().DeleteCollection(&metav1.DeleteOptions{GracePeriodSeconds: &noGrace}, metav1.ListOptions{}); err != nil {
glog.Errorf("Error deleting node: %v", err)
}
}
func createNodes(apiURL string, config *Config) error {
clientSet := clientset.NewForConfigOrDie(&restclient.Config{
Host: apiURL,
ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}},
QPS: float32(config.CreateQPS),
Burst: config.CreateQPS,
})
glog.Infof("Creating %d nodes", config.NumNodes)
for i := 0; i < config.NumNodes; i++ {
var err error
for j := 0; j < maxCreateRetries; j++ {
if _, err = clientSet.CoreV1().Nodes().Create(baseNodeTemplate); err != nil && errors.IsServerTimeout(err) {
glog.Infof("Server timeout creating nodes, retrying after %v", retryDelay)
time.Sleep(retryDelay)
continue
}
break
}
if err != nil {
glog.Errorf("Error creating nodes: %v", err)
return err
}
}
glog.Infof("%d nodes created", config.NumNodes)
return nil
}

255
vendor/k8s.io/kubernetes/test/integration/master/BUILD generated vendored Normal file
View File

@@ -0,0 +1,255 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"crd_test.go",
"kube_apiserver_test.go",
"main_test.go",
"secrets_transformation_test.go",
"synthetic_master_test.go",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:darwin": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:linux": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:nacl": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:plan9": [
"kms_transformation_test.go",
],
"@io_bazel_rules_go//go/platform:solaris": [
"kms_transformation_test.go",
],
"//conditions:default": [],
}),
embed = [":go_default_library"],
tags = ["integration"],
deps = [
"//cmd/kube-apiserver/app/testing:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/master:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/networking/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/group:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:nacl": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:plan9": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:solaris": [
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"transformation_testcase.go",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:darwin": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:linux": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:nacl": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:plan9": [
"kms_plugin_mock.go",
],
"@io_bazel_rules_go//go/platform:solaris": [
"kms_plugin_mock.go",
],
"//conditions:default": [],
}),
importpath = "k8s.io/kubernetes/test/integration/master",
deps = [
"//cmd/kube-apiserver/app/testing:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:nacl": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:plan9": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"@io_bazel_rules_go//go/platform:solaris": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/google.golang.org/grpc:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1:go_default_library",
],
"//conditions:default": [],
}),
)

View File

@@ -0,0 +1,343 @@
/*
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 master
import (
"encoding/json"
"fmt"
"testing"
"time"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
networkingv1 "k8s.io/api/networking/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestCRDShadowGroup(t *testing.T) {
result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
defer result.TearDownFn()
kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
apiextensionsclient, err := apiextensionsclientset.NewForConfig(result.ClientConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
t.Logf("Creating a NetworkPolicy")
nwPolicy, err := kubeclient.NetworkingV1().NetworkPolicies("default").Create(&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
Ingress: []networkingv1.NetworkPolicyIngressRule{},
},
})
if err != nil {
t.Fatalf("Failed to create NetworkPolicy: %v", err)
}
t.Logf("Trying to shadow networking group")
crd := &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "foos." + networkingv1.GroupName,
},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: networkingv1.GroupName,
Version: networkingv1.SchemeGroupVersion.Version,
Scope: apiextensionsv1beta1.ClusterScoped,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Plural: "foos",
Kind: "Foo",
},
},
}
if _, err = apiextensionsclient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil {
t.Fatalf("Failed to create networking group CRD: %v", err)
}
if err := waitForEstablishedCRD(apiextensionsclient, crd.Name); err != nil {
t.Fatalf("Failed to establish networking group CRD: %v", err)
}
// wait to give aggregator time to update
time.Sleep(2 * time.Second)
t.Logf("Checking that we still see the NetworkPolicy")
_, err = kubeclient.NetworkingV1().NetworkPolicies(nwPolicy.Namespace).Get(nwPolicy.Name, metav1.GetOptions{})
if err != nil {
t.Errorf("Failed to get NetworkPolocy: %v", err)
}
t.Logf("Checking that crd resource does not show up in networking group")
found, err := crdExistsInDiscovery(apiextensionsclient, crd)
if err != nil {
t.Fatalf("unexpected discovery error: %v", err)
}
if found {
t.Errorf("CRD resource shows up in discovery, but shouldn't.")
}
}
func TestCRD(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--admission-control", "Initializers"}, framework.SharedEtcd())
defer result.TearDownFn()
kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
apiextensionsclient, err := apiextensionsclientset.NewForConfig(result.ClientConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
t.Logf("Trying to create a custom resource without conflict")
crd := &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "foos.cr.bar.com",
},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: "cr.bar.com",
Version: "v1",
Scope: apiextensionsv1beta1.NamespaceScoped,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Plural: "foos",
Kind: "Foo",
},
},
}
if _, err = apiextensionsclient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil {
t.Fatalf("Failed to create foos.cr.bar.com CRD; %v", err)
}
if err := waitForEstablishedCRD(apiextensionsclient, crd.Name); err != nil {
t.Fatalf("Failed to establish foos.cr.bar.com CRD: %v", err)
}
if err := wait.PollImmediate(500*time.Millisecond, 30*time.Second, func() (bool, error) {
return crdExistsInDiscovery(apiextensionsclient, crd)
}); err != nil {
t.Fatalf("Failed to see foos.cr.bar.com in discovery: %v", err)
}
t.Logf("Trying to access foos.cr.bar.com with dynamic client")
dynamicClient, err := dynamic.NewForConfig(result.ClientConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
fooResource := schema.GroupVersionResource{Group: "cr.bar.com", Version: "v1", Resource: "foos"}
_, err = dynamicClient.Resource(fooResource).Namespace("default").List(metav1.ListOptions{})
if err != nil {
t.Errorf("Failed to list foos.cr.bar.com instances: %v", err)
}
t.Logf("Creating InitializerConfiguration")
_, err = kubeclient.AdmissionregistrationV1alpha1().InitializerConfigurations().Create(&admissionregistrationv1alpha1.InitializerConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "foos.cr.bar.com",
},
Initializers: []admissionregistrationv1alpha1.Initializer{
{
Name: "cr.bar.com",
Rules: []admissionregistrationv1alpha1.Rule{
{
APIGroups: []string{"cr.bar.com"},
APIVersions: []string{"*"},
Resources: []string{"*"},
},
},
},
},
})
if err != nil {
t.Fatalf("Failed to create InitializerConfiguration: %v", err)
}
// TODO DO NOT MERGE THIS
time.Sleep(5 * time.Second)
t.Logf("Creating Foo instance")
foo := &Foo{
TypeMeta: metav1.TypeMeta{
APIVersion: "cr.bar.com/v1",
Kind: "Foo",
},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
}
unstructuredFoo, err := unstructuredFoo(foo)
if err != nil {
t.Fatalf("Unable to create Foo: %v", err)
}
createErr := make(chan error, 1)
go func() {
_, err := dynamicClient.Resource(fooResource).Namespace("default").Create(unstructuredFoo)
t.Logf("Foo instance create returned: %v", err)
if err != nil {
createErr <- err
}
}()
err = wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
select {
case createErr := <-createErr:
return true, createErr
default:
}
t.Logf("Checking that Foo instance is visible with IncludeUninitialized=true")
_, err := dynamicClient.Resource(fooResource).Namespace("default").Get(foo.ObjectMeta.Name, metav1.GetOptions{
IncludeUninitialized: true,
})
switch {
case err == nil:
return true, nil
case errors.IsNotFound(err):
return false, nil
default:
return false, err
}
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
t.Logf("Removing initializer from Foo instance")
success := false
for i := 0; i < 10; i++ {
// would love to replace the following with a patch, but removing strings from the intitializer array
// is not what JSON (Merge) patch authors had in mind.
fooUnstructured, err := dynamicClient.Resource(fooResource).Namespace("default").Get(foo.ObjectMeta.Name, metav1.GetOptions{
IncludeUninitialized: true,
})
if err != nil {
t.Fatalf("Error getting Foo instance: %v", err)
}
bs, _ := fooUnstructured.MarshalJSON()
t.Logf("Got Foo instance: %v", string(bs))
foo := Foo{}
if err := json.Unmarshal(bs, &foo); err != nil {
t.Fatalf("Error parsing Foo instance: %v", err)
}
// remove initialize
if foo.ObjectMeta.Initializers == nil {
t.Fatalf("Expected initializers to be set in Foo instance")
}
found := false
for i := range foo.ObjectMeta.Initializers.Pending {
if foo.ObjectMeta.Initializers.Pending[i].Name == "cr.bar.com" {
foo.ObjectMeta.Initializers.Pending = append(foo.ObjectMeta.Initializers.Pending[:i], foo.ObjectMeta.Initializers.Pending[i+1:]...)
found = true
break
}
}
if !found {
t.Fatalf("Expected cr.bar.com as initializer on Foo instance")
}
if len(foo.ObjectMeta.Initializers.Pending) == 0 && foo.ObjectMeta.Initializers.Result == nil {
foo.ObjectMeta.Initializers = nil
}
bs, err = json.Marshal(&foo)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
fooUnstructured.UnmarshalJSON(bs)
_, err = dynamicClient.Resource(fooResource).Namespace("default").Update(fooUnstructured)
if err != nil && !errors.IsConflict(err) {
t.Fatalf("Failed to update Foo instance: %v", err)
} else if err == nil {
success = true
break
}
}
if !success {
t.Fatalf("Failed to remove initializer from Foo object")
}
t.Logf("Checking that Foo instance is visible after removing the initializer")
if _, err := dynamicClient.Resource(fooResource).Namespace("default").Get(foo.ObjectMeta.Name, metav1.GetOptions{}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
type Foo struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
}
func unstructuredFoo(foo *Foo) (*unstructured.Unstructured, error) {
bs, err := json.Marshal(foo)
if err != nil {
return nil, err
}
ret := &unstructured.Unstructured{}
if err = ret.UnmarshalJSON(bs); err != nil {
return nil, err
}
return ret, nil
}
func waitForEstablishedCRD(client apiextensionsclientset.Interface, name string) error {
return wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
crd, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
return false, err
}
for _, cond := range crd.Status.Conditions {
switch cond.Type {
case apiextensionsv1beta1.Established:
if cond.Status == apiextensionsv1beta1.ConditionTrue {
return true, err
}
case apiextensionsv1beta1.NamesAccepted:
if cond.Status == apiextensionsv1beta1.ConditionFalse {
fmt.Printf("Name conflict: %v\n", cond.Reason)
}
}
}
return false, nil
})
}
func crdExistsInDiscovery(client apiextensionsclientset.Interface, crd *apiextensionsv1beta1.CustomResourceDefinition) (bool, error) {
resourceList, err := client.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + crd.Spec.Version)
if err != nil {
return false, nil
}
for _, resource := range resourceList.APIResources {
if resource.Name == crd.Spec.Names.Plural {
return true, nil
}
}
return false, nil
}

View File

@@ -0,0 +1,115 @@
// +build !windows
/*
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 master
import (
"context"
"encoding/base64"
"fmt"
"net"
"os"
"golang.org/x/sys/unix"
"google.golang.org/grpc"
"github.com/golang/glog"
kmsapi "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1"
)
const (
kmsAPIVersion = "v1beta1"
sockFile = "/tmp/kms-provider.sock"
unixProtocol = "unix"
)
// base64Plugin gRPC sever for a mock KMS provider.
// Uses base64 to simulate encrypt and decrypt.
type base64Plugin struct {
grpcServer *grpc.Server
listener net.Listener
// Allow users of the plugin to sense requests that were passed to KMS.
encryptRequest chan *kmsapi.EncryptRequest
}
func NewBase64Plugin() (*base64Plugin, error) {
if err := cleanSockFile(); err != nil {
return nil, err
}
listener, err := net.Listen(unixProtocol, sockFile)
if err != nil {
return nil, fmt.Errorf("failed to listen on the unix socket, error: %v", err)
}
glog.Infof("Listening on %s", sockFile)
server := grpc.NewServer()
result := &base64Plugin{
grpcServer: server,
listener: listener,
encryptRequest: make(chan *kmsapi.EncryptRequest, 1),
}
kmsapi.RegisterKeyManagementServiceServer(server, result)
return result, nil
}
func (s *base64Plugin) cleanUp() {
s.grpcServer.Stop()
s.listener.Close()
cleanSockFile()
}
var testProviderAPIVersion = kmsAPIVersion
func (s *base64Plugin) Version(ctx context.Context, request *kmsapi.VersionRequest) (*kmsapi.VersionResponse, error) {
return &kmsapi.VersionResponse{Version: testProviderAPIVersion, RuntimeName: "testKMS", RuntimeVersion: "0.0.1"}, nil
}
func (s *base64Plugin) Decrypt(ctx context.Context, request *kmsapi.DecryptRequest) (*kmsapi.DecryptResponse, error) {
glog.Infof("Received Decrypt Request for DEK: %s", string(request.Cipher))
buf := make([]byte, base64.StdEncoding.DecodedLen(len(request.Cipher)))
n, err := base64.StdEncoding.Decode(buf, request.Cipher)
if err != nil {
return nil, err
}
return &kmsapi.DecryptResponse{Plain: buf[:n]}, nil
}
func (s *base64Plugin) Encrypt(ctx context.Context, request *kmsapi.EncryptRequest) (*kmsapi.EncryptResponse, error) {
glog.Infof("Received Encrypt Request for DEK: %x", request.Plain)
s.encryptRequest <- request
buf := make([]byte, base64.StdEncoding.EncodedLen(len(request.Plain)))
base64.StdEncoding.Encode(buf, request.Plain)
return &kmsapi.EncryptResponse{Cipher: buf}, nil
}
func cleanSockFile() error {
err := unix.Unlink(sockFile)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete the socket file, error: %v", err)
}
return nil
}

View File

@@ -0,0 +1,175 @@
// +build !windows
/*
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 master
import (
"bytes"
"context"
"crypto/aes"
"encoding/binary"
"fmt"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/storage/value"
aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
kmsapi "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1"
)
const (
kmsPrefix = "k8s:enc:kms:v1:grpc-kms-provider:"
dekKeySizeLen = 2
kmsConfigYAML = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- kms:
name: grpc-kms-provider
cachesize: 1000
endpoint: unix:///tmp/kms-provider.sock
`
)
// rawDEKKEKSecret provides operations for working with secrets transformed with Data Encryption Key(DEK) Key Encryption Kye(KEK) envelop.
type rawDEKKEKSecret []byte
func (r rawDEKKEKSecret) getDEKLen() int {
// DEK's length is stored in the two bytes that follow the prefix.
return int(binary.BigEndian.Uint16(r[len(kmsPrefix) : len(kmsPrefix)+dekKeySizeLen]))
}
func (r rawDEKKEKSecret) getDEK() []byte {
return r[len(kmsPrefix)+dekKeySizeLen : len(kmsPrefix)+dekKeySizeLen+r.getDEKLen()]
}
func (r rawDEKKEKSecret) getStartOfPayload() int {
return len(kmsPrefix) + dekKeySizeLen + r.getDEKLen()
}
func (r rawDEKKEKSecret) getPayload() []byte {
return r[r.getStartOfPayload():]
}
// TestKMSProvider is an integration test between KubAPI, ETCD and KMS Plugin
// Concretely, this test verifies the following integration contracts:
// 1. Raw records in ETCD that were processed by KMS Provider should be prefixed with k8s:enc:kms:v1:grpc-kms-provider-name:
// 2. Data Encryption Key (DEK) should be generated by envelopeTransformer and passed to KMS gRPC Plugin
// 3. KMS gRPC Plugin should encrypt the DEK with a Key Encryption Key (KEK) and pass it back to envelopeTransformer
// 4. The payload (ex. Secret) should be encrypted via AES CBC transform
// 5. Prefix-EncryptedDEK-EncryptedPayload structure should be deposited to ETCD
func TestKMSProvider(t *testing.T) {
pluginMock, err := NewBase64Plugin()
if err != nil {
t.Fatalf("failed to create mock of KMS Plugin: %v", err)
}
defer pluginMock.cleanUp()
serveErr := make(chan error, 1)
go func() {
serveErr <- pluginMock.grpcServer.Serve(pluginMock.listener)
}()
test, err := newTransformTest(t, kmsConfigYAML)
if err != nil {
t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s", kmsConfigYAML)
}
defer test.cleanUp()
// As part of newTransformTest a new secret was created, so KMS Mock should have been exercised by this point.
if len(serveErr) != 0 {
t.Fatalf("KMSPlugin failed while serving requests: %v", <-serveErr)
}
secretETCDPath := test.getETCDPath()
var rawSecretAsSeenByETCD rawDEKKEKSecret
rawSecretAsSeenByETCD, err = test.getRawSecretFromETCD()
if err != nil {
t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
}
if !bytes.HasPrefix(rawSecretAsSeenByETCD, []byte(kmsPrefix)) {
t.Fatalf("expected secret to be prefixed with %s, but got %s", kmsPrefix, rawSecretAsSeenByETCD)
}
// Since Data Encryption Key (DEK) is randomly generated (per encryption operation), we need to ask KMS Mock for it.
dekPlainAsSeenByKMS, err := getDEKFromKMSPlugin(pluginMock)
if err != nil {
t.Fatalf("failed to get DEK from KMS: %v", err)
}
decryptResponse, err := pluginMock.Decrypt(context.Background(),
&kmsapi.DecryptRequest{Version: kmsAPIVersion, Cipher: rawSecretAsSeenByETCD.getDEK()})
if err != nil {
t.Fatalf("failed to decrypt DEK, %v", err)
}
dekPlainAsWouldBeSeenByETCD := decryptResponse.Plain
if !bytes.Equal(dekPlainAsSeenByKMS, dekPlainAsWouldBeSeenByETCD) {
t.Fatalf("expected dekPlainAsSeenByKMS %v to be passed to KMS Plugin, but got %s",
dekPlainAsSeenByKMS, dekPlainAsWouldBeSeenByETCD)
}
plainSecret, err := decryptPayload(dekPlainAsWouldBeSeenByETCD, rawSecretAsSeenByETCD, secretETCDPath)
if err != nil {
t.Fatalf("failed to transform from storage via AESCBC, err: %v", err)
}
if !strings.Contains(string(plainSecret), secretVal) {
t.Fatalf("expected %q after decryption, but got %q", secretVal, string(plainSecret))
}
// Secrets should be un-enveloped on direct reads from Kube API Server.
s, err := test.restClient.CoreV1().Secrets(testNamespace).Get(testSecret, metav1.GetOptions{})
if secretVal != string(s.Data[secretKey]) {
t.Fatalf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
}
test.printMetrics()
}
func getDEKFromKMSPlugin(pluginMock *base64Plugin) ([]byte, error) {
// We expect KMS to already have seen an encryptRequest. Hence non-blocking call.
e, ok := <-pluginMock.encryptRequest
if !ok {
return nil, fmt.Errorf("failed to sense encryptRequest from KMS Plugin Mock")
}
return e.Plain, nil
}
func decryptPayload(key []byte, secret rawDEKKEKSecret, secretETCDPath string) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to initialize AES Cipher: %v", err)
}
// etcd path of the key is used as the authenticated context - need to pass it to decrypt
ctx := value.DefaultContext([]byte(secretETCDPath))
aescbcTransformer := aestransformer.NewCBCTransformer(block)
plainSecret, _, err := aescbcTransformer.TransformFromStorage(secret.getPayload(), ctx)
if err != nil {
return nil, fmt.Errorf("failed to transform from storage via AESCBC, err: %v", err)
}
return plainSecret, nil
}

View File

@@ -0,0 +1,252 @@
/*
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 master
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
"time"
appsv1beta1 "k8s.io/api/apps/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/client-go/kubernetes"
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestRun(t *testing.T) {
server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
defer server.TearDownFn()
client, err := kubernetes.NewForConfig(server.ClientConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// test whether the server is really healthy after /healthz told us so
t.Logf("Creating Deployment directly after being healthy")
var replicas int32 = 1
_, err = client.AppsV1beta1().Deployments("default").Create(&appsv1beta1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: appsv1beta1.DeploymentSpec{
Replicas: &replicas,
Strategy: appsv1beta1.DeploymentStrategy{
Type: appsv1beta1.RollingUpdateDeploymentStrategyType,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Image: "foo",
},
},
},
},
},
})
if err != nil {
t.Fatalf("Failed to create deployment: %v", err)
}
}
// TestOpenAPIDelegationChainPlumbing is a smoke test that checks for
// the existence of some representative paths from the
// apiextensions-server and the kube-aggregator server, both part of
// the delegation chain in kube-apiserver.
func TestOpenAPIDelegationChainPlumbing(t *testing.T) {
server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
defer server.TearDownFn()
kubeclient, err := kubernetes.NewForConfig(server.ClientConfig)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result := kubeclient.RESTClient().Get().AbsPath("/swagger.json").Do()
status := 0
result.StatusCode(&status)
if status != 200 {
t.Fatalf("GET /swagger.json failed: expected status=%d, got=%d", 200, status)
}
raw, err := result.Raw()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
type openAPISchema struct {
Paths map[string]interface{} `json:"paths"`
}
var doc openAPISchema
err = json.Unmarshal(raw, &doc)
if err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
matchedExtension := false
extensionsPrefix := "/apis/" + apiextensions.GroupName
matchedRegistration := false
registrationPrefix := "/apis/" + apiregistration.GroupName
for path := range doc.Paths {
if strings.HasPrefix(path, extensionsPrefix) {
matchedExtension = true
}
if strings.HasPrefix(path, registrationPrefix) {
matchedRegistration = true
}
if matchedExtension && matchedRegistration {
return
}
}
if !matchedExtension {
t.Errorf("missing path: %q", extensionsPrefix)
}
if !matchedRegistration {
t.Errorf("missing path: %q", registrationPrefix)
}
}
// return the unique endpoint IPs
func getEndpointIPs(endpoints *corev1.Endpoints) []string {
endpointMap := make(map[string]bool)
ips := make([]string, 0)
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
if _, ok := endpointMap[address.IP]; !ok {
endpointMap[address.IP] = true
ips = append(ips, address.IP)
}
}
}
return ips
}
func verifyEndpointsWithIPs(servers []*kubeapiservertesting.TestServer, ips []string) bool {
listenAddresses := make([]string, 0)
for _, server := range servers {
listenAddresses = append(listenAddresses, server.ServerOpts.GenericServerRunOptions.AdvertiseAddress.String())
}
return reflect.DeepEqual(listenAddresses, ips)
}
func testReconcilersMasterLease(t *testing.T, leaseCount int, masterCount int) {
var leaseServers []*kubeapiservertesting.TestServer
var masterCountServers []*kubeapiservertesting.TestServer
etcd := framework.SharedEtcd()
instanceOptions := &kubeapiservertesting.TestServerInstanceOptions{
DisableStorageCleanup: true,
}
// cleanup the registry storage
defer registry.CleanupStorage()
// 1. start masterCount api servers
for i := 0; i < masterCount; i++ {
// start master count api server
server := kubeapiservertesting.StartTestServerOrDie(t, instanceOptions, []string{
"--endpoint-reconciler-type", "master-count",
"--advertise-address", fmt.Sprintf("10.0.1.%v", i+1),
"--apiserver-count", fmt.Sprintf("%v", masterCount),
}, etcd)
masterCountServers = append(masterCountServers, server)
}
// 2. verify master count servers have registered
if err := wait.PollImmediate(3*time.Second, 2*time.Minute, func() (bool, error) {
client, err := kubernetes.NewForConfig(masterCountServers[0].ClientConfig)
endpoints, err := client.CoreV1().Endpoints("default").Get("kubernetes", metav1.GetOptions{})
if err != nil {
t.Logf("error fetching endpoints: %v", err)
return false, nil
}
return verifyEndpointsWithIPs(masterCountServers, getEndpointIPs(endpoints)), nil
}); err != nil {
t.Fatalf("master count endpoints failed to register: %v", err)
}
// 3. start lease api servers
for i := 0; i < leaseCount; i++ {
options := []string{
"--endpoint-reconciler-type", "lease",
"--advertise-address", fmt.Sprintf("10.0.1.%v", i+10),
}
server := kubeapiservertesting.StartTestServerOrDie(t, instanceOptions, options, etcd)
defer server.TearDownFn()
leaseServers = append(leaseServers, server)
}
time.Sleep(3 * time.Second)
// 4. Shutdown the masterCount server
for _, server := range masterCountServers {
server.TearDownFn()
}
// 5. verify only leaseEndpoint servers left
if err := wait.PollImmediate(3*time.Second, 2*time.Minute, func() (bool, error) {
client, err := kubernetes.NewForConfig(leaseServers[0].ClientConfig)
if err != nil {
t.Logf("create client error: %v", err)
return false, nil
}
endpoints, err := client.CoreV1().Endpoints("default").Get("kubernetes", metav1.GetOptions{})
if err != nil {
t.Logf("error fetching endpoints: %v", err)
return false, nil
}
return verifyEndpointsWithIPs(leaseServers, getEndpointIPs(endpoints)), nil
}); err != nil {
t.Fatalf("did not find only lease endpoints: %v", err)
}
}
func TestReconcilerMasterLeaseCombined(t *testing.T) {
testReconcilersMasterLease(t, 1, 3)
}
func TestReconcilerMasterLeaseMultiMoreMasters(t *testing.T) {
testReconcilersMasterLease(t, 3, 2)
}
func TestReconcilerMasterLeaseMultiCombined(t *testing.T) {
testReconcilersMasterLease(t, 3, 3)
}

View File

@@ -0,0 +1,27 @@
/*
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 master
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,178 @@
/*
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 master
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"testing"
"k8s.io/apiserver/pkg/server/options/encryptionconfig"
"k8s.io/apiserver/pkg/storage/value"
aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
)
const (
aesGCMPrefix = "k8s:enc:aesgcm:v1:key1:"
aesCBCPrefix = "k8s:enc:aescbc:v1:key1:"
aesGCMConfigYAML = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
`
aesCBCConfigYAML = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
`
identityConfigYAML = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- identity: {}
`
)
// TestSecretsShouldBeEnveloped is an integration test between KubeAPI and etcd that checks:
// 1. Secrets are encrypted on write
// 2. Secrets are decrypted on read
// when EncryptionConfig is passed to KubeAPI server.
func TestSecretsShouldBeTransformed(t *testing.T) {
var testCases = []struct {
transformerConfigContent string
transformerPrefix string
unSealFunc unSealSecret
}{
{aesGCMConfigYAML, aesGCMPrefix, unSealWithGCMTransformer},
{aesCBCConfigYAML, aesCBCPrefix, unSealWithCBCTransformer},
// TODO: add secretbox
}
for _, tt := range testCases {
test, err := newTransformTest(t, tt.transformerConfigContent)
if err != nil {
test.cleanUp()
t.Errorf("failed to setup test for envelop %s, error was %v", tt.transformerPrefix, err)
continue
}
test.run(tt.unSealFunc, tt.transformerPrefix)
test.cleanUp()
}
}
// Baseline (no enveloping) - use to contrast with enveloping benchmarks.
func BenchmarkBase(b *testing.B) {
runBenchmark(b, "")
}
// Identity transformer is a NOOP (crypto-wise) - use to contrast with AESGCM and AESCBC benchmark results.
func BenchmarkIdentityWrite(b *testing.B) {
runBenchmark(b, identityConfigYAML)
}
func BenchmarkAESGCMEnvelopeWrite(b *testing.B) {
runBenchmark(b, aesGCMConfigYAML)
}
func BenchmarkAESCBCEnvelopeWrite(b *testing.B) {
runBenchmark(b, aesCBCConfigYAML)
}
func runBenchmark(b *testing.B, transformerConfig string) {
b.StopTimer()
test, err := newTransformTest(b, transformerConfig)
defer test.cleanUp()
if err != nil {
b.Fatalf("failed to setup benchmark for config %s, error was %v", transformerConfig, err)
}
b.StartTimer()
test.benchmark(b)
b.StopTimer()
test.printMetrics()
}
func unSealWithGCMTransformer(cipherText []byte, ctx value.Context,
transformerConfig encryptionconfig.ProviderConfig) ([]byte, error) {
block, err := newAESCipher(transformerConfig.AESGCM.Keys[0].Secret)
if err != nil {
return nil, fmt.Errorf("failed to create block cipher: %v", err)
}
gcmTransformer := aestransformer.NewGCMTransformer(block)
clearText, _, err := gcmTransformer.TransformFromStorage(cipherText, ctx)
if err != nil {
return nil, fmt.Errorf("failed to decypt secret: %v", err)
}
return clearText, nil
}
func unSealWithCBCTransformer(cipherText []byte, ctx value.Context,
transformerConfig encryptionconfig.ProviderConfig) ([]byte, error) {
block, err := newAESCipher(transformerConfig.AESCBC.Keys[0].Secret)
if err != nil {
return nil, err
}
cbcTransformer := aestransformer.NewCBCTransformer(block)
clearText, _, err := cbcTransformer.TransformFromStorage(cipherText, ctx)
if err != nil {
return nil, fmt.Errorf("failed to decypt secret: %v", err)
}
return clearText, nil
}
func newAESCipher(key string) (cipher.Block, error) {
k, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return nil, fmt.Errorf("failed to decode config secret: %v", err)
}
block, err := aes.NewCipher(k)
if err != nil {
return nil, fmt.Errorf("failed to create AES cipher: %v", err)
}
return block, nil
}

View File

@@ -0,0 +1,882 @@
/*
Copyright 2015 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 master
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/ghodss/yaml"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/group"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
"k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest"
clientsetv1 "k8s.io/client-go/kubernetes"
clienttypedv1 "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework"
)
const (
AliceToken string = "abc123" // username: alice. Present in token file.
BobToken string = "xyz987" // username: bob. Present in token file.
)
type allowAliceAuthorizer struct{}
func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if a.GetUser() != nil && a.GetUser().GetName() == "alice" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil
}
func testPrefix(t *testing.T, prefix string) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
resp, err := http.Get(s.URL + prefix)
if err != nil {
t.Fatalf("unexpected error getting %s prefix: %v", prefix, err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("got status %v instead of 200 OK", resp.StatusCode)
}
}
func TestAutoscalingPrefix(t *testing.T) {
testPrefix(t, "/apis/autoscaling/")
}
func TestBatchPrefix(t *testing.T) {
testPrefix(t, "/apis/batch/")
}
func TestAppsPrefix(t *testing.T) {
testPrefix(t, "/apis/apps/")
}
func TestExtensionsPrefix(t *testing.T) {
testPrefix(t, "/apis/extensions/")
}
func TestKubernetesService(t *testing.T) {
config := framework.NewMasterConfig()
_, _, closeFn := framework.RunAMaster(config)
defer closeFn()
coreClient := clientset.NewForConfigOrDie(config.GenericConfig.LoopbackClientConfig)
if _, err := coreClient.Core().Services(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{}); err != nil {
t.Fatalf("Expected kubernetes service to exists, got: %v", err)
}
}
func TestEmptyList(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
u := s.URL + "/api/v1/namespaces/default/pods"
resp, err := http.Get(u)
if err != nil {
t.Fatalf("unexpected error getting %s: %v", u, err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("got status %v instead of 200 OK", resp.StatusCode)
}
defer resp.Body.Close()
data, _ := ioutil.ReadAll(resp.Body)
decodedData := map[string]interface{}{}
if err := json.Unmarshal(data, &decodedData); err != nil {
t.Logf("body: %s", string(data))
t.Fatalf("got error decoding data: %v", err)
}
if items, ok := decodedData["items"]; !ok {
t.Logf("body: %s", string(data))
t.Fatalf("missing items field in empty list (all lists should return an items field)")
} else if items == nil {
t.Logf("body: %s", string(data))
t.Fatalf("nil items field from empty list (all lists should return non-nil empty items lists)")
}
}
func initStatusForbiddenMasterCongfig() *master.Config {
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysDenyAuthorizer()
return masterConfig
}
func initUnauthorizedMasterCongfig() *master.Config {
masterConfig := framework.NewIntegrationTestMasterConfig()
tokenAuthenticator := tokentest.New()
tokenAuthenticator.Tokens[AliceToken] = &user.DefaultInfo{Name: "alice", UID: "1"}
tokenAuthenticator.Tokens[BobToken] = &user.DefaultInfo{Name: "bob", UID: "2"}
masterConfig.GenericConfig.Authentication.Authenticator = group.NewGroupAdder(bearertoken.New(tokenAuthenticator), []string{user.AllAuthenticated})
masterConfig.GenericConfig.Authorization.Authorizer = allowAliceAuthorizer{}
return masterConfig
}
func TestStatus(t *testing.T) {
testCases := []struct {
name string
masterConfig *master.Config
statusCode int
reqPath string
reason string
message string
}{
{
name: "404",
masterConfig: nil,
statusCode: http.StatusNotFound,
reqPath: "/apis/batch/v1/namespaces/default/jobs/foo",
reason: "NotFound",
message: `jobs.batch "foo" not found`,
},
{
name: "403",
masterConfig: initStatusForbiddenMasterCongfig(),
statusCode: http.StatusForbidden,
reqPath: "/apis",
reason: "Forbidden",
message: `forbidden: User "" cannot get path "/apis": Everything is forbidden.`,
},
{
name: "401",
masterConfig: initUnauthorizedMasterCongfig(),
statusCode: http.StatusUnauthorized,
reqPath: "/apis",
reason: "Unauthorized",
message: `Unauthorized`,
},
}
for _, tc := range testCases {
_, s, closeFn := framework.RunAMaster(tc.masterConfig)
defer closeFn()
u := s.URL + tc.reqPath
resp, err := http.Get(u)
if err != nil {
t.Fatalf("unexpected error getting %s: %v", u, err)
}
if resp.StatusCode != tc.statusCode {
t.Fatalf("got status %v instead of %s", resp.StatusCode, tc.name)
}
defer resp.Body.Close()
data, _ := ioutil.ReadAll(resp.Body)
decodedData := map[string]interface{}{}
if err := json.Unmarshal(data, &decodedData); err != nil {
t.Logf("body: %s", string(data))
t.Fatalf("got error decoding data: %v", err)
}
t.Logf("body: %s", string(data))
if got, expected := decodedData["apiVersion"], "v1"; got != expected {
t.Errorf("unexpected apiVersion %q, expected %q", got, expected)
}
if got, expected := decodedData["kind"], "Status"; got != expected {
t.Errorf("unexpected kind %q, expected %q", got, expected)
}
if got, expected := decodedData["status"], "Failure"; got != expected {
t.Errorf("unexpected status %q, expected %q", got, expected)
}
if got, expected := decodedData["code"], float64(tc.statusCode); got != expected {
t.Errorf("unexpected code %v, expected %v", got, expected)
}
if got, expected := decodedData["reason"], tc.reason; got != expected {
t.Errorf("unexpected reason %v, expected %v", got, expected)
}
if got, expected := decodedData["message"], tc.message; got != expected {
t.Errorf("unexpected message %v, expected %v", got, expected)
}
}
}
func constructBody(val string, size int, field string, t *testing.T) *appsv1.Deployment {
var replicas int32 = 1
deploymentObject := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test",
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Image: "foo",
},
},
},
},
},
}
switch field {
case "labels":
labelsMap := map[string]string{}
for i := 0; i < size; i++ {
key := val + strconv.Itoa(i)
labelsMap[key] = val
}
deploymentObject.ObjectMeta.Labels = labelsMap
case "annotations":
annotationsMap := map[string]string{}
for i := 0; i < size; i++ {
key := val + strconv.Itoa(i)
annotationsMap[key] = val
}
deploymentObject.ObjectMeta.Annotations = annotationsMap
case "finalizers":
finalizerString := []string{}
for i := 0; i < size; i++ {
finalizerString = append(finalizerString, val)
}
deploymentObject.ObjectMeta.Finalizers = finalizerString
default:
t.Fatalf("Unexpected field: %s used for making large deployment object value", field)
}
return deploymentObject
}
func TestObjectSizeResponses(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
client := clientsetv1.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
const DeploymentMegabyteSize = 100000
const DeploymentTwoMegabyteSize = 1000000
expectedMsgFor1MB := `etcdserver: request is too large`
expectedMsgFor2MB := `rpc error: code = ResourceExhausted desc = grpc: trying to send message larger than max`
expectedMsgForLargeAnnotation := `metadata.annotations: Too long: must have at most 262144 characters`
deployment1 := constructBody("a", DeploymentMegabyteSize, "labels", t) // >1 MB file
deployment2 := constructBody("a", DeploymentTwoMegabyteSize, "labels", t) // >2 MB file
deployment3 := constructBody("a", DeploymentMegabyteSize, "annotations", t)
deployment4 := constructBody("sample/sample", DeploymentMegabyteSize, "finalizers", t) // >1 MB file
deployment5 := constructBody("sample/sample", DeploymentTwoMegabyteSize, "finalizers", t) // >2 MB file
requests := []struct {
size string
deploymentObject *appsv1.Deployment
expectedMessage string
}{
{"1 MB", deployment1, expectedMsgFor1MB},
{"2 MB", deployment2, expectedMsgFor2MB},
{"1 MB", deployment3, expectedMsgForLargeAnnotation},
{"1 MB", deployment4, expectedMsgFor1MB},
{"2 MB", deployment5, expectedMsgFor2MB},
}
for _, r := range requests {
t.Run(r.size, func(t *testing.T) {
_, err := client.AppsV1().Deployments(metav1.NamespaceDefault).Create(r.deploymentObject)
if err != nil {
if !strings.Contains(err.Error(), r.expectedMessage) {
t.Errorf("got: %s;want: %s", err.Error(), r.expectedMessage)
}
}
})
}
}
func TestWatchSucceedsWithoutArgs(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
resp, err := http.Get(s.URL + "/api/v1/namespaces?watch=1")
if err != nil {
t.Fatalf("unexpected error getting experimental prefix: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("got status %v instead of 200 OK", resp.StatusCode)
}
resp.Body.Close()
}
var hpaV1 string = `
{
"apiVersion": "autoscaling/v1",
"kind": "HorizontalPodAutoscaler",
"metadata": {
"name": "test-hpa",
"namespace": "default"
},
"spec": {
"scaleTargetRef": {
"kind": "ReplicationController",
"name": "test-hpa",
"namespace": "default"
},
"minReplicas": 1,
"maxReplicas": 10,
"targetCPUUtilizationPercentage": 50
}
}
`
var deploymentExtensions string = `
{
"apiVersion": "extensions/v1beta1",
"kind": "Deployment",
"metadata": {
"name": "test-deployment1",
"namespace": "default"
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"labels": {
"app": "nginx0"
}
},
"spec": {
"containers": [{
"name": "nginx",
"image": "k8s.gcr.io/nginx:1.7.9"
}]
}
}
}
}
`
var deploymentApps string = `
{
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"metadata": {
"name": "test-deployment2",
"namespace": "default"
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"labels": {
"app": "nginx0"
}
},
"spec": {
"containers": [{
"name": "nginx",
"image": "k8s.gcr.io/nginx:1.7.9"
}]
}
}
}
}
`
func autoscalingPath(resource, namespace, name string) string {
return testapi.Autoscaling.ResourcePath(resource, namespace, name)
}
func batchPath(resource, namespace, name string) string {
return testapi.Batch.ResourcePath(resource, namespace, name)
}
func extensionsPath(resource, namespace, name string) string {
return testapi.Extensions.ResourcePath(resource, namespace, name)
}
func appsPath(resource, namespace, name string) string {
return testapi.Apps.ResourcePath(resource, namespace, name)
}
func TestAutoscalingGroupBackwardCompatibility(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
transport := http.DefaultTransport
requests := []struct {
verb string
URL string
body string
expectedStatusCodes map[int]bool
expectedVersion string
}{
{"POST", autoscalingPath("horizontalpodautoscalers", metav1.NamespaceDefault, ""), hpaV1, integration.Code201, ""},
{"GET", autoscalingPath("horizontalpodautoscalers", metav1.NamespaceDefault, ""), "", integration.Code200, testapi.Autoscaling.GroupVersion().String()},
}
for _, r := range requests {
bodyBytes := bytes.NewReader([]byte(r.body))
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
if err != nil {
t.Logf("case %v", r)
t.Fatalf("unexpected error: %v", err)
}
func() {
resp, err := transport.RoundTrip(req)
defer resp.Body.Close()
if err != nil {
t.Logf("case %v", r)
t.Fatalf("unexpected error: %v", err)
}
b, _ := ioutil.ReadAll(resp.Body)
body := string(b)
if _, ok := r.expectedStatusCodes[resp.StatusCode]; !ok {
t.Logf("case %v", r)
t.Errorf("Expected status one of %v, but got %v", r.expectedStatusCodes, resp.StatusCode)
t.Errorf("Body: %v", body)
}
if !strings.Contains(body, "\"apiVersion\":\""+r.expectedVersion) {
t.Logf("case %v", r)
t.Errorf("Expected version %v, got body %v", r.expectedVersion, body)
}
}()
}
}
func TestAppsGroupBackwardCompatibility(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
transport := http.DefaultTransport
requests := []struct {
verb string
URL string
body string
expectedStatusCodes map[int]bool
expectedVersion string
}{
// Post to extensions endpoint and get back from both: extensions and apps
{"POST", extensionsPath("deployments", metav1.NamespaceDefault, ""), deploymentExtensions, integration.Code201, ""},
{"GET", extensionsPath("deployments", metav1.NamespaceDefault, "test-deployment1"), "", integration.Code200, testapi.Extensions.GroupVersion().String()},
{"GET", appsPath("deployments", metav1.NamespaceDefault, "test-deployment1"), "", integration.Code200, testapi.Apps.GroupVersion().String()},
{"DELETE", extensionsPath("deployments", metav1.NamespaceDefault, "test-deployment1"), "", integration.Code200, testapi.Extensions.GroupVersion().String()},
// Post to apps endpoint and get back from both: apps and extensions
{"POST", appsPath("deployments", metav1.NamespaceDefault, ""), deploymentApps, integration.Code201, ""},
{"GET", appsPath("deployments", metav1.NamespaceDefault, "test-deployment2"), "", integration.Code200, testapi.Apps.GroupVersion().String()},
{"GET", extensionsPath("deployments", metav1.NamespaceDefault, "test-deployment2"), "", integration.Code200, testapi.Extensions.GroupVersion().String()},
{"DELETE", appsPath("deployments", metav1.NamespaceDefault, "test-deployment2"), "", integration.Code200, testapi.Apps.GroupVersion().String()},
}
for _, r := range requests {
bodyBytes := bytes.NewReader([]byte(r.body))
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
if err != nil {
t.Logf("case %v", r)
t.Fatalf("unexpected error: %v", err)
}
func() {
resp, err := transport.RoundTrip(req)
defer resp.Body.Close()
if err != nil {
t.Logf("case %v", r)
t.Fatalf("unexpected error: %v", err)
}
b, _ := ioutil.ReadAll(resp.Body)
body := string(b)
if _, ok := r.expectedStatusCodes[resp.StatusCode]; !ok {
t.Logf("case %v", r)
t.Errorf("Expected status one of %v, but got %v", r.expectedStatusCodes, resp.StatusCode)
t.Errorf("Body: %v", body)
}
if !strings.Contains(body, "\"apiVersion\":\""+r.expectedVersion) {
t.Logf("case %v", r)
t.Errorf("Expected version %v, got body %v", r.expectedVersion, body)
}
}()
}
}
func TestAccept(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
resp, err := http.Get(s.URL + "/api/")
if err != nil {
t.Fatalf("unexpected error getting api: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("got status %v instead of 200 OK", resp.StatusCode)
}
body, _ := ioutil.ReadAll(resp.Body)
if resp.Header.Get("Content-Type") != "application/json" {
t.Errorf("unexpected content: %s", body)
}
if err := json.Unmarshal(body, &map[string]interface{}{}); err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("GET", s.URL+"/api/", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application/yaml")
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
body, _ = ioutil.ReadAll(resp.Body)
if resp.Header.Get("Content-Type") != "application/yaml" {
t.Errorf("unexpected content: %s", body)
}
t.Logf("body: %s", body)
if err := yaml.Unmarshal(body, &map[string]interface{}{}); err != nil {
t.Fatal(err)
}
req, err = http.NewRequest("GET", s.URL+"/api/", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application/json, application/yaml")
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
body, _ = ioutil.ReadAll(resp.Body)
if resp.Header.Get("Content-Type") != "application/json" {
t.Errorf("unexpected content: %s", body)
}
t.Logf("body: %s", body)
if err := yaml.Unmarshal(body, &map[string]interface{}{}); err != nil {
t.Fatal(err)
}
req, err = http.NewRequest("GET", s.URL+"/api/", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application") // not a valid media type
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusNotAcceptable {
t.Errorf("unexpected error from the server")
}
}
func countEndpoints(eps *api.Endpoints) int {
count := 0
for i := range eps.Subsets {
count += len(eps.Subsets[i].Addresses) * len(eps.Subsets[i].Ports)
}
return count
}
func TestMasterService(t *testing.T) {
_, s, closeFn := framework.RunAMaster(framework.NewIntegrationTestMasterConfig())
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
err := wait.Poll(time.Second, time.Minute, func() (bool, error) {
svcList, err := client.Core().Services(metav1.NamespaceDefault).List(metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
return false, nil
}
found := false
for i := range svcList.Items {
if svcList.Items[i].Name == "kubernetes" {
found = true
break
}
}
if found {
ep, err := client.Core().Endpoints(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{})
if err != nil {
return false, nil
}
if countEndpoints(ep) == 0 {
return false, fmt.Errorf("no endpoints for kubernetes service: %v", ep)
}
return true, nil
}
return false, nil
})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func TestServiceAlloc(t *testing.T) {
cfg := framework.NewIntegrationTestMasterConfig()
_, cidr, err := net.ParseCIDR("192.168.0.0/29")
if err != nil {
t.Fatalf("bad cidr: %v", err)
}
cfg.ExtraConfig.ServiceIPRange = *cidr
_, s, closeFn := framework.RunAMaster(cfg)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
svc := func(i int) *api.Service {
return &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("svc-%v", i),
},
Spec: api.ServiceSpec{
Type: api.ServiceTypeClusterIP,
Ports: []api.ServicePort{
{Port: 80},
},
},
}
}
// Wait until the default "kubernetes" service is created.
if err = wait.Poll(250*time.Millisecond, time.Minute, func() (bool, error) {
_, err := client.Core().Services(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{})
if err != nil && !errors.IsNotFound(err) {
return false, err
}
return !errors.IsNotFound(err), nil
}); err != nil {
t.Fatalf("creating kubernetes service timed out")
}
// make 5 more services to take up all IPs
for i := 0; i < 5; i++ {
if _, err := client.Core().Services(metav1.NamespaceDefault).Create(svc(i)); err != nil {
t.Error(err)
}
}
// Make another service. It will fail because we're out of cluster IPs
if _, err := client.Core().Services(metav1.NamespaceDefault).Create(svc(8)); err != nil {
if !strings.Contains(err.Error(), "range is full") {
t.Errorf("unexpected error text: %v", err)
}
} else {
svcs, err := client.Core().Services(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected success, and error getting the services: %v", err)
}
allIPs := []string{}
for _, s := range svcs.Items {
allIPs = append(allIPs, s.Spec.ClusterIP)
}
t.Fatalf("unexpected creation success. The following IPs exist: %#v. It should only be possible to allocate 2 IP addresses in this cluster.\n\n%#v", allIPs, svcs)
}
// Delete the first service.
if err := client.Core().Services(metav1.NamespaceDefault).Delete(svc(1).ObjectMeta.Name, nil); err != nil {
t.Fatalf("got unexpected error: %v", err)
}
// This time creating the second service should work.
if _, err := client.Core().Services(metav1.NamespaceDefault).Create(svc(8)); err != nil {
t.Fatalf("got unexpected error: %v", err)
}
}
// TestUpdateNodeObjects represents a simple version of the behavior of node checkins at steady
// state. This test allows for easy profiling of a realistic master scenario for baseline CPU
// in very large clusters. It is disabled by default - start a kube-apiserver and pass
// UPDATE_NODE_APISERVER as the host value.
func TestUpdateNodeObjects(t *testing.T) {
server := os.Getenv("UPDATE_NODE_APISERVER")
if len(server) == 0 {
t.Skip("UPDATE_NODE_APISERVER is not set")
}
c := clienttypedv1.NewForConfigOrDie(&restclient.Config{
QPS: 10000,
Host: server,
ContentConfig: restclient.ContentConfig{
AcceptContentTypes: "application/vnd.kubernetes.protobuf",
ContentType: "application/vnd.kubernetes.protobuf",
},
})
nodes := 400
listers := 5
watchers := 50
iterations := 10000
for i := 0; i < nodes*6; i++ {
c.Nodes().Delete(fmt.Sprintf("node-%d", i), nil)
_, err := c.Nodes().Create(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("node-%d", i),
},
})
if err != nil {
t.Fatal(err)
}
}
for k := 0; k < listers; k++ {
go func(lister int) {
for i := 0; i < iterations; i++ {
_, err := c.Nodes().List(metav1.ListOptions{})
if err != nil {
fmt.Printf("[list:%d] error after %d: %v\n", lister, i, err)
break
}
time.Sleep(time.Duration(lister)*10*time.Millisecond + 1500*time.Millisecond)
}
}(k)
}
for k := 0; k < watchers; k++ {
go func(lister int) {
w, err := c.Nodes().Watch(metav1.ListOptions{})
if err != nil {
fmt.Printf("[watch:%d] error: %v", lister, err)
return
}
i := 0
for r := range w.ResultChan() {
i++
if _, ok := r.Object.(*v1.Node); !ok {
fmt.Printf("[watch:%d] unexpected object after %d: %#v\n", lister, i, r)
}
if i%100 == 0 {
fmt.Printf("[watch:%d] iteration %d ...\n", lister, i)
}
}
fmt.Printf("[watch:%d] done\n", lister)
}(k)
}
var wg sync.WaitGroup
wg.Add(nodes - listers)
for j := 0; j < nodes; j++ {
go func(node int) {
var lastCount int
for i := 0; i < iterations; i++ {
if i%100 == 0 {
fmt.Printf("[%d] iteration %d ...\n", node, i)
}
if i%20 == 0 {
_, err := c.Nodes().List(metav1.ListOptions{})
if err != nil {
fmt.Printf("[%d] error after %d: %v\n", node, i, err)
break
}
}
r, err := c.Nodes().List(metav1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=node-%d", node),
ResourceVersion: "0",
})
if err != nil {
fmt.Printf("[%d] error after %d: %v\n", node, i, err)
break
}
if len(r.Items) != 1 {
fmt.Printf("[%d] error after %d: unexpected list count\n", node, i)
break
}
n, err := c.Nodes().Get(fmt.Sprintf("node-%d", node), metav1.GetOptions{})
if err != nil {
fmt.Printf("[%d] error after %d: %v\n", node, i, err)
break
}
if len(n.Status.Conditions) != lastCount {
fmt.Printf("[%d] worker set %d, read %d conditions\n", node, lastCount, len(n.Status.Conditions))
break
}
previousCount := lastCount
switch {
case i%4 == 0:
lastCount = 1
n.Status.Conditions = []v1.NodeCondition{
{
Type: v1.NodeReady,
Status: v1.ConditionTrue,
Reason: "foo",
},
}
case i%4 == 1:
lastCount = 2
n.Status.Conditions = []v1.NodeCondition{
{
Type: v1.NodeReady,
Status: v1.ConditionFalse,
Reason: "foo",
},
{
Type: v1.NodeDiskPressure,
Status: v1.ConditionTrue,
Reason: "bar",
},
}
case i%4 == 1:
lastCount = 0
n.Status.Conditions = nil
}
if _, err := c.Nodes().UpdateStatus(n); err != nil {
if !errors.IsConflict(err) {
fmt.Printf("[%d] error after %d: %v\n", node, i, err)
break
}
lastCount = previousCount
}
}
wg.Done()
fmt.Printf("[%d] done\n", node)
}(j)
}
wg.Wait()
}

View File

@@ -0,0 +1,269 @@
/*
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 master
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"testing"
"github.com/coreos/etcd/clientv3"
"github.com/ghodss/yaml"
"github.com/prometheus/client_golang/prometheus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/server/options/encryptionconfig"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/apiserver/pkg/storage/value"
"k8s.io/client-go/kubernetes"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework"
)
const (
secretKey = "api_key"
secretVal = "086a7ffc-0225-11e8-ba89-0ed5f89f718b"
encryptionConfigFileName = "encryption.conf"
testNamespace = "secret-encryption-test"
testSecret = "test-secret"
metricsPrefix = "apiserver_storage_"
)
type unSealSecret func(cipherText []byte, ctx value.Context, config encryptionconfig.ProviderConfig) ([]byte, error)
type transformTest struct {
logger kubeapiservertesting.Logger
storageConfig *storagebackend.Config
configDir string
transformerConfig string
kubeAPIServer kubeapiservertesting.TestServer
restClient *kubernetes.Clientset
ns *corev1.Namespace
secret *corev1.Secret
}
func newTransformTest(l kubeapiservertesting.Logger, transformerConfigYAML string) (*transformTest, error) {
e := transformTest{
logger: l,
transformerConfig: transformerConfigYAML,
storageConfig: framework.SharedEtcd(),
}
var err error
if transformerConfigYAML != "" {
if e.configDir, err = e.createEncryptionConfig(); err != nil {
return nil, fmt.Errorf("error while creating KubeAPIServer encryption config: %v", err)
}
}
if e.kubeAPIServer, err = kubeapiservertesting.StartTestServer(l, nil, e.getEncryptionOptions(), e.storageConfig); err != nil {
return nil, fmt.Errorf("failed to start KubeAPI server: %v", err)
}
if e.restClient, err = kubernetes.NewForConfig(e.kubeAPIServer.ClientConfig); err != nil {
return nil, fmt.Errorf("error while creating rest client: %v", err)
}
if e.ns, err = e.createNamespace(testNamespace); err != nil {
return nil, err
}
if e.secret, err = e.createSecret(testSecret, e.ns.Name); err != nil {
return nil, err
}
return &e, nil
}
func (e *transformTest) cleanUp() {
os.RemoveAll(e.configDir)
e.restClient.CoreV1().Namespaces().Delete(e.ns.Name, metav1.NewDeleteOptions(0))
e.kubeAPIServer.TearDownFn()
}
func (e *transformTest) run(unSealSecretFunc unSealSecret, expectedEnvelopePrefix string) {
response, err := e.readRawRecordFromETCD(e.getETCDPath())
if err != nil {
e.logger.Errorf("failed to read from etcd: %v", err)
return
}
if !bytes.HasPrefix(response.Kvs[0].Value, []byte(expectedEnvelopePrefix)) {
e.logger.Errorf("expected secret to be prefixed with %s, but got %s",
expectedEnvelopePrefix, response.Kvs[0].Value)
return
}
// etcd path of the key is used as the authenticated context - need to pass it to decrypt
ctx := value.DefaultContext([]byte(e.getETCDPath()))
// Envelope header precedes the payload
sealedData := response.Kvs[0].Value[len(expectedEnvelopePrefix):]
transformerConfig, err := e.getEncryptionConfig()
if err != nil {
e.logger.Errorf("failed to parse transformer config: %v", err)
}
v, err := unSealSecretFunc(sealedData, ctx, *transformerConfig)
if err != nil {
e.logger.Errorf("failed to unseal secret: %v", err)
return
}
if !strings.Contains(string(v), secretVal) {
e.logger.Errorf("expected %q after decryption, but got %q", secretVal, string(v))
}
// Secrets should be un-enveloped on direct reads from Kube API Server.
s, err := e.restClient.CoreV1().Secrets(testNamespace).Get(testSecret, metav1.GetOptions{})
if secretVal != string(s.Data[secretKey]) {
e.logger.Errorf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
}
}
func (e *transformTest) benchmark(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := e.createSecret(e.secret.Name+strconv.Itoa(i), e.ns.Name)
if err != nil {
b.Fatalf("failed to create a secret: %v", err)
}
}
}
func (e *transformTest) getETCDPath() string {
return fmt.Sprintf("/%s/secrets/%s/%s", e.storageConfig.Prefix, e.ns.Name, e.secret.Name)
}
func (e *transformTest) getRawSecretFromETCD() ([]byte, error) {
secretETCDPath := e.getETCDPath()
etcdResponse, err := e.readRawRecordFromETCD(secretETCDPath)
if err != nil {
return nil, fmt.Errorf("failed to read %s from etcd: %v", secretETCDPath, err)
}
return etcdResponse.Kvs[0].Value, nil
}
func (e *transformTest) getEncryptionOptions() []string {
if e.transformerConfig != "" {
return []string{"--experimental-encryption-provider-config", path.Join(e.configDir, encryptionConfigFileName)}
}
return nil
}
func (e *transformTest) createEncryptionConfig() (string, error) {
tempDir, err := ioutil.TempDir("", "secrets-encryption-test")
if err != nil {
return "", fmt.Errorf("failed to create temp directory: %v", err)
}
encryptionConfig := path.Join(tempDir, encryptionConfigFileName)
if err := ioutil.WriteFile(encryptionConfig, []byte(e.transformerConfig), 0644); err != nil {
os.RemoveAll(tempDir)
return "", fmt.Errorf("error while writing encryption config: %v", err)
}
return tempDir, nil
}
func (e *transformTest) getEncryptionConfig() (*encryptionconfig.ProviderConfig, error) {
var config encryptionconfig.EncryptionConfig
err := yaml.Unmarshal([]byte(e.transformerConfig), &config)
if err != nil {
return nil, fmt.Errorf("failed to extract transformer key: %v", err)
}
return &config.Resources[0].Providers[0], nil
}
func (e *transformTest) createNamespace(name string) (*corev1.Namespace, error) {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
if _, err := e.restClient.CoreV1().Namespaces().Create(ns); err != nil {
return nil, fmt.Errorf("unable to create testing namespace %v", err)
}
return ns, nil
}
func (e *transformTest) createSecret(name, namespace string) (*corev1.Secret, error) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Data: map[string][]byte{
secretKey: []byte(secretVal),
},
}
if _, err := e.restClient.CoreV1().Secrets(secret.Namespace).Create(secret); err != nil {
return nil, fmt.Errorf("error while writing secret: %v", err)
}
return secret, nil
}
func (e *transformTest) readRawRecordFromETCD(path string) (*clientv3.GetResponse, error) {
etcdClient, err := integration.GetEtcdKVClient(e.kubeAPIServer.ServerOpts.Etcd.StorageConfig)
if err != nil {
return nil, fmt.Errorf("failed to create etcd client: %v", err)
}
response, err := etcdClient.Get(context.Background(), path, clientv3.WithPrefix())
if err != nil {
return nil, fmt.Errorf("failed to retrieve secret from etcd %v", err)
}
return response, nil
}
func (e *transformTest) printMetrics() error {
e.logger.Logf("Transformation Metrics:")
metrics, err := prometheus.DefaultGatherer.Gather()
if err != nil {
return fmt.Errorf("failed to gather metrics: %s", err)
}
for _, mf := range metrics {
if strings.HasPrefix(*mf.Name, metricsPrefix) {
e.logger.Logf("%s", *mf.Name)
for _, metric := range mf.GetMetric() {
e.logger.Logf("%v", metric)
}
}
}
return nil
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
importpath = "k8s.io/kubernetes/test/integration/metrics",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"metrics_test.go",
],
embed = [":go_default_library"],
tags = ["integration"],
deps = [
"//test/integration/framework:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/golang/protobuf/proto:go_default_library",
"//vendor/github.com/prometheus/client_model/go:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)

View File

@@ -0,0 +1,17 @@
/*
Copyright 2016 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 metrics

View File

@@ -0,0 +1,27 @@
/*
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 metrics
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,126 @@
/*
Copyright 2015 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 metrics
import (
"bufio"
"fmt"
"net/http"
"net/http/httptest"
"runtime"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/test/integration/framework"
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
prometheuspb "github.com/prometheus/client_model/go"
)
const scrapeRequestHeader = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text"
func scrapeMetrics(s *httptest.Server) ([]*prometheuspb.MetricFamily, error) {
req, err := http.NewRequest("GET", s.URL+"/metrics", nil)
if err != nil {
return nil, fmt.Errorf("Unable to create http request: %v", err)
}
// Ask the prometheus exporter for its text protocol buffer format, since it's
// much easier to parse than its plain-text format. Don't use the serialized
// proto representation since it uses a non-standard varint delimiter between
// metric families.
req.Header.Add("Accept", scrapeRequestHeader)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("Unable to contact metrics endpoint of master: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Non-200 response trying to scrape metrics from master: %v", resp)
}
// Each line in the response body should contain all the data for a single metric.
var metrics []*prometheuspb.MetricFamily
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
var metric prometheuspb.MetricFamily
if err := proto.UnmarshalText(scanner.Text(), &metric); err != nil {
return nil, fmt.Errorf("Failed to unmarshal line of metrics response: %v", err)
}
glog.V(4).Infof("Got metric %q", metric.GetName())
metrics = append(metrics, &metric)
}
return metrics, nil
}
func checkForExpectedMetrics(t *testing.T, metrics []*prometheuspb.MetricFamily, expectedMetrics []string) {
foundMetrics := make(map[string]bool)
for _, metric := range metrics {
foundMetrics[metric.GetName()] = true
}
for _, expected := range expectedMetrics {
if _, found := foundMetrics[expected]; !found {
t.Errorf("Master metrics did not include expected metric %q", expected)
}
}
}
func TestMasterProcessMetrics(t *testing.T) {
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
t.Skipf("not supported on GOOS=%s", runtime.GOOS)
}
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
metrics, err := scrapeMetrics(s)
if err != nil {
t.Fatal(err)
}
checkForExpectedMetrics(t, metrics, []string{
"process_start_time_seconds",
"process_cpu_seconds_total",
"process_open_fds",
"process_resident_memory_bytes",
})
}
func TestApiserverMetrics(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
// Make a request to the apiserver to ensure there's at least one data point
// for the metrics we're expecting -- otherwise, they won't be exported.
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
if _, err := client.Core().Pods(metav1.NamespaceDefault).List(metav1.ListOptions{}); err != nil {
t.Fatalf("unexpected error getting pods: %v", err)
}
metrics, err := scrapeMetrics(s)
if err != nil {
t.Fatal(err)
}
checkForExpectedMetrics(t, metrics, []string{
"apiserver_request_count",
"apiserver_request_latencies",
})
}

View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"objectmeta_test.go",
],
tags = ["integration"],
deps = [
"//test/integration/framework:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,27 @@
/*
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 objectmeta
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,53 @@
/*
Copyright 2016 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 objectmeta
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/test/integration/framework"
)
func TestIgnoreClusterName(t *testing.T) {
config := framework.NewMasterConfig()
_, s, closeFn := framework.RunAMaster(config)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
ns := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test-namespace",
ClusterName: "cluster-name-to-ignore",
},
}
nsNew, err := client.Core().Namespaces().Create(&ns)
assert.Nil(t, err)
assert.Equal(t, ns.Name, nsNew.Name)
assert.Empty(t, nsNew.ClusterName)
nsNew, err = client.Core().Namespaces().Update(&ns)
assert.Nil(t, err)
assert.Equal(t, ns.Name, nsNew.Name)
assert.Empty(t, nsNew.ClusterName)
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"openshift_test.go",
],
tags = ["integration"],
deps = [
"//pkg/master:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
],
)
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,27 @@
/*
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 openshift
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2015 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 openshift
import (
"testing"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kubernetes/pkg/master"
)
// This test references methods that OpenShift uses to customize the master on startup, that
// are not referenced directly by a master.
func TestMasterExportsSymbols(t *testing.T) {
_ = &master.Config{
GenericConfig: &genericapiserver.Config{
EnableMetrics: true,
},
ExtraConfig: master.ExtraConfig{
EnableLogsSupport: false,
},
}
_ = &master.Master{
GenericAPIServer: &genericapiserver.GenericAPIServer{},
}
}

38
vendor/k8s.io/kubernetes/test/integration/pods/BUILD generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"pods_test.go",
],
tags = ["integration"],
deps = [
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
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,27 @@
/*
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 pods
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,181 @@
/*
Copyright 2015 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 pods
import (
"fmt"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework"
)
func TestPodUpdateActiveDeadlineSeconds(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
ns := framework.CreateTestingNamespace("pod-activedeadline-update", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
var (
iZero = int64(0)
i30 = int64(30)
i60 = int64(60)
iNeg = int64(-1)
)
prototypePod := func() *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "xxx",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
},
},
},
}
}
cases := []struct {
name string
original *int64
update *int64
valid bool
}{
{
name: "no change, nil",
original: nil,
update: nil,
valid: true,
},
{
name: "no change, set",
original: &i30,
update: &i30,
valid: true,
},
{
name: "change to positive from nil",
original: nil,
update: &i60,
valid: true,
},
{
name: "change to smaller positive",
original: &i60,
update: &i30,
valid: true,
},
{
name: "change to larger positive",
original: &i30,
update: &i60,
valid: false,
},
{
name: "change to negative from positive",
original: &i30,
update: &iNeg,
valid: false,
},
{
name: "change to negative from nil",
original: nil,
update: &iNeg,
valid: false,
},
// zero is not allowed, must be a positive integer
{
name: "change to zero from positive",
original: &i30,
update: &iZero,
valid: false,
},
{
name: "change to nil from positive",
original: &i30,
update: nil,
valid: false,
},
}
for i, tc := range cases {
pod := prototypePod()
pod.Spec.ActiveDeadlineSeconds = tc.original
pod.ObjectMeta.Name = fmt.Sprintf("activedeadlineseconds-test-%v", i)
if _, err := client.Core().Pods(ns.Name).Create(pod); err != nil {
t.Errorf("Failed to create pod: %v", err)
}
pod.Spec.ActiveDeadlineSeconds = tc.update
_, err := client.Core().Pods(ns.Name).Update(pod)
if tc.valid && err != nil {
t.Errorf("%v: failed to update pod: %v", tc.name, err)
} else if !tc.valid && err == nil {
t.Errorf("%v: unexpected allowed update to pod", tc.name)
}
integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
}
}
func TestPodReadOnlyFilesystem(t *testing.T) {
_, s, closeFn := framework.RunAMaster(nil)
defer closeFn()
isReadOnly := true
ns := framework.CreateTestingNamespace("pod-readonly-root", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "xxx",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
SecurityContext: &v1.SecurityContext{
ReadOnlyRootFilesystem: &isReadOnly,
},
},
},
},
}
if _, err := client.Core().Pods(ns.Name).Create(pod); err != nil {
t.Errorf("Failed to create pod: %v", err)
}
integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
}

53
vendor/k8s.io/kubernetes/test/integration/quota/BUILD generated vendored Normal file
View File

@@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"quota_test.go",
],
tags = ["integration"],
deps = [
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/replication:go_default_library",
"//pkg/controller/resourcequota:go_default_library",
"//pkg/quota/generic:go_default_library",
"//pkg/quota/install:go_default_library",
"//plugin/pkg/admission/resourcequota:go_default_library",
"//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
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,27 @@
/*
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 quota
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,371 @@
/*
Copyright 2015 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 quota
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
internalinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/controller"
replicationcontroller "k8s.io/kubernetes/pkg/controller/replication"
resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota"
"k8s.io/kubernetes/pkg/quota/generic"
quotainstall "k8s.io/kubernetes/pkg/quota/install"
"k8s.io/kubernetes/plugin/pkg/admission/resourcequota"
resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
"k8s.io/kubernetes/test/integration/framework"
)
// 1.2 code gets:
// quota_test.go:95: Took 4.218619579s to scale up without quota
// quota_test.go:199: unexpected error: timed out waiting for the condition, ended with 342 pods (1 minute)
// 1.3+ code gets:
// quota_test.go:100: Took 4.196205966s to scale up without quota
// quota_test.go:115: Took 12.021640372s to scale up with quota
func TestQuota(t *testing.T) {
// Set up a master
h := &framework.MasterHolder{Initialized: make(chan struct{})}
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
<-h.Initialized
h.M.GenericAPIServer.Handler.ServeHTTP(w, req)
}))
admissionCh := make(chan struct{})
clientset := clientset.NewForConfigOrDie(&restclient.Config{QPS: -1, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
internalClientset := internalclientset.NewForConfigOrDie(&restclient.Config{QPS: -1, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
config := &resourcequotaapi.Configuration{}
admission, err := resourcequota.NewResourceQuota(config, 5, admissionCh)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
admission.SetInternalKubeClientSet(internalClientset)
internalInformers := internalinformers.NewSharedInformerFactory(internalClientset, controller.NoResyncPeriodFunc())
admission.SetInternalKubeInformerFactory(internalInformers)
qca := quotainstall.NewQuotaConfigurationForAdmission()
admission.SetQuotaConfiguration(qca)
defer close(admissionCh)
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.AdmissionControl = admission
_, _, closeFn := framework.RunAMasterUsingServer(masterConfig, s, h)
defer closeFn()
ns := framework.CreateTestingNamespace("quotaed", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
ns2 := framework.CreateTestingNamespace("non-quotaed", s, t)
defer framework.DeleteTestingNamespace(ns2, s, t)
controllerCh := make(chan struct{})
defer close(controllerCh)
informers := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc())
rm := replicationcontroller.NewReplicationManager(
informers.Core().V1().Pods(),
informers.Core().V1().ReplicationControllers(),
clientset,
replicationcontroller.BurstReplicas,
)
rm.SetEventRecorder(&record.FakeRecorder{})
go rm.Run(3, controllerCh)
discoveryFunc := clientset.Discovery().ServerPreferredNamespacedResources
listerFuncForResource := generic.ListerFuncForResourceFunc(informers.ForResource)
qc := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource)
informersStarted := make(chan struct{})
resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
QuotaClient: clientset.Core(),
ResourceQuotaInformer: informers.Core().V1().ResourceQuotas(),
ResyncPeriod: controller.NoResyncPeriodFunc,
InformerFactory: informers,
ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
DiscoveryFunc: discoveryFunc,
IgnoredResourcesFunc: qc.IgnoredResources,
InformersStarted: informersStarted,
Registry: generic.NewRegistry(qc.Evaluators()),
}
resourceQuotaController, err := resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
go resourceQuotaController.Run(2, controllerCh)
// Periodically the quota controller to detect new resource types
go resourceQuotaController.Sync(discoveryFunc, 30*time.Second, controllerCh)
internalInformers.Start(controllerCh)
informers.Start(controllerCh)
close(informersStarted)
startTime := time.Now()
scale(t, ns2.Name, clientset)
endTime := time.Now()
t.Logf("Took %v to scale up without quota", endTime.Sub(startTime))
quota := &v1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "quota",
Namespace: ns.Name,
},
Spec: v1.ResourceQuotaSpec{
Hard: v1.ResourceList{
v1.ResourcePods: resource.MustParse("1000"),
},
},
}
waitForQuota(t, quota, clientset)
startTime = time.Now()
scale(t, "quotaed", clientset)
endTime = time.Now()
t.Logf("Took %v to scale up with quota", endTime.Sub(startTime))
}
func waitForQuota(t *testing.T, quota *v1.ResourceQuota, clientset *clientset.Clientset) {
w, err := clientset.Core().ResourceQuotas(quota.Namespace).Watch(metav1.SingleObject(metav1.ObjectMeta{Name: quota.Name}))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := clientset.Core().ResourceQuotas(quota.Namespace).Create(quota); err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = watch.Until(1*time.Minute, w, func(event watch.Event) (bool, error) {
switch event.Type {
case watch.Modified:
default:
return false, nil
}
switch cast := event.Object.(type) {
case *v1.ResourceQuota:
if len(cast.Status.Hard) > 0 {
return true, nil
}
}
return false, nil
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func scale(t *testing.T, namespace string, clientset *clientset.Clientset) {
target := int32(100)
rc := &v1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: namespace,
},
Spec: v1.ReplicationControllerSpec{
Replicas: &target,
Selector: map[string]string{"foo": "bar"},
Template: &v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "container",
Image: "busybox",
},
},
},
},
},
}
w, err := clientset.Core().ReplicationControllers(namespace).Watch(metav1.SingleObject(metav1.ObjectMeta{Name: rc.Name}))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := clientset.Core().ReplicationControllers(namespace).Create(rc); err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = watch.Until(3*time.Minute, w, func(event watch.Event) (bool, error) {
switch event.Type {
case watch.Modified:
default:
return false, nil
}
switch cast := event.Object.(type) {
case *v1.ReplicationController:
fmt.Printf("Found %v of %v replicas\n", int(cast.Status.Replicas), target)
if cast.Status.Replicas == target {
return true, nil
}
}
return false, nil
})
if err != nil {
pods, _ := clientset.Core().Pods(namespace).List(metav1.ListOptions{LabelSelector: labels.Everything().String(), FieldSelector: fields.Everything().String()})
t.Fatalf("unexpected error: %v, ended with %v pods", err, len(pods.Items))
}
}
func TestQuotaLimitedResourceDenial(t *testing.T) {
// Set up a master
h := &framework.MasterHolder{Initialized: make(chan struct{})}
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
<-h.Initialized
h.M.GenericAPIServer.Handler.ServeHTTP(w, req)
}))
admissionCh := make(chan struct{})
clientset := clientset.NewForConfigOrDie(&restclient.Config{QPS: -1, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
internalClientset := internalclientset.NewForConfigOrDie(&restclient.Config{QPS: -1, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
// stop creation of a pod resource unless there is a quota
config := &resourcequotaapi.Configuration{
LimitedResources: []resourcequotaapi.LimitedResource{
{
Resource: "pods",
MatchContains: []string{"pods"},
},
},
}
qca := quotainstall.NewQuotaConfigurationForAdmission()
admission, err := resourcequota.NewResourceQuota(config, 5, admissionCh)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
admission.SetInternalKubeClientSet(internalClientset)
internalInformers := internalinformers.NewSharedInformerFactory(internalClientset, controller.NoResyncPeriodFunc())
admission.SetInternalKubeInformerFactory(internalInformers)
admission.SetQuotaConfiguration(qca)
defer close(admissionCh)
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.GenericConfig.AdmissionControl = admission
_, _, closeFn := framework.RunAMasterUsingServer(masterConfig, s, h)
defer closeFn()
ns := framework.CreateTestingNamespace("quota", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
controllerCh := make(chan struct{})
defer close(controllerCh)
informers := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc())
rm := replicationcontroller.NewReplicationManager(
informers.Core().V1().Pods(),
informers.Core().V1().ReplicationControllers(),
clientset,
replicationcontroller.BurstReplicas,
)
rm.SetEventRecorder(&record.FakeRecorder{})
go rm.Run(3, controllerCh)
discoveryFunc := clientset.Discovery().ServerPreferredNamespacedResources
listerFuncForResource := generic.ListerFuncForResourceFunc(informers.ForResource)
qc := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource)
informersStarted := make(chan struct{})
resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
QuotaClient: clientset.Core(),
ResourceQuotaInformer: informers.Core().V1().ResourceQuotas(),
ResyncPeriod: controller.NoResyncPeriodFunc,
InformerFactory: informers,
ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
DiscoveryFunc: discoveryFunc,
IgnoredResourcesFunc: qc.IgnoredResources,
InformersStarted: informersStarted,
Registry: generic.NewRegistry(qc.Evaluators()),
}
resourceQuotaController, err := resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
go resourceQuotaController.Run(2, controllerCh)
// Periodically the quota controller to detect new resource types
go resourceQuotaController.Sync(discoveryFunc, 30*time.Second, controllerCh)
internalInformers.Start(controllerCh)
informers.Start(controllerCh)
close(informersStarted)
// try to create a pod
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: ns.Name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "container",
Image: "busybox",
},
},
},
}
if _, err := clientset.Core().Pods(ns.Name).Create(pod); err == nil {
t.Fatalf("expected error for insufficient quota")
}
// now create a covering quota
// note: limited resource does a matchContains, so we now have "pods" matching "pods" and "count/pods"
quota := &v1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "quota",
Namespace: ns.Name,
},
Spec: v1.ResourceQuotaSpec{
Hard: v1.ResourceList{
v1.ResourcePods: resource.MustParse("1000"),
v1.ResourceName("count/pods"): resource.MustParse("1000"),
},
},
}
waitForQuota(t, quota, clientset)
// attempt to create a new pod once the quota is propagated
err = wait.PollImmediate(5*time.Second, time.Minute, func() (bool, error) {
// retry until we succeed (to allow time for all changes to propagate)
if _, err := clientset.Core().Pods(ns.Name).Create(pod); err == nil {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

View File

@@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"replicaset_test.go",
],
tags = ["integration"],
deps = [
"//pkg/api/v1/pod:go_default_library",
"//pkg/controller/replicaset:go_default_library",
"//pkg/util/slice:go_default_library",
"//test/integration/framework:go_default_library",
"//test/utils:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/util/retry:go_default_library",
],
)
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,27 @@
/*
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 replicaset
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"main_test.go",
"replicationcontroller_test.go",
],
tags = ["integration"],
deps = [
"//pkg/api/v1/pod:go_default_library",
"//pkg/controller/replication:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/util/retry:go_default_library",
],
)
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,27 @@
/*
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 replicationcontroller
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -0,0 +1,887 @@
/*
Copyright 2015 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 replicationcontroller
import (
"fmt"
"net/http/httptest"
"reflect"
"testing"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
typedv1 "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/retry"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/controller/replication"
"k8s.io/kubernetes/test/integration/framework"
)
const (
interval = 100 * time.Millisecond
timeout = 60 * time.Second
)
func labelMap() map[string]string {
return map[string]string{"foo": "bar"}
}
func newRC(name, namespace string, replicas int) *v1.ReplicationController {
replicasCopy := int32(replicas)
return &v1.ReplicationController{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
Spec: v1.ReplicationControllerSpec{
Selector: labelMap(),
Replicas: &replicasCopy,
Template: &v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labelMap(),
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
},
},
},
},
},
}
}
func newMatchingPod(podName, namespace string) *v1.Pod {
return &v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: labelMap(),
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
},
},
},
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
}
}
// verifyRemainingObjects verifies if the number of the remaining replication
// controllers and pods are rcNum and podNum. It returns error if the
// communication with the API server fails.
func verifyRemainingObjects(t *testing.T, clientSet clientset.Interface, namespace string, rcNum, podNum int) (bool, error) {
rcClient := clientSet.CoreV1().ReplicationControllers(namespace)
podClient := clientSet.CoreV1().Pods(namespace)
pods, err := podClient.List(metav1.ListOptions{})
if err != nil {
return false, fmt.Errorf("Failed to list pods: %v", err)
}
var ret = true
if len(pods.Items) != podNum {
ret = false
t.Logf("expect %d pods, got %d pods", podNum, len(pods.Items))
}
rcs, err := rcClient.List(metav1.ListOptions{})
if err != nil {
return false, fmt.Errorf("Failed to list replication controllers: %v", err)
}
if len(rcs.Items) != rcNum {
ret = false
t.Logf("expect %d RCs, got %d RCs", rcNum, len(rcs.Items))
}
return ret, nil
}
func rmSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, *replication.ReplicationManager, informers.SharedInformerFactory, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
config := restclient.Config{Host: s.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
resyncPeriod := 12 * time.Hour
informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "rc-informers")), resyncPeriod)
rm := replication.NewReplicationManager(
informers.Core().V1().Pods(),
informers.Core().V1().ReplicationControllers(),
clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "replication-controller")),
replication.BurstReplicas,
)
if err != nil {
t.Fatalf("Failed to create replication controller")
}
return s, closeFn, rm, informers, clientSet
}
func rmSimpleSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
config := restclient.Config{Host: s.URL}
clientSet, err := clientset.NewForConfig(&config)
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
return s, closeFn, clientSet
}
// Run RC controller and informers
func runControllerAndInformers(t *testing.T, rm *replication.ReplicationManager, informers informers.SharedInformerFactory, podNum int) chan struct{} {
stopCh := make(chan struct{})
informers.Start(stopCh)
waitToObservePods(t, informers.Core().V1().Pods().Informer(), podNum)
go rm.Run(5, stopCh)
return stopCh
}
// wait for the podInformer to observe the pods. Call this function before
// running the RC controller to prevent the rc manager from creating new pods
// rather than adopting the existing ones.
func waitToObservePods(t *testing.T, podInformer cache.SharedIndexInformer, podNum int) {
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
objects := podInformer.GetIndexer().List()
return len(objects) == podNum, nil
}); err != nil {
t.Fatalf("Error encountered when waiting for podInformer to observe the pods: %v", err)
}
}
func createRCsPods(t *testing.T, clientSet clientset.Interface, rcs []*v1.ReplicationController, pods []*v1.Pod) ([]*v1.ReplicationController, []*v1.Pod) {
var createdRCs []*v1.ReplicationController
var createdPods []*v1.Pod
for _, rc := range rcs {
createdRC, err := clientSet.CoreV1().ReplicationControllers(rc.Namespace).Create(rc)
if err != nil {
t.Fatalf("Failed to create replication controller %s: %v", rc.Name, err)
}
createdRCs = append(createdRCs, createdRC)
}
for _, pod := range pods {
createdPod, err := clientSet.CoreV1().Pods(pod.Namespace).Create(pod)
if err != nil {
t.Fatalf("Failed to create pod %s: %v", pod.Name, err)
}
createdPods = append(createdPods, createdPod)
}
return createdRCs, createdPods
}
// Verify .Status.Replicas is equal to .Spec.Replicas
func waitRCStable(t *testing.T, clientSet clientset.Interface, rc *v1.ReplicationController) {
rcClient := clientSet.CoreV1().ReplicationControllers(rc.Namespace)
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newRC, err := rcClient.Get(rc.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return newRC.Status.Replicas == *rc.Spec.Replicas, nil
}); err != nil {
t.Fatalf("Failed to verify .Status.Replicas is equal to .Spec.Replicas for rc %s: %v", rc.Name, err)
}
}
// Update .Spec.Replicas to replicas and verify .Status.Replicas is changed accordingly
func scaleRC(t *testing.T, c clientset.Interface, rc *v1.ReplicationController, replicas int32) {
rcClient := c.CoreV1().ReplicationControllers(rc.Namespace)
rc = updateRC(t, rcClient, rc.Name, func(rc *v1.ReplicationController) {
*rc.Spec.Replicas = replicas
})
waitRCStable(t, c, rc)
}
func updatePod(t *testing.T, podClient typedv1.PodInterface, podName string, updateFunc func(*v1.Pod)) *v1.Pod {
var pod *v1.Pod
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
newPod, err := podClient.Get(podName, metav1.GetOptions{})
if err != nil {
return err
}
updateFunc(newPod)
pod, err = podClient.Update(newPod)
return err
}); err != nil {
t.Fatalf("Failed to update pod %s: %v", podName, err)
}
return pod
}
func updatePodStatus(t *testing.T, podClient typedv1.PodInterface, pod *v1.Pod, updateStatusFunc func(*v1.Pod)) {
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
newPod, err := podClient.Get(pod.Name, metav1.GetOptions{})
if err != nil {
return err
}
updateStatusFunc(newPod)
_, err = podClient.UpdateStatus(newPod)
return err
}); err != nil {
t.Fatalf("Failed to update status of pod %s: %v", pod.Name, err)
}
}
func getPods(t *testing.T, podClient typedv1.PodInterface, labelMap map[string]string) *v1.PodList {
podSelector := labels.Set(labelMap).AsSelector()
options := metav1.ListOptions{LabelSelector: podSelector.String()}
pods, err := podClient.List(options)
if err != nil {
t.Fatalf("Failed obtaining a list of pods that match the pod labels %v: %v", labelMap, err)
}
return pods
}
func updateRC(t *testing.T, rcClient typedv1.ReplicationControllerInterface, rcName string, updateFunc func(*v1.ReplicationController)) *v1.ReplicationController {
var rc *v1.ReplicationController
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
newRC, err := rcClient.Get(rcName, metav1.GetOptions{})
if err != nil {
return err
}
updateFunc(newRC)
rc, err = rcClient.Update(newRC)
return err
}); err != nil {
t.Fatalf("Failed to update rc %s: %v", rcName, err)
}
return rc
}
// Verify ControllerRef of a RC pod that has incorrect attributes is automatically patched by the RC
func testPodControllerRefPatch(t *testing.T, c clientset.Interface, pod *v1.Pod, ownerReference *metav1.OwnerReference, rc *v1.ReplicationController, expectedOwnerReferenceNum int) {
ns := rc.Namespace
podClient := c.CoreV1().Pods(ns)
updatePod(t, podClient, pod.Name, func(pod *v1.Pod) {
pod.OwnerReferences = []metav1.OwnerReference{*ownerReference}
})
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newPod, err := podClient.Get(pod.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return metav1.GetControllerOf(newPod) != nil, nil
}); err != nil {
t.Fatalf("Failed to verify ControllerRef for the pod %s is not nil: %v", pod.Name, err)
}
newPod, err := podClient.Get(pod.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to obtain pod %s: %v", pod.Name, err)
}
controllerRef := metav1.GetControllerOf(newPod)
if controllerRef.UID != rc.UID {
t.Fatalf("RC owner of the pod %s has a different UID: Expected %v, got %v", newPod.Name, rc.UID, controllerRef.UID)
}
ownerReferenceNum := len(newPod.GetOwnerReferences())
if ownerReferenceNum != expectedOwnerReferenceNum {
t.Fatalf("Unexpected number of owner references for pod %s: Expected %d, got %d", newPod.Name, expectedOwnerReferenceNum, ownerReferenceNum)
}
}
func setPodsReadyCondition(t *testing.T, clientSet clientset.Interface, pods *v1.PodList, conditionStatus v1.ConditionStatus, lastTransitionTime time.Time) {
replicas := int32(len(pods.Items))
var readyPods int32
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
readyPods = 0
for i := range pods.Items {
pod := &pods.Items[i]
if podutil.IsPodReady(pod) {
readyPods++
continue
}
pod.Status.Phase = v1.PodRunning
_, condition := podutil.GetPodCondition(&pod.Status, v1.PodReady)
if condition != nil {
condition.Status = conditionStatus
condition.LastTransitionTime = metav1.Time{Time: lastTransitionTime}
} else {
condition = &v1.PodCondition{
Type: v1.PodReady,
Status: conditionStatus,
LastTransitionTime: metav1.Time{Time: lastTransitionTime},
}
pod.Status.Conditions = append(pod.Status.Conditions, *condition)
}
_, err := clientSet.CoreV1().Pods(pod.Namespace).UpdateStatus(pod)
if err != nil {
// When status fails to be updated, we continue to next pod
continue
}
readyPods++
}
return readyPods >= replicas, nil
})
if err != nil {
t.Fatalf("failed to mark all ReplicationController pods to ready: %v", err)
}
}
func testScalingUsingScaleSubresource(t *testing.T, c clientset.Interface, rc *v1.ReplicationController, replicas int32) {
ns := rc.Namespace
rcClient := c.CoreV1().ReplicationControllers(ns)
newRC, err := rcClient.Get(rc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to obtain rc %s: %v", rc.Name, err)
}
kind := "ReplicationController"
scaleClient := c.ExtensionsV1beta1().Scales(ns)
scale, err := scaleClient.Get(kind, rc.Name)
if err != nil {
t.Fatalf("Failed to obtain scale subresource for rc %s: %v", rc.Name, err)
}
if scale.Spec.Replicas != *newRC.Spec.Replicas {
t.Fatalf("Scale subresource for rc %s does not match .Spec.Replicas: expected %d, got %d", rc.Name, *newRC.Spec.Replicas, scale.Spec.Replicas)
}
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
scale, err := scaleClient.Get(kind, rc.Name)
if err != nil {
return err
}
scale.Spec.Replicas = replicas
_, err = scaleClient.Update(kind, scale)
return err
}); err != nil {
t.Fatalf("Failed to set .Spec.Replicas of scale subresource for rc %s: %v", rc.Name, err)
}
newRC, err = rcClient.Get(rc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to obtain rc %s: %v", rc.Name, err)
}
if *newRC.Spec.Replicas != replicas {
t.Fatalf(".Spec.Replicas of rc %s does not match its scale subresource: expected %d, got %d", rc.Name, replicas, *newRC.Spec.Replicas)
}
}
func TestAdoption(t *testing.T) {
boolPtr := func(b bool) *bool { return &b }
testCases := []struct {
name string
existingOwnerReferences func(rc *v1.ReplicationController) []metav1.OwnerReference
expectedOwnerReferences func(rc *v1.ReplicationController) []metav1.OwnerReference
}{
{
"pod refers rc as an owner, not a controller",
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController"}}
},
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController", Controller: boolPtr(true), BlockOwnerDeletion: boolPtr(true)}}
},
},
{
"pod doesn't have owner references",
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{}
},
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController", Controller: boolPtr(true), BlockOwnerDeletion: boolPtr(true)}}
},
},
{
"pod refers rc as a controller",
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController", Controller: boolPtr(true)}}
},
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController", Controller: boolPtr(true)}}
},
},
{
"pod refers other rc as the controller, refers the rc as an owner",
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{
{UID: "1", Name: "anotherRC", APIVersion: "v1", Kind: "ReplicationController", Controller: boolPtr(true)},
{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController"},
}
},
func(rc *v1.ReplicationController) []metav1.OwnerReference {
return []metav1.OwnerReference{
{UID: "1", Name: "anotherRC", APIVersion: "v1", Kind: "ReplicationController", Controller: boolPtr(true)},
{UID: rc.UID, Name: rc.Name, APIVersion: "v1", Kind: "ReplicationController"},
}
},
},
}
for i, tc := range testCases {
func() {
s, closeFn, rm, informers, clientSet := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace(fmt.Sprintf("rc-adoption-%d", i), s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rcClient := clientSet.CoreV1().ReplicationControllers(ns.Name)
podClient := clientSet.CoreV1().Pods(ns.Name)
const rcName = "rc"
rc, err := rcClient.Create(newRC(rcName, ns.Name, 1))
if err != nil {
t.Fatalf("Failed to create replication controllers: %v", err)
}
podName := fmt.Sprintf("pod%d", i)
pod := newMatchingPod(podName, ns.Name)
pod.OwnerReferences = tc.existingOwnerReferences(rc)
_, err = podClient.Create(pod)
if err != nil {
t.Fatalf("Failed to create Pod: %v", err)
}
stopCh := runControllerAndInformers(t, rm, informers, 1)
defer close(stopCh)
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
updatedPod, err := podClient.Get(pod.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
if e, a := tc.expectedOwnerReferences(rc), updatedPod.OwnerReferences; reflect.DeepEqual(e, a) {
return true, nil
} else {
t.Logf("ownerReferences don't match, expect %v, got %v", e, a)
return false, nil
}
}); err != nil {
t.Fatalf("test %q failed: %v", tc.name, err)
}
}()
}
}
func TestSpecReplicasChange(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-spec-replicas-change", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rc := newRC("rc", ns.Name, 2)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
// Update .Spec.Replicas and verify .Status.Replicas is changed accordingly
scaleRC(t, c, rc, 3)
scaleRC(t, c, rc, 0)
scaleRC(t, c, rc, 2)
// Add a template annotation change to test RC's status does update
// without .Spec.Replicas change
rcClient := c.CoreV1().ReplicationControllers(ns.Name)
var oldGeneration int64
newRC := updateRC(t, rcClient, rc.Name, func(rc *v1.ReplicationController) {
oldGeneration = rc.Generation
rc.Spec.Template.Annotations = map[string]string{"test": "annotation"}
})
savedGeneration := newRC.Generation
if savedGeneration == oldGeneration {
t.Fatalf("Failed to verify .Generation has incremented for rc %s", rc.Name)
}
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newRC, err := rcClient.Get(rc.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return newRC.Status.ObservedGeneration >= savedGeneration, nil
}); err != nil {
t.Fatalf("Failed to verify .Status.ObservedGeneration has incremented for rc %s: %v", rc.Name, err)
}
}
func TestDeletingAndFailedPods(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-deleting-and-failed-pods", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rc := newRC("rc", ns.Name, 2)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
// Verify RC creates 2 pods
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 2 {
t.Fatalf("len(pods) = %d, want 2", len(pods.Items))
}
// Set first pod as deleting pod
// Set finalizers for the pod to simulate pending deletion status
deletingPod := &pods.Items[0]
updatePod(t, podClient, deletingPod.Name, func(pod *v1.Pod) {
pod.Finalizers = []string{"fake.example.com/blockDeletion"}
})
if err := c.CoreV1().Pods(ns.Name).Delete(deletingPod.Name, &metav1.DeleteOptions{}); err != nil {
t.Fatalf("Error deleting pod %s: %v", deletingPod.Name, err)
}
// Set second pod as failed pod
failedPod := &pods.Items[1]
updatePodStatus(t, podClient, failedPod, func(pod *v1.Pod) {
pod.Status.Phase = v1.PodFailed
})
// Pool until 2 new pods have been created to replace deleting and failed pods
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
pods = getPods(t, podClient, labelMap())
return len(pods.Items) == 4, nil
}); err != nil {
t.Fatalf("Failed to verify 2 new pods have been created (expected 4 pods): %v", err)
}
// Verify deleting and failed pods are among the four pods
foundDeletingPod := false
foundFailedPod := false
for _, pod := range pods.Items {
if pod.UID == deletingPod.UID {
foundDeletingPod = true
}
if pod.UID == failedPod.UID {
foundFailedPod = true
}
}
// Verify deleting pod exists
if !foundDeletingPod {
t.Fatalf("expected deleting pod %s exists, but it is not found", deletingPod.Name)
}
// Verify failed pod exists
if !foundFailedPod {
t.Fatalf("expected failed pod %s exists, but it is not found", failedPod.Name)
}
}
func TestOverlappingRCs(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-overlapping-rcs", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
// Create 2 RCs with identical selectors
for i := 0; i < 2; i++ {
// One RC has 1 replica, and another has 2 replicas
rc := newRC(fmt.Sprintf("rc-%d", i+1), ns.Name, i+1)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
waitRCStable(t, c, rcs[0])
}
// Expect 3 total Pods to be created
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 3 {
t.Errorf("len(pods) = %d, want 3", len(pods.Items))
}
// Expect both RCs have .status.replicas = .spec.replicas
for i := 0; i < 2; i++ {
newRC, err := c.CoreV1().ReplicationControllers(ns.Name).Get(fmt.Sprintf("rc-%d", i+1), metav1.GetOptions{})
if err != nil {
t.Fatalf("failed to obtain rc rc-%d: %v", i+1, err)
}
if newRC.Status.Replicas != *newRC.Spec.Replicas {
t.Fatalf(".Status.Replicas %d is not equal to .Spec.Replicas %d", newRC.Status.Replicas, *newRC.Spec.Replicas)
}
}
}
func TestPodOrphaningAndAdoptionWhenLabelsChange(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-pod-orphaning-and-adoption-when-labels-change", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rc := newRC("rc", ns.Name, 1)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
// Orphaning: RC should remove OwnerReference from a pod when the pod's labels change to not match its labels
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 1 {
t.Fatalf("len(pods) = %d, want 1", len(pods.Items))
}
pod := &pods.Items[0]
// Start by verifying ControllerRef for the pod is not nil
if metav1.GetControllerOf(pod) == nil {
t.Fatalf("ControllerRef of pod %s is nil", pod.Name)
}
newLabelMap := map[string]string{"new-foo": "new-bar"}
updatePod(t, podClient, pod.Name, func(pod *v1.Pod) {
pod.Labels = newLabelMap
})
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newPod, err := podClient.Get(pod.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
pod = newPod
return metav1.GetControllerOf(newPod) == nil, nil
}); err != nil {
t.Fatalf("Failed to verify ControllerRef for the pod %s is nil: %v", pod.Name, err)
}
// Adoption: RC should add ControllerRef to a pod when the pod's labels change to match its labels
updatePod(t, podClient, pod.Name, func(pod *v1.Pod) {
pod.Labels = labelMap()
})
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newPod, err := podClient.Get(pod.Name, metav1.GetOptions{})
if err != nil {
// If the pod is not found, it means the RC picks the pod for deletion (it is extra)
// Verify there is only one pod in namespace and it has ControllerRef to the RC
if errors.IsNotFound(err) {
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 1 {
return false, fmt.Errorf("Expected 1 pod in current namespace, got %d", len(pods.Items))
}
// Set the pod accordingly
pod = &pods.Items[0]
return true, nil
}
return false, err
}
// Always update the pod so that we can save a GET call to API server later
pod = newPod
// If the pod is found, verify the pod has a ControllerRef
return metav1.GetControllerOf(newPod) != nil, nil
}); err != nil {
t.Fatalf("Failed to verify ControllerRef for pod %s is not nil: %v", pod.Name, err)
}
// Verify the pod has a ControllerRef to the RC
// Do nothing if the pod is nil (i.e., has been picked for deletion)
if pod != nil {
controllerRef := metav1.GetControllerOf(pod)
if controllerRef.UID != rc.UID {
t.Fatalf("RC owner of the pod %s has a different UID: Expected %v, got %v", pod.Name, rc.UID, controllerRef.UID)
}
}
}
func TestGeneralPodAdoption(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-general-pod-adoption", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rc := newRC("rc", ns.Name, 1)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 1 {
t.Fatalf("len(pods) = %d, want 1", len(pods.Items))
}
pod := &pods.Items[0]
var falseVar = false
// When the only OwnerReference of the pod points to another type of API object such as statefulset
// with Controller=false, the RC should add a second OwnerReference (ControllerRef) pointing to itself
// with Controller=true
ownerReference := metav1.OwnerReference{UID: uuid.NewUUID(), APIVersion: "apps/v1beta1", Kind: "StatefulSet", Name: rc.Name, Controller: &falseVar}
testPodControllerRefPatch(t, c, pod, &ownerReference, rc, 2)
// When the only OwnerReference of the pod points to the RC, but Controller=false
ownerReference = metav1.OwnerReference{UID: rc.UID, APIVersion: "v1", Kind: "ReplicationController", Name: rc.Name, Controller: &falseVar}
testPodControllerRefPatch(t, c, pod, &ownerReference, rc, 1)
}
func TestReadyAndAvailableReplicas(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-ready-and-available-replicas", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rc := newRC("rc", ns.Name, 3)
rc.Spec.MinReadySeconds = 3600
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
// First verify no pod is available
if rc.Status.AvailableReplicas != 0 {
t.Fatalf("Unexpected .Status.AvailableReplicas: Expected 0, saw %d", rc.Status.AvailableReplicas)
}
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 3 {
t.Fatalf("len(pods) = %d, want 3", len(pods.Items))
}
// Separate 3 pods into their own list
firstPodList := &v1.PodList{Items: pods.Items[:1]}
secondPodList := &v1.PodList{Items: pods.Items[1:2]}
thirdPodList := &v1.PodList{Items: pods.Items[2:]}
// First pod: Running, but not Ready
// by setting the Ready condition to false with LastTransitionTime to be now
setPodsReadyCondition(t, c, firstPodList, v1.ConditionFalse, time.Now())
// Second pod: Running and Ready, but not Available
// by setting LastTransitionTime to now
setPodsReadyCondition(t, c, secondPodList, v1.ConditionTrue, time.Now())
// Third pod: Running, Ready, and Available
// by setting LastTransitionTime to more than 3600 seconds ago
setPodsReadyCondition(t, c, thirdPodList, v1.ConditionTrue, time.Now().Add(-120*time.Minute))
rcClient := c.CoreV1().ReplicationControllers(ns.Name)
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newRC, err := rcClient.Get(rc.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
// Verify 3 pods exist, 2 pods are Ready, and 1 pod is Available
return newRC.Status.Replicas == 3 && newRC.Status.ReadyReplicas == 2 && newRC.Status.AvailableReplicas == 1, nil
}); err != nil {
t.Fatalf("Failed to verify number of Replicas, ReadyReplicas and AvailableReplicas of rc %s to be as expected: %v", rc.Name, err)
}
}
func TestRCScaleSubresource(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-rc-scale-subresource", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rc := newRC("rc", ns.Name, 1)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
// Use scale subresource to scale up .Spec.Replicas to 3
testScalingUsingScaleSubresource(t, c, rc, 3)
// Use the scale subresource to scale down .Spec.Replicas to 0
testScalingUsingScaleSubresource(t, c, rc, 0)
}
func TestExtraPodsAdoptionAndDeletion(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-extra-pods-adoption-and-deletion", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rc := newRC("rc", ns.Name, 2)
// Create 3 pods, RC should adopt only 2 of them
podList := []*v1.Pod{}
for i := 0; i < 3; i++ {
pod := newMatchingPod(fmt.Sprintf("pod-%d", i+1), ns.Name)
pod.Labels = labelMap()
podList = append(podList, pod)
}
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, podList)
rc = rcs[0]
stopCh := runControllerAndInformers(t, rm, informers, 3)
defer close(stopCh)
waitRCStable(t, c, rc)
// Verify the extra pod is deleted eventually by determining whether number of
// all pods within namespace matches .spec.replicas of the RC (2 in this case)
podClient := c.CoreV1().Pods(ns.Name)
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
// All pods have labelMap as their labels
pods := getPods(t, podClient, labelMap())
return int32(len(pods.Items)) == *rc.Spec.Replicas, nil
}); err != nil {
t.Fatalf("Failed to verify number of all pods within current namespace matches .spec.replicas of rc %s: %v", rc.Name, err)
}
}
func TestFullyLabeledReplicas(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-fully-labeled-replicas", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
extraLabelMap := map[string]string{"foo": "bar", "extraKey": "extraValue"}
rc := newRC("rc", ns.Name, 2)
rcs, _ := createRCsPods(t, c, []*v1.ReplicationController{rc}, []*v1.Pod{})
rc = rcs[0]
waitRCStable(t, c, rc)
// Change RC's template labels to have extra labels, but not its selector
rcClient := c.CoreV1().ReplicationControllers(ns.Name)
updateRC(t, rcClient, rc.Name, func(rc *v1.ReplicationController) {
rc.Spec.Template.Labels = extraLabelMap
})
// Set one of the pods to have extra labels
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 2 {
t.Fatalf("len(pods) = %d, want 2", len(pods.Items))
}
fullyLabeledPod := &pods.Items[0]
updatePod(t, podClient, fullyLabeledPod.Name, func(pod *v1.Pod) {
pod.Labels = extraLabelMap
})
// Verify only one pod is fully labeled
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newRC, err := rcClient.Get(rc.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return (newRC.Status.Replicas == 2 && newRC.Status.FullyLabeledReplicas == 1), nil
}); err != nil {
t.Fatalf("Failed to verify only one pod is fully labeled: %v", err)
}
}

38
vendor/k8s.io/kubernetes/test/integration/scale/BUILD generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = ["scale_test.go"],
tags = ["integration"],
deps = [
"//cmd/kube-apiserver/app/testing:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/coreos/etcd/etcdserver/api/v3rpc:go_default_library",
"//vendor/github.com/coreos/pkg/capnslog:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
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,242 @@
/*
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 scale
import (
"encoding/json"
"path"
"strings"
"testing"
_ "github.com/coreos/etcd/etcdserver/api/v3rpc" // Force package logger init.
"github.com/coreos/pkg/capnslog"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
apitesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/framework"
)
type subresourceTest struct {
resource schema.GroupVersionResource
kind schema.GroupVersionKind
}
func makeGVR(group, version, resource string) schema.GroupVersionResource {
return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
}
func makeGVK(group, version, kind string) schema.GroupVersionKind {
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
}
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}
func TestScaleSubresources(t *testing.T) {
clientSet, tearDown := setup(t)
defer tearDown()
resourceLists, err := clientSet.Discovery().ServerResources()
if err != nil {
t.Fatal(err)
}
expectedScaleSubresources := map[schema.GroupVersionResource]schema.GroupVersionKind{
makeGVR("", "v1", "replicationcontrollers/scale"): makeGVK("autoscaling", "v1", "Scale"),
makeGVR("extensions", "v1beta1", "deployments/scale"): makeGVK("extensions", "v1beta1", "Scale"),
makeGVR("extensions", "v1beta1", "replicationcontrollers/scale"): makeGVK("extensions", "v1beta1", "Scale"),
makeGVR("extensions", "v1beta1", "replicasets/scale"): makeGVK("extensions", "v1beta1", "Scale"),
makeGVR("apps", "v1beta1", "deployments/scale"): makeGVK("apps", "v1beta1", "Scale"),
makeGVR("apps", "v1beta1", "statefulsets/scale"): makeGVK("apps", "v1beta1", "Scale"),
makeGVR("apps", "v1beta2", "deployments/scale"): makeGVK("apps", "v1beta2", "Scale"),
makeGVR("apps", "v1beta2", "replicasets/scale"): makeGVK("apps", "v1beta2", "Scale"),
makeGVR("apps", "v1beta2", "statefulsets/scale"): makeGVK("apps", "v1beta2", "Scale"),
makeGVR("apps", "v1", "deployments/scale"): makeGVK("autoscaling", "v1", "Scale"),
makeGVR("apps", "v1", "replicasets/scale"): makeGVK("autoscaling", "v1", "Scale"),
makeGVR("apps", "v1", "statefulsets/scale"): makeGVK("autoscaling", "v1", "Scale"),
}
autoscalingGVK := schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}
discoveredScaleSubresources := map[schema.GroupVersionResource]schema.GroupVersionKind{}
for _, resourceList := range resourceLists {
containingGV, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
t.Fatalf("error getting group version for %#v: %v", resourceList, err)
}
for _, resource := range resourceList.APIResources {
if !strings.HasSuffix(resource.Name, "/scale") {
continue
}
gvr := containingGV.WithResource(resource.Name)
if _, exists := discoveredScaleSubresources[gvr]; exists {
t.Errorf("scale subresource %#v listed multiple times in discovery", gvr)
continue
}
gvk := containingGV.WithKind(resource.Kind)
if resource.Group != "" {
gvk.Group = resource.Group
}
if resource.Version != "" {
gvk.Version = resource.Version
}
discoveredScaleSubresources[gvr] = gvk
}
}
// Ensure nothing is missing
for gvr, gvk := range expectedScaleSubresources {
if _, ok := discoveredScaleSubresources[gvr]; !ok {
t.Errorf("expected scale subresource %#v of kind %#v was missing from discovery", gvr, gvk)
}
}
// Ensure discovery lists expected types
for gvr, gvk := range discoveredScaleSubresources {
if expectedGVK, expected := expectedScaleSubresources[gvr]; !expected {
if gvk == autoscalingGVK {
t.Errorf("unexpected scale subresource %#v of kind %#v. new scale subresource should be added to expectedScaleSubresources", gvr, gvk)
} else {
t.Errorf("unexpected scale subresource %#v of kind %#v. new scale resources are expected to use Scale from the autoscaling/v1 API group", gvr, gvk)
}
continue
} else if expectedGVK != gvk {
t.Errorf("scale subresource %#v should be of kind %#v, but %#v was listed in discovery", gvr, expectedGVK, gvk)
continue
}
}
// Create objects required to exercise scale subresources
if _, err := clientSet.CoreV1().ReplicationControllers("default").Create(rcStub); err != nil {
t.Fatal(err)
}
if _, err := clientSet.AppsV1beta2().ReplicaSets("default").Create(rsStub); err != nil {
t.Fatal(err)
}
if _, err := clientSet.AppsV1beta2().Deployments("default").Create(deploymentStub); err != nil {
t.Fatal(err)
}
if _, err := clientSet.AppsV1beta2().StatefulSets("default").Create(ssStub); err != nil {
t.Fatal(err)
}
// Ensure scale subresources return and accept expected kinds
for gvr, gvk := range discoveredScaleSubresources {
prefix := "/apis"
if gvr.Group == corev1.GroupName {
prefix = "/api"
}
resourceParts := strings.SplitN(gvr.Resource, "/", 2)
urlPath := path.Join(prefix, gvr.Group, gvr.Version, "namespaces", "default", resourceParts[0], "test", resourceParts[1])
obj := &unstructured.Unstructured{}
getData, err := clientSet.CoreV1().RESTClient().Get().AbsPath(urlPath).DoRaw()
if err != nil {
t.Errorf("error fetching %s: %v", urlPath, err)
continue
}
if err := json.Unmarshal(getData, obj); err != nil {
t.Errorf("error decoding %s: %v", urlPath, err)
t.Log(string(getData))
continue
}
if obj.GetObjectKind().GroupVersionKind() != gvk {
t.Errorf("expected %#v, got %#v from %s", gvk, obj.GetObjectKind().GroupVersionKind(), urlPath)
t.Log(string(getData))
continue
}
updateData, err := clientSet.CoreV1().RESTClient().Put().AbsPath(urlPath).Body(getData).DoRaw()
if err != nil {
t.Errorf("error putting to %s: %v", urlPath, err)
t.Log(string(getData))
t.Log(string(updateData))
continue
}
}
}
var (
replicas = int32(1)
podStub = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "busybox"}}},
}
rcStub = &corev1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: corev1.ReplicationControllerSpec{Selector: podStub.Labels, Replicas: &replicas, Template: &podStub},
}
rsStub = &appsv1beta2.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1beta2.ReplicaSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.Labels}, Replicas: &replicas, Template: podStub},
}
deploymentStub = &appsv1beta2.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1beta2.DeploymentSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.Labels}, Replicas: &replicas, Template: podStub},
}
ssStub = &appsv1beta2.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1beta2.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.Labels}, Replicas: &replicas, Template: podStub},
}
)
func setup(t *testing.T) (client kubernetes.Interface, tearDown func()) {
result := apitesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
// TODO: Disable logging here until we resolve teardown issues which result in
// massive log spam. Another path forward would be to refactor
// StartTestServerOrDie to work with the etcd instance already started by the
// integration test scripts.
// See https://github.com/kubernetes/kubernetes/issues/49489.
repo, err := capnslog.GetRepoLogger("github.com/coreos/etcd")
if err != nil {
t.Fatalf("couldn't configure logging: %v", err)
}
repo.SetLogLevel(map[string]capnslog.LogLevel{
"etcdserver/api/v3rpc": capnslog.CRITICAL,
})
result.ClientConfig.AcceptContentTypes = ""
result.ClientConfig.ContentType = ""
result.ClientConfig.NegotiatedSerializer = nil
clientSet, err := kubernetes.NewForConfig(result.ClientConfig)
if err != nil {
t.Fatalf("error creating clientset: %v", err)
}
return clientSet, result.TearDownFn
}

View File

@@ -0,0 +1,121 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = [
"extender_test.go",
"main_test.go",
"predicates_test.go",
"preemption_test.go",
"priorities_test.go",
"scheduler_test.go",
"taint_test.go",
"volume_binding_test.go",
],
embed = [":go_default_library"],
tags = ["integration"],
deps = [
"//cmd/kube-scheduler/app:go_default_library",
"//cmd/kube-scheduler/app/config:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/componentconfig:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller/nodelifecycle:go_default_library",
"//pkg/controller/volume/persistentvolume:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//pkg/scheduler:go_default_library",
"//pkg/scheduler/algorithm:go_default_library",
"//pkg/scheduler/algorithmprovider:go_default_library",
"//pkg/scheduler/api:go_default_library",
"//pkg/scheduler/cache:go_default_library",
"//pkg/scheduler/factory:go_default_library",
"//plugin/pkg/admission/podtolerationrestriction:go_default_library",
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library",
"//test/integration/framework:go_default_library",
"//test/utils:go_default_library",
"//test/utils/image:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["util.go"],
importpath = "k8s.io/kubernetes/test/integration/scheduler",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/disruption:go_default_library",
"//pkg/features:go_default_library",
"//pkg/scheduler:go_default_library",
"//pkg/scheduler/algorithmprovider:go_default_library",
"//pkg/scheduler/api:go_default_library",
"//pkg/scheduler/factory:go_default_library",
"//test/integration/framework:go_default_library",
"//test/utils/image:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)

Some files were not shown because too many files have changed in this diff Show More