mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
Update to kube v1.17
Signed-off-by: Humble Chirammal <hchiramm@redhat.com>
This commit is contained in:
committed by
mergify[bot]
parent
327fcd1b1b
commit
3af1e26d7c
42
vendor/k8s.io/kubernetes/test/e2e/framework/auth/helpers.go
generated
vendored
42
vendor/k8s.io/kubernetes/test/e2e/framework/auth/helpers.go
generated
vendored
@ -23,13 +23,13 @@ import (
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/pkg/errors"
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
v1beta1authorization "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
|
||||
v1beta1rbac "k8s.io/client-go/kubernetes/typed/rbac/v1beta1"
|
||||
v1authorization "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
||||
v1rbac "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -38,23 +38,23 @@ const (
|
||||
)
|
||||
|
||||
type bindingsGetter interface {
|
||||
v1beta1rbac.RoleBindingsGetter
|
||||
v1beta1rbac.ClusterRoleBindingsGetter
|
||||
v1beta1rbac.ClusterRolesGetter
|
||||
v1rbac.RoleBindingsGetter
|
||||
v1rbac.ClusterRoleBindingsGetter
|
||||
v1rbac.ClusterRolesGetter
|
||||
}
|
||||
|
||||
// WaitForAuthorizationUpdate checks if the given user can perform the named verb and action.
|
||||
// If policyCachePollTimeout is reached without the expected condition matching, an error is returned
|
||||
func WaitForAuthorizationUpdate(c v1beta1authorization.SubjectAccessReviewsGetter, user, namespace, verb string, resource schema.GroupResource, allowed bool) error {
|
||||
func WaitForAuthorizationUpdate(c v1authorization.SubjectAccessReviewsGetter, user, namespace, verb string, resource schema.GroupResource, allowed bool) error {
|
||||
return WaitForNamedAuthorizationUpdate(c, user, namespace, verb, "", resource, allowed)
|
||||
}
|
||||
|
||||
// WaitForNamedAuthorizationUpdate checks if the given user can perform the named verb and action on the named resource.
|
||||
// If policyCachePollTimeout is reached without the expected condition matching, an error is returned
|
||||
func WaitForNamedAuthorizationUpdate(c v1beta1authorization.SubjectAccessReviewsGetter, user, namespace, verb, resourceName string, resource schema.GroupResource, allowed bool) error {
|
||||
review := &authorizationv1beta1.SubjectAccessReview{
|
||||
Spec: authorizationv1beta1.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationv1beta1.ResourceAttributes{
|
||||
func WaitForNamedAuthorizationUpdate(c v1authorization.SubjectAccessReviewsGetter, user, namespace, verb, resourceName string, resource schema.GroupResource, allowed bool) error {
|
||||
review := &authorizationv1.SubjectAccessReview{
|
||||
Spec: authorizationv1.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||
Group: resource.Group,
|
||||
Verb: verb,
|
||||
Resource: resource.Resource,
|
||||
@ -80,17 +80,17 @@ func WaitForNamedAuthorizationUpdate(c v1beta1authorization.SubjectAccessReviews
|
||||
|
||||
// BindClusterRole binds the cluster role at the cluster scope. If RBAC is not enabled, nil
|
||||
// is returned with no action.
|
||||
func BindClusterRole(c bindingsGetter, clusterRole, ns string, subjects ...rbacv1beta1.Subject) error {
|
||||
func BindClusterRole(c bindingsGetter, clusterRole, ns string, subjects ...rbacv1.Subject) error {
|
||||
if !IsRBACEnabled(c) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Since the namespace names are unique, we can leave this lying around so we don't have to race any caches
|
||||
_, err := c.ClusterRoleBindings().Create(&rbacv1beta1.ClusterRoleBinding{
|
||||
_, err := c.ClusterRoleBindings().Create(&rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ns + "--" + clusterRole,
|
||||
},
|
||||
RoleRef: rbacv1beta1.RoleRef{
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: clusterRole,
|
||||
@ -107,27 +107,27 @@ func BindClusterRole(c bindingsGetter, clusterRole, ns string, subjects ...rbacv
|
||||
|
||||
// BindClusterRoleInNamespace binds the cluster role at the namespace scope. If RBAC is not enabled, nil
|
||||
// is returned with no action.
|
||||
func BindClusterRoleInNamespace(c bindingsGetter, clusterRole, ns string, subjects ...rbacv1beta1.Subject) error {
|
||||
func BindClusterRoleInNamespace(c bindingsGetter, clusterRole, ns string, subjects ...rbacv1.Subject) error {
|
||||
return bindInNamespace(c, "ClusterRole", clusterRole, ns, subjects...)
|
||||
}
|
||||
|
||||
// BindRoleInNamespace binds the role at the namespace scope. If RBAC is not enabled, nil
|
||||
// is returned with no action.
|
||||
func BindRoleInNamespace(c bindingsGetter, role, ns string, subjects ...rbacv1beta1.Subject) error {
|
||||
func BindRoleInNamespace(c bindingsGetter, role, ns string, subjects ...rbacv1.Subject) error {
|
||||
return bindInNamespace(c, "Role", role, ns, subjects...)
|
||||
}
|
||||
|
||||
func bindInNamespace(c bindingsGetter, roleType, role, ns string, subjects ...rbacv1beta1.Subject) error {
|
||||
func bindInNamespace(c bindingsGetter, roleType, role, ns string, subjects ...rbacv1.Subject) error {
|
||||
if !IsRBACEnabled(c) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Since the namespace names are unique, we can leave this lying around so we don't have to race any caches
|
||||
_, err := c.RoleBindings(ns).Create(&rbacv1beta1.RoleBinding{
|
||||
_, err := c.RoleBindings(ns).Create(&rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ns + "--" + role,
|
||||
},
|
||||
RoleRef: rbacv1beta1.RoleRef{
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: roleType,
|
||||
Name: role,
|
||||
@ -148,7 +148,7 @@ var (
|
||||
)
|
||||
|
||||
// IsRBACEnabled returns true if RBAC is enabled. Otherwise false.
|
||||
func IsRBACEnabled(crGetter v1beta1rbac.ClusterRolesGetter) bool {
|
||||
func IsRBACEnabled(crGetter v1rbac.ClusterRolesGetter) bool {
|
||||
isRBACEnabledOnce.Do(func() {
|
||||
crs, err := crGetter.ClusterRoles().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
|
22
vendor/k8s.io/kubernetes/test/e2e/framework/checks.go
generated
vendored
Normal file
22
vendor/k8s.io/kubernetes/test/e2e/framework/checks.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2019 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
|
||||
|
||||
// IsAppArmorSupported checks whether the AppArmor is supported by the node OS distro.
|
||||
func IsAppArmorSupported() bool {
|
||||
return NodeOSDistroIs(AppArmorDistros...)
|
||||
}
|
588
vendor/k8s.io/kubernetes/test/e2e/framework/create.go
generated
vendored
588
vendor/k8s.io/kubernetes/test/e2e/framework/create.go
generated
vendored
@ -1,588 +0,0 @@
|
||||
/*
|
||||
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 framework
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
storage "k8s.io/api/storage/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/framework/testfiles"
|
||||
)
|
||||
|
||||
// LoadFromManifests loads .yaml or .json manifest files and returns
|
||||
// all items that it finds in them. It supports all items for which
|
||||
// there is a factory registered in factories and .yaml files with
|
||||
// multiple items separated by "---". Files are accessed via the
|
||||
// "testfiles" package, which means they can come from a file system
|
||||
// or be built into the binary.
|
||||
//
|
||||
// LoadFromManifests has some limitations:
|
||||
// - aliases are not supported (i.e. use serviceAccountName instead of the deprecated serviceAccount,
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#podspec-v1-core)
|
||||
// and silently ignored
|
||||
// - the latest stable API version for each item is used, regardless of what
|
||||
// is specified in the manifest files
|
||||
func (f *Framework) LoadFromManifests(files ...string) ([]interface{}, error) {
|
||||
var items []interface{}
|
||||
err := visitManifests(func(data []byte) error {
|
||||
// Ignore any additional fields for now, just determine what we have.
|
||||
var what What
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, &what); err != nil {
|
||||
return errors.Wrap(err, "decode TypeMeta")
|
||||
}
|
||||
|
||||
factory := factories[what]
|
||||
if factory == nil {
|
||||
return errors.Errorf("item of type %+v not supported", what)
|
||||
}
|
||||
|
||||
object := factory.New()
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, object); err != nil {
|
||||
return errors.Wrapf(err, "decode %+v", what)
|
||||
}
|
||||
items = append(items, object)
|
||||
return nil
|
||||
}, files...)
|
||||
|
||||
return items, err
|
||||
}
|
||||
|
||||
func visitManifests(cb func([]byte) error, files ...string) error {
|
||||
for _, fileName := range files {
|
||||
data, err := testfiles.Read(fileName)
|
||||
if err != nil {
|
||||
Failf("reading manifest file: %v", err)
|
||||
}
|
||||
|
||||
// Split at the "---" separator before working on
|
||||
// individual item. Only works for .yaml.
|
||||
//
|
||||
// We need to split ourselves because we need access
|
||||
// to each original chunk of data for
|
||||
// runtime.DecodeInto. kubectl has its own
|
||||
// infrastructure for this, but that is a lot of code
|
||||
// with many dependencies.
|
||||
items := bytes.Split(data, []byte("\n---"))
|
||||
|
||||
for _, item := range items {
|
||||
if err := cb(item); err != nil {
|
||||
return errors.Wrap(err, fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchItems modifies the given items in place such that each test
|
||||
// gets its own instances, to avoid conflicts between different tests
|
||||
// and between tests and normal deployments.
|
||||
//
|
||||
// This is done by:
|
||||
// - creating namespaced items inside the test's namespace
|
||||
// - changing the name of non-namespaced items like ClusterRole
|
||||
//
|
||||
// PatchItems has some limitations:
|
||||
// - only some common items are supported, unknown ones trigger an error
|
||||
// - only the latest stable API version for each item is supported
|
||||
func (f *Framework) PatchItems(items ...interface{}) error {
|
||||
for _, item := range items {
|
||||
// Uncomment when debugging the loading and patching of items.
|
||||
// e2elog.Logf("patching original content of %T:\n%s", item, PrettyPrint(item))
|
||||
if err := f.patchItemRecursively(item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateItems creates the items. Each of them must be an API object
|
||||
// of a type that is registered in Factory.
|
||||
//
|
||||
// It returns either a cleanup function or an error, but never both.
|
||||
//
|
||||
// Cleaning up after a test can be triggered in two ways:
|
||||
// - the test invokes the returned cleanup function,
|
||||
// usually in an AfterEach
|
||||
// - the test suite terminates, potentially after
|
||||
// skipping the test's AfterEach (https://github.com/onsi/ginkgo/issues/222)
|
||||
//
|
||||
// PatchItems has the some limitations as LoadFromManifests:
|
||||
// - only some common items are supported, unknown ones trigger an error
|
||||
// - only the latest stable API version for each item is supported
|
||||
func (f *Framework) CreateItems(items ...interface{}) (func(), error) {
|
||||
var destructors []func() error
|
||||
var cleanupHandle CleanupActionHandle
|
||||
cleanup := func() {
|
||||
if cleanupHandle == nil {
|
||||
// Already done.
|
||||
return
|
||||
}
|
||||
RemoveCleanupAction(cleanupHandle)
|
||||
|
||||
// TODO (?): use same logic as framework.go for determining
|
||||
// whether we are expected to clean up? This would change the
|
||||
// meaning of the -delete-namespace and -delete-namespace-on-failure
|
||||
// command line flags, because they would also start to apply
|
||||
// to non-namespaced items.
|
||||
for _, destructor := range destructors {
|
||||
if err := destructor(); err != nil && !apierrs.IsNotFound(err) {
|
||||
e2elog.Logf("deleting failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanupHandle = AddCleanupAction(cleanup)
|
||||
|
||||
var result error
|
||||
for _, item := range items {
|
||||
// Each factory knows which item(s) it supports, so try each one.
|
||||
done := false
|
||||
description := DescribeItem(item)
|
||||
// Uncomment this line to get a full dump of the entire item.
|
||||
// description = fmt.Sprintf("%s:\n%s", description, PrettyPrint(item))
|
||||
e2elog.Logf("creating %s", description)
|
||||
for _, factory := range factories {
|
||||
destructor, err := factory.Create(f, item)
|
||||
if destructor != nil {
|
||||
destructors = append(destructors, func() error {
|
||||
e2elog.Logf("deleting %s", description)
|
||||
return destructor()
|
||||
})
|
||||
}
|
||||
if err == nil {
|
||||
done = true
|
||||
break
|
||||
} else if errors.Cause(err) != errorItemNotSupported {
|
||||
result = err
|
||||
break
|
||||
}
|
||||
}
|
||||
if result == nil && !done {
|
||||
result = errors.Errorf("item of type %T not supported", item)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
cleanup()
|
||||
return nil, result
|
||||
}
|
||||
|
||||
return cleanup, nil
|
||||
}
|
||||
|
||||
// CreateFromManifests is a combination of LoadFromManifests,
|
||||
// PatchItems, patching with an optional custom function,
|
||||
// and CreateItems.
|
||||
func (f *Framework) CreateFromManifests(patch func(item interface{}) error, files ...string) (func(), error) {
|
||||
items, err := f.LoadFromManifests(files...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "CreateFromManifests")
|
||||
}
|
||||
if err := f.PatchItems(items...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if patch != nil {
|
||||
for _, item := range items {
|
||||
if err := patch(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.CreateItems(items...)
|
||||
}
|
||||
|
||||
// What is a subset of metav1.TypeMeta which (in contrast to
|
||||
// metav1.TypeMeta itself) satisfies the runtime.Object interface.
|
||||
type What struct {
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new What.
|
||||
func (in *What) DeepCopy() *What {
|
||||
return &What{Kind: in.Kind}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out.
|
||||
func (in *What) DeepCopyInto(out *What) {
|
||||
out.Kind = in.Kind
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *What) DeepCopyObject() runtime.Object {
|
||||
return &What{Kind: in.Kind}
|
||||
}
|
||||
|
||||
// GetObjectKind returns the ObjectKind schema
|
||||
func (in *What) GetObjectKind() schema.ObjectKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ItemFactory provides support for creating one particular item.
|
||||
// The type gets exported because other packages might want to
|
||||
// extend the set of pre-defined factories.
|
||||
type ItemFactory interface {
|
||||
// New returns a new empty item.
|
||||
New() runtime.Object
|
||||
|
||||
// Create is responsible for creating the item. It returns an
|
||||
// error or a cleanup function for the created item.
|
||||
// If the item is of an unsupported type, it must return
|
||||
// an error that has errorItemNotSupported as cause.
|
||||
Create(f *Framework, item interface{}) (func() error, error)
|
||||
}
|
||||
|
||||
// DescribeItem always returns a string that describes the item,
|
||||
// usually by calling out to cache.MetaNamespaceKeyFunc which
|
||||
// concatenates namespace (if set) and name. If that fails, the entire
|
||||
// item gets converted to a string.
|
||||
func DescribeItem(item interface{}) string {
|
||||
key, err := cache.MetaNamespaceKeyFunc(item)
|
||||
if err == nil && key != "" {
|
||||
return fmt.Sprintf("%T: %s", item, key)
|
||||
}
|
||||
return fmt.Sprintf("%T: %s", item, item)
|
||||
}
|
||||
|
||||
// errorItemNotSupported is the error that Create methods
|
||||
// must return or wrap when they don't support the given item.
|
||||
var errorItemNotSupported = errors.New("not supported")
|
||||
|
||||
var factories = map[What]ItemFactory{
|
||||
{"ClusterRole"}: &clusterRoleFactory{},
|
||||
{"ClusterRoleBinding"}: &clusterRoleBindingFactory{},
|
||||
{"DaemonSet"}: &daemonSetFactory{},
|
||||
{"Role"}: &roleFactory{},
|
||||
{"RoleBinding"}: &roleBindingFactory{},
|
||||
{"Secret"}: &secretFactory{},
|
||||
{"Service"}: &serviceFactory{},
|
||||
{"ServiceAccount"}: &serviceAccountFactory{},
|
||||
{"StatefulSet"}: &statefulSetFactory{},
|
||||
{"StorageClass"}: &storageClassFactory{},
|
||||
}
|
||||
|
||||
// PatchName makes the name of some item unique by appending the
|
||||
// generated unique name.
|
||||
func (f *Framework) PatchName(item *string) {
|
||||
if *item != "" {
|
||||
*item = *item + "-" + f.UniqueName
|
||||
}
|
||||
}
|
||||
|
||||
// PatchNamespace moves the item into the test's namespace. Not
|
||||
// all items can be namespaced. For those, the name also needs to be
|
||||
// patched.
|
||||
func (f *Framework) PatchNamespace(item *string) {
|
||||
if f.Namespace != nil {
|
||||
*item = f.Namespace.GetName()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Framework) patchItemRecursively(item interface{}) error {
|
||||
switch item := item.(type) {
|
||||
case *rbac.Subject:
|
||||
f.PatchNamespace(&item.Namespace)
|
||||
case *rbac.RoleRef:
|
||||
// TODO: avoid hard-coding this special name. Perhaps add a Framework.PredefinedRoles
|
||||
// which contains all role names that are defined cluster-wide before the test starts?
|
||||
// All those names are excempt from renaming. That list could be populated by querying
|
||||
// and get extended by tests.
|
||||
if item.Name != "e2e-test-privileged-psp" {
|
||||
f.PatchName(&item.Name)
|
||||
}
|
||||
case *rbac.ClusterRole:
|
||||
f.PatchName(&item.Name)
|
||||
case *rbac.Role:
|
||||
f.PatchNamespace(&item.Namespace)
|
||||
// Roles are namespaced, but because for RoleRef above we don't
|
||||
// know whether the referenced role is a ClusterRole or Role
|
||||
// and therefore always renames, we have to do the same here.
|
||||
f.PatchName(&item.Name)
|
||||
case *storage.StorageClass:
|
||||
f.PatchName(&item.Name)
|
||||
case *v1.ServiceAccount:
|
||||
f.PatchNamespace(&item.ObjectMeta.Namespace)
|
||||
case *v1.Secret:
|
||||
f.PatchNamespace(&item.ObjectMeta.Namespace)
|
||||
case *rbac.ClusterRoleBinding:
|
||||
f.PatchName(&item.Name)
|
||||
for i := range item.Subjects {
|
||||
if err := f.patchItemRecursively(&item.Subjects[i]); err != nil {
|
||||
return errors.Wrapf(err, "%T", f)
|
||||
}
|
||||
}
|
||||
if err := f.patchItemRecursively(&item.RoleRef); err != nil {
|
||||
return errors.Wrapf(err, "%T", f)
|
||||
}
|
||||
case *rbac.RoleBinding:
|
||||
f.PatchNamespace(&item.Namespace)
|
||||
for i := range item.Subjects {
|
||||
if err := f.patchItemRecursively(&item.Subjects[i]); err != nil {
|
||||
return errors.Wrapf(err, "%T", f)
|
||||
}
|
||||
}
|
||||
if err := f.patchItemRecursively(&item.RoleRef); err != nil {
|
||||
return errors.Wrapf(err, "%T", f)
|
||||
}
|
||||
case *v1.Service:
|
||||
f.PatchNamespace(&item.ObjectMeta.Namespace)
|
||||
case *apps.StatefulSet:
|
||||
f.PatchNamespace(&item.ObjectMeta.Namespace)
|
||||
case *apps.DaemonSet:
|
||||
f.PatchNamespace(&item.ObjectMeta.Namespace)
|
||||
default:
|
||||
return errors.Errorf("missing support for patching item of type %T", item)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The individual factories all follow the same template, but with
|
||||
// enough differences in types and functions that copy-and-paste
|
||||
// looked like the least dirty approach. Perhaps one day Go will have
|
||||
// generics.
|
||||
|
||||
type serviceAccountFactory struct{}
|
||||
|
||||
func (f *serviceAccountFactory) New() runtime.Object {
|
||||
return &v1.ServiceAccount{}
|
||||
}
|
||||
|
||||
func (*serviceAccountFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*v1.ServiceAccount)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
client := f.ClientSet.CoreV1().ServiceAccounts(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create ServiceAccount")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type clusterRoleFactory struct{}
|
||||
|
||||
func (f *clusterRoleFactory) New() runtime.Object {
|
||||
return &rbac.ClusterRole{}
|
||||
}
|
||||
|
||||
func (*clusterRoleFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*rbac.ClusterRole)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
e2elog.Logf("Define cluster role %v", item.GetName())
|
||||
client := f.ClientSet.RbacV1().ClusterRoles()
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create ClusterRole")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type clusterRoleBindingFactory struct{}
|
||||
|
||||
func (f *clusterRoleBindingFactory) New() runtime.Object {
|
||||
return &rbac.ClusterRoleBinding{}
|
||||
}
|
||||
|
||||
func (*clusterRoleBindingFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*rbac.ClusterRoleBinding)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.RbacV1().ClusterRoleBindings()
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create ClusterRoleBinding")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type roleFactory struct{}
|
||||
|
||||
func (f *roleFactory) New() runtime.Object {
|
||||
return &rbac.Role{}
|
||||
}
|
||||
|
||||
func (*roleFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*rbac.Role)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.RbacV1().Roles(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create Role")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type roleBindingFactory struct{}
|
||||
|
||||
func (f *roleBindingFactory) New() runtime.Object {
|
||||
return &rbac.RoleBinding{}
|
||||
}
|
||||
|
||||
func (*roleBindingFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*rbac.RoleBinding)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.RbacV1().RoleBindings(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create RoleBinding")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type serviceFactory struct{}
|
||||
|
||||
func (f *serviceFactory) New() runtime.Object {
|
||||
return &v1.Service{}
|
||||
}
|
||||
|
||||
func (*serviceFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*v1.Service)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.CoreV1().Services(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create Service")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type statefulSetFactory struct{}
|
||||
|
||||
func (f *statefulSetFactory) New() runtime.Object {
|
||||
return &apps.StatefulSet{}
|
||||
}
|
||||
|
||||
func (*statefulSetFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.AppsV1().StatefulSets(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create StatefulSet")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type daemonSetFactory struct{}
|
||||
|
||||
func (f *daemonSetFactory) New() runtime.Object {
|
||||
return &apps.DaemonSet{}
|
||||
}
|
||||
|
||||
func (*daemonSetFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*apps.DaemonSet)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.AppsV1().DaemonSets(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create DaemonSet")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type storageClassFactory struct{}
|
||||
|
||||
func (f *storageClassFactory) New() runtime.Object {
|
||||
return &storage.StorageClass{}
|
||||
}
|
||||
|
||||
func (*storageClassFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*storage.StorageClass)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.StorageV1().StorageClasses()
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create StorageClass")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
type secretFactory struct{}
|
||||
|
||||
func (f *secretFactory) New() runtime.Object {
|
||||
return &v1.Secret{}
|
||||
}
|
||||
|
||||
func (*secretFactory) Create(f *Framework, i interface{}) (func() error, error) {
|
||||
item, ok := i.(*v1.Secret)
|
||||
if !ok {
|
||||
return nil, errorItemNotSupported
|
||||
}
|
||||
|
||||
client := f.ClientSet.CoreV1().Secrets(f.Namespace.GetName())
|
||||
if _, err := client.Create(item); err != nil {
|
||||
return nil, errors.Wrap(err, "create Secret")
|
||||
}
|
||||
return func() error {
|
||||
return client.Delete(item.GetName(), &metav1.DeleteOptions{})
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PrettyPrint returns a human-readable representation of an item.
|
||||
func PrettyPrint(item interface{}) string {
|
||||
data, err := json.MarshalIndent(item, "", " ")
|
||||
if err == nil {
|
||||
return string(data)
|
||||
}
|
||||
return fmt.Sprintf("%+v", item)
|
||||
}
|
5
vendor/k8s.io/kubernetes/test/e2e/framework/exec_util.go
generated
vendored
5
vendor/k8s.io/kubernetes/test/e2e/framework/exec_util.go
generated
vendored
@ -27,7 +27,6 @@ import (
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
)
|
||||
@ -49,7 +48,7 @@ type ExecOptions struct {
|
||||
// returning stdout, stderr and error. `options` allowed for
|
||||
// additional parameters to be passed.
|
||||
func (f *Framework) ExecWithOptions(options ExecOptions) (string, string, error) {
|
||||
e2elog.Logf("ExecWithOptions %+v", options)
|
||||
Logf("ExecWithOptions %+v", options)
|
||||
|
||||
config, err := LoadConfig()
|
||||
ExpectNoError(err, "failed to load restclient config")
|
||||
@ -98,7 +97,7 @@ func (f *Framework) ExecCommandInContainerWithFullOutput(podName, containerName
|
||||
// ExecCommandInContainer executes a command in the specified container.
|
||||
func (f *Framework) ExecCommandInContainer(podName, containerName string, cmd ...string) string {
|
||||
stdout, stderr, err := f.ExecCommandInContainerWithFullOutput(podName, containerName, cmd...)
|
||||
e2elog.Logf("Exec stderr: %q", stderr)
|
||||
Logf("Exec stderr: %q", stderr)
|
||||
ExpectNoError(err,
|
||||
"failed to execute command in pod %v, container %v: %v",
|
||||
podName, containerName, err)
|
||||
|
47
vendor/k8s.io/kubernetes/test/e2e/framework/expect.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/test/e2e/framework/expect.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// ExpectEqual expects the specified two are the same, otherwise an exception raises
|
||||
func ExpectEqual(actual interface{}, extra interface{}, explain ...interface{}) {
|
||||
gomega.ExpectWithOffset(1, actual).To(gomega.Equal(extra), explain...)
|
||||
}
|
||||
|
||||
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
|
||||
func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
|
||||
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
|
||||
}
|
||||
|
||||
// ExpectError expects an error happens, otherwise an exception raises
|
||||
func ExpectError(err error, explain ...interface{}) {
|
||||
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
|
||||
}
|
||||
|
||||
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
|
||||
func ExpectNoError(err error, explain ...interface{}) {
|
||||
ExpectNoErrorWithOffset(1, err, explain...)
|
||||
}
|
||||
|
||||
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
|
||||
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
|
||||
func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
|
||||
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
|
||||
}
|
4
vendor/k8s.io/kubernetes/test/e2e/framework/flake_reporting_util.go
generated
vendored
4
vendor/k8s.io/kubernetes/test/e2e/framework/flake_reporting_util.go
generated
vendored
@ -20,8 +20,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
// FlakeReport is a struct for managing the flake report.
|
||||
@ -59,7 +57,7 @@ func (f *FlakeReport) RecordFlakeIfError(err error, optionalDescription ...inter
|
||||
if desc != "" {
|
||||
msg = fmt.Sprintf("%v (Description: %v)", msg, desc)
|
||||
}
|
||||
e2elog.Logf(msg)
|
||||
Logf(msg)
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.Flakes = append(f.Flakes, msg)
|
||||
|
197
vendor/k8s.io/kubernetes/test/e2e/framework/framework.go
generated
vendored
197
vendor/k8s.io/kubernetes/test/e2e/framework/framework.go
generated
vendored
@ -22,16 +22,16 @@ limitations under the License.
|
||||
package framework
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@ -46,20 +46,21 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
scaleclient "k8s.io/client-go/scale"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/framework/metrics"
|
||||
testutils "k8s.io/kubernetes/test/utils"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2emetrics "k8s.io/kubernetes/test/e2e/framework/metrics"
|
||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
||||
)
|
||||
|
||||
const (
|
||||
maxKubectlExecRetries = 5
|
||||
// DefaultNamespaceDeletionTimeout is timeout duration for waiting for a namespace deletion.
|
||||
// TODO(mikedanese): reset this to 5 minutes once #47135 is resolved.
|
||||
// ref https://github.com/kubernetes/kubernetes/issues/47135
|
||||
DefaultNamespaceDeletionTimeout = 10 * time.Minute
|
||||
DefaultNamespaceDeletionTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you.
|
||||
@ -111,7 +112,7 @@ type Framework struct {
|
||||
TestSummaries []TestDataSummary
|
||||
|
||||
// Place to keep ClusterAutoscaler metrics from before test in order to compute delta.
|
||||
clusterAutoscalerMetricsBeforeTest metrics.Collection
|
||||
clusterAutoscalerMetricsBeforeTest e2emetrics.Collection
|
||||
}
|
||||
|
||||
// TestDataSummary is an interface for managing test data.
|
||||
@ -161,16 +162,8 @@ func (f *Framework) BeforeEach() {
|
||||
if f.ClientSet == nil {
|
||||
ginkgo.By("Creating a kubernetes client")
|
||||
config, err := LoadConfig()
|
||||
testDesc := ginkgo.CurrentGinkgoTestDescription()
|
||||
if len(testDesc.ComponentTexts) > 0 {
|
||||
componentTexts := strings.Join(testDesc.ComponentTexts, " ")
|
||||
config.UserAgent = fmt.Sprintf(
|
||||
"%v -- %v",
|
||||
rest.DefaultKubernetesUserAgent(),
|
||||
componentTexts)
|
||||
}
|
||||
|
||||
ExpectNoError(err)
|
||||
|
||||
config.QPS = f.Options.ClientQPS
|
||||
config.Burst = f.Options.ClientBurst
|
||||
if f.Options.GroupVersion != nil {
|
||||
@ -223,7 +216,7 @@ func (f *Framework) BeforeEach() {
|
||||
err = WaitForDefaultServiceAccountInNamespace(f.ClientSet, namespace.Name)
|
||||
ExpectNoError(err)
|
||||
} else {
|
||||
e2elog.Logf("Skipping waiting for service account")
|
||||
Logf("Skipping waiting for service account")
|
||||
}
|
||||
f.UniqueName = f.Namespace.GetName()
|
||||
} else {
|
||||
@ -251,7 +244,7 @@ func (f *Framework) BeforeEach() {
|
||||
PrintVerboseLogs: false,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
e2elog.Logf("Error while creating NewResourceUsageGatherer: %v", err)
|
||||
Logf("Error while creating NewResourceUsageGatherer: %v", err)
|
||||
} else {
|
||||
go f.gatherer.StartGatheringData()
|
||||
}
|
||||
@ -270,15 +263,15 @@ func (f *Framework) BeforeEach() {
|
||||
|
||||
gatherMetricsAfterTest := TestContext.GatherMetricsAfterTest == "true" || TestContext.GatherMetricsAfterTest == "master"
|
||||
if gatherMetricsAfterTest && TestContext.IncludeClusterAutoscalerMetrics {
|
||||
grabber, err := metrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, !ProviderIs("kubemark"), false, false, false, TestContext.IncludeClusterAutoscalerMetrics)
|
||||
grabber, err := e2emetrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, !ProviderIs("kubemark"), false, false, false, TestContext.IncludeClusterAutoscalerMetrics)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to create MetricsGrabber (skipping ClusterAutoscaler metrics gathering before test): %v", err)
|
||||
Logf("Failed to create MetricsGrabber (skipping ClusterAutoscaler metrics gathering before test): %v", err)
|
||||
} else {
|
||||
f.clusterAutoscalerMetricsBeforeTest, err = grabber.Grab()
|
||||
if err != nil {
|
||||
e2elog.Logf("MetricsGrabber failed to grab CA metrics before test (skipping metrics gathering): %v", err)
|
||||
Logf("MetricsGrabber failed to grab CA metrics before test (skipping metrics gathering): %v", err)
|
||||
} else {
|
||||
e2elog.Logf("Gathered ClusterAutoscaler metrics before test")
|
||||
Logf("Gathered ClusterAutoscaler metrics before test")
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,6 +280,43 @@ func (f *Framework) BeforeEach() {
|
||||
f.flakeReport = NewFlakeReport()
|
||||
}
|
||||
|
||||
// printSummaries prints summaries of tests.
|
||||
func printSummaries(summaries []TestDataSummary, testBaseName string) {
|
||||
now := time.Now()
|
||||
for i := range summaries {
|
||||
Logf("Printing summary: %v", summaries[i].SummaryKind())
|
||||
switch TestContext.OutputPrintType {
|
||||
case "hr":
|
||||
if TestContext.ReportDir == "" {
|
||||
Logf(summaries[i].PrintHumanReadable())
|
||||
} else {
|
||||
// TODO: learn to extract test name and append it to the kind instead of timestamp.
|
||||
filePath := path.Join(TestContext.ReportDir, summaries[i].SummaryKind()+"_"+testBaseName+"_"+now.Format(time.RFC3339)+".txt")
|
||||
if err := ioutil.WriteFile(filePath, []byte(summaries[i].PrintHumanReadable()), 0644); err != nil {
|
||||
Logf("Failed to write file %v with test performance data: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
case "json":
|
||||
fallthrough
|
||||
default:
|
||||
if TestContext.OutputPrintType != "json" {
|
||||
Logf("Unknown output type: %v. Printing JSON", TestContext.OutputPrintType)
|
||||
}
|
||||
if TestContext.ReportDir == "" {
|
||||
Logf("%v JSON\n%v", summaries[i].SummaryKind(), summaries[i].PrintJSON())
|
||||
Logf("Finished")
|
||||
} else {
|
||||
// TODO: learn to extract test name and append it to the kind instead of timestamp.
|
||||
filePath := path.Join(TestContext.ReportDir, summaries[i].SummaryKind()+"_"+testBaseName+"_"+now.Format(time.RFC3339)+".json")
|
||||
Logf("Writing to %s", filePath)
|
||||
if err := ioutil.WriteFile(filePath, []byte(summaries[i].PrintJSON()), 0644); err != nil {
|
||||
Logf("Failed to write file %v with test performance data: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AfterEach deletes the namespace, after reading its events.
|
||||
func (f *Framework) AfterEach() {
|
||||
RemoveCleanupAction(f.cleanupHandle)
|
||||
@ -301,23 +331,19 @@ func (f *Framework) AfterEach() {
|
||||
if TestContext.DeleteNamespace && (TestContext.DeleteNamespaceOnFailure || !ginkgo.CurrentGinkgoTestDescription().Failed) {
|
||||
for _, ns := range f.namespacesToDelete {
|
||||
ginkgo.By(fmt.Sprintf("Destroying namespace %q for this suite.", ns.Name))
|
||||
timeout := DefaultNamespaceDeletionTimeout
|
||||
if f.NamespaceDeletionTimeout != 0 {
|
||||
timeout = f.NamespaceDeletionTimeout
|
||||
}
|
||||
if err := deleteNS(f.ClientSet, f.DynamicClient, ns.Name, timeout); err != nil {
|
||||
if err := f.ClientSet.CoreV1().Namespaces().Delete(ns.Name, nil); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
nsDeletionErrors[ns.Name] = err
|
||||
} else {
|
||||
e2elog.Logf("Namespace %v was already deleted", ns.Name)
|
||||
Logf("Namespace %v was already deleted", ns.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !TestContext.DeleteNamespace {
|
||||
e2elog.Logf("Found DeleteNamespace=false, skipping namespace deletion!")
|
||||
Logf("Found DeleteNamespace=false, skipping namespace deletion!")
|
||||
} else {
|
||||
e2elog.Logf("Found DeleteNamespaceOnFailure=false and current test failed, skipping namespace deletion!")
|
||||
Logf("Found DeleteNamespaceOnFailure=false and current test failed, skipping namespace deletion!")
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,16 +388,16 @@ func (f *Framework) AfterEach() {
|
||||
ginkgo.By("Gathering metrics")
|
||||
// Grab apiserver, scheduler, controller-manager metrics and (optionally) nodes' kubelet metrics.
|
||||
grabMetricsFromKubelets := TestContext.GatherMetricsAfterTest != "master" && !ProviderIs("kubemark")
|
||||
grabber, err := metrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, grabMetricsFromKubelets, true, true, true, TestContext.IncludeClusterAutoscalerMetrics)
|
||||
grabber, err := e2emetrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, grabMetricsFromKubelets, true, true, true, TestContext.IncludeClusterAutoscalerMetrics)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to create MetricsGrabber (skipping metrics gathering): %v", err)
|
||||
Logf("Failed to create MetricsGrabber (skipping metrics gathering): %v", err)
|
||||
} else {
|
||||
received, err := grabber.Grab()
|
||||
if err != nil {
|
||||
e2elog.Logf("MetricsGrabber failed to grab some of the metrics: %v", err)
|
||||
Logf("MetricsGrabber failed to grab some of the metrics: %v", err)
|
||||
}
|
||||
(*MetricsForE2E)(&received).computeClusterAutoscalerMetricsDelta(f.clusterAutoscalerMetricsBeforeTest)
|
||||
f.TestSummaries = append(f.TestSummaries, (*MetricsForE2E)(&received))
|
||||
(*e2emetrics.ComponentCollection)(&received).ComputeClusterAutoscalerMetricsDelta(f.clusterAutoscalerMetricsBeforeTest)
|
||||
f.TestSummaries = append(f.TestSummaries, (*e2emetrics.ComponentCollection)(&received))
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +409,7 @@ func (f *Framework) AfterEach() {
|
||||
f.flakeReport = nil
|
||||
}
|
||||
|
||||
PrintSummaries(f.TestSummaries, f.BaseName)
|
||||
printSummaries(f.TestSummaries, f.BaseName)
|
||||
|
||||
// Check whether all nodes are ready after the test.
|
||||
// This is explicitly done at the very end of the test, to avoid
|
||||
@ -405,7 +431,7 @@ func (f *Framework) CreateNamespace(baseName string, labels map[string]string) (
|
||||
f.AddNamespacesToDelete(ns)
|
||||
|
||||
if err == nil && !f.SkipPrivilegedPSPBinding {
|
||||
createPrivilegedPSPBinding(f, ns.Name)
|
||||
CreatePrivilegedPSPBinding(f.ClientSet, ns.Name)
|
||||
}
|
||||
|
||||
return ns, err
|
||||
@ -431,34 +457,34 @@ func (f *Framework) AddNamespacesToDelete(namespaces ...*v1.Namespace) {
|
||||
|
||||
// WaitForPodTerminated waits for the pod to be terminated with the given reason.
|
||||
func (f *Framework) WaitForPodTerminated(podName, reason string) error {
|
||||
return waitForPodTerminatedInNamespace(f.ClientSet, podName, reason, f.Namespace.Name)
|
||||
return e2epod.WaitForPodTerminatedInNamespace(f.ClientSet, podName, reason, f.Namespace.Name)
|
||||
}
|
||||
|
||||
// WaitForPodNotFound waits for the pod to be completely terminated (not "Get-able").
|
||||
func (f *Framework) WaitForPodNotFound(podName string, timeout time.Duration) error {
|
||||
return waitForPodNotFoundInNamespace(f.ClientSet, podName, f.Namespace.Name, timeout)
|
||||
return e2epod.WaitForPodNotFoundInNamespace(f.ClientSet, podName, f.Namespace.Name, timeout)
|
||||
}
|
||||
|
||||
// WaitForPodRunning waits for the pod to run in the namespace.
|
||||
func (f *Framework) WaitForPodRunning(podName string) error {
|
||||
return WaitForPodNameRunningInNamespace(f.ClientSet, podName, f.Namespace.Name)
|
||||
return e2epod.WaitForPodNameRunningInNamespace(f.ClientSet, podName, f.Namespace.Name)
|
||||
}
|
||||
|
||||
// WaitForPodReady waits for the pod to flip to ready in the namespace.
|
||||
func (f *Framework) WaitForPodReady(podName string) error {
|
||||
return waitTimeoutForPodReadyInNamespace(f.ClientSet, podName, f.Namespace.Name, PodStartTimeout)
|
||||
return e2epod.WaitTimeoutForPodReadyInNamespace(f.ClientSet, podName, f.Namespace.Name, PodStartTimeout)
|
||||
}
|
||||
|
||||
// WaitForPodRunningSlow waits for the pod to run in the namespace.
|
||||
// It has a longer timeout then WaitForPodRunning (util.slowPodStartTimeout).
|
||||
func (f *Framework) WaitForPodRunningSlow(podName string) error {
|
||||
return waitForPodRunningInNamespaceSlow(f.ClientSet, podName, f.Namespace.Name)
|
||||
return e2epod.WaitForPodRunningInNamespaceSlow(f.ClientSet, podName, f.Namespace.Name)
|
||||
}
|
||||
|
||||
// WaitForPodNoLongerRunning waits for the pod to no longer be running in the namespace, for either
|
||||
// success or failure.
|
||||
func (f *Framework) WaitForPodNoLongerRunning(podName string) error {
|
||||
return WaitForPodNoLongerRunningInNamespace(f.ClientSet, podName, f.Namespace.Name)
|
||||
return e2epod.WaitForPodNoLongerRunningInNamespace(f.ClientSet, podName, f.Namespace.Name)
|
||||
}
|
||||
|
||||
// TestContainerOutput runs the given pod in the given namespace and waits
|
||||
@ -485,10 +511,10 @@ func (f *Framework) WriteFileViaContainer(podName, containerName string, path st
|
||||
return fmt.Errorf("Unsupported character in string to write: %v", c)
|
||||
}
|
||||
}
|
||||
command := fmt.Sprintf("echo '%s' > '%s'", contents, path)
|
||||
command := fmt.Sprintf("echo '%s' > '%s'; sync", contents, path)
|
||||
stdout, stderr, err := kubectlExecWithRetry(f.Namespace.Name, podName, containerName, "--", "/bin/sh", "-c", command)
|
||||
if err != nil {
|
||||
e2elog.Logf("error running kubectl exec to write file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
|
||||
Logf("error running kubectl exec to write file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -499,7 +525,7 @@ func (f *Framework) ReadFileViaContainer(podName, containerName string, path str
|
||||
|
||||
stdout, stderr, err := kubectlExecWithRetry(f.Namespace.Name, podName, containerName, "--", "cat", path)
|
||||
if err != nil {
|
||||
e2elog.Logf("error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
|
||||
Logf("error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
|
||||
}
|
||||
return string(stdout), err
|
||||
}
|
||||
@ -510,7 +536,7 @@ func (f *Framework) CheckFileSizeViaContainer(podName, containerName, path strin
|
||||
|
||||
stdout, stderr, err := kubectlExecWithRetry(f.Namespace.Name, podName, containerName, "--", "ls", "-l", path)
|
||||
if err != nil {
|
||||
e2elog.Logf("error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
|
||||
Logf("error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
|
||||
}
|
||||
return string(stdout), err
|
||||
}
|
||||
@ -547,7 +573,7 @@ func (f *Framework) CreateServiceForSimpleApp(contPort, svcPort int, appName str
|
||||
TargetPort: intstr.FromInt(contPort),
|
||||
}}
|
||||
}
|
||||
e2elog.Logf("Creating a service-for-%v for selecting app=%v-pod", appName, appName)
|
||||
Logf("Creating a service-for-%v for selecting app=%v-pod", appName, appName)
|
||||
service, err := f.ClientSet.CoreV1().Services(f.Namespace.Name).Create(&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "service-for-" + appName,
|
||||
@ -566,25 +592,23 @@ func (f *Framework) CreateServiceForSimpleApp(contPort, svcPort int, appName str
|
||||
|
||||
// CreatePodsPerNodeForSimpleApp creates pods w/ labels. Useful for tests which make a bunch of pods w/o any networking.
|
||||
func (f *Framework) CreatePodsPerNodeForSimpleApp(appName string, podSpec func(n v1.Node) v1.PodSpec, maxCount int) map[string]string {
|
||||
nodes := GetReadySchedulableNodesOrDie(f.ClientSet)
|
||||
labels := map[string]string{
|
||||
nodes, err := e2enode.GetBoundedReadySchedulableNodes(f.ClientSet, maxCount)
|
||||
ExpectNoError(err)
|
||||
podLabels := map[string]string{
|
||||
"app": appName + "-pod",
|
||||
}
|
||||
for i, node := range nodes.Items {
|
||||
// one per node, but no more than maxCount.
|
||||
if i <= maxCount {
|
||||
e2elog.Logf("%v/%v : Creating container with label app=%v-pod", i, maxCount, appName)
|
||||
_, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf(appName+"-pod-%v", i),
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: podSpec(node),
|
||||
})
|
||||
ExpectNoError(err)
|
||||
}
|
||||
Logf("%v/%v : Creating container with label app=%v-pod", i, maxCount, appName)
|
||||
_, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf(appName+"-pod-%v", i),
|
||||
Labels: podLabels,
|
||||
},
|
||||
Spec: podSpec(node),
|
||||
})
|
||||
ExpectNoError(err)
|
||||
}
|
||||
return labels
|
||||
return podLabels
|
||||
}
|
||||
|
||||
// KubeUser is a struct for managing kubernetes user info.
|
||||
@ -644,19 +668,19 @@ func (kc *KubeConfig) FindCluster(name string) *KubeCluster {
|
||||
func kubectlExecWithRetry(namespace string, podName, containerName string, args ...string) ([]byte, []byte, error) {
|
||||
for numRetries := 0; numRetries < maxKubectlExecRetries; numRetries++ {
|
||||
if numRetries > 0 {
|
||||
e2elog.Logf("Retrying kubectl exec (retry count=%v/%v)", numRetries+1, maxKubectlExecRetries)
|
||||
Logf("Retrying kubectl exec (retry count=%v/%v)", numRetries+1, maxKubectlExecRetries)
|
||||
}
|
||||
|
||||
stdOutBytes, stdErrBytes, err := kubectlExec(namespace, podName, containerName, args...)
|
||||
if err != nil {
|
||||
if strings.Contains(strings.ToLower(string(stdErrBytes)), "i/o timeout") {
|
||||
// Retry on "i/o timeout" errors
|
||||
e2elog.Logf("Warning: kubectl exec encountered i/o timeout.\nerr=%v\nstdout=%v\nstderr=%v)", err, string(stdOutBytes), string(stdErrBytes))
|
||||
Logf("Warning: kubectl exec encountered i/o timeout.\nerr=%v\nstdout=%v\nstderr=%v)", err, string(stdOutBytes), string(stdErrBytes))
|
||||
continue
|
||||
}
|
||||
if strings.Contains(strings.ToLower(string(stdErrBytes)), "container not found") {
|
||||
// Retry on "container not found" errors
|
||||
e2elog.Logf("Warning: kubectl exec encountered container not found.\nerr=%v\nstdout=%v\nstderr=%v)", err, string(stdOutBytes), string(stdErrBytes))
|
||||
Logf("Warning: kubectl exec encountered container not found.\nerr=%v\nstdout=%v\nstderr=%v)", err, string(stdOutBytes), string(stdErrBytes))
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
@ -681,7 +705,7 @@ func kubectlExec(namespace string, podName, containerName string, args ...string
|
||||
cmd := KubectlCmd(cmdArgs...)
|
||||
cmd.Stdout, cmd.Stderr = &stdout, &stderr
|
||||
|
||||
e2elog.Logf("Running '%s %s'", cmd.Path, strings.Join(cmdArgs, " "))
|
||||
Logf("Running '%s %s'", cmd.Path, strings.Join(cmdArgs, " "))
|
||||
err := cmd.Run()
|
||||
return stdout.Bytes(), stderr.Bytes(), err
|
||||
}
|
||||
@ -788,7 +812,7 @@ func (p *PodStateVerification) filter(c clientset.Interface, namespace *v1.Names
|
||||
|
||||
ns := namespace.Name
|
||||
pl, err := filterLabels(p.Selectors, c, ns) // Build an v1.PodList to operate against.
|
||||
e2elog.Logf("Selector matched %v pods for %v", len(pl.Items), p.Selectors)
|
||||
Logf("Selector matched %v pods for %v", len(pl.Items), p.Selectors)
|
||||
if len(pl.Items) == 0 || err != nil {
|
||||
return pl.Items, err
|
||||
}
|
||||
@ -803,7 +827,7 @@ ReturnPodsSoFar:
|
||||
}
|
||||
passesVerify, err := passesVerifyFilter(pod, p.Verify)
|
||||
if err != nil {
|
||||
e2elog.Logf("Error detected on %v : %v !", pod.Name, err)
|
||||
Logf("Error detected on %v : %v !", pod.Name, err)
|
||||
break ReturnPodsSoFar
|
||||
}
|
||||
if passesVerify {
|
||||
@ -824,12 +848,12 @@ func (cl *ClusterVerification) WaitFor(atLeast int, timeout time.Duration) ([]v1
|
||||
|
||||
// Failure
|
||||
if returnedErr != nil {
|
||||
e2elog.Logf("Cutting polling short: We got an error from the pod filtering layer.")
|
||||
Logf("Cutting polling short: We got an error from the pod filtering layer.")
|
||||
// stop polling if the pod filtering returns an error. that should never happen.
|
||||
// it indicates, for example, that the client is broken or something non-pod related.
|
||||
return false, returnedErr
|
||||
}
|
||||
e2elog.Logf("Found %v / %v", len(pods), atLeast)
|
||||
Logf("Found %v / %v", len(pods), atLeast)
|
||||
|
||||
// Success
|
||||
if len(pods) >= atLeast {
|
||||
@ -838,7 +862,7 @@ func (cl *ClusterVerification) WaitFor(atLeast int, timeout time.Duration) ([]v1
|
||||
// Keep trying...
|
||||
return false, nil
|
||||
})
|
||||
e2elog.Logf("WaitFor completed with timeout %v. Pods found = %v out of %v", timeout, len(pods), atLeast)
|
||||
Logf("WaitFor completed with timeout %v. Pods found = %v out of %v", timeout, len(pods), atLeast)
|
||||
return pods, err
|
||||
}
|
||||
|
||||
@ -861,25 +885,28 @@ func (cl *ClusterVerification) ForEach(podFunc func(v1.Pod)) error {
|
||||
if len(pods) == 0 {
|
||||
Failf("No pods matched the filter.")
|
||||
}
|
||||
e2elog.Logf("ForEach: Found %v pods from the filter. Now looping through them.", len(pods))
|
||||
Logf("ForEach: Found %v pods from the filter. Now looping through them.", len(pods))
|
||||
for _, p := range pods {
|
||||
podFunc(p)
|
||||
}
|
||||
} else {
|
||||
e2elog.Logf("ForEach: Something went wrong when filtering pods to execute against: %v", err)
|
||||
Logf("ForEach: Something went wrong when filtering pods to execute against: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLogToFileFunc is a convenience function that returns a function that have the same interface as
|
||||
// e2elog.Logf, but writes to a specified file.
|
||||
func GetLogToFileFunc(file *os.File) func(format string, args ...interface{}) {
|
||||
return func(format string, args ...interface{}) {
|
||||
writer := bufio.NewWriter(file)
|
||||
if _, err := fmt.Fprintf(writer, format, args...); err != nil {
|
||||
e2elog.Logf("Failed to write file %v with test performance data: %v", file.Name(), err)
|
||||
}
|
||||
writer.Flush()
|
||||
const (
|
||||
// preconfiguredRuntimeHandler is the name of the runtime handler that is expected to be
|
||||
// preconfigured in the test environment.
|
||||
preconfiguredRuntimeHandler = "test-handler"
|
||||
)
|
||||
|
||||
// PreconfiguredRuntimeClassHandler returns configured runtime handler.
|
||||
func PreconfiguredRuntimeClassHandler() string {
|
||||
if TestContext.ContainerRuntime == "docker" {
|
||||
return TestContext.ContainerRuntime
|
||||
}
|
||||
|
||||
return preconfiguredRuntimeHandler
|
||||
}
|
||||
|
6
vendor/k8s.io/kubernetes/test/e2e/framework/get-kubemark-resource-usage.go
generated
vendored
6
vendor/k8s.io/kubernetes/test/e2e/framework/get-kubemark-resource-usage.go
generated
vendored
@ -21,7 +21,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
||||
)
|
||||
|
||||
@ -47,7 +47,7 @@ func GetKubemarkMasterComponentsResourceUsage() map[string]*KubemarkResourceUsag
|
||||
// Get kubernetes component resource usage
|
||||
sshResult, err := getMasterUsageByPrefix("kube")
|
||||
if err != nil {
|
||||
e2elog.Logf("Error when trying to SSH to master machine. Skipping probe. %v", err)
|
||||
Logf("Error when trying to SSH to master machine. Skipping probe. %v", err)
|
||||
return nil
|
||||
}
|
||||
scanner := bufio.NewScanner(strings.NewReader(sshResult))
|
||||
@ -65,7 +65,7 @@ func GetKubemarkMasterComponentsResourceUsage() map[string]*KubemarkResourceUsag
|
||||
// Get etcd resource usage
|
||||
sshResult, err = getMasterUsageByPrefix("bin/etcd")
|
||||
if err != nil {
|
||||
e2elog.Logf("Error when trying to SSH to master machine. Skipping probe")
|
||||
Logf("Error when trying to SSH to master machine. Skipping probe")
|
||||
return nil
|
||||
}
|
||||
scanner = bufio.NewScanner(strings.NewReader(sshResult))
|
||||
|
14
vendor/k8s.io/kubernetes/test/e2e/framework/google_compute.go
generated
vendored
14
vendor/k8s.io/kubernetes/test/e2e/framework/google_compute.go
generated
vendored
@ -23,8 +23,6 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
// TODO: These should really just use the GCE API client library or at least use
|
||||
@ -48,9 +46,9 @@ func lookupClusterImageSources() (string, string, error) {
|
||||
str = strings.Replace(str, ";", "\n", -1)
|
||||
lines := strings.Split(str, "\n")
|
||||
if err != nil {
|
||||
e2elog.Logf("lookupDiskImageSources: gcloud error with [%#v]; err:%v", argv, err)
|
||||
Logf("lookupDiskImageSources: gcloud error with [%#v]; err:%v", argv, err)
|
||||
for _, l := range lines {
|
||||
e2elog.Logf(" > %s", l)
|
||||
Logf(" > %s", l)
|
||||
}
|
||||
}
|
||||
return lines, err
|
||||
@ -114,11 +112,11 @@ func lookupClusterImageSources() (string, string, error) {
|
||||
func LogClusterImageSources() {
|
||||
masterImg, nodeImg, err := lookupClusterImageSources()
|
||||
if err != nil {
|
||||
e2elog.Logf("Cluster image sources lookup failed: %v\n", err)
|
||||
Logf("Cluster image sources lookup failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
e2elog.Logf("cluster-master-image: %s", masterImg)
|
||||
e2elog.Logf("cluster-node-image: %s", nodeImg)
|
||||
Logf("cluster-master-image: %s", masterImg)
|
||||
Logf("cluster-node-image: %s", nodeImg)
|
||||
|
||||
images := map[string]string{
|
||||
"master_os_image": masterImg,
|
||||
@ -128,7 +126,7 @@ func LogClusterImageSources() {
|
||||
outputBytes, _ := json.MarshalIndent(images, "", " ")
|
||||
filePath := filepath.Join(TestContext.ReportDir, "images.json")
|
||||
if err := ioutil.WriteFile(filePath, outputBytes, 0644); err != nil {
|
||||
e2elog.Logf("cluster images sources, could not write to %q: %v", filePath, err)
|
||||
Logf("cluster images sources, could not write to %q: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
876
vendor/k8s.io/kubernetes/test/e2e/framework/kubelet_stats.go
generated
vendored
876
vendor/k8s.io/kubernetes/test/e2e/framework/kubelet_stats.go
generated
vendored
@ -1,876 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
dockermetrics "k8s.io/kubernetes/pkg/kubelet/dockershim/metrics"
|
||||
kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics"
|
||||
"k8s.io/kubernetes/pkg/master/ports"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/framework/metrics"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// KubeletLatencyMetric stores metrics scraped from the kubelet server's /metric endpoint.
|
||||
// TODO: Get some more structure around the metrics and this type
|
||||
type KubeletLatencyMetric struct {
|
||||
// eg: list, info, create
|
||||
Operation string
|
||||
// eg: sync_pods, pod_worker
|
||||
Method string
|
||||
// 0 <= quantile <=1, e.g. 0.95 is 95%tile, 0.5 is median.
|
||||
Quantile float64
|
||||
Latency time.Duration
|
||||
}
|
||||
|
||||
// KubeletLatencyMetrics implements sort.Interface for []KubeletMetric based on
|
||||
// the latency field.
|
||||
type KubeletLatencyMetrics []KubeletLatencyMetric
|
||||
|
||||
func (a KubeletLatencyMetrics) Len() int { return len(a) }
|
||||
func (a KubeletLatencyMetrics) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a KubeletLatencyMetrics) Less(i, j int) bool { return a[i].Latency > a[j].Latency }
|
||||
|
||||
// If a apiserver client is passed in, the function will try to get kubelet metrics from metrics grabber;
|
||||
// or else, the function will try to get kubelet metrics directly from the node.
|
||||
func getKubeletMetricsFromNode(c clientset.Interface, nodeName string) (metrics.KubeletMetrics, error) {
|
||||
if c == nil {
|
||||
return metrics.GrabKubeletMetricsWithoutProxy(nodeName, "/metrics")
|
||||
}
|
||||
grabber, err := metrics.NewMetricsGrabber(c, nil, true, false, false, false, false)
|
||||
if err != nil {
|
||||
return metrics.KubeletMetrics{}, err
|
||||
}
|
||||
return grabber.GrabFromKubelet(nodeName)
|
||||
}
|
||||
|
||||
// getKubeletMetrics gets all metrics in kubelet subsystem from specified node and trims
|
||||
// the subsystem prefix.
|
||||
func getKubeletMetrics(c clientset.Interface, nodeName string) (metrics.KubeletMetrics, error) {
|
||||
ms, err := getKubeletMetricsFromNode(c, nodeName)
|
||||
if err != nil {
|
||||
return metrics.KubeletMetrics{}, err
|
||||
}
|
||||
|
||||
kubeletMetrics := make(metrics.KubeletMetrics)
|
||||
for name, samples := range ms {
|
||||
const prefix = kubeletmetrics.KubeletSubsystem + "_"
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
// Not a kubelet metric.
|
||||
continue
|
||||
}
|
||||
method := strings.TrimPrefix(name, prefix)
|
||||
kubeletMetrics[method] = samples
|
||||
}
|
||||
return kubeletMetrics, nil
|
||||
}
|
||||
|
||||
// GetDefaultKubeletLatencyMetrics calls GetKubeletLatencyMetrics with a set of default metricNames
|
||||
// identifying common latency metrics.
|
||||
// Note that the KubeletMetrics passed in should not contain subsystem prefix.
|
||||
func GetDefaultKubeletLatencyMetrics(ms metrics.KubeletMetrics) KubeletLatencyMetrics {
|
||||
latencyMetricNames := sets.NewString(
|
||||
kubeletmetrics.PodWorkerDurationKey,
|
||||
kubeletmetrics.PodWorkerStartDurationKey,
|
||||
kubeletmetrics.PodStartDurationKey,
|
||||
kubeletmetrics.CgroupManagerOperationsKey,
|
||||
dockermetrics.DockerOperationsLatencyKey,
|
||||
kubeletmetrics.PodWorkerStartDurationKey,
|
||||
kubeletmetrics.PLEGRelistDurationKey,
|
||||
)
|
||||
return GetKubeletLatencyMetrics(ms, latencyMetricNames)
|
||||
}
|
||||
|
||||
// GetKubeletLatencyMetrics filters ms to include only those contained in the metricNames set,
|
||||
// then constructs a KubeletLatencyMetrics list based on the samples associated with those metrics.
|
||||
func GetKubeletLatencyMetrics(ms metrics.KubeletMetrics, filterMetricNames sets.String) KubeletLatencyMetrics {
|
||||
var latencyMetrics KubeletLatencyMetrics
|
||||
for name, samples := range ms {
|
||||
if !filterMetricNames.Has(name) {
|
||||
continue
|
||||
}
|
||||
for _, sample := range samples {
|
||||
latency := sample.Value
|
||||
operation := string(sample.Metric["operation_type"])
|
||||
var quantile float64
|
||||
if val, ok := sample.Metric[model.QuantileLabel]; ok {
|
||||
var err error
|
||||
if quantile, err = strconv.ParseFloat(string(val), 64); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
latencyMetrics = append(latencyMetrics, KubeletLatencyMetric{
|
||||
Operation: operation,
|
||||
Method: name,
|
||||
Quantile: quantile,
|
||||
Latency: time.Duration(int64(latency)) * time.Microsecond,
|
||||
})
|
||||
}
|
||||
}
|
||||
return latencyMetrics
|
||||
}
|
||||
|
||||
// RuntimeOperationMonitor is the tool getting and parsing docker operation metrics.
|
||||
type RuntimeOperationMonitor struct {
|
||||
client clientset.Interface
|
||||
nodesRuntimeOps map[string]NodeRuntimeOperationErrorRate
|
||||
}
|
||||
|
||||
// NodeRuntimeOperationErrorRate is the runtime operation error rate on one node.
|
||||
type NodeRuntimeOperationErrorRate map[string]*RuntimeOperationErrorRate
|
||||
|
||||
// RuntimeOperationErrorRate is the error rate of a specified runtime operation.
|
||||
type RuntimeOperationErrorRate struct {
|
||||
TotalNumber float64
|
||||
ErrorRate float64
|
||||
TimeoutRate float64
|
||||
}
|
||||
|
||||
// NewRuntimeOperationMonitor returns a new RuntimeOperationMonitor.
|
||||
func NewRuntimeOperationMonitor(c clientset.Interface) *RuntimeOperationMonitor {
|
||||
m := &RuntimeOperationMonitor{
|
||||
client: c,
|
||||
nodesRuntimeOps: make(map[string]NodeRuntimeOperationErrorRate),
|
||||
}
|
||||
nodes, err := m.client.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
Failf("RuntimeOperationMonitor: unable to get list of nodes: %v", err)
|
||||
}
|
||||
for _, node := range nodes.Items {
|
||||
m.nodesRuntimeOps[node.Name] = make(NodeRuntimeOperationErrorRate)
|
||||
}
|
||||
// Initialize the runtime operation error rate
|
||||
m.GetRuntimeOperationErrorRate()
|
||||
return m
|
||||
}
|
||||
|
||||
// GetRuntimeOperationErrorRate gets runtime operation records from kubelet metrics and calculate
|
||||
// error rates of all runtime operations.
|
||||
func (m *RuntimeOperationMonitor) GetRuntimeOperationErrorRate() map[string]NodeRuntimeOperationErrorRate {
|
||||
for node := range m.nodesRuntimeOps {
|
||||
nodeResult, err := getNodeRuntimeOperationErrorRate(m.client, node)
|
||||
if err != nil {
|
||||
e2elog.Logf("GetRuntimeOperationErrorRate: unable to get kubelet metrics from node %q: %v", node, err)
|
||||
continue
|
||||
}
|
||||
m.nodesRuntimeOps[node] = nodeResult
|
||||
}
|
||||
return m.nodesRuntimeOps
|
||||
}
|
||||
|
||||
// GetLatestRuntimeOperationErrorRate gets latest error rate and timeout rate from last observed RuntimeOperationErrorRate.
|
||||
func (m *RuntimeOperationMonitor) GetLatestRuntimeOperationErrorRate() map[string]NodeRuntimeOperationErrorRate {
|
||||
result := make(map[string]NodeRuntimeOperationErrorRate)
|
||||
for node := range m.nodesRuntimeOps {
|
||||
result[node] = make(NodeRuntimeOperationErrorRate)
|
||||
oldNodeResult := m.nodesRuntimeOps[node]
|
||||
curNodeResult, err := getNodeRuntimeOperationErrorRate(m.client, node)
|
||||
if err != nil {
|
||||
e2elog.Logf("GetLatestRuntimeOperationErrorRate: unable to get kubelet metrics from node %q: %v", node, err)
|
||||
continue
|
||||
}
|
||||
for op, cur := range curNodeResult {
|
||||
t := *cur
|
||||
if old, found := oldNodeResult[op]; found {
|
||||
t.ErrorRate = (t.ErrorRate*t.TotalNumber - old.ErrorRate*old.TotalNumber) / (t.TotalNumber - old.TotalNumber)
|
||||
t.TimeoutRate = (t.TimeoutRate*t.TotalNumber - old.TimeoutRate*old.TotalNumber) / (t.TotalNumber - old.TotalNumber)
|
||||
t.TotalNumber -= old.TotalNumber
|
||||
}
|
||||
result[node][op] = &t
|
||||
}
|
||||
m.nodesRuntimeOps[node] = curNodeResult
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FormatRuntimeOperationErrorRate formats the runtime operation error rate to string.
|
||||
func FormatRuntimeOperationErrorRate(nodesResult map[string]NodeRuntimeOperationErrorRate) string {
|
||||
lines := []string{}
|
||||
for node, nodeResult := range nodesResult {
|
||||
lines = append(lines, fmt.Sprintf("node %q runtime operation error rate:", node))
|
||||
for op, result := range nodeResult {
|
||||
line := fmt.Sprintf("operation %q: total - %.0f; error rate - %f; timeout rate - %f", op,
|
||||
result.TotalNumber, result.ErrorRate, result.TimeoutRate)
|
||||
lines = append(lines, line)
|
||||
}
|
||||
lines = append(lines, fmt.Sprintln())
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// getNodeRuntimeOperationErrorRate gets runtime operation error rate from specified node.
|
||||
func getNodeRuntimeOperationErrorRate(c clientset.Interface, node string) (NodeRuntimeOperationErrorRate, error) {
|
||||
result := make(NodeRuntimeOperationErrorRate)
|
||||
ms, err := getKubeletMetrics(c, node)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
// If no corresponding metrics are found, the returned samples will be empty. Then the following
|
||||
// loop will be skipped automatically.
|
||||
allOps := ms[dockermetrics.DockerOperationsKey]
|
||||
errOps := ms[dockermetrics.DockerOperationsErrorsKey]
|
||||
timeoutOps := ms[dockermetrics.DockerOperationsTimeoutKey]
|
||||
for _, sample := range allOps {
|
||||
operation := string(sample.Metric["operation_type"])
|
||||
result[operation] = &RuntimeOperationErrorRate{TotalNumber: float64(sample.Value)}
|
||||
}
|
||||
for _, sample := range errOps {
|
||||
operation := string(sample.Metric["operation_type"])
|
||||
// Should always find the corresponding item, just in case
|
||||
if _, found := result[operation]; found {
|
||||
result[operation].ErrorRate = float64(sample.Value) / result[operation].TotalNumber
|
||||
}
|
||||
}
|
||||
for _, sample := range timeoutOps {
|
||||
operation := string(sample.Metric["operation_type"])
|
||||
if _, found := result[operation]; found {
|
||||
result[operation].TimeoutRate = float64(sample.Value) / result[operation].TotalNumber
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// HighLatencyKubeletOperations logs and counts the high latency metrics exported by the kubelet server via /metrics.
|
||||
func HighLatencyKubeletOperations(c clientset.Interface, threshold time.Duration, nodeName string, logFunc func(fmt string, args ...interface{})) (KubeletLatencyMetrics, error) {
|
||||
ms, err := getKubeletMetrics(c, nodeName)
|
||||
if err != nil {
|
||||
return KubeletLatencyMetrics{}, err
|
||||
}
|
||||
latencyMetrics := GetDefaultKubeletLatencyMetrics(ms)
|
||||
sort.Sort(latencyMetrics)
|
||||
var badMetrics KubeletLatencyMetrics
|
||||
logFunc("\nLatency metrics for node %v", nodeName)
|
||||
for _, m := range latencyMetrics {
|
||||
if m.Latency > threshold {
|
||||
badMetrics = append(badMetrics, m)
|
||||
e2elog.Logf("%+v", m)
|
||||
}
|
||||
}
|
||||
return badMetrics, nil
|
||||
}
|
||||
|
||||
// GetStatsSummary contacts kubelet for the container information.
|
||||
func GetStatsSummary(c clientset.Interface, nodeName string) (*stats.Summary, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), SingleCallTimeout)
|
||||
defer cancel()
|
||||
|
||||
data, err := c.CoreV1().RESTClient().Get().
|
||||
Context(ctx).
|
||||
Resource("nodes").
|
||||
SubResource("proxy").
|
||||
Name(fmt.Sprintf("%v:%v", nodeName, ports.KubeletPort)).
|
||||
Suffix("stats/summary").
|
||||
Do().Raw()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summary := stats.Summary{}
|
||||
err = json.Unmarshal(data, &summary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &summary, nil
|
||||
}
|
||||
|
||||
func removeUint64Ptr(ptr *uint64) uint64 {
|
||||
if ptr == nil {
|
||||
return 0
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// getOneTimeResourceUsageOnNode queries the node's /stats/summary endpoint
|
||||
// and returns the resource usage of all containerNames for the past
|
||||
// cpuInterval.
|
||||
// The acceptable range of the interval is 2s~120s. Be warned that as the
|
||||
// interval (and #containers) increases, the size of kubelet's response
|
||||
// could be significant. E.g., the 60s interval stats for ~20 containers is
|
||||
// ~1.5MB. Don't hammer the node with frequent, heavy requests.
|
||||
//
|
||||
// cadvisor records cumulative cpu usage in nanoseconds, so we need to have two
|
||||
// stats points to compute the cpu usage over the interval. Assuming cadvisor
|
||||
// polls every second, we'd need to get N stats points for N-second interval.
|
||||
// Note that this is an approximation and may not be accurate, hence we also
|
||||
// write the actual interval used for calculation (based on the timestamps of
|
||||
// the stats points in ContainerResourceUsage.CPUInterval.
|
||||
//
|
||||
// containerNames is a function returning a collection of container names in which
|
||||
// user is interested in.
|
||||
func getOneTimeResourceUsageOnNode(
|
||||
c clientset.Interface,
|
||||
nodeName string,
|
||||
cpuInterval time.Duration,
|
||||
containerNames func() []string,
|
||||
) (ResourceUsagePerContainer, error) {
|
||||
const (
|
||||
// cadvisor records stats about every second.
|
||||
cadvisorStatsPollingIntervalInSeconds float64 = 1.0
|
||||
// cadvisor caches up to 2 minutes of stats (configured by kubelet).
|
||||
maxNumStatsToRequest int = 120
|
||||
)
|
||||
|
||||
numStats := int(float64(cpuInterval.Seconds()) / cadvisorStatsPollingIntervalInSeconds)
|
||||
if numStats < 2 || numStats > maxNumStatsToRequest {
|
||||
return nil, fmt.Errorf("numStats needs to be > 1 and < %d", maxNumStatsToRequest)
|
||||
}
|
||||
// Get information of all containers on the node.
|
||||
summary, err := GetStatsSummary(c, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := func(name string, newStats *stats.ContainerStats) *ContainerResourceUsage {
|
||||
if newStats == nil || newStats.CPU == nil || newStats.Memory == nil {
|
||||
return nil
|
||||
}
|
||||
return &ContainerResourceUsage{
|
||||
Name: name,
|
||||
Timestamp: newStats.StartTime.Time,
|
||||
CPUUsageInCores: float64(removeUint64Ptr(newStats.CPU.UsageNanoCores)) / 1000000000,
|
||||
MemoryUsageInBytes: removeUint64Ptr(newStats.Memory.UsageBytes),
|
||||
MemoryWorkingSetInBytes: removeUint64Ptr(newStats.Memory.WorkingSetBytes),
|
||||
MemoryRSSInBytes: removeUint64Ptr(newStats.Memory.RSSBytes),
|
||||
CPUInterval: 0,
|
||||
}
|
||||
}
|
||||
// Process container infos that are relevant to us.
|
||||
containers := containerNames()
|
||||
usageMap := make(ResourceUsagePerContainer, len(containers))
|
||||
observedContainers := []string{}
|
||||
for _, pod := range summary.Pods {
|
||||
for _, container := range pod.Containers {
|
||||
isInteresting := false
|
||||
for _, interestingContainerName := range containers {
|
||||
if container.Name == interestingContainerName {
|
||||
isInteresting = true
|
||||
observedContainers = append(observedContainers, container.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isInteresting {
|
||||
continue
|
||||
}
|
||||
if usage := f(pod.PodRef.Name+"/"+container.Name, &container); usage != nil {
|
||||
usageMap[pod.PodRef.Name+"/"+container.Name] = usage
|
||||
}
|
||||
}
|
||||
}
|
||||
return usageMap, nil
|
||||
}
|
||||
|
||||
func getNodeStatsSummary(c clientset.Interface, nodeName string) (*stats.Summary, error) {
|
||||
data, err := c.CoreV1().RESTClient().Get().
|
||||
Resource("nodes").
|
||||
SubResource("proxy").
|
||||
Name(fmt.Sprintf("%v:%v", nodeName, ports.KubeletPort)).
|
||||
Suffix("stats/summary").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
Do().Raw()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var summary *stats.Summary
|
||||
err = json.Unmarshal(data, &summary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func getSystemContainerStats(summary *stats.Summary) map[string]*stats.ContainerStats {
|
||||
statsList := summary.Node.SystemContainers
|
||||
statsMap := make(map[string]*stats.ContainerStats)
|
||||
for i := range statsList {
|
||||
statsMap[statsList[i].Name] = &statsList[i]
|
||||
}
|
||||
|
||||
// Create a root container stats using information available in
|
||||
// stats.NodeStats. This is necessary since it is a different type.
|
||||
statsMap[rootContainerName] = &stats.ContainerStats{
|
||||
CPU: summary.Node.CPU,
|
||||
Memory: summary.Node.Memory,
|
||||
}
|
||||
return statsMap
|
||||
}
|
||||
|
||||
const (
|
||||
rootContainerName = "/"
|
||||
)
|
||||
|
||||
// TargetContainers returns a list of containers for which we want to collect resource usage.
|
||||
func TargetContainers() []string {
|
||||
return []string{
|
||||
rootContainerName,
|
||||
stats.SystemContainerRuntime,
|
||||
stats.SystemContainerKubelet,
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerResourceUsage is a structure for gathering container resource usage.
|
||||
type ContainerResourceUsage struct {
|
||||
Name string
|
||||
Timestamp time.Time
|
||||
CPUUsageInCores float64
|
||||
MemoryUsageInBytes uint64
|
||||
MemoryWorkingSetInBytes uint64
|
||||
MemoryRSSInBytes uint64
|
||||
// The interval used to calculate CPUUsageInCores.
|
||||
CPUInterval time.Duration
|
||||
}
|
||||
|
||||
func (r *ContainerResourceUsage) isStrictlyGreaterThan(rhs *ContainerResourceUsage) bool {
|
||||
return r.CPUUsageInCores > rhs.CPUUsageInCores && r.MemoryWorkingSetInBytes > rhs.MemoryWorkingSetInBytes
|
||||
}
|
||||
|
||||
// ResourceUsagePerContainer is map of ContainerResourceUsage
|
||||
type ResourceUsagePerContainer map[string]*ContainerResourceUsage
|
||||
|
||||
// ResourceUsagePerNode is map of ResourceUsagePerContainer.
|
||||
type ResourceUsagePerNode map[string]ResourceUsagePerContainer
|
||||
|
||||
func formatResourceUsageStats(nodeName string, containerStats ResourceUsagePerContainer) string {
|
||||
// Example output:
|
||||
//
|
||||
// Resource usage for node "e2e-test-foo-node-abcde":
|
||||
// container cpu(cores) memory(MB)
|
||||
// "/" 0.363 2942.09
|
||||
// "/docker-daemon" 0.088 521.80
|
||||
// "/kubelet" 0.086 424.37
|
||||
// "/system" 0.007 119.88
|
||||
buf := &bytes.Buffer{}
|
||||
w := tabwriter.NewWriter(buf, 1, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "container\tcpu(cores)\tmemory_working_set(MB)\tmemory_rss(MB)\n")
|
||||
for name, s := range containerStats {
|
||||
fmt.Fprintf(w, "%q\t%.3f\t%.2f\t%.2f\n", name, s.CPUUsageInCores, float64(s.MemoryWorkingSetInBytes)/(1024*1024), float64(s.MemoryRSSInBytes)/(1024*1024))
|
||||
}
|
||||
w.Flush()
|
||||
return fmt.Sprintf("Resource usage on node %q:\n%s", nodeName, buf.String())
|
||||
}
|
||||
|
||||
type uint64arr []uint64
|
||||
|
||||
func (a uint64arr) Len() int { return len(a) }
|
||||
func (a uint64arr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a uint64arr) Less(i, j int) bool { return a[i] < a[j] }
|
||||
|
||||
type usageDataPerContainer struct {
|
||||
cpuData []float64
|
||||
memUseData []uint64
|
||||
memWorkSetData []uint64
|
||||
}
|
||||
|
||||
// GetKubeletHeapStats returns stats of kubelet heap.
|
||||
func GetKubeletHeapStats(c clientset.Interface, nodeName string) (string, error) {
|
||||
client, err := NodeProxyRequest(c, nodeName, "debug/pprof/heap", ports.KubeletPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
raw, errRaw := client.Raw()
|
||||
if errRaw != nil {
|
||||
return "", err
|
||||
}
|
||||
stats := string(raw)
|
||||
// Only dumping the runtime.MemStats numbers to avoid polluting the log.
|
||||
numLines := 23
|
||||
lines := strings.Split(stats, "\n")
|
||||
return strings.Join(lines[len(lines)-numLines:], "\n"), nil
|
||||
}
|
||||
|
||||
// PrintAllKubeletPods outputs status of all kubelet pods into log.
|
||||
func PrintAllKubeletPods(c clientset.Interface, nodeName string) {
|
||||
podList, err := GetKubeletPods(c, nodeName)
|
||||
if err != nil {
|
||||
e2elog.Logf("Unable to retrieve kubelet pods for node %v: %v", nodeName, err)
|
||||
return
|
||||
}
|
||||
for _, p := range podList.Items {
|
||||
e2elog.Logf("%v from %v started at %v (%d container statuses recorded)", p.Name, p.Namespace, p.Status.StartTime, len(p.Status.ContainerStatuses))
|
||||
for _, c := range p.Status.ContainerStatuses {
|
||||
e2elog.Logf("\tContainer %v ready: %v, restart count %v",
|
||||
c.Name, c.Ready, c.RestartCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func computeContainerResourceUsage(name string, oldStats, newStats *stats.ContainerStats) *ContainerResourceUsage {
|
||||
return &ContainerResourceUsage{
|
||||
Name: name,
|
||||
Timestamp: newStats.CPU.Time.Time,
|
||||
CPUUsageInCores: float64(*newStats.CPU.UsageCoreNanoSeconds-*oldStats.CPU.UsageCoreNanoSeconds) / float64(newStats.CPU.Time.Time.Sub(oldStats.CPU.Time.Time).Nanoseconds()),
|
||||
MemoryUsageInBytes: *newStats.Memory.UsageBytes,
|
||||
MemoryWorkingSetInBytes: *newStats.Memory.WorkingSetBytes,
|
||||
MemoryRSSInBytes: *newStats.Memory.RSSBytes,
|
||||
CPUInterval: newStats.CPU.Time.Time.Sub(oldStats.CPU.Time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// resourceCollector periodically polls the node, collect stats for a given
|
||||
// list of containers, computes and cache resource usage up to
|
||||
// maxEntriesPerContainer for each container.
|
||||
type resourceCollector struct {
|
||||
lock sync.RWMutex
|
||||
node string
|
||||
containers []string
|
||||
client clientset.Interface
|
||||
buffers map[string][]*ContainerResourceUsage
|
||||
pollingInterval time.Duration
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func newResourceCollector(c clientset.Interface, nodeName string, containerNames []string, pollingInterval time.Duration) *resourceCollector {
|
||||
buffers := make(map[string][]*ContainerResourceUsage)
|
||||
return &resourceCollector{
|
||||
node: nodeName,
|
||||
containers: containerNames,
|
||||
client: c,
|
||||
buffers: buffers,
|
||||
pollingInterval: pollingInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts a goroutine to Poll the node every pollingInterval.
|
||||
func (r *resourceCollector) Start() {
|
||||
r.stopCh = make(chan struct{}, 1)
|
||||
// Keep the last observed stats for comparison.
|
||||
oldStats := make(map[string]*stats.ContainerStats)
|
||||
go wait.Until(func() { r.collectStats(oldStats) }, r.pollingInterval, r.stopCh)
|
||||
}
|
||||
|
||||
// Stop sends a signal to terminate the stats collecting goroutine.
|
||||
func (r *resourceCollector) Stop() {
|
||||
close(r.stopCh)
|
||||
}
|
||||
|
||||
// collectStats gets the latest stats from kubelet stats summary API, computes
|
||||
// the resource usage, and pushes it to the buffer.
|
||||
func (r *resourceCollector) collectStats(oldStatsMap map[string]*stats.ContainerStats) {
|
||||
summary, err := getNodeStatsSummary(r.client, r.node)
|
||||
if err != nil {
|
||||
e2elog.Logf("Error getting node stats summary on %q, err: %v", r.node, err)
|
||||
return
|
||||
}
|
||||
cStatsMap := getSystemContainerStats(summary)
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
for _, name := range r.containers {
|
||||
cStats, ok := cStatsMap[name]
|
||||
if !ok {
|
||||
e2elog.Logf("Missing info/stats for container %q on node %q", name, r.node)
|
||||
return
|
||||
}
|
||||
|
||||
if oldStats, ok := oldStatsMap[name]; ok {
|
||||
if oldStats.CPU.Time.Equal(&cStats.CPU.Time) {
|
||||
// No change -> skip this stat.
|
||||
continue
|
||||
}
|
||||
r.buffers[name] = append(r.buffers[name], computeContainerResourceUsage(name, oldStats, cStats))
|
||||
}
|
||||
// Update the old stats.
|
||||
oldStatsMap[name] = cStats
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resourceCollector) GetLatest() (ResourceUsagePerContainer, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
stats := make(ResourceUsagePerContainer)
|
||||
for _, name := range r.containers {
|
||||
contStats, ok := r.buffers[name]
|
||||
if !ok || len(contStats) == 0 {
|
||||
return nil, fmt.Errorf("Resource usage on node %q is not ready yet", r.node)
|
||||
}
|
||||
stats[name] = contStats[len(contStats)-1]
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// Reset frees the stats and start over.
|
||||
func (r *resourceCollector) Reset() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
for _, name := range r.containers {
|
||||
r.buffers[name] = []*ContainerResourceUsage{}
|
||||
}
|
||||
}
|
||||
|
||||
type resourceUsageByCPU []*ContainerResourceUsage
|
||||
|
||||
func (r resourceUsageByCPU) Len() int { return len(r) }
|
||||
func (r resourceUsageByCPU) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r resourceUsageByCPU) Less(i, j int) bool { return r[i].CPUUsageInCores < r[j].CPUUsageInCores }
|
||||
|
||||
// The percentiles to report.
|
||||
var percentiles = [...]float64{0.05, 0.20, 0.50, 0.70, 0.90, 0.95, 0.99}
|
||||
|
||||
// GetBasicCPUStats returns the percentiles the cpu usage in cores for
|
||||
// containerName. This method examines all data currently in the buffer.
|
||||
func (r *resourceCollector) GetBasicCPUStats(containerName string) map[float64]float64 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
result := make(map[float64]float64, len(percentiles))
|
||||
usages := r.buffers[containerName]
|
||||
sort.Sort(resourceUsageByCPU(usages))
|
||||
for _, q := range percentiles {
|
||||
index := int(float64(len(usages))*q) - 1
|
||||
if index < 0 {
|
||||
// We don't have enough data.
|
||||
result[q] = 0
|
||||
continue
|
||||
}
|
||||
result[q] = usages[index].CPUUsageInCores
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ResourceMonitor manages a resourceCollector per node.
|
||||
type ResourceMonitor struct {
|
||||
client clientset.Interface
|
||||
containers []string
|
||||
pollingInterval time.Duration
|
||||
collectors map[string]*resourceCollector
|
||||
}
|
||||
|
||||
// NewResourceMonitor returns a new ResourceMonitor.
|
||||
func NewResourceMonitor(c clientset.Interface, containerNames []string, pollingInterval time.Duration) *ResourceMonitor {
|
||||
return &ResourceMonitor{
|
||||
containers: containerNames,
|
||||
client: c,
|
||||
pollingInterval: pollingInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts collectors.
|
||||
func (r *ResourceMonitor) Start() {
|
||||
// It should be OK to monitor unschedulable Nodes
|
||||
nodes, err := r.client.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
Failf("ResourceMonitor: unable to get list of nodes: %v", err)
|
||||
}
|
||||
r.collectors = make(map[string]*resourceCollector, 0)
|
||||
for _, node := range nodes.Items {
|
||||
collector := newResourceCollector(r.client, node.Name, r.containers, r.pollingInterval)
|
||||
r.collectors[node.Name] = collector
|
||||
collector.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops collectors.
|
||||
func (r *ResourceMonitor) Stop() {
|
||||
for _, collector := range r.collectors {
|
||||
collector.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets collectors.
|
||||
func (r *ResourceMonitor) Reset() {
|
||||
for _, collector := range r.collectors {
|
||||
collector.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// LogLatest outputs the latest resource usage into log.
|
||||
func (r *ResourceMonitor) LogLatest() {
|
||||
summary, err := r.GetLatest()
|
||||
if err != nil {
|
||||
e2elog.Logf("%v", err)
|
||||
}
|
||||
e2elog.Logf("%s", r.FormatResourceUsage(summary))
|
||||
}
|
||||
|
||||
// FormatResourceUsage returns the formatted string for LogLatest().
|
||||
// TODO(oomichi): This can be made to local function after making test/e2e/node/kubelet_perf.go use LogLatest directly instead.
|
||||
func (r *ResourceMonitor) FormatResourceUsage(s ResourceUsagePerNode) string {
|
||||
summary := []string{}
|
||||
for node, usage := range s {
|
||||
summary = append(summary, formatResourceUsageStats(node, usage))
|
||||
}
|
||||
return strings.Join(summary, "\n")
|
||||
}
|
||||
|
||||
// GetLatest returns the latest resource usage.
|
||||
func (r *ResourceMonitor) GetLatest() (ResourceUsagePerNode, error) {
|
||||
result := make(ResourceUsagePerNode)
|
||||
errs := []error{}
|
||||
for key, collector := range r.collectors {
|
||||
s, err := collector.GetLatest()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
result[key] = s
|
||||
}
|
||||
return result, utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// GetMasterNodeLatest returns the latest resource usage of master and node.
|
||||
func (r *ResourceMonitor) GetMasterNodeLatest(usagePerNode ResourceUsagePerNode) ResourceUsagePerNode {
|
||||
result := make(ResourceUsagePerNode)
|
||||
var masterUsage ResourceUsagePerContainer
|
||||
var nodesUsage []ResourceUsagePerContainer
|
||||
for node, usage := range usagePerNode {
|
||||
if strings.HasSuffix(node, "master") {
|
||||
masterUsage = usage
|
||||
} else {
|
||||
nodesUsage = append(nodesUsage, usage)
|
||||
}
|
||||
}
|
||||
nodeAvgUsage := make(ResourceUsagePerContainer)
|
||||
for _, nodeUsage := range nodesUsage {
|
||||
for c, usage := range nodeUsage {
|
||||
if _, found := nodeAvgUsage[c]; !found {
|
||||
nodeAvgUsage[c] = &ContainerResourceUsage{Name: usage.Name}
|
||||
}
|
||||
nodeAvgUsage[c].CPUUsageInCores += usage.CPUUsageInCores
|
||||
nodeAvgUsage[c].MemoryUsageInBytes += usage.MemoryUsageInBytes
|
||||
nodeAvgUsage[c].MemoryWorkingSetInBytes += usage.MemoryWorkingSetInBytes
|
||||
nodeAvgUsage[c].MemoryRSSInBytes += usage.MemoryRSSInBytes
|
||||
}
|
||||
}
|
||||
for c := range nodeAvgUsage {
|
||||
nodeAvgUsage[c].CPUUsageInCores /= float64(len(nodesUsage))
|
||||
nodeAvgUsage[c].MemoryUsageInBytes /= uint64(len(nodesUsage))
|
||||
nodeAvgUsage[c].MemoryWorkingSetInBytes /= uint64(len(nodesUsage))
|
||||
nodeAvgUsage[c].MemoryRSSInBytes /= uint64(len(nodesUsage))
|
||||
}
|
||||
result["master"] = masterUsage
|
||||
result["node"] = nodeAvgUsage
|
||||
return result
|
||||
}
|
||||
|
||||
// ContainersCPUSummary is indexed by the container name with each entry a
|
||||
// (percentile, value) map.
|
||||
type ContainersCPUSummary map[string]map[float64]float64
|
||||
|
||||
// NodesCPUSummary is indexed by the node name with each entry a
|
||||
// ContainersCPUSummary map.
|
||||
type NodesCPUSummary map[string]ContainersCPUSummary
|
||||
|
||||
// FormatCPUSummary returns the string of human-readable CPU summary from the specified summary data.
|
||||
func (r *ResourceMonitor) FormatCPUSummary(summary NodesCPUSummary) string {
|
||||
// Example output for a node (the percentiles may differ):
|
||||
// CPU usage of containers on node "e2e-test-foo-node-0vj7":
|
||||
// container 5th% 50th% 90th% 95th%
|
||||
// "/" 0.051 0.159 0.387 0.455
|
||||
// "/runtime 0.000 0.000 0.146 0.166
|
||||
// "/kubelet" 0.036 0.053 0.091 0.154
|
||||
// "/misc" 0.001 0.001 0.001 0.002
|
||||
var summaryStrings []string
|
||||
var header []string
|
||||
header = append(header, "container")
|
||||
for _, p := range percentiles {
|
||||
header = append(header, fmt.Sprintf("%.0fth%%", p*100))
|
||||
}
|
||||
for nodeName, containers := range summary {
|
||||
buf := &bytes.Buffer{}
|
||||
w := tabwriter.NewWriter(buf, 1, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "%s\n", strings.Join(header, "\t"))
|
||||
for _, containerName := range TargetContainers() {
|
||||
var s []string
|
||||
s = append(s, fmt.Sprintf("%q", containerName))
|
||||
data, ok := containers[containerName]
|
||||
for _, p := range percentiles {
|
||||
value := "N/A"
|
||||
if ok {
|
||||
value = fmt.Sprintf("%.3f", data[p])
|
||||
}
|
||||
s = append(s, value)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", strings.Join(s, "\t"))
|
||||
}
|
||||
w.Flush()
|
||||
summaryStrings = append(summaryStrings, fmt.Sprintf("CPU usage of containers on node %q\n:%s", nodeName, buf.String()))
|
||||
}
|
||||
return strings.Join(summaryStrings, "\n")
|
||||
}
|
||||
|
||||
// LogCPUSummary outputs summary of CPU into log.
|
||||
func (r *ResourceMonitor) LogCPUSummary() {
|
||||
summary := r.GetCPUSummary()
|
||||
e2elog.Logf("%s", r.FormatCPUSummary(summary))
|
||||
}
|
||||
|
||||
// GetCPUSummary returns summary of CPU.
|
||||
func (r *ResourceMonitor) GetCPUSummary() NodesCPUSummary {
|
||||
result := make(NodesCPUSummary)
|
||||
for nodeName, collector := range r.collectors {
|
||||
result[nodeName] = make(ContainersCPUSummary)
|
||||
for _, containerName := range TargetContainers() {
|
||||
data := collector.GetBasicCPUStats(containerName)
|
||||
result[nodeName][containerName] = data
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetMasterNodeCPUSummary returns summary of master node CPUs.
|
||||
func (r *ResourceMonitor) GetMasterNodeCPUSummary(summaryPerNode NodesCPUSummary) NodesCPUSummary {
|
||||
result := make(NodesCPUSummary)
|
||||
var masterSummary ContainersCPUSummary
|
||||
var nodesSummaries []ContainersCPUSummary
|
||||
for node, summary := range summaryPerNode {
|
||||
if strings.HasSuffix(node, "master") {
|
||||
masterSummary = summary
|
||||
} else {
|
||||
nodesSummaries = append(nodesSummaries, summary)
|
||||
}
|
||||
}
|
||||
|
||||
nodeAvgSummary := make(ContainersCPUSummary)
|
||||
for _, nodeSummary := range nodesSummaries {
|
||||
for c, summary := range nodeSummary {
|
||||
if _, found := nodeAvgSummary[c]; !found {
|
||||
nodeAvgSummary[c] = map[float64]float64{}
|
||||
}
|
||||
for perc, value := range summary {
|
||||
nodeAvgSummary[c][perc] += value
|
||||
}
|
||||
}
|
||||
}
|
||||
for c := range nodeAvgSummary {
|
||||
for perc := range nodeAvgSummary[c] {
|
||||
nodeAvgSummary[c][perc] /= float64(len(nodesSummaries))
|
||||
}
|
||||
}
|
||||
result["master"] = masterSummary
|
||||
result["node"] = nodeAvgSummary
|
||||
return result
|
||||
}
|
106
vendor/k8s.io/kubernetes/test/e2e/framework/log.go
generated
vendored
Normal file
106
vendor/k8s.io/kubernetes/test/e2e/framework/log.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright 2019 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"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
"k8s.io/kubernetes/test/e2e/framework/ginkgowrapper"
|
||||
)
|
||||
|
||||
func nowStamp() string {
|
||||
return time.Now().Format(time.StampMilli)
|
||||
}
|
||||
|
||||
func log(level string, format string, args ...interface{}) {
|
||||
fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
|
||||
}
|
||||
|
||||
// Logf logs the info.
|
||||
func Logf(format string, args ...interface{}) {
|
||||
log("INFO", format, args...)
|
||||
}
|
||||
|
||||
// Failf logs the fail info, including a stack trace.
|
||||
func Failf(format string, args ...interface{}) {
|
||||
FailfWithOffset(1, format, args...)
|
||||
}
|
||||
|
||||
// FailfWithOffset calls "Fail" and logs the error with a stack trace that starts at "offset" levels above its caller
|
||||
// (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
|
||||
func FailfWithOffset(offset int, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
skip := offset + 1
|
||||
log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
|
||||
ginkgowrapper.Fail(nowStamp()+": "+msg, skip)
|
||||
}
|
||||
|
||||
// Fail is a replacement for ginkgo.Fail which logs the problem as it occurs
|
||||
// together with a stack trace and then calls ginkgowrapper.Fail.
|
||||
func Fail(msg string, callerSkip ...int) {
|
||||
skip := 1
|
||||
if len(callerSkip) > 0 {
|
||||
skip += callerSkip[0]
|
||||
}
|
||||
log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
|
||||
ginkgowrapper.Fail(nowStamp()+": "+msg, skip)
|
||||
}
|
||||
|
||||
var codeFilterRE = regexp.MustCompile(`/github.com/onsi/ginkgo/`)
|
||||
|
||||
// PrunedStack is a wrapper around debug.Stack() that removes information
|
||||
// about the current goroutine and optionally skips some of the initial stack entries.
|
||||
// With skip == 0, the returned stack will start with the caller of PruneStack.
|
||||
// From the remaining entries it automatically filters out useless ones like
|
||||
// entries coming from Ginkgo.
|
||||
//
|
||||
// This is a modified copy of PruneStack in https://github.com/onsi/ginkgo/blob/f90f37d87fa6b1dd9625e2b1e83c23ffae3de228/internal/codelocation/code_location.go#L25:
|
||||
// - simplified API and thus renamed (calls debug.Stack() instead of taking a parameter)
|
||||
// - source code filtering updated to be specific to Kubernetes
|
||||
func PrunedStack(skip int) string {
|
||||
fullStackTrace := string(debug.Stack())
|
||||
stack := strings.Split(fullStackTrace, "\n")
|
||||
// Ensure that the even entries are the method names and the
|
||||
// the odd entries the source code information.
|
||||
if len(stack) > 0 && strings.HasPrefix(stack[0], "goroutine ") {
|
||||
// Ignore "goroutine 29 [running]:" line.
|
||||
stack = stack[1:]
|
||||
}
|
||||
// The "+2" is for skipping over:
|
||||
// - runtime/debug.Stack()
|
||||
// - PrunedStack()
|
||||
skip += 2
|
||||
if len(stack) > 2*skip {
|
||||
stack = stack[2*skip:]
|
||||
}
|
||||
prunedStack := []string{}
|
||||
for i := 0; i < len(stack)/2; i++ {
|
||||
// We filter out based on the source code file name.
|
||||
if !codeFilterRE.Match([]byte(stack[i*2+1])) {
|
||||
prunedStack = append(prunedStack, stack[i*2])
|
||||
prunedStack = append(prunedStack, stack[i*2+1])
|
||||
}
|
||||
}
|
||||
return strings.Join(prunedStack, "\n")
|
||||
}
|
28
vendor/k8s.io/kubernetes/test/e2e/framework/log/logger.go
generated
vendored
28
vendor/k8s.io/kubernetes/test/e2e/framework/log/logger.go
generated
vendored
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package log will be removed after switching to use core framework log.
|
||||
// Do not make further changes here!
|
||||
package log
|
||||
|
||||
import (
|
||||
@ -21,6 +23,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
"k8s.io/kubernetes/test/e2e/framework/ginkgowrapper"
|
||||
)
|
||||
|
||||
func nowStamp() string {
|
||||
@ -35,3 +39,27 @@ func log(level string, format string, args ...interface{}) {
|
||||
func Logf(format string, args ...interface{}) {
|
||||
log("INFO", format, args...)
|
||||
}
|
||||
|
||||
// Failf logs the fail info.
|
||||
func Failf(format string, args ...interface{}) {
|
||||
FailfWithOffset(1, format, args...)
|
||||
}
|
||||
|
||||
// FailfWithOffset calls "Fail" and logs the error at "offset" levels above its caller
|
||||
// (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
|
||||
func FailfWithOffset(offset int, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
log("FAIL", msg)
|
||||
ginkgowrapper.Fail(nowStamp()+": "+msg, 1+offset)
|
||||
}
|
||||
|
||||
// Fail is a replacement for ginkgo.Fail which logs the problem as it occurs
|
||||
// and then calls ginkgowrapper.Fail.
|
||||
func Fail(msg string, callerSkip ...int) {
|
||||
skip := 1
|
||||
if len(callerSkip) > 0 {
|
||||
skip += callerSkip[0]
|
||||
}
|
||||
log("FAIL", msg)
|
||||
ginkgowrapper.Fail(nowStamp()+": "+msg, skip)
|
||||
}
|
||||
|
9
vendor/k8s.io/kubernetes/test/e2e/framework/log_size_monitoring.go
generated
vendored
9
vendor/k8s.io/kubernetes/test/e2e/framework/log_size_monitoring.go
generated
vendored
@ -26,7 +26,8 @@ import (
|
||||
"time"
|
||||
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
||||
)
|
||||
|
||||
@ -258,10 +259,10 @@ func (g *LogSizeGatherer) Work() bool {
|
||||
TestContext.Provider,
|
||||
)
|
||||
if err != nil {
|
||||
e2elog.Logf("Error while trying to SSH to %v, skipping probe. Error: %v", workItem.ip, err)
|
||||
Logf("Error while trying to SSH to %v, skipping probe. Error: %v", workItem.ip, err)
|
||||
// In case of repeated error give up.
|
||||
if workItem.backoffMultiplier >= 128 {
|
||||
e2elog.Logf("Failed to ssh to a node %v multiple times in a row. Giving up.", workItem.ip)
|
||||
Logf("Failed to ssh to a node %v multiple times in a row. Giving up.", workItem.ip)
|
||||
g.wg.Done()
|
||||
return false
|
||||
}
|
||||
@ -277,7 +278,7 @@ func (g *LogSizeGatherer) Work() bool {
|
||||
path := results[i]
|
||||
size, err := strconv.Atoi(results[i+1])
|
||||
if err != nil {
|
||||
e2elog.Logf("Error during conversion to int: %v, skipping data. Error: %v", results[i+1], err)
|
||||
Logf("Error during conversion to int: %v, skipping data. Error: %v", results[i+1], err)
|
||||
continue
|
||||
}
|
||||
g.data.addNewData(workItem.ip, path, now, size)
|
||||
|
89
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/api.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/api.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2019 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 (
|
||||
"fmt"
|
||||
|
||||
e2eperftype "k8s.io/kubernetes/test/e2e/perftype"
|
||||
)
|
||||
|
||||
// APICall is a struct for managing API call.
|
||||
type APICall struct {
|
||||
Resource string `json:"resource"`
|
||||
Subresource string `json:"subresource"`
|
||||
Verb string `json:"verb"`
|
||||
Scope string `json:"scope"`
|
||||
Latency LatencyMetric `json:"latency"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// APIResponsiveness is a struct for managing multiple API calls.
|
||||
type APIResponsiveness struct {
|
||||
APICalls []APICall `json:"apicalls"`
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of API responsiveness.
|
||||
func (a *APIResponsiveness) SummaryKind() string {
|
||||
return "APIResponsiveness"
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns metrics with JSON format.
|
||||
func (a *APIResponsiveness) PrintHumanReadable() string {
|
||||
return PrettyPrintJSON(a)
|
||||
}
|
||||
|
||||
// PrintJSON returns metrics of PerfData(50, 90 and 99th percentiles) with JSON format.
|
||||
func (a *APIResponsiveness) PrintJSON() string {
|
||||
return PrettyPrintJSON(APICallToPerfData(a))
|
||||
}
|
||||
|
||||
func (a *APIResponsiveness) Len() int { return len(a.APICalls) }
|
||||
func (a *APIResponsiveness) Swap(i, j int) {
|
||||
a.APICalls[i], a.APICalls[j] = a.APICalls[j], a.APICalls[i]
|
||||
}
|
||||
func (a *APIResponsiveness) Less(i, j int) bool {
|
||||
return a.APICalls[i].Latency.Perc99 < a.APICalls[j].Latency.Perc99
|
||||
}
|
||||
|
||||
// currentAPICallMetricsVersion is the current apicall performance metrics version. We should
|
||||
// bump up the version each time we make incompatible change to the metrics.
|
||||
const currentAPICallMetricsVersion = "v1"
|
||||
|
||||
// APICallToPerfData transforms APIResponsiveness to PerfData.
|
||||
func APICallToPerfData(apicalls *APIResponsiveness) *e2eperftype.PerfData {
|
||||
perfData := &e2eperftype.PerfData{Version: currentAPICallMetricsVersion}
|
||||
for _, apicall := range apicalls.APICalls {
|
||||
item := e2eperftype.DataItem{
|
||||
Data: map[string]float64{
|
||||
"Perc50": float64(apicall.Latency.Perc50) / 1000000, // us -> ms
|
||||
"Perc90": float64(apicall.Latency.Perc90) / 1000000,
|
||||
"Perc99": float64(apicall.Latency.Perc99) / 1000000,
|
||||
},
|
||||
Unit: "ms",
|
||||
Labels: map[string]string{
|
||||
"Verb": apicall.Verb,
|
||||
"Resource": apicall.Resource,
|
||||
"Subresource": apicall.Subresource,
|
||||
"Scope": apicall.Scope,
|
||||
"Count": fmt.Sprintf("%v", apicall.Count),
|
||||
},
|
||||
}
|
||||
perfData.DataItems = append(perfData.DataItems, item)
|
||||
}
|
||||
return perfData
|
||||
}
|
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/api_server_metrics.go
generated
vendored
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/api_server_metrics.go
generated
vendored
@ -16,22 +16,24 @@ limitations under the License.
|
||||
|
||||
package metrics
|
||||
|
||||
import "k8s.io/component-base/metrics/testutil"
|
||||
|
||||
// APIServerMetrics is metrics for API server
|
||||
type APIServerMetrics Metrics
|
||||
type APIServerMetrics testutil.Metrics
|
||||
|
||||
// Equal returns true if all metrics are the same as the arguments.
|
||||
func (m *APIServerMetrics) Equal(o APIServerMetrics) bool {
|
||||
return (*Metrics)(m).Equal(Metrics(o))
|
||||
return (*testutil.Metrics)(m).Equal(testutil.Metrics(o))
|
||||
}
|
||||
|
||||
func newAPIServerMetrics() APIServerMetrics {
|
||||
result := NewMetrics()
|
||||
result := testutil.NewMetrics()
|
||||
return APIServerMetrics(result)
|
||||
}
|
||||
|
||||
func parseAPIServerMetrics(data string) (APIServerMetrics, error) {
|
||||
result := newAPIServerMetrics()
|
||||
if err := parseMetrics(data, (*Metrics)(&result)); err != nil {
|
||||
if err := testutil.ParseMetrics(data, (*testutil.Metrics)(&result)); err != nil {
|
||||
return APIServerMetrics{}, err
|
||||
}
|
||||
return result, nil
|
||||
|
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/cluster_autoscaler_metrics.go
generated
vendored
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/cluster_autoscaler_metrics.go
generated
vendored
@ -16,22 +16,24 @@ limitations under the License.
|
||||
|
||||
package metrics
|
||||
|
||||
import "k8s.io/component-base/metrics/testutil"
|
||||
|
||||
// ClusterAutoscalerMetrics is metrics for cluster autoscaler
|
||||
type ClusterAutoscalerMetrics Metrics
|
||||
type ClusterAutoscalerMetrics testutil.Metrics
|
||||
|
||||
// Equal returns true if all metrics are the same as the arguments.
|
||||
func (m *ClusterAutoscalerMetrics) Equal(o ClusterAutoscalerMetrics) bool {
|
||||
return (*Metrics)(m).Equal(Metrics(o))
|
||||
return (*testutil.Metrics)(m).Equal(testutil.Metrics(o))
|
||||
}
|
||||
|
||||
func newClusterAutoscalerMetrics() ClusterAutoscalerMetrics {
|
||||
result := NewMetrics()
|
||||
result := testutil.NewMetrics()
|
||||
return ClusterAutoscalerMetrics(result)
|
||||
}
|
||||
|
||||
func parseClusterAutoscalerMetrics(data string) (ClusterAutoscalerMetrics, error) {
|
||||
result := newClusterAutoscalerMetrics()
|
||||
if err := parseMetrics(data, (*Metrics)(&result)); err != nil {
|
||||
if err := testutil.ParseMetrics(data, (*testutil.Metrics)(&result)); err != nil {
|
||||
return ClusterAutoscalerMetrics{}, err
|
||||
}
|
||||
return result, nil
|
||||
|
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/controller_manager_metrics.go
generated
vendored
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/controller_manager_metrics.go
generated
vendored
@ -16,22 +16,24 @@ limitations under the License.
|
||||
|
||||
package metrics
|
||||
|
||||
import "k8s.io/component-base/metrics/testutil"
|
||||
|
||||
// ControllerManagerMetrics is metrics for controller manager
|
||||
type ControllerManagerMetrics Metrics
|
||||
type ControllerManagerMetrics testutil.Metrics
|
||||
|
||||
// Equal returns true if all metrics are the same as the arguments.
|
||||
func (m *ControllerManagerMetrics) Equal(o ControllerManagerMetrics) bool {
|
||||
return (*Metrics)(m).Equal(Metrics(o))
|
||||
return (*testutil.Metrics)(m).Equal(testutil.Metrics(o))
|
||||
}
|
||||
|
||||
func newControllerManagerMetrics() ControllerManagerMetrics {
|
||||
result := NewMetrics()
|
||||
result := testutil.NewMetrics()
|
||||
return ControllerManagerMetrics(result)
|
||||
}
|
||||
|
||||
func parseControllerManagerMetrics(data string) (ControllerManagerMetrics, error) {
|
||||
result := newControllerManagerMetrics()
|
||||
if err := parseMetrics(data, (*Metrics)(&result)); err != nil {
|
||||
if err := testutil.ParseMetrics(data, (*testutil.Metrics)(&result)); err != nil {
|
||||
return ControllerManagerMetrics{}, err
|
||||
}
|
||||
return result, nil
|
||||
|
127
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/e2e_metrics.go
generated
vendored
Normal file
127
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/e2e_metrics.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2019 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// Cluster Autoscaler metrics names
|
||||
caFunctionMetric = "cluster_autoscaler_function_duration_seconds_bucket"
|
||||
caFunctionMetricLabel = "function"
|
||||
)
|
||||
|
||||
// ComponentCollection is metrics collection of components.
|
||||
type ComponentCollection Collection
|
||||
|
||||
func (m *ComponentCollection) filterMetrics() {
|
||||
apiServerMetrics := make(APIServerMetrics)
|
||||
for _, metric := range interestingAPIServerMetrics {
|
||||
apiServerMetrics[metric] = (*m).APIServerMetrics[metric]
|
||||
}
|
||||
controllerManagerMetrics := make(ControllerManagerMetrics)
|
||||
for _, metric := range interestingControllerManagerMetrics {
|
||||
controllerManagerMetrics[metric] = (*m).ControllerManagerMetrics[metric]
|
||||
}
|
||||
kubeletMetrics := make(map[string]KubeletMetrics)
|
||||
for kubelet, grabbed := range (*m).KubeletMetrics {
|
||||
kubeletMetrics[kubelet] = make(KubeletMetrics)
|
||||
for _, metric := range interestingKubeletMetrics {
|
||||
kubeletMetrics[kubelet][metric] = grabbed[metric]
|
||||
}
|
||||
}
|
||||
(*m).APIServerMetrics = apiServerMetrics
|
||||
(*m).ControllerManagerMetrics = controllerManagerMetrics
|
||||
(*m).KubeletMetrics = kubeletMetrics
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns e2e metrics with JSON format.
|
||||
func (m *ComponentCollection) PrintHumanReadable() string {
|
||||
buf := bytes.Buffer{}
|
||||
for _, interestingMetric := range interestingAPIServerMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", interestingMetric))
|
||||
for _, sample := range (*m).APIServerMetrics[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t%v\n", testutil.PrintSample(sample)))
|
||||
}
|
||||
}
|
||||
for _, interestingMetric := range interestingControllerManagerMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", interestingMetric))
|
||||
for _, sample := range (*m).ControllerManagerMetrics[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t%v\n", testutil.PrintSample(sample)))
|
||||
}
|
||||
}
|
||||
for _, interestingMetric := range interestingClusterAutoscalerMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", interestingMetric))
|
||||
for _, sample := range (*m).ClusterAutoscalerMetrics[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t%v\n", testutil.PrintSample(sample)))
|
||||
}
|
||||
}
|
||||
for kubelet, grabbed := range (*m).KubeletMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", kubelet))
|
||||
for _, interestingMetric := range interestingKubeletMetrics {
|
||||
buf.WriteString(fmt.Sprintf("\tFor %v:\n", interestingMetric))
|
||||
for _, sample := range grabbed[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t\t%v\n", testutil.PrintSample(sample)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PrettyPrintJSON converts metrics to JSON format.
|
||||
// TODO: This function should be replaced with framework.PrettyPrintJSON after solving
|
||||
// circulary dependency between core framework and this metrics subpackage.
|
||||
func PrettyPrintJSON(metrics interface{}) string {
|
||||
output := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(output).Encode(metrics); err != nil {
|
||||
e2elog.Logf("Error building encoder: %v", err)
|
||||
return ""
|
||||
}
|
||||
formatted := &bytes.Buffer{}
|
||||
if err := json.Indent(formatted, output.Bytes(), "", " "); err != nil {
|
||||
e2elog.Logf("Error indenting: %v", err)
|
||||
return ""
|
||||
}
|
||||
return string(formatted.Bytes())
|
||||
}
|
||||
|
||||
// PrintJSON returns e2e metrics with JSON format.
|
||||
func (m *ComponentCollection) PrintJSON() string {
|
||||
m.filterMetrics()
|
||||
return PrettyPrintJSON(m)
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of e2e metrics.
|
||||
func (m *ComponentCollection) SummaryKind() string {
|
||||
return "ComponentCollection"
|
||||
}
|
||||
|
||||
// ComputeClusterAutoscalerMetricsDelta computes the change in cluster
|
||||
// autoscaler metrics.
|
||||
func (m *ComponentCollection) ComputeClusterAutoscalerMetricsDelta(before Collection) {
|
||||
if beforeSamples, found := before.ClusterAutoscalerMetrics[caFunctionMetric]; found {
|
||||
if afterSamples, found := m.ClusterAutoscalerMetrics[caFunctionMetric]; found {
|
||||
testutil.ComputeHistogramDelta(beforeSamples, afterSamples, caFunctionMetricLabel)
|
||||
}
|
||||
}
|
||||
}
|
81
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/generic_metrics.go
generated
vendored
81
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/generic_metrics.go
generated
vendored
@ -1,81 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/expfmt"
|
||||
"github.com/prometheus/common/model"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// Metrics is generic metrics for other specific metrics
|
||||
type Metrics map[string]model.Samples
|
||||
|
||||
// Equal returns true if all metrics are the same as the arguments.
|
||||
func (m *Metrics) Equal(o Metrics) bool {
|
||||
leftKeySet := []string{}
|
||||
rightKeySet := []string{}
|
||||
for k := range *m {
|
||||
leftKeySet = append(leftKeySet, k)
|
||||
}
|
||||
for k := range o {
|
||||
rightKeySet = append(rightKeySet, k)
|
||||
}
|
||||
if !reflect.DeepEqual(leftKeySet, rightKeySet) {
|
||||
return false
|
||||
}
|
||||
for _, k := range leftKeySet {
|
||||
if !(*m)[k].Equal(o[k]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NewMetrics returns new metrics which are initialized.
|
||||
func NewMetrics() Metrics {
|
||||
result := make(Metrics)
|
||||
return result
|
||||
}
|
||||
|
||||
func parseMetrics(data string, output *Metrics) error {
|
||||
dec := expfmt.NewDecoder(strings.NewReader(data), expfmt.FmtText)
|
||||
decoder := expfmt.SampleDecoder{
|
||||
Dec: dec,
|
||||
Opts: &expfmt.DecodeOptions{},
|
||||
}
|
||||
|
||||
for {
|
||||
var v model.Vector
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
if err == io.EOF {
|
||||
// Expected loop termination condition.
|
||||
return nil
|
||||
}
|
||||
klog.Warningf("Invalid Decode. Skipping.")
|
||||
continue
|
||||
}
|
||||
for _, metric := range v {
|
||||
name := string(metric.Metric[model.MetricNameLabel])
|
||||
(*output)[name] = append((*output)[name], metric)
|
||||
}
|
||||
}
|
||||
}
|
57
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/interesting_metrics.go
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/interesting_metrics.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2019 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
|
||||
|
||||
var interestingAPIServerMetrics = []string{
|
||||
"apiserver_request_total",
|
||||
"apiserver_request_latency_seconds",
|
||||
"apiserver_init_events_total",
|
||||
}
|
||||
|
||||
var interestingControllerManagerMetrics = []string{
|
||||
"garbage_collector_attempt_to_delete_queue_latency",
|
||||
"garbage_collector_attempt_to_delete_work_duration",
|
||||
"garbage_collector_attempt_to_orphan_queue_latency",
|
||||
"garbage_collector_attempt_to_orphan_work_duration",
|
||||
"garbage_collector_dirty_processing_latency_microseconds",
|
||||
"garbage_collector_event_processing_latency_microseconds",
|
||||
"garbage_collector_graph_changes_queue_latency",
|
||||
"garbage_collector_graph_changes_work_duration",
|
||||
"garbage_collector_orphan_processing_latency_microseconds",
|
||||
|
||||
"namespace_queue_latency",
|
||||
"namespace_queue_latency_sum",
|
||||
"namespace_queue_latency_count",
|
||||
"namespace_retries",
|
||||
"namespace_work_duration",
|
||||
"namespace_work_duration_sum",
|
||||
"namespace_work_duration_count",
|
||||
}
|
||||
|
||||
var interestingKubeletMetrics = []string{
|
||||
"kubelet_docker_operations_errors_total",
|
||||
"kubelet_docker_operations_duration_seconds",
|
||||
"kubelet_pod_start_duration_seconds",
|
||||
"kubelet_pod_worker_duration_seconds",
|
||||
"kubelet_pod_worker_start_duration_seconds",
|
||||
}
|
||||
|
||||
var interestingClusterAutoscalerMetrics = []string{
|
||||
"function_duration_seconds",
|
||||
"errors_total",
|
||||
"evicted_pods_total",
|
||||
}
|
140
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/kubelet_metrics.go
generated
vendored
140
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/kubelet_metrics.go
generated
vendored
@ -20,7 +20,17 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
dockermetrics "k8s.io/kubernetes/pkg/kubelet/dockershim/metrics"
|
||||
kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,16 +38,16 @@ const (
|
||||
)
|
||||
|
||||
// KubeletMetrics is metrics for kubelet
|
||||
type KubeletMetrics Metrics
|
||||
type KubeletMetrics testutil.Metrics
|
||||
|
||||
// Equal returns true if all metrics are the same as the arguments.
|
||||
func (m *KubeletMetrics) Equal(o KubeletMetrics) bool {
|
||||
return (*Metrics)(m).Equal(Metrics(o))
|
||||
return (*testutil.Metrics)(m).Equal(testutil.Metrics(o))
|
||||
}
|
||||
|
||||
// NewKubeletMetrics returns new metrics which are initialized.
|
||||
func NewKubeletMetrics() KubeletMetrics {
|
||||
result := NewMetrics()
|
||||
result := testutil.NewMetrics()
|
||||
return KubeletMetrics(result)
|
||||
}
|
||||
|
||||
@ -58,7 +68,7 @@ func GrabKubeletMetricsWithoutProxy(nodeName, path string) (KubeletMetrics, erro
|
||||
|
||||
func parseKubeletMetrics(data string) (KubeletMetrics, error) {
|
||||
result := NewKubeletMetrics()
|
||||
if err := parseMetrics(data, (*Metrics)(&result)); err != nil {
|
||||
if err := testutil.ParseMetrics(data, (*testutil.Metrics)(&result)); err != nil {
|
||||
return KubeletMetrics{}, err
|
||||
}
|
||||
return result, nil
|
||||
@ -66,8 +76,7 @@ func parseKubeletMetrics(data string) (KubeletMetrics, error) {
|
||||
|
||||
func (g *Grabber) getMetricsFromNode(nodeName string, kubeletPort int) (string, error) {
|
||||
// There's a problem with timing out during proxy. Wrapping this in a goroutine to prevent deadlock.
|
||||
// Hanging goroutine will be leaked.
|
||||
finished := make(chan struct{})
|
||||
finished := make(chan struct{}, 1)
|
||||
var err error
|
||||
var rawOutput []byte
|
||||
go func() {
|
||||
@ -89,3 +98,122 @@ func (g *Grabber) getMetricsFromNode(nodeName string, kubeletPort int) (string,
|
||||
return string(rawOutput), nil
|
||||
}
|
||||
}
|
||||
|
||||
// KubeletLatencyMetric stores metrics scraped from the kubelet server's /metric endpoint.
|
||||
// TODO: Get some more structure around the metrics and this type
|
||||
type KubeletLatencyMetric struct {
|
||||
// eg: list, info, create
|
||||
Operation string
|
||||
// eg: sync_pods, pod_worker
|
||||
Method string
|
||||
// 0 <= quantile <=1, e.g. 0.95 is 95%tile, 0.5 is median.
|
||||
Quantile float64
|
||||
Latency time.Duration
|
||||
}
|
||||
|
||||
// KubeletLatencyMetrics implements sort.Interface for []KubeletMetric based on
|
||||
// the latency field.
|
||||
type KubeletLatencyMetrics []KubeletLatencyMetric
|
||||
|
||||
func (a KubeletLatencyMetrics) Len() int { return len(a) }
|
||||
func (a KubeletLatencyMetrics) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a KubeletLatencyMetrics) Less(i, j int) bool { return a[i].Latency > a[j].Latency }
|
||||
|
||||
// If a apiserver client is passed in, the function will try to get kubelet metrics from metrics grabber;
|
||||
// or else, the function will try to get kubelet metrics directly from the node.
|
||||
func getKubeletMetricsFromNode(c clientset.Interface, nodeName string) (KubeletMetrics, error) {
|
||||
if c == nil {
|
||||
return GrabKubeletMetricsWithoutProxy(nodeName, "/metrics")
|
||||
}
|
||||
grabber, err := NewMetricsGrabber(c, nil, true, false, false, false, false)
|
||||
if err != nil {
|
||||
return KubeletMetrics{}, err
|
||||
}
|
||||
return grabber.GrabFromKubelet(nodeName)
|
||||
}
|
||||
|
||||
// GetKubeletMetrics gets all metrics in kubelet subsystem from specified node and trims
|
||||
// the subsystem prefix.
|
||||
func GetKubeletMetrics(c clientset.Interface, nodeName string) (KubeletMetrics, error) {
|
||||
ms, err := getKubeletMetricsFromNode(c, nodeName)
|
||||
if err != nil {
|
||||
return KubeletMetrics{}, err
|
||||
}
|
||||
|
||||
kubeletMetrics := make(KubeletMetrics)
|
||||
for name, samples := range ms {
|
||||
const prefix = kubeletmetrics.KubeletSubsystem + "_"
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
// Not a kubelet metric.
|
||||
continue
|
||||
}
|
||||
method := strings.TrimPrefix(name, prefix)
|
||||
kubeletMetrics[method] = samples
|
||||
}
|
||||
return kubeletMetrics, nil
|
||||
}
|
||||
|
||||
// GetDefaultKubeletLatencyMetrics calls GetKubeletLatencyMetrics with a set of default metricNames
|
||||
// identifying common latency metrics.
|
||||
// Note that the KubeletMetrics passed in should not contain subsystem prefix.
|
||||
func GetDefaultKubeletLatencyMetrics(ms KubeletMetrics) KubeletLatencyMetrics {
|
||||
latencyMetricNames := sets.NewString(
|
||||
kubeletmetrics.PodWorkerDurationKey,
|
||||
kubeletmetrics.PodWorkerStartDurationKey,
|
||||
kubeletmetrics.PodStartDurationKey,
|
||||
kubeletmetrics.CgroupManagerOperationsKey,
|
||||
dockermetrics.DockerOperationsLatencyKey,
|
||||
kubeletmetrics.PodWorkerStartDurationKey,
|
||||
kubeletmetrics.PLEGRelistDurationKey,
|
||||
)
|
||||
return GetKubeletLatencyMetrics(ms, latencyMetricNames)
|
||||
}
|
||||
|
||||
// GetKubeletLatencyMetrics filters ms to include only those contained in the metricNames set,
|
||||
// then constructs a KubeletLatencyMetrics list based on the samples associated with those metrics.
|
||||
func GetKubeletLatencyMetrics(ms KubeletMetrics, filterMetricNames sets.String) KubeletLatencyMetrics {
|
||||
var latencyMetrics KubeletLatencyMetrics
|
||||
for name, samples := range ms {
|
||||
if !filterMetricNames.Has(name) {
|
||||
continue
|
||||
}
|
||||
for _, sample := range samples {
|
||||
latency := sample.Value
|
||||
operation := string(sample.Metric["operation_type"])
|
||||
var quantile float64
|
||||
if val, ok := sample.Metric[testutil.QuantileLabel]; ok {
|
||||
var err error
|
||||
if quantile, err = strconv.ParseFloat(string(val), 64); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
latencyMetrics = append(latencyMetrics, KubeletLatencyMetric{
|
||||
Operation: operation,
|
||||
Method: name,
|
||||
Quantile: quantile,
|
||||
Latency: time.Duration(int64(latency)) * time.Microsecond,
|
||||
})
|
||||
}
|
||||
}
|
||||
return latencyMetrics
|
||||
}
|
||||
|
||||
// HighLatencyKubeletOperations logs and counts the high latency metrics exported by the kubelet server via /metrics.
|
||||
func HighLatencyKubeletOperations(c clientset.Interface, threshold time.Duration, nodeName string, logFunc func(fmt string, args ...interface{})) (KubeletLatencyMetrics, error) {
|
||||
ms, err := GetKubeletMetrics(c, nodeName)
|
||||
if err != nil {
|
||||
return KubeletLatencyMetrics{}, err
|
||||
}
|
||||
latencyMetrics := GetDefaultKubeletLatencyMetrics(ms)
|
||||
sort.Sort(latencyMetrics)
|
||||
var badMetrics KubeletLatencyMetrics
|
||||
logFunc("\nLatency metrics for node %v", nodeName)
|
||||
for _, m := range latencyMetrics {
|
||||
if m.Latency > threshold {
|
||||
badMetrics = append(badMetrics, m)
|
||||
e2elog.Logf("%+v", m)
|
||||
}
|
||||
}
|
||||
return badMetrics, nil
|
||||
}
|
||||
|
81
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/latencies.go
generated
vendored
Normal file
81
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/latencies.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2019 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// SingleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
|
||||
// transient failures from failing tests.
|
||||
// TODO: client should not apply this timeout to Watch calls. Increased from 30s until that is fixed.
|
||||
SingleCallTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// VerifyLatencyWithinThreshold verifies whether 50, 90 and 99th percentiles of a latency metric are
|
||||
// within the expected threshold.
|
||||
func VerifyLatencyWithinThreshold(threshold, actual LatencyMetric, metricName string) error {
|
||||
if actual.Perc50 > threshold.Perc50 {
|
||||
return fmt.Errorf("too high %v latency 50th percentile: %v", metricName, actual.Perc50)
|
||||
}
|
||||
if actual.Perc90 > threshold.Perc90 {
|
||||
return fmt.Errorf("too high %v latency 90th percentile: %v", metricName, actual.Perc90)
|
||||
}
|
||||
if actual.Perc99 > threshold.Perc99 {
|
||||
return fmt.Errorf("too high %v latency 99th percentile: %v", metricName, actual.Perc99)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PodLatencyData encapsulates pod startup latency information.
|
||||
type PodLatencyData struct {
|
||||
// Name of the pod
|
||||
Name string
|
||||
// Node this pod was running on
|
||||
Node string
|
||||
// Latency information related to pod startuptime
|
||||
Latency time.Duration
|
||||
}
|
||||
|
||||
// LatencySlice is an array of PodLatencyData which encapsulates pod startup latency information.
|
||||
type LatencySlice []PodLatencyData
|
||||
|
||||
func (a LatencySlice) Len() int { return len(a) }
|
||||
func (a LatencySlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a LatencySlice) Less(i, j int) bool { return a[i].Latency < a[j].Latency }
|
||||
|
||||
// ExtractLatencyMetrics returns latency metrics for each percentile(50th, 90th and 99th).
|
||||
func ExtractLatencyMetrics(latencies []PodLatencyData) LatencyMetric {
|
||||
length := len(latencies)
|
||||
perc50 := latencies[int(math.Ceil(float64(length*50)/100))-1].Latency
|
||||
perc90 := latencies[int(math.Ceil(float64(length*90)/100))-1].Latency
|
||||
perc99 := latencies[int(math.Ceil(float64(length*99)/100))-1].Latency
|
||||
perc100 := latencies[length-1].Latency
|
||||
return LatencyMetric{Perc50: perc50, Perc90: perc90, Perc99: perc99, Perc100: perc100}
|
||||
}
|
||||
|
||||
// PrintLatencies outputs latencies to log with readable format.
|
||||
func PrintLatencies(latencies []PodLatencyData, header string) {
|
||||
metrics := ExtractLatencyMetrics(latencies)
|
||||
e2elog.Logf("10%% %s: %v", header, latencies[(len(latencies)*9)/10:])
|
||||
e2elog.Logf("perc50: %v, perc90: %v, perc99: %v", metrics.Perc50, metrics.Perc90, metrics.Perc99)
|
||||
}
|
4
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/metrics_grabber.go
generated
vendored
4
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/metrics_grabber.go
generated
vendored
@ -24,7 +24,7 @@ import (
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/master/ports"
|
||||
"k8s.io/kubernetes/pkg/util/system"
|
||||
"k8s.io/kubernetes/test/e2e/system"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
@ -63,7 +63,7 @@ func NewMetricsGrabber(c clientset.Interface, ec clientset.Interface, kubelets b
|
||||
klog.Warning("Can't find any Nodes in the API server to grab metrics from")
|
||||
}
|
||||
for _, node := range nodeList.Items {
|
||||
if system.IsMasterNode(node.Name) {
|
||||
if system.DeprecatedMightBeMasterNode(node.Name) {
|
||||
registeredMaster = true
|
||||
masterName = node.Name
|
||||
break
|
||||
|
29
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/pod.go
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/pod.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2019 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 (
|
||||
"time"
|
||||
)
|
||||
|
||||
// LatencyMetric is a struct for dashboard metrics.
|
||||
type LatencyMetric struct {
|
||||
Perc50 time.Duration `json:"Perc50"`
|
||||
Perc90 time.Duration `json:"Perc90"`
|
||||
Perc99 time.Duration `json:"Perc99"`
|
||||
Perc100 time.Duration `json:"Perc100"`
|
||||
}
|
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/scheduler_metrics.go
generated
vendored
10
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/scheduler_metrics.go
generated
vendored
@ -16,22 +16,24 @@ limitations under the License.
|
||||
|
||||
package metrics
|
||||
|
||||
import "k8s.io/component-base/metrics/testutil"
|
||||
|
||||
// SchedulerMetrics is metrics for scheduler
|
||||
type SchedulerMetrics Metrics
|
||||
type SchedulerMetrics testutil.Metrics
|
||||
|
||||
// Equal returns true if all metrics are the same as the arguments.
|
||||
func (m *SchedulerMetrics) Equal(o SchedulerMetrics) bool {
|
||||
return (*Metrics)(m).Equal(Metrics(o))
|
||||
return (*testutil.Metrics)(m).Equal(testutil.Metrics(o))
|
||||
}
|
||||
|
||||
func newSchedulerMetrics() SchedulerMetrics {
|
||||
result := NewMetrics()
|
||||
result := testutil.NewMetrics()
|
||||
return SchedulerMetrics(result)
|
||||
}
|
||||
|
||||
func parseSchedulerMetrics(data string) (SchedulerMetrics, error) {
|
||||
result := newSchedulerMetrics()
|
||||
if err := parseMetrics(data, (*Metrics)(&result)); err != nil {
|
||||
if err := testutil.ParseMetrics(data, (*testutil.Metrics)(&result)); err != nil {
|
||||
return SchedulerMetrics{}, err
|
||||
}
|
||||
return result, nil
|
||||
|
44
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/scheduling.go
generated
vendored
Normal file
44
vendor/k8s.io/kubernetes/test/e2e/framework/metrics/scheduling.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2019 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
|
||||
|
||||
// SchedulingMetrics is a struct for managing scheduling metrics.
|
||||
type SchedulingMetrics struct {
|
||||
PredicateEvaluationLatency LatencyMetric `json:"predicateEvaluationLatency"`
|
||||
PriorityEvaluationLatency LatencyMetric `json:"priorityEvaluationLatency"`
|
||||
PreemptionEvaluationLatency LatencyMetric `json:"preemptionEvaluationLatency"`
|
||||
BindingLatency LatencyMetric `json:"bindingLatency"`
|
||||
ThroughputAverage float64 `json:"throughputAverage"`
|
||||
ThroughputPerc50 float64 `json:"throughputPerc50"`
|
||||
ThroughputPerc90 float64 `json:"throughputPerc90"`
|
||||
ThroughputPerc99 float64 `json:"throughputPerc99"`
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of scheduling metrics.
|
||||
func (l *SchedulingMetrics) SummaryKind() string {
|
||||
return "SchedulingMetrics"
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns scheduling metrics with JSON format.
|
||||
func (l *SchedulingMetrics) PrintHumanReadable() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
||||
|
||||
// PrintJSON returns scheduling metrics with JSON format.
|
||||
func (l *SchedulingMetrics) PrintJSON() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
856
vendor/k8s.io/kubernetes/test/e2e/framework/metrics_util.go
generated
vendored
856
vendor/k8s.io/kubernetes/test/e2e/framework/metrics_util.go
generated
vendored
@ -1,856 +0,0 @@
|
||||
/*
|
||||
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 framework
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/master/ports"
|
||||
schedulermetric "k8s.io/kubernetes/pkg/scheduler/metrics"
|
||||
"k8s.io/kubernetes/pkg/util/system"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/framework/metrics"
|
||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
||||
|
||||
"github.com/prometheus/common/expfmt"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
// NodeStartupThreshold is a rough estimate of the time allocated for a pod to start on a node.
|
||||
NodeStartupThreshold = 4 * time.Second
|
||||
|
||||
// We are setting 1s threshold for apicalls even in small clusters to avoid flakes.
|
||||
// The problem is that if long GC is happening in small clusters (where we have e.g.
|
||||
// 1-core master machines) and tests are pretty short, it may consume significant
|
||||
// portion of CPU and basically stop all the real work.
|
||||
// Increasing threshold to 1s is within our SLO and should solve this problem.
|
||||
apiCallLatencyThreshold time.Duration = 1 * time.Second
|
||||
|
||||
// We use a higher threshold for list apicalls if the cluster is big (i.e having > 500 nodes)
|
||||
// as list response sizes are bigger in general for big clusters. We also use a higher threshold
|
||||
// for list calls at cluster scope (this includes non-namespaced and all-namespaced calls).
|
||||
apiListCallLatencyThreshold time.Duration = 5 * time.Second
|
||||
apiClusterScopeListCallThreshold time.Duration = 10 * time.Second
|
||||
bigClusterNodeCountThreshold = 500
|
||||
|
||||
// Cluster Autoscaler metrics names
|
||||
caFunctionMetric = "cluster_autoscaler_function_duration_seconds_bucket"
|
||||
caFunctionMetricLabel = "function"
|
||||
)
|
||||
|
||||
// MetricsForE2E is metrics collection of components.
|
||||
type MetricsForE2E metrics.Collection
|
||||
|
||||
func (m *MetricsForE2E) filterMetrics() {
|
||||
apiServerMetrics := make(metrics.APIServerMetrics)
|
||||
for _, metric := range interestingAPIServerMetrics {
|
||||
apiServerMetrics[metric] = (*m).APIServerMetrics[metric]
|
||||
}
|
||||
controllerManagerMetrics := make(metrics.ControllerManagerMetrics)
|
||||
for _, metric := range interestingControllerManagerMetrics {
|
||||
controllerManagerMetrics[metric] = (*m).ControllerManagerMetrics[metric]
|
||||
}
|
||||
kubeletMetrics := make(map[string]metrics.KubeletMetrics)
|
||||
for kubelet, grabbed := range (*m).KubeletMetrics {
|
||||
kubeletMetrics[kubelet] = make(metrics.KubeletMetrics)
|
||||
for _, metric := range interestingKubeletMetrics {
|
||||
kubeletMetrics[kubelet][metric] = grabbed[metric]
|
||||
}
|
||||
}
|
||||
(*m).APIServerMetrics = apiServerMetrics
|
||||
(*m).ControllerManagerMetrics = controllerManagerMetrics
|
||||
(*m).KubeletMetrics = kubeletMetrics
|
||||
}
|
||||
|
||||
func printSample(sample *model.Sample) string {
|
||||
buf := make([]string, 0)
|
||||
// Id is a VERY special label. For 'normal' container it's useless, but it's necessary
|
||||
// for 'system' containers (e.g. /docker-daemon, /kubelet, etc.). We know if that's the
|
||||
// case by checking if there's a label "kubernetes_container_name" present. It's hacky
|
||||
// but it works...
|
||||
_, normalContainer := sample.Metric["kubernetes_container_name"]
|
||||
for k, v := range sample.Metric {
|
||||
if strings.HasPrefix(string(k), "__") {
|
||||
continue
|
||||
}
|
||||
|
||||
if string(k) == "id" && normalContainer {
|
||||
continue
|
||||
}
|
||||
buf = append(buf, fmt.Sprintf("%v=%v", string(k), v))
|
||||
}
|
||||
return fmt.Sprintf("[%v] = %v", strings.Join(buf, ","), sample.Value)
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns e2e metrics with JSON format.
|
||||
func (m *MetricsForE2E) PrintHumanReadable() string {
|
||||
buf := bytes.Buffer{}
|
||||
for _, interestingMetric := range interestingAPIServerMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", interestingMetric))
|
||||
for _, sample := range (*m).APIServerMetrics[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t%v\n", printSample(sample)))
|
||||
}
|
||||
}
|
||||
for _, interestingMetric := range interestingControllerManagerMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", interestingMetric))
|
||||
for _, sample := range (*m).ControllerManagerMetrics[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t%v\n", printSample(sample)))
|
||||
}
|
||||
}
|
||||
for _, interestingMetric := range interestingClusterAutoscalerMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", interestingMetric))
|
||||
for _, sample := range (*m).ClusterAutoscalerMetrics[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t%v\n", printSample(sample)))
|
||||
}
|
||||
}
|
||||
for kubelet, grabbed := range (*m).KubeletMetrics {
|
||||
buf.WriteString(fmt.Sprintf("For %v:\n", kubelet))
|
||||
for _, interestingMetric := range interestingKubeletMetrics {
|
||||
buf.WriteString(fmt.Sprintf("\tFor %v:\n", interestingMetric))
|
||||
for _, sample := range grabbed[interestingMetric] {
|
||||
buf.WriteString(fmt.Sprintf("\t\t%v\n", printSample(sample)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PrintJSON returns e2e metrics with JSON format.
|
||||
func (m *MetricsForE2E) PrintJSON() string {
|
||||
m.filterMetrics()
|
||||
return PrettyPrintJSON(m)
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of e2e metrics.
|
||||
func (m *MetricsForE2E) SummaryKind() string {
|
||||
return "MetricsForE2E"
|
||||
}
|
||||
|
||||
var schedulingLatencyMetricName = model.LabelValue(schedulermetric.SchedulerSubsystem + "_" + schedulermetric.SchedulingLatencyName)
|
||||
|
||||
var interestingAPIServerMetrics = []string{
|
||||
"apiserver_request_total",
|
||||
// TODO(krzysied): apiserver_request_latencies_summary is a deprecated metric.
|
||||
// It should be replaced with new metric.
|
||||
"apiserver_request_latencies_summary",
|
||||
"apiserver_init_events_total",
|
||||
}
|
||||
|
||||
var interestingControllerManagerMetrics = []string{
|
||||
"garbage_collector_attempt_to_delete_queue_latency",
|
||||
"garbage_collector_attempt_to_delete_work_duration",
|
||||
"garbage_collector_attempt_to_orphan_queue_latency",
|
||||
"garbage_collector_attempt_to_orphan_work_duration",
|
||||
"garbage_collector_dirty_processing_latency_microseconds",
|
||||
"garbage_collector_event_processing_latency_microseconds",
|
||||
"garbage_collector_graph_changes_queue_latency",
|
||||
"garbage_collector_graph_changes_work_duration",
|
||||
"garbage_collector_orphan_processing_latency_microseconds",
|
||||
|
||||
"namespace_queue_latency",
|
||||
"namespace_queue_latency_sum",
|
||||
"namespace_queue_latency_count",
|
||||
"namespace_retries",
|
||||
"namespace_work_duration",
|
||||
"namespace_work_duration_sum",
|
||||
"namespace_work_duration_count",
|
||||
}
|
||||
|
||||
var interestingKubeletMetrics = []string{
|
||||
"kubelet_docker_operations_errors_total",
|
||||
"kubelet_docker_operations_duration_seconds",
|
||||
"kubelet_pod_start_duration_seconds",
|
||||
"kubelet_pod_worker_duration_seconds",
|
||||
"kubelet_pod_worker_start_duration_seconds",
|
||||
}
|
||||
|
||||
var interestingClusterAutoscalerMetrics = []string{
|
||||
"function_duration_seconds",
|
||||
"errors_total",
|
||||
"evicted_pods_total",
|
||||
}
|
||||
|
||||
// LatencyMetric is a struct for dashboard metrics.
|
||||
type LatencyMetric struct {
|
||||
Perc50 time.Duration `json:"Perc50"`
|
||||
Perc90 time.Duration `json:"Perc90"`
|
||||
Perc99 time.Duration `json:"Perc99"`
|
||||
Perc100 time.Duration `json:"Perc100"`
|
||||
}
|
||||
|
||||
// PodStartupLatency is a struct for managing latency of pod startup.
|
||||
type PodStartupLatency struct {
|
||||
CreateToScheduleLatency LatencyMetric `json:"createToScheduleLatency"`
|
||||
ScheduleToRunLatency LatencyMetric `json:"scheduleToRunLatency"`
|
||||
RunToWatchLatency LatencyMetric `json:"runToWatchLatency"`
|
||||
ScheduleToWatchLatency LatencyMetric `json:"scheduleToWatchLatency"`
|
||||
E2ELatency LatencyMetric `json:"e2eLatency"`
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of pod startup latency.
|
||||
func (l *PodStartupLatency) SummaryKind() string {
|
||||
return "PodStartupLatency"
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns pod startup letency with JSON format.
|
||||
func (l *PodStartupLatency) PrintHumanReadable() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
||||
|
||||
// PrintJSON returns pod startup letency with JSON format.
|
||||
func (l *PodStartupLatency) PrintJSON() string {
|
||||
return PrettyPrintJSON(PodStartupLatencyToPerfData(l))
|
||||
}
|
||||
|
||||
// SchedulingMetrics is a struct for managing scheduling metrics.
|
||||
type SchedulingMetrics struct {
|
||||
PredicateEvaluationLatency LatencyMetric `json:"predicateEvaluationLatency"`
|
||||
PriorityEvaluationLatency LatencyMetric `json:"priorityEvaluationLatency"`
|
||||
PreemptionEvaluationLatency LatencyMetric `json:"preemptionEvaluationLatency"`
|
||||
BindingLatency LatencyMetric `json:"bindingLatency"`
|
||||
ThroughputAverage float64 `json:"throughputAverage"`
|
||||
ThroughputPerc50 float64 `json:"throughputPerc50"`
|
||||
ThroughputPerc90 float64 `json:"throughputPerc90"`
|
||||
ThroughputPerc99 float64 `json:"throughputPerc99"`
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of scheduling metrics.
|
||||
func (l *SchedulingMetrics) SummaryKind() string {
|
||||
return "SchedulingMetrics"
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns scheduling metrics with JSON format.
|
||||
func (l *SchedulingMetrics) PrintHumanReadable() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
||||
|
||||
// PrintJSON returns scheduling metrics with JSON format.
|
||||
func (l *SchedulingMetrics) PrintJSON() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
||||
|
||||
// Histogram is a struct for managing histogram.
|
||||
type Histogram struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
Buckets map[string]int `json:"buckets"`
|
||||
}
|
||||
|
||||
// HistogramVec is an array of Histogram.
|
||||
type HistogramVec []Histogram
|
||||
|
||||
func newHistogram(labels map[string]string) *Histogram {
|
||||
return &Histogram{
|
||||
Labels: labels,
|
||||
Buckets: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// EtcdMetrics is a struct for managing etcd metrics.
|
||||
type EtcdMetrics struct {
|
||||
BackendCommitDuration HistogramVec `json:"backendCommitDuration"`
|
||||
SnapshotSaveTotalDuration HistogramVec `json:"snapshotSaveTotalDuration"`
|
||||
PeerRoundTripTime HistogramVec `json:"peerRoundTripTime"`
|
||||
WalFsyncDuration HistogramVec `json:"walFsyncDuration"`
|
||||
MaxDatabaseSize float64 `json:"maxDatabaseSize"`
|
||||
}
|
||||
|
||||
func newEtcdMetrics() *EtcdMetrics {
|
||||
return &EtcdMetrics{
|
||||
BackendCommitDuration: make(HistogramVec, 0),
|
||||
SnapshotSaveTotalDuration: make(HistogramVec, 0),
|
||||
PeerRoundTripTime: make(HistogramVec, 0),
|
||||
WalFsyncDuration: make(HistogramVec, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of etcd metrics.
|
||||
func (l *EtcdMetrics) SummaryKind() string {
|
||||
return "EtcdMetrics"
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns etcd metrics with JSON format.
|
||||
func (l *EtcdMetrics) PrintHumanReadable() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
||||
|
||||
// PrintJSON returns etcd metrics with JSON format.
|
||||
func (l *EtcdMetrics) PrintJSON() string {
|
||||
return PrettyPrintJSON(l)
|
||||
}
|
||||
|
||||
// EtcdMetricsCollector is a struct for managing etcd metrics collector.
|
||||
type EtcdMetricsCollector struct {
|
||||
stopCh chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
metrics *EtcdMetrics
|
||||
}
|
||||
|
||||
// NewEtcdMetricsCollector creates a new etcd metrics collector.
|
||||
func NewEtcdMetricsCollector() *EtcdMetricsCollector {
|
||||
return &EtcdMetricsCollector{
|
||||
stopCh: make(chan struct{}),
|
||||
wg: &sync.WaitGroup{},
|
||||
metrics: newEtcdMetrics(),
|
||||
}
|
||||
}
|
||||
|
||||
func getEtcdMetrics() ([]*model.Sample, error) {
|
||||
// Etcd is only exposed on localhost level. We are using ssh method
|
||||
if TestContext.Provider == "gke" || TestContext.Provider == "eks" {
|
||||
e2elog.Logf("Not grabbing etcd metrics through master SSH: unsupported for %s", TestContext.Provider)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cmd := "curl http://localhost:2379/metrics"
|
||||
sshResult, err := e2essh.SSH(cmd, GetMasterHost()+":22", TestContext.Provider)
|
||||
if err != nil || sshResult.Code != 0 {
|
||||
return nil, fmt.Errorf("unexpected error (code: %d) in ssh connection to master: %#v", sshResult.Code, err)
|
||||
}
|
||||
data := sshResult.Stdout
|
||||
|
||||
return extractMetricSamples(data)
|
||||
}
|
||||
|
||||
func getEtcdDatabaseSize() (float64, error) {
|
||||
samples, err := getEtcdMetrics()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, sample := range samples {
|
||||
if sample.Metric[model.MetricNameLabel] == "etcd_debugging_mvcc_db_total_size_in_bytes" {
|
||||
return float64(sample.Value), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("Couldn't find etcd database size metric")
|
||||
}
|
||||
|
||||
// StartCollecting starts to collect etcd db size metric periodically
|
||||
// and updates MaxDatabaseSize accordingly.
|
||||
func (mc *EtcdMetricsCollector) StartCollecting(interval time.Duration) {
|
||||
mc.wg.Add(1)
|
||||
go func() {
|
||||
defer mc.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(interval):
|
||||
dbSize, err := getEtcdDatabaseSize()
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to collect etcd database size")
|
||||
continue
|
||||
}
|
||||
mc.metrics.MaxDatabaseSize = math.Max(mc.metrics.MaxDatabaseSize, dbSize)
|
||||
case <-mc.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopAndSummarize stops etcd metrics collector and summarizes the metrics.
|
||||
func (mc *EtcdMetricsCollector) StopAndSummarize() error {
|
||||
close(mc.stopCh)
|
||||
mc.wg.Wait()
|
||||
|
||||
// Do some one-off collection of metrics.
|
||||
samples, err := getEtcdMetrics()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sample := range samples {
|
||||
switch sample.Metric[model.MetricNameLabel] {
|
||||
case "etcd_disk_backend_commit_duration_seconds_bucket":
|
||||
convertSampleToBucket(sample, &mc.metrics.BackendCommitDuration)
|
||||
case "etcd_debugging_snap_save_total_duration_seconds_bucket":
|
||||
convertSampleToBucket(sample, &mc.metrics.SnapshotSaveTotalDuration)
|
||||
case "etcd_disk_wal_fsync_duration_seconds_bucket":
|
||||
convertSampleToBucket(sample, &mc.metrics.WalFsyncDuration)
|
||||
case "etcd_network_peer_round_trip_time_seconds_bucket":
|
||||
convertSampleToBucket(sample, &mc.metrics.PeerRoundTripTime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMetrics returns metrics of etcd metrics collector.
|
||||
func (mc *EtcdMetricsCollector) GetMetrics() *EtcdMetrics {
|
||||
return mc.metrics
|
||||
}
|
||||
|
||||
// APICall is a struct for managing API call.
|
||||
type APICall struct {
|
||||
Resource string `json:"resource"`
|
||||
Subresource string `json:"subresource"`
|
||||
Verb string `json:"verb"`
|
||||
Scope string `json:"scope"`
|
||||
Latency LatencyMetric `json:"latency"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// APIResponsiveness is a struct for managing multiple API calls.
|
||||
type APIResponsiveness struct {
|
||||
APICalls []APICall `json:"apicalls"`
|
||||
}
|
||||
|
||||
// SummaryKind returns the summary of API responsiveness.
|
||||
func (a *APIResponsiveness) SummaryKind() string {
|
||||
return "APIResponsiveness"
|
||||
}
|
||||
|
||||
// PrintHumanReadable returns metrics with JSON format.
|
||||
func (a *APIResponsiveness) PrintHumanReadable() string {
|
||||
return PrettyPrintJSON(a)
|
||||
}
|
||||
|
||||
// PrintJSON returns metrics of PerfData(50, 90 and 99th percentiles) with JSON format.
|
||||
func (a *APIResponsiveness) PrintJSON() string {
|
||||
return PrettyPrintJSON(APICallToPerfData(a))
|
||||
}
|
||||
|
||||
func (a *APIResponsiveness) Len() int { return len(a.APICalls) }
|
||||
func (a *APIResponsiveness) Swap(i, j int) {
|
||||
a.APICalls[i], a.APICalls[j] = a.APICalls[j], a.APICalls[i]
|
||||
}
|
||||
func (a *APIResponsiveness) Less(i, j int) bool {
|
||||
return a.APICalls[i].Latency.Perc99 < a.APICalls[j].Latency.Perc99
|
||||
}
|
||||
|
||||
// Set request latency for a particular quantile in the APICall metric entry (creating one if necessary).
|
||||
// 0 <= quantile <=1 (e.g. 0.95 is 95%tile, 0.5 is median)
|
||||
// Only 0.5, 0.9 and 0.99 quantiles are supported.
|
||||
func (a *APIResponsiveness) addMetricRequestLatency(resource, subresource, verb, scope string, quantile float64, latency time.Duration) {
|
||||
for i, apicall := range a.APICalls {
|
||||
if apicall.Resource == resource && apicall.Subresource == subresource && apicall.Verb == verb && apicall.Scope == scope {
|
||||
a.APICalls[i] = setQuantileAPICall(apicall, quantile, latency)
|
||||
return
|
||||
}
|
||||
}
|
||||
apicall := setQuantileAPICall(APICall{Resource: resource, Subresource: subresource, Verb: verb, Scope: scope}, quantile, latency)
|
||||
a.APICalls = append(a.APICalls, apicall)
|
||||
}
|
||||
|
||||
// 0 <= quantile <=1 (e.g. 0.95 is 95%tile, 0.5 is median)
|
||||
// Only 0.5, 0.9 and 0.99 quantiles are supported.
|
||||
func setQuantileAPICall(apicall APICall, quantile float64, latency time.Duration) APICall {
|
||||
setQuantile(&apicall.Latency, quantile, latency)
|
||||
return apicall
|
||||
}
|
||||
|
||||
// Only 0.5, 0.9 and 0.99 quantiles are supported.
|
||||
func setQuantile(metric *LatencyMetric, quantile float64, latency time.Duration) {
|
||||
switch quantile {
|
||||
case 0.5:
|
||||
metric.Perc50 = latency
|
||||
case 0.9:
|
||||
metric.Perc90 = latency
|
||||
case 0.99:
|
||||
metric.Perc99 = latency
|
||||
}
|
||||
}
|
||||
|
||||
// Add request count to the APICall metric entry (creating one if necessary).
|
||||
func (a *APIResponsiveness) addMetricRequestCount(resource, subresource, verb, scope string, count int) {
|
||||
for i, apicall := range a.APICalls {
|
||||
if apicall.Resource == resource && apicall.Subresource == subresource && apicall.Verb == verb && apicall.Scope == scope {
|
||||
a.APICalls[i].Count += count
|
||||
return
|
||||
}
|
||||
}
|
||||
apicall := APICall{Resource: resource, Subresource: subresource, Verb: verb, Count: count, Scope: scope}
|
||||
a.APICalls = append(a.APICalls, apicall)
|
||||
}
|
||||
|
||||
func readLatencyMetrics(c clientset.Interface) (*APIResponsiveness, error) {
|
||||
var a APIResponsiveness
|
||||
|
||||
body, err := getMetrics(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
samples, err := extractMetricSamples(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ignoredResources := sets.NewString("events")
|
||||
// TODO: figure out why we're getting non-capitalized proxy and fix this.
|
||||
ignoredVerbs := sets.NewString("WATCH", "WATCHLIST", "PROXY", "proxy", "CONNECT")
|
||||
|
||||
for _, sample := range samples {
|
||||
// Example line:
|
||||
// apiserver_request_latencies_summary{resource="namespaces",verb="LIST",quantile="0.99"} 908
|
||||
// apiserver_request_total{resource="pods",verb="LIST",client="kubectl",code="200",contentType="json"} 233
|
||||
if sample.Metric[model.MetricNameLabel] != "apiserver_request_latencies_summary" &&
|
||||
sample.Metric[model.MetricNameLabel] != "apiserver_request_total" {
|
||||
continue
|
||||
}
|
||||
|
||||
resource := string(sample.Metric["resource"])
|
||||
subresource := string(sample.Metric["subresource"])
|
||||
verb := string(sample.Metric["verb"])
|
||||
scope := string(sample.Metric["scope"])
|
||||
if ignoredResources.Has(resource) || ignoredVerbs.Has(verb) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch sample.Metric[model.MetricNameLabel] {
|
||||
case "apiserver_request_latencies_summary":
|
||||
latency := sample.Value
|
||||
quantile, err := strconv.ParseFloat(string(sample.Metric[model.QuantileLabel]), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.addMetricRequestLatency(resource, subresource, verb, scope, quantile, time.Duration(int64(latency))*time.Microsecond)
|
||||
case "apiserver_request_total":
|
||||
count := sample.Value
|
||||
a.addMetricRequestCount(resource, subresource, verb, scope, int(count))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return &a, err
|
||||
}
|
||||
|
||||
// HighLatencyRequests prints top five summary metrics for request types with latency and returns
|
||||
// number of such request types above threshold. We use a higher threshold for
|
||||
// list calls if nodeCount is above a given threshold (i.e. cluster is big).
|
||||
func HighLatencyRequests(c clientset.Interface, nodeCount int) (int, *APIResponsiveness, error) {
|
||||
isBigCluster := (nodeCount > bigClusterNodeCountThreshold)
|
||||
metrics, err := readLatencyMetrics(c)
|
||||
if err != nil {
|
||||
return 0, metrics, err
|
||||
}
|
||||
sort.Sort(sort.Reverse(metrics))
|
||||
badMetrics := 0
|
||||
top := 5
|
||||
for i := range metrics.APICalls {
|
||||
latency := metrics.APICalls[i].Latency.Perc99
|
||||
isListCall := (metrics.APICalls[i].Verb == "LIST")
|
||||
isClusterScopedCall := (metrics.APICalls[i].Scope == "cluster")
|
||||
isBad := false
|
||||
latencyThreshold := apiCallLatencyThreshold
|
||||
if isListCall && isBigCluster {
|
||||
latencyThreshold = apiListCallLatencyThreshold
|
||||
if isClusterScopedCall {
|
||||
latencyThreshold = apiClusterScopeListCallThreshold
|
||||
}
|
||||
}
|
||||
if latency > latencyThreshold {
|
||||
isBad = true
|
||||
badMetrics++
|
||||
}
|
||||
if top > 0 || isBad {
|
||||
top--
|
||||
prefix := ""
|
||||
if isBad {
|
||||
prefix = "WARNING "
|
||||
}
|
||||
e2elog.Logf("%vTop latency metric: %+v", prefix, metrics.APICalls[i])
|
||||
}
|
||||
}
|
||||
return badMetrics, metrics, nil
|
||||
}
|
||||
|
||||
// VerifyLatencyWithinThreshold verifies whether 50, 90 and 99th percentiles of a latency metric are
|
||||
// within the expected threshold.
|
||||
func VerifyLatencyWithinThreshold(threshold, actual LatencyMetric, metricName string) error {
|
||||
if actual.Perc50 > threshold.Perc50 {
|
||||
return fmt.Errorf("too high %v latency 50th percentile: %v", metricName, actual.Perc50)
|
||||
}
|
||||
if actual.Perc90 > threshold.Perc90 {
|
||||
return fmt.Errorf("too high %v latency 90th percentile: %v", metricName, actual.Perc90)
|
||||
}
|
||||
if actual.Perc99 > threshold.Perc99 {
|
||||
return fmt.Errorf("too high %v latency 99th percentile: %v", metricName, actual.Perc99)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetMetrics resets latency metrics in apiserver.
|
||||
func ResetMetrics(c clientset.Interface) error {
|
||||
e2elog.Logf("Resetting latency metrics in apiserver...")
|
||||
body, err := c.CoreV1().RESTClient().Delete().AbsPath("/metrics").DoRaw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(body) != "metrics reset\n" {
|
||||
return fmt.Errorf("Unexpected response: %q", string(body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieves metrics information.
|
||||
func getMetrics(c clientset.Interface) (string, error) {
|
||||
body, err := c.CoreV1().RESTClient().Get().AbsPath("/metrics").DoRaw()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
// Sends REST request to kube scheduler metrics
|
||||
func sendRestRequestToScheduler(c clientset.Interface, op string) (string, error) {
|
||||
opUpper := strings.ToUpper(op)
|
||||
if opUpper != "GET" && opUpper != "DELETE" {
|
||||
return "", fmt.Errorf("Unknown REST request")
|
||||
}
|
||||
|
||||
nodes, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
ExpectNoError(err)
|
||||
|
||||
var masterRegistered = false
|
||||
for _, node := range nodes.Items {
|
||||
if system.IsMasterNode(node.Name) {
|
||||
masterRegistered = true
|
||||
}
|
||||
}
|
||||
|
||||
var responseText string
|
||||
if masterRegistered {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), SingleCallTimeout)
|
||||
defer cancel()
|
||||
|
||||
body, err := c.CoreV1().RESTClient().Verb(opUpper).
|
||||
Context(ctx).
|
||||
Namespace(metav1.NamespaceSystem).
|
||||
Resource("pods").
|
||||
Name(fmt.Sprintf("kube-scheduler-%v:%v", TestContext.CloudConfig.MasterName, ports.InsecureSchedulerPort)).
|
||||
SubResource("proxy").
|
||||
Suffix("metrics").
|
||||
Do().Raw()
|
||||
|
||||
ExpectNoError(err)
|
||||
responseText = string(body)
|
||||
} else {
|
||||
// If master is not registered fall back to old method of using SSH.
|
||||
if TestContext.Provider == "gke" || TestContext.Provider == "eks" {
|
||||
e2elog.Logf("Not grabbing scheduler metrics through master SSH: unsupported for %s", TestContext.Provider)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
cmd := "curl -X " + opUpper + " http://localhost:10251/metrics"
|
||||
sshResult, err := e2essh.SSH(cmd, GetMasterHost()+":22", TestContext.Provider)
|
||||
if err != nil || sshResult.Code != 0 {
|
||||
return "", fmt.Errorf("unexpected error (code: %d) in ssh connection to master: %#v", sshResult.Code, err)
|
||||
}
|
||||
responseText = sshResult.Stdout
|
||||
}
|
||||
return responseText, nil
|
||||
}
|
||||
|
||||
// Retrieves scheduler latency metrics.
|
||||
func getSchedulingLatency(c clientset.Interface) (*SchedulingMetrics, error) {
|
||||
result := SchedulingMetrics{}
|
||||
data, err := sendRestRequestToScheduler(c, "GET")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
samples, err := extractMetricSamples(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, sample := range samples {
|
||||
if sample.Metric[model.MetricNameLabel] != schedulingLatencyMetricName {
|
||||
continue
|
||||
}
|
||||
|
||||
var metric *LatencyMetric
|
||||
switch sample.Metric[schedulermetric.OperationLabel] {
|
||||
case schedulermetric.PredicateEvaluation:
|
||||
metric = &result.PredicateEvaluationLatency
|
||||
case schedulermetric.PriorityEvaluation:
|
||||
metric = &result.PriorityEvaluationLatency
|
||||
case schedulermetric.PreemptionEvaluation:
|
||||
metric = &result.PreemptionEvaluationLatency
|
||||
case schedulermetric.Binding:
|
||||
metric = &result.BindingLatency
|
||||
}
|
||||
if metric == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
quantile, err := strconv.ParseFloat(string(sample.Metric[model.QuantileLabel]), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setQuantile(metric, quantile, time.Duration(int64(float64(sample.Value)*float64(time.Second))))
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// VerifySchedulerLatency verifies (currently just by logging them) the scheduling latencies.
|
||||
func VerifySchedulerLatency(c clientset.Interface) (*SchedulingMetrics, error) {
|
||||
latency, err := getSchedulingLatency(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return latency, nil
|
||||
}
|
||||
|
||||
// ResetSchedulerMetrics sends a DELETE request to kube-scheduler for resetting metrics.
|
||||
func ResetSchedulerMetrics(c clientset.Interface) error {
|
||||
responseText, err := sendRestRequestToScheduler(c, "DELETE")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unexpected response: %q", responseText)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSampleToBucket(sample *model.Sample, h *HistogramVec) {
|
||||
labels := make(map[string]string)
|
||||
for k, v := range sample.Metric {
|
||||
if k != "le" {
|
||||
labels[string(k)] = string(v)
|
||||
}
|
||||
}
|
||||
var hist *Histogram
|
||||
for i := range *h {
|
||||
if reflect.DeepEqual(labels, (*h)[i].Labels) {
|
||||
hist = &((*h)[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
if hist == nil {
|
||||
hist = newHistogram(labels)
|
||||
*h = append(*h, *hist)
|
||||
}
|
||||
hist.Buckets[string(sample.Metric["le"])] = int(sample.Value)
|
||||
}
|
||||
|
||||
// PrettyPrintJSON converts metrics to JSON format.
|
||||
func PrettyPrintJSON(metrics interface{}) string {
|
||||
output := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(output).Encode(metrics); err != nil {
|
||||
e2elog.Logf("Error building encoder: %v", err)
|
||||
return ""
|
||||
}
|
||||
formatted := &bytes.Buffer{}
|
||||
if err := json.Indent(formatted, output.Bytes(), "", " "); err != nil {
|
||||
e2elog.Logf("Error indenting: %v", err)
|
||||
return ""
|
||||
}
|
||||
return string(formatted.Bytes())
|
||||
}
|
||||
|
||||
// extractMetricSamples parses the prometheus metric samples from the input string.
|
||||
func extractMetricSamples(metricsBlob string) ([]*model.Sample, error) {
|
||||
dec := expfmt.NewDecoder(strings.NewReader(metricsBlob), expfmt.FmtText)
|
||||
decoder := expfmt.SampleDecoder{
|
||||
Dec: dec,
|
||||
Opts: &expfmt.DecodeOptions{},
|
||||
}
|
||||
|
||||
var samples []*model.Sample
|
||||
for {
|
||||
var v model.Vector
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
if err == io.EOF {
|
||||
// Expected loop termination condition.
|
||||
return samples, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
samples = append(samples, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// PodLatencyData encapsulates pod startup latency information.
|
||||
type PodLatencyData struct {
|
||||
// Name of the pod
|
||||
Name string
|
||||
// Node this pod was running on
|
||||
Node string
|
||||
// Latency information related to pod startuptime
|
||||
Latency time.Duration
|
||||
}
|
||||
|
||||
// LatencySlice is an array of PodLatencyData which encapsulates pod startup latency information.
|
||||
type LatencySlice []PodLatencyData
|
||||
|
||||
func (a LatencySlice) Len() int { return len(a) }
|
||||
func (a LatencySlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a LatencySlice) Less(i, j int) bool { return a[i].Latency < a[j].Latency }
|
||||
|
||||
// ExtractLatencyMetrics returns latency metrics for each percentile(50th, 90th and 99th).
|
||||
func ExtractLatencyMetrics(latencies []PodLatencyData) LatencyMetric {
|
||||
length := len(latencies)
|
||||
perc50 := latencies[int(math.Ceil(float64(length*50)/100))-1].Latency
|
||||
perc90 := latencies[int(math.Ceil(float64(length*90)/100))-1].Latency
|
||||
perc99 := latencies[int(math.Ceil(float64(length*99)/100))-1].Latency
|
||||
perc100 := latencies[length-1].Latency
|
||||
return LatencyMetric{Perc50: perc50, Perc90: perc90, Perc99: perc99, Perc100: perc100}
|
||||
}
|
||||
|
||||
// LogSuspiciousLatency logs metrics/docker errors from all nodes that had slow startup times
|
||||
// If latencyDataLag is nil then it will be populated from latencyData
|
||||
func LogSuspiciousLatency(latencyData []PodLatencyData, latencyDataLag []PodLatencyData, nodeCount int, c clientset.Interface) {
|
||||
if latencyDataLag == nil {
|
||||
latencyDataLag = latencyData
|
||||
}
|
||||
for _, l := range latencyData {
|
||||
if l.Latency > NodeStartupThreshold {
|
||||
HighLatencyKubeletOperations(c, 1*time.Second, l.Node, e2elog.Logf)
|
||||
}
|
||||
}
|
||||
e2elog.Logf("Approx throughput: %v pods/min",
|
||||
float64(nodeCount)/(latencyDataLag[len(latencyDataLag)-1].Latency.Minutes()))
|
||||
}
|
||||
|
||||
// PrintLatencies outputs latencies to log with readable format.
|
||||
func PrintLatencies(latencies []PodLatencyData, header string) {
|
||||
metrics := ExtractLatencyMetrics(latencies)
|
||||
e2elog.Logf("10%% %s: %v", header, latencies[(len(latencies)*9)/10:])
|
||||
e2elog.Logf("perc50: %v, perc90: %v, perc99: %v", metrics.Perc50, metrics.Perc90, metrics.Perc99)
|
||||
}
|
||||
|
||||
func (m *MetricsForE2E) computeClusterAutoscalerMetricsDelta(before metrics.Collection) {
|
||||
if beforeSamples, found := before.ClusterAutoscalerMetrics[caFunctionMetric]; found {
|
||||
if afterSamples, found := m.ClusterAutoscalerMetrics[caFunctionMetric]; found {
|
||||
beforeSamplesMap := make(map[string]*model.Sample)
|
||||
for _, bSample := range beforeSamples {
|
||||
beforeSamplesMap[makeKey(bSample.Metric[caFunctionMetricLabel], bSample.Metric["le"])] = bSample
|
||||
}
|
||||
for _, aSample := range afterSamples {
|
||||
if bSample, found := beforeSamplesMap[makeKey(aSample.Metric[caFunctionMetricLabel], aSample.Metric["le"])]; found {
|
||||
aSample.Value = aSample.Value - bSample.Value
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeKey(a, b model.LabelValue) string {
|
||||
return string(a) + "___" + string(b)
|
||||
}
|
1082
vendor/k8s.io/kubernetes/test/e2e/framework/networking_utils.go
generated
vendored
1082
vendor/k8s.io/kubernetes/test/e2e/framework/networking_utils.go
generated
vendored
File diff suppressed because it is too large
Load Diff
538
vendor/k8s.io/kubernetes/test/e2e/framework/node/resource.go
generated
vendored
Normal file
538
vendor/k8s.io/kubernetes/test/e2e/framework/node/resource.go
generated
vendored
Normal file
@ -0,0 +1,538 @@
|
||||
/*
|
||||
Copyright 2019 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 node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
nodectlr "k8s.io/kubernetes/pkg/controller/nodelifecycle"
|
||||
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
||||
schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/system"
|
||||
testutils "k8s.io/kubernetes/test/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// poll is how often to Poll pods, nodes and claims.
|
||||
poll = 2 * time.Second
|
||||
|
||||
// singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
|
||||
// transient failures from failing tests.
|
||||
// TODO: client should not apply this timeout to Watch calls. Increased from 30s until that is fixed.
|
||||
singleCallTimeout = 5 * time.Minute
|
||||
|
||||
// ssh port
|
||||
sshPort = "22"
|
||||
)
|
||||
|
||||
// PodNode is a pod-node pair indicating which node a given pod is running on
|
||||
type PodNode struct {
|
||||
// Pod represents pod name
|
||||
Pod string
|
||||
// Node represents node name
|
||||
Node string
|
||||
}
|
||||
|
||||
// FirstAddress returns the first address of the given type of each node.
|
||||
// TODO: Use return type string instead of []string
|
||||
func FirstAddress(nodelist *v1.NodeList, addrType v1.NodeAddressType) []string {
|
||||
hosts := []string{}
|
||||
for _, n := range nodelist.Items {
|
||||
for _, addr := range n.Status.Addresses {
|
||||
if addr.Type == addrType && addr.Address != "" {
|
||||
hosts = append(hosts, addr.Address)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hosts
|
||||
}
|
||||
|
||||
// TODO: better to change to a easy read name
|
||||
func isNodeConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, wantTrue, silent bool) bool {
|
||||
// Check the node readiness condition (logging all).
|
||||
for _, cond := range node.Status.Conditions {
|
||||
// Ensure that the condition type and the status matches as desired.
|
||||
if cond.Type == conditionType {
|
||||
// For NodeReady condition we need to check Taints as well
|
||||
if cond.Type == v1.NodeReady {
|
||||
hasNodeControllerTaints := false
|
||||
// For NodeReady we need to check if Taints are gone as well
|
||||
taints := node.Spec.Taints
|
||||
for _, taint := range taints {
|
||||
if taint.MatchTaint(nodectlr.UnreachableTaintTemplate) || taint.MatchTaint(nodectlr.NotReadyTaintTemplate) {
|
||||
hasNodeControllerTaints = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if wantTrue {
|
||||
if (cond.Status == v1.ConditionTrue) && !hasNodeControllerTaints {
|
||||
return true
|
||||
}
|
||||
msg := ""
|
||||
if !hasNodeControllerTaints {
|
||||
msg = fmt.Sprintf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
|
||||
conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Condition %s of node %s is %v, but Node is tainted by NodeController with %v. Failure",
|
||||
conditionType, node.Name, cond.Status == v1.ConditionTrue, taints)
|
||||
}
|
||||
if !silent {
|
||||
e2elog.Logf(msg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
// TODO: check if the Node is tainted once we enable NC notReady/unreachable taints by default
|
||||
if cond.Status != v1.ConditionTrue {
|
||||
return true
|
||||
}
|
||||
if !silent {
|
||||
e2elog.Logf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
|
||||
conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (wantTrue && (cond.Status == v1.ConditionTrue)) || (!wantTrue && (cond.Status != v1.ConditionTrue)) {
|
||||
return true
|
||||
}
|
||||
if !silent {
|
||||
e2elog.Logf("Condition %s of node %s is %v instead of %t. Reason: %v, message: %v",
|
||||
conditionType, node.Name, cond.Status == v1.ConditionTrue, wantTrue, cond.Reason, cond.Message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
if !silent {
|
||||
e2elog.Logf("Couldn't find condition %v on node %v", conditionType, node.Name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsConditionSetAsExpected returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue with detailed logging.
|
||||
func IsConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, wantTrue bool) bool {
|
||||
return isNodeConditionSetAsExpected(node, conditionType, wantTrue, false)
|
||||
}
|
||||
|
||||
// IsConditionSetAsExpectedSilent returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue.
|
||||
func IsConditionSetAsExpectedSilent(node *v1.Node, conditionType v1.NodeConditionType, wantTrue bool) bool {
|
||||
return isNodeConditionSetAsExpected(node, conditionType, wantTrue, true)
|
||||
}
|
||||
|
||||
// IsConditionUnset returns true if conditions of the given node do not have a match to the given conditionType, otherwise false.
|
||||
func IsConditionUnset(node *v1.Node, conditionType v1.NodeConditionType) bool {
|
||||
for _, cond := range node.Status.Conditions {
|
||||
if cond.Type == conditionType {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Filter filters nodes in NodeList in place, removing nodes that do not
|
||||
// satisfy the given condition
|
||||
// TODO: consider merging with pkg/client/cache.NodeLister
|
||||
func Filter(nodeList *v1.NodeList, fn func(node v1.Node) bool) {
|
||||
var l []v1.Node
|
||||
|
||||
for _, node := range nodeList.Items {
|
||||
if fn(node) {
|
||||
l = append(l, node)
|
||||
}
|
||||
}
|
||||
nodeList.Items = l
|
||||
}
|
||||
|
||||
// TotalRegistered returns number of registered Nodes excluding Master Node.
|
||||
func TotalRegistered(c clientset.Interface) (int, error) {
|
||||
nodes, err := waitListSchedulableNodes(c)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to list nodes: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
return len(nodes.Items), nil
|
||||
}
|
||||
|
||||
// TotalReady returns number of ready Nodes excluding Master Node.
|
||||
func TotalReady(c clientset.Interface) (int, error) {
|
||||
nodes, err := waitListSchedulableNodes(c)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to list nodes: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Filter out not-ready nodes.
|
||||
Filter(nodes, func(node v1.Node) bool {
|
||||
return IsConditionSetAsExpected(&node, v1.NodeReady, true)
|
||||
})
|
||||
return len(nodes.Items), nil
|
||||
}
|
||||
|
||||
// getSvcNodePort returns the node port for the given service:port.
|
||||
func getSvcNodePort(client clientset.Interface, ns, name string, svcPort int) (int, error) {
|
||||
svc, err := client.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, p := range svc.Spec.Ports {
|
||||
if p.Port == int32(svcPort) {
|
||||
if p.NodePort != 0 {
|
||||
return int(p.NodePort), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf(
|
||||
"No node port found for service %v, port %v", name, svcPort)
|
||||
}
|
||||
|
||||
// GetPortURL returns the url to a nodeport Service.
|
||||
func GetPortURL(client clientset.Interface, ns, name string, svcPort int) (string, error) {
|
||||
nodePort, err := getSvcNodePort(client, ns, name, svcPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// This list of nodes must not include the master, which is marked
|
||||
// unschedulable, since the master doesn't run kube-proxy. Without
|
||||
// kube-proxy NodePorts won't work.
|
||||
var nodes *v1.NodeList
|
||||
if wait.PollImmediate(poll, singleCallTimeout, func() (bool, error) {
|
||||
nodes, err = client.CoreV1().Nodes().List(metav1.ListOptions{FieldSelector: fields.Set{
|
||||
"spec.unschedulable": "false",
|
||||
}.AsSelector().String()})
|
||||
if err != nil {
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}) != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(nodes.Items) == 0 {
|
||||
return "", fmt.Errorf("Unable to list nodes in cluster")
|
||||
}
|
||||
for _, node := range nodes.Items {
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == v1.NodeExternalIP {
|
||||
if address.Address != "" {
|
||||
host := net.JoinHostPort(address.Address, fmt.Sprint(nodePort))
|
||||
return fmt.Sprintf("http://%s", host), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("Failed to find external address for service %v", name)
|
||||
}
|
||||
|
||||
// GetExternalIP returns node external IP concatenated with port 22 for ssh
|
||||
// e.g. 1.2.3.4:22
|
||||
func GetExternalIP(node *v1.Node) (string, error) {
|
||||
e2elog.Logf("Getting external IP address for %s", node.Name)
|
||||
host := ""
|
||||
for _, a := range node.Status.Addresses {
|
||||
if a.Type == v1.NodeExternalIP && a.Address != "" {
|
||||
host = net.JoinHostPort(a.Address, sshPort)
|
||||
break
|
||||
}
|
||||
}
|
||||
if host == "" {
|
||||
return "", fmt.Errorf("Couldn't get the external IP of host %s with addresses %v", node.Name, node.Status.Addresses)
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// GetInternalIP returns node internal IP
|
||||
func GetInternalIP(node *v1.Node) (string, error) {
|
||||
host := ""
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == v1.NodeInternalIP {
|
||||
if address.Address != "" {
|
||||
host = net.JoinHostPort(address.Address, sshPort)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if host == "" {
|
||||
return "", fmt.Errorf("Couldn't get the internal IP of host %s with addresses %v", node.Name, node.Status.Addresses)
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// GetAddresses returns a list of addresses of the given addressType for the given node
|
||||
func GetAddresses(node *v1.Node, addressType v1.NodeAddressType) (ips []string) {
|
||||
for j := range node.Status.Addresses {
|
||||
nodeAddress := &node.Status.Addresses[j]
|
||||
if nodeAddress.Type == addressType && nodeAddress.Address != "" {
|
||||
ips = append(ips, nodeAddress.Address)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CollectAddresses returns a list of addresses of the given addressType for the given list of nodes
|
||||
func CollectAddresses(nodes *v1.NodeList, addressType v1.NodeAddressType) []string {
|
||||
ips := []string{}
|
||||
for i := range nodes.Items {
|
||||
ips = append(ips, GetAddresses(&nodes.Items[i], addressType)...)
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
// PickIP picks one public node IP
|
||||
func PickIP(c clientset.Interface) (string, error) {
|
||||
publicIps, err := GetPublicIps(c)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get node public IPs error: %s", err)
|
||||
}
|
||||
if len(publicIps) == 0 {
|
||||
return "", fmt.Errorf("got unexpected number (%d) of public IPs", len(publicIps))
|
||||
}
|
||||
ip := publicIps[0]
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// GetPublicIps returns a public IP list of nodes.
|
||||
func GetPublicIps(c clientset.Interface) ([]string, error) {
|
||||
nodes, err := GetReadySchedulableNodes(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get schedulable and ready nodes error: %s", err)
|
||||
}
|
||||
ips := CollectAddresses(nodes, v1.NodeExternalIP)
|
||||
if len(ips) == 0 {
|
||||
// If ExternalIP isn't set, assume the test programs can reach the InternalIP
|
||||
ips = CollectAddresses(nodes, v1.NodeInternalIP)
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// GetReadySchedulableNodes addresses the common use case of getting nodes you can do work on.
|
||||
// 1) Needs to be schedulable.
|
||||
// 2) Needs to be ready.
|
||||
// If EITHER 1 or 2 is not true, most tests will want to ignore the node entirely.
|
||||
// If there are no nodes that are both ready and schedulable, this will return an error.
|
||||
// TODO: remove references in framework/util.go.
|
||||
func GetReadySchedulableNodes(c clientset.Interface) (nodes *v1.NodeList, err error) {
|
||||
nodes, err = checkWaitListSchedulableNodes(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing schedulable nodes error: %s", err)
|
||||
}
|
||||
Filter(nodes, func(node v1.Node) bool {
|
||||
return IsNodeSchedulable(&node) && IsNodeUntainted(&node)
|
||||
})
|
||||
if len(nodes.Items) == 0 {
|
||||
return nil, fmt.Errorf("there are currently no ready, schedulable nodes in the cluster")
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// GetBoundedReadySchedulableNodes is like GetReadySchedulableNodes except that it returns
|
||||
// at most maxNodes nodes. Use this to keep your test case from blowing up when run on a
|
||||
// large cluster.
|
||||
func GetBoundedReadySchedulableNodes(c clientset.Interface, maxNodes int) (nodes *v1.NodeList, err error) {
|
||||
nodes, err = GetReadySchedulableNodes(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes.Items) > maxNodes {
|
||||
shuffled := make([]v1.Node, maxNodes)
|
||||
perm := rand.Perm(len(nodes.Items))
|
||||
for i, j := range perm {
|
||||
if j < len(shuffled) {
|
||||
shuffled[j] = nodes.Items[i]
|
||||
}
|
||||
}
|
||||
nodes.Items = shuffled
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// GetRandomReadySchedulableNode gets a single randomly-selected node which is available for
|
||||
// running pods on. If there are no available nodes it will return an error.
|
||||
func GetRandomReadySchedulableNode(c clientset.Interface) (*v1.Node, error) {
|
||||
nodes, err := GetReadySchedulableNodes(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nodes.Items[rand.Intn(len(nodes.Items))], nil
|
||||
}
|
||||
|
||||
// GetReadyNodesIncludingTainted returns all ready nodes, even those which are tainted.
|
||||
// There are cases when we care about tainted nodes
|
||||
// E.g. in tests related to nodes with gpu we care about nodes despite
|
||||
// presence of nvidia.com/gpu=present:NoSchedule taint
|
||||
func GetReadyNodesIncludingTainted(c clientset.Interface) (nodes *v1.NodeList, err error) {
|
||||
nodes, err = checkWaitListSchedulableNodes(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing schedulable nodes error: %s", err)
|
||||
}
|
||||
Filter(nodes, func(node v1.Node) bool {
|
||||
return IsNodeSchedulable(&node)
|
||||
})
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// GetMasterAndWorkerNodes will return a list masters and schedulable worker nodes
|
||||
func GetMasterAndWorkerNodes(c clientset.Interface) (sets.String, *v1.NodeList, error) {
|
||||
nodes := &v1.NodeList{}
|
||||
masters := sets.NewString()
|
||||
all, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get nodes error: %s", err)
|
||||
}
|
||||
for _, n := range all.Items {
|
||||
if system.DeprecatedMightBeMasterNode(n.Name) {
|
||||
masters.Insert(n.Name)
|
||||
} else if IsNodeSchedulable(&n) && IsNodeUntainted(&n) {
|
||||
nodes.Items = append(nodes.Items, n)
|
||||
}
|
||||
}
|
||||
return masters, nodes, nil
|
||||
}
|
||||
|
||||
// IsNodeUntainted tests whether a fake pod can be scheduled on "node", given its current taints.
|
||||
// TODO: need to discuss wether to return bool and error type
|
||||
func IsNodeUntainted(node *v1.Node) bool {
|
||||
return isNodeUntaintedWithNonblocking(node, "")
|
||||
}
|
||||
|
||||
// isNodeUntaintedWithNonblocking tests whether a fake pod can be scheduled on "node"
|
||||
// but allows for taints in the list of non-blocking taints.
|
||||
func isNodeUntaintedWithNonblocking(node *v1.Node, nonblockingTaints string) bool {
|
||||
fakePod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fake-not-scheduled",
|
||||
Namespace: "fake-not-scheduled",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "fake-not-scheduled",
|
||||
Image: "fake-not-scheduled",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
nodeInfo := schedulernodeinfo.NewNodeInfo()
|
||||
|
||||
// Simple lookup for nonblocking taints based on comma-delimited list.
|
||||
nonblockingTaintsMap := map[string]struct{}{}
|
||||
for _, t := range strings.Split(nonblockingTaints, ",") {
|
||||
if strings.TrimSpace(t) != "" {
|
||||
nonblockingTaintsMap[strings.TrimSpace(t)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(nonblockingTaintsMap) > 0 {
|
||||
nodeCopy := node.DeepCopy()
|
||||
nodeCopy.Spec.Taints = []v1.Taint{}
|
||||
for _, v := range node.Spec.Taints {
|
||||
if _, isNonblockingTaint := nonblockingTaintsMap[v.Key]; !isNonblockingTaint {
|
||||
nodeCopy.Spec.Taints = append(nodeCopy.Spec.Taints, v)
|
||||
}
|
||||
}
|
||||
nodeInfo.SetNode(nodeCopy)
|
||||
} else {
|
||||
nodeInfo.SetNode(node)
|
||||
}
|
||||
|
||||
fit, _, err := predicates.PodToleratesNodeTaints(fakePod, nil, nodeInfo)
|
||||
if err != nil {
|
||||
e2elog.Failf("Can't test predicates for node %s: %v", node.Name, err)
|
||||
return false
|
||||
}
|
||||
return fit
|
||||
}
|
||||
|
||||
// IsNodeSchedulable returns true if:
|
||||
// 1) doesn't have "unschedulable" field set
|
||||
// 2) it also returns true from IsNodeReady
|
||||
func IsNodeSchedulable(node *v1.Node) bool {
|
||||
if node == nil {
|
||||
return false
|
||||
}
|
||||
return !node.Spec.Unschedulable && IsNodeReady(node)
|
||||
}
|
||||
|
||||
// IsNodeReady returns true if:
|
||||
// 1) it's Ready condition is set to true
|
||||
// 2) doesn't have NetworkUnavailable condition set to true
|
||||
func IsNodeReady(node *v1.Node) bool {
|
||||
nodeReady := IsConditionSetAsExpected(node, v1.NodeReady, true)
|
||||
networkReady := IsConditionUnset(node, v1.NodeNetworkUnavailable) ||
|
||||
IsConditionSetAsExpectedSilent(node, v1.NodeNetworkUnavailable, false)
|
||||
return nodeReady && networkReady
|
||||
}
|
||||
|
||||
// hasNonblockingTaint returns true if the node contains at least
|
||||
// one taint with a key matching the regexp.
|
||||
func hasNonblockingTaint(node *v1.Node, nonblockingTaints string) bool {
|
||||
if node == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Simple lookup for nonblocking taints based on comma-delimited list.
|
||||
nonblockingTaintsMap := map[string]struct{}{}
|
||||
for _, t := range strings.Split(nonblockingTaints, ",") {
|
||||
if strings.TrimSpace(t) != "" {
|
||||
nonblockingTaintsMap[strings.TrimSpace(t)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, taint := range node.Spec.Taints {
|
||||
if _, hasNonblockingTaint := nonblockingTaintsMap[taint.Key]; hasNonblockingTaint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// PodNodePairs return podNode pairs for all pods in a namespace
|
||||
func PodNodePairs(c clientset.Interface, ns string) ([]PodNode, error) {
|
||||
var result []PodNode
|
||||
|
||||
podList, err := c.CoreV1().Pods(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
result = append(result, PodNode{
|
||||
Pod: pod.Name,
|
||||
Node: pod.Spec.NodeName,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
281
vendor/k8s.io/kubernetes/test/e2e/framework/node/wait.go
generated
vendored
Normal file
281
vendor/k8s.io/kubernetes/test/e2e/framework/node/wait.go
generated
vendored
Normal file
@ -0,0 +1,281 @@
|
||||
/*
|
||||
Copyright 2019 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 node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/system"
|
||||
testutils "k8s.io/kubernetes/test/utils"
|
||||
)
|
||||
|
||||
const sleepTime = 20 * time.Second
|
||||
|
||||
var requiredPerNodePods = []*regexp.Regexp{
|
||||
regexp.MustCompile(".*kube-proxy.*"),
|
||||
regexp.MustCompile(".*fluentd-elasticsearch.*"),
|
||||
regexp.MustCompile(".*node-problem-detector.*"),
|
||||
}
|
||||
|
||||
// WaitForReadyNodes waits up to timeout for cluster to has desired size and
|
||||
// there is no not-ready nodes in it. By cluster size we mean number of Nodes
|
||||
// excluding Master Node.
|
||||
func WaitForReadyNodes(c clientset.Interface, size int, timeout time.Duration) error {
|
||||
_, err := CheckReady(c, size, timeout)
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitForTotalHealthy checks whether all registered nodes are ready and all required Pods are running on them.
|
||||
func WaitForTotalHealthy(c clientset.Interface, timeout time.Duration) error {
|
||||
e2elog.Logf("Waiting up to %v for all nodes to be ready", timeout)
|
||||
|
||||
var notReady []v1.Node
|
||||
var missingPodsPerNode map[string][]string
|
||||
err := wait.PollImmediate(poll, timeout, func() (bool, error) {
|
||||
notReady = nil
|
||||
// It should be OK to list unschedulable Nodes here.
|
||||
nodes, err := c.CoreV1().Nodes().List(metav1.ListOptions{ResourceVersion: "0"})
|
||||
if err != nil {
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for _, node := range nodes.Items {
|
||||
if !IsConditionSetAsExpected(&node, v1.NodeReady, true) {
|
||||
notReady = append(notReady, node)
|
||||
}
|
||||
}
|
||||
pods, err := c.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{ResourceVersion: "0"})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
systemPodsPerNode := make(map[string][]string)
|
||||
for _, pod := range pods.Items {
|
||||
if pod.Namespace == metav1.NamespaceSystem && pod.Status.Phase == v1.PodRunning {
|
||||
if pod.Spec.NodeName != "" {
|
||||
systemPodsPerNode[pod.Spec.NodeName] = append(systemPodsPerNode[pod.Spec.NodeName], pod.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
missingPodsPerNode = make(map[string][]string)
|
||||
for _, node := range nodes.Items {
|
||||
if !system.DeprecatedMightBeMasterNode(node.Name) {
|
||||
for _, requiredPod := range requiredPerNodePods {
|
||||
foundRequired := false
|
||||
for _, presentPod := range systemPodsPerNode[node.Name] {
|
||||
if requiredPod.MatchString(presentPod) {
|
||||
foundRequired = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundRequired {
|
||||
missingPodsPerNode[node.Name] = append(missingPodsPerNode[node.Name], requiredPod.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(notReady) == 0 && len(missingPodsPerNode) == 0, nil
|
||||
})
|
||||
|
||||
if err != nil && err != wait.ErrWaitTimeout {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(notReady) > 0 {
|
||||
return fmt.Errorf("Not ready nodes: %v", notReady)
|
||||
}
|
||||
if len(missingPodsPerNode) > 0 {
|
||||
return fmt.Errorf("Not running system Pods: %v", missingPodsPerNode)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// WaitConditionToBe returns whether node "name's" condition state matches wantTrue
|
||||
// within timeout. If wantTrue is true, it will ensure the node condition status
|
||||
// is ConditionTrue; if it's false, it ensures the node condition is in any state
|
||||
// other than ConditionTrue (e.g. not true or unknown).
|
||||
func WaitConditionToBe(c clientset.Interface, name string, conditionType v1.NodeConditionType, wantTrue bool, timeout time.Duration) bool {
|
||||
e2elog.Logf("Waiting up to %v for node %s condition %s to be %t", timeout, name, conditionType, wantTrue)
|
||||
for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) {
|
||||
node, err := c.CoreV1().Nodes().Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Couldn't get node %s", name)
|
||||
continue
|
||||
}
|
||||
|
||||
if IsConditionSetAsExpected(node, conditionType, wantTrue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
e2elog.Logf("Node %s didn't reach desired %s condition status (%t) within %v", name, conditionType, wantTrue, timeout)
|
||||
return false
|
||||
}
|
||||
|
||||
// WaitForNodeToBeNotReady returns whether node name is not ready (i.e. the
|
||||
// readiness condition is anything but ready, e.g false or unknown) within
|
||||
// timeout.
|
||||
func WaitForNodeToBeNotReady(c clientset.Interface, name string, timeout time.Duration) bool {
|
||||
return WaitConditionToBe(c, name, v1.NodeReady, false, timeout)
|
||||
}
|
||||
|
||||
// WaitForNodeToBeReady returns whether node name is ready within timeout.
|
||||
func WaitForNodeToBeReady(c clientset.Interface, name string, timeout time.Duration) bool {
|
||||
return WaitConditionToBe(c, name, v1.NodeReady, true, timeout)
|
||||
}
|
||||
|
||||
// CheckReady waits up to timeout for cluster to has desired size and
|
||||
// there is no not-ready nodes in it. By cluster size we mean number of Nodes
|
||||
// excluding Master Node.
|
||||
func CheckReady(c clientset.Interface, size int, timeout time.Duration) ([]v1.Node, error) {
|
||||
for start := time.Now(); time.Since(start) < timeout; time.Sleep(sleepTime) {
|
||||
nodes, err := waitListSchedulableNodes(c)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to list nodes: %v", err)
|
||||
continue
|
||||
}
|
||||
numNodes := len(nodes.Items)
|
||||
|
||||
// Filter out not-ready nodes.
|
||||
Filter(nodes, func(node v1.Node) bool {
|
||||
nodeReady := IsConditionSetAsExpected(&node, v1.NodeReady, true)
|
||||
networkReady := IsConditionUnset(&node, v1.NodeNetworkUnavailable) || IsConditionSetAsExpected(&node, v1.NodeNetworkUnavailable, false)
|
||||
return nodeReady && networkReady
|
||||
})
|
||||
numReady := len(nodes.Items)
|
||||
|
||||
if numNodes == size && numReady == size {
|
||||
e2elog.Logf("Cluster has reached the desired number of ready nodes %d", size)
|
||||
return nodes.Items, nil
|
||||
}
|
||||
e2elog.Logf("Waiting for ready nodes %d, current ready %d, not ready nodes %d", size, numReady, numNodes-numReady)
|
||||
}
|
||||
return nil, fmt.Errorf("timeout waiting %v for number of ready nodes to be %d", timeout, size)
|
||||
}
|
||||
|
||||
// waitListSchedulableNodes is a wrapper around listing nodes supporting retries.
|
||||
func waitListSchedulableNodes(c clientset.Interface) (*v1.NodeList, error) {
|
||||
var nodes *v1.NodeList
|
||||
var err error
|
||||
if wait.PollImmediate(poll, singleCallTimeout, func() (bool, error) {
|
||||
nodes, err = c.CoreV1().Nodes().List(metav1.ListOptions{FieldSelector: fields.Set{
|
||||
"spec.unschedulable": "false",
|
||||
}.AsSelector().String()})
|
||||
if err != nil {
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}) != nil {
|
||||
return nodes, err
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// checkWaitListSchedulableNodes is a wrapper around listing nodes supporting retries.
|
||||
func checkWaitListSchedulableNodes(c clientset.Interface) (*v1.NodeList, error) {
|
||||
nodes, err := waitListSchedulableNodes(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error: %s. Non-retryable failure or timed out while listing nodes for e2e cluster", err)
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// CheckReadyForTests returns a method usable in polling methods which will check that the nodes are
|
||||
// in a testable state based on schedulability.
|
||||
func CheckReadyForTests(c clientset.Interface, nonblockingTaints string, allowedNotReadyNodes, largeClusterThreshold int) func() (bool, error) {
|
||||
attempt := 0
|
||||
var notSchedulable []*v1.Node
|
||||
return func() (bool, error) {
|
||||
attempt++
|
||||
notSchedulable = nil
|
||||
opts := metav1.ListOptions{
|
||||
ResourceVersion: "0",
|
||||
FieldSelector: fields.Set{"spec.unschedulable": "false"}.AsSelector().String(),
|
||||
}
|
||||
nodes, err := c.CoreV1().Nodes().List(opts)
|
||||
if err != nil {
|
||||
e2elog.Logf("Unexpected error listing nodes: %v", err)
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for i := range nodes.Items {
|
||||
node := &nodes.Items[i]
|
||||
if !readyForTests(node, nonblockingTaints) {
|
||||
notSchedulable = append(notSchedulable, node)
|
||||
}
|
||||
}
|
||||
// Framework allows for <TestContext.AllowedNotReadyNodes> nodes to be non-ready,
|
||||
// to make it possible e.g. for incorrect deployment of some small percentage
|
||||
// of nodes (which we allow in cluster validation). Some nodes that are not
|
||||
// provisioned correctly at startup will never become ready (e.g. when something
|
||||
// won't install correctly), so we can't expect them to be ready at any point.
|
||||
//
|
||||
// However, we only allow non-ready nodes with some specific reasons.
|
||||
if len(notSchedulable) > 0 {
|
||||
// In large clusters, log them only every 10th pass.
|
||||
if len(nodes.Items) < largeClusterThreshold || attempt%10 == 0 {
|
||||
e2elog.Logf("Unschedulable nodes:")
|
||||
for i := range notSchedulable {
|
||||
e2elog.Logf("-> %s Ready=%t Network=%t Taints=%v NonblockingTaints:%v",
|
||||
notSchedulable[i].Name,
|
||||
IsConditionSetAsExpectedSilent(notSchedulable[i], v1.NodeReady, true),
|
||||
IsConditionSetAsExpectedSilent(notSchedulable[i], v1.NodeNetworkUnavailable, false),
|
||||
notSchedulable[i].Spec.Taints,
|
||||
nonblockingTaints,
|
||||
)
|
||||
|
||||
}
|
||||
e2elog.Logf("================================")
|
||||
}
|
||||
}
|
||||
return len(notSchedulable) <= allowedNotReadyNodes, nil
|
||||
}
|
||||
}
|
||||
|
||||
// readyForTests determines whether or not we should continue waiting for the nodes
|
||||
// to enter a testable state. By default this means it is schedulable, NodeReady, and untainted.
|
||||
// Nodes with taints nonblocking taints are permitted to have that taint and
|
||||
// also have their node.Spec.Unschedulable field ignored for the purposes of this function.
|
||||
func readyForTests(node *v1.Node, nonblockingTaints string) bool {
|
||||
if hasNonblockingTaint(node, nonblockingTaints) {
|
||||
// If the node has one of the nonblockingTaints taints; just check that it is ready
|
||||
// and don't require node.Spec.Unschedulable to be set either way.
|
||||
if !IsNodeReady(node) || !isNodeUntaintedWithNonblocking(node, nonblockingTaints) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !IsNodeSchedulable(node) || !IsNodeUntainted(node) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
142
vendor/k8s.io/kubernetes/test/e2e/framework/nodes_util.go
generated
vendored
142
vendor/k8s.io/kubernetes/test/e2e/framework/nodes_util.go
generated
vendored
@ -28,10 +28,14 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
||||
)
|
||||
|
||||
const etcdImage = "3.4.3-0"
|
||||
|
||||
// EtcdUpgrade upgrades etcd on GCE.
|
||||
func EtcdUpgrade(targetStorage, targetVersion string) error {
|
||||
switch TestContext.Provider {
|
||||
@ -61,7 +65,7 @@ func etcdUpgradeGCE(targetStorage, targetVersion string) error {
|
||||
os.Environ(),
|
||||
"TEST_ETCD_VERSION="+targetVersion,
|
||||
"STORAGE_BACKEND="+targetStorage,
|
||||
"TEST_ETCD_IMAGE=3.3.10-1")
|
||||
"TEST_ETCD_IMAGE="+etcdImage)
|
||||
|
||||
_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-l", "-M")
|
||||
return err
|
||||
@ -81,7 +85,7 @@ func masterUpgradeGCE(rawV string, enableKubeProxyDaemonSet bool) error {
|
||||
env = append(env,
|
||||
"TEST_ETCD_VERSION="+TestContext.EtcdUpgradeVersion,
|
||||
"STORAGE_BACKEND="+TestContext.EtcdUpgradeStorage,
|
||||
"TEST_ETCD_IMAGE=3.3.10-1")
|
||||
"TEST_ETCD_IMAGE="+etcdImage)
|
||||
} else {
|
||||
// In e2e tests, we skip the confirmation prompt about
|
||||
// implicit etcd upgrades to simulate the user entering "y".
|
||||
@ -110,7 +114,7 @@ func appendContainerCommandGroupIfNeeded(args []string) []string {
|
||||
}
|
||||
|
||||
func masterUpgradeGKE(v string) error {
|
||||
e2elog.Logf("Upgrading master to %q", v)
|
||||
Logf("Upgrading master to %q", v)
|
||||
args := []string{
|
||||
"container",
|
||||
"clusters",
|
||||
@ -133,7 +137,7 @@ func masterUpgradeGKE(v string) error {
|
||||
}
|
||||
|
||||
func masterUpgradeKubernetesAnywhere(v string) error {
|
||||
e2elog.Logf("Upgrading master to %q", v)
|
||||
Logf("Upgrading master to %q", v)
|
||||
|
||||
kaPath := TestContext.KubernetesAnywherePath
|
||||
originalConfigPath := filepath.Join(kaPath, ".config")
|
||||
@ -151,7 +155,7 @@ func masterUpgradeKubernetesAnywhere(v string) error {
|
||||
defer func() {
|
||||
// revert .config.bak to .config
|
||||
if err := os.Rename(backupConfigPath, originalConfigPath); err != nil {
|
||||
e2elog.Logf("Could not rename %s back to %s", backupConfigPath, originalConfigPath)
|
||||
Logf("Could not rename %s back to %s", backupConfigPath, originalConfigPath)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -202,12 +206,12 @@ func waitForNodesReadyAfterUpgrade(f *Framework) error {
|
||||
//
|
||||
// TODO(ihmccreery) We shouldn't have to wait for nodes to be ready in
|
||||
// GKE; the operation shouldn't return until they all are.
|
||||
numNodes, err := NumberOfRegisteredNodes(f.ClientSet)
|
||||
numNodes, err := e2enode.TotalRegistered(f.ClientSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't detect number of nodes")
|
||||
}
|
||||
e2elog.Logf("Waiting up to %v for all %d nodes to be ready after the upgrade", RestartNodeReadyAgainTimeout, numNodes)
|
||||
if _, err := CheckNodesReady(f.ClientSet, numNodes, RestartNodeReadyAgainTimeout); err != nil {
|
||||
Logf("Waiting up to %v for all %d nodes to be ready after the upgrade", RestartNodeReadyAgainTimeout, numNodes)
|
||||
if _, err := e2enode.CheckReady(f.ClientSet, numNodes, RestartNodeReadyAgainTimeout); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -227,66 +231,56 @@ func nodeUpgradeGCE(rawV, img string, enableKubeProxyDaemonSet bool) error {
|
||||
}
|
||||
|
||||
func nodeUpgradeGKE(v string, img string) error {
|
||||
e2elog.Logf("Upgrading nodes to version %q and image %q", v, img)
|
||||
args := []string{
|
||||
"container",
|
||||
"clusters",
|
||||
fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
|
||||
locationParamGKE(),
|
||||
"upgrade",
|
||||
TestContext.CloudConfig.Cluster,
|
||||
fmt.Sprintf("--cluster-version=%s", v),
|
||||
"--quiet",
|
||||
}
|
||||
if len(img) > 0 {
|
||||
args = append(args, fmt.Sprintf("--image-type=%s", img))
|
||||
}
|
||||
_, _, err := RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)
|
||||
|
||||
Logf("Upgrading nodes to version %q and image %q", v, img)
|
||||
nps, err := nodePoolsGKE()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Logf("Found node pools %v", nps)
|
||||
for _, np := range nps {
|
||||
args := []string{
|
||||
"container",
|
||||
"clusters",
|
||||
fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
|
||||
locationParamGKE(),
|
||||
"upgrade",
|
||||
TestContext.CloudConfig.Cluster,
|
||||
fmt.Sprintf("--node-pool=%s", np),
|
||||
fmt.Sprintf("--cluster-version=%s", v),
|
||||
"--quiet",
|
||||
}
|
||||
if len(img) > 0 {
|
||||
args = append(args, fmt.Sprintf("--image-type=%s", img))
|
||||
}
|
||||
_, _, err = RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)
|
||||
|
||||
waitForSSHTunnels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
waitForSSHTunnels()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigTemplate (GCE-only) returns the name of the MIG template that the
|
||||
// nodes of the cluster use.
|
||||
func MigTemplate() (string, error) {
|
||||
var errLast error
|
||||
var templ string
|
||||
key := "instanceTemplate"
|
||||
if wait.Poll(Poll, SingleCallTimeout, func() (bool, error) {
|
||||
// TODO(mikedanese): make this hit the compute API directly instead of
|
||||
// shelling out to gcloud.
|
||||
// An `instance-groups managed describe` call outputs what we want to stdout.
|
||||
output, _, err := retryCmd("gcloud", "compute", "instance-groups", "managed",
|
||||
fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
|
||||
"describe",
|
||||
fmt.Sprintf("--zone=%s", TestContext.CloudConfig.Zone),
|
||||
TestContext.CloudConfig.NodeInstanceGroup)
|
||||
if err != nil {
|
||||
errLast = fmt.Errorf("gcloud compute instance-groups managed describe call failed with err: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// The 'describe' call probably succeeded; parse the output and try to
|
||||
// find the line that looks like "instanceTemplate: url/to/<templ>" and
|
||||
// return <templ>.
|
||||
if val := ParseKVLines(output, key); len(val) > 0 {
|
||||
url := strings.Split(val, "/")
|
||||
templ = url[len(url)-1]
|
||||
e2elog.Logf("MIG group %s using template: %s", TestContext.CloudConfig.NodeInstanceGroup, templ)
|
||||
return true, nil
|
||||
}
|
||||
errLast = fmt.Errorf("couldn't find %s in output to get MIG template. Output: %s", key, output)
|
||||
return false, nil
|
||||
}) != nil {
|
||||
return "", fmt.Errorf("MigTemplate() failed with last error: %v", errLast)
|
||||
func nodePoolsGKE() ([]string, error) {
|
||||
args := []string{
|
||||
"container",
|
||||
"node-pools",
|
||||
fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
|
||||
locationParamGKE(),
|
||||
"list",
|
||||
fmt.Sprintf("--cluster=%s", TestContext.CloudConfig.Cluster),
|
||||
"--format=get(name)",
|
||||
}
|
||||
return templ, nil
|
||||
stdout, _, err := RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(strings.TrimSpace(stdout)) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
return strings.Fields(stdout), nil
|
||||
}
|
||||
|
||||
func gceUpgradeScript() string {
|
||||
@ -297,7 +291,7 @@ func gceUpgradeScript() string {
|
||||
}
|
||||
|
||||
func waitForSSHTunnels() {
|
||||
e2elog.Logf("Waiting for SSH tunnels to establish")
|
||||
Logf("Waiting for SSH tunnels to establish")
|
||||
RunKubectl("run", "ssh-tunnel-test",
|
||||
"--image=busybox",
|
||||
"--restart=Never",
|
||||
@ -321,6 +315,7 @@ type NodeKiller struct {
|
||||
|
||||
// NewNodeKiller creates new NodeKiller.
|
||||
func NewNodeKiller(config NodeKillerConfig, client clientset.Interface, provider string) *NodeKiller {
|
||||
config.NodeKillerStopCh = make(chan struct{})
|
||||
return &NodeKiller{config, client, provider}
|
||||
}
|
||||
|
||||
@ -335,13 +330,13 @@ func (k *NodeKiller) Run(stopCh <-chan struct{}) {
|
||||
}
|
||||
|
||||
func (k *NodeKiller) pickNodes() []v1.Node {
|
||||
nodes := GetReadySchedulableNodesOrDie(k.client)
|
||||
nodes, err := e2enode.GetReadySchedulableNodes(k.client)
|
||||
ExpectNoError(err)
|
||||
numNodes := int(k.config.FailureRatio * float64(len(nodes.Items)))
|
||||
shuffledNodes := shuffleNodes(nodes.Items)
|
||||
if len(shuffledNodes) > numNodes {
|
||||
return shuffledNodes[:numNodes]
|
||||
}
|
||||
return shuffledNodes
|
||||
|
||||
nodes, err = e2enode.GetBoundedReadySchedulableNodes(k.client, numNodes)
|
||||
ExpectNoError(err)
|
||||
return nodes.Items
|
||||
}
|
||||
|
||||
func (k *NodeKiller) kill(nodes []v1.Node) {
|
||||
@ -352,27 +347,22 @@ func (k *NodeKiller) kill(nodes []v1.Node) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
e2elog.Logf("Stopping docker and kubelet on %q to simulate failure", node.Name)
|
||||
Logf("Stopping docker and kubelet on %q to simulate failure", node.Name)
|
||||
err := e2essh.IssueSSHCommand("sudo systemctl stop docker kubelet", k.provider, &node)
|
||||
if err != nil {
|
||||
e2elog.Logf("ERROR while stopping node %q: %v", node.Name, err)
|
||||
Logf("ERROR while stopping node %q: %v", node.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(k.config.SimulatedDowntime)
|
||||
|
||||
e2elog.Logf("Rebooting %q to repair the node", node.Name)
|
||||
Logf("Rebooting %q to repair the node", node.Name)
|
||||
err = e2essh.IssueSSHCommand("sudo reboot", k.provider, &node)
|
||||
if err != nil {
|
||||
e2elog.Logf("ERROR while rebooting node %q: %v", node.Name, err)
|
||||
Logf("ERROR while rebooting node %q: %v", node.Name, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// DeleteNodeOnCloudProvider deletes the specified node.
|
||||
func DeleteNodeOnCloudProvider(node *v1.Node) error {
|
||||
return TestContext.CloudConfig.Provider.DeleteNode(node)
|
||||
}
|
||||
|
166
vendor/k8s.io/kubernetes/test/e2e/framework/perf_util.go
generated
vendored
166
vendor/k8s.io/kubernetes/test/e2e/framework/perf_util.go
generated
vendored
@ -1,166 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/perftype"
|
||||
)
|
||||
|
||||
// TODO(random-liu): Change the tests to actually use PerfData from the beginning instead of
|
||||
// translating one to the other here.
|
||||
|
||||
// currentAPICallMetricsVersion is the current apicall performance metrics version. We should
|
||||
// bump up the version each time we make incompatible change to the metrics.
|
||||
const currentAPICallMetricsVersion = "v1"
|
||||
|
||||
// APICallToPerfData transforms APIResponsiveness to PerfData.
|
||||
func APICallToPerfData(apicalls *APIResponsiveness) *perftype.PerfData {
|
||||
perfData := &perftype.PerfData{Version: currentAPICallMetricsVersion}
|
||||
for _, apicall := range apicalls.APICalls {
|
||||
item := perftype.DataItem{
|
||||
Data: map[string]float64{
|
||||
"Perc50": float64(apicall.Latency.Perc50) / 1000000, // us -> ms
|
||||
"Perc90": float64(apicall.Latency.Perc90) / 1000000,
|
||||
"Perc99": float64(apicall.Latency.Perc99) / 1000000,
|
||||
},
|
||||
Unit: "ms",
|
||||
Labels: map[string]string{
|
||||
"Verb": apicall.Verb,
|
||||
"Resource": apicall.Resource,
|
||||
"Subresource": apicall.Subresource,
|
||||
"Scope": apicall.Scope,
|
||||
"Count": fmt.Sprintf("%v", apicall.Count),
|
||||
},
|
||||
}
|
||||
perfData.DataItems = append(perfData.DataItems, item)
|
||||
}
|
||||
return perfData
|
||||
}
|
||||
|
||||
func latencyToPerfData(l LatencyMetric, name string) perftype.DataItem {
|
||||
return perftype.DataItem{
|
||||
Data: map[string]float64{
|
||||
"Perc50": float64(l.Perc50) / 1000000, // us -> ms
|
||||
"Perc90": float64(l.Perc90) / 1000000,
|
||||
"Perc99": float64(l.Perc99) / 1000000,
|
||||
"Perc100": float64(l.Perc100) / 1000000,
|
||||
},
|
||||
Unit: "ms",
|
||||
Labels: map[string]string{
|
||||
"Metric": name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PodStartupLatencyToPerfData transforms PodStartupLatency to PerfData.
|
||||
func PodStartupLatencyToPerfData(latency *PodStartupLatency) *perftype.PerfData {
|
||||
perfData := &perftype.PerfData{Version: currentAPICallMetricsVersion}
|
||||
perfData.DataItems = append(perfData.DataItems, latencyToPerfData(latency.CreateToScheduleLatency, "create_to_schedule"))
|
||||
perfData.DataItems = append(perfData.DataItems, latencyToPerfData(latency.ScheduleToRunLatency, "schedule_to_run"))
|
||||
perfData.DataItems = append(perfData.DataItems, latencyToPerfData(latency.RunToWatchLatency, "run_to_watch"))
|
||||
perfData.DataItems = append(perfData.DataItems, latencyToPerfData(latency.ScheduleToWatchLatency, "schedule_to_watch"))
|
||||
perfData.DataItems = append(perfData.DataItems, latencyToPerfData(latency.E2ELatency, "pod_startup"))
|
||||
return perfData
|
||||
}
|
||||
|
||||
// CurrentKubeletPerfMetricsVersion is the current kubelet performance metrics
|
||||
// version. This is used by mutiple perf related data structures. We should
|
||||
// bump up the version each time we make an incompatible change to the metrics.
|
||||
const CurrentKubeletPerfMetricsVersion = "v2"
|
||||
|
||||
// ResourceUsageToPerfData transforms ResourceUsagePerNode to PerfData. Notice that this function
|
||||
// only cares about memory usage, because cpu usage information will be extracted from NodesCPUSummary.
|
||||
func ResourceUsageToPerfData(usagePerNode ResourceUsagePerNode) *perftype.PerfData {
|
||||
return ResourceUsageToPerfDataWithLabels(usagePerNode, nil)
|
||||
}
|
||||
|
||||
// CPUUsageToPerfData transforms NodesCPUSummary to PerfData.
|
||||
func CPUUsageToPerfData(usagePerNode NodesCPUSummary) *perftype.PerfData {
|
||||
return CPUUsageToPerfDataWithLabels(usagePerNode, nil)
|
||||
}
|
||||
|
||||
// PrintPerfData prints the perfdata in json format with PerfResultTag prefix.
|
||||
// If an error occurs, nothing will be printed.
|
||||
func PrintPerfData(p *perftype.PerfData) {
|
||||
// Notice that we must make sure the perftype.PerfResultEnd is in a new line.
|
||||
if str := PrettyPrintJSON(p); str != "" {
|
||||
e2elog.Logf("%s %s\n%s", perftype.PerfResultTag, str, perftype.PerfResultEnd)
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceUsageToPerfDataWithLabels transforms ResourceUsagePerNode to PerfData with additional labels.
|
||||
// Notice that this function only cares about memory usage, because cpu usage information will be extracted from NodesCPUSummary.
|
||||
func ResourceUsageToPerfDataWithLabels(usagePerNode ResourceUsagePerNode, labels map[string]string) *perftype.PerfData {
|
||||
items := []perftype.DataItem{}
|
||||
for node, usages := range usagePerNode {
|
||||
for c, usage := range usages {
|
||||
item := perftype.DataItem{
|
||||
Data: map[string]float64{
|
||||
"memory": float64(usage.MemoryUsageInBytes) / (1024 * 1024),
|
||||
"workingset": float64(usage.MemoryWorkingSetInBytes) / (1024 * 1024),
|
||||
"rss": float64(usage.MemoryRSSInBytes) / (1024 * 1024),
|
||||
},
|
||||
Unit: "MB",
|
||||
Labels: map[string]string{
|
||||
"node": node,
|
||||
"container": c,
|
||||
"datatype": "resource",
|
||||
"resource": "memory",
|
||||
},
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
return &perftype.PerfData{
|
||||
Version: CurrentKubeletPerfMetricsVersion,
|
||||
DataItems: items,
|
||||
Labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// CPUUsageToPerfDataWithLabels transforms NodesCPUSummary to PerfData with additional labels.
|
||||
func CPUUsageToPerfDataWithLabels(usagePerNode NodesCPUSummary, labels map[string]string) *perftype.PerfData {
|
||||
items := []perftype.DataItem{}
|
||||
for node, usages := range usagePerNode {
|
||||
for c, usage := range usages {
|
||||
data := map[string]float64{}
|
||||
for perc, value := range usage {
|
||||
data[fmt.Sprintf("Perc%02.0f", perc*100)] = value * 1000
|
||||
}
|
||||
|
||||
item := perftype.DataItem{
|
||||
Data: data,
|
||||
Unit: "mCPU",
|
||||
Labels: map[string]string{
|
||||
"node": node,
|
||||
"container": c,
|
||||
"datatype": "resource",
|
||||
"resource": "cpu",
|
||||
},
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
return &perftype.PerfData{
|
||||
Version: CurrentKubeletPerfMetricsVersion,
|
||||
DataItems: items,
|
||||
Labels: labels,
|
||||
}
|
||||
}
|
261
vendor/k8s.io/kubernetes/test/e2e/framework/pod/create.go
generated
vendored
Normal file
261
vendor/k8s.io/kubernetes/test/e2e/framework/pod/create.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
Copyright 2019 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 pod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||
)
|
||||
|
||||
var (
|
||||
// BusyBoxImage is the image URI of BusyBox.
|
||||
BusyBoxImage = imageutils.GetE2EImage(imageutils.BusyBox)
|
||||
)
|
||||
|
||||
// CreateWaitAndDeletePod creates the test pod, wait for (hopefully) success, and then delete the pod.
|
||||
// Note: need named return value so that the err assignment in the defer sets the returned error.
|
||||
// Has been shown to be necessary using Go 1.7.
|
||||
func CreateWaitAndDeletePod(c clientset.Interface, ns string, pvc *v1.PersistentVolumeClaim, command string) (err error) {
|
||||
e2elog.Logf("Creating nfs test pod")
|
||||
pod := MakePod(ns, nil, []*v1.PersistentVolumeClaim{pvc}, true, command)
|
||||
runPod, err := c.CoreV1().Pods(ns).Create(pod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pod Create API error: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
delErr := DeletePodWithWait(c, runPod)
|
||||
if err == nil { // don't override previous err value
|
||||
err = delErr // assign to returned err, can be nil
|
||||
}
|
||||
}()
|
||||
|
||||
err = testPodSuccessOrFail(c, ns, runPod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pod %q did not exit with Success: %v", runPod.Name, err)
|
||||
}
|
||||
return // note: named return value
|
||||
}
|
||||
|
||||
// testPodSuccessOrFail tests whether the pod's exit code is zero.
|
||||
func testPodSuccessOrFail(c clientset.Interface, ns string, pod *v1.Pod) error {
|
||||
e2elog.Logf("Pod should terminate with exitcode 0 (success)")
|
||||
if err := WaitForPodSuccessInNamespace(c, pod.Name, ns); err != nil {
|
||||
return fmt.Errorf("pod %q failed to reach Success: %v", pod.Name, err)
|
||||
}
|
||||
e2elog.Logf("Pod %v succeeded ", pod.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateUnschedulablePod with given claims based on node selector
|
||||
func CreateUnschedulablePod(client clientset.Interface, namespace string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim, isPrivileged bool, command string) (*v1.Pod, error) {
|
||||
pod := MakePod(namespace, nodeSelector, pvclaims, isPrivileged, command)
|
||||
pod, err := client.CoreV1().Pods(namespace).Create(pod)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pod Create API error: %v", err)
|
||||
}
|
||||
// Waiting for pod to become Unschedulable
|
||||
err = WaitForPodNameUnschedulableInNamespace(client, pod.Name, namespace)
|
||||
if err != nil {
|
||||
return pod, fmt.Errorf("pod %q is not Unschedulable: %v", pod.Name, err)
|
||||
}
|
||||
// get fresh pod info
|
||||
pod, err = client.CoreV1().Pods(namespace).Get(pod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return pod, fmt.Errorf("pod Get API error: %v", err)
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// CreateClientPod defines and creates a pod with a mounted PV. Pod runs infinite loop until killed.
|
||||
func CreateClientPod(c clientset.Interface, ns string, pvc *v1.PersistentVolumeClaim) (*v1.Pod, error) {
|
||||
return CreatePod(c, ns, nil, []*v1.PersistentVolumeClaim{pvc}, true, "")
|
||||
}
|
||||
|
||||
// CreatePod with given claims based on node selector
|
||||
func CreatePod(client clientset.Interface, namespace string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim, isPrivileged bool, command string) (*v1.Pod, error) {
|
||||
pod := MakePod(namespace, nodeSelector, pvclaims, isPrivileged, command)
|
||||
pod, err := client.CoreV1().Pods(namespace).Create(pod)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pod Create API error: %v", err)
|
||||
}
|
||||
// Waiting for pod to be running
|
||||
err = WaitForPodNameRunningInNamespace(client, pod.Name, namespace)
|
||||
if err != nil {
|
||||
return pod, fmt.Errorf("pod %q is not Running: %v", pod.Name, err)
|
||||
}
|
||||
// get fresh pod info
|
||||
pod, err = client.CoreV1().Pods(namespace).Get(pod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return pod, fmt.Errorf("pod Get API error: %v", err)
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// CreateSecPod creates security pod with given claims
|
||||
func CreateSecPod(client clientset.Interface, namespace string, pvclaims []*v1.PersistentVolumeClaim, inlineVolumeSources []*v1.VolumeSource, isPrivileged bool, command string, hostIPC bool, hostPID bool, seLinuxLabel *v1.SELinuxOptions, fsGroup *int64, timeout time.Duration) (*v1.Pod, error) {
|
||||
return CreateSecPodWithNodeSelection(client, namespace, pvclaims, inlineVolumeSources, isPrivileged, command, hostIPC, hostPID, seLinuxLabel, fsGroup, NodeSelection{}, timeout)
|
||||
}
|
||||
|
||||
// CreateSecPodWithNodeSelection creates security pod with given claims
|
||||
func CreateSecPodWithNodeSelection(client clientset.Interface, namespace string, pvclaims []*v1.PersistentVolumeClaim, inlineVolumeSources []*v1.VolumeSource, isPrivileged bool, command string, hostIPC bool, hostPID bool, seLinuxLabel *v1.SELinuxOptions, fsGroup *int64, node NodeSelection, timeout time.Duration) (*v1.Pod, error) {
|
||||
pod := MakeSecPod(namespace, pvclaims, inlineVolumeSources, isPrivileged, command, hostIPC, hostPID, seLinuxLabel, fsGroup)
|
||||
// Setting node
|
||||
pod.Spec.NodeName = node.Name
|
||||
pod.Spec.NodeSelector = node.Selector
|
||||
pod.Spec.Affinity = node.Affinity
|
||||
|
||||
pod, err := client.CoreV1().Pods(namespace).Create(pod)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pod Create API error: %v", err)
|
||||
}
|
||||
|
||||
// Waiting for pod to be running
|
||||
err = WaitTimeoutForPodRunningInNamespace(client, pod.Name, namespace, timeout)
|
||||
if err != nil {
|
||||
return pod, fmt.Errorf("pod %q is not Running: %v", pod.Name, err)
|
||||
}
|
||||
// get fresh pod info
|
||||
pod, err = client.CoreV1().Pods(namespace).Get(pod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return pod, fmt.Errorf("pod Get API error: %v", err)
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// MakePod returns a pod definition based on the namespace. The pod references the PVC's
|
||||
// name. A slice of BASH commands can be supplied as args to be run by the pod
|
||||
func MakePod(ns string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim, isPrivileged bool, command string) *v1.Pod {
|
||||
if len(command) == 0 {
|
||||
command = "trap exit TERM; while true; do sleep 1; done"
|
||||
}
|
||||
podSpec := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "pvc-tester-",
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "write-pod",
|
||||
Image: BusyBoxImage,
|
||||
Command: []string{"/bin/sh"},
|
||||
Args: []string{"-c", command},
|
||||
SecurityContext: &v1.SecurityContext{
|
||||
Privileged: &isPrivileged,
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
}
|
||||
var volumeMounts = make([]v1.VolumeMount, len(pvclaims))
|
||||
var volumes = make([]v1.Volume, len(pvclaims))
|
||||
for index, pvclaim := range pvclaims {
|
||||
volumename := fmt.Sprintf("volume%v", index+1)
|
||||
volumeMounts[index] = v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename}
|
||||
volumes[index] = v1.Volume{Name: volumename, VolumeSource: v1.VolumeSource{PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: pvclaim.Name, ReadOnly: false}}}
|
||||
}
|
||||
podSpec.Spec.Containers[0].VolumeMounts = volumeMounts
|
||||
podSpec.Spec.Volumes = volumes
|
||||
if nodeSelector != nil {
|
||||
podSpec.Spec.NodeSelector = nodeSelector
|
||||
}
|
||||
return podSpec
|
||||
}
|
||||
|
||||
// MakeSecPod returns a pod definition based on the namespace. The pod references the PVC's
|
||||
// name. A slice of BASH commands can be supplied as args to be run by the pod.
|
||||
// SELinux testing requires to pass HostIPC and HostPID as booleansi arguments.
|
||||
func MakeSecPod(ns string, pvclaims []*v1.PersistentVolumeClaim, inlineVolumeSources []*v1.VolumeSource, isPrivileged bool, command string, hostIPC bool, hostPID bool, seLinuxLabel *v1.SELinuxOptions, fsGroup *int64) *v1.Pod {
|
||||
if len(command) == 0 {
|
||||
command = "trap exit TERM; while true; do sleep 1; done"
|
||||
}
|
||||
podName := "security-context-" + string(uuid.NewUUID())
|
||||
if fsGroup == nil {
|
||||
fsGroup = func(i int64) *int64 {
|
||||
return &i
|
||||
}(1000)
|
||||
}
|
||||
podSpec := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
HostIPC: hostIPC,
|
||||
HostPID: hostPID,
|
||||
SecurityContext: &v1.PodSecurityContext{
|
||||
FSGroup: fsGroup,
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "write-pod",
|
||||
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
||||
Command: []string{"/bin/sh"},
|
||||
Args: []string{"-c", command},
|
||||
SecurityContext: &v1.SecurityContext{
|
||||
Privileged: &isPrivileged,
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
}
|
||||
var volumeMounts = make([]v1.VolumeMount, 0)
|
||||
var volumeDevices = make([]v1.VolumeDevice, 0)
|
||||
var volumes = make([]v1.Volume, len(pvclaims)+len(inlineVolumeSources))
|
||||
volumeIndex := 0
|
||||
for _, pvclaim := range pvclaims {
|
||||
volumename := fmt.Sprintf("volume%v", volumeIndex+1)
|
||||
if pvclaim.Spec.VolumeMode != nil && *pvclaim.Spec.VolumeMode == v1.PersistentVolumeBlock {
|
||||
volumeDevices = append(volumeDevices, v1.VolumeDevice{Name: volumename, DevicePath: "/mnt/" + volumename})
|
||||
} else {
|
||||
volumeMounts = append(volumeMounts, v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename})
|
||||
}
|
||||
|
||||
volumes[volumeIndex] = v1.Volume{Name: volumename, VolumeSource: v1.VolumeSource{PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: pvclaim.Name, ReadOnly: false}}}
|
||||
volumeIndex++
|
||||
}
|
||||
for _, src := range inlineVolumeSources {
|
||||
volumename := fmt.Sprintf("volume%v", volumeIndex+1)
|
||||
// In-line volumes can be only filesystem, not block.
|
||||
volumeMounts = append(volumeMounts, v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename})
|
||||
volumes[volumeIndex] = v1.Volume{Name: volumename, VolumeSource: *src}
|
||||
volumeIndex++
|
||||
}
|
||||
|
||||
podSpec.Spec.Containers[0].VolumeMounts = volumeMounts
|
||||
podSpec.Spec.Containers[0].VolumeDevices = volumeDevices
|
||||
podSpec.Spec.Volumes = volumes
|
||||
podSpec.Spec.SecurityContext.SELinuxOptions = seLinuxLabel
|
||||
return podSpec
|
||||
}
|
69
vendor/k8s.io/kubernetes/test/e2e/framework/pod/delete.go
generated
vendored
Normal file
69
vendor/k8s.io/kubernetes/test/e2e/framework/pod/delete.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2019 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 pod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// PodDeleteTimeout is how long to wait for a pod to be deleted.
|
||||
PodDeleteTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// DeletePodOrFail deletes the pod of the specified namespace and name.
|
||||
func DeletePodOrFail(c clientset.Interface, ns, name string) {
|
||||
ginkgo.By(fmt.Sprintf("Deleting pod %s in namespace %s", name, ns))
|
||||
err := c.CoreV1().Pods(ns).Delete(name, nil)
|
||||
expectNoError(err, "failed to delete pod %s in namespace %s", name, ns)
|
||||
}
|
||||
|
||||
// DeletePodWithWait deletes the passed-in pod and waits for the pod to be terminated. Resilient to the pod
|
||||
// not existing.
|
||||
func DeletePodWithWait(c clientset.Interface, pod *v1.Pod) error {
|
||||
if pod == nil {
|
||||
return nil
|
||||
}
|
||||
return DeletePodWithWaitByName(c, pod.GetName(), pod.GetNamespace())
|
||||
}
|
||||
|
||||
// DeletePodWithWaitByName deletes the named and namespaced pod and waits for the pod to be terminated. Resilient to the pod
|
||||
// not existing.
|
||||
func DeletePodWithWaitByName(c clientset.Interface, podName, podNamespace string) error {
|
||||
e2elog.Logf("Deleting pod %q in namespace %q", podName, podNamespace)
|
||||
err := c.CoreV1().Pods(podNamespace).Delete(podName, nil)
|
||||
if err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
return nil // assume pod was already deleted
|
||||
}
|
||||
return fmt.Errorf("pod Delete API error: %v", err)
|
||||
}
|
||||
e2elog.Logf("Wait up to %v for pod %q to be fully deleted", PodDeleteTimeout, podName)
|
||||
err = WaitForPodNotFoundInNamespace(c, podName, podNamespace, PodDeleteTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pod %q was not deleted: %v", podName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
59
vendor/k8s.io/kubernetes/test/e2e/framework/pod/node_selection.go
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/test/e2e/framework/pod/node_selection.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2019 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 pod
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// NodeSelection specifies where to run a pod, using a combination of fixed node name,
|
||||
// node selector and/or affinity.
|
||||
type NodeSelection struct {
|
||||
Name string
|
||||
Selector map[string]string
|
||||
Affinity *v1.Affinity
|
||||
}
|
||||
|
||||
// setNodeAffinityRequirement sets affinity with specified operator to nodeName to nodeSelection
|
||||
func setNodeAffinityRequirement(nodeSelection *NodeSelection, operator v1.NodeSelectorOperator, nodeName string) {
|
||||
// Add node-anti-affinity.
|
||||
if nodeSelection.Affinity == nil {
|
||||
nodeSelection.Affinity = &v1.Affinity{}
|
||||
}
|
||||
if nodeSelection.Affinity.NodeAffinity == nil {
|
||||
nodeSelection.Affinity.NodeAffinity = &v1.NodeAffinity{}
|
||||
}
|
||||
if nodeSelection.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
|
||||
nodeSelection.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &v1.NodeSelector{}
|
||||
}
|
||||
nodeSelection.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = append(nodeSelection.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms,
|
||||
v1.NodeSelectorTerm{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{Key: "metadata.name", Operator: operator, Values: []string{nodeName}},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// SetAffinity sets affinity to nodeName to nodeSelection
|
||||
func SetAffinity(nodeSelection *NodeSelection, nodeName string) {
|
||||
setNodeAffinityRequirement(nodeSelection, v1.NodeSelectorOpIn, nodeName)
|
||||
}
|
||||
|
||||
// SetAntiAffinity sets anti-affinity to nodeName to nodeSelection
|
||||
func SetAntiAffinity(nodeSelection *NodeSelection, nodeName string) {
|
||||
setNodeAffinityRequirement(nodeSelection, v1.NodeSelectorOpNotIn, nodeName)
|
||||
}
|
617
vendor/k8s.io/kubernetes/test/e2e/framework/pod/resource.go
generated
vendored
Normal file
617
vendor/k8s.io/kubernetes/test/e2e/framework/pod/resource.go
generated
vendored
Normal file
@ -0,0 +1,617 @@
|
||||
/*
|
||||
Copyright 2019 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 pod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/client/conditions"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
testutils "k8s.io/kubernetes/test/utils"
|
||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||
)
|
||||
|
||||
// TODO: Move to its own subpkg.
|
||||
// expectNoError checks if "err" is set, and if so, fails assertion while logging the error.
|
||||
func expectNoError(err error, explain ...interface{}) {
|
||||
expectNoErrorWithOffset(1, err, explain...)
|
||||
}
|
||||
|
||||
// TODO: Move to its own subpkg.
|
||||
// expectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
|
||||
// (for example, for call chain f -> g -> expectNoErrorWithOffset(1, ...) error would be logged for "f").
|
||||
func expectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
|
||||
if err != nil {
|
||||
e2elog.Logf("Unexpected error occurred: %v", err)
|
||||
}
|
||||
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
|
||||
}
|
||||
|
||||
func isElementOf(podUID types.UID, pods *v1.PodList) bool {
|
||||
for _, pod := range pods.Items {
|
||||
if pod.UID == podUID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ProxyResponseChecker is a context for checking pods responses by issuing GETs to them (via the API
|
||||
// proxy) and verifying that they answer with their own pod name.
|
||||
type ProxyResponseChecker struct {
|
||||
c clientset.Interface
|
||||
ns string
|
||||
label labels.Selector
|
||||
controllerName string
|
||||
respondName bool // Whether the pod should respond with its own name.
|
||||
pods *v1.PodList
|
||||
}
|
||||
|
||||
// NewProxyResponseChecker returns a context for checking pods responses.
|
||||
func NewProxyResponseChecker(c clientset.Interface, ns string, label labels.Selector, controllerName string, respondName bool, pods *v1.PodList) ProxyResponseChecker {
|
||||
return ProxyResponseChecker{c, ns, label, controllerName, respondName, pods}
|
||||
}
|
||||
|
||||
// CheckAllResponses issues GETs to all pods in the context and verify they
|
||||
// reply with their own pod name.
|
||||
func (r ProxyResponseChecker) CheckAllResponses() (done bool, err error) {
|
||||
successes := 0
|
||||
options := metav1.ListOptions{LabelSelector: r.label.String()}
|
||||
currentPods, err := r.c.CoreV1().Pods(r.ns).List(options)
|
||||
expectNoError(err, "Failed to get list of currentPods in namespace: %s", r.ns)
|
||||
for i, pod := range r.pods.Items {
|
||||
// Check that the replica list remains unchanged, otherwise we have problems.
|
||||
if !isElementOf(pod.UID, currentPods) {
|
||||
return false, fmt.Errorf("pod with UID %s is no longer a member of the replica set. Must have been restarted for some reason. Current replica set: %v", pod.UID, currentPods)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), singleCallTimeout)
|
||||
defer cancel()
|
||||
|
||||
body, err := r.c.CoreV1().RESTClient().Get().
|
||||
Context(ctx).
|
||||
Namespace(r.ns).
|
||||
Resource("pods").
|
||||
SubResource("proxy").
|
||||
Name(string(pod.Name)).
|
||||
Do().
|
||||
Raw()
|
||||
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
// We may encounter errors here because of a race between the pod readiness and apiserver
|
||||
// proxy. So, we log the error and retry if this occurs.
|
||||
e2elog.Logf("Controller %s: Failed to Get from replica %d [%s]: %v\n pod status: %#v", r.controllerName, i+1, pod.Name, err, pod.Status)
|
||||
return false, nil
|
||||
}
|
||||
e2elog.Logf("Controller %s: Failed to GET from replica %d [%s]: %v\npod status: %#v", r.controllerName, i+1, pod.Name, err, pod.Status)
|
||||
continue
|
||||
}
|
||||
// The response checker expects the pod's name unless !respondName, in
|
||||
// which case it just checks for a non-empty response.
|
||||
got := string(body)
|
||||
what := ""
|
||||
if r.respondName {
|
||||
what = "expected"
|
||||
want := pod.Name
|
||||
if got != want {
|
||||
e2elog.Logf("Controller %s: Replica %d [%s] expected response %q but got %q",
|
||||
r.controllerName, i+1, pod.Name, want, got)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
what = "non-empty"
|
||||
if len(got) == 0 {
|
||||
e2elog.Logf("Controller %s: Replica %d [%s] expected non-empty response",
|
||||
r.controllerName, i+1, pod.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
successes++
|
||||
e2elog.Logf("Controller %s: Got %s result from replica %d [%s]: %q, %d of %d required successes so far",
|
||||
r.controllerName, what, i+1, pod.Name, got, successes, len(r.pods.Items))
|
||||
}
|
||||
if successes < len(r.pods.Items) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CountRemainingPods queries the server to count number of remaining pods, and number of pods that had a missing deletion timestamp.
|
||||
func CountRemainingPods(c clientset.Interface, namespace string) (int, int, error) {
|
||||
// check for remaining pods
|
||||
pods, err := c.CoreV1().Pods(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// nothing remains!
|
||||
if len(pods.Items) == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
// stuff remains, log about it
|
||||
LogPodStates(pods.Items)
|
||||
|
||||
// check if there were any pods with missing deletion timestamp
|
||||
numPods := len(pods.Items)
|
||||
missingTimestamp := 0
|
||||
for _, pod := range pods.Items {
|
||||
if pod.DeletionTimestamp == nil {
|
||||
missingTimestamp++
|
||||
}
|
||||
}
|
||||
return numPods, missingTimestamp, nil
|
||||
}
|
||||
|
||||
func podRunning(c clientset.Interface, podName, namespace string) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
pod, err := c.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodRunning:
|
||||
return true, nil
|
||||
case v1.PodFailed, v1.PodSucceeded:
|
||||
return false, conditions.ErrPodCompleted
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func podCompleted(c clientset.Interface, podName, namespace string) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
pod, err := c.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodFailed, v1.PodSucceeded:
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func podRunningAndReady(c clientset.Interface, podName, namespace string) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
pod, err := c.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodFailed, v1.PodSucceeded:
|
||||
return false, conditions.ErrPodCompleted
|
||||
case v1.PodRunning:
|
||||
return podutil.IsPodReady(pod), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func podNotPending(c clientset.Interface, podName, namespace string) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
pod, err := c.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodPending:
|
||||
return false, nil
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PodsCreated returns a pod list matched by the given name.
|
||||
func PodsCreated(c clientset.Interface, ns, name string, replicas int32) (*v1.PodList, error) {
|
||||
label := labels.SelectorFromSet(labels.Set(map[string]string{"name": name}))
|
||||
return PodsCreatedByLabel(c, ns, name, replicas, label)
|
||||
}
|
||||
|
||||
// PodsCreatedByLabel returns a created pod list matched by the given label.
|
||||
func PodsCreatedByLabel(c clientset.Interface, ns, name string, replicas int32, label labels.Selector) (*v1.PodList, error) {
|
||||
timeout := 2 * time.Minute
|
||||
for start := time.Now(); time.Since(start) < timeout; time.Sleep(5 * time.Second) {
|
||||
options := metav1.ListOptions{LabelSelector: label.String()}
|
||||
|
||||
// List the pods, making sure we observe all the replicas.
|
||||
pods, err := c.CoreV1().Pods(ns).List(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
created := []v1.Pod{}
|
||||
for _, pod := range pods.Items {
|
||||
if pod.DeletionTimestamp != nil {
|
||||
continue
|
||||
}
|
||||
created = append(created, pod)
|
||||
}
|
||||
e2elog.Logf("Pod name %s: Found %d pods out of %d", name, len(created), replicas)
|
||||
|
||||
if int32(len(created)) == replicas {
|
||||
pods.Items = created
|
||||
return pods, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Pod name %s: Gave up waiting %v for %d pods to come up", name, timeout, replicas)
|
||||
}
|
||||
|
||||
// VerifyPods checks if the specified pod is responding.
|
||||
func VerifyPods(c clientset.Interface, ns, name string, wantName bool, replicas int32) error {
|
||||
return podRunningMaybeResponding(c, ns, name, wantName, replicas, true)
|
||||
}
|
||||
|
||||
// VerifyPodsRunning checks if the specified pod is running.
|
||||
func VerifyPodsRunning(c clientset.Interface, ns, name string, wantName bool, replicas int32) error {
|
||||
return podRunningMaybeResponding(c, ns, name, wantName, replicas, false)
|
||||
}
|
||||
|
||||
func podRunningMaybeResponding(c clientset.Interface, ns, name string, wantName bool, replicas int32, checkResponding bool) error {
|
||||
pods, err := PodsCreated(c, ns, name, replicas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e := podsRunning(c, pods)
|
||||
if len(e) > 0 {
|
||||
return fmt.Errorf("failed to wait for pods running: %v", e)
|
||||
}
|
||||
if checkResponding {
|
||||
err = PodsResponding(c, ns, name, wantName, pods)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for pods responding: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func podsRunning(c clientset.Interface, pods *v1.PodList) []error {
|
||||
// Wait for the pods to enter the running state. Waiting loops until the pods
|
||||
// are running so non-running pods cause a timeout for this test.
|
||||
ginkgo.By("ensuring each pod is running")
|
||||
e := []error{}
|
||||
errorChan := make(chan error)
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
go func(p v1.Pod) {
|
||||
errorChan <- WaitForPodRunningInNamespace(c, &p)
|
||||
}(pod)
|
||||
}
|
||||
|
||||
for range pods.Items {
|
||||
err := <-errorChan
|
||||
if err != nil {
|
||||
e = append(e, err)
|
||||
}
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// DumpAllPodInfo logs basic info for all pods.
|
||||
func DumpAllPodInfo(c clientset.Interface) {
|
||||
pods, err := c.CoreV1().Pods("").List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("unable to fetch pod debug info: %v", err)
|
||||
}
|
||||
LogPodStates(pods.Items)
|
||||
}
|
||||
|
||||
// LogPodStates logs basic info of provided pods for debugging.
|
||||
func LogPodStates(pods []v1.Pod) {
|
||||
// Find maximum widths for pod, node, and phase strings for column printing.
|
||||
maxPodW, maxNodeW, maxPhaseW, maxGraceW := len("POD"), len("NODE"), len("PHASE"), len("GRACE")
|
||||
for i := range pods {
|
||||
pod := &pods[i]
|
||||
if len(pod.ObjectMeta.Name) > maxPodW {
|
||||
maxPodW = len(pod.ObjectMeta.Name)
|
||||
}
|
||||
if len(pod.Spec.NodeName) > maxNodeW {
|
||||
maxNodeW = len(pod.Spec.NodeName)
|
||||
}
|
||||
if len(pod.Status.Phase) > maxPhaseW {
|
||||
maxPhaseW = len(pod.Status.Phase)
|
||||
}
|
||||
}
|
||||
// Increase widths by one to separate by a single space.
|
||||
maxPodW++
|
||||
maxNodeW++
|
||||
maxPhaseW++
|
||||
maxGraceW++
|
||||
|
||||
// Log pod info. * does space padding, - makes them left-aligned.
|
||||
e2elog.Logf("%-[1]*[2]s %-[3]*[4]s %-[5]*[6]s %-[7]*[8]s %[9]s",
|
||||
maxPodW, "POD", maxNodeW, "NODE", maxPhaseW, "PHASE", maxGraceW, "GRACE", "CONDITIONS")
|
||||
for _, pod := range pods {
|
||||
grace := ""
|
||||
if pod.DeletionGracePeriodSeconds != nil {
|
||||
grace = fmt.Sprintf("%ds", *pod.DeletionGracePeriodSeconds)
|
||||
}
|
||||
e2elog.Logf("%-[1]*[2]s %-[3]*[4]s %-[5]*[6]s %-[7]*[8]s %[9]s",
|
||||
maxPodW, pod.ObjectMeta.Name, maxNodeW, pod.Spec.NodeName, maxPhaseW, pod.Status.Phase, maxGraceW, grace, pod.Status.Conditions)
|
||||
}
|
||||
e2elog.Logf("") // Final empty line helps for readability.
|
||||
}
|
||||
|
||||
// logPodTerminationMessages logs termination messages for failing pods. It's a short snippet (much smaller than full logs), but it often shows
|
||||
// why pods crashed and since it is in the API, it's fast to retrieve.
|
||||
func logPodTerminationMessages(pods []v1.Pod) {
|
||||
for _, pod := range pods {
|
||||
for _, status := range pod.Status.InitContainerStatuses {
|
||||
if status.LastTerminationState.Terminated != nil && len(status.LastTerminationState.Terminated.Message) > 0 {
|
||||
e2elog.Logf("%s[%s].initContainer[%s]=%s", pod.Name, pod.Namespace, status.Name, status.LastTerminationState.Terminated.Message)
|
||||
}
|
||||
}
|
||||
for _, status := range pod.Status.ContainerStatuses {
|
||||
if status.LastTerminationState.Terminated != nil && len(status.LastTerminationState.Terminated.Message) > 0 {
|
||||
e2elog.Logf("%s[%s].container[%s]=%s", pod.Name, pod.Namespace, status.Name, status.LastTerminationState.Terminated.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DumpAllPodInfoForNamespace logs all pod information for a given namespace.
|
||||
func DumpAllPodInfoForNamespace(c clientset.Interface, namespace string) {
|
||||
pods, err := c.CoreV1().Pods(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("unable to fetch pod debug info: %v", err)
|
||||
}
|
||||
LogPodStates(pods.Items)
|
||||
logPodTerminationMessages(pods.Items)
|
||||
}
|
||||
|
||||
// FilterNonRestartablePods filters out pods that will never get recreated if
|
||||
// deleted after termination.
|
||||
func FilterNonRestartablePods(pods []*v1.Pod) []*v1.Pod {
|
||||
var results []*v1.Pod
|
||||
for _, p := range pods {
|
||||
if isNotRestartAlwaysMirrorPod(p) {
|
||||
// Mirror pods with restart policy == Never will not get
|
||||
// recreated if they are deleted after the pods have
|
||||
// terminated. For now, we discount such pods.
|
||||
// https://github.com/kubernetes/kubernetes/issues/34003
|
||||
continue
|
||||
}
|
||||
results = append(results, p)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func isNotRestartAlwaysMirrorPod(p *v1.Pod) bool {
|
||||
if !kubetypes.IsMirrorPod(p) {
|
||||
return false
|
||||
}
|
||||
return p.Spec.RestartPolicy != v1.RestartPolicyAlways
|
||||
}
|
||||
|
||||
// NewExecPodSpec returns the pod spec of hostexec pod
|
||||
func NewExecPodSpec(ns, name string, hostNetwork bool) *v1.Pod {
|
||||
immediate := int64(0)
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "agnhost",
|
||||
Image: imageutils.GetE2EImage(imageutils.Agnhost),
|
||||
ImagePullPolicy: v1.PullIfNotPresent,
|
||||
},
|
||||
},
|
||||
HostNetwork: hostNetwork,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
TerminationGracePeriodSeconds: &immediate,
|
||||
},
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
// newExecPodSpec returns the pod spec of exec pod
|
||||
func newExecPodSpec(ns, generateName string) *v1.Pod {
|
||||
immediate := int64(0)
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: generateName,
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
TerminationGracePeriodSeconds: &immediate,
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "agnhost-pause",
|
||||
Image: imageutils.GetE2EImage(imageutils.Agnhost),
|
||||
Args: []string{"pause"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
// CreateExecPodOrFail creates a agnhost pause pod used as a vessel for kubectl exec commands.
|
||||
// Pod name is uniquely generated.
|
||||
func CreateExecPodOrFail(client clientset.Interface, ns, generateName string, tweak func(*v1.Pod)) *v1.Pod {
|
||||
e2elog.Logf("Creating new exec pod")
|
||||
pod := newExecPodSpec(ns, generateName)
|
||||
if tweak != nil {
|
||||
tweak(pod)
|
||||
}
|
||||
execPod, err := client.CoreV1().Pods(ns).Create(pod)
|
||||
expectNoError(err, "failed to create new exec pod in namespace: %s", ns)
|
||||
err = wait.PollImmediate(poll, 5*time.Minute, func() (bool, error) {
|
||||
retrievedPod, err := client.CoreV1().Pods(execPod.Namespace).Get(execPod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return retrievedPod.Status.Phase == v1.PodRunning, nil
|
||||
})
|
||||
expectNoError(err)
|
||||
return execPod
|
||||
}
|
||||
|
||||
// CheckPodsRunningReady returns whether all pods whose names are listed in
|
||||
// podNames in namespace ns are running and ready, using c and waiting at most
|
||||
// timeout.
|
||||
func CheckPodsRunningReady(c clientset.Interface, ns string, podNames []string, timeout time.Duration) bool {
|
||||
return checkPodsCondition(c, ns, podNames, timeout, testutils.PodRunningReady, "running and ready")
|
||||
}
|
||||
|
||||
// CheckPodsRunningReadyOrSucceeded returns whether all pods whose names are
|
||||
// listed in podNames in namespace ns are running and ready, or succeeded; use
|
||||
// c and waiting at most timeout.
|
||||
func CheckPodsRunningReadyOrSucceeded(c clientset.Interface, ns string, podNames []string, timeout time.Duration) bool {
|
||||
return checkPodsCondition(c, ns, podNames, timeout, testutils.PodRunningReadyOrSucceeded, "running and ready, or succeeded")
|
||||
}
|
||||
|
||||
// checkPodsCondition returns whether all pods whose names are listed in podNames
|
||||
// in namespace ns are in the condition, using c and waiting at most timeout.
|
||||
func checkPodsCondition(c clientset.Interface, ns string, podNames []string, timeout time.Duration, condition podCondition, desc string) bool {
|
||||
np := len(podNames)
|
||||
e2elog.Logf("Waiting up to %v for %d pods to be %s: %s", timeout, np, desc, podNames)
|
||||
type waitPodResult struct {
|
||||
success bool
|
||||
podName string
|
||||
}
|
||||
result := make(chan waitPodResult, len(podNames))
|
||||
for _, podName := range podNames {
|
||||
// Launch off pod readiness checkers.
|
||||
go func(name string) {
|
||||
err := WaitForPodCondition(c, ns, name, desc, timeout, condition)
|
||||
result <- waitPodResult{err == nil, name}
|
||||
}(podName)
|
||||
}
|
||||
// Wait for them all to finish.
|
||||
success := true
|
||||
for range podNames {
|
||||
res := <-result
|
||||
if !res.success {
|
||||
e2elog.Logf("Pod %[1]s failed to be %[2]s.", res.podName, desc)
|
||||
success = false
|
||||
}
|
||||
}
|
||||
e2elog.Logf("Wanted all %d pods to be %s. Result: %t. Pods: %v", np, desc, success, podNames)
|
||||
return success
|
||||
}
|
||||
|
||||
// GetPodLogs returns the logs of the specified container (namespace/pod/container).
|
||||
// TODO(random-liu): Change this to be a member function of the framework.
|
||||
func GetPodLogs(c clientset.Interface, namespace, podName, containerName string) (string, error) {
|
||||
return getPodLogsInternal(c, namespace, podName, containerName, false)
|
||||
}
|
||||
|
||||
// GetPreviousPodLogs returns the logs of the previous instance of the
|
||||
// specified container (namespace/pod/container).
|
||||
func GetPreviousPodLogs(c clientset.Interface, namespace, podName, containerName string) (string, error) {
|
||||
return getPodLogsInternal(c, namespace, podName, containerName, true)
|
||||
}
|
||||
|
||||
// utility function for gomega Eventually
|
||||
func getPodLogsInternal(c clientset.Interface, namespace, podName, containerName string, previous bool) (string, error) {
|
||||
logs, err := c.CoreV1().RESTClient().Get().
|
||||
Resource("pods").
|
||||
Namespace(namespace).
|
||||
Name(podName).SubResource("log").
|
||||
Param("container", containerName).
|
||||
Param("previous", strconv.FormatBool(previous)).
|
||||
Do().
|
||||
Raw()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err == nil && strings.Contains(string(logs), "Internal Error") {
|
||||
return "", fmt.Errorf("Fetched log contains \"Internal Error\": %q", string(logs))
|
||||
}
|
||||
return string(logs), err
|
||||
}
|
||||
|
||||
// GetPodsInNamespace returns the pods in the given namespace.
|
||||
func GetPodsInNamespace(c clientset.Interface, ns string, ignoreLabels map[string]string) ([]*v1.Pod, error) {
|
||||
pods, err := c.CoreV1().Pods(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return []*v1.Pod{}, err
|
||||
}
|
||||
ignoreSelector := labels.SelectorFromSet(ignoreLabels)
|
||||
filtered := []*v1.Pod{}
|
||||
for _, p := range pods.Items {
|
||||
if len(ignoreLabels) != 0 && ignoreSelector.Matches(labels.Set(p.Labels)) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, &p)
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// GetPodsScheduled returns a number of currently scheduled and not scheduled Pods.
|
||||
func GetPodsScheduled(masterNodes sets.String, pods *v1.PodList) (scheduledPods, notScheduledPods []v1.Pod) {
|
||||
for _, pod := range pods.Items {
|
||||
if !masterNodes.Has(pod.Spec.NodeName) {
|
||||
if pod.Spec.NodeName != "" {
|
||||
_, scheduledCondition := podutil.GetPodCondition(&pod.Status, v1.PodScheduled)
|
||||
gomega.Expect(scheduledCondition != nil).To(gomega.Equal(true))
|
||||
gomega.Expect(scheduledCondition.Status).To(gomega.Equal(v1.ConditionTrue))
|
||||
scheduledPods = append(scheduledPods, pod)
|
||||
} else {
|
||||
_, scheduledCondition := podutil.GetPodCondition(&pod.Status, v1.PodScheduled)
|
||||
gomega.Expect(scheduledCondition != nil).To(gomega.Equal(true))
|
||||
gomega.Expect(scheduledCondition.Status).To(gomega.Equal(v1.ConditionFalse))
|
||||
if scheduledCondition.Reason == "Unschedulable" {
|
||||
|
||||
notScheduledPods = append(notScheduledPods, pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PatchContainerImages replaces the specified Container Registry with a custom
|
||||
// one provided via the KUBE_TEST_REPO_LIST env variable
|
||||
func PatchContainerImages(containers []v1.Container) error {
|
||||
var err error
|
||||
for _, c := range containers {
|
||||
c.Image, err = imageutils.ReplaceRegistryInImageURL(c.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
624
vendor/k8s.io/kubernetes/test/e2e/framework/pod/wait.go
generated
vendored
Normal file
624
vendor/k8s.io/kubernetes/test/e2e/framework/pod/wait.go
generated
vendored
Normal file
@ -0,0 +1,624 @@
|
||||
/*
|
||||
Copyright 2019 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 pod
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
e2eresource "k8s.io/kubernetes/test/e2e/framework/resource"
|
||||
testutils "k8s.io/kubernetes/test/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultPodDeletionTimeout is the default timeout for deleting pod.
|
||||
defaultPodDeletionTimeout = 3 * time.Minute
|
||||
|
||||
// podListTimeout is how long to wait for the pod to be listable.
|
||||
podListTimeout = time.Minute
|
||||
|
||||
podRespondingTimeout = 15 * time.Minute
|
||||
|
||||
// How long pods have to become scheduled onto nodes
|
||||
podScheduledBeforeTimeout = podListTimeout + (20 * time.Second)
|
||||
|
||||
// podStartTimeout is how long to wait for the pod to be started.
|
||||
// Initial pod start can be delayed O(minutes) by slow docker pulls.
|
||||
// TODO: Make this 30 seconds once #4566 is resolved.
|
||||
podStartTimeout = 5 * time.Minute
|
||||
|
||||
// poll is how often to poll pods, nodes and claims.
|
||||
poll = 2 * time.Second
|
||||
pollShortTimeout = 1 * time.Minute
|
||||
pollLongTimeout = 5 * time.Minute
|
||||
|
||||
// singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
|
||||
// transient failures from failing tests.
|
||||
// TODO: client should not apply this timeout to Watch calls. Increased from 30s until that is fixed.
|
||||
singleCallTimeout = 5 * time.Minute
|
||||
|
||||
// Some pods can take much longer to get ready due to volume attach/detach latency.
|
||||
slowPodStartTimeout = 15 * time.Minute
|
||||
)
|
||||
|
||||
type podCondition func(pod *v1.Pod) (bool, error)
|
||||
|
||||
// errorBadPodsStates create error message of basic info of bad pods for debugging.
|
||||
func errorBadPodsStates(badPods []v1.Pod, desiredPods int, ns, desiredState string, timeout time.Duration) string {
|
||||
errStr := fmt.Sprintf("%d / %d pods in namespace %q are NOT in %s state in %v\n", len(badPods), desiredPods, ns, desiredState, timeout)
|
||||
// Print bad pods info only if there are fewer than 10 bad pods
|
||||
if len(badPods) > 10 {
|
||||
return errStr + "There are too many bad pods. Please check log for details."
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintln(w, "POD\tNODE\tPHASE\tGRACE\tCONDITIONS")
|
||||
for _, badPod := range badPods {
|
||||
grace := ""
|
||||
if badPod.DeletionGracePeriodSeconds != nil {
|
||||
grace = fmt.Sprintf("%ds", *badPod.DeletionGracePeriodSeconds)
|
||||
}
|
||||
podInfo := fmt.Sprintf("%s\t%s\t%s\t%s\t%+v",
|
||||
badPod.ObjectMeta.Name, badPod.Spec.NodeName, badPod.Status.Phase, grace, badPod.Status.Conditions)
|
||||
fmt.Fprintln(w, podInfo)
|
||||
}
|
||||
w.Flush()
|
||||
return errStr + buf.String()
|
||||
}
|
||||
|
||||
// WaitForPodsRunningReady waits up to timeout to ensure that all pods in
|
||||
// namespace ns are either running and ready, or failed but controlled by a
|
||||
// controller. Also, it ensures that at least minPods are running and
|
||||
// ready. It has separate behavior from other 'wait for' pods functions in
|
||||
// that it requests the list of pods on every iteration. This is useful, for
|
||||
// example, in cluster startup, because the number of pods increases while
|
||||
// waiting. All pods that are in SUCCESS state are not counted.
|
||||
//
|
||||
// If ignoreLabels is not empty, pods matching this selector are ignored.
|
||||
func WaitForPodsRunningReady(c clientset.Interface, ns string, minPods, allowedNotReadyPods int32, timeout time.Duration, ignoreLabels map[string]string) error {
|
||||
ignoreSelector := labels.SelectorFromSet(map[string]string{})
|
||||
start := time.Now()
|
||||
e2elog.Logf("Waiting up to %v for all pods (need at least %d) in namespace '%s' to be running and ready",
|
||||
timeout, minPods, ns)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var ignoreNotReady bool
|
||||
badPods := []v1.Pod{}
|
||||
desiredPods := 0
|
||||
notReady := int32(0)
|
||||
|
||||
if wait.PollImmediate(poll, timeout, func() (bool, error) {
|
||||
// We get the new list of pods, replication controllers, and
|
||||
// replica sets in every iteration because more pods come
|
||||
// online during startup and we want to ensure they are also
|
||||
// checked.
|
||||
replicas, replicaOk := int32(0), int32(0)
|
||||
|
||||
rcList, err := c.CoreV1().ReplicationControllers(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Error getting replication controllers in namespace '%s': %v", ns, err)
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for _, rc := range rcList.Items {
|
||||
replicas += *rc.Spec.Replicas
|
||||
replicaOk += rc.Status.ReadyReplicas
|
||||
}
|
||||
|
||||
rsList, err := c.AppsV1().ReplicaSets(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Error getting replication sets in namespace %q: %v", ns, err)
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for _, rs := range rsList.Items {
|
||||
replicas += *rs.Spec.Replicas
|
||||
replicaOk += rs.Status.ReadyReplicas
|
||||
}
|
||||
|
||||
podList, err := c.CoreV1().Pods(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Error getting pods in namespace '%s': %v", ns, err)
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
nOk := int32(0)
|
||||
notReady = int32(0)
|
||||
badPods = []v1.Pod{}
|
||||
desiredPods = len(podList.Items)
|
||||
for _, pod := range podList.Items {
|
||||
if len(ignoreLabels) != 0 && ignoreSelector.Matches(labels.Set(pod.Labels)) {
|
||||
continue
|
||||
}
|
||||
res, err := testutils.PodRunningReady(&pod)
|
||||
switch {
|
||||
case res && err == nil:
|
||||
nOk++
|
||||
case pod.Status.Phase == v1.PodSucceeded:
|
||||
e2elog.Logf("The status of Pod %s is Succeeded, skipping waiting", pod.ObjectMeta.Name)
|
||||
// it doesn't make sense to wait for this pod
|
||||
continue
|
||||
case pod.Status.Phase != v1.PodFailed:
|
||||
e2elog.Logf("The status of Pod %s is %s (Ready = false), waiting for it to be either Running (with Ready = true) or Failed", pod.ObjectMeta.Name, pod.Status.Phase)
|
||||
notReady++
|
||||
badPods = append(badPods, pod)
|
||||
default:
|
||||
if metav1.GetControllerOf(&pod) == nil {
|
||||
e2elog.Logf("Pod %s is Failed, but it's not controlled by a controller", pod.ObjectMeta.Name)
|
||||
badPods = append(badPods, pod)
|
||||
}
|
||||
//ignore failed pods that are controlled by some controller
|
||||
}
|
||||
}
|
||||
|
||||
e2elog.Logf("%d / %d pods in namespace '%s' are running and ready (%d seconds elapsed)",
|
||||
nOk, len(podList.Items), ns, int(time.Since(start).Seconds()))
|
||||
e2elog.Logf("expected %d pod replicas in namespace '%s', %d are Running and Ready.", replicas, ns, replicaOk)
|
||||
|
||||
if replicaOk == replicas && nOk >= minPods && len(badPods) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
ignoreNotReady = (notReady <= allowedNotReadyPods)
|
||||
LogPodStates(badPods)
|
||||
return false, nil
|
||||
}) != nil {
|
||||
if !ignoreNotReady {
|
||||
return errors.New(errorBadPodsStates(badPods, desiredPods, ns, "RUNNING and READY", timeout))
|
||||
}
|
||||
e2elog.Logf("Number of not-ready pods (%d) is below the allowed threshold (%d).", notReady, allowedNotReadyPods)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForPodCondition waits a pods to be matched to the given condition.
|
||||
func WaitForPodCondition(c clientset.Interface, ns, podName, desc string, timeout time.Duration, condition podCondition) error {
|
||||
e2elog.Logf("Waiting up to %v for pod %q in namespace %q to be %q", timeout, podName, ns, desc)
|
||||
for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) {
|
||||
pod, err := c.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
e2elog.Logf("Pod %q in namespace %q not found. Error: %v", podName, ns, err)
|
||||
return err
|
||||
}
|
||||
e2elog.Logf("Get pod %q in namespace %q failed, ignoring for %v. Error: %v", podName, ns, poll, err)
|
||||
continue
|
||||
}
|
||||
// log now so that current pod info is reported before calling `condition()`
|
||||
e2elog.Logf("Pod %q: Phase=%q, Reason=%q, readiness=%t. Elapsed: %v",
|
||||
podName, pod.Status.Phase, pod.Status.Reason, podutil.IsPodReady(pod), time.Since(start))
|
||||
if done, err := condition(pod); done {
|
||||
if err == nil {
|
||||
e2elog.Logf("Pod %q satisfied condition %q", podName, desc)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Gave up after waiting %v for pod %q to be %q", timeout, podName, desc)
|
||||
}
|
||||
|
||||
// WaitForPodTerminatedInNamespace returns an error if it takes too long for the pod to terminate,
|
||||
// if the pod Get api returns an error (IsNotFound or other), or if the pod failed (and thus did not
|
||||
// terminate) with an unexpected reason. Typically called to test that the passed-in pod is fully
|
||||
// terminated (reason==""), but may be called to detect if a pod did *not* terminate according to
|
||||
// the supplied reason.
|
||||
func WaitForPodTerminatedInNamespace(c clientset.Interface, podName, reason, namespace string) error {
|
||||
return WaitForPodCondition(c, namespace, podName, "terminated due to deadline exceeded", podStartTimeout, func(pod *v1.Pod) (bool, error) {
|
||||
// Only consider Failed pods. Successful pods will be deleted and detected in
|
||||
// waitForPodCondition's Get call returning `IsNotFound`
|
||||
if pod.Status.Phase == v1.PodFailed {
|
||||
if pod.Status.Reason == reason { // short-circuit waitForPodCondition's loop
|
||||
return true, nil
|
||||
}
|
||||
return true, fmt.Errorf("Expected pod %q in namespace %q to be terminated with reason %q, got reason: %q", podName, namespace, reason, pod.Status.Reason)
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// waitForPodSuccessInNamespaceTimeout returns nil if the pod reached state success, or an error if it reached failure or ran too long.
|
||||
func waitForPodSuccessInNamespaceTimeout(c clientset.Interface, podName string, namespace string, timeout time.Duration) error {
|
||||
return WaitForPodCondition(c, namespace, podName, "success or failure", timeout, func(pod *v1.Pod) (bool, error) {
|
||||
if pod.Spec.RestartPolicy == v1.RestartPolicyAlways {
|
||||
return true, fmt.Errorf("pod %q will never terminate with a succeeded state since its restart policy is Always", podName)
|
||||
}
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodSucceeded:
|
||||
ginkgo.By("Saw pod success")
|
||||
return true, nil
|
||||
case v1.PodFailed:
|
||||
return true, fmt.Errorf("pod %q failed with status: %+v", podName, pod.Status)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForPodNameUnschedulableInNamespace returns an error if it takes too long for the pod to become Pending
|
||||
// and have condition Status equal to Unschedulable,
|
||||
// if the pod Get api returns an error (IsNotFound or other), or if the pod failed with an unexpected reason.
|
||||
// Typically called to test that the passed-in pod is Pending and Unschedulable.
|
||||
func WaitForPodNameUnschedulableInNamespace(c clientset.Interface, podName, namespace string) error {
|
||||
return WaitForPodCondition(c, namespace, podName, "Unschedulable", podStartTimeout, func(pod *v1.Pod) (bool, error) {
|
||||
// Only consider Failed pods. Successful pods will be deleted and detected in
|
||||
// waitForPodCondition's Get call returning `IsNotFound`
|
||||
if pod.Status.Phase == v1.PodPending {
|
||||
for _, cond := range pod.Status.Conditions {
|
||||
if cond.Type == v1.PodScheduled && cond.Status == v1.ConditionFalse && cond.Reason == "Unschedulable" {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
|
||||
return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v", podName, namespace, pod.Status.Phase)
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForMatchPodsCondition finds match pods based on the input ListOptions.
|
||||
// waits and checks if all match pods are in the given podCondition
|
||||
func WaitForMatchPodsCondition(c clientset.Interface, opts metav1.ListOptions, desc string, timeout time.Duration, condition podCondition) error {
|
||||
e2elog.Logf("Waiting up to %v for matching pods' status to be %s", timeout, desc)
|
||||
for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) {
|
||||
pods, err := c.CoreV1().Pods(metav1.NamespaceAll).List(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conditionNotMatch := []string{}
|
||||
for _, pod := range pods.Items {
|
||||
done, err := condition(&pod)
|
||||
if done && err != nil {
|
||||
return fmt.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !done {
|
||||
conditionNotMatch = append(conditionNotMatch, format.Pod(&pod))
|
||||
}
|
||||
}
|
||||
if len(conditionNotMatch) <= 0 {
|
||||
return err
|
||||
}
|
||||
e2elog.Logf("%d pods are not %s: %v", len(conditionNotMatch), desc, conditionNotMatch)
|
||||
}
|
||||
return fmt.Errorf("gave up waiting for matching pods to be '%s' after %v", desc, timeout)
|
||||
}
|
||||
|
||||
// WaitForPodNameRunningInNamespace waits default amount of time (PodStartTimeout) for the specified pod to become running.
|
||||
// Returns an error if timeout occurs first, or pod goes in to failed state.
|
||||
func WaitForPodNameRunningInNamespace(c clientset.Interface, podName, namespace string) error {
|
||||
return WaitTimeoutForPodRunningInNamespace(c, podName, namespace, podStartTimeout)
|
||||
}
|
||||
|
||||
// WaitForPodRunningInNamespaceSlow waits an extended amount of time (slowPodStartTimeout) for the specified pod to become running.
|
||||
// The resourceVersion is used when Watching object changes, it tells since when we care
|
||||
// about changes to the pod. Returns an error if timeout occurs first, or pod goes in to failed state.
|
||||
func WaitForPodRunningInNamespaceSlow(c clientset.Interface, podName, namespace string) error {
|
||||
return WaitTimeoutForPodRunningInNamespace(c, podName, namespace, slowPodStartTimeout)
|
||||
}
|
||||
|
||||
// WaitTimeoutForPodRunningInNamespace waits the given timeout duration for the specified pod to become running.
|
||||
func WaitTimeoutForPodRunningInNamespace(c clientset.Interface, podName, namespace string, timeout time.Duration) error {
|
||||
return wait.PollImmediate(poll, timeout, podRunning(c, podName, namespace))
|
||||
}
|
||||
|
||||
// WaitForPodRunningInNamespace waits default amount of time (podStartTimeout) for the specified pod to become running.
|
||||
// Returns an error if timeout occurs first, or pod goes in to failed state.
|
||||
func WaitForPodRunningInNamespace(c clientset.Interface, pod *v1.Pod) error {
|
||||
if pod.Status.Phase == v1.PodRunning {
|
||||
return nil
|
||||
}
|
||||
return WaitTimeoutForPodRunningInNamespace(c, pod.Name, pod.Namespace, podStartTimeout)
|
||||
}
|
||||
|
||||
// WaitTimeoutForPodNoLongerRunningInNamespace waits the given timeout duration for the specified pod to stop.
|
||||
func WaitTimeoutForPodNoLongerRunningInNamespace(c clientset.Interface, podName, namespace string, timeout time.Duration) error {
|
||||
return wait.PollImmediate(poll, timeout, podCompleted(c, podName, namespace))
|
||||
}
|
||||
|
||||
// WaitForPodNoLongerRunningInNamespace waits default amount of time (defaultPodDeletionTimeout) for the specified pod to stop running.
|
||||
// Returns an error if timeout occurs first.
|
||||
func WaitForPodNoLongerRunningInNamespace(c clientset.Interface, podName, namespace string) error {
|
||||
return WaitTimeoutForPodNoLongerRunningInNamespace(c, podName, namespace, defaultPodDeletionTimeout)
|
||||
}
|
||||
|
||||
// WaitTimeoutForPodReadyInNamespace waits the given timeout diration for the
|
||||
// specified pod to be ready and running.
|
||||
func WaitTimeoutForPodReadyInNamespace(c clientset.Interface, podName, namespace string, timeout time.Duration) error {
|
||||
return wait.PollImmediate(poll, timeout, podRunningAndReady(c, podName, namespace))
|
||||
}
|
||||
|
||||
// WaitForPodNotPending returns an error if it took too long for the pod to go out of pending state.
|
||||
// The resourceVersion is used when Watching object changes, it tells since when we care
|
||||
// about changes to the pod.
|
||||
func WaitForPodNotPending(c clientset.Interface, ns, podName string) error {
|
||||
return wait.PollImmediate(poll, podStartTimeout, podNotPending(c, podName, ns))
|
||||
}
|
||||
|
||||
// WaitForPodSuccessInNamespace returns nil if the pod reached state success, or an error if it reached failure or until podStartupTimeout.
|
||||
func WaitForPodSuccessInNamespace(c clientset.Interface, podName string, namespace string) error {
|
||||
return waitForPodSuccessInNamespaceTimeout(c, podName, namespace, podStartTimeout)
|
||||
}
|
||||
|
||||
// WaitForPodSuccessInNamespaceSlow returns nil if the pod reached state success, or an error if it reached failure or until slowPodStartupTimeout.
|
||||
func WaitForPodSuccessInNamespaceSlow(c clientset.Interface, podName string, namespace string) error {
|
||||
return waitForPodSuccessInNamespaceTimeout(c, podName, namespace, slowPodStartTimeout)
|
||||
}
|
||||
|
||||
// WaitForPodNotFoundInNamespace returns an error if it takes too long for the pod to fully terminate.
|
||||
// Unlike `waitForPodTerminatedInNamespace`, the pod's Phase and Reason are ignored. If the pod Get
|
||||
// api returns IsNotFound then the wait stops and nil is returned. If the Get api returns an error other
|
||||
// than "not found" then that error is returned and the wait stops.
|
||||
func WaitForPodNotFoundInNamespace(c clientset.Interface, podName, ns string, timeout time.Duration) error {
|
||||
return wait.PollImmediate(poll, timeout, func() (bool, error) {
|
||||
_, err := c.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{})
|
||||
if apierrs.IsNotFound(err) {
|
||||
return true, nil // done
|
||||
}
|
||||
if err != nil {
|
||||
return true, err // stop wait with error
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForPodToDisappear waits the given timeout duration for the specified pod to disappear.
|
||||
func WaitForPodToDisappear(c clientset.Interface, ns, podName string, label labels.Selector, interval, timeout time.Duration) error {
|
||||
return wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
e2elog.Logf("Waiting for pod %s to disappear", podName)
|
||||
options := metav1.ListOptions{LabelSelector: label.String()}
|
||||
pods, err := c.CoreV1().Pods(ns).List(options)
|
||||
if err != nil {
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
found := false
|
||||
for _, pod := range pods.Items {
|
||||
if pod.Name == podName {
|
||||
e2elog.Logf("Pod %s still exists", podName)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
e2elog.Logf("Pod %s no longer exists", podName)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// PodsResponding waits for the pods to response.
|
||||
func PodsResponding(c clientset.Interface, ns, name string, wantName bool, pods *v1.PodList) error {
|
||||
ginkgo.By("trying to dial each unique pod")
|
||||
label := labels.SelectorFromSet(labels.Set(map[string]string{"name": name}))
|
||||
return wait.PollImmediate(poll, podRespondingTimeout, NewProxyResponseChecker(c, ns, label, name, wantName, pods).CheckAllResponses)
|
||||
}
|
||||
|
||||
// WaitForControlledPodsRunning waits up to 10 minutes for pods to become Running.
|
||||
func WaitForControlledPodsRunning(c clientset.Interface, ns, name string, kind schema.GroupKind) error {
|
||||
rtObject, err := e2eresource.GetRuntimeObjectForKind(c, kind, ns, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selector, err := e2eresource.GetSelectorFromRuntimeObject(rtObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replicas, err := e2eresource.GetReplicasFromRuntimeObject(rtObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = testutils.WaitForEnoughPodsWithLabelRunning(c, ns, selector, int(replicas))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while waiting for replication controller %s pods to be running: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForControlledPods waits up to podListTimeout for getting pods of the specified controller name and return them.
|
||||
func WaitForControlledPods(c clientset.Interface, ns, name string, kind schema.GroupKind) (pods *v1.PodList, err error) {
|
||||
rtObject, err := e2eresource.GetRuntimeObjectForKind(c, kind, ns, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector, err := e2eresource.GetSelectorFromRuntimeObject(rtObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return WaitForPodsWithLabel(c, ns, selector)
|
||||
}
|
||||
|
||||
// WaitForPodsWithLabelScheduled waits for all matching pods to become scheduled and at least one
|
||||
// matching pod exists. Return the list of matching pods.
|
||||
func WaitForPodsWithLabelScheduled(c clientset.Interface, ns string, label labels.Selector) (pods *v1.PodList, err error) {
|
||||
err = wait.PollImmediate(poll, podScheduledBeforeTimeout,
|
||||
func() (bool, error) {
|
||||
pods, err = WaitForPodsWithLabel(c, ns, label)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, pod := range pods.Items {
|
||||
if pod.Spec.NodeName == "" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return pods, err
|
||||
}
|
||||
|
||||
// WaitForPodsWithLabel waits up to podListTimeout for getting pods with certain label
|
||||
func WaitForPodsWithLabel(c clientset.Interface, ns string, label labels.Selector) (pods *v1.PodList, err error) {
|
||||
for t := time.Now(); time.Since(t) < podListTimeout; time.Sleep(poll) {
|
||||
options := metav1.ListOptions{LabelSelector: label.String()}
|
||||
pods, err = c.CoreV1().Pods(ns).List(options)
|
||||
if err != nil {
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(pods.Items) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if pods == nil || len(pods.Items) == 0 {
|
||||
err = fmt.Errorf("Timeout while waiting for pods with label %v", label)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WaitForPodsWithLabelRunningReady waits for exact amount of matching pods to become running and ready.
|
||||
// Return the list of matching pods.
|
||||
func WaitForPodsWithLabelRunningReady(c clientset.Interface, ns string, label labels.Selector, num int, timeout time.Duration) (pods *v1.PodList, err error) {
|
||||
var current int
|
||||
err = wait.Poll(poll, timeout,
|
||||
func() (bool, error) {
|
||||
pods, err = WaitForPodsWithLabel(c, ns, label)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to list pods: %v", err)
|
||||
if testutils.IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
current = 0
|
||||
for _, pod := range pods.Items {
|
||||
if flag, err := testutils.PodRunningReady(&pod); err == nil && flag == true {
|
||||
current++
|
||||
}
|
||||
}
|
||||
if current != num {
|
||||
e2elog.Logf("Got %v pods running and ready, expect: %v", current, num)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return pods, err
|
||||
}
|
||||
|
||||
// WaitForPodsInactive waits until there are no active pods left in the PodStore.
|
||||
// This is to make a fair comparison of deletion time between DeleteRCAndPods
|
||||
// and DeleteRCAndWaitForGC, because the RC controller decreases status.replicas
|
||||
// when the pod is inactvie.
|
||||
func WaitForPodsInactive(ps *testutils.PodStore, interval, timeout time.Duration) error {
|
||||
var activePods []*v1.Pod
|
||||
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
pods := ps.List()
|
||||
activePods = controller.FilterActivePods(pods)
|
||||
if len(activePods) != 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if err == wait.ErrWaitTimeout {
|
||||
for _, pod := range activePods {
|
||||
e2elog.Logf("ERROR: Pod %q running on %q is still active", pod.Name, pod.Spec.NodeName)
|
||||
}
|
||||
return fmt.Errorf("there are %d active pods. E.g. %q on node %q", len(activePods), activePods[0].Name, activePods[0].Spec.NodeName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitForPodsGone waits until there are no pods left in the PodStore.
|
||||
func WaitForPodsGone(ps *testutils.PodStore, interval, timeout time.Duration) error {
|
||||
var pods []*v1.Pod
|
||||
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
if pods = ps.List(); len(pods) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err == wait.ErrWaitTimeout {
|
||||
for _, pod := range pods {
|
||||
e2elog.Logf("ERROR: Pod %q still exists. Node: %q", pod.Name, pod.Spec.NodeName)
|
||||
}
|
||||
return fmt.Errorf("there are %d pods left. E.g. %q on node %q", len(pods), pods[0].Name, pods[0].Spec.NodeName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitForPodsReady waits for the pods to become ready.
|
||||
func WaitForPodsReady(c clientset.Interface, ns, name string, minReadySeconds int) error {
|
||||
label := labels.SelectorFromSet(labels.Set(map[string]string{"name": name}))
|
||||
options := metav1.ListOptions{LabelSelector: label.String()}
|
||||
return wait.Poll(poll, 5*time.Minute, func() (bool, error) {
|
||||
pods, err := c.CoreV1().Pods(ns).List(options)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
for _, pod := range pods.Items {
|
||||
if !podutil.IsPodAvailable(&pod, int32(minReadySeconds), metav1.Now()) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForNRestartablePods tries to list restarting pods using ps until it finds expect of them,
|
||||
// returning their names if it can do so before timeout.
|
||||
func WaitForNRestartablePods(ps *testutils.PodStore, expect int, timeout time.Duration) ([]string, error) {
|
||||
var pods []*v1.Pod
|
||||
var errLast error
|
||||
found := wait.Poll(poll, timeout, func() (bool, error) {
|
||||
allPods := ps.List()
|
||||
pods = FilterNonRestartablePods(allPods)
|
||||
if len(pods) != expect {
|
||||
errLast = fmt.Errorf("expected to find %d pods but found only %d", expect, len(pods))
|
||||
e2elog.Logf("Error getting pods: %v", errLast)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}) == nil
|
||||
podNames := make([]string, len(pods))
|
||||
for i, p := range pods {
|
||||
podNames[i] = p.ObjectMeta.Name
|
||||
}
|
||||
if !found {
|
||||
return podNames, fmt.Errorf("couldn't find %d pods within %v; last error: %v",
|
||||
expect, timeout, errLast)
|
||||
}
|
||||
return podNames, nil
|
||||
}
|
46
vendor/k8s.io/kubernetes/test/e2e/framework/pods.go
generated
vendored
46
vendor/k8s.io/kubernetes/test/e2e/framework/pods.go
generated
vendored
@ -33,10 +33,12 @@ import (
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/kubelet/events"
|
||||
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
||||
)
|
||||
|
||||
// DefaultPodDeletionTimeout is the default timeout for deleting pod
|
||||
@ -81,36 +83,10 @@ func (c *PodClient) Create(pod *v1.Pod) *v1.Pod {
|
||||
return p
|
||||
}
|
||||
|
||||
// CreateEventually retries pod creation for a while before failing
|
||||
// the test with the most recent error. This mimicks the behavior
|
||||
// of a controller (like the one for DaemonSet) and is necessary
|
||||
// because pod creation can fail while its service account is still
|
||||
// getting provisioned
|
||||
// (https://github.com/kubernetes/kubernetes/issues/68776).
|
||||
//
|
||||
// Both the timeout and polling interval are configurable as optional
|
||||
// arguments:
|
||||
// - The first optional argument is the timeout.
|
||||
// - The second optional argument is the polling interval.
|
||||
//
|
||||
// Both intervals can either be specified as time.Duration, parsable
|
||||
// duration strings or as floats/integers. In the last case they are
|
||||
// interpreted as seconds.
|
||||
func (c *PodClient) CreateEventually(pod *v1.Pod, opts ...interface{}) *v1.Pod {
|
||||
c.mungeSpec(pod)
|
||||
var ret *v1.Pod
|
||||
gomega.Eventually(func() error {
|
||||
p, err := c.PodInterface.Create(pod)
|
||||
ret = p
|
||||
return err
|
||||
}, opts...).ShouldNot(gomega.HaveOccurred(), "Failed to create %q pod", pod.GetName())
|
||||
return ret
|
||||
}
|
||||
|
||||
// CreateSyncInNamespace creates a new pod according to the framework specifications in the given namespace, and waits for it to start.
|
||||
func (c *PodClient) CreateSyncInNamespace(pod *v1.Pod, namespace string) *v1.Pod {
|
||||
p := c.Create(pod)
|
||||
ExpectNoError(WaitForPodNameRunningInNamespace(c.f.ClientSet, p.Name, namespace))
|
||||
ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(c.f.ClientSet, p.Name, namespace))
|
||||
// Get the newest pod after it becomes running, some status may change after pod created, such as pod ip.
|
||||
p, err := c.Get(p.Name, metav1.GetOptions{})
|
||||
ExpectNoError(err)
|
||||
@ -150,11 +126,11 @@ func (c *PodClient) Update(name string, updateFn func(pod *v1.Pod)) {
|
||||
updateFn(pod)
|
||||
_, err = c.PodInterface.Update(pod)
|
||||
if err == nil {
|
||||
e2elog.Logf("Successfully updated pod %q", name)
|
||||
Logf("Successfully updated pod %q", name)
|
||||
return true, nil
|
||||
}
|
||||
if errors.IsConflict(err) {
|
||||
e2elog.Logf("Conflicting update to pod %q, re-get and re-update: %v", name, err)
|
||||
Logf("Conflicting update to pod %q, re-get and re-update: %v", name, err)
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to update pod %q: %v", name, err)
|
||||
@ -174,7 +150,7 @@ func (c *PodClient) DeleteSyncInNamespace(name string, namespace string, options
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
Failf("Failed to delete pod %q: %v", name, err)
|
||||
}
|
||||
gomega.Expect(WaitForPodToDisappear(c.f.ClientSet, namespace, name, labels.Everything(),
|
||||
gomega.Expect(e2epod.WaitForPodToDisappear(c.f.ClientSet, namespace, name, labels.Everything(),
|
||||
2*time.Second, timeout)).To(gomega.Succeed(), "wait for pod %q to disappear", name)
|
||||
}
|
||||
|
||||
@ -218,7 +194,7 @@ func (c *PodClient) mungeSpec(pod *v1.Pod) {
|
||||
// TODO(random-liu): Move pod wait function into this file
|
||||
func (c *PodClient) WaitForSuccess(name string, timeout time.Duration) {
|
||||
f := c.f
|
||||
gomega.Expect(WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, "success or failure", timeout,
|
||||
gomega.Expect(e2epod.WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, "success or failure", timeout,
|
||||
func(pod *v1.Pod) (bool, error) {
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodFailed:
|
||||
@ -235,7 +211,7 @@ func (c *PodClient) WaitForSuccess(name string, timeout time.Duration) {
|
||||
// WaitForFailure waits for pod to fail.
|
||||
func (c *PodClient) WaitForFailure(name string, timeout time.Duration) {
|
||||
f := c.f
|
||||
gomega.Expect(WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, "success or failure", timeout,
|
||||
gomega.Expect(e2epod.WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, "success or failure", timeout,
|
||||
func(pod *v1.Pod) (bool, error) {
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodFailed:
|
||||
@ -252,7 +228,7 @@ func (c *PodClient) WaitForFailure(name string, timeout time.Duration) {
|
||||
// WaitForFinish waits for pod to finish running, regardless of success or failure.
|
||||
func (c *PodClient) WaitForFinish(name string, timeout time.Duration) {
|
||||
f := c.f
|
||||
gomega.Expect(WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, "success or failure", timeout,
|
||||
gomega.Expect(e2epod.WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, "success or failure", timeout,
|
||||
func(pod *v1.Pod) (bool, error) {
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodFailed:
|
||||
@ -293,7 +269,7 @@ func (c *PodClient) WaitForErrorEventOrSuccess(pod *v1.Pod) (*v1.Event, error) {
|
||||
// MatchContainerOutput gets output of a container and match expected regexp in the output.
|
||||
func (c *PodClient) MatchContainerOutput(name string, containerName string, expectedRegexp string) error {
|
||||
f := c.f
|
||||
output, err := GetPodLogs(f.ClientSet, f.Namespace.Name, name, containerName)
|
||||
output, err := e2epod.GetPodLogs(f.ClientSet, f.Namespace.Name, name, containerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get output for container %q of pod %q", containerName, name)
|
||||
}
|
||||
|
6
vendor/k8s.io/kubernetes/test/e2e/framework/profile_gatherer.go
generated
vendored
6
vendor/k8s.io/kubernetes/test/e2e/framework/profile_gatherer.go
generated
vendored
@ -26,7 +26,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
||||
)
|
||||
|
||||
@ -182,7 +182,7 @@ func GatherCPUProfileForSeconds(componentName string, profileBaseName string, se
|
||||
defer wg.Done()
|
||||
}
|
||||
if err := gatherProfile(componentName, profileBaseName, fmt.Sprintf("profile?seconds=%v", seconds)); err != nil {
|
||||
e2elog.Logf("Failed to gather %v CPU profile: %v", componentName, err)
|
||||
Logf("Failed to gather %v CPU profile: %v", componentName, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ func GatherMemoryProfile(componentName string, profileBaseName string, wg *sync.
|
||||
defer wg.Done()
|
||||
}
|
||||
if err := gatherProfile(componentName, profileBaseName, "heap"); err != nil {
|
||||
e2elog.Logf("Failed to gather %v memory profile: %v", componentName, err)
|
||||
Logf("Failed to gather %v memory profile: %v", componentName, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,18 +20,20 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
"k8s.io/kubernetes/test/e2e/framework/auth"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
"k8s.io/kubernetes/test/e2e/framework/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -44,33 +46,33 @@ var (
|
||||
)
|
||||
|
||||
// privilegedPSP creates a PodSecurityPolicy that allows everything.
|
||||
func privilegedPSP(name string) *policy.PodSecurityPolicy {
|
||||
func privilegedPSP(name string) *policyv1beta1.PodSecurityPolicy {
|
||||
allowPrivilegeEscalation := true
|
||||
return &policy.PodSecurityPolicy{
|
||||
return &policyv1beta1.PodSecurityPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny},
|
||||
},
|
||||
Spec: policy.PodSecurityPolicySpec{
|
||||
Spec: policyv1beta1.PodSecurityPolicySpec{
|
||||
Privileged: true,
|
||||
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
|
||||
AllowedCapabilities: []corev1.Capability{"*"},
|
||||
Volumes: []policy.FSType{policy.All},
|
||||
AllowedCapabilities: []v1.Capability{"*"},
|
||||
Volumes: []policyv1beta1.FSType{policyv1beta1.All},
|
||||
HostNetwork: true,
|
||||
HostPorts: []policy.HostPortRange{{Min: 0, Max: 65535}},
|
||||
HostPorts: []policyv1beta1.HostPortRange{{Min: 0, Max: 65535}},
|
||||
HostIPC: true,
|
||||
HostPID: true,
|
||||
RunAsUser: policy.RunAsUserStrategyOptions{
|
||||
Rule: policy.RunAsUserStrategyRunAsAny,
|
||||
RunAsUser: policyv1beta1.RunAsUserStrategyOptions{
|
||||
Rule: policyv1beta1.RunAsUserStrategyRunAsAny,
|
||||
},
|
||||
SELinux: policy.SELinuxStrategyOptions{
|
||||
Rule: policy.SELinuxStrategyRunAsAny,
|
||||
SELinux: policyv1beta1.SELinuxStrategyOptions{
|
||||
Rule: policyv1beta1.SELinuxStrategyRunAsAny,
|
||||
},
|
||||
SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
|
||||
Rule: policy.SupplementalGroupsStrategyRunAsAny,
|
||||
SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{
|
||||
Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny,
|
||||
},
|
||||
FSGroup: policy.FSGroupStrategyOptions{
|
||||
Rule: policy.FSGroupStrategyRunAsAny,
|
||||
FSGroup: policyv1beta1.FSGroupStrategyOptions{
|
||||
Rule: policyv1beta1.FSGroupStrategyRunAsAny,
|
||||
},
|
||||
ReadOnlyRootFilesystem: false,
|
||||
AllowedUnsafeSysctls: []string{"*"},
|
||||
@ -79,17 +81,17 @@ func privilegedPSP(name string) *policy.PodSecurityPolicy {
|
||||
}
|
||||
|
||||
// IsPodSecurityPolicyEnabled returns true if PodSecurityPolicy is enabled. Otherwise false.
|
||||
func IsPodSecurityPolicyEnabled(f *Framework) bool {
|
||||
func IsPodSecurityPolicyEnabled(kubeClient clientset.Interface) bool {
|
||||
isPSPEnabledOnce.Do(func() {
|
||||
psps, err := f.ClientSet.PolicyV1beta1().PodSecurityPolicies().List(metav1.ListOptions{})
|
||||
psps, err := kubeClient.PolicyV1beta1().PodSecurityPolicies().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Error listing PodSecurityPolicies; assuming PodSecurityPolicy is disabled: %v", err)
|
||||
Logf("Error listing PodSecurityPolicies; assuming PodSecurityPolicy is disabled: %v", err)
|
||||
isPSPEnabled = false
|
||||
} else if psps == nil || len(psps.Items) == 0 {
|
||||
e2elog.Logf("No PodSecurityPolicies found; assuming PodSecurityPolicy is disabled.")
|
||||
Logf("No PodSecurityPolicies found; assuming PodSecurityPolicy is disabled.")
|
||||
isPSPEnabled = false
|
||||
} else {
|
||||
e2elog.Logf("Found PodSecurityPolicies; assuming PodSecurityPolicy is enabled.")
|
||||
Logf("Found PodSecurityPolicies; assuming PodSecurityPolicy is enabled.")
|
||||
isPSPEnabled = true
|
||||
}
|
||||
})
|
||||
@ -100,13 +102,14 @@ var (
|
||||
privilegedPSPOnce sync.Once
|
||||
)
|
||||
|
||||
func createPrivilegedPSPBinding(f *Framework, namespace string) {
|
||||
if !IsPodSecurityPolicyEnabled(f) {
|
||||
// CreatePrivilegedPSPBinding creates the privileged PSP & role
|
||||
func CreatePrivilegedPSPBinding(kubeClient clientset.Interface, namespace string) {
|
||||
if !IsPodSecurityPolicyEnabled(kubeClient) {
|
||||
return
|
||||
}
|
||||
// Create the privileged PSP & role
|
||||
privilegedPSPOnce.Do(func() {
|
||||
_, err := f.ClientSet.PolicyV1beta1().PodSecurityPolicies().Get(
|
||||
_, err := kubeClient.PolicyV1beta1().PodSecurityPolicies().Get(
|
||||
podSecurityPolicyPrivileged, metav1.GetOptions{})
|
||||
if !apierrs.IsNotFound(err) {
|
||||
// Privileged PSP was already created.
|
||||
@ -115,16 +118,16 @@ func createPrivilegedPSPBinding(f *Framework, namespace string) {
|
||||
}
|
||||
|
||||
psp := privilegedPSP(podSecurityPolicyPrivileged)
|
||||
psp, err = f.ClientSet.PolicyV1beta1().PodSecurityPolicies().Create(psp)
|
||||
_, err = kubeClient.PolicyV1beta1().PodSecurityPolicies().Create(psp)
|
||||
if !apierrs.IsAlreadyExists(err) {
|
||||
ExpectNoError(err, "Failed to create PSP %s", podSecurityPolicyPrivileged)
|
||||
}
|
||||
|
||||
if auth.IsRBACEnabled(f.ClientSet.RbacV1beta1()) {
|
||||
if auth.IsRBACEnabled(kubeClient.RbacV1()) {
|
||||
// Create the Role to bind it to the namespace.
|
||||
_, err = f.ClientSet.RbacV1beta1().ClusterRoles().Create(&rbacv1beta1.ClusterRole{
|
||||
_, err = kubeClient.RbacV1().ClusterRoles().Create(&rbacv1.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: podSecurityPolicyPrivileged},
|
||||
Rules: []rbacv1beta1.PolicyRule{{
|
||||
Rules: []rbacv1.PolicyRule{{
|
||||
APIGroups: []string{"extensions"},
|
||||
Resources: []string{"podsecuritypolicies"},
|
||||
ResourceNames: []string{podSecurityPolicyPrivileged},
|
||||
@ -137,19 +140,19 @@ func createPrivilegedPSPBinding(f *Framework, namespace string) {
|
||||
}
|
||||
})
|
||||
|
||||
if auth.IsRBACEnabled(f.ClientSet.RbacV1beta1()) {
|
||||
if auth.IsRBACEnabled(kubeClient.RbacV1()) {
|
||||
ginkgo.By(fmt.Sprintf("Binding the %s PodSecurityPolicy to the default service account in %s",
|
||||
podSecurityPolicyPrivileged, namespace))
|
||||
err := auth.BindClusterRoleInNamespace(f.ClientSet.RbacV1beta1(),
|
||||
err := auth.BindClusterRoleInNamespace(kubeClient.RbacV1(),
|
||||
podSecurityPolicyPrivileged,
|
||||
namespace,
|
||||
rbacv1beta1.Subject{
|
||||
Kind: rbacv1beta1.ServiceAccountKind,
|
||||
rbacv1.Subject{
|
||||
Kind: rbacv1.ServiceAccountKind,
|
||||
Namespace: namespace,
|
||||
Name: "default",
|
||||
})
|
||||
ExpectNoError(err)
|
||||
ExpectNoError(auth.WaitForNamedAuthorizationUpdate(f.ClientSet.AuthorizationV1beta1(),
|
||||
ExpectNoError(auth.WaitForNamedAuthorizationUpdate(kubeClient.AuthorizationV1(),
|
||||
serviceaccount.MakeUsername(namespace, "default"), namespace, "use", podSecurityPolicyPrivileged,
|
||||
schema.GroupResource{Group: "extensions", Resource: "podsecuritypolicies"}, true))
|
||||
}
|
1055
vendor/k8s.io/kubernetes/test/e2e/framework/pv_util.go
generated
vendored
1055
vendor/k8s.io/kubernetes/test/e2e/framework/pv_util.go
generated
vendored
File diff suppressed because it is too large
Load Diff
177
vendor/k8s.io/kubernetes/test/e2e/framework/rc_util.go
generated
vendored
177
vendor/k8s.io/kubernetes/test/e2e/framework/rc_util.go
generated
vendored
@ -18,33 +18,17 @@ package framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
scaleclient "k8s.io/client-go/scale"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
testutils "k8s.io/kubernetes/test/utils"
|
||||
)
|
||||
|
||||
// RcByNamePort returns a ReplicationController with specified name and port
|
||||
func RcByNamePort(name string, replicas int32, image string, port int, protocol v1.Protocol,
|
||||
labels map[string]string, gracePeriod *int64) *v1.ReplicationController {
|
||||
|
||||
return RcByNameContainer(name, replicas, image, labels, v1.Container{
|
||||
Name: name,
|
||||
Image: image,
|
||||
Ports: []v1.ContainerPort{{ContainerPort: int32(port), Protocol: protocol}},
|
||||
}, gracePeriod)
|
||||
}
|
||||
|
||||
// RcByNameContainer returns a ReplicationController with specified name and container
|
||||
func RcByNameContainer(name string, replicas int32, image string, labels map[string]string, c v1.Container,
|
||||
gracePeriod *int64) *v1.ReplicationController {
|
||||
@ -82,35 +66,6 @@ func RcByNameContainer(name string, replicas int32, image string, labels map[str
|
||||
}
|
||||
}
|
||||
|
||||
type updateRcFunc func(d *v1.ReplicationController)
|
||||
|
||||
// UpdateReplicationControllerWithRetries retries updating the given rc on conflict with the following steps:
|
||||
// 1. Get latest resource
|
||||
// 2. applyUpdate
|
||||
// 3. Update the resource
|
||||
func UpdateReplicationControllerWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateRcFunc) (*v1.ReplicationController, error) {
|
||||
var rc *v1.ReplicationController
|
||||
var updateErr error
|
||||
pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
|
||||
var err error
|
||||
if rc, err = c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Apply the update, then attempt to push it to the apiserver.
|
||||
applyUpdate(rc)
|
||||
if rc, err = c.CoreV1().ReplicationControllers(namespace).Update(rc); err == nil {
|
||||
e2elog.Logf("Updating replication controller %q", name)
|
||||
return true, nil
|
||||
}
|
||||
updateErr = err
|
||||
return false, nil
|
||||
})
|
||||
if pollErr == wait.ErrWaitTimeout {
|
||||
pollErr = fmt.Errorf("couldn't apply the provided updated to rc %q: %v", name, updateErr)
|
||||
}
|
||||
return rc, pollErr
|
||||
}
|
||||
|
||||
// DeleteRCAndWaitForGC deletes only the Replication Controller and waits for GC to delete the pods.
|
||||
func DeleteRCAndWaitForGC(c clientset.Interface, ns, name string) error {
|
||||
return DeleteResourceAndWaitForGC(c, api.Kind("ReplicationController"), ns, name)
|
||||
@ -118,7 +73,7 @@ func DeleteRCAndWaitForGC(c clientset.Interface, ns, name string) error {
|
||||
|
||||
// ScaleRC scales Replication Controller to be desired size.
|
||||
func ScaleRC(clientset clientset.Interface, scalesGetter scaleclient.ScalesGetter, ns, name string, size uint, wait bool) error {
|
||||
return ScaleResource(clientset, scalesGetter, ns, name, size, wait, api.Kind("ReplicationController"), api.Resource("replicationcontrollers"))
|
||||
return ScaleResource(clientset, scalesGetter, ns, name, size, wait, api.Kind("ReplicationController"), api.SchemeGroupVersion.WithResource("replicationcontrollers"))
|
||||
}
|
||||
|
||||
// RunRC Launches (and verifies correctness) of a Replication Controller
|
||||
@ -129,131 +84,3 @@ func RunRC(config testutils.RCConfig) error {
|
||||
config.ContainerDumpFunc = LogFailedContainers
|
||||
return testutils.RunRC(config)
|
||||
}
|
||||
|
||||
// WaitForRCPodToDisappear returns nil if the pod from the given replication controller (described by rcName) no longer exists.
|
||||
// In case of failure or too long waiting time, an error is returned.
|
||||
func WaitForRCPodToDisappear(c clientset.Interface, ns, rcName, podName string) error {
|
||||
label := labels.SelectorFromSet(labels.Set(map[string]string{"name": rcName}))
|
||||
// NodeController evicts pod after 5 minutes, so we need timeout greater than that to observe effects.
|
||||
// The grace period must be set to 0 on the pod for it to be deleted during the partition.
|
||||
// Otherwise, it goes to the 'Terminating' state till the kubelet confirms deletion.
|
||||
return WaitForPodToDisappear(c, ns, podName, label, 20*time.Second, 10*time.Minute)
|
||||
}
|
||||
|
||||
// WaitForReplicationController waits until the RC appears (exist == true), or disappears (exist == false)
|
||||
func WaitForReplicationController(c clientset.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error {
|
||||
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
_, err := c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Get ReplicationController %s in namespace %s failed (%v).", name, namespace, err)
|
||||
return !exist, nil
|
||||
}
|
||||
e2elog.Logf("ReplicationController %s in namespace %s found.", name, namespace)
|
||||
return exist, nil
|
||||
})
|
||||
if err != nil {
|
||||
stateMsg := map[bool]string{true: "to appear", false: "to disappear"}
|
||||
return fmt.Errorf("error waiting for ReplicationController %s/%s %s: %v", namespace, name, stateMsg[exist], err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForReplicationControllerwithSelector waits until any RC with given selector appears (exist == true), or disappears (exist == false)
|
||||
func WaitForReplicationControllerwithSelector(c clientset.Interface, namespace string, selector labels.Selector, exist bool, interval,
|
||||
timeout time.Duration) error {
|
||||
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
rcs, err := c.CoreV1().ReplicationControllers(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
|
||||
switch {
|
||||
case len(rcs.Items) != 0:
|
||||
e2elog.Logf("ReplicationController with %s in namespace %s found.", selector.String(), namespace)
|
||||
return exist, nil
|
||||
case len(rcs.Items) == 0:
|
||||
e2elog.Logf("ReplicationController with %s in namespace %s disappeared.", selector.String(), namespace)
|
||||
return !exist, nil
|
||||
default:
|
||||
e2elog.Logf("List ReplicationController with %s in namespace %s failed: %v", selector.String(), namespace, err)
|
||||
return false, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
stateMsg := map[bool]string{true: "to appear", false: "to disappear"}
|
||||
return fmt.Errorf("error waiting for ReplicationControllers with %s in namespace %s %s: %v", selector.String(), namespace, stateMsg[exist], err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// trimDockerRegistry is the function for trimming the docker.io/library from the beginning of the imagename.
|
||||
// If community docker installed it will not prefix the registry names with the dockerimages vs registry names prefixed with other runtimes or docker installed via RHEL extra repo.
|
||||
// So this function will help to trim the docker.io/library if exists
|
||||
func trimDockerRegistry(imagename string) string {
|
||||
imagename = strings.Replace(imagename, "docker.io/", "", 1)
|
||||
return strings.Replace(imagename, "library/", "", 1)
|
||||
}
|
||||
|
||||
// validatorFn is the function which is individual tests will implement.
|
||||
// we may want it to return more than just an error, at some point.
|
||||
type validatorFn func(c clientset.Interface, podID string) error
|
||||
|
||||
// ValidateController is a generic mechanism for testing RC's that are running.
|
||||
// It takes a container name, a test name, and a validator function which is plugged in by a specific test.
|
||||
// "containername": this is grepped for.
|
||||
// "containerImage" : this is the name of the image we expect to be launched. Not to confuse w/ images (kitten.jpg) which are validated.
|
||||
// "testname": which gets bubbled up to the logging/failure messages if errors happen.
|
||||
// "validator" function: This function is given a podID and a client, and it can do some specific validations that way.
|
||||
func ValidateController(c clientset.Interface, containerImage string, replicas int, containername string, testname string, validator validatorFn, ns string) {
|
||||
containerImage = trimDockerRegistry(containerImage)
|
||||
getPodsTemplate := "--template={{range.items}}{{.metadata.name}} {{end}}"
|
||||
// NB: kubectl adds the "exists" function to the standard template functions.
|
||||
// This lets us check to see if the "running" entry exists for each of the containers
|
||||
// we care about. Exists will never return an error and it's safe to check a chain of
|
||||
// things, any one of which may not exist. In the below template, all of info,
|
||||
// containername, and running might be nil, so the normal index function isn't very
|
||||
// helpful.
|
||||
// This template is unit-tested in kubectl, so if you change it, update the unit test.
|
||||
// You can read about the syntax here: http://golang.org/pkg/text/template/.
|
||||
getContainerStateTemplate := fmt.Sprintf(`--template={{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "%s") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`, containername)
|
||||
|
||||
getImageTemplate := fmt.Sprintf(`--template={{if (exists . "spec" "containers")}}{{range .spec.containers}}{{if eq .name "%s"}}{{.image}}{{end}}{{end}}{{end}}`, containername)
|
||||
|
||||
ginkgo.By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector
|
||||
waitLoop:
|
||||
for start := time.Now(); time.Since(start) < PodStartTimeout; time.Sleep(5 * time.Second) {
|
||||
getPodsOutput := RunKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "-l", testname, fmt.Sprintf("--namespace=%v", ns))
|
||||
pods := strings.Fields(getPodsOutput)
|
||||
if numPods := len(pods); numPods != replicas {
|
||||
ginkgo.By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods))
|
||||
continue
|
||||
}
|
||||
var runningPods []string
|
||||
for _, podID := range pods {
|
||||
running := RunKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, fmt.Sprintf("--namespace=%v", ns))
|
||||
if running != "true" {
|
||||
e2elog.Logf("%s is created but not running", podID)
|
||||
continue waitLoop
|
||||
}
|
||||
|
||||
currentImage := RunKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, fmt.Sprintf("--namespace=%v", ns))
|
||||
currentImage = trimDockerRegistry(currentImage)
|
||||
if currentImage != containerImage {
|
||||
e2elog.Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage)
|
||||
continue waitLoop
|
||||
}
|
||||
|
||||
// Call the generic validator function here.
|
||||
// This might validate for example, that (1) getting a url works and (2) url is serving correct content.
|
||||
if err := validator(c, podID); err != nil {
|
||||
e2elog.Logf("%s is running right image but validator function failed: %v", podID, err)
|
||||
continue waitLoop
|
||||
}
|
||||
|
||||
e2elog.Logf("%s is verified up and running", podID)
|
||||
runningPods = append(runningPods, podID)
|
||||
}
|
||||
// If we reach here, then all our checks passed.
|
||||
if len(runningPods) == replicas {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Reaching here means that one of more checks failed multiple times. Assuming its not a race condition, something is broken.
|
||||
Failf("Timed out after %v seconds waiting for %s pods to reach valid state", PodStartTimeout.Seconds(), testname)
|
||||
}
|
||||
|
123
vendor/k8s.io/kubernetes/test/e2e/framework/resource/runtimeobj.go
generated
vendored
Normal file
123
vendor/k8s.io/kubernetes/test/e2e/framework/resource/runtimeobj.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2019 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 resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
appsinternal "k8s.io/kubernetes/pkg/apis/apps"
|
||||
batchinternal "k8s.io/kubernetes/pkg/apis/batch"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
// GetRuntimeObjectForKind returns a runtime.Object based on its GroupKind,
|
||||
// namespace and name.
|
||||
func GetRuntimeObjectForKind(c clientset.Interface, kind schema.GroupKind, ns, name string) (runtime.Object, error) {
|
||||
switch kind {
|
||||
case api.Kind("ReplicationController"):
|
||||
return c.CoreV1().ReplicationControllers(ns).Get(name, metav1.GetOptions{})
|
||||
case extensionsinternal.Kind("ReplicaSet"), appsinternal.Kind("ReplicaSet"):
|
||||
return c.AppsV1().ReplicaSets(ns).Get(name, metav1.GetOptions{})
|
||||
case extensionsinternal.Kind("Deployment"), appsinternal.Kind("Deployment"):
|
||||
return c.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{})
|
||||
case extensionsinternal.Kind("DaemonSet"):
|
||||
return c.AppsV1().DaemonSets(ns).Get(name, metav1.GetOptions{})
|
||||
case batchinternal.Kind("Job"):
|
||||
return c.BatchV1().Jobs(ns).Get(name, metav1.GetOptions{})
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported kind when getting runtime object: %v", kind)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSelectorFromRuntimeObject returns the labels for the given object.
|
||||
func GetSelectorFromRuntimeObject(obj runtime.Object) (labels.Selector, error) {
|
||||
switch typed := obj.(type) {
|
||||
case *v1.ReplicationController:
|
||||
return labels.SelectorFromSet(typed.Spec.Selector), nil
|
||||
case *extensionsv1beta1.ReplicaSet:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
case *appsv1.ReplicaSet:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
case *extensionsv1beta1.Deployment:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
case *appsv1.Deployment:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
case *extensionsv1beta1.DaemonSet:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
case *appsv1.DaemonSet:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
case *batchv1.Job:
|
||||
return metav1.LabelSelectorAsSelector(typed.Spec.Selector)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported kind when getting selector: %v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
// GetReplicasFromRuntimeObject returns the number of replicas for the given
|
||||
// object.
|
||||
func GetReplicasFromRuntimeObject(obj runtime.Object) (int32, error) {
|
||||
switch typed := obj.(type) {
|
||||
case *v1.ReplicationController:
|
||||
if typed.Spec.Replicas != nil {
|
||||
return *typed.Spec.Replicas, nil
|
||||
}
|
||||
return 0, nil
|
||||
case *extensionsv1beta1.ReplicaSet:
|
||||
if typed.Spec.Replicas != nil {
|
||||
return *typed.Spec.Replicas, nil
|
||||
}
|
||||
return 0, nil
|
||||
case *appsv1.ReplicaSet:
|
||||
if typed.Spec.Replicas != nil {
|
||||
return *typed.Spec.Replicas, nil
|
||||
}
|
||||
return 0, nil
|
||||
case *extensionsv1beta1.Deployment:
|
||||
if typed.Spec.Replicas != nil {
|
||||
return *typed.Spec.Replicas, nil
|
||||
}
|
||||
return 0, nil
|
||||
case *appsv1.Deployment:
|
||||
if typed.Spec.Replicas != nil {
|
||||
return *typed.Spec.Replicas, nil
|
||||
}
|
||||
return 0, nil
|
||||
case *extensionsv1beta1.DaemonSet:
|
||||
return 0, nil
|
||||
case *appsv1.DaemonSet:
|
||||
return 0, nil
|
||||
case *batchv1.Job:
|
||||
// TODO: currently we use pause pods so that's OK. When we'll want to switch to Pods
|
||||
// that actually finish we need a better way to do this.
|
||||
if typed.Spec.Parallelism != nil {
|
||||
return *typed.Spec.Parallelism, nil
|
||||
}
|
||||
return 0, nil
|
||||
default:
|
||||
return -1, fmt.Errorf("Unsupported kind when getting number of replicas: %v", obj)
|
||||
}
|
||||
}
|
312
vendor/k8s.io/kubernetes/test/e2e/framework/resource_usage_gatherer.go
generated
vendored
312
vendor/k8s.io/kubernetes/test/e2e/framework/resource_usage_gatherer.go
generated
vendored
@ -18,6 +18,8 @@ package framework
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
@ -27,12 +29,13 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/util/system"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
kubeletstatsv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/master/ports"
|
||||
"k8s.io/kubernetes/test/e2e/system"
|
||||
)
|
||||
|
||||
// ResourceConstraint is a struct to hold constraints.
|
||||
@ -48,6 +51,21 @@ type SingleContainerSummary struct {
|
||||
Mem uint64
|
||||
}
|
||||
|
||||
// ContainerResourceUsage is a structure for gathering container resource usage.
|
||||
type ContainerResourceUsage struct {
|
||||
Name string
|
||||
Timestamp time.Time
|
||||
CPUUsageInCores float64
|
||||
MemoryUsageInBytes uint64
|
||||
MemoryWorkingSetInBytes uint64
|
||||
MemoryRSSInBytes uint64
|
||||
// The interval used to calculate CPUUsageInCores.
|
||||
CPUInterval time.Duration
|
||||
}
|
||||
|
||||
// ResourceUsagePerContainer is map of ContainerResourceUsage
|
||||
type ResourceUsagePerContainer map[string]*ContainerResourceUsage
|
||||
|
||||
// ResourceUsageSummary is a struct to hold resource usage summary.
|
||||
// we can't have int here, as JSON does not accept integer keys.
|
||||
type ResourceUsageSummary map[string][]SingleContainerSummary
|
||||
@ -80,6 +98,18 @@ func (s *ResourceUsageSummary) SummaryKind() string {
|
||||
return "ResourceUsageSummary"
|
||||
}
|
||||
|
||||
type uint64arr []uint64
|
||||
|
||||
func (a uint64arr) Len() int { return len(a) }
|
||||
func (a uint64arr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a uint64arr) Less(i, j int) bool { return a[i] < a[j] }
|
||||
|
||||
type usageDataPerContainer struct {
|
||||
cpuData []float64
|
||||
memUseData []uint64
|
||||
memWorkSetData []uint64
|
||||
}
|
||||
|
||||
func computePercentiles(timeSeries []ResourceUsagePerContainer, percentilesToCompute []int) map[int]ResourceUsagePerContainer {
|
||||
if len(timeSeries) == 0 {
|
||||
return make(map[int]ResourceUsagePerContainer)
|
||||
@ -167,23 +197,132 @@ func (w *resourceGatherWorker) singleProbe() {
|
||||
} else {
|
||||
nodeUsage, err := getOneTimeResourceUsageOnNode(w.c, w.nodeName, w.probeDuration, func() []string { return w.containerIDs })
|
||||
if err != nil {
|
||||
e2elog.Logf("Error while reading data from %v: %v", w.nodeName, err)
|
||||
Logf("Error while reading data from %v: %v", w.nodeName, err)
|
||||
return
|
||||
}
|
||||
for k, v := range nodeUsage {
|
||||
data[k] = v
|
||||
if w.printVerboseLogs {
|
||||
e2elog.Logf("Get container %v usage on node %v. CPUUsageInCores: %v, MemoryUsageInBytes: %v, MemoryWorkingSetInBytes: %v", k, w.nodeName, v.CPUUsageInCores, v.MemoryUsageInBytes, v.MemoryWorkingSetInBytes)
|
||||
Logf("Get container %v usage on node %v. CPUUsageInCores: %v, MemoryUsageInBytes: %v, MemoryWorkingSetInBytes: %v", k, w.nodeName, v.CPUUsageInCores, v.MemoryUsageInBytes, v.MemoryWorkingSetInBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.dataSeries = append(w.dataSeries, data)
|
||||
}
|
||||
|
||||
// getOneTimeResourceUsageOnNode queries the node's /stats/summary endpoint
|
||||
// and returns the resource usage of all containerNames for the past
|
||||
// cpuInterval.
|
||||
// The acceptable range of the interval is 2s~120s. Be warned that as the
|
||||
// interval (and #containers) increases, the size of kubelet's response
|
||||
// could be significant. E.g., the 60s interval stats for ~20 containers is
|
||||
// ~1.5MB. Don't hammer the node with frequent, heavy requests.
|
||||
//
|
||||
// cadvisor records cumulative cpu usage in nanoseconds, so we need to have two
|
||||
// stats points to compute the cpu usage over the interval. Assuming cadvisor
|
||||
// polls every second, we'd need to get N stats points for N-second interval.
|
||||
// Note that this is an approximation and may not be accurate, hence we also
|
||||
// write the actual interval used for calculation (based on the timestamps of
|
||||
// the stats points in ContainerResourceUsage.CPUInterval.
|
||||
//
|
||||
// containerNames is a function returning a collection of container names in which
|
||||
// user is interested in.
|
||||
func getOneTimeResourceUsageOnNode(
|
||||
c clientset.Interface,
|
||||
nodeName string,
|
||||
cpuInterval time.Duration,
|
||||
containerNames func() []string,
|
||||
) (ResourceUsagePerContainer, error) {
|
||||
const (
|
||||
// cadvisor records stats about every second.
|
||||
cadvisorStatsPollingIntervalInSeconds float64 = 1.0
|
||||
// cadvisor caches up to 2 minutes of stats (configured by kubelet).
|
||||
maxNumStatsToRequest int = 120
|
||||
)
|
||||
|
||||
numStats := int(float64(cpuInterval.Seconds()) / cadvisorStatsPollingIntervalInSeconds)
|
||||
if numStats < 2 || numStats > maxNumStatsToRequest {
|
||||
return nil, fmt.Errorf("numStats needs to be > 1 and < %d", maxNumStatsToRequest)
|
||||
}
|
||||
// Get information of all containers on the node.
|
||||
summary, err := getStatsSummary(c, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := func(name string, newStats *kubeletstatsv1alpha1.ContainerStats) *ContainerResourceUsage {
|
||||
if newStats == nil || newStats.CPU == nil || newStats.Memory == nil {
|
||||
return nil
|
||||
}
|
||||
return &ContainerResourceUsage{
|
||||
Name: name,
|
||||
Timestamp: newStats.StartTime.Time,
|
||||
CPUUsageInCores: float64(removeUint64Ptr(newStats.CPU.UsageNanoCores)) / 1000000000,
|
||||
MemoryUsageInBytes: removeUint64Ptr(newStats.Memory.UsageBytes),
|
||||
MemoryWorkingSetInBytes: removeUint64Ptr(newStats.Memory.WorkingSetBytes),
|
||||
MemoryRSSInBytes: removeUint64Ptr(newStats.Memory.RSSBytes),
|
||||
CPUInterval: 0,
|
||||
}
|
||||
}
|
||||
// Process container infos that are relevant to us.
|
||||
containers := containerNames()
|
||||
usageMap := make(ResourceUsagePerContainer, len(containers))
|
||||
for _, pod := range summary.Pods {
|
||||
for _, container := range pod.Containers {
|
||||
isInteresting := false
|
||||
for _, interestingContainerName := range containers {
|
||||
if container.Name == interestingContainerName {
|
||||
isInteresting = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isInteresting {
|
||||
continue
|
||||
}
|
||||
if usage := f(pod.PodRef.Name+"/"+container.Name, &container); usage != nil {
|
||||
usageMap[pod.PodRef.Name+"/"+container.Name] = usage
|
||||
}
|
||||
}
|
||||
}
|
||||
return usageMap, nil
|
||||
}
|
||||
|
||||
// getStatsSummary contacts kubelet for the container information.
|
||||
func getStatsSummary(c clientset.Interface, nodeName string) (*kubeletstatsv1alpha1.Summary, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), SingleCallTimeout)
|
||||
defer cancel()
|
||||
|
||||
data, err := c.CoreV1().RESTClient().Get().
|
||||
Context(ctx).
|
||||
Resource("nodes").
|
||||
SubResource("proxy").
|
||||
Name(fmt.Sprintf("%v:%v", nodeName, ports.KubeletPort)).
|
||||
Suffix("stats/summary").
|
||||
Do().Raw()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summary := kubeletstatsv1alpha1.Summary{}
|
||||
err = json.Unmarshal(data, &summary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &summary, nil
|
||||
}
|
||||
|
||||
func removeUint64Ptr(ptr *uint64) uint64 {
|
||||
if ptr == nil {
|
||||
return 0
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
func (w *resourceGatherWorker) gather(initialSleep time.Duration) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer w.wg.Done()
|
||||
defer e2elog.Logf("Closing worker for %v", w.nodeName)
|
||||
defer Logf("Closing worker for %v", w.nodeName)
|
||||
defer func() { w.finished = true }()
|
||||
select {
|
||||
case <-time.After(initialSleep):
|
||||
@ -252,58 +391,59 @@ func NewResourceUsageGatherer(c clientset.Interface, options ResourceGathererOpt
|
||||
probeDuration: options.ProbeDuration,
|
||||
printVerboseLogs: options.PrintVerboseLogs,
|
||||
})
|
||||
} else {
|
||||
// Tracks kube-system pods if no valid PodList is passed in.
|
||||
var err error
|
||||
if pods == nil {
|
||||
pods, err = c.CoreV1().Pods("kube-system").List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Error while listing Pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dnsNodes := make(map[string]bool)
|
||||
for _, pod := range pods.Items {
|
||||
if (options.Nodes == MasterNodes) && !system.IsMasterNode(pod.Spec.NodeName) {
|
||||
continue
|
||||
}
|
||||
if (options.Nodes == MasterAndDNSNodes) && !system.IsMasterNode(pod.Spec.NodeName) && pod.Labels["k8s-app"] != "kube-dns" {
|
||||
continue
|
||||
}
|
||||
for _, container := range pod.Status.InitContainerStatuses {
|
||||
g.containerIDs = append(g.containerIDs, container.Name)
|
||||
}
|
||||
for _, container := range pod.Status.ContainerStatuses {
|
||||
g.containerIDs = append(g.containerIDs, container.Name)
|
||||
}
|
||||
if options.Nodes == MasterAndDNSNodes {
|
||||
dnsNodes[pod.Spec.NodeName] = true
|
||||
}
|
||||
}
|
||||
nodeList, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
// Tracks kube-system pods if no valid PodList is passed in.
|
||||
var err error
|
||||
if pods == nil {
|
||||
pods, err = c.CoreV1().Pods("kube-system").List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
e2elog.Logf("Error while listing Nodes: %v", err)
|
||||
Logf("Error while listing Pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dnsNodes := make(map[string]bool)
|
||||
for _, pod := range pods.Items {
|
||||
if (options.Nodes == MasterNodes) && !system.DeprecatedMightBeMasterNode(pod.Spec.NodeName) {
|
||||
continue
|
||||
}
|
||||
if (options.Nodes == MasterAndDNSNodes) && !system.DeprecatedMightBeMasterNode(pod.Spec.NodeName) && pod.Labels["k8s-app"] != "kube-dns" {
|
||||
continue
|
||||
}
|
||||
for _, container := range pod.Status.InitContainerStatuses {
|
||||
g.containerIDs = append(g.containerIDs, container.Name)
|
||||
}
|
||||
for _, container := range pod.Status.ContainerStatuses {
|
||||
g.containerIDs = append(g.containerIDs, container.Name)
|
||||
}
|
||||
if options.Nodes == MasterAndDNSNodes {
|
||||
dnsNodes[pod.Spec.NodeName] = true
|
||||
}
|
||||
}
|
||||
nodeList, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
Logf("Error while listing Nodes: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, node := range nodeList.Items {
|
||||
if options.Nodes == AllNodes || system.IsMasterNode(node.Name) || dnsNodes[node.Name] {
|
||||
g.workerWg.Add(1)
|
||||
g.workers = append(g.workers, resourceGatherWorker{
|
||||
c: c,
|
||||
nodeName: node.Name,
|
||||
wg: &g.workerWg,
|
||||
containerIDs: g.containerIDs,
|
||||
stopCh: g.stopCh,
|
||||
finished: false,
|
||||
inKubemark: false,
|
||||
resourceDataGatheringPeriod: options.ResourceDataGatheringPeriod,
|
||||
probeDuration: options.ProbeDuration,
|
||||
printVerboseLogs: options.PrintVerboseLogs,
|
||||
})
|
||||
if options.Nodes == MasterNodes {
|
||||
break
|
||||
}
|
||||
for _, node := range nodeList.Items {
|
||||
if options.Nodes == AllNodes || system.DeprecatedMightBeMasterNode(node.Name) || dnsNodes[node.Name] {
|
||||
g.workerWg.Add(1)
|
||||
g.workers = append(g.workers, resourceGatherWorker{
|
||||
c: c,
|
||||
nodeName: node.Name,
|
||||
wg: &g.workerWg,
|
||||
containerIDs: g.containerIDs,
|
||||
stopCh: g.stopCh,
|
||||
finished: false,
|
||||
inKubemark: false,
|
||||
resourceDataGatheringPeriod: options.ResourceDataGatheringPeriod,
|
||||
probeDuration: options.ProbeDuration,
|
||||
printVerboseLogs: options.PrintVerboseLogs,
|
||||
})
|
||||
if options.Nodes == MasterNodes {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -331,15 +471,15 @@ func (g *ContainerResourceGatherer) StartGatheringData() {
|
||||
// specified resource constraints.
|
||||
func (g *ContainerResourceGatherer) StopAndSummarize(percentiles []int, constraints map[string]ResourceConstraint) (*ResourceUsageSummary, error) {
|
||||
close(g.stopCh)
|
||||
e2elog.Logf("Closed stop channel. Waiting for %v workers", len(g.workers))
|
||||
finished := make(chan struct{})
|
||||
Logf("Closed stop channel. Waiting for %v workers", len(g.workers))
|
||||
finished := make(chan struct{}, 1)
|
||||
go func() {
|
||||
g.workerWg.Wait()
|
||||
finished <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-finished:
|
||||
e2elog.Logf("Waitgroup finished.")
|
||||
Logf("Waitgroup finished.")
|
||||
case <-time.After(2 * time.Minute):
|
||||
unfinished := make([]string, 0)
|
||||
for i := range g.workers {
|
||||
@ -347,11 +487,11 @@ func (g *ContainerResourceGatherer) StopAndSummarize(percentiles []int, constrai
|
||||
unfinished = append(unfinished, g.workers[i].nodeName)
|
||||
}
|
||||
}
|
||||
e2elog.Logf("Timed out while waiting for waitgroup, some workers failed to finish: %v", unfinished)
|
||||
Logf("Timed out while waiting for waitgroup, some workers failed to finish: %v", unfinished)
|
||||
}
|
||||
|
||||
if len(percentiles) == 0 {
|
||||
e2elog.Logf("Warning! Empty percentile list for stopAndPrintData.")
|
||||
Logf("Warning! Empty percentile list for stopAndPrintData.")
|
||||
return &ResourceUsageSummary{}, fmt.Errorf("Failed to get any resource usage data")
|
||||
}
|
||||
data := make(map[int]ResourceUsagePerContainer)
|
||||
@ -378,32 +518,36 @@ func (g *ContainerResourceGatherer) StopAndSummarize(percentiles []int, constrai
|
||||
CPU: usage.CPUUsageInCores,
|
||||
Mem: usage.MemoryWorkingSetInBytes,
|
||||
})
|
||||
|
||||
// Verifying 99th percentile of resource usage
|
||||
if perc == 99 {
|
||||
// Name has a form: <pod_name>/<container_name>
|
||||
containerName := strings.Split(name, "/")[1]
|
||||
if constraint, ok := constraints[containerName]; ok {
|
||||
if usage.CPUUsageInCores > constraint.CPUConstraint {
|
||||
violatedConstraints = append(
|
||||
violatedConstraints,
|
||||
fmt.Sprintf("Container %v is using %v/%v CPU",
|
||||
name,
|
||||
usage.CPUUsageInCores,
|
||||
constraint.CPUConstraint,
|
||||
),
|
||||
)
|
||||
}
|
||||
if usage.MemoryWorkingSetInBytes > constraint.MemoryConstraint {
|
||||
violatedConstraints = append(
|
||||
violatedConstraints,
|
||||
fmt.Sprintf("Container %v is using %v/%v MB of memory",
|
||||
name,
|
||||
float64(usage.MemoryWorkingSetInBytes)/(1024*1024),
|
||||
float64(constraint.MemoryConstraint)/(1024*1024),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
if perc != 99 {
|
||||
continue
|
||||
}
|
||||
// Name has a form: <pod_name>/<container_name>
|
||||
containerName := strings.Split(name, "/")[1]
|
||||
constraint, ok := constraints[containerName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if usage.CPUUsageInCores > constraint.CPUConstraint {
|
||||
violatedConstraints = append(
|
||||
violatedConstraints,
|
||||
fmt.Sprintf("Container %v is using %v/%v CPU",
|
||||
name,
|
||||
usage.CPUUsageInCores,
|
||||
constraint.CPUConstraint,
|
||||
),
|
||||
)
|
||||
}
|
||||
if usage.MemoryWorkingSetInBytes > constraint.MemoryConstraint {
|
||||
violatedConstraints = append(
|
||||
violatedConstraints,
|
||||
fmt.Sprintf("Container %v is using %v/%v MB of memory",
|
||||
name,
|
||||
float64(usage.MemoryWorkingSetInBytes)/(1024*1024),
|
||||
float64(constraint.MemoryConstraint)/(1024*1024),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1541
vendor/k8s.io/kubernetes/test/e2e/framework/service_util.go
generated
vendored
1541
vendor/k8s.io/kubernetes/test/e2e/framework/service_util.go
generated
vendored
File diff suppressed because it is too large
Load Diff
13
vendor/k8s.io/kubernetes/test/e2e/framework/size.go
generated
vendored
13
vendor/k8s.io/kubernetes/test/e2e/framework/size.go
generated
vendored
@ -19,13 +19,6 @@ package framework
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
const (
|
||||
resizeNodeReadyTimeout = 2 * time.Minute
|
||||
resizeNodeNotReadyTimeout = 2 * time.Minute
|
||||
)
|
||||
|
||||
// ResizeGroup resizes an instance group
|
||||
@ -53,14 +46,14 @@ func WaitForGroupSize(group string, size int32) error {
|
||||
for start := time.Now(); time.Since(start) < timeout; time.Sleep(20 * time.Second) {
|
||||
currentSize, err := GroupSize(group)
|
||||
if err != nil {
|
||||
e2elog.Logf("Failed to get node instance group size: %v", err)
|
||||
Logf("Failed to get node instance group size: %v", err)
|
||||
continue
|
||||
}
|
||||
if currentSize != int(size) {
|
||||
e2elog.Logf("Waiting for node instance group size %d, current size %d", size, currentSize)
|
||||
Logf("Waiting for node instance group size %d, current size %d", size, currentSize)
|
||||
continue
|
||||
}
|
||||
e2elog.Logf("Node instance group has reached the desired size %d", size)
|
||||
Logf("Node instance group has reached the desired size %d", size)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("timeout waiting %v for node instance group size to be %d", timeout, size)
|
||||
|
186
vendor/k8s.io/kubernetes/test/e2e/framework/skip.go
generated
vendored
Normal file
186
vendor/k8s.io/kubernetes/test/e2e/framework/skip.go
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilversion "k8s.io/apimachinery/pkg/util/version"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
"k8s.io/kubernetes/test/e2e/framework/ginkgowrapper"
|
||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
||||
)
|
||||
|
||||
func skipInternalf(caller int, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
log("INFO", msg)
|
||||
ginkgowrapper.Skip(msg, caller+1)
|
||||
}
|
||||
|
||||
// Skipf skips with information about why the test is being skipped.
|
||||
func Skipf(format string, args ...interface{}) {
|
||||
skipInternalf(1, format, args...)
|
||||
}
|
||||
|
||||
// SkipUnlessAtLeast skips if the value is less than the minValue.
|
||||
func SkipUnlessAtLeast(value int, minValue int, message string) {
|
||||
if value < minValue {
|
||||
skipInternalf(1, message)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessLocalEphemeralStorageEnabled skips if the LocalStorageCapacityIsolation is not enabled.
|
||||
func SkipUnlessLocalEphemeralStorageEnabled() {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
|
||||
skipInternalf(1, "Only supported when %v feature is enabled", features.LocalStorageCapacityIsolation)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipIfMissingResource skips if the gvr resource is missing.
|
||||
func SkipIfMissingResource(dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, namespace string) {
|
||||
resourceClient := dynamicClient.Resource(gvr).Namespace(namespace)
|
||||
_, err := resourceClient.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
// not all resources support list, so we ignore those
|
||||
if apierrs.IsMethodNotSupported(err) || apierrs.IsNotFound(err) || apierrs.IsForbidden(err) {
|
||||
skipInternalf(1, "Could not find %s resource, skipping test: %#v", gvr, err)
|
||||
}
|
||||
Failf("Unexpected error getting %v: %v", gvr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessNodeCountIsAtLeast skips if the number of nodes is less than the minNodeCount.
|
||||
func SkipUnlessNodeCountIsAtLeast(minNodeCount int) {
|
||||
if TestContext.CloudConfig.NumNodes < minNodeCount {
|
||||
skipInternalf(1, "Requires at least %d nodes (not %d)", minNodeCount, TestContext.CloudConfig.NumNodes)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessNodeCountIsAtMost skips if the number of nodes is greater than the maxNodeCount.
|
||||
func SkipUnlessNodeCountIsAtMost(maxNodeCount int) {
|
||||
if TestContext.CloudConfig.NumNodes > maxNodeCount {
|
||||
skipInternalf(1, "Requires at most %d nodes (not %d)", maxNodeCount, TestContext.CloudConfig.NumNodes)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipIfProviderIs skips if the provider is included in the unsupportedProviders.
|
||||
func SkipIfProviderIs(unsupportedProviders ...string) {
|
||||
if ProviderIs(unsupportedProviders...) {
|
||||
skipInternalf(1, "Not supported for providers %v (found %s)", unsupportedProviders, TestContext.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessProviderIs skips if the provider is not included in the supportedProviders.
|
||||
func SkipUnlessProviderIs(supportedProviders ...string) {
|
||||
if !ProviderIs(supportedProviders...) {
|
||||
skipInternalf(1, "Only supported for providers %v (not %s)", supportedProviders, TestContext.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessMultizone skips if the cluster does not have multizone.
|
||||
func SkipUnlessMultizone(c clientset.Interface) {
|
||||
zones, err := GetClusterZones(c)
|
||||
if err != nil {
|
||||
skipInternalf(1, "Error listing cluster zones")
|
||||
}
|
||||
if zones.Len() <= 1 {
|
||||
skipInternalf(1, "Requires more than one zone")
|
||||
}
|
||||
}
|
||||
|
||||
// SkipIfMultizone skips if the cluster has multizone.
|
||||
func SkipIfMultizone(c clientset.Interface) {
|
||||
zones, err := GetClusterZones(c)
|
||||
if err != nil {
|
||||
skipInternalf(1, "Error listing cluster zones")
|
||||
}
|
||||
if zones.Len() > 1 {
|
||||
skipInternalf(1, "Requires at most one zone")
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessMasterOSDistroIs skips if the master OS distro is not included in the supportedMasterOsDistros.
|
||||
func SkipUnlessMasterOSDistroIs(supportedMasterOsDistros ...string) {
|
||||
if !MasterOSDistroIs(supportedMasterOsDistros...) {
|
||||
skipInternalf(1, "Only supported for master OS distro %v (not %s)", supportedMasterOsDistros, TestContext.MasterOSDistro)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessNodeOSDistroIs skips if the node OS distro is not included in the supportedNodeOsDistros.
|
||||
func SkipUnlessNodeOSDistroIs(supportedNodeOsDistros ...string) {
|
||||
if !NodeOSDistroIs(supportedNodeOsDistros...) {
|
||||
skipInternalf(1, "Only supported for node OS distro %v (not %s)", supportedNodeOsDistros, TestContext.NodeOSDistro)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipIfNodeOSDistroIs skips if the node OS distro is included in the unsupportedNodeOsDistros.
|
||||
func SkipIfNodeOSDistroIs(unsupportedNodeOsDistros ...string) {
|
||||
if NodeOSDistroIs(unsupportedNodeOsDistros...) {
|
||||
skipInternalf(1, "Not supported for node OS distro %v (is %s)", unsupportedNodeOsDistros, TestContext.NodeOSDistro)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessServerVersionGTE skips if the server version is less than v.
|
||||
func SkipUnlessServerVersionGTE(v *utilversion.Version, c discovery.ServerVersionInterface) {
|
||||
gte, err := serverVersionGTE(v, c)
|
||||
if err != nil {
|
||||
Failf("Failed to get server version: %v", err)
|
||||
}
|
||||
if !gte {
|
||||
skipInternalf(1, "Not supported for server versions before %q", v)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipUnlessSSHKeyPresent skips if no SSH key is found.
|
||||
func SkipUnlessSSHKeyPresent() {
|
||||
if _, err := e2essh.GetSigner(TestContext.Provider); err != nil {
|
||||
skipInternalf(1, "No SSH Key for provider %s: '%v'", TestContext.Provider, err)
|
||||
}
|
||||
}
|
||||
|
||||
// serverVersionGTE returns true if v is greater than or equal to the server
|
||||
// version.
|
||||
//
|
||||
// TODO(18726): This should be incorporated into client.VersionInterface.
|
||||
func serverVersionGTE(v *utilversion.Version, c discovery.ServerVersionInterface) (bool, error) {
|
||||
serverVersion, err := c.ServerVersion()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Unable to get server version: %v", err)
|
||||
}
|
||||
sv, err := utilversion.ParseSemantic(serverVersion.GitVersion)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Unable to parse server version %q: %v", serverVersion.GitVersion, err)
|
||||
}
|
||||
return sv.AtLeast(v), nil
|
||||
}
|
||||
|
||||
// AppArmorDistros are distros with AppArmor support
|
||||
var AppArmorDistros = []string{"gci", "ubuntu"}
|
||||
|
||||
// SkipIfAppArmorNotSupported skips if the AppArmor is not supported by the node OS distro.
|
||||
func SkipIfAppArmorNotSupported() {
|
||||
SkipUnlessNodeOSDistroIs(AppArmorDistros...)
|
||||
}
|
16
vendor/k8s.io/kubernetes/test/e2e/framework/ssh/ssh.go
generated
vendored
16
vendor/k8s.io/kubernetes/test/e2e/framework/ssh/ssh.go
generated
vendored
@ -105,17 +105,13 @@ func NodeSSHHosts(c clientset.Interface) ([]string, error) {
|
||||
nodelist := waitListSchedulableNodesOrDie(c)
|
||||
|
||||
hosts := nodeAddresses(nodelist, v1.NodeExternalIP)
|
||||
// If ExternalIPs aren't set, assume the test programs can reach the
|
||||
// InternalIP. Simplified exception logic here assumes that the hosts will
|
||||
// either all have ExternalIP or none will. Simplifies handling here and
|
||||
// should be adequate since the setting of the external IPs is provider
|
||||
// specific: they should either all have them or none of them will.
|
||||
if len(hosts) == 0 {
|
||||
// If ExternalIPs aren't available for all nodes, try falling back to the InternalIPs.
|
||||
if len(hosts) < len(nodelist.Items) {
|
||||
e2elog.Logf("No external IP address on nodes, falling back to internal IPs")
|
||||
hosts = nodeAddresses(nodelist, v1.NodeInternalIP)
|
||||
}
|
||||
|
||||
// Error if any node didn't have an external/internal IP.
|
||||
// Error if neither External nor Internal IPs weren't available for all nodes.
|
||||
if len(hosts) != len(nodelist.Items) {
|
||||
return hosts, fmt.Errorf(
|
||||
"only found %d IPs on nodes, but found %d nodes. Nodelist: %v",
|
||||
@ -166,7 +162,7 @@ func SSH(cmd, host, provider string) (Result, error) {
|
||||
}
|
||||
|
||||
if bastion := os.Getenv("KUBE_SSH_BASTION"); len(bastion) > 0 {
|
||||
stdout, stderr, code, err := RunSSHCommandViaBastion(cmd, result.User, bastion, host, signer)
|
||||
stdout, stderr, code, err := runSSHCommandViaBastion(cmd, result.User, bastion, host, signer)
|
||||
result.Stdout = stdout
|
||||
result.Stderr = stderr
|
||||
result.Code = code
|
||||
@ -181,11 +177,11 @@ func SSH(cmd, host, provider string) (Result, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// RunSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
|
||||
// runSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
|
||||
// host as specific user, along with any SSH-level error. It uses an SSH proxy to connect
|
||||
// to bastion, then via that tunnel connects to the remote host. Similar to
|
||||
// sshutil.RunSSHCommand but scoped to the needs of the test infrastructure.
|
||||
func RunSSHCommandViaBastion(cmd, user, bastion, host string, signer ssh.Signer) (string, string, int, error) {
|
||||
func runSSHCommandViaBastion(cmd, user, bastion, host string, signer ssh.Signer) (string, string, int, error) {
|
||||
// Setup the config, dial the server, and open a session.
|
||||
config := &ssh.ClientConfig{
|
||||
User: user,
|
||||
|
894
vendor/k8s.io/kubernetes/test/e2e/framework/statefulset_utils.go
generated
vendored
894
vendor/k8s.io/kubernetes/test/e2e/framework/statefulset_utils.go
generated
vendored
@ -1,894 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
appsV1beta2 "k8s.io/api/apps/v1beta2"
|
||||
"k8s.io/api/core/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
"k8s.io/kubernetes/test/e2e/manifest"
|
||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||
)
|
||||
|
||||
const (
|
||||
// StatefulSetPoll is a poll interval for StatefulSet tests
|
||||
StatefulSetPoll = 10 * time.Second
|
||||
// StatefulSetTimeout is a timeout interval for StatefulSet operations
|
||||
StatefulSetTimeout = 10 * time.Minute
|
||||
// StatefulPodTimeout is a timeout for stateful pods to change state
|
||||
StatefulPodTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// CreateStatefulSetService creates a Headless Service with Name name and Selector set to match labels.
|
||||
func CreateStatefulSetService(name string, labels map[string]string) *v1.Service {
|
||||
headlessService := &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: labels,
|
||||
},
|
||||
}
|
||||
headlessService.Spec.Ports = []v1.ServicePort{
|
||||
{Port: 80, Name: "http", Protocol: v1.ProtocolTCP},
|
||||
}
|
||||
headlessService.Spec.ClusterIP = "None"
|
||||
return headlessService
|
||||
}
|
||||
|
||||
// StatefulSetTester is a struct that contains utility methods for testing StatefulSet related functionality. It uses a
|
||||
// clientset.Interface to communicate with the API server.
|
||||
type StatefulSetTester struct {
|
||||
c clientset.Interface
|
||||
}
|
||||
|
||||
// NewStatefulSetTester creates a StatefulSetTester that uses c to interact with the API server.
|
||||
func NewStatefulSetTester(c clientset.Interface) *StatefulSetTester {
|
||||
return &StatefulSetTester{c}
|
||||
}
|
||||
|
||||
// GetStatefulSet gets the StatefulSet named name in namespace.
|
||||
func (s *StatefulSetTester) GetStatefulSet(namespace, name string) *apps.StatefulSet {
|
||||
ss, err := s.c.AppsV1().StatefulSets(namespace).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
Failf("Failed to get StatefulSet %s/%s: %v", namespace, name, err)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
// CreateStatefulSet creates a StatefulSet from the manifest at manifestPath in the Namespace ns using kubectl create.
|
||||
func (s *StatefulSetTester) CreateStatefulSet(manifestPath, ns string) *apps.StatefulSet {
|
||||
mkpath := func(file string) string {
|
||||
return filepath.Join(manifestPath, file)
|
||||
}
|
||||
|
||||
e2elog.Logf("Parsing statefulset from %v", mkpath("statefulset.yaml"))
|
||||
ss, err := manifest.StatefulSetFromManifest(mkpath("statefulset.yaml"), ns)
|
||||
ExpectNoError(err)
|
||||
e2elog.Logf("Parsing service from %v", mkpath("service.yaml"))
|
||||
svc, err := manifest.SvcFromManifest(mkpath("service.yaml"))
|
||||
ExpectNoError(err)
|
||||
|
||||
e2elog.Logf(fmt.Sprintf("creating " + ss.Name + " service"))
|
||||
_, err = s.c.CoreV1().Services(ns).Create(svc)
|
||||
ExpectNoError(err)
|
||||
|
||||
e2elog.Logf(fmt.Sprintf("creating statefulset %v/%v with %d replicas and selector %+v", ss.Namespace, ss.Name, *(ss.Spec.Replicas), ss.Spec.Selector))
|
||||
_, err = s.c.AppsV1().StatefulSets(ns).Create(ss)
|
||||
ExpectNoError(err)
|
||||
s.WaitForRunningAndReady(*ss.Spec.Replicas, ss)
|
||||
return ss
|
||||
}
|
||||
|
||||
// CheckMount checks that the mount at mountPath is valid for all Pods in ss.
|
||||
func (s *StatefulSetTester) CheckMount(ss *apps.StatefulSet, mountPath string) error {
|
||||
for _, cmd := range []string{
|
||||
// Print inode, size etc
|
||||
fmt.Sprintf("ls -idlh %v", mountPath),
|
||||
// Print subdirs
|
||||
fmt.Sprintf("find %v", mountPath),
|
||||
// Try writing
|
||||
fmt.Sprintf("touch %v", filepath.Join(mountPath, fmt.Sprintf("%v", time.Now().UnixNano()))),
|
||||
} {
|
||||
if err := s.ExecInStatefulPods(ss, cmd); err != nil {
|
||||
return fmt.Errorf("failed to execute %v, error: %v", cmd, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecInStatefulPods executes cmd in all Pods in ss. If a error occurs it is returned and cmd is not execute in any subsequent Pods.
|
||||
func (s *StatefulSetTester) ExecInStatefulPods(ss *apps.StatefulSet, cmd string) error {
|
||||
podList := s.GetPodList(ss)
|
||||
for _, statefulPod := range podList.Items {
|
||||
stdout, err := RunHostCmdWithRetries(statefulPod.Namespace, statefulPod.Name, cmd, StatefulSetPoll, StatefulPodTimeout)
|
||||
e2elog.Logf("stdout of %v on %v: %v", cmd, statefulPod.Name, stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckHostname verifies that all Pods in ss have the correct Hostname. If the returned error is not nil than verification failed.
|
||||
func (s *StatefulSetTester) CheckHostname(ss *apps.StatefulSet) error {
|
||||
cmd := "printf $(hostname)"
|
||||
podList := s.GetPodList(ss)
|
||||
for _, statefulPod := range podList.Items {
|
||||
hostname, err := RunHostCmdWithRetries(statefulPod.Namespace, statefulPod.Name, cmd, StatefulSetPoll, StatefulPodTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostname != statefulPod.Name {
|
||||
return fmt.Errorf("unexpected hostname (%s) and stateful pod name (%s) not equal", hostname, statefulPod.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Saturate waits for all Pods in ss to become Running and Ready.
|
||||
func (s *StatefulSetTester) Saturate(ss *apps.StatefulSet) {
|
||||
var i int32
|
||||
for i = 0; i < *(ss.Spec.Replicas); i++ {
|
||||
e2elog.Logf("Waiting for stateful pod at index %v to enter Running", i)
|
||||
s.WaitForRunning(i+1, i, ss)
|
||||
e2elog.Logf("Resuming stateful pod at index %v", i)
|
||||
s.ResumeNextPod(ss)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteStatefulPodAtIndex deletes the Pod with ordinal index in ss.
|
||||
func (s *StatefulSetTester) DeleteStatefulPodAtIndex(index int, ss *apps.StatefulSet) {
|
||||
name := getStatefulSetPodNameAtIndex(index, ss)
|
||||
noGrace := int64(0)
|
||||
if err := s.c.CoreV1().Pods(ss.Namespace).Delete(name, &metav1.DeleteOptions{GracePeriodSeconds: &noGrace}); err != nil {
|
||||
Failf("Failed to delete stateful pod %v for StatefulSet %v/%v: %v", name, ss.Namespace, ss.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyStatefulPodFunc is a func that examines a StatefulSetPod.
|
||||
type VerifyStatefulPodFunc func(*v1.Pod)
|
||||
|
||||
// VerifyPodAtIndex applies a visitor patter to the Pod at index in ss. verify is applied to the Pod to "visit" it.
|
||||
func (s *StatefulSetTester) VerifyPodAtIndex(index int, ss *apps.StatefulSet, verify VerifyStatefulPodFunc) {
|
||||
name := getStatefulSetPodNameAtIndex(index, ss)
|
||||
pod, err := s.c.CoreV1().Pods(ss.Namespace).Get(name, metav1.GetOptions{})
|
||||
ExpectNoError(err, fmt.Sprintf("Failed to get stateful pod %s for StatefulSet %s/%s", name, ss.Namespace, ss.Name))
|
||||
verify(pod)
|
||||
}
|
||||
|
||||
func getStatefulSetPodNameAtIndex(index int, ss *apps.StatefulSet) string {
|
||||
// TODO: we won't use "-index" as the name strategy forever,
|
||||
// pull the name out from an identity mapper.
|
||||
return fmt.Sprintf("%v-%v", ss.Name, index)
|
||||
}
|
||||
|
||||
// Scale scales ss to count replicas.
|
||||
func (s *StatefulSetTester) Scale(ss *apps.StatefulSet, count int32) (*apps.StatefulSet, error) {
|
||||
name := ss.Name
|
||||
ns := ss.Namespace
|
||||
|
||||
e2elog.Logf("Scaling statefulset %s to %d", name, count)
|
||||
ss = s.update(ns, name, func(ss *apps.StatefulSet) { *(ss.Spec.Replicas) = count })
|
||||
|
||||
var statefulPodList *v1.PodList
|
||||
pollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout, func() (bool, error) {
|
||||
statefulPodList = s.GetPodList(ss)
|
||||
if int32(len(statefulPodList.Items)) == count {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if pollErr != nil {
|
||||
unhealthy := []string{}
|
||||
for _, statefulPod := range statefulPodList.Items {
|
||||
delTs, phase, readiness := statefulPod.DeletionTimestamp, statefulPod.Status.Phase, podutil.IsPodReady(&statefulPod)
|
||||
if delTs != nil || phase != v1.PodRunning || !readiness {
|
||||
unhealthy = append(unhealthy, fmt.Sprintf("%v: deletion %v, phase %v, readiness %v", statefulPod.Name, delTs, phase, readiness))
|
||||
}
|
||||
}
|
||||
return ss, fmt.Errorf("Failed to scale statefulset to %d in %v. Remaining pods:\n%v", count, StatefulSetTimeout, unhealthy)
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// UpdateReplicas updates the replicas of ss to count.
|
||||
func (s *StatefulSetTester) UpdateReplicas(ss *apps.StatefulSet, count int32) {
|
||||
s.update(ss.Namespace, ss.Name, func(ss *apps.StatefulSet) { *(ss.Spec.Replicas) = count })
|
||||
}
|
||||
|
||||
// Restart scales ss to 0 and then back to its previous number of replicas.
|
||||
func (s *StatefulSetTester) Restart(ss *apps.StatefulSet) {
|
||||
oldReplicas := *(ss.Spec.Replicas)
|
||||
ss, err := s.Scale(ss, 0)
|
||||
ExpectNoError(err)
|
||||
// Wait for controller to report the desired number of Pods.
|
||||
// This way we know the controller has observed all Pod deletions
|
||||
// before we scale it back up.
|
||||
s.WaitForStatusReplicas(ss, 0)
|
||||
s.update(ss.Namespace, ss.Name, func(ss *apps.StatefulSet) { *(ss.Spec.Replicas) = oldReplicas })
|
||||
}
|
||||
|
||||
func (s *StatefulSetTester) update(ns, name string, update func(ss *apps.StatefulSet)) *apps.StatefulSet {
|
||||
for i := 0; i < 3; i++ {
|
||||
ss, err := s.c.AppsV1().StatefulSets(ns).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
Failf("failed to get statefulset %q: %v", name, err)
|
||||
}
|
||||
update(ss)
|
||||
ss, err = s.c.AppsV1().StatefulSets(ns).Update(ss)
|
||||
if err == nil {
|
||||
return ss
|
||||
}
|
||||
if !apierrs.IsConflict(err) && !apierrs.IsServerTimeout(err) {
|
||||
Failf("failed to update statefulset %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
Failf("too many retries draining statefulset %q", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodList gets the current Pods in ss.
|
||||
func (s *StatefulSetTester) GetPodList(ss *apps.StatefulSet) *v1.PodList {
|
||||
selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector)
|
||||
ExpectNoError(err)
|
||||
podList, err := s.c.CoreV1().Pods(ss.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
|
||||
ExpectNoError(err)
|
||||
return podList
|
||||
}
|
||||
|
||||
// ConfirmStatefulPodCount asserts that the current number of Pods in ss is count waiting up to timeout for ss to
|
||||
// to scale to count.
|
||||
func (s *StatefulSetTester) ConfirmStatefulPodCount(count int, ss *apps.StatefulSet, timeout time.Duration, hard bool) {
|
||||
start := time.Now()
|
||||
deadline := start.Add(timeout)
|
||||
for t := time.Now(); t.Before(deadline); t = time.Now() {
|
||||
podList := s.GetPodList(ss)
|
||||
statefulPodCount := len(podList.Items)
|
||||
if statefulPodCount != count {
|
||||
logPodStates(podList.Items)
|
||||
if hard {
|
||||
Failf("StatefulSet %v scaled unexpectedly scaled to %d -> %d replicas", ss.Name, count, len(podList.Items))
|
||||
} else {
|
||||
e2elog.Logf("StatefulSet %v has not reached scale %d, at %d", ss.Name, count, statefulPodCount)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
e2elog.Logf("Verifying statefulset %v doesn't scale past %d for another %+v", ss.Name, count, deadline.Sub(t))
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForRunning waits for numPodsRunning in ss to be Running and for the first
|
||||
// numPodsReady ordinals to be Ready.
|
||||
func (s *StatefulSetTester) WaitForRunning(numPodsRunning, numPodsReady int32, ss *apps.StatefulSet) {
|
||||
pollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout,
|
||||
func() (bool, error) {
|
||||
podList := s.GetPodList(ss)
|
||||
s.SortStatefulPods(podList)
|
||||
if int32(len(podList.Items)) < numPodsRunning {
|
||||
e2elog.Logf("Found %d stateful pods, waiting for %d", len(podList.Items), numPodsRunning)
|
||||
return false, nil
|
||||
}
|
||||
if int32(len(podList.Items)) > numPodsRunning {
|
||||
return false, fmt.Errorf("Too many pods scheduled, expected %d got %d", numPodsRunning, len(podList.Items))
|
||||
}
|
||||
for _, p := range podList.Items {
|
||||
shouldBeReady := getStatefulPodOrdinal(&p) < int(numPodsReady)
|
||||
isReady := podutil.IsPodReady(&p)
|
||||
desiredReadiness := shouldBeReady == isReady
|
||||
e2elog.Logf("Waiting for pod %v to enter %v - Ready=%v, currently %v - Ready=%v", p.Name, v1.PodRunning, shouldBeReady, p.Status.Phase, isReady)
|
||||
if p.Status.Phase != v1.PodRunning || !desiredReadiness {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if pollErr != nil {
|
||||
Failf("Failed waiting for pods to enter running: %v", pollErr)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForState periodically polls for the ss and its pods until the until function returns either true or an error
|
||||
func (s *StatefulSetTester) WaitForState(ss *apps.StatefulSet, until func(*apps.StatefulSet, *v1.PodList) (bool, error)) {
|
||||
pollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout,
|
||||
func() (bool, error) {
|
||||
ssGet, err := s.c.AppsV1().StatefulSets(ss.Namespace).Get(ss.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
podList := s.GetPodList(ssGet)
|
||||
return until(ssGet, podList)
|
||||
})
|
||||
if pollErr != nil {
|
||||
Failf("Failed waiting for state update: %v", pollErr)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForStatus waits for the StatefulSetStatus's ObservedGeneration to be greater than or equal to set's Generation.
|
||||
// The returned StatefulSet contains such a StatefulSetStatus
|
||||
func (s *StatefulSetTester) WaitForStatus(set *apps.StatefulSet) *apps.StatefulSet {
|
||||
s.WaitForState(set, func(set2 *apps.StatefulSet, pods *v1.PodList) (bool, error) {
|
||||
if set2.Status.ObservedGeneration >= set.Generation {
|
||||
set = set2
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return set
|
||||
}
|
||||
|
||||
// WaitForRunningAndReady waits for numStatefulPods in ss to be Running and Ready.
|
||||
func (s *StatefulSetTester) WaitForRunningAndReady(numStatefulPods int32, ss *apps.StatefulSet) {
|
||||
s.WaitForRunning(numStatefulPods, numStatefulPods, ss)
|
||||
}
|
||||
|
||||
// WaitForPodReady waits for the Pod named podName in set to exist and have a Ready condition.
|
||||
func (s *StatefulSetTester) WaitForPodReady(set *apps.StatefulSet, podName string) (*apps.StatefulSet, *v1.PodList) {
|
||||
var pods *v1.PodList
|
||||
s.WaitForState(set, func(set2 *apps.StatefulSet, pods2 *v1.PodList) (bool, error) {
|
||||
set = set2
|
||||
pods = pods2
|
||||
for i := range pods.Items {
|
||||
if pods.Items[i].Name == podName {
|
||||
return podutil.IsPodReady(&pods.Items[i]), nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return set, pods
|
||||
|
||||
}
|
||||
|
||||
// WaitForPodNotReady waist for the Pod named podName in set to exist and to not have a Ready condition.
|
||||
func (s *StatefulSetTester) WaitForPodNotReady(set *apps.StatefulSet, podName string) (*apps.StatefulSet, *v1.PodList) {
|
||||
var pods *v1.PodList
|
||||
s.WaitForState(set, func(set2 *apps.StatefulSet, pods2 *v1.PodList) (bool, error) {
|
||||
set = set2
|
||||
pods = pods2
|
||||
for i := range pods.Items {
|
||||
if pods.Items[i].Name == podName {
|
||||
return !podutil.IsPodReady(&pods.Items[i]), nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return set, pods
|
||||
|
||||
}
|
||||
|
||||
// WaitForRollingUpdate waits for all Pods in set to exist and have the correct revision and for the RollingUpdate to
|
||||
// complete. set must have a RollingUpdateStatefulSetStrategyType.
|
||||
func (s *StatefulSetTester) WaitForRollingUpdate(set *apps.StatefulSet) (*apps.StatefulSet, *v1.PodList) {
|
||||
var pods *v1.PodList
|
||||
if set.Spec.UpdateStrategy.Type != apps.RollingUpdateStatefulSetStrategyType {
|
||||
Failf("StatefulSet %s/%s attempt to wait for rolling update with updateStrategy %s",
|
||||
set.Namespace,
|
||||
set.Name,
|
||||
set.Spec.UpdateStrategy.Type)
|
||||
}
|
||||
s.WaitForState(set, func(set2 *apps.StatefulSet, pods2 *v1.PodList) (bool, error) {
|
||||
set = set2
|
||||
pods = pods2
|
||||
if len(pods.Items) < int(*set.Spec.Replicas) {
|
||||
return false, nil
|
||||
}
|
||||
if set.Status.UpdateRevision != set.Status.CurrentRevision {
|
||||
e2elog.Logf("Waiting for StatefulSet %s/%s to complete update",
|
||||
set.Namespace,
|
||||
set.Name,
|
||||
)
|
||||
s.SortStatefulPods(pods)
|
||||
for i := range pods.Items {
|
||||
if pods.Items[i].Labels[apps.StatefulSetRevisionLabel] != set.Status.UpdateRevision {
|
||||
e2elog.Logf("Waiting for Pod %s/%s to have revision %s update revision %s",
|
||||
pods.Items[i].Namespace,
|
||||
pods.Items[i].Name,
|
||||
set.Status.UpdateRevision,
|
||||
pods.Items[i].Labels[apps.StatefulSetRevisionLabel])
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return set, pods
|
||||
}
|
||||
|
||||
// WaitForPartitionedRollingUpdate waits for all Pods in set to exist and have the correct revision. set must have
|
||||
// a RollingUpdateStatefulSetStrategyType with a non-nil RollingUpdate and Partition. All Pods with ordinals less
|
||||
// than or equal to the Partition are expected to be at set's current revision. All other Pods are expected to be
|
||||
// at its update revision.
|
||||
func (s *StatefulSetTester) WaitForPartitionedRollingUpdate(set *apps.StatefulSet) (*apps.StatefulSet, *v1.PodList) {
|
||||
var pods *v1.PodList
|
||||
if set.Spec.UpdateStrategy.Type != apps.RollingUpdateStatefulSetStrategyType {
|
||||
Failf("StatefulSet %s/%s attempt to wait for partitioned update with updateStrategy %s",
|
||||
set.Namespace,
|
||||
set.Name,
|
||||
set.Spec.UpdateStrategy.Type)
|
||||
}
|
||||
if set.Spec.UpdateStrategy.RollingUpdate == nil || set.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
|
||||
Failf("StatefulSet %s/%s attempt to wait for partitioned update with nil RollingUpdate or nil Partition",
|
||||
set.Namespace,
|
||||
set.Name)
|
||||
}
|
||||
s.WaitForState(set, func(set2 *apps.StatefulSet, pods2 *v1.PodList) (bool, error) {
|
||||
set = set2
|
||||
pods = pods2
|
||||
partition := int(*set.Spec.UpdateStrategy.RollingUpdate.Partition)
|
||||
if len(pods.Items) < int(*set.Spec.Replicas) {
|
||||
return false, nil
|
||||
}
|
||||
if partition <= 0 && set.Status.UpdateRevision != set.Status.CurrentRevision {
|
||||
e2elog.Logf("Waiting for StatefulSet %s/%s to complete update",
|
||||
set.Namespace,
|
||||
set.Name,
|
||||
)
|
||||
s.SortStatefulPods(pods)
|
||||
for i := range pods.Items {
|
||||
if pods.Items[i].Labels[apps.StatefulSetRevisionLabel] != set.Status.UpdateRevision {
|
||||
e2elog.Logf("Waiting for Pod %s/%s to have revision %s update revision %s",
|
||||
pods.Items[i].Namespace,
|
||||
pods.Items[i].Name,
|
||||
set.Status.UpdateRevision,
|
||||
pods.Items[i].Labels[apps.StatefulSetRevisionLabel])
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
for i := int(*set.Spec.Replicas) - 1; i >= partition; i-- {
|
||||
if pods.Items[i].Labels[apps.StatefulSetRevisionLabel] != set.Status.UpdateRevision {
|
||||
e2elog.Logf("Waiting for Pod %s/%s to have revision %s update revision %s",
|
||||
pods.Items[i].Namespace,
|
||||
pods.Items[i].Name,
|
||||
set.Status.UpdateRevision,
|
||||
pods.Items[i].Labels[apps.StatefulSetRevisionLabel])
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return set, pods
|
||||
}
|
||||
|
||||
// WaitForRunningAndNotReady waits for numStatefulPods in ss to be Running and not Ready.
|
||||
func (s *StatefulSetTester) WaitForRunningAndNotReady(numStatefulPods int32, ss *apps.StatefulSet) {
|
||||
s.WaitForRunning(numStatefulPods, 0, ss)
|
||||
}
|
||||
|
||||
var httpProbe = &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
Path: "/index.html",
|
||||
Port: intstr.IntOrString{IntVal: 80},
|
||||
},
|
||||
},
|
||||
PeriodSeconds: 1,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 1,
|
||||
}
|
||||
|
||||
// SetHTTPProbe sets the pod template's ReadinessProbe for Nginx StatefulSet containers.
|
||||
// This probe can then be controlled with BreakHTTPProbe() and RestoreHTTPProbe().
|
||||
// Note that this cannot be used together with PauseNewPods().
|
||||
func (s *StatefulSetTester) SetHTTPProbe(ss *apps.StatefulSet) {
|
||||
ss.Spec.Template.Spec.Containers[0].ReadinessProbe = httpProbe
|
||||
}
|
||||
|
||||
// BreakHTTPProbe breaks the readiness probe for Nginx StatefulSet containers in ss.
|
||||
func (s *StatefulSetTester) BreakHTTPProbe(ss *apps.StatefulSet) error {
|
||||
path := httpProbe.HTTPGet.Path
|
||||
if path == "" {
|
||||
return fmt.Errorf("Path expected to be not empty: %v", path)
|
||||
}
|
||||
// Ignore 'mv' errors to make this idempotent.
|
||||
cmd := fmt.Sprintf("mv -v /usr/share/nginx/html%v /tmp/ || true", path)
|
||||
return s.ExecInStatefulPods(ss, cmd)
|
||||
}
|
||||
|
||||
// BreakPodHTTPProbe breaks the readiness probe for Nginx StatefulSet containers in one pod.
|
||||
func (s *StatefulSetTester) BreakPodHTTPProbe(ss *apps.StatefulSet, pod *v1.Pod) error {
|
||||
path := httpProbe.HTTPGet.Path
|
||||
if path == "" {
|
||||
return fmt.Errorf("Path expected to be not empty: %v", path)
|
||||
}
|
||||
// Ignore 'mv' errors to make this idempotent.
|
||||
cmd := fmt.Sprintf("mv -v /usr/share/nginx/html%v /tmp/ || true", path)
|
||||
stdout, err := RunHostCmdWithRetries(pod.Namespace, pod.Name, cmd, StatefulSetPoll, StatefulPodTimeout)
|
||||
e2elog.Logf("stdout of %v on %v: %v", cmd, pod.Name, stdout)
|
||||
return err
|
||||
}
|
||||
|
||||
// RestoreHTTPProbe restores the readiness probe for Nginx StatefulSet containers in ss.
|
||||
func (s *StatefulSetTester) RestoreHTTPProbe(ss *apps.StatefulSet) error {
|
||||
path := httpProbe.HTTPGet.Path
|
||||
if path == "" {
|
||||
return fmt.Errorf("Path expected to be not empty: %v", path)
|
||||
}
|
||||
// Ignore 'mv' errors to make this idempotent.
|
||||
cmd := fmt.Sprintf("mv -v /tmp%v /usr/share/nginx/html/ || true", path)
|
||||
return s.ExecInStatefulPods(ss, cmd)
|
||||
}
|
||||
|
||||
// RestorePodHTTPProbe restores the readiness probe for Nginx StatefulSet containers in pod.
|
||||
func (s *StatefulSetTester) RestorePodHTTPProbe(ss *apps.StatefulSet, pod *v1.Pod) error {
|
||||
path := httpProbe.HTTPGet.Path
|
||||
if path == "" {
|
||||
return fmt.Errorf("Path expected to be not empty: %v", path)
|
||||
}
|
||||
// Ignore 'mv' errors to make this idempotent.
|
||||
cmd := fmt.Sprintf("mv -v /tmp%v /usr/share/nginx/html/ || true", path)
|
||||
stdout, err := RunHostCmdWithRetries(pod.Namespace, pod.Name, cmd, StatefulSetPoll, StatefulPodTimeout)
|
||||
e2elog.Logf("stdout of %v on %v: %v", cmd, pod.Name, stdout)
|
||||
return err
|
||||
}
|
||||
|
||||
var pauseProbe = &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
Exec: &v1.ExecAction{Command: []string{"test", "-f", "/data/statefulset-continue"}},
|
||||
},
|
||||
PeriodSeconds: 1,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 1,
|
||||
}
|
||||
|
||||
func hasPauseProbe(pod *v1.Pod) bool {
|
||||
probe := pod.Spec.Containers[0].ReadinessProbe
|
||||
return probe != nil && reflect.DeepEqual(probe.Exec.Command, pauseProbe.Exec.Command)
|
||||
}
|
||||
|
||||
// PauseNewPods adds an always-failing ReadinessProbe to the StatefulSet PodTemplate.
|
||||
// This causes all newly-created Pods to stay Unready until they are manually resumed
|
||||
// with ResumeNextPod().
|
||||
// Note that this cannot be used together with SetHTTPProbe().
|
||||
func (s *StatefulSetTester) PauseNewPods(ss *apps.StatefulSet) {
|
||||
ss.Spec.Template.Spec.Containers[0].ReadinessProbe = pauseProbe
|
||||
}
|
||||
|
||||
// ResumeNextPod allows the next Pod in the StatefulSet to continue by removing the ReadinessProbe
|
||||
// added by PauseNewPods(), if it's still there.
|
||||
// It fails the test if it finds any pods that are not in phase Running,
|
||||
// or if it finds more than one paused Pod existing at the same time.
|
||||
// This is a no-op if there are no paused pods.
|
||||
func (s *StatefulSetTester) ResumeNextPod(ss *apps.StatefulSet) {
|
||||
podList := s.GetPodList(ss)
|
||||
resumedPod := ""
|
||||
for _, pod := range podList.Items {
|
||||
if pod.Status.Phase != v1.PodRunning {
|
||||
Failf("Found pod in phase %q, cannot resume", pod.Status.Phase)
|
||||
}
|
||||
if podutil.IsPodReady(&pod) || !hasPauseProbe(&pod) {
|
||||
continue
|
||||
}
|
||||
if resumedPod != "" {
|
||||
Failf("Found multiple paused stateful pods: %v and %v", pod.Name, resumedPod)
|
||||
}
|
||||
_, err := RunHostCmdWithRetries(pod.Namespace, pod.Name, "dd if=/dev/zero of=/data/statefulset-continue bs=1 count=1 conv=fsync", StatefulSetPoll, StatefulPodTimeout)
|
||||
ExpectNoError(err)
|
||||
e2elog.Logf("Resumed pod %v", pod.Name)
|
||||
resumedPod = pod.Name
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForStatusReadyReplicas waits for the ss.Status.ReadyReplicas to be equal to expectedReplicas
|
||||
func (s *StatefulSetTester) WaitForStatusReadyReplicas(ss *apps.StatefulSet, expectedReplicas int32) {
|
||||
e2elog.Logf("Waiting for statefulset status.replicas updated to %d", expectedReplicas)
|
||||
|
||||
ns, name := ss.Namespace, ss.Name
|
||||
pollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout,
|
||||
func() (bool, error) {
|
||||
ssGet, err := s.c.AppsV1().StatefulSets(ns).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ssGet.Status.ObservedGeneration < ss.Generation {
|
||||
return false, nil
|
||||
}
|
||||
if ssGet.Status.ReadyReplicas != expectedReplicas {
|
||||
e2elog.Logf("Waiting for stateful set status.readyReplicas to become %d, currently %d", expectedReplicas, ssGet.Status.ReadyReplicas)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if pollErr != nil {
|
||||
Failf("Failed waiting for stateful set status.readyReplicas updated to %d: %v", expectedReplicas, pollErr)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForStatusReplicas waits for the ss.Status.Replicas to be equal to expectedReplicas
|
||||
func (s *StatefulSetTester) WaitForStatusReplicas(ss *apps.StatefulSet, expectedReplicas int32) {
|
||||
e2elog.Logf("Waiting for statefulset status.replicas updated to %d", expectedReplicas)
|
||||
|
||||
ns, name := ss.Namespace, ss.Name
|
||||
pollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout,
|
||||
func() (bool, error) {
|
||||
ssGet, err := s.c.AppsV1().StatefulSets(ns).Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ssGet.Status.ObservedGeneration < ss.Generation {
|
||||
return false, nil
|
||||
}
|
||||
if ssGet.Status.Replicas != expectedReplicas {
|
||||
e2elog.Logf("Waiting for stateful set status.replicas to become %d, currently %d", expectedReplicas, ssGet.Status.Replicas)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if pollErr != nil {
|
||||
Failf("Failed waiting for stateful set status.replicas updated to %d: %v", expectedReplicas, pollErr)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckServiceName asserts that the ServiceName for ss is equivalent to expectedServiceName.
|
||||
func (s *StatefulSetTester) CheckServiceName(ss *apps.StatefulSet, expectedServiceName string) error {
|
||||
e2elog.Logf("Checking if statefulset spec.serviceName is %s", expectedServiceName)
|
||||
|
||||
if expectedServiceName != ss.Spec.ServiceName {
|
||||
return fmt.Errorf("Wrong service name governing statefulset. Expected %s got %s",
|
||||
expectedServiceName, ss.Spec.ServiceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SortStatefulPods sorts pods by their ordinals
|
||||
func (s *StatefulSetTester) SortStatefulPods(pods *v1.PodList) {
|
||||
sort.Sort(statefulPodsByOrdinal(pods.Items))
|
||||
}
|
||||
|
||||
// DeleteAllStatefulSets deletes all StatefulSet API Objects in Namespace ns.
|
||||
func DeleteAllStatefulSets(c clientset.Interface, ns string) {
|
||||
sst := &StatefulSetTester{c: c}
|
||||
ssList, err := c.AppsV1().StatefulSets(ns).List(metav1.ListOptions{LabelSelector: labels.Everything().String()})
|
||||
ExpectNoError(err)
|
||||
|
||||
// Scale down each statefulset, then delete it completely.
|
||||
// Deleting a pvc without doing this will leak volumes, #25101.
|
||||
errList := []string{}
|
||||
for i := range ssList.Items {
|
||||
ss := &ssList.Items[i]
|
||||
var err error
|
||||
if ss, err = sst.Scale(ss, 0); err != nil {
|
||||
errList = append(errList, fmt.Sprintf("%v", err))
|
||||
}
|
||||
sst.WaitForStatusReplicas(ss, 0)
|
||||
e2elog.Logf("Deleting statefulset %v", ss.Name)
|
||||
// Use OrphanDependents=false so it's deleted synchronously.
|
||||
// We already made sure the Pods are gone inside Scale().
|
||||
if err := c.AppsV1().StatefulSets(ss.Namespace).Delete(ss.Name, &metav1.DeleteOptions{OrphanDependents: new(bool)}); err != nil {
|
||||
errList = append(errList, fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// pvs are global, so we need to wait for the exact ones bound to the statefulset pvcs.
|
||||
pvNames := sets.NewString()
|
||||
// TODO: Don't assume all pvcs in the ns belong to a statefulset
|
||||
pvcPollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout, func() (bool, error) {
|
||||
pvcList, err := c.CoreV1().PersistentVolumeClaims(ns).List(metav1.ListOptions{LabelSelector: labels.Everything().String()})
|
||||
if err != nil {
|
||||
e2elog.Logf("WARNING: Failed to list pvcs, retrying %v", err)
|
||||
return false, nil
|
||||
}
|
||||
for _, pvc := range pvcList.Items {
|
||||
pvNames.Insert(pvc.Spec.VolumeName)
|
||||
// TODO: Double check that there are no pods referencing the pvc
|
||||
e2elog.Logf("Deleting pvc: %v with volume %v", pvc.Name, pvc.Spec.VolumeName)
|
||||
if err := c.CoreV1().PersistentVolumeClaims(ns).Delete(pvc.Name, nil); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if pvcPollErr != nil {
|
||||
errList = append(errList, fmt.Sprintf("Timeout waiting for pvc deletion."))
|
||||
}
|
||||
|
||||
pollErr := wait.PollImmediate(StatefulSetPoll, StatefulSetTimeout, func() (bool, error) {
|
||||
pvList, err := c.CoreV1().PersistentVolumes().List(metav1.ListOptions{LabelSelector: labels.Everything().String()})
|
||||
if err != nil {
|
||||
e2elog.Logf("WARNING: Failed to list pvs, retrying %v", err)
|
||||
return false, nil
|
||||
}
|
||||
waitingFor := []string{}
|
||||
for _, pv := range pvList.Items {
|
||||
if pvNames.Has(pv.Name) {
|
||||
waitingFor = append(waitingFor, fmt.Sprintf("%v: %+v", pv.Name, pv.Status))
|
||||
}
|
||||
}
|
||||
if len(waitingFor) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
e2elog.Logf("Still waiting for pvs of statefulset to disappear:\n%v", strings.Join(waitingFor, "\n"))
|
||||
return false, nil
|
||||
})
|
||||
if pollErr != nil {
|
||||
errList = append(errList, fmt.Sprintf("Timeout waiting for pv provisioner to delete pvs, this might mean the test leaked pvs."))
|
||||
}
|
||||
if len(errList) != 0 {
|
||||
ExpectNoError(fmt.Errorf("%v", strings.Join(errList, "\n")))
|
||||
}
|
||||
}
|
||||
|
||||
// NewStatefulSetPVC returns a PersistentVolumeClaim named name, for testing StatefulSets.
|
||||
func NewStatefulSetPVC(name string) v1.PersistentVolumeClaim {
|
||||
return v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStatefulSet creates a new NGINX StatefulSet for testing. The StatefulSet is named name, is in namespace ns,
|
||||
// statefulPodsMounts are the mounts that will be backed by PVs. podsMounts are the mounts that are mounted directly
|
||||
// to the Pod. labels are the labels that will be usd for the StatefulSet selector.
|
||||
func NewStatefulSet(name, ns, governingSvcName string, replicas int32, statefulPodMounts []v1.VolumeMount, podMounts []v1.VolumeMount, labels map[string]string) *apps.StatefulSet {
|
||||
mounts := append(statefulPodMounts, podMounts...)
|
||||
claims := []v1.PersistentVolumeClaim{}
|
||||
for _, m := range statefulPodMounts {
|
||||
claims = append(claims, NewStatefulSetPVC(m.Name))
|
||||
}
|
||||
|
||||
vols := []v1.Volume{}
|
||||
for _, m := range podMounts {
|
||||
vols = append(vols, v1.Volume{
|
||||
Name: m.Name,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: fmt.Sprintf("/tmp/%v", m.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &apps.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StatefulSet",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: apps.StatefulSetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: labels,
|
||||
},
|
||||
Replicas: func(i int32) *int32 { return &i }(replicas),
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: imageutils.GetE2EImage(imageutils.Nginx),
|
||||
VolumeMounts: mounts,
|
||||
SecurityContext: &v1.SecurityContext{},
|
||||
},
|
||||
},
|
||||
Volumes: vols,
|
||||
},
|
||||
},
|
||||
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
|
||||
VolumeClaimTemplates: claims,
|
||||
ServiceName: governingSvcName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStatefulSetScale creates a new StatefulSet scale subresource and returns it
|
||||
func NewStatefulSetScale(ss *apps.StatefulSet) *appsV1beta2.Scale {
|
||||
return &appsV1beta2.Scale{
|
||||
// TODO: Create a variant of ObjectMeta type that only contains the fields below.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ss.Name,
|
||||
Namespace: ss.Namespace,
|
||||
},
|
||||
Spec: appsV1beta2.ScaleSpec{
|
||||
Replicas: *(ss.Spec.Replicas),
|
||||
},
|
||||
Status: appsV1beta2.ScaleStatus{
|
||||
Replicas: ss.Status.Replicas,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var statefulPodRegex = regexp.MustCompile("(.*)-([0-9]+)$")
|
||||
|
||||
func getStatefulPodOrdinal(pod *v1.Pod) int {
|
||||
ordinal := -1
|
||||
subMatches := statefulPodRegex.FindStringSubmatch(pod.Name)
|
||||
if len(subMatches) < 3 {
|
||||
return ordinal
|
||||
}
|
||||
if i, err := strconv.ParseInt(subMatches[2], 10, 32); err == nil {
|
||||
ordinal = int(i)
|
||||
}
|
||||
return ordinal
|
||||
}
|
||||
|
||||
type statefulPodsByOrdinal []v1.Pod
|
||||
|
||||
func (sp statefulPodsByOrdinal) Len() int {
|
||||
return len(sp)
|
||||
}
|
||||
|
||||
func (sp statefulPodsByOrdinal) Swap(i, j int) {
|
||||
sp[i], sp[j] = sp[j], sp[i]
|
||||
}
|
||||
|
||||
func (sp statefulPodsByOrdinal) Less(i, j int) bool {
|
||||
return getStatefulPodOrdinal(&sp[i]) < getStatefulPodOrdinal(&sp[j])
|
||||
}
|
||||
|
||||
type updateStatefulSetFunc func(*apps.StatefulSet)
|
||||
|
||||
// UpdateStatefulSetWithRetries updates statfulset template with retries.
|
||||
func UpdateStatefulSetWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateStatefulSetFunc) (statefulSet *apps.StatefulSet, err error) {
|
||||
statefulSets := c.AppsV1().StatefulSets(namespace)
|
||||
var updateErr error
|
||||
pollErr := wait.Poll(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
|
||||
if statefulSet, err = statefulSets.Get(name, metav1.GetOptions{}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Apply the update, then attempt to push it to the apiserver.
|
||||
applyUpdate(statefulSet)
|
||||
if statefulSet, err = statefulSets.Update(statefulSet); err == nil {
|
||||
e2elog.Logf("Updating stateful set %s", name)
|
||||
return true, nil
|
||||
}
|
||||
updateErr = err
|
||||
return false, nil
|
||||
})
|
||||
if pollErr == wait.ErrWaitTimeout {
|
||||
pollErr = fmt.Errorf("couldn't apply the provided updated to stateful set %q: %v", name, updateErr)
|
||||
}
|
||||
return statefulSet, pollErr
|
||||
}
|
86
vendor/k8s.io/kubernetes/test/e2e/framework/suites.go
generated
vendored
Normal file
86
vendor/k8s.io/kubernetes/test/e2e/framework/suites.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2019 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"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
|
||||
e2emetrics "k8s.io/kubernetes/test/e2e/framework/metrics"
|
||||
)
|
||||
|
||||
// CleanupSuite is the boilerplate that can be used after tests on ginkgo were run, on the SynchronizedAfterSuite step.
|
||||
// Similar to SynchronizedBeforeSuite, we want to run some operations only once (such as collecting cluster logs).
|
||||
// Here, the order of functions is reversed; first, the function which runs everywhere,
|
||||
// and then the function that only runs on the first Ginkgo node.
|
||||
func CleanupSuite() {
|
||||
// Run on all Ginkgo nodes
|
||||
Logf("Running AfterSuite actions on all nodes")
|
||||
RunCleanupActions()
|
||||
}
|
||||
|
||||
// AfterSuiteActions are actions that are run on ginkgo's SynchronizedAfterSuite
|
||||
func AfterSuiteActions() {
|
||||
// Run only Ginkgo on node 1
|
||||
Logf("Running AfterSuite actions on node 1")
|
||||
if TestContext.ReportDir != "" {
|
||||
CoreDump(TestContext.ReportDir)
|
||||
}
|
||||
if TestContext.GatherSuiteMetricsAfterTest {
|
||||
if err := gatherTestSuiteMetrics(); err != nil {
|
||||
Logf("Error gathering metrics: %v", err)
|
||||
}
|
||||
}
|
||||
if TestContext.NodeKiller.Enabled {
|
||||
close(TestContext.NodeKiller.NodeKillerStopCh)
|
||||
}
|
||||
}
|
||||
|
||||
func gatherTestSuiteMetrics() error {
|
||||
Logf("Gathering metrics")
|
||||
c, err := LoadClientset()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading client: %v", err)
|
||||
}
|
||||
|
||||
// Grab metrics for apiserver, scheduler, controller-manager, kubelet (for non-kubemark case) and cluster autoscaler (optionally).
|
||||
grabber, err := e2emetrics.NewMetricsGrabber(c, nil, !ProviderIs("kubemark"), true, true, true, TestContext.IncludeClusterAutoscalerMetrics)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create MetricsGrabber: %v", err)
|
||||
}
|
||||
|
||||
received, err := grabber.Grab()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to grab metrics: %v", err)
|
||||
}
|
||||
|
||||
metricsForE2E := (*e2emetrics.ComponentCollection)(&received)
|
||||
metricsJSON := metricsForE2E.PrintJSON()
|
||||
if TestContext.ReportDir != "" {
|
||||
filePath := path.Join(TestContext.ReportDir, "MetricsForE2ESuite_"+time.Now().Format(time.RFC3339)+".json")
|
||||
if err := ioutil.WriteFile(filePath, []byte(metricsJSON), 0644); err != nil {
|
||||
return fmt.Errorf("error writing to %q: %v", filePath, err)
|
||||
}
|
||||
} else {
|
||||
Logf("\n\nTest Suite Metrics:\n%s\n", metricsJSON)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
226
vendor/k8s.io/kubernetes/test/e2e/framework/test_context.go
generated
vendored
226
vendor/k8s.io/kubernetes/test/e2e/framework/test_context.go
generated
vendored
@ -33,7 +33,6 @@ import (
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
"k8s.io/klog"
|
||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -152,8 +151,6 @@ type TestContextType struct {
|
||||
NodeTestContextType
|
||||
// Monitoring solution that is used in current cluster.
|
||||
ClusterMonitoringMode string
|
||||
// Separate Prometheus monitoring deployed in cluster
|
||||
EnablePrometheusMonitoring bool
|
||||
|
||||
// Indicates what path the kubernetes-anywhere is installed on
|
||||
KubernetesAnywherePath string
|
||||
@ -161,8 +158,17 @@ type TestContextType struct {
|
||||
// The DNS Domain of the cluster.
|
||||
ClusterDNSDomain string
|
||||
|
||||
// The configration of NodeKiller.
|
||||
// The configuration of NodeKiller.
|
||||
NodeKiller NodeKillerConfig
|
||||
|
||||
// The Default IP Family of the cluster ("ipv4" or "ipv6")
|
||||
IPFamily string
|
||||
|
||||
// NonblockingTaints is the comma-delimeted string given by the user to specify taints which should not stop the test framework from running tests.
|
||||
NonblockingTaints string
|
||||
|
||||
// ProgressReportURL is the URL which progress updates will be posted to as tests complete. If empty, no updates are sent.
|
||||
ProgressReportURL string
|
||||
}
|
||||
|
||||
// NodeKillerConfig describes configuration of NodeKiller -- a utility to
|
||||
@ -180,6 +186,8 @@ type NodeKillerConfig struct {
|
||||
JitterFactor float64
|
||||
// SimulatedDowntime is a duration between node is killed and recreated.
|
||||
SimulatedDowntime time.Duration
|
||||
// NodeKillerStopCh is a channel that is used to notify NodeKiller to stop killing nodes.
|
||||
NodeKillerStopCh chan struct{}
|
||||
}
|
||||
|
||||
// NodeTestContextType is part of TestContextType, it is shared by all node e2e test.
|
||||
@ -229,8 +237,23 @@ type CloudConfig struct {
|
||||
// TestContext should be used by all tests to access common context data.
|
||||
var TestContext TestContextType
|
||||
|
||||
// ClusterIsIPv6 returns true if the cluster is IPv6
|
||||
func (tc TestContextType) ClusterIsIPv6() bool {
|
||||
return tc.IPFamily == "ipv6"
|
||||
}
|
||||
|
||||
// RegisterCommonFlags registers flags common to all e2e test suites.
|
||||
func RegisterCommonFlags() {
|
||||
// The flag set can be flag.CommandLine (if desired) or a custom
|
||||
// flag set that then gets passed to viperconfig.ViperizeFlags.
|
||||
//
|
||||
// The other Register*Flags methods below can be used to add more
|
||||
// test-specific flags. However, those settings then get added
|
||||
// regardless whether the test is actually in the test suite.
|
||||
//
|
||||
// For tests that have been converted to registering their
|
||||
// options themselves, copy flags from test/e2e/framework/config
|
||||
// as shown in HandleFlags.
|
||||
func RegisterCommonFlags(flags *flag.FlagSet) {
|
||||
// Turn on verbose by default to get spec names
|
||||
config.DefaultReporterConfig.Verbose = true
|
||||
|
||||
@ -240,118 +263,95 @@ func RegisterCommonFlags() {
|
||||
// Randomize specs as well as suites
|
||||
config.GinkgoConfig.RandomizeAllSpecs = true
|
||||
|
||||
flag.StringVar(&TestContext.GatherKubeSystemResourceUsageData, "gather-resource-usage", "false", "If set to 'true' or 'all' framework will be monitoring resource usage of system all add-ons in (some) e2e tests, if set to 'master' framework will be monitoring master node only, if set to 'none' of 'false' monitoring will be turned off.")
|
||||
flag.BoolVar(&TestContext.GatherLogsSizes, "gather-logs-sizes", false, "If set to true framework will be monitoring logs sizes on all machines running e2e tests.")
|
||||
flag.IntVar(&TestContext.MaxNodesToGather, "max-nodes-to-gather-from", 20, "The maximum number of nodes to gather extended info from on test failure.")
|
||||
flag.StringVar(&TestContext.GatherMetricsAfterTest, "gather-metrics-at-teardown", "false", "If set to 'true' framework will gather metrics from all components after each test. If set to 'master' only master component metrics would be gathered.")
|
||||
flag.BoolVar(&TestContext.GatherSuiteMetricsAfterTest, "gather-suite-metrics-at-teardown", false, "If set to true framwork will gather metrics from all components after the whole test suite completes.")
|
||||
flag.BoolVar(&TestContext.AllowGatheringProfiles, "allow-gathering-profiles", true, "If set to true framework will allow to gather CPU/memory allocation pprof profiles from the master.")
|
||||
flag.BoolVar(&TestContext.IncludeClusterAutoscalerMetrics, "include-cluster-autoscaler", false, "If set to true, framework will include Cluster Autoscaler when gathering metrics.")
|
||||
flag.StringVar(&TestContext.OutputPrintType, "output-print-type", "json", "Format in which summaries should be printed: 'hr' for human readable, 'json' for JSON ones.")
|
||||
flag.BoolVar(&TestContext.DumpLogsOnFailure, "dump-logs-on-failure", true, "If set to true test will dump data about the namespace in which test was running.")
|
||||
flag.BoolVar(&TestContext.DisableLogDump, "disable-log-dump", false, "If set to true, logs from master and nodes won't be gathered after test run.")
|
||||
flag.StringVar(&TestContext.LogexporterGCSPath, "logexporter-gcs-path", "", "Path to the GCS artifacts directory to dump logs from nodes. Logexporter gets enabled if this is non-empty.")
|
||||
flag.BoolVar(&TestContext.DeleteNamespace, "delete-namespace", true, "If true tests will delete namespace after completion. It is only designed to make debugging easier, DO NOT turn it off by default.")
|
||||
flag.BoolVar(&TestContext.DeleteNamespaceOnFailure, "delete-namespace-on-failure", true, "If true, framework will delete test namespace on failure. Used only during test debugging.")
|
||||
flag.IntVar(&TestContext.AllowedNotReadyNodes, "allowed-not-ready-nodes", 0, "If non-zero, framework will allow for that many non-ready nodes when checking for all ready nodes.")
|
||||
flags.StringVar(&TestContext.GatherKubeSystemResourceUsageData, "gather-resource-usage", "false", "If set to 'true' or 'all' framework will be monitoring resource usage of system all add-ons in (some) e2e tests, if set to 'master' framework will be monitoring master node only, if set to 'none' of 'false' monitoring will be turned off.")
|
||||
flags.BoolVar(&TestContext.GatherLogsSizes, "gather-logs-sizes", false, "If set to true framework will be monitoring logs sizes on all machines running e2e tests.")
|
||||
flags.IntVar(&TestContext.MaxNodesToGather, "max-nodes-to-gather-from", 20, "The maximum number of nodes to gather extended info from on test failure.")
|
||||
flags.StringVar(&TestContext.GatherMetricsAfterTest, "gather-metrics-at-teardown", "false", "If set to 'true' framework will gather metrics from all components after each test. If set to 'master' only master component metrics would be gathered.")
|
||||
flags.BoolVar(&TestContext.GatherSuiteMetricsAfterTest, "gather-suite-metrics-at-teardown", false, "If set to true framwork will gather metrics from all components after the whole test suite completes.")
|
||||
flags.BoolVar(&TestContext.AllowGatheringProfiles, "allow-gathering-profiles", true, "If set to true framework will allow to gather CPU/memory allocation pprof profiles from the master.")
|
||||
flags.BoolVar(&TestContext.IncludeClusterAutoscalerMetrics, "include-cluster-autoscaler", false, "If set to true, framework will include Cluster Autoscaler when gathering metrics.")
|
||||
flags.StringVar(&TestContext.OutputPrintType, "output-print-type", "json", "Format in which summaries should be printed: 'hr' for human readable, 'json' for JSON ones.")
|
||||
flags.BoolVar(&TestContext.DumpLogsOnFailure, "dump-logs-on-failure", true, "If set to true test will dump data about the namespace in which test was running.")
|
||||
flags.BoolVar(&TestContext.DisableLogDump, "disable-log-dump", false, "If set to true, logs from master and nodes won't be gathered after test run.")
|
||||
flags.StringVar(&TestContext.LogexporterGCSPath, "logexporter-gcs-path", "", "Path to the GCS artifacts directory to dump logs from nodes. Logexporter gets enabled if this is non-empty.")
|
||||
flags.BoolVar(&TestContext.DeleteNamespace, "delete-namespace", true, "If true tests will delete namespace after completion. It is only designed to make debugging easier, DO NOT turn it off by default.")
|
||||
flags.BoolVar(&TestContext.DeleteNamespaceOnFailure, "delete-namespace-on-failure", true, "If true, framework will delete test namespace on failure. Used only during test debugging.")
|
||||
flags.IntVar(&TestContext.AllowedNotReadyNodes, "allowed-not-ready-nodes", 0, "If non-zero, framework will allow for that many non-ready nodes when checking for all ready nodes.")
|
||||
|
||||
flag.StringVar(&TestContext.Host, "host", "", fmt.Sprintf("The host, or apiserver, to connect to. Will default to %s if this argument and --kubeconfig are not set", defaultHost))
|
||||
flag.StringVar(&TestContext.ReportPrefix, "report-prefix", "", "Optional prefix for JUnit XML reports. Default is empty, which doesn't prepend anything to the default name.")
|
||||
flag.StringVar(&TestContext.ReportDir, "report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.")
|
||||
flag.Var(cliflag.NewMapStringBool(&TestContext.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features.")
|
||||
flag.StringVar(&TestContext.ContainerRuntime, "container-runtime", "docker", "The container runtime of cluster VM instances (docker/remote).")
|
||||
flag.StringVar(&TestContext.ContainerRuntimeEndpoint, "container-runtime-endpoint", "unix:///var/run/dockershim.sock", "The container runtime endpoint of cluster VM instances.")
|
||||
flag.StringVar(&TestContext.ContainerRuntimeProcessName, "container-runtime-process-name", "dockerd", "The name of the container runtime process.")
|
||||
flag.StringVar(&TestContext.ContainerRuntimePidFile, "container-runtime-pid-file", "/var/run/docker.pid", "The pid file of the container runtime.")
|
||||
flag.StringVar(&TestContext.SystemdServices, "systemd-services", "docker", "The comma separated list of systemd services the framework will dump logs for.")
|
||||
flag.BoolVar(&TestContext.DumpSystemdJournal, "dump-systemd-journal", false, "Whether to dump the full systemd journal.")
|
||||
flag.StringVar(&TestContext.ImageServiceEndpoint, "image-service-endpoint", "", "The image service endpoint of cluster VM instances.")
|
||||
flag.StringVar(&TestContext.DockershimCheckpointDir, "dockershim-checkpoint-dir", "/var/lib/dockershim/sandbox", "The directory for dockershim to store sandbox checkpoints.")
|
||||
flag.StringVar(&TestContext.KubernetesAnywherePath, "kubernetes-anywhere-path", "/workspace/k8s.io/kubernetes-anywhere", "Which directory kubernetes-anywhere is installed to.")
|
||||
flags.StringVar(&TestContext.Host, "host", "", fmt.Sprintf("The host, or apiserver, to connect to. Will default to %s if this argument and --kubeconfig are not set", defaultHost))
|
||||
flags.StringVar(&TestContext.ReportPrefix, "report-prefix", "", "Optional prefix for JUnit XML reports. Default is empty, which doesn't prepend anything to the default name.")
|
||||
flags.StringVar(&TestContext.ReportDir, "report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.")
|
||||
flags.Var(cliflag.NewMapStringBool(&TestContext.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features.")
|
||||
flags.StringVar(&TestContext.ContainerRuntime, "container-runtime", "docker", "The container runtime of cluster VM instances (docker/remote).")
|
||||
flags.StringVar(&TestContext.ContainerRuntimeEndpoint, "container-runtime-endpoint", "unix:///var/run/dockershim.sock", "The container runtime endpoint of cluster VM instances.")
|
||||
flags.StringVar(&TestContext.ContainerRuntimeProcessName, "container-runtime-process-name", "dockerd", "The name of the container runtime process.")
|
||||
flags.StringVar(&TestContext.ContainerRuntimePidFile, "container-runtime-pid-file", "/var/run/docker.pid", "The pid file of the container runtime.")
|
||||
flags.StringVar(&TestContext.SystemdServices, "systemd-services", "docker", "The comma separated list of systemd services the framework will dump logs for.")
|
||||
flags.BoolVar(&TestContext.DumpSystemdJournal, "dump-systemd-journal", false, "Whether to dump the full systemd journal.")
|
||||
flags.StringVar(&TestContext.ImageServiceEndpoint, "image-service-endpoint", "", "The image service endpoint of cluster VM instances.")
|
||||
flags.StringVar(&TestContext.DockershimCheckpointDir, "dockershim-checkpoint-dir", "/var/lib/dockershim/sandbox", "The directory for dockershim to store sandbox checkpoints.")
|
||||
flags.StringVar(&TestContext.KubernetesAnywherePath, "kubernetes-anywhere-path", "/workspace/k8s.io/kubernetes-anywhere", "Which directory kubernetes-anywhere is installed to.")
|
||||
flags.StringVar(&TestContext.NonblockingTaints, "non-blocking-taints", `node-role.kubernetes.io/master`, "Nodes with taints in this comma-delimited list will not block the test framework from starting tests.")
|
||||
|
||||
flag.BoolVar(&TestContext.ListImages, "list-images", false, "If true, will show list of images used for runnning tests.")
|
||||
flags.BoolVar(&TestContext.ListImages, "list-images", false, "If true, will show list of images used for runnning tests.")
|
||||
flags.StringVar(&TestContext.KubectlPath, "kubectl-path", "kubectl", "The kubectl binary to use. For development, you might use 'cluster/kubectl.sh' here.")
|
||||
|
||||
flags.StringVar(&TestContext.ProgressReportURL, "progress-report-url", "", "The URL to POST progress updates to as the suite runs to assist in aiding integrations. If empty, no messages sent.")
|
||||
}
|
||||
|
||||
// RegisterClusterFlags registers flags specific to the cluster e2e test suite.
|
||||
func RegisterClusterFlags() {
|
||||
flag.BoolVar(&TestContext.VerifyServiceAccount, "e2e-verify-service-account", true, "If true tests will verify the service account before running.")
|
||||
flag.StringVar(&TestContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to kubeconfig containing embedded authinfo.")
|
||||
flag.StringVar(&TestContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'")
|
||||
flag.StringVar(&TestContext.KubeAPIContentType, "kube-api-content-type", "application/vnd.kubernetes.protobuf", "ContentType used to communicate with apiserver")
|
||||
func RegisterClusterFlags(flags *flag.FlagSet) {
|
||||
flags.BoolVar(&TestContext.VerifyServiceAccount, "e2e-verify-service-account", true, "If true tests will verify the service account before running.")
|
||||
flags.StringVar(&TestContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to kubeconfig containing embedded authinfo.")
|
||||
flags.StringVar(&TestContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'")
|
||||
flags.StringVar(&TestContext.KubeAPIContentType, "kube-api-content-type", "application/vnd.kubernetes.protobuf", "ContentType used to communicate with apiserver")
|
||||
|
||||
flag.StringVar(&TestContext.KubeVolumeDir, "volume-dir", "/var/lib/kubelet", "Path to the directory containing the kubelet volumes.")
|
||||
flag.StringVar(&TestContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.")
|
||||
flag.StringVar(&TestContext.RepoRoot, "repo-root", "../../", "Root directory of kubernetes repository, for finding test files.")
|
||||
flag.StringVar(&TestContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, skeleton (the fallback if not set), etc.)")
|
||||
flag.StringVar(&TestContext.Tooling, "tooling", "", "The tooling in use (kops, gke, etc.)")
|
||||
flag.StringVar(&TestContext.KubectlPath, "kubectl-path", "kubectl", "The kubectl binary to use. For development, you might use 'cluster/kubectl.sh' here.")
|
||||
flag.StringVar(&TestContext.OutputDir, "e2e-output-dir", "/tmp", "Output directory for interesting/useful test data, like performance data, benchmarks, and other metrics.")
|
||||
flag.StringVar(&TestContext.Prefix, "prefix", "e2e", "A prefix to be added to cloud resources created during testing.")
|
||||
flag.StringVar(&TestContext.MasterOSDistro, "master-os-distro", "debian", "The OS distribution of cluster master (debian, ubuntu, gci, coreos, or custom).")
|
||||
flag.StringVar(&TestContext.NodeOSDistro, "node-os-distro", "debian", "The OS distribution of cluster VM instances (debian, ubuntu, gci, coreos, or custom).")
|
||||
flag.StringVar(&TestContext.ClusterMonitoringMode, "cluster-monitoring-mode", "standalone", "The monitoring solution that is used in the cluster.")
|
||||
flag.BoolVar(&TestContext.EnablePrometheusMonitoring, "prometheus-monitoring", false, "Separate Prometheus monitoring deployed in cluster.")
|
||||
flag.StringVar(&TestContext.ClusterDNSDomain, "dns-domain", "cluster.local", "The DNS Domain of the cluster.")
|
||||
flags.StringVar(&TestContext.KubeVolumeDir, "volume-dir", "/var/lib/kubelet", "Path to the directory containing the kubelet volumes.")
|
||||
flags.StringVar(&TestContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.")
|
||||
flags.StringVar(&TestContext.RepoRoot, "repo-root", "../../", "Root directory of kubernetes repository, for finding test files.")
|
||||
flags.StringVar(&TestContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, skeleton (the fallback if not set), etc.)")
|
||||
flags.StringVar(&TestContext.Tooling, "tooling", "", "The tooling in use (kops, gke, etc.)")
|
||||
flags.StringVar(&TestContext.OutputDir, "e2e-output-dir", "/tmp", "Output directory for interesting/useful test data, like performance data, benchmarks, and other metrics.")
|
||||
flags.StringVar(&TestContext.Prefix, "prefix", "e2e", "A prefix to be added to cloud resources created during testing.")
|
||||
flags.StringVar(&TestContext.MasterOSDistro, "master-os-distro", "debian", "The OS distribution of cluster master (debian, ubuntu, gci, coreos, or custom).")
|
||||
flags.StringVar(&TestContext.NodeOSDistro, "node-os-distro", "debian", "The OS distribution of cluster VM instances (debian, ubuntu, gci, coreos, or custom).")
|
||||
flags.StringVar(&TestContext.ClusterMonitoringMode, "cluster-monitoring-mode", "standalone", "The monitoring solution that is used in the cluster.")
|
||||
flags.StringVar(&TestContext.ClusterDNSDomain, "dns-domain", "cluster.local", "The DNS Domain of the cluster.")
|
||||
|
||||
// TODO: Flags per provider? Rename gce-project/gce-zone?
|
||||
cloudConfig := &TestContext.CloudConfig
|
||||
flag.StringVar(&cloudConfig.MasterName, "kube-master", "", "Name of the kubernetes master. Only required if provider is gce or gke")
|
||||
flag.StringVar(&cloudConfig.APIEndpoint, "gce-api-endpoint", "", "The GCE APIEndpoint being used, if applicable")
|
||||
flag.StringVar(&cloudConfig.ProjectID, "gce-project", "", "The GCE project being used, if applicable")
|
||||
flag.StringVar(&cloudConfig.Zone, "gce-zone", "", "GCE zone being used, if applicable")
|
||||
flag.StringVar(&cloudConfig.Region, "gce-region", "", "GCE region being used, if applicable")
|
||||
flag.BoolVar(&cloudConfig.MultiZone, "gce-multizone", false, "If true, start GCE cloud provider with multizone support.")
|
||||
flag.BoolVar(&cloudConfig.MultiMaster, "gce-multimaster", false, "If true, the underlying GCE/GKE cluster is assumed to be multi-master.")
|
||||
flag.StringVar(&cloudConfig.Cluster, "gke-cluster", "", "GKE name of cluster being used, if applicable")
|
||||
flag.StringVar(&cloudConfig.NodeInstanceGroup, "node-instance-group", "", "Name of the managed instance group for nodes. Valid only for gce, gke or aws. If there is more than one group: comma separated list of groups.")
|
||||
flag.StringVar(&cloudConfig.Network, "network", "e2e", "The cloud provider network for this e2e cluster.")
|
||||
flag.IntVar(&cloudConfig.NumNodes, "num-nodes", DefaultNumNodes, fmt.Sprintf("Number of nodes in the cluster. If the default value of '%q' is used the number of schedulable nodes is auto-detected.", DefaultNumNodes))
|
||||
flag.StringVar(&cloudConfig.ClusterIPRange, "cluster-ip-range", "10.64.0.0/14", "A CIDR notation IP range from which to assign IPs in the cluster.")
|
||||
flag.StringVar(&cloudConfig.NodeTag, "node-tag", "", "Network tags used on node instances. Valid only for gce, gke")
|
||||
flag.StringVar(&cloudConfig.MasterTag, "master-tag", "", "Network tags used on master instances. Valid only for gce, gke")
|
||||
flags.StringVar(&cloudConfig.MasterName, "kube-master", "", "Name of the kubernetes master. Only required if provider is gce or gke")
|
||||
flags.StringVar(&cloudConfig.APIEndpoint, "gce-api-endpoint", "", "The GCE APIEndpoint being used, if applicable")
|
||||
flags.StringVar(&cloudConfig.ProjectID, "gce-project", "", "The GCE project being used, if applicable")
|
||||
flags.StringVar(&cloudConfig.Zone, "gce-zone", "", "GCE zone being used, if applicable")
|
||||
flags.StringVar(&cloudConfig.Region, "gce-region", "", "GCE region being used, if applicable")
|
||||
flags.BoolVar(&cloudConfig.MultiZone, "gce-multizone", false, "If true, start GCE cloud provider with multizone support.")
|
||||
flags.BoolVar(&cloudConfig.MultiMaster, "gce-multimaster", false, "If true, the underlying GCE/GKE cluster is assumed to be multi-master.")
|
||||
flags.StringVar(&cloudConfig.Cluster, "gke-cluster", "", "GKE name of cluster being used, if applicable")
|
||||
flags.StringVar(&cloudConfig.NodeInstanceGroup, "node-instance-group", "", "Name of the managed instance group for nodes. Valid only for gce, gke or aws. If there is more than one group: comma separated list of groups.")
|
||||
flags.StringVar(&cloudConfig.Network, "network", "e2e", "The cloud provider network for this e2e cluster.")
|
||||
flags.IntVar(&cloudConfig.NumNodes, "num-nodes", DefaultNumNodes, fmt.Sprintf("Number of nodes in the cluster. If the default value of '%q' is used the number of schedulable nodes is auto-detected.", DefaultNumNodes))
|
||||
flags.StringVar(&cloudConfig.ClusterIPRange, "cluster-ip-range", "10.64.0.0/14", "A CIDR notation IP range from which to assign IPs in the cluster.")
|
||||
flags.StringVar(&cloudConfig.NodeTag, "node-tag", "", "Network tags used on node instances. Valid only for gce, gke")
|
||||
flags.StringVar(&cloudConfig.MasterTag, "master-tag", "", "Network tags used on master instances. Valid only for gce, gke")
|
||||
|
||||
flag.StringVar(&cloudConfig.ClusterTag, "cluster-tag", "", "Tag used to identify resources. Only required if provider is aws.")
|
||||
flag.StringVar(&cloudConfig.ConfigFile, "cloud-config-file", "", "Cloud config file. Only required if provider is azure.")
|
||||
flag.IntVar(&TestContext.MinStartupPods, "minStartupPods", 0, "The number of pods which we need to see in 'Running' state with a 'Ready' condition of true, before we try running tests. This is useful in any cluster which needs some base pod-based services running before it can be used.")
|
||||
flag.DurationVar(&TestContext.SystemPodsStartupTimeout, "system-pods-startup-timeout", 10*time.Minute, "Timeout for waiting for all system pods to be running before starting tests.")
|
||||
flag.DurationVar(&TestContext.NodeSchedulableTimeout, "node-schedulable-timeout", 30*time.Minute, "Timeout for waiting for all nodes to be schedulable.")
|
||||
flag.DurationVar(&TestContext.SystemDaemonsetStartupTimeout, "system-daemonsets-startup-timeout", 5*time.Minute, "Timeout for waiting for all system daemonsets to be ready.")
|
||||
flag.StringVar(&TestContext.EtcdUpgradeStorage, "etcd-upgrade-storage", "", "The storage version to upgrade to (either 'etcdv2' or 'etcdv3') if doing an etcd upgrade test.")
|
||||
flag.StringVar(&TestContext.EtcdUpgradeVersion, "etcd-upgrade-version", "", "The etcd binary version to upgrade to (e.g., '3.0.14', '2.3.7') if doing an etcd upgrade test.")
|
||||
flag.StringVar(&TestContext.GCEUpgradeScript, "gce-upgrade-script", "", "Script to use to upgrade a GCE cluster.")
|
||||
flag.BoolVar(&TestContext.CleanStart, "clean-start", false, "If true, purge all namespaces except default and system before running tests. This serves to Cleanup test namespaces from failed/interrupted e2e runs in a long-lived cluster.")
|
||||
flags.StringVar(&cloudConfig.ClusterTag, "cluster-tag", "", "Tag used to identify resources. Only required if provider is aws.")
|
||||
flags.StringVar(&cloudConfig.ConfigFile, "cloud-config-file", "", "Cloud config file. Only required if provider is azure.")
|
||||
flags.IntVar(&TestContext.MinStartupPods, "minStartupPods", 0, "The number of pods which we need to see in 'Running' state with a 'Ready' condition of true, before we try running tests. This is useful in any cluster which needs some base pod-based services running before it can be used.")
|
||||
flags.DurationVar(&TestContext.SystemPodsStartupTimeout, "system-pods-startup-timeout", 10*time.Minute, "Timeout for waiting for all system pods to be running before starting tests.")
|
||||
flags.DurationVar(&TestContext.NodeSchedulableTimeout, "node-schedulable-timeout", 30*time.Minute, "Timeout for waiting for all nodes to be schedulable.")
|
||||
flags.DurationVar(&TestContext.SystemDaemonsetStartupTimeout, "system-daemonsets-startup-timeout", 5*time.Minute, "Timeout for waiting for all system daemonsets to be ready.")
|
||||
flags.StringVar(&TestContext.EtcdUpgradeStorage, "etcd-upgrade-storage", "", "The storage version to upgrade to (either 'etcdv2' or 'etcdv3') if doing an etcd upgrade test.")
|
||||
flags.StringVar(&TestContext.EtcdUpgradeVersion, "etcd-upgrade-version", "", "The etcd binary version to upgrade to (e.g., '3.0.14', '2.3.7') if doing an etcd upgrade test.")
|
||||
flags.StringVar(&TestContext.GCEUpgradeScript, "gce-upgrade-script", "", "Script to use to upgrade a GCE cluster.")
|
||||
flags.BoolVar(&TestContext.CleanStart, "clean-start", false, "If true, purge all namespaces except default and system before running tests. This serves to Cleanup test namespaces from failed/interrupted e2e runs in a long-lived cluster.")
|
||||
|
||||
nodeKiller := &TestContext.NodeKiller
|
||||
flag.BoolVar(&nodeKiller.Enabled, "node-killer", false, "Whether NodeKiller should kill any nodes.")
|
||||
flag.Float64Var(&nodeKiller.FailureRatio, "node-killer-failure-ratio", 0.01, "Percentage of nodes to be killed")
|
||||
flag.DurationVar(&nodeKiller.Interval, "node-killer-interval", 1*time.Minute, "Time between node failures.")
|
||||
flag.Float64Var(&nodeKiller.JitterFactor, "node-killer-jitter-factor", 60, "Factor used to jitter node failures.")
|
||||
flag.DurationVar(&nodeKiller.SimulatedDowntime, "node-killer-simulated-downtime", 10*time.Minute, "A delay between node death and recreation")
|
||||
}
|
||||
|
||||
// RegisterNodeFlags registers flags specific to the node e2e test suite.
|
||||
func RegisterNodeFlags() {
|
||||
// Mark the test as node e2e when node flags are api.Registry.
|
||||
TestContext.NodeE2E = true
|
||||
flag.StringVar(&TestContext.NodeName, "node-name", "", "Name of the node to run tests on.")
|
||||
// TODO(random-liu): Move kubelet start logic out of the test.
|
||||
// TODO(random-liu): Move log fetch logic out of the test.
|
||||
// There are different ways to start kubelet (systemd, initd, docker, manually started etc.)
|
||||
// and manage logs (journald, upstart etc.).
|
||||
// For different situation we need to mount different things into the container, run different commands.
|
||||
// It is hard and unnecessary to deal with the complexity inside the test suite.
|
||||
flag.BoolVar(&TestContext.NodeConformance, "conformance", false, "If true, the test suite will not start kubelet, and fetch system log (kernel, docker, kubelet log etc.) to the report directory.")
|
||||
flag.BoolVar(&TestContext.PrepullImages, "prepull-images", true, "If true, prepull images so image pull failures do not cause test failures.")
|
||||
flag.StringVar(&TestContext.ImageDescription, "image-description", "", "The description of the image which the test will be running on.")
|
||||
flag.StringVar(&TestContext.SystemSpecName, "system-spec-name", "", "The name of the system spec (e.g., gke) that's used in the node e2e test. The system specs are in test/e2e_node/system/specs/. This is used by the test framework to determine which tests to run for validating the system requirements.")
|
||||
flag.Var(cliflag.NewMapStringString(&TestContext.ExtraEnvs), "extra-envs", "The extra environment variables needed for node e2e tests. Format: a list of key=value pairs, e.g., env1=val1,env2=val2")
|
||||
}
|
||||
|
||||
// HandleFlags sets up all flags and parses the command line.
|
||||
func HandleFlags() {
|
||||
RegisterCommonFlags()
|
||||
RegisterClusterFlags()
|
||||
flag.Parse()
|
||||
flags.BoolVar(&nodeKiller.Enabled, "node-killer", false, "Whether NodeKiller should kill any nodes.")
|
||||
flags.Float64Var(&nodeKiller.FailureRatio, "node-killer-failure-ratio", 0.01, "Percentage of nodes to be killed")
|
||||
flags.DurationVar(&nodeKiller.Interval, "node-killer-interval", 1*time.Minute, "Time between node failures.")
|
||||
flags.Float64Var(&nodeKiller.JitterFactor, "node-killer-jitter-factor", 60, "Factor used to jitter node failures.")
|
||||
flags.DurationVar(&nodeKiller.SimulatedDowntime, "node-killer-simulated-downtime", 10*time.Minute, "A delay between node death and recreation")
|
||||
}
|
||||
|
||||
func createKubeConfig(clientCfg *restclient.Config) *clientcmdapi.Config {
|
||||
@ -359,7 +359,7 @@ func createKubeConfig(clientCfg *restclient.Config) *clientcmdapi.Config {
|
||||
userNick := "user"
|
||||
contextNick := "context"
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
configCmd := clientcmdapi.NewConfig()
|
||||
|
||||
credentials := clientcmdapi.NewAuthInfo()
|
||||
credentials.Token = clientCfg.BearerToken
|
||||
@ -372,7 +372,7 @@ func createKubeConfig(clientCfg *restclient.Config) *clientcmdapi.Config {
|
||||
if len(credentials.ClientKey) == 0 {
|
||||
credentials.ClientKeyData = clientCfg.TLSClientConfig.KeyData
|
||||
}
|
||||
config.AuthInfos[userNick] = credentials
|
||||
configCmd.AuthInfos[userNick] = credentials
|
||||
|
||||
cluster := clientcmdapi.NewCluster()
|
||||
cluster.Server = clientCfg.Host
|
||||
@ -381,15 +381,15 @@ func createKubeConfig(clientCfg *restclient.Config) *clientcmdapi.Config {
|
||||
cluster.CertificateAuthorityData = clientCfg.CAData
|
||||
}
|
||||
cluster.InsecureSkipTLSVerify = clientCfg.Insecure
|
||||
config.Clusters[clusterNick] = cluster
|
||||
configCmd.Clusters[clusterNick] = cluster
|
||||
|
||||
context := clientcmdapi.NewContext()
|
||||
context.Cluster = clusterNick
|
||||
context.AuthInfo = userNick
|
||||
config.Contexts[contextNick] = context
|
||||
config.CurrentContext = contextNick
|
||||
configCmd.Contexts[contextNick] = context
|
||||
configCmd.CurrentContext = contextNick
|
||||
|
||||
return config
|
||||
return configCmd
|
||||
}
|
||||
|
||||
// AfterReadingAllFlags makes changes to the context after all flags
|
||||
@ -416,13 +416,15 @@ func AfterReadingAllFlags(t *TestContextType) {
|
||||
t.AllowedNotReadyNodes = t.CloudConfig.NumNodes / 100
|
||||
}
|
||||
|
||||
klog.Infof("Tolerating taints %q when considering if nodes are ready", TestContext.NonblockingTaints)
|
||||
|
||||
// Make sure that all test runs have a valid TestContext.CloudConfig.Provider.
|
||||
// TODO: whether and how long this code is needed is getting discussed
|
||||
// in https://github.com/kubernetes/kubernetes/issues/70194.
|
||||
if TestContext.Provider == "" {
|
||||
// Some users of the e2e.test binary pass --provider=.
|
||||
// We need to support that, changing it would break those usages.
|
||||
e2elog.Logf("The --provider flag is not set. Continuing as if --provider=skeleton had been used.")
|
||||
Logf("The --provider flag is not set. Continuing as if --provider=skeleton had been used.")
|
||||
TestContext.Provider = "skeleton"
|
||||
}
|
||||
|
||||
|
196
vendor/k8s.io/kubernetes/test/e2e/framework/testfiles/testfiles.go
generated
vendored
196
vendor/k8s.io/kubernetes/test/e2e/framework/testfiles/testfiles.go
generated
vendored
@ -1,196 +0,0 @@
|
||||
/*
|
||||
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 testfiles provides a wrapper around various optional ways
|
||||
// of retrieving additional files needed during a test run:
|
||||
// - builtin bindata
|
||||
// - filesystem access
|
||||
//
|
||||
// Because it is a is self-contained package, it can be used by
|
||||
// test/e2e/framework and test/e2e/manifest without creating
|
||||
// a circular dependency.
|
||||
package testfiles
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var filesources []FileSource
|
||||
|
||||
// AddFileSource registers another provider for files that may be
|
||||
// needed at runtime. Should be called during initialization of a test
|
||||
// binary.
|
||||
func AddFileSource(filesource FileSource) {
|
||||
filesources = append(filesources, filesource)
|
||||
}
|
||||
|
||||
// FileSource implements one way of retrieving test file content. For
|
||||
// example, one file source could read from the original source code
|
||||
// file tree, another from bindata compiled into a test executable.
|
||||
type FileSource interface {
|
||||
// ReadTestFile retrieves the content of a file that gets maintained
|
||||
// alongside a test's source code. Files are identified by the
|
||||
// relative path inside the repository containing the tests, for
|
||||
// example "cluster/gce/upgrade.sh" inside kubernetes/kubernetes.
|
||||
//
|
||||
// When the file is not found, a nil slice is returned. An error is
|
||||
// returned for all fatal errors.
|
||||
ReadTestFile(filePath string) ([]byte, error)
|
||||
|
||||
// DescribeFiles returns a multi-line description of which
|
||||
// files are available via this source. It is meant to be
|
||||
// used as part of the error message when a file cannot be
|
||||
// found.
|
||||
DescribeFiles() string
|
||||
}
|
||||
|
||||
// Fail is an error handler function with the same prototype and
|
||||
// semantic as ginkgo.Fail. Typically ginkgo.Fail is what callers
|
||||
// of ReadOrDie and Exists will pass. This way this package
|
||||
// avoids depending on Ginkgo.
|
||||
type Fail func(failure string, callerSkip ...int)
|
||||
|
||||
// ReadOrDie tries to retrieve the desired file content from
|
||||
// one of the registered file sources. In contrast to FileSource, it
|
||||
// will either return a valid slice or abort the test by calling the fatal function,
|
||||
// i.e. the caller doesn't have to implement error checking.
|
||||
func ReadOrDie(filePath string, fail Fail) []byte {
|
||||
data, err := Read(filePath)
|
||||
if err != nil {
|
||||
fail(err.Error(), 1)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Read tries to retrieve the desired file content from
|
||||
// one of the registered file sources.
|
||||
func Read(filePath string) ([]byte, error) {
|
||||
if len(filesources) == 0 {
|
||||
return nil, fmt.Errorf("no file sources registered (yet?), cannot retrieve test file %s", filePath)
|
||||
}
|
||||
for _, filesource := range filesources {
|
||||
data, err := filesource.ReadTestFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fatal error retrieving test file %s: %s", filePath, err)
|
||||
}
|
||||
if data != nil {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
// Here we try to generate an error that points test authors
|
||||
// or users in the right direction for resolving the problem.
|
||||
error := fmt.Sprintf("Test file %q was not found.\n", filePath)
|
||||
for _, filesource := range filesources {
|
||||
error += filesource.DescribeFiles()
|
||||
error += "\n"
|
||||
}
|
||||
return nil, errors.New(error)
|
||||
}
|
||||
|
||||
// Exists checks whether a file could be read. Unexpected errors
|
||||
// are handled by calling the fail function, which then should
|
||||
// abort the current test.
|
||||
func Exists(filePath string, fail Fail) bool {
|
||||
for _, filesource := range filesources {
|
||||
data, err := filesource.ReadTestFile(filePath)
|
||||
if err != nil {
|
||||
fail(fmt.Sprintf("fatal error looking for test file %s: %s", filePath, err), 1)
|
||||
}
|
||||
if data != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RootFileSource looks for files relative to a root directory.
|
||||
type RootFileSource struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
// ReadTestFile looks for the file relative to the configured
|
||||
// root directory. If the path is already absolute, for example
|
||||
// in a test that has its own method of determining where
|
||||
// files are, then the path will be used directly.
|
||||
func (r RootFileSource) ReadTestFile(filePath string) ([]byte, error) {
|
||||
var fullPath string
|
||||
if path.IsAbs(filePath) {
|
||||
fullPath = filePath
|
||||
} else {
|
||||
fullPath = filepath.Join(r.Root, filePath)
|
||||
}
|
||||
data, err := ioutil.ReadFile(fullPath)
|
||||
if os.IsNotExist(err) {
|
||||
// Not an error (yet), some other provider may have the file.
|
||||
return nil, nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// DescribeFiles explains that it looks for files inside a certain
|
||||
// root directory.
|
||||
func (r RootFileSource) DescribeFiles() string {
|
||||
description := fmt.Sprintf("Test files are expected in %q", r.Root)
|
||||
if !path.IsAbs(r.Root) {
|
||||
// The default in test_context.go is the relative path
|
||||
// ../../, which doesn't really help locating the
|
||||
// actual location. Therefore we add also the absolute
|
||||
// path if necessary.
|
||||
abs, err := filepath.Abs(r.Root)
|
||||
if err == nil {
|
||||
description += fmt.Sprintf(" = %q", abs)
|
||||
}
|
||||
}
|
||||
description += "."
|
||||
return description
|
||||
}
|
||||
|
||||
// BindataFileSource handles files stored in a package generated with bindata.
|
||||
type BindataFileSource struct {
|
||||
Asset func(string) ([]byte, error)
|
||||
AssetNames func() []string
|
||||
}
|
||||
|
||||
// ReadTestFile looks for an asset with the given path.
|
||||
func (b BindataFileSource) ReadTestFile(filePath string) ([]byte, error) {
|
||||
fileBytes, err := b.Asset(filePath)
|
||||
if err != nil {
|
||||
// It would be nice to have a better way to detect
|
||||
// "not found" errors :-/
|
||||
if strings.HasSuffix(err.Error(), "not found") {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return fileBytes, nil
|
||||
}
|
||||
|
||||
// DescribeFiles explains about gobindata and then lists all available files.
|
||||
func (b BindataFileSource) DescribeFiles() string {
|
||||
var lines []string
|
||||
lines = append(lines, "The following files are built into the test executable via gobindata. For questions on maintaining gobindata, contact the sig-testing group.")
|
||||
assets := b.AssetNames()
|
||||
sort.Strings(assets)
|
||||
lines = append(lines, assets...)
|
||||
description := strings.Join(lines, "\n ")
|
||||
return description
|
||||
}
|
3546
vendor/k8s.io/kubernetes/test/e2e/framework/util.go
generated
vendored
3546
vendor/k8s.io/kubernetes/test/e2e/framework/util.go
generated
vendored
File diff suppressed because it is too large
Load Diff
192
vendor/k8s.io/kubernetes/test/e2e/manifest/manifest.go
generated
vendored
192
vendor/k8s.io/kubernetes/test/e2e/manifest/manifest.go
generated
vendored
@ -1,192 +0,0 @@
|
||||
/*
|
||||
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 manifest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/kubernetes/test/e2e/framework/testfiles"
|
||||
)
|
||||
|
||||
// PodFromManifest reads a .json/yaml file and returns the pod in it.
|
||||
func PodFromManifest(filename string) (*v1.Pod, error) {
|
||||
var pod v1.Pod
|
||||
data, err := testfiles.Read(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &pod); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pod, nil
|
||||
}
|
||||
|
||||
// RcFromManifest reads a .json/yaml file and returns the rc in it.
|
||||
func RcFromManifest(fileName string) (*v1.ReplicationController, error) {
|
||||
var controller v1.ReplicationController
|
||||
data, err := testfiles.Read(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &controller); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &controller, nil
|
||||
}
|
||||
|
||||
// SvcFromManifest reads a .json/yaml file and returns the service in it.
|
||||
func SvcFromManifest(fileName string) (*v1.Service, error) {
|
||||
var svc v1.Service
|
||||
data, err := testfiles.Read(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &svc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &svc, nil
|
||||
}
|
||||
|
||||
// IngressFromManifest reads a .json/yaml file and returns the ingress in it.
|
||||
func IngressFromManifest(fileName string) (*networkingv1beta1.Ingress, error) {
|
||||
var ing networkingv1beta1.Ingress
|
||||
data, err := testfiles.Read(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &ing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ing, nil
|
||||
}
|
||||
|
||||
// IngressToManifest generates a yaml file in the given path with the given ingress.
|
||||
// Assumes that a directory exists at the given path.
|
||||
func IngressToManifest(ing *networkingv1beta1.Ingress, path string) error {
|
||||
serialized, err := marshalToYaml(ing, networkingv1beta1.SchemeGroupVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal ingress %v to YAML: %v", ing, err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, serialized, 0600); err != nil {
|
||||
return fmt.Errorf("error in writing ingress to file: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatefulSetFromManifest returns a StatefulSet from a manifest stored in fileName in the Namespace indicated by ns.
|
||||
func StatefulSetFromManifest(fileName, ns string) (*apps.StatefulSet, error) {
|
||||
var ss apps.StatefulSet
|
||||
data, err := testfiles.Read(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &ss); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss.Namespace = ns
|
||||
if ss.Spec.Selector == nil {
|
||||
ss.Spec.Selector = &metav1.LabelSelector{
|
||||
MatchLabels: ss.Spec.Template.Labels,
|
||||
}
|
||||
}
|
||||
return &ss, nil
|
||||
}
|
||||
|
||||
// DaemonSetFromManifest returns a DaemonSet from a manifest stored in fileName in the Namespace indicated by ns.
|
||||
func DaemonSetFromManifest(fileName, ns string) (*apps.DaemonSet, error) {
|
||||
var ds apps.DaemonSet
|
||||
data, err := testfiles.Read(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds.Namespace = ns
|
||||
return &ds, nil
|
||||
}
|
||||
|
||||
// RoleFromManifest returns a Role from a manifest stored in fileName in the Namespace indicated by ns.
|
||||
func RoleFromManifest(fileName, ns string) (*rbac.Role, error) {
|
||||
var role rbac.Role
|
||||
data, err := testfiles.Read(fileName)
|
||||
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
role.Namespace = ns
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// marshalToYaml marshals an object into YAML for a given GroupVersion.
|
||||
// The object must be known in SupportedMediaTypes() for the Codecs under "client-go/kubernetes/scheme".
|
||||
func marshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
|
||||
mediaType := "application/yaml"
|
||||
info, ok := runtime.SerializerInfoForMediaType(scheme.Codecs.SupportedMediaTypes(), mediaType)
|
||||
if !ok {
|
||||
return []byte{}, fmt.Errorf("unsupported media type %q", mediaType)
|
||||
}
|
||||
encoder := scheme.Codecs.EncoderForVersion(info.Serializer, gv)
|
||||
return runtime.Encode(encoder, obj)
|
||||
}
|
39
vendor/k8s.io/kubernetes/test/e2e/system/system_utils.go
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/test/e2e/system/system_utils.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 system
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DeprecatedMightBeMasterNode returns true if given node is a registered master.
|
||||
// This code must not be updated to use node role labels, since node role labels
|
||||
// may not change behavior of the system.
|
||||
// DEPRECATED: use a label selector provided by test initialization.
|
||||
func DeprecatedMightBeMasterNode(nodeName string) bool {
|
||||
// We are trying to capture "master(-...)?$" regexp.
|
||||
// However, using regexp.MatchString() results even in more than 35%
|
||||
// of all space allocations in ControllerManager spent in this function.
|
||||
// That's why we are trying to be a bit smarter.
|
||||
if strings.HasSuffix(nodeName, "master") {
|
||||
return true
|
||||
}
|
||||
if len(nodeName) >= 10 {
|
||||
return strings.HasSuffix(nodeName[:len(nodeName)-3], "master-")
|
||||
}
|
||||
return false
|
||||
}
|
137
vendor/k8s.io/kubernetes/test/utils/admission_webhook.go
generated
vendored
Normal file
137
vendor/k8s.io/kubernetes/test/utils/admission_webhook.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2019 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 utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
)
|
||||
|
||||
// NewAdmissionWebhookServer sets up a webhook server with TLS enabled, returns URL and Close function
|
||||
// for the server
|
||||
func NewAdmissionWebhookServer(handler http.Handler) (string, func(), error) {
|
||||
// set up webhook server
|
||||
roots := x509.NewCertPool()
|
||||
if !roots.AppendCertsFromPEM(LocalhostCert) {
|
||||
return "", nil, fmt.Errorf("Failed to append Cert from PEM")
|
||||
}
|
||||
cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Failed to build cert with error: %+v", err)
|
||||
}
|
||||
webhookServer := httptest.NewUnstartedServer(handler)
|
||||
webhookServer.TLS = &tls.Config{
|
||||
RootCAs: roots,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
webhookServer.StartTLS()
|
||||
return webhookServer.URL, webhookServer.Close, nil
|
||||
}
|
||||
|
||||
// AdmissionWebhookHandler creates a HandlerFunc that decodes/encodes AdmissionReview and performs
|
||||
// given admit function
|
||||
func AdmissionWebhookHandler(t *testing.T, admit func(*v1beta1.AdmissionReview) error) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
|
||||
t.Errorf("contentType=%s, expect application/json", contentType)
|
||||
return
|
||||
}
|
||||
|
||||
review := v1beta1.AdmissionReview{}
|
||||
if err := json.Unmarshal(data, &review); err != nil {
|
||||
t.Errorf("Fail to deserialize object: %s with error: %v", string(data), err)
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
if err := admit(&review); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(review); err != nil {
|
||||
t.Errorf("Marshal of response failed with error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// LocalhostCert was generated from crypto/tls/generate_cert.go with the following command:
|
||||
// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDGDCCAgCgAwIBAgIQTKCKn99d5HhQVCLln2Q+eTANBgkqhkiG9w0BAQsFADAS
|
||||
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
||||
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEA1Z5/aTwqY706M34tn60l8ZHkanWDl8mM1pYf4Q7qg3zA9XqWLX6S
|
||||
4rTYDYCb4stEasC72lQnbEWHbthiQE76zubP8WOFHdvGR3mjAvHWz4FxvLOTheZ+
|
||||
3iDUrl6Aj9UIsYqzmpBJAoY4+vGGf+xHvuukHrVcFqR9ZuBdZuJ/HbbjUyuNr3X9
|
||||
erNIr5Ha17gVzf17SNbYgNrX9gbCeEB8Z9Ox7dVuJhLDkpF0T/B5Zld3BjyUVY/T
|
||||
cukU4dTVp6isbWPvCMRCZCCOpb+qIhxEjJ0n6tnPt8nf9lvDl4SWMl6X1bH+2EFa
|
||||
a8R06G0QI+XhwPyjXUyCR8QEOZPCR5wyqQIDAQABo2gwZjAOBgNVHQ8BAf8EBAMC
|
||||
AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAuBgNVHREE
|
||||
JzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG
|
||||
9w0BAQsFAAOCAQEAThqgJ/AFqaANsOp48lojDZfZBFxJQ3A4zfR/MgggUoQ9cP3V
|
||||
rxuKAFWQjze1EZc7J9iO1WvH98lOGVNRY/t2VIrVoSsBiALP86Eew9WucP60tbv2
|
||||
8/zsBDSfEo9Wl+Q/gwdEh8dgciUKROvCm76EgAwPGicMAgRsxXgwXHhS5e8nnbIE
|
||||
Ewaqvb5dY++6kh0Oz+adtNT5OqOwXTIRI67WuEe6/B3Z4LNVPQDIj7ZUJGNw8e6L
|
||||
F4nkUthwlKx4yEJHZBRuFPnO7Z81jNKuwL276+mczRH7piI6z9uyMV/JbEsOIxyL
|
||||
W6CzB7pZ9Nj1YLpgzc1r6oONHLokMJJIz/IvkQ==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
// LocalhostKey is the private key for LocalhostCert.
|
||||
var LocalhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA1Z5/aTwqY706M34tn60l8ZHkanWDl8mM1pYf4Q7qg3zA9XqW
|
||||
LX6S4rTYDYCb4stEasC72lQnbEWHbthiQE76zubP8WOFHdvGR3mjAvHWz4FxvLOT
|
||||
heZ+3iDUrl6Aj9UIsYqzmpBJAoY4+vGGf+xHvuukHrVcFqR9ZuBdZuJ/HbbjUyuN
|
||||
r3X9erNIr5Ha17gVzf17SNbYgNrX9gbCeEB8Z9Ox7dVuJhLDkpF0T/B5Zld3BjyU
|
||||
VY/TcukU4dTVp6isbWPvCMRCZCCOpb+qIhxEjJ0n6tnPt8nf9lvDl4SWMl6X1bH+
|
||||
2EFaa8R06G0QI+XhwPyjXUyCR8QEOZPCR5wyqQIDAQABAoIBAFAJmb1pMIy8OpFO
|
||||
hnOcYWoYepe0vgBiIOXJy9n8R7vKQ1X2f0w+b3SHw6eTd1TLSjAhVIEiJL85cdwD
|
||||
MRTdQrXA30qXOioMzUa8eWpCCHUpD99e/TgfO4uoi2dluw+pBx/WUyLnSqOqfLDx
|
||||
S66kbeFH0u86jm1hZibki7pfxLbxvu7KQgPe0meO5/13Retztz7/xa/pWIY71Zqd
|
||||
YC8UckuQdWUTxfuQf0470lAK34GZlDy9tvdVOG/PmNkG4j6OQjy0Kmz4Uk7rewKo
|
||||
ZbdphaLPJ2A4Rdqfn4WCoyDnxlfV861T922/dEDZEbNWiQpB81G8OfLL+FLHxyIT
|
||||
LKEu4R0CgYEA4RDj9jatJ/wGkMZBt+UF05mcJlRVMEijqdKgFwR2PP8b924Ka1mj
|
||||
9zqWsfbxQbdPdwsCeVBZrSlTEmuFSQLeWtqBxBKBTps/tUP0qZf7HjfSmcVI89WE
|
||||
3ab8LFjfh4PtK/LOq2D1GRZZkFliqi0gKwYdDoK6gxXWwrumXq4c2l8CgYEA8vrX
|
||||
dMuGCNDjNQkGXx3sr8pyHCDrSNR4Z4FrSlVUkgAW1L7FrCM911BuGh86FcOu9O/1
|
||||
Ggo0E8ge7qhQiXhB5vOo7hiVzSp0FxxCtGSlpdp4W6wx6ZWK8+Pc+6Moos03XdG7
|
||||
MKsdPGDciUn9VMOP3r8huX/btFTh90C/L50sH/cCgYAd02wyW8qUqux/0RYydZJR
|
||||
GWE9Hx3u+SFfRv9aLYgxyyj8oEOXOFjnUYdY7D3KlK1ePEJGq2RG81wD6+XM6Clp
|
||||
Zt2di0pBjYdi0S+iLfbkaUdqg1+ImLoz2YY/pkNxJQWQNmw2//FbMsAJxh6yKKrD
|
||||
qNq+6oonBwTf55hDodVHBwKBgEHgEBnyM9ygBXmTgM645jqiwF0v75pHQH2PcO8u
|
||||
Q0dyDr6PGjiZNWLyw2cBoFXWP9DYXbM5oPTcBMbfizY6DGP5G4uxzqtZHzBE0TDn
|
||||
OKHGoWr5PG7/xDRrSrZOfe3lhWVCP2XqfnqoKCJwlOYuPws89n+8UmyJttm6DBt0
|
||||
mUnxAoGBAIvbR87ZFXkvqstLs4KrdqTz4TQIcpzB3wENukHODPA6C1gzWTqp+OEe
|
||||
GMNltPfGCLO+YmoMQuTpb0kECYV3k4jR3gXO6YvlL9KbY+UOA6P0dDX4ROi2Rklj
|
||||
yh+lxFLYa1vlzzi9r8B7nkR9hrOGMvkfXF42X89g7lx4uMtu2I4q
|
||||
-----END RSA PRIVATE KEY-----`)
|
109
vendor/k8s.io/kubernetes/test/utils/audit.go
generated
vendored
109
vendor/k8s.io/kubernetes/test/utils/audit.go
generated
vendored
@ -20,12 +20,14 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
@ -46,6 +48,11 @@ type AuditEvent struct {
|
||||
RequestObject bool
|
||||
ResponseObject bool
|
||||
AuthorizeDecision string
|
||||
|
||||
// The Check functions in this package takes ownerships of these maps. You should
|
||||
// not reference these maps after calling the Check functions.
|
||||
AdmissionWebhookMutationAnnotations map[string]string
|
||||
AdmissionWebhookPatchAnnotations map[string]string
|
||||
}
|
||||
|
||||
// MissingEventsReport provides an analysis if any events are missing
|
||||
@ -71,7 +78,7 @@ func (m *MissingEventsReport) String() string {
|
||||
|
||||
// CheckAuditLines searches the audit log for the expected audit lines.
|
||||
func CheckAuditLines(stream io.Reader, expected []AuditEvent, version schema.GroupVersion) (missingReport *MissingEventsReport, err error) {
|
||||
expectations := buildEventExpectations(expected)
|
||||
expectations := newAuditEventTracker(expected)
|
||||
|
||||
scanner := bufio.NewScanner(stream)
|
||||
|
||||
@ -98,24 +105,20 @@ func CheckAuditLines(stream io.Reader, expected []AuditEvent, version schema.Gro
|
||||
return missingReport, err
|
||||
}
|
||||
|
||||
// If the event was expected, mark it as found.
|
||||
if _, found := expectations[event]; found {
|
||||
expectations[event] = true
|
||||
}
|
||||
expectations.Mark(event)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return missingReport, err
|
||||
}
|
||||
|
||||
missingEvents := findMissing(expectations)
|
||||
missingReport.MissingEvents = missingEvents
|
||||
missingReport.MissingEvents = expectations.Missing()
|
||||
missingReport.NumEventsChecked = i
|
||||
return missingReport, nil
|
||||
}
|
||||
|
||||
// CheckAuditList searches an audit event list for the expected audit events.
|
||||
func CheckAuditList(el auditinternal.EventList, expected []AuditEvent) (missing []AuditEvent, err error) {
|
||||
expectations := buildEventExpectations(expected)
|
||||
expectations := newAuditEventTracker(expected)
|
||||
|
||||
for _, e := range el.Items {
|
||||
event, err := testEventFromInternal(&e)
|
||||
@ -123,45 +126,38 @@ func CheckAuditList(el auditinternal.EventList, expected []AuditEvent) (missing
|
||||
return expected, err
|
||||
}
|
||||
|
||||
// If the event was expected, mark it as found.
|
||||
if _, found := expectations[event]; found {
|
||||
expectations[event] = true
|
||||
}
|
||||
expectations.Mark(event)
|
||||
}
|
||||
|
||||
missing = findMissing(expectations)
|
||||
return missing, nil
|
||||
return expectations.Missing(), nil
|
||||
}
|
||||
|
||||
// CheckForDuplicates checks a list for duplicate events
|
||||
func CheckForDuplicates(el auditinternal.EventList) (auditinternal.EventList, error) {
|
||||
// eventMap holds a map of audit events with just a nil value
|
||||
eventMap := map[AuditEvent]*bool{}
|
||||
// existingEvents holds a slice of audit events that have been seen
|
||||
existingEvents := []AuditEvent{}
|
||||
duplicates := auditinternal.EventList{}
|
||||
var err error
|
||||
for _, e := range el.Items {
|
||||
event, err := testEventFromInternal(&e)
|
||||
if err != nil {
|
||||
return duplicates, err
|
||||
}
|
||||
event.ID = e.AuditID
|
||||
if _, ok := eventMap[event]; ok {
|
||||
duplicates.Items = append(duplicates.Items, e)
|
||||
err = fmt.Errorf("failed duplicate check")
|
||||
continue
|
||||
for _, existing := range existingEvents {
|
||||
if reflect.DeepEqual(existing, event) {
|
||||
duplicates.Items = append(duplicates.Items, e)
|
||||
continue
|
||||
}
|
||||
}
|
||||
eventMap[event] = nil
|
||||
existingEvents = append(existingEvents, event)
|
||||
}
|
||||
return duplicates, err
|
||||
}
|
||||
|
||||
// buildEventExpectations creates a bool map out of a list of audit events
|
||||
func buildEventExpectations(expected []AuditEvent) map[AuditEvent]bool {
|
||||
expectations := map[AuditEvent]bool{}
|
||||
for _, event := range expected {
|
||||
expectations[event] = false
|
||||
var err error
|
||||
if len(duplicates.Items) > 0 {
|
||||
err = fmt.Errorf("failed duplicate check")
|
||||
}
|
||||
return expectations
|
||||
|
||||
return duplicates, err
|
||||
}
|
||||
|
||||
// testEventFromInternal takes an internal audit event and returns a test event
|
||||
@ -192,15 +188,58 @@ func testEventFromInternal(e *auditinternal.Event) (AuditEvent, error) {
|
||||
event.ImpersonatedGroups = strings.Join(e.ImpersonatedUser.Groups, ",")
|
||||
}
|
||||
event.AuthorizeDecision = e.Annotations["authorization.k8s.io/decision"]
|
||||
for k, v := range e.Annotations {
|
||||
if strings.HasPrefix(k, mutating.PatchAuditAnnotationPrefix) {
|
||||
if event.AdmissionWebhookPatchAnnotations == nil {
|
||||
event.AdmissionWebhookPatchAnnotations = map[string]string{}
|
||||
}
|
||||
event.AdmissionWebhookPatchAnnotations[k] = v
|
||||
} else if strings.HasPrefix(k, mutating.MutationAuditAnnotationPrefix) {
|
||||
if event.AdmissionWebhookMutationAnnotations == nil {
|
||||
event.AdmissionWebhookMutationAnnotations = map[string]string{}
|
||||
}
|
||||
event.AdmissionWebhookMutationAnnotations[k] = v
|
||||
}
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
||||
// findMissing checks for false values in the expectations map and returns them as a list
|
||||
func findMissing(expectations map[AuditEvent]bool) []AuditEvent {
|
||||
// auditEvent is a private wrapper on top of AuditEvent used by auditEventTracker
|
||||
type auditEvent struct {
|
||||
event AuditEvent
|
||||
found bool
|
||||
}
|
||||
|
||||
// auditEventTracker keeps track of AuditEvent expectations and marks matching events as found
|
||||
type auditEventTracker struct {
|
||||
events []*auditEvent
|
||||
}
|
||||
|
||||
// newAuditEventTracker creates a tracker that tracks whether expect events are found
|
||||
func newAuditEventTracker(expected []AuditEvent) *auditEventTracker {
|
||||
expectations := &auditEventTracker{events: []*auditEvent{}}
|
||||
for _, event := range expected {
|
||||
// we copy the references to the maps in event
|
||||
expectations.events = append(expectations.events, &auditEvent{event: event, found: false})
|
||||
}
|
||||
return expectations
|
||||
}
|
||||
|
||||
// Mark marks the given event as found if it's expected
|
||||
func (t *auditEventTracker) Mark(event AuditEvent) {
|
||||
for _, e := range t.events {
|
||||
if reflect.DeepEqual(e.event, event) {
|
||||
e.found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Missing reports events that are expected but not found
|
||||
func (t *auditEventTracker) Missing() []AuditEvent {
|
||||
var missing []AuditEvent
|
||||
for event, found := range expectations {
|
||||
if !found {
|
||||
missing = append(missing, event)
|
||||
for _, e := range t.events {
|
||||
if !e.found {
|
||||
missing = append(missing, e.event)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
|
2
vendor/k8s.io/kubernetes/test/utils/audit_dynamic.go
generated
vendored
2
vendor/k8s.io/kubernetes/test/utils/audit_dynamic.go
generated
vendored
@ -95,8 +95,6 @@ func (a *AuditTestServer) WaitForEvents(expected []AuditEvent) ([]AuditEvent, er
|
||||
var missing []AuditEvent
|
||||
err := wait.PollImmediate(50*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
var err error
|
||||
a.LockedEventList.RLock()
|
||||
defer a.LockedEventList.RUnlock()
|
||||
el := a.GetEventList()
|
||||
if len(el.Items) < 1 {
|
||||
return false, nil
|
||||
|
34
vendor/k8s.io/kubernetes/test/utils/create_resources.go
generated
vendored
34
vendor/k8s.io/kubernetes/test/utils/create_resources.go
generated
vendored
@ -232,3 +232,37 @@ func CreateResourceQuotaWithRetries(c clientset.Interface, namespace string, obj
|
||||
}
|
||||
return RetryWithExponentialBackOff(createFunc)
|
||||
}
|
||||
|
||||
func CreatePersistentVolumeWithRetries(c clientset.Interface, obj *v1.PersistentVolume) error {
|
||||
if obj == nil {
|
||||
return fmt.Errorf("Object provided to create is empty")
|
||||
}
|
||||
createFunc := func() (bool, error) {
|
||||
_, err := c.CoreV1().PersistentVolumes().Create(obj)
|
||||
if err == nil || apierrs.IsAlreadyExists(err) {
|
||||
return true, nil
|
||||
}
|
||||
if IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
|
||||
}
|
||||
return RetryWithExponentialBackOff(createFunc)
|
||||
}
|
||||
|
||||
func CreatePersistentVolumeClaimWithRetries(c clientset.Interface, namespace string, obj *v1.PersistentVolumeClaim) error {
|
||||
if obj == nil {
|
||||
return fmt.Errorf("Object provided to create is empty")
|
||||
}
|
||||
createFunc := func() (bool, error) {
|
||||
_, err := c.CoreV1().PersistentVolumeClaims(namespace).Create(obj)
|
||||
if err == nil || apierrs.IsAlreadyExists(err) {
|
||||
return true, nil
|
||||
}
|
||||
if IsRetryableAPIError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
|
||||
}
|
||||
return RetryWithExponentialBackOff(createFunc)
|
||||
}
|
||||
|
192
vendor/k8s.io/kubernetes/test/utils/image/manifest.go
generated
vendored
192
vendor/k8s.io/kubernetes/test/utils/image/manifest.go
generated
vendored
@ -20,17 +20,25 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// RegistryList holds public and private image registries
|
||||
type RegistryList struct {
|
||||
DockerLibraryRegistry string `yaml:"dockerLibraryRegistry"`
|
||||
E2eRegistry string `yaml:"e2eRegistry"`
|
||||
GcRegistry string `yaml:"gcRegistry"`
|
||||
PrivateRegistry string `yaml:"privateRegistry"`
|
||||
SampleRegistry string `yaml:"sampleRegistry"`
|
||||
GcAuthenticatedRegistry string `yaml:"gcAuthenticatedRegistry"`
|
||||
DockerLibraryRegistry string `yaml:"dockerLibraryRegistry"`
|
||||
DockerGluster string `yaml:"dockerGluster"`
|
||||
E2eRegistry string `yaml:"e2eRegistry"`
|
||||
InvalidRegistry string `yaml:"invalidRegistry"`
|
||||
GcRegistry string `yaml:"gcRegistry"`
|
||||
GcrReleaseRegistry string `yaml:"gcrReleaseRegistry"`
|
||||
GoogleContainerRegistry string `yaml:"googleContainerRegistry"`
|
||||
PrivateRegistry string `yaml:"privateRegistry"`
|
||||
SampleRegistry string `yaml:"sampleRegistry"`
|
||||
QuayK8sCSI string `yaml:"quayK8sCSI"`
|
||||
QuayIncubator string `yaml:"quayIncubator"`
|
||||
}
|
||||
|
||||
// Config holds an images registry, name, and version
|
||||
@ -57,11 +65,18 @@ func (i *Config) SetVersion(version string) {
|
||||
|
||||
func initReg() RegistryList {
|
||||
registry := RegistryList{
|
||||
DockerLibraryRegistry: "docker.io/library",
|
||||
E2eRegistry: "gcr.io/kubernetes-e2e-test-images",
|
||||
GcRegistry: "k8s.gcr.io",
|
||||
PrivateRegistry: "gcr.io/k8s-authenticated-test",
|
||||
SampleRegistry: "gcr.io/google-samples",
|
||||
GcAuthenticatedRegistry: "gcr.io/authenticated-image-pulling",
|
||||
DockerLibraryRegistry: "docker.io/library",
|
||||
DockerGluster: "docker.io/gluster",
|
||||
E2eRegistry: "gcr.io/kubernetes-e2e-test-images",
|
||||
InvalidRegistry: "invalid.com/invalid",
|
||||
GcRegistry: "k8s.gcr.io",
|
||||
GcrReleaseRegistry: "gcr.io/gke-release",
|
||||
GoogleContainerRegistry: "gcr.io/google-containers",
|
||||
PrivateRegistry: "gcr.io/k8s-authenticated-test",
|
||||
SampleRegistry: "gcr.io/google-samples",
|
||||
QuayK8sCSI: "quay.io/k8scsi",
|
||||
QuayIncubator: "quay.io/kubernetes_incubator",
|
||||
}
|
||||
repoList := os.Getenv("KUBE_TEST_REPO_LIST")
|
||||
if repoList == "" {
|
||||
@ -81,10 +96,17 @@ func initReg() RegistryList {
|
||||
}
|
||||
|
||||
var (
|
||||
registry = initReg()
|
||||
dockerLibraryRegistry = registry.DockerLibraryRegistry
|
||||
e2eRegistry = registry.E2eRegistry
|
||||
gcRegistry = registry.GcRegistry
|
||||
registry = initReg()
|
||||
dockerLibraryRegistry = registry.DockerLibraryRegistry
|
||||
dockerGluster = registry.DockerGluster
|
||||
e2eRegistry = registry.E2eRegistry
|
||||
gcAuthenticatedRegistry = registry.GcAuthenticatedRegistry
|
||||
gcRegistry = registry.GcRegistry
|
||||
gcrReleaseRegistry = registry.GcrReleaseRegistry
|
||||
googleContainerRegistry = registry.GoogleContainerRegistry
|
||||
invalidRegistry = registry.InvalidRegistry
|
||||
quayK8sCSI = registry.QuayK8sCSI
|
||||
quayIncubator = registry.QuayIncubator
|
||||
// PrivateRegistry is an image repository that requires authentication
|
||||
PrivateRegistry = registry.PrivateRegistry
|
||||
sampleRegistry = registry.SampleRegistry
|
||||
@ -94,18 +116,18 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
// CRDConversionWebhook image
|
||||
CRDConversionWebhook = iota
|
||||
// AdmissionWebhook image
|
||||
AdmissionWebhook
|
||||
// Agnhost image
|
||||
Agnhost
|
||||
Agnhost = iota
|
||||
// AgnhostPrivate image
|
||||
AgnhostPrivate
|
||||
// APIServer image
|
||||
APIServer
|
||||
// AppArmorLoader image
|
||||
AppArmorLoader
|
||||
// AuditProxy image
|
||||
AuditProxy
|
||||
// AuthenticatedAlpine image
|
||||
AuthenticatedAlpine
|
||||
// AuthenticatedWindowsNanoServer image
|
||||
AuthenticatedWindowsNanoServer
|
||||
// BusyBox image
|
||||
BusyBox
|
||||
// CheckMetadataConcealment image
|
||||
@ -118,44 +140,30 @@ const (
|
||||
Dnsutils
|
||||
// EchoServer image
|
||||
EchoServer
|
||||
// EntrypointTester image
|
||||
EntrypointTester
|
||||
// Etcd image
|
||||
Etcd
|
||||
// Fakegitserver image
|
||||
Fakegitserver
|
||||
// GBFrontend image
|
||||
GBFrontend
|
||||
// GBRedisSlave image
|
||||
GBRedisSlave
|
||||
// Hostexec image
|
||||
Hostexec
|
||||
// InClusterClient image
|
||||
InClusterClient
|
||||
// GlusterDynamicProvisioner image
|
||||
GlusterDynamicProvisioner
|
||||
// Httpd image
|
||||
Httpd
|
||||
// HttpdNew image
|
||||
HttpdNew
|
||||
// InvalidRegistryImage image
|
||||
InvalidRegistryImage
|
||||
// IpcUtils image
|
||||
IpcUtils
|
||||
// Iperf image
|
||||
Iperf
|
||||
// JessieDnsutils image
|
||||
JessieDnsutils
|
||||
// Kitten image
|
||||
Kitten
|
||||
// Liveness image
|
||||
Liveness
|
||||
// LogsGenerator image
|
||||
LogsGenerator
|
||||
// Mounttest image
|
||||
Mounttest
|
||||
// MounttestUser image
|
||||
MounttestUser
|
||||
// Nautilus image
|
||||
Nautilus
|
||||
// Net image
|
||||
Net
|
||||
// Netexec image
|
||||
Netexec
|
||||
// Nettest image
|
||||
Nettest
|
||||
// NFSProvisioner image
|
||||
NFSProvisioner
|
||||
// Nginx image
|
||||
Nginx
|
||||
// NginxNew image
|
||||
@ -164,27 +172,27 @@ const (
|
||||
Nonewprivs
|
||||
// NonRoot runs with a default user of 1234
|
||||
NonRoot
|
||||
// NoSnatTest image
|
||||
NoSnatTest
|
||||
// NoSnatTestProxy image
|
||||
NoSnatTestProxy
|
||||
// Pause - when these values are updated, also update cmd/kubelet/app/options/container_runtime.go
|
||||
// Pause image
|
||||
Pause
|
||||
// Perl image
|
||||
Perl
|
||||
// Porter image
|
||||
Porter
|
||||
// PortForwardTester image
|
||||
PortForwardTester
|
||||
// PrometheusDummyExporter image
|
||||
PrometheusDummyExporter
|
||||
// PrometheusToSd image
|
||||
PrometheusToSd
|
||||
// Redis image
|
||||
Redis
|
||||
// RegressionIssue74839 image
|
||||
RegressionIssue74839
|
||||
// ResourceConsumer image
|
||||
ResourceConsumer
|
||||
// ResourceController image
|
||||
ResourceController
|
||||
// ServeHostname image
|
||||
ServeHostname
|
||||
// SdDummyExporter image
|
||||
SdDummyExporter
|
||||
// StartupScript image
|
||||
StartupScript
|
||||
// TestWebserver image
|
||||
TestWebserver
|
||||
// VolumeNFSServer image
|
||||
@ -199,52 +207,45 @@ const (
|
||||
|
||||
func initImageConfigs() map[int]Config {
|
||||
configs := map[int]Config{}
|
||||
configs[CRDConversionWebhook] = Config{e2eRegistry, "crd-conversion-webhook", "1.13rev2"}
|
||||
configs[AdmissionWebhook] = Config{e2eRegistry, "webhook", "1.15v1"}
|
||||
configs[Agnhost] = Config{e2eRegistry, "agnhost", "1.0"}
|
||||
configs[Agnhost] = Config{e2eRegistry, "agnhost", "2.8"}
|
||||
configs[AgnhostPrivate] = Config{PrivateRegistry, "agnhost", "2.6"}
|
||||
configs[AuthenticatedAlpine] = Config{gcAuthenticatedRegistry, "alpine", "3.7"}
|
||||
configs[AuthenticatedWindowsNanoServer] = Config{gcAuthenticatedRegistry, "windows-nanoserver", "v1"}
|
||||
configs[APIServer] = Config{e2eRegistry, "sample-apiserver", "1.10"}
|
||||
configs[AppArmorLoader] = Config{e2eRegistry, "apparmor-loader", "1.0"}
|
||||
configs[AuditProxy] = Config{e2eRegistry, "audit-proxy", "1.0"}
|
||||
configs[BusyBox] = Config{dockerLibraryRegistry, "busybox", "1.29"}
|
||||
configs[CheckMetadataConcealment] = Config{e2eRegistry, "metadata-concealment", "1.2"}
|
||||
configs[CudaVectorAdd] = Config{e2eRegistry, "cuda-vector-add", "1.0"}
|
||||
configs[CudaVectorAdd2] = Config{e2eRegistry, "cuda-vector-add", "2.0"}
|
||||
configs[Dnsutils] = Config{e2eRegistry, "dnsutils", "1.1"}
|
||||
configs[EchoServer] = Config{e2eRegistry, "echoserver", "2.2"}
|
||||
configs[EntrypointTester] = Config{e2eRegistry, "entrypoint-tester", "1.0"}
|
||||
configs[Etcd] = Config{gcRegistry, "etcd", "3.3.10"}
|
||||
configs[Fakegitserver] = Config{e2eRegistry, "fakegitserver", "1.0"}
|
||||
configs[GBFrontend] = Config{sampleRegistry, "gb-frontend", "v6"}
|
||||
configs[GBRedisSlave] = Config{sampleRegistry, "gb-redisslave", "v3"}
|
||||
configs[Hostexec] = Config{e2eRegistry, "hostexec", "1.1"}
|
||||
configs[InClusterClient] = Config{e2eRegistry, "inclusterclient", "1.0"}
|
||||
configs[Etcd] = Config{gcRegistry, "etcd", "3.4.3"}
|
||||
configs[GlusterDynamicProvisioner] = Config{dockerGluster, "glusterdynamic-provisioner", "v1.0"}
|
||||
configs[Httpd] = Config{dockerLibraryRegistry, "httpd", "2.4.38-alpine"}
|
||||
configs[HttpdNew] = Config{dockerLibraryRegistry, "httpd", "2.4.39-alpine"}
|
||||
configs[InvalidRegistryImage] = Config{invalidRegistry, "alpine", "3.1"}
|
||||
configs[IpcUtils] = Config{e2eRegistry, "ipc-utils", "1.0"}
|
||||
configs[Iperf] = Config{e2eRegistry, "iperf", "1.0"}
|
||||
configs[JessieDnsutils] = Config{e2eRegistry, "jessie-dnsutils", "1.0"}
|
||||
configs[Kitten] = Config{e2eRegistry, "kitten", "1.0"}
|
||||
configs[Liveness] = Config{e2eRegistry, "liveness", "1.1"}
|
||||
configs[LogsGenerator] = Config{e2eRegistry, "logs-generator", "1.0"}
|
||||
configs[Mounttest] = Config{e2eRegistry, "mounttest", "1.0"}
|
||||
configs[MounttestUser] = Config{e2eRegistry, "mounttest-user", "1.0"}
|
||||
configs[Nautilus] = Config{e2eRegistry, "nautilus", "1.0"}
|
||||
configs[Net] = Config{e2eRegistry, "net", "1.0"}
|
||||
configs[Netexec] = Config{e2eRegistry, "netexec", "1.1"}
|
||||
configs[Nettest] = Config{e2eRegistry, "nettest", "1.0"}
|
||||
configs[NFSProvisioner] = Config{quayIncubator, "nfs-provisioner", "v2.2.2"}
|
||||
configs[Nginx] = Config{dockerLibraryRegistry, "nginx", "1.14-alpine"}
|
||||
configs[NginxNew] = Config{dockerLibraryRegistry, "nginx", "1.15-alpine"}
|
||||
configs[Nonewprivs] = Config{e2eRegistry, "nonewprivs", "1.0"}
|
||||
configs[NonRoot] = Config{e2eRegistry, "nonroot", "1.0"}
|
||||
configs[NoSnatTest] = Config{e2eRegistry, "no-snat-test", "1.0"}
|
||||
configs[NoSnatTestProxy] = Config{e2eRegistry, "no-snat-test-proxy", "1.0"}
|
||||
// Pause - when these values are updated, also update cmd/kubelet/app/options/container_runtime.go
|
||||
configs[Pause] = Config{gcRegistry, "pause", "3.1"}
|
||||
configs[Perl] = Config{dockerLibraryRegistry, "perl", "5.26"}
|
||||
configs[Porter] = Config{e2eRegistry, "porter", "1.0"}
|
||||
configs[PortForwardTester] = Config{e2eRegistry, "port-forward-tester", "1.0"}
|
||||
configs[Redis] = Config{e2eRegistry, "redis", "1.0"}
|
||||
configs[PrometheusDummyExporter] = Config{gcRegistry, "prometheus-dummy-exporter", "v0.1.0"}
|
||||
configs[PrometheusToSd] = Config{gcRegistry, "prometheus-to-sd", "v0.5.0"}
|
||||
configs[Redis] = Config{dockerLibraryRegistry, "redis", "5.0.5-alpine"}
|
||||
configs[RegressionIssue74839] = Config{e2eRegistry, "regression-issue-74839-amd64", "1.0"}
|
||||
configs[ResourceConsumer] = Config{e2eRegistry, "resource-consumer", "1.5"}
|
||||
configs[ResourceController] = Config{e2eRegistry, "resource-consumer-controller", "1.0"}
|
||||
configs[ServeHostname] = Config{e2eRegistry, "serve-hostname", "1.1"}
|
||||
configs[SdDummyExporter] = Config{gcRegistry, "sd-dummy-exporter", "v0.2.0"}
|
||||
configs[StartupScript] = Config{googleContainerRegistry, "startup-script", "v1"}
|
||||
configs[TestWebserver] = Config{e2eRegistry, "test-webserver", "1.0"}
|
||||
configs[VolumeNFSServer] = Config{e2eRegistry, "volume/nfs", "1.0"}
|
||||
configs[VolumeISCSIServer] = Config{e2eRegistry, "volume/iscsi", "2.0"}
|
||||
@ -277,3 +278,38 @@ func (i *Config) GetE2EImage() string {
|
||||
func GetPauseImageName() string {
|
||||
return GetE2EImage(Pause)
|
||||
}
|
||||
|
||||
// ReplaceRegistryInImageURL replaces the registry in the image URL with a custom one
|
||||
func ReplaceRegistryInImageURL(imageURL string) (string, error) {
|
||||
parts := strings.Split(imageURL, "/")
|
||||
countParts := len(parts)
|
||||
registryAndUser := strings.Join(parts[:countParts-1], "/")
|
||||
|
||||
switch registryAndUser {
|
||||
case "gcr.io/kubernetes-e2e-test-images":
|
||||
registryAndUser = e2eRegistry
|
||||
case "k8s.gcr.io":
|
||||
registryAndUser = gcRegistry
|
||||
case "gcr.io/k8s-authenticated-test":
|
||||
registryAndUser = PrivateRegistry
|
||||
case "gcr.io/google-samples":
|
||||
registryAndUser = sampleRegistry
|
||||
case "gcr.io/gke-release":
|
||||
registryAndUser = gcrReleaseRegistry
|
||||
case "docker.io/library":
|
||||
registryAndUser = dockerLibraryRegistry
|
||||
case "quay.io/k8scsi":
|
||||
registryAndUser = quayK8sCSI
|
||||
default:
|
||||
if countParts == 1 {
|
||||
// We assume we found an image from docker hub library
|
||||
// e.g. openjdk -> docker.io/library/openjdk
|
||||
registryAndUser = dockerLibraryRegistry
|
||||
break
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Registry: %s is missing in test/utils/image/manifest.go, please add the registry, otherwise the test will fail on air-gapped clusters", registryAndUser)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", registryAndUser, parts[countParts-1]), nil
|
||||
}
|
||||
|
1
vendor/k8s.io/kubernetes/test/utils/pod_store.go
generated
vendored
1
vendor/k8s.io/kubernetes/test/utils/pod_store.go
generated
vendored
@ -61,6 +61,7 @@ func NewPodStore(c clientset.Interface, namespace string, label labels.Selector,
|
||||
}
|
||||
return false, nil
|
||||
}); err != nil {
|
||||
close(stopCh)
|
||||
return nil, err
|
||||
}
|
||||
return &PodStore{Store: store, stopCh: stopCh, Reflector: reflector}, nil
|
||||
|
396
vendor/k8s.io/kubernetes/test/utils/runners.go
generated
vendored
396
vendor/k8s.io/kubernetes/test/utils/runners.go
generated
vendored
@ -28,6 +28,7 @@ import (
|
||||
apps "k8s.io/api/apps/v1"
|
||||
batch "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
storagev1beta1 "k8s.io/api/storage/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
@ -36,7 +37,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
@ -104,28 +107,31 @@ type RunObjectConfig interface {
|
||||
GetReplicas() int
|
||||
GetLabelValue(string) (string, bool)
|
||||
GetGroupResource() schema.GroupResource
|
||||
GetGroupVersionResource() schema.GroupVersionResource
|
||||
}
|
||||
|
||||
type RCConfig struct {
|
||||
Affinity *v1.Affinity
|
||||
Client clientset.Interface
|
||||
ScalesGetter scaleclient.ScalesGetter
|
||||
Image string
|
||||
Command []string
|
||||
Name string
|
||||
Namespace string
|
||||
PollInterval time.Duration
|
||||
Timeout time.Duration
|
||||
PodStatusFile *os.File
|
||||
Replicas int
|
||||
CpuRequest int64 // millicores
|
||||
CpuLimit int64 // millicores
|
||||
MemRequest int64 // bytes
|
||||
MemLimit int64 // bytes
|
||||
GpuLimit int64 // count
|
||||
ReadinessProbe *v1.Probe
|
||||
DNSPolicy *v1.DNSPolicy
|
||||
PriorityClassName string
|
||||
Affinity *v1.Affinity
|
||||
Client clientset.Interface
|
||||
ScalesGetter scaleclient.ScalesGetter
|
||||
Image string
|
||||
Command []string
|
||||
Name string
|
||||
Namespace string
|
||||
PollInterval time.Duration
|
||||
Timeout time.Duration
|
||||
PodStatusFile *os.File
|
||||
Replicas int
|
||||
CpuRequest int64 // millicores
|
||||
CpuLimit int64 // millicores
|
||||
MemRequest int64 // bytes
|
||||
MemLimit int64 // bytes
|
||||
GpuLimit int64 // count
|
||||
ReadinessProbe *v1.Probe
|
||||
DNSPolicy *v1.DNSPolicy
|
||||
PriorityClassName string
|
||||
TerminationGracePeriodSeconds *int64
|
||||
Lifecycle *v1.Lifecycle
|
||||
|
||||
// Env vars, set the same for every pod.
|
||||
Env map[string]string
|
||||
@ -155,6 +161,9 @@ type RCConfig struct {
|
||||
// Maximum allowable container failures. If exceeded, RunRC returns an error.
|
||||
// Defaults to replicas*0.1 if unspecified.
|
||||
MaxContainerFailures *int
|
||||
// Maximum allowed pod deletions count. If exceeded, RunRC returns an error.
|
||||
// Defaults to 0.
|
||||
MaxAllowedPodDeletions int
|
||||
|
||||
// If set to false starting RC will print progress, otherwise only errors will be printed.
|
||||
Silent bool
|
||||
@ -295,6 +304,10 @@ func (config *DeploymentConfig) GetGroupResource() schema.GroupResource {
|
||||
return extensionsinternal.Resource("deployments")
|
||||
}
|
||||
|
||||
func (config *DeploymentConfig) GetGroupVersionResource() schema.GroupVersionResource {
|
||||
return extensionsinternal.SchemeGroupVersion.WithResource("deployments")
|
||||
}
|
||||
|
||||
func (config *DeploymentConfig) create() error {
|
||||
deployment := &apps.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -313,13 +326,15 @@ func (config *DeploymentConfig) create() error {
|
||||
Annotations: config.Annotations,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: config.Affinity,
|
||||
Affinity: config.Affinity,
|
||||
TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil),
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: config.Name,
|
||||
Image: config.Image,
|
||||
Command: config.Command,
|
||||
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||||
Name: config.Name,
|
||||
Image: config.Image,
|
||||
Command: config.Command,
|
||||
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||||
Lifecycle: config.Lifecycle,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -371,6 +386,10 @@ func (config *ReplicaSetConfig) GetGroupResource() schema.GroupResource {
|
||||
return extensionsinternal.Resource("replicasets")
|
||||
}
|
||||
|
||||
func (config *ReplicaSetConfig) GetGroupVersionResource() schema.GroupVersionResource {
|
||||
return extensionsinternal.SchemeGroupVersion.WithResource("replicasets")
|
||||
}
|
||||
|
||||
func (config *ReplicaSetConfig) create() error {
|
||||
rs := &apps.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -389,13 +408,15 @@ func (config *ReplicaSetConfig) create() error {
|
||||
Annotations: config.Annotations,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: config.Affinity,
|
||||
Affinity: config.Affinity,
|
||||
TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil),
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: config.Name,
|
||||
Image: config.Image,
|
||||
Command: config.Command,
|
||||
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||||
Name: config.Name,
|
||||
Image: config.Image,
|
||||
Command: config.Command,
|
||||
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||||
Lifecycle: config.Lifecycle,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -443,6 +464,10 @@ func (config *JobConfig) GetGroupResource() schema.GroupResource {
|
||||
return batchinternal.Resource("jobs")
|
||||
}
|
||||
|
||||
func (config *JobConfig) GetGroupVersionResource() schema.GroupVersionResource {
|
||||
return batchinternal.SchemeGroupVersion.WithResource("jobs")
|
||||
}
|
||||
|
||||
func (config *JobConfig) create() error {
|
||||
job := &batch.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -457,12 +482,14 @@ func (config *JobConfig) create() error {
|
||||
Annotations: config.Annotations,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: config.Affinity,
|
||||
Affinity: config.Affinity,
|
||||
TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil),
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: config.Name,
|
||||
Image: config.Image,
|
||||
Command: config.Command,
|
||||
Name: config.Name,
|
||||
Image: config.Image,
|
||||
Command: config.Command,
|
||||
Lifecycle: config.Lifecycle,
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
@ -519,6 +546,10 @@ func (config *RCConfig) GetGroupResource() schema.GroupResource {
|
||||
return api.Resource("replicationcontrollers")
|
||||
}
|
||||
|
||||
func (config *RCConfig) GetGroupVersionResource() schema.GroupVersionResource {
|
||||
return api.SchemeGroupVersion.WithResource("replicationcontrollers")
|
||||
}
|
||||
|
||||
func (config *RCConfig) GetClient() clientset.Interface {
|
||||
return config.Client
|
||||
}
|
||||
@ -573,12 +604,13 @@ func (config *RCConfig) create() error {
|
||||
Command: config.Command,
|
||||
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||||
ReadinessProbe: config.ReadinessProbe,
|
||||
Lifecycle: config.Lifecycle,
|
||||
},
|
||||
},
|
||||
DNSPolicy: *config.DNSPolicy,
|
||||
NodeSelector: config.NodeSelector,
|
||||
Tolerations: config.Tolerations,
|
||||
TerminationGracePeriodSeconds: &one,
|
||||
TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(&one),
|
||||
PriorityClassName: config.PriorityClassName,
|
||||
},
|
||||
},
|
||||
@ -655,6 +687,9 @@ func (config *RCConfig) applyTo(template *v1.PodTemplateSpec) {
|
||||
if config.GpuLimit > 0 {
|
||||
template.Spec.Containers[0].Resources.Limits["nvidia.com/gpu"] = *resource.NewQuantity(config.GpuLimit, resource.DecimalSI)
|
||||
}
|
||||
if config.Lifecycle != nil {
|
||||
template.Spec.Containers[0].Lifecycle = config.Lifecycle
|
||||
}
|
||||
if len(config.Volumes) > 0 {
|
||||
template.Spec.Volumes = config.Volumes
|
||||
}
|
||||
@ -763,6 +798,7 @@ func (config *RCConfig) start() error {
|
||||
oldPods := make([]*v1.Pod, 0)
|
||||
oldRunning := 0
|
||||
lastChange := time.Now()
|
||||
podDeletionsCount := 0
|
||||
for oldRunning != config.Replicas {
|
||||
time.Sleep(interval)
|
||||
|
||||
@ -793,9 +829,10 @@ func (config *RCConfig) start() error {
|
||||
|
||||
diff := Diff(oldPods, pods)
|
||||
deletedPods := diff.DeletedPods()
|
||||
if len(deletedPods) != 0 {
|
||||
// There are some pods that have disappeared.
|
||||
err := fmt.Errorf("%d pods disappeared for %s: %v", len(deletedPods), config.Name, strings.Join(deletedPods, ", "))
|
||||
podDeletionsCount += len(deletedPods)
|
||||
if podDeletionsCount > config.MaxAllowedPodDeletions {
|
||||
// Number of pods which disappeared is over threshold
|
||||
err := fmt.Errorf("%d pods disappeared for %s: %v", podDeletionsCount, config.Name, strings.Join(deletedPods, ", "))
|
||||
config.RCConfigLog(err.Error())
|
||||
config.RCConfigLog(diff.String(sets.NewString()))
|
||||
return err
|
||||
@ -908,12 +945,22 @@ type TestNodePreparer interface {
|
||||
}
|
||||
|
||||
type PrepareNodeStrategy interface {
|
||||
// Modify pre-created Node objects before the test starts.
|
||||
PreparePatch(node *v1.Node) []byte
|
||||
// Create or modify any objects that depend on the node before the test starts.
|
||||
// Caller will re-try when http.StatusConflict error is returned.
|
||||
PrepareDependentObjects(node *v1.Node, client clientset.Interface) error
|
||||
// Clean up any node modifications after the test finishes.
|
||||
CleanupNode(node *v1.Node) *v1.Node
|
||||
// Clean up any objects that depend on the node after the test finishes.
|
||||
// Caller will re-try when http.StatusConflict error is returned.
|
||||
CleanupDependentObjects(nodeName string, client clientset.Interface) error
|
||||
}
|
||||
|
||||
type TrivialNodePrepareStrategy struct{}
|
||||
|
||||
var _ PrepareNodeStrategy = &TrivialNodePrepareStrategy{}
|
||||
|
||||
func (*TrivialNodePrepareStrategy) PreparePatch(*v1.Node) []byte {
|
||||
return []byte{}
|
||||
}
|
||||
@ -923,11 +970,21 @@ func (*TrivialNodePrepareStrategy) CleanupNode(node *v1.Node) *v1.Node {
|
||||
return &nodeCopy
|
||||
}
|
||||
|
||||
func (*TrivialNodePrepareStrategy) PrepareDependentObjects(node *v1.Node, client clientset.Interface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*TrivialNodePrepareStrategy) CleanupDependentObjects(nodeName string, client clientset.Interface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LabelNodePrepareStrategy struct {
|
||||
labelKey string
|
||||
labelValue string
|
||||
}
|
||||
|
||||
var _ PrepareNodeStrategy = &LabelNodePrepareStrategy{}
|
||||
|
||||
func NewLabelNodePrepareStrategy(labelKey string, labelValue string) *LabelNodePrepareStrategy {
|
||||
return &LabelNodePrepareStrategy{
|
||||
labelKey: labelKey,
|
||||
@ -949,6 +1006,148 @@ func (s *LabelNodePrepareStrategy) CleanupNode(node *v1.Node) *v1.Node {
|
||||
return nodeCopy
|
||||
}
|
||||
|
||||
func (*LabelNodePrepareStrategy) PrepareDependentObjects(node *v1.Node, client clientset.Interface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*LabelNodePrepareStrategy) CleanupDependentObjects(nodeName string, client clientset.Interface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeAllocatableStrategy fills node.status.allocatable and csiNode.spec.drivers[*].allocatable.
|
||||
// csiNode is created if it does not exist. On cleanup, any csiNode.spec.drivers[*].allocatable is
|
||||
// set to nil.
|
||||
type NodeAllocatableStrategy struct {
|
||||
// Node.status.allocatable to fill to all nodes.
|
||||
nodeAllocatable map[v1.ResourceName]string
|
||||
// Map <driver_name> -> VolumeNodeResources to fill into csiNode.spec.drivers[<driver_name>].
|
||||
csiNodeAllocatable map[string]*storagev1beta1.VolumeNodeResources
|
||||
// List of in-tree volume plugins migrated to CSI.
|
||||
migratedPlugins []string
|
||||
}
|
||||
|
||||
var _ PrepareNodeStrategy = &NodeAllocatableStrategy{}
|
||||
|
||||
func NewNodeAllocatableStrategy(nodeAllocatable map[v1.ResourceName]string, csiNodeAllocatable map[string]*storagev1beta1.VolumeNodeResources, migratedPlugins []string) *NodeAllocatableStrategy {
|
||||
return &NodeAllocatableStrategy{nodeAllocatable, csiNodeAllocatable, migratedPlugins}
|
||||
}
|
||||
|
||||
func (s *NodeAllocatableStrategy) PreparePatch(node *v1.Node) []byte {
|
||||
newNode := node.DeepCopy()
|
||||
for name, value := range s.nodeAllocatable {
|
||||
newNode.Status.Allocatable[name] = resource.MustParse(value)
|
||||
}
|
||||
|
||||
oldJSON, err := json.Marshal(node)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newJSON, err := json.Marshal(newNode)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
patch, err := strategicpatch.CreateTwoWayMergePatch(oldJSON, newJSON, v1.Node{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return patch
|
||||
}
|
||||
|
||||
func (s *NodeAllocatableStrategy) CleanupNode(node *v1.Node) *v1.Node {
|
||||
nodeCopy := node.DeepCopy()
|
||||
for name := range s.nodeAllocatable {
|
||||
delete(nodeCopy.Status.Allocatable, name)
|
||||
}
|
||||
return nodeCopy
|
||||
}
|
||||
|
||||
func (s *NodeAllocatableStrategy) createCSINode(nodeName string, client clientset.Interface) error {
|
||||
csiNode := &storagev1beta1.CSINode{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nodeName,
|
||||
Annotations: map[string]string{
|
||||
v1.MigratedPluginsAnnotationKey: strings.Join(s.migratedPlugins, ","),
|
||||
},
|
||||
},
|
||||
Spec: storagev1beta1.CSINodeSpec{
|
||||
Drivers: []storagev1beta1.CSINodeDriver{},
|
||||
},
|
||||
}
|
||||
|
||||
for driver, allocatable := range s.csiNodeAllocatable {
|
||||
d := storagev1beta1.CSINodeDriver{
|
||||
Name: driver,
|
||||
Allocatable: allocatable,
|
||||
NodeID: nodeName,
|
||||
}
|
||||
csiNode.Spec.Drivers = append(csiNode.Spec.Drivers, d)
|
||||
}
|
||||
|
||||
_, err := client.StorageV1beta1().CSINodes().Create(csiNode)
|
||||
if apierrs.IsAlreadyExists(err) {
|
||||
// Something created CSINode instance after we checked it did not exist.
|
||||
// Make the caller to re-try PrepareDependentObjects by returning Conflict error
|
||||
err = apierrs.NewConflict(storagev1beta1.Resource("csinodes"), nodeName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *NodeAllocatableStrategy) updateCSINode(csiNode *storagev1beta1.CSINode, client clientset.Interface) error {
|
||||
for driverName, allocatable := range s.csiNodeAllocatable {
|
||||
found := false
|
||||
for i, driver := range csiNode.Spec.Drivers {
|
||||
if driver.Name == driverName {
|
||||
found = true
|
||||
csiNode.Spec.Drivers[i].Allocatable = allocatable
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
d := storagev1beta1.CSINodeDriver{
|
||||
Name: driverName,
|
||||
Allocatable: allocatable,
|
||||
}
|
||||
|
||||
csiNode.Spec.Drivers = append(csiNode.Spec.Drivers, d)
|
||||
}
|
||||
}
|
||||
csiNode.Annotations[v1.MigratedPluginsAnnotationKey] = strings.Join(s.migratedPlugins, ",")
|
||||
|
||||
_, err := client.StorageV1beta1().CSINodes().Update(csiNode)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *NodeAllocatableStrategy) PrepareDependentObjects(node *v1.Node, client clientset.Interface) error {
|
||||
csiNode, err := client.StorageV1beta1().CSINodes().Get(node.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
return s.createCSINode(node.Name, client)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return s.updateCSINode(csiNode, client)
|
||||
}
|
||||
|
||||
func (s *NodeAllocatableStrategy) CleanupDependentObjects(nodeName string, client clientset.Interface) error {
|
||||
csiNode, err := client.StorageV1beta1().CSINodes().Get(nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for driverName := range s.csiNodeAllocatable {
|
||||
for i, driver := range csiNode.Spec.Drivers {
|
||||
if driver.Name == driverName {
|
||||
csiNode.Spec.Drivers[i].Allocatable = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.updateCSINode(csiNode, client)
|
||||
}
|
||||
|
||||
func DoPrepareNode(client clientset.Interface, node *v1.Node, strategy PrepareNodeStrategy) error {
|
||||
var err error
|
||||
patch := strategy.PreparePatch(node)
|
||||
@ -957,17 +1156,34 @@ func DoPrepareNode(client clientset.Interface, node *v1.Node, strategy PrepareNo
|
||||
}
|
||||
for attempt := 0; attempt < retries; attempt++ {
|
||||
if _, err = client.CoreV1().Nodes().Patch(node.Name, types.MergePatchType, []byte(patch)); err == nil {
|
||||
return nil
|
||||
break
|
||||
}
|
||||
if !apierrs.IsConflict(err) {
|
||||
return fmt.Errorf("Error while applying patch %v to Node %v: %v", string(patch), node.Name, err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("To many conflicts when applying patch %v to Node %v", string(patch), node.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Too many conflicts when applying patch %v to Node %v: %s", string(patch), node.Name, err)
|
||||
}
|
||||
|
||||
for attempt := 0; attempt < retries; attempt++ {
|
||||
if err = strategy.PrepareDependentObjects(node, client); err == nil {
|
||||
break
|
||||
}
|
||||
if !apierrs.IsConflict(err) {
|
||||
return fmt.Errorf("Error while preparing objects for node %s: %s", node.Name, err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Too many conflicts when creating objects for node %s: %s", node.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DoCleanupNode(client clientset.Interface, nodeName string, strategy PrepareNodeStrategy) error {
|
||||
var err error
|
||||
for attempt := 0; attempt < retries; attempt++ {
|
||||
node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
@ -978,14 +1194,31 @@ func DoCleanupNode(client clientset.Interface, nodeName string, strategy Prepare
|
||||
return nil
|
||||
}
|
||||
if _, err = client.CoreV1().Nodes().Update(updatedNode); err == nil {
|
||||
return nil
|
||||
break
|
||||
}
|
||||
if !apierrs.IsConflict(err) {
|
||||
return fmt.Errorf("Error when updating Node %v: %v", nodeName, err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("To many conflicts when trying to cleanup Node %v", nodeName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Too many conflicts when trying to cleanup Node %v: %s", nodeName, err)
|
||||
}
|
||||
|
||||
for attempt := 0; attempt < retries; attempt++ {
|
||||
err = strategy.CleanupDependentObjects(nodeName, client)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !apierrs.IsConflict(err) {
|
||||
return fmt.Errorf("Error when cleaning up Node %v objects: %v", nodeName, err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Too many conflicts when trying to cleanup Node %v objects: %s", nodeName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TestPodCreateStrategy func(client clientset.Interface, namespace string, podCount int) error
|
||||
@ -1077,6 +1310,70 @@ func CreatePod(client clientset.Interface, namespace string, podCount int, podTe
|
||||
return createError
|
||||
}
|
||||
|
||||
func CreatePodWithPersistentVolume(client clientset.Interface, namespace string, claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod, count int) error {
|
||||
var createError error
|
||||
lock := sync.Mutex{}
|
||||
createPodFunc := func(i int) {
|
||||
pvcName := fmt.Sprintf("pvc-%d", i)
|
||||
|
||||
// pv
|
||||
pv := factory(i)
|
||||
// bind to "pvc-$i"
|
||||
pv.Spec.ClaimRef = &v1.ObjectReference{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
Namespace: namespace,
|
||||
Name: pvcName,
|
||||
APIVersion: "v1",
|
||||
}
|
||||
pv.Status.Phase = v1.VolumeBound
|
||||
if err := CreatePersistentVolumeWithRetries(client, pv); err != nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
createError = fmt.Errorf("error creating PV: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// pvc
|
||||
pvc := claimTemplate.DeepCopy()
|
||||
pvc.Name = pvcName
|
||||
// bind to "pv-$i"
|
||||
pvc.Spec.VolumeName = pv.Name
|
||||
pvc.Status.Phase = v1.ClaimBound
|
||||
if err := CreatePersistentVolumeClaimWithRetries(client, namespace, pvc); err != nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
createError = fmt.Errorf("error creating PVC: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// pod
|
||||
pod := podTemplate.DeepCopy()
|
||||
pod.Spec.Volumes = []v1.Volume{
|
||||
{
|
||||
Name: "vol",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: pvcName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := makeCreatePod(client, namespace, pod); err != nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
createError = err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if count < 30 {
|
||||
workqueue.ParallelizeUntil(context.TODO(), count, count, createPodFunc)
|
||||
} else {
|
||||
workqueue.ParallelizeUntil(context.TODO(), 30, count, createPodFunc)
|
||||
}
|
||||
return createError
|
||||
}
|
||||
|
||||
func createController(client clientset.Interface, controllerName, namespace string, podCount int, podTemplate *v1.Pod) error {
|
||||
rc := &v1.ReplicationController{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1105,6 +1402,14 @@ func NewCustomCreatePodStrategy(podTemplate *v1.Pod) TestPodCreateStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
// volumeFactory creates an unique PersistentVolume for given integer.
|
||||
type volumeFactory func(uniqueID int) *v1.PersistentVolume
|
||||
|
||||
func NewCreatePodWithPersistentVolumeStrategy(claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod) TestPodCreateStrategy {
|
||||
return func(client clientset.Interface, namespace string, podCount int) error {
|
||||
return CreatePodWithPersistentVolume(client, namespace, claimTemplate, factory, podTemplate, podCount)
|
||||
}
|
||||
}
|
||||
func NewSimpleCreatePodStrategy() TestPodCreateStrategy {
|
||||
basePod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1249,6 +1554,13 @@ func attachConfigMaps(template *v1.PodTemplateSpec, configMapNames []string) {
|
||||
template.Spec.Containers[0].VolumeMounts = mounts
|
||||
}
|
||||
|
||||
func (config *RCConfig) getTerminationGracePeriodSeconds(defaultGrace *int64) *int64 {
|
||||
if config.TerminationGracePeriodSeconds == nil || *config.TerminationGracePeriodSeconds < 0 {
|
||||
return defaultGrace
|
||||
}
|
||||
return config.TerminationGracePeriodSeconds
|
||||
}
|
||||
|
||||
func attachServiceAccountTokenProjection(template *v1.PodTemplateSpec, name string) {
|
||||
template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts,
|
||||
v1.VolumeMount{
|
||||
@ -1382,7 +1694,7 @@ func (config *DaemonConfig) Run() error {
|
||||
return running == len(nodes.Items), nil
|
||||
})
|
||||
if err != nil {
|
||||
config.LogFunc("Timed out while waiting for DaemonsSet %v/%v to be running.", config.Namespace, config.Name)
|
||||
config.LogFunc("Timed out while waiting for DaemonSet %v/%v to be running.", config.Namespace, config.Name)
|
||||
} else {
|
||||
config.LogFunc("Created Daemon %v/%v", config.Namespace, config.Name)
|
||||
}
|
||||
|
18
vendor/k8s.io/kubernetes/test/utils/update_resources.go
generated
vendored
18
vendor/k8s.io/kubernetes/test/utils/update_resources.go
generated
vendored
@ -22,13 +22,13 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/scale"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
scaleclient "k8s.io/client-go/scale"
|
||||
"k8s.io/kubectl/pkg/scale"
|
||||
)
|
||||
|
||||
const (
|
||||
// Parameters for retrying updates/waits with linear backoff.
|
||||
// TODO: Try to move this to exponential backoff by modifying kubectl.Scale().
|
||||
// TODO: Try to move this to exponential backoff by modifying scale.Scale().
|
||||
updateRetryInterval = 5 * time.Second
|
||||
updateRetryTimeout = 1 * time.Minute
|
||||
waitRetryInterval = 5 * time.Second
|
||||
@ -45,17 +45,17 @@ func RetryErrorCondition(condition wait.ConditionFunc) wait.ConditionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func ScaleResourceWithRetries(scalesGetter scale.ScalesGetter, namespace, name string, size uint, gr schema.GroupResource) error {
|
||||
scaler := kubectl.NewScaler(scalesGetter)
|
||||
preconditions := &kubectl.ScalePrecondition{
|
||||
func ScaleResourceWithRetries(scalesGetter scaleclient.ScalesGetter, namespace, name string, size uint, gvr schema.GroupVersionResource) error {
|
||||
scaler := scale.NewScaler(scalesGetter)
|
||||
preconditions := &scale.ScalePrecondition{
|
||||
Size: -1,
|
||||
ResourceVersion: "",
|
||||
}
|
||||
waitForReplicas := kubectl.NewRetryParams(waitRetryInterval, waitRetryTimeout)
|
||||
cond := RetryErrorCondition(kubectl.ScaleCondition(scaler, preconditions, namespace, name, size, nil, gr))
|
||||
waitForReplicas := scale.NewRetryParams(waitRetryInterval, waitRetryTimeout)
|
||||
cond := RetryErrorCondition(scale.ScaleCondition(scaler, preconditions, namespace, name, size, nil, gvr))
|
||||
err := wait.PollImmediate(updateRetryInterval, updateRetryTimeout, cond)
|
||||
if err == nil {
|
||||
err = kubectl.WaitForScaleHasDesiredReplicas(scalesGetter, gr, name, namespace, size, waitForReplicas)
|
||||
err = scale.WaitForScaleHasDesiredReplicas(scalesGetter, gvr.GroupResource(), name, namespace, size, waitForReplicas)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while scaling %s to %d replicas: %v", name, size, err)
|
||||
|
Reference in New Issue
Block a user