Merge pull request #73285 from tallclair/typed-runtimeclass

Migrate RuntimeClass support to the generated typed client
pull/564/head
Kubernetes Prow Robot 2019-02-04 16:13:30 -08:00 committed by GitHub
commit 6a149864fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 98 additions and 134 deletions

View File

@ -122,7 +122,6 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/server/healthz:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server/healthz:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/authentication/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/authentication/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/authorization/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/authorization/v1beta1:go_default_library",
@ -136,6 +135,7 @@ go_library(
"//staging/src/k8s.io/cloud-provider:go_default_library", "//staging/src/k8s.io/cloud-provider:go_default_library",
"//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned:go_default_library", "//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned:go_default_library",
"//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library", "//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library",
"//staging/src/k8s.io/node-api/pkg/client/clientset/versioned:go_default_library",
"//vendor/github.com/coreos/go-systemd/daemon:go_default_library", "//vendor/github.com/coreos/go-systemd/daemon:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library",

View File

@ -48,7 +48,6 @@ import (
"k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/healthz"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/flag" "k8s.io/apiserver/pkg/util/flag"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
@ -95,6 +94,7 @@ import (
"k8s.io/kubernetes/pkg/util/rlimit" "k8s.io/kubernetes/pkg/util/rlimit"
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/pkg/version/verflag" "k8s.io/kubernetes/pkg/version/verflag"
nodeapiclientset "k8s.io/node-api/pkg/client/clientset/versioned"
"k8s.io/utils/exec" "k8s.io/utils/exec"
"k8s.io/utils/nsenter" "k8s.io/utils/nsenter"
) )
@ -545,12 +545,11 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies, stopCh <-chan
switch { switch {
case standaloneMode: case standaloneMode:
kubeDeps.KubeClient = nil kubeDeps.KubeClient = nil
kubeDeps.DynamicKubeClient = nil
kubeDeps.EventClient = nil kubeDeps.EventClient = nil
kubeDeps.HeartbeatClient = nil kubeDeps.HeartbeatClient = nil
klog.Warningf("standalone mode, no API client") klog.Warningf("standalone mode, no API client")
case kubeDeps.KubeClient == nil, kubeDeps.EventClient == nil, kubeDeps.HeartbeatClient == nil, kubeDeps.DynamicKubeClient == nil: case kubeDeps.KubeClient == nil, kubeDeps.EventClient == nil, kubeDeps.HeartbeatClient == nil:
clientConfig, closeAllConns, err := buildKubeletClientConfig(s, nodeName) clientConfig, closeAllConns, err := buildKubeletClientConfig(s, nodeName)
if err != nil { if err != nil {
return err return err
@ -562,11 +561,6 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies, stopCh <-chan
return fmt.Errorf("failed to initialize kubelet client: %v", err) return fmt.Errorf("failed to initialize kubelet client: %v", err)
} }
kubeDeps.DynamicKubeClient, err = dynamic.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("failed to initialize kubelet dynamic client: %v", err)
}
// make a separate client for events // make a separate client for events
eventClientConfig := *clientConfig eventClientConfig := *clientConfig
eventClientConfig.QPS = float32(s.EventRecordQPS) eventClientConfig.QPS = float32(s.EventRecordQPS)
@ -593,12 +587,17 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies, stopCh <-chan
} }
// CRDs are JSON only, and client renegotiation for streaming is not correct as per #67803 // CRDs are JSON only, and client renegotiation for streaming is not correct as per #67803
csiClientConfig := restclient.CopyConfig(clientConfig) crdClientConfig := restclient.CopyConfig(clientConfig)
csiClientConfig.ContentType = "application/json" crdClientConfig.ContentType = "application/json"
kubeDeps.CSIClient, err = csiclientset.NewForConfig(csiClientConfig) kubeDeps.CSIClient, err = csiclientset.NewForConfig(crdClientConfig)
if err != nil { if err != nil {
return fmt.Errorf("failed to initialize kubelet storage client: %v", err) return fmt.Errorf("failed to initialize kubelet storage client: %v", err)
} }
kubeDeps.NodeAPIClient, err = nodeapiclientset.NewForConfig(crdClientConfig)
if err != nil {
return fmt.Errorf("failed to initialize kubelet node-api client: %v", err)
}
} }
// If the kubelet config controller is available, and dynamic config is enabled, start the config and status sync loops // If the kubelet config controller is available, and dynamic config is enabled, start the config and status sync loops

View File

@ -131,7 +131,6 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
@ -141,6 +140,7 @@ go_library(
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library", "//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/cloud-provider:go_default_library", "//staging/src/k8s.io/cloud-provider:go_default_library",
"//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned:go_default_library", "//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned:go_default_library",
"//staging/src/k8s.io/node-api/pkg/client/clientset/versioned:go_default_library",
"//third_party/forked/golang/expansion:go_default_library", "//third_party/forked/golang/expansion:go_default_library",
"//vendor/github.com/golang/groupcache/lru:go_default_library", "//vendor/github.com/golang/groupcache/lru:go_default_library",
"//vendor/github.com/google/cadvisor/events:go_default_library", "//vendor/github.com/google/cadvisor/events:go_default_library",

View File

@ -43,7 +43,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
@ -114,6 +113,7 @@ import (
"k8s.io/kubernetes/pkg/util/oom" "k8s.io/kubernetes/pkg/util/oom"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/csi" "k8s.io/kubernetes/pkg/volume/csi"
nodeapiclientset "k8s.io/node-api/pkg/client/clientset/versioned"
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
"k8s.io/utils/integer" "k8s.io/utils/integer"
) )
@ -249,7 +249,7 @@ type Dependencies struct {
OnHeartbeatFailure func() OnHeartbeatFailure func()
KubeClient clientset.Interface KubeClient clientset.Interface
CSIClient csiclientset.Interface CSIClient csiclientset.Interface
DynamicKubeClient dynamic.Interface NodeAPIClient nodeapiclientset.Interface
Mounter mount.Interface Mounter mount.Interface
OOMAdjuster *oom.OOMAdjuster OOMAdjuster *oom.OOMAdjuster
OSInterface kubecontainer.OSInterface OSInterface kubecontainer.OSInterface
@ -658,8 +658,8 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
} }
klet.runtimeService = runtimeService klet.runtimeService = runtimeService
if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) && kubeDeps.DynamicKubeClient != nil { if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) && kubeDeps.NodeAPIClient != nil {
klet.runtimeClassManager = runtimeclass.NewManager(kubeDeps.DynamicKubeClient) klet.runtimeClassManager = runtimeclass.NewManager(kubeDeps.NodeAPIClient)
} }
runtime, err := kuberuntime.NewKubeGenericRuntimeManager( runtime, err := kuberuntime.NewKubeGenericRuntimeManager(
@ -1426,7 +1426,7 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
// Start syncing RuntimeClasses if enabled. // Start syncing RuntimeClasses if enabled.
if kl.runtimeClassManager != nil { if kl.runtimeClassManager != nil {
go kl.runtimeClassManager.Run(wait.NeverStop) kl.runtimeClassManager.Start(wait.NeverStop)
} }
// Start the pod lifecycle event generator. // Start the pod lifecycle event generator.

View File

@ -61,8 +61,8 @@ func TestCreatePodSandbox(t *testing.T) {
func TestCreatePodSandbox_RuntimeClass(t *testing.T) { func TestCreatePodSandbox_RuntimeClass(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClass, true)() defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClass, true)()
rcm := runtimeclass.NewManager(rctest.NewPopulatedDynamicClient()) rcm := runtimeclass.NewManager(rctest.NewPopulatedClient())
defer rctest.StartManagerSync(t, rcm)() defer rctest.StartManagerSync(rcm)()
fakeRuntime, _, m, err := createTestRuntimeManager() fakeRuntime, _, m, err := createTestRuntimeManager()
require.NoError(t, err) require.NoError(t, err)

View File

@ -7,12 +7,10 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library", "//staging/src/k8s.io/node-api/pkg/client/clientset/versioned:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library", "//staging/src/k8s.io/node-api/pkg/client/informers/externalversions:go_default_library",
"//staging/src/k8s.io/node-api/pkg/client/listers/node/v1alpha1:go_default_library",
], ],
) )

View File

@ -20,12 +20,10 @@ import (
"fmt" "fmt"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic" nodeapiclient "k8s.io/node-api/pkg/client/clientset/versioned"
"k8s.io/client-go/tools/cache" nodeapiinformer "k8s.io/node-api/pkg/client/informers/externalversions"
nodev1alpha1 "k8s.io/node-api/pkg/client/listers/node/v1alpha1"
) )
var ( var (
@ -38,33 +36,36 @@ var (
// Manager caches RuntimeClass API objects, and provides accessors to the Kubelet. // Manager caches RuntimeClass API objects, and provides accessors to the Kubelet.
type Manager struct { type Manager struct {
informer cache.SharedInformer informerFactory nodeapiinformer.SharedInformerFactory
lister nodev1alpha1.RuntimeClassLister
} }
// NewManager returns a new RuntimeClass Manager. Run must be called before the manager can be used. // NewManager returns a new RuntimeClass Manager. Run must be called before the manager can be used.
func NewManager(client dynamic.Interface) *Manager { func NewManager(client nodeapiclient.Interface) *Manager {
rc := client.Resource(runtimeClassGVR) const resyncPeriod = 0
lw := &cache.ListWatch{ factory := nodeapiinformer.NewSharedInformerFactory(client, resyncPeriod)
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { lister := factory.Node().V1alpha1().RuntimeClasses().Lister()
return rc.List(options)
},
WatchFunc: rc.Watch,
}
informer := cache.NewSharedInformer(lw, &unstructured.Unstructured{}, 0)
return &Manager{ return &Manager{
informer: informer, informerFactory: factory,
lister: lister,
} }
} }
// Run starts syncing the RuntimeClass cache with the apiserver. // Start starts syncing the RuntimeClass cache with the apiserver.
func (m *Manager) Run(stopCh <-chan struct{}) { func (m *Manager) Start(stopCh <-chan struct{}) {
m.informer.Run(stopCh) m.informerFactory.Start(stopCh)
}
// WaitForCacheSync exposes the WaitForCacheSync method on the informer factory for testing
// purposes.
func (m *Manager) WaitForCacheSync(stopCh <-chan struct{}) {
m.informerFactory.WaitForCacheSync(stopCh)
} }
// LookupRuntimeHandler returns the RuntimeHandler string associated with the given RuntimeClass // LookupRuntimeHandler returns the RuntimeHandler string associated with the given RuntimeClass
// name (or the default of "" for nil). If the RuntimeClass is not found, it returns an // name (or the default of "" for nil). If the RuntimeClass is not found, it returns an
// apierrors.NotFound error. // errors.NotFound error.
func (m *Manager) LookupRuntimeHandler(runtimeClassName *string) (string, error) { func (m *Manager) LookupRuntimeHandler(runtimeClassName *string) (string, error) {
if runtimeClassName == nil || *runtimeClassName == "" { if runtimeClassName == nil || *runtimeClassName == "" {
// The default RuntimeClass always resolves to the empty runtime handler. // The default RuntimeClass always resolves to the empty runtime handler.
@ -72,26 +73,18 @@ func (m *Manager) LookupRuntimeHandler(runtimeClassName *string) (string, error)
} }
name := *runtimeClassName name := *runtimeClassName
item, exists, err := m.informer.GetStore().GetByKey(name)
rc, err := m.lister.Get(name)
if err != nil { if err != nil {
if errors.IsNotFound(err) {
return "", err
}
return "", fmt.Errorf("Failed to lookup RuntimeClass %s: %v", name, err) return "", fmt.Errorf("Failed to lookup RuntimeClass %s: %v", name, err)
} }
if !exists {
return "", errors.NewNotFound(schema.GroupResource{
Group: runtimeClassGVR.Group,
Resource: runtimeClassGVR.Resource,
}, name)
}
rc, ok := item.(*unstructured.Unstructured) handler := rc.Spec.RuntimeHandler
if !ok { if handler == nil {
return "", fmt.Errorf("unexpected RuntimeClass type %T", item) return "", nil
} }
return *handler, nil
handler, _, err := unstructured.NestedString(rc.Object, "spec", "runtimeHandler")
if err != nil {
return "", fmt.Errorf("Invalid RuntimeClass object: %v", err)
}
return handler, nil
} }

View File

@ -21,6 +21,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/kubelet/runtimeclass" "k8s.io/kubernetes/pkg/kubelet/runtimeclass"
rctest "k8s.io/kubernetes/pkg/kubelet/runtimeclass/testing" rctest "k8s.io/kubernetes/pkg/kubelet/runtimeclass/testing"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
@ -36,12 +37,11 @@ func TestLookupRuntimeHandler(t *testing.T) {
{rcn: pointer.StringPtr(""), expected: ""}, {rcn: pointer.StringPtr(""), expected: ""},
{rcn: pointer.StringPtr(rctest.EmptyRuntimeClass), expected: ""}, {rcn: pointer.StringPtr(rctest.EmptyRuntimeClass), expected: ""},
{rcn: pointer.StringPtr(rctest.SandboxRuntimeClass), expected: "kata-containers"}, {rcn: pointer.StringPtr(rctest.SandboxRuntimeClass), expected: "kata-containers"},
{rcn: pointer.StringPtr(rctest.InvalidRuntimeClass), expectError: true},
{rcn: pointer.StringPtr("phantom"), expectError: true}, {rcn: pointer.StringPtr("phantom"), expectError: true},
} }
manager := runtimeclass.NewManager(rctest.NewPopulatedDynamicClient()) manager := runtimeclass.NewManager(rctest.NewPopulatedClient())
defer rctest.StartManagerSync(t, manager)() defer rctest.StartManagerSync(manager)()
for _, test := range tests { for _, test := range tests {
tname := "nil" tname := "nil"

View File

@ -7,14 +7,10 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/kubelet/runtimeclass:go_default_library", "//pkg/kubelet/runtimeclass:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/node-api/pkg/apis/node/v1alpha1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/node-api/pkg/client/clientset/versioned:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/node-api/pkg/client/clientset/versioned/fake:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/dynamic/fake:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
], ],
) )

View File

@ -17,18 +17,11 @@ limitations under the License.
package testing package testing
import ( import (
"testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"time"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
fakedynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/kubernetes/pkg/kubelet/runtimeclass" "k8s.io/kubernetes/pkg/kubelet/runtimeclass"
"k8s.io/utils/pointer" nodev1alpha1 "k8s.io/node-api/pkg/apis/node/v1alpha1"
nodeapiclient "k8s.io/node-api/pkg/client/clientset/versioned"
nodeapifake "k8s.io/node-api/pkg/client/clientset/versioned/fake"
) )
const ( const (
@ -39,64 +32,38 @@ const (
// EmptyRuntimeClass is a valid RuntimeClass without a handler pre-populated in the populated dynamic client. // EmptyRuntimeClass is a valid RuntimeClass without a handler pre-populated in the populated dynamic client.
EmptyRuntimeClass = "native" EmptyRuntimeClass = "native"
// InvalidRuntimeClass is an invalid RuntimeClass pre-populated in the populated dynamic client.
InvalidRuntimeClass = "foo"
) )
// NewPopulatedDynamicClient creates a dynamic client for use with the runtimeclass.Manager, // NewPopulatedClient creates a node-api client for use with the runtimeclass.Manager,
// and populates it with a few test RuntimeClass objects. // and populates it with a few test RuntimeClass objects.
func NewPopulatedDynamicClient() dynamic.Interface { func NewPopulatedClient() nodeapiclient.Interface {
invalidRC := NewUnstructuredRuntimeClass(InvalidRuntimeClass, "") return nodeapifake.NewSimpleClientset(
invalidRC.Object["spec"].(map[string]interface{})["runtimeHandler"] = true NewRuntimeClass(EmptyRuntimeClass, ""),
NewRuntimeClass(SandboxRuntimeClass, SandboxRuntimeHandler),
client := fakedynamic.NewSimpleDynamicClient(runtime.NewScheme(),
NewUnstructuredRuntimeClass(EmptyRuntimeClass, ""),
NewUnstructuredRuntimeClass(SandboxRuntimeClass, SandboxRuntimeHandler),
invalidRC,
) )
return client
} }
// StartManagerSync runs the manager, and waits for startup by polling for the expected "native" // StartManagerSync starts the manager, and waits for the informer cache to sync.
// RuntimeClass to be populated. Returns a function to stop the manager, which should be called with // Returns a function to stop the manager, which should be called with a defer:
// a defer:
// defer StartManagerSync(t, m)() // defer StartManagerSync(t, m)()
// Any errors are considered fatal to the test. func StartManagerSync(m *runtimeclass.Manager) func() {
func StartManagerSync(t *testing.T, m *runtimeclass.Manager) func() {
stopCh := make(chan struct{}) stopCh := make(chan struct{})
go m.Run(stopCh) m.Start(stopCh)
m.WaitForCacheSync(stopCh)
// Wait for informer to populate.
err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
_, err := m.LookupRuntimeHandler(pointer.StringPtr(EmptyRuntimeClass))
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
})
require.NoError(t, err, "Failed to start manager")
return func() { return func() {
close(stopCh) close(stopCh)
} }
} }
// NewUnstructuredRuntimeClass is a helper to generate an unstructured RuntimeClass resource with // NewRuntimeClass is a helper to generate a RuntimeClass resource with
// the given name & handler. // the given name & handler.
func NewUnstructuredRuntimeClass(name, handler string) *unstructured.Unstructured { func NewRuntimeClass(name, handler string) *nodev1alpha1.RuntimeClass {
return &unstructured.Unstructured{ return &nodev1alpha1.RuntimeClass{
Object: map[string]interface{}{ ObjectMeta: metav1.ObjectMeta{
"apiVersion": "node.k8s.io/v1alpha1", Name: name,
"kind": "RuntimeClass", },
"metadata": map[string]interface{}{ Spec: nodev1alpha1.RuntimeClassSpec{
"name": name, RuntimeHandler: &handler,
},
"spec": map[string]interface{}{
"runtimeHandler": handler,
},
}, },
} }
} }

View File

@ -129,6 +129,7 @@ go_library(
"//staging/src/k8s.io/client-go/util/retry:go_default_library", "//staging/src/k8s.io/client-go/util/retry:go_default_library",
"//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned:go_default_library", "//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned:go_default_library",
"//staging/src/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset:go_default_library", "//staging/src/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset:go_default_library",
"//staging/src/k8s.io/node-api/pkg/client/clientset/versioned:go_default_library",
"//test/e2e/framework/ginkgowrapper:go_default_library", "//test/e2e/framework/ginkgowrapper:go_default_library",
"//test/e2e/framework/metrics:go_default_library", "//test/e2e/framework/metrics:go_default_library",
"//test/e2e/framework/testfiles:go_default_library", "//test/e2e/framework/testfiles:go_default_library",

View File

@ -52,6 +52,7 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/test/e2e/framework/metrics" "k8s.io/kubernetes/test/e2e/framework/metrics"
testutils "k8s.io/kubernetes/test/utils" testutils "k8s.io/kubernetes/test/utils"
nodeapiclient "k8s.io/node-api/pkg/client/clientset/versioned"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -78,6 +79,7 @@ type Framework struct {
KubemarkExternalClusterClientSet clientset.Interface KubemarkExternalClusterClientSet clientset.Interface
APIExtensionsClientSet apiextensionsclient.Interface APIExtensionsClientSet apiextensionsclient.Interface
CSIClientSet csi.Interface CSIClientSet csi.Interface
NodeAPIClientSet nodeapiclient.Interface
InternalClientset *internalclientset.Clientset InternalClientset *internalclientset.Clientset
AggregatorClient *aggregatorclient.Clientset AggregatorClient *aggregatorclient.Clientset
@ -197,6 +199,9 @@ func (f *Framework) BeforeEach() {
jsonConfig.ContentType = "application/json" jsonConfig.ContentType = "application/json"
f.CSIClientSet, err = csi.NewForConfig(jsonConfig) f.CSIClientSet, err = csi.NewForConfig(jsonConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// node.k8s.io is also based on CRD
f.NodeAPIClientSet, err = nodeapiclient.NewForConfig(jsonConfig)
Expect(err).NotTo(HaveOccurred())
// create scales getter, set GroupVersion and NegotiatedSerializer to default values // create scales getter, set GroupVersion and NegotiatedSerializer to default values
// as they are required when creating a REST client. // as they are required when creating a REST client.

View File

@ -69,14 +69,15 @@ var _ = SIGDescribe("RuntimeClass [Feature:RuntimeClass]", func() {
It("should reject a Pod requesting a deleted RuntimeClass", func() { It("should reject a Pod requesting a deleted RuntimeClass", func() {
rcName := createRuntimeClass(f, "delete-me", "") rcName := createRuntimeClass(f, "delete-me", "")
rcClient := f.NodeAPIClientSet.NodeV1alpha1().RuntimeClasses()
By("Deleting RuntimeClass "+rcName, func() { By("Deleting RuntimeClass "+rcName, func() {
err := f.DynamicClient.Resource(runtimeClassGVR).Delete(rcName, nil) err := rcClient.Delete(rcName, nil)
framework.ExpectNoError(err, "failed to delete RuntimeClass %s", rcName) framework.ExpectNoError(err, "failed to delete RuntimeClass %s", rcName)
By("Waiting for the RuntimeClass to disappear") By("Waiting for the RuntimeClass to disappear")
framework.ExpectNoError(wait.PollImmediate(framework.Poll, time.Minute, func() (bool, error) { framework.ExpectNoError(wait.PollImmediate(framework.Poll, time.Minute, func() (bool, error) {
_, err := f.DynamicClient.Resource(runtimeClassGVR).Get(rcName, metav1.GetOptions{}) _, err := rcClient.Get(rcName, metav1.GetOptions{})
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
return true, nil // done return true, nil // done
} }
@ -132,18 +133,22 @@ var _ = SIGDescribe("RuntimeClass [Feature:RuntimeClass]", func() {
rcName := createRuntimeClass(f, "valid", "") rcName := createRuntimeClass(f, "valid", "")
pod := createRuntimeClassPod(f, rcName) pod := createRuntimeClassPod(f, rcName)
expectPodSuccess(f, pod)
// Before the pod can be run, the RuntimeClass informer must time out, by which time the Kubelet
// will probably be in a backoff state, so the pod can take a long time to start.
framework.ExpectNoError(framework.WaitForPodSuccessInNamespaceSlow(
f.ClientSet, pod.Name, f.Namespace.Name))
}) })
// TODO(tallclair): Test an actual configured non-default runtimeHandler. // TODO(tallclair): Test an actual configured non-default runtimeHandler.
}) })
// createRuntimeClass generates a RuntimeClass with the desired handler and a "namespaced" name, // createRuntimeClass generates a RuntimeClass with the desired handler and a "namespaced" name,
// synchronously creates it with the dynamic client, and returns the resulting name. // synchronously creates it, and returns the generated name.
func createRuntimeClass(f *framework.Framework, name, handler string) string { func createRuntimeClass(f *framework.Framework, name, handler string) string {
uniqueName := fmt.Sprintf("%s-%s", f.Namespace.Name, name) uniqueName := fmt.Sprintf("%s-%s", f.Namespace.Name, name)
rc := runtimeclasstest.NewUnstructuredRuntimeClass(uniqueName, handler) rc := runtimeclasstest.NewRuntimeClass(uniqueName, handler)
rc, err := f.DynamicClient.Resource(runtimeClassGVR).Create(rc, metav1.CreateOptions{}) rc, err := f.NodeAPIClientSet.NodeV1alpha1().RuntimeClasses().Create(rc)
framework.ExpectNoError(err, "failed to create RuntimeClass resource") framework.ExpectNoError(err, "failed to create RuntimeClass resource")
return rc.GetName() return rc.GetName()
} }