Merge pull request #28807 from Random-Liu/e2e-node-e2e-share-test
Automatic merge from submit-queue Node E2E: Make it possible to share test between e2e and node e2e This PR is part of the plan to improve node e2e test coverage. * Now to improve test coverage, we have to copy test from e2e to node e2e. * When adding a new test, we have to decide its destiny at the very beginning - whether it is a node e2e or e2e. This PR makes it possible to share test between e2e and node e2e. By leveraging the mechanism of ginkgo, as long as we can import the test package in the test suite, the corresponding `Describe` will be run to initialize the global variable `_`, and the test will be inserted into the test suite. (See https://github.com/onsi/composition-ginkgo-example) In the future, we just need to use the framework to write the test, and put the test into `test/e2e/node`, then it will be automatically shared by the 2 test suites. This PR: 1) Refactored the framework to make it automatically differentiate e2e and node e2e (Mainly refactored the `PodClient` and the apiserver client initialization). 2) Created a new directory `test/e2e/node` and make it shared by e2e and node e2e. 3) Moved `container_probe.go` into `test/e2e/node` to verify the change. @kubernetes/sig-node []()
This commit is contained in:
@@ -14,17 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -33,36 +31,23 @@ import (
|
||||
|
||||
const (
|
||||
probTestContainerName = "test-webserver"
|
||||
probTestInitialDelaySeconds = 30
|
||||
probTestInitialDelaySeconds = 15
|
||||
)
|
||||
|
||||
var _ = framework.KubeDescribe("Probing container", func() {
|
||||
f := framework.NewDefaultFramework("container-probe")
|
||||
var podClient client.PodInterface
|
||||
var podClient *framework.PodClient
|
||||
probe := webserverProbeBuilder{}
|
||||
|
||||
BeforeEach(func() {
|
||||
podClient = f.Client.Pods(f.Namespace.Name)
|
||||
podClient = f.PodClient()
|
||||
})
|
||||
|
||||
It("with readiness probe should not be ready before initial delay and never restart [Conformance]", func() {
|
||||
p, err := podClient.Create(makePodSpec(probe.withInitialDelay().build(), nil))
|
||||
framework.ExpectNoError(err)
|
||||
p := podClient.Create(makePodSpec(probe.withInitialDelay().build(), nil))
|
||||
f.WaitForPodReady(p.Name)
|
||||
|
||||
Expect(wait.Poll(framework.Poll, 240*time.Second, func() (bool, error) {
|
||||
p, err := podClient.Get(p.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ready := api.IsPodReady(p)
|
||||
if !ready {
|
||||
framework.Logf("pod is not yet ready; pod has phase %q.", p.Status.Phase)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})).NotTo(HaveOccurred(), "pod never became ready")
|
||||
|
||||
p, err = podClient.Get(p.Name)
|
||||
p, err := podClient.Get(p.Name)
|
||||
framework.ExpectNoError(err)
|
||||
isReady, err := framework.PodRunningReady(p)
|
||||
framework.ExpectNoError(err)
|
||||
@@ -86,21 +71,16 @@ var _ = framework.KubeDescribe("Probing container", func() {
|
||||
})
|
||||
|
||||
It("with readiness probe that fails should never be ready and never restart [Conformance]", func() {
|
||||
p, err := podClient.Create(makePodSpec(probe.withFailing().build(), nil))
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
err = wait.Poll(framework.Poll, 180*time.Second, func() (bool, error) {
|
||||
p := podClient.Create(makePodSpec(probe.withFailing().build(), nil))
|
||||
Consistently(func() (bool, error) {
|
||||
p, err := podClient.Get(p.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return api.IsPodReady(p), nil
|
||||
})
|
||||
if err != wait.ErrWaitTimeout {
|
||||
framework.Failf("expecting wait timeout error but got: %v", err)
|
||||
}
|
||||
}, 1*time.Minute, 1*time.Second).ShouldNot(BeTrue(), "pod should not be ready")
|
||||
|
||||
p, err = podClient.Get(p.Name)
|
||||
p, err := podClient.Get(p.Name)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
isReady, err := framework.PodRunningReady(p)
|
||||
26
test/e2e/common/util.go
Normal file
26
test/e2e/common/util.go
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 common
|
||||
|
||||
type Suite string
|
||||
|
||||
const (
|
||||
E2E Suite = "e2e"
|
||||
NodeE2E Suite = "node e2e"
|
||||
)
|
||||
|
||||
var CurrentSuite Suite
|
||||
@@ -81,7 +81,7 @@ var _ = framework.KubeDescribe("Downward API volume", func() {
|
||||
podLogTimeout, framework.Poll).Should(ContainSubstring("key1=\"value1\"\n"))
|
||||
|
||||
//modify labels
|
||||
f.UpdatePod(podName, func(pod *api.Pod) {
|
||||
f.PodClient().Update(podName, func(pod *api.Pod) {
|
||||
pod.Labels["key3"] = "value3"
|
||||
})
|
||||
|
||||
@@ -116,7 +116,7 @@ var _ = framework.KubeDescribe("Downward API volume", func() {
|
||||
podLogTimeout, framework.Poll).Should(ContainSubstring("builder=\"bar\"\n"))
|
||||
|
||||
//modify annotations
|
||||
f.UpdatePod(podName, func(pod *api.Pod) {
|
||||
f.PodClient().Update(podName, func(pod *api.Pod) {
|
||||
pod.Annotations["builder"] = "foo"
|
||||
})
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
"k8s.io/kubernetes/pkg/util/runtime"
|
||||
commontest "k8s.io/kubernetes/test/e2e/common"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
)
|
||||
|
||||
@@ -137,6 +138,9 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
|
||||
framework.LogContainersInPodsWithLabels(c, api.NamespaceSystem, framework.ImagePullerLabels, "nethealth")
|
||||
}
|
||||
|
||||
// Reference common test to make the import valid.
|
||||
commontest.CurrentSuite = commontest.E2E
|
||||
|
||||
return nil
|
||||
|
||||
}, func(data []byte) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
@@ -53,10 +54,9 @@ const (
|
||||
type Framework struct {
|
||||
BaseName string
|
||||
|
||||
ClientConfigGetter ClientConfigGetter
|
||||
Client *client.Client
|
||||
Clientset_1_2 *release_1_2.Clientset
|
||||
Clientset_1_3 *release_1_3.Clientset
|
||||
Client *client.Client
|
||||
Clientset_1_2 *release_1_2.Clientset
|
||||
Clientset_1_3 *release_1_3.Clientset
|
||||
|
||||
// TODO(mml): Remove this. We should generally use the versioned clientset.
|
||||
FederationClientset *federation_internalclientset.Clientset
|
||||
@@ -117,16 +117,11 @@ func NewDefaultFederatedFramework(baseName string) *Framework {
|
||||
}
|
||||
|
||||
func NewFramework(baseName string, options FrameworkOptions, client *client.Client) *Framework {
|
||||
return NewFrameworkWithConfigGetter(baseName, options, client, LoadConfig)
|
||||
}
|
||||
|
||||
func NewFrameworkWithConfigGetter(baseName string, options FrameworkOptions, client *client.Client, configGetter ClientConfigGetter) *Framework {
|
||||
f := &Framework{
|
||||
BaseName: baseName,
|
||||
AddonResourceConstraints: make(map[string]ResourceConstraint),
|
||||
options: options,
|
||||
Client: client,
|
||||
ClientConfigGetter: configGetter,
|
||||
}
|
||||
|
||||
BeforeEach(f.BeforeEach)
|
||||
@@ -142,10 +137,21 @@ func (f *Framework) BeforeEach() {
|
||||
f.cleanupHandle = AddCleanupAction(f.AfterEach)
|
||||
if f.Client == nil {
|
||||
By("Creating a kubernetes client")
|
||||
config, err := f.ClientConfigGetter()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
config.QPS = f.options.ClientQPS
|
||||
config.Burst = f.options.ClientBurst
|
||||
var config *restclient.Config
|
||||
if TestContext.NodeName != "" {
|
||||
// This is a node e2e test, apply the node e2e configuration
|
||||
config = &restclient.Config{
|
||||
Host: TestContext.Host,
|
||||
QPS: 100,
|
||||
Burst: 100,
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
config, err = LoadConfig()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
config.QPS = f.options.ClientQPS
|
||||
config.Burst = f.options.ClientBurst
|
||||
}
|
||||
if TestContext.KubeAPIContentType != "" {
|
||||
config.ContentType = TestContext.KubeAPIContentType
|
||||
}
|
||||
|
||||
@@ -29,43 +29,46 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// TODO: Consolidate pod-specific framework functions here.
|
||||
|
||||
// Convenience method for getting a pod client interface in the framework's namespace.
|
||||
func (f *Framework) PodClient() unversioned.PodInterface {
|
||||
return f.Client.Pods(f.Namespace.Name)
|
||||
func (f *Framework) PodClient() *PodClient {
|
||||
return &PodClient{
|
||||
f: f,
|
||||
PodInterface: f.Client.Pods(f.Namespace.Name),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new pod according to the framework specifications, and wait for it to start.
|
||||
// Returns the server's representation of the pod.
|
||||
func (f *Framework) CreatePod(pod *api.Pod) *api.Pod {
|
||||
p := f.CreatePodAsync(pod)
|
||||
ExpectNoError(f.WaitForPodRunning(p.Name))
|
||||
return p
|
||||
type PodClient struct {
|
||||
f *Framework
|
||||
unversioned.PodInterface
|
||||
}
|
||||
|
||||
// Create a new pod according to the framework specifications (don't wait for it to start).
|
||||
// Returns the server's representation of the pod.
|
||||
func (f *Framework) CreatePodAsync(pod *api.Pod) *api.Pod {
|
||||
f.MungePodSpec(pod)
|
||||
p, err := f.PodClient().Create(pod)
|
||||
// Create creates a new pod according to the framework specifications (don't wait for it to start).
|
||||
func (c *PodClient) Create(pod *api.Pod) *api.Pod {
|
||||
c.MungeSpec(pod)
|
||||
p, err := c.PodInterface.Create(pod)
|
||||
ExpectNoError(err, "Error creating Pod")
|
||||
return p
|
||||
}
|
||||
|
||||
// Batch version of CreatePod. All pods are created before waiting.
|
||||
// Returns a slice, in the same order as pods, containing the server's representations of the pods.
|
||||
func (f *Framework) CreatePods(pods []*api.Pod) []*api.Pod {
|
||||
// CreateSync creates a new pod according to the framework specifications, and wait for it to start.
|
||||
func (c *PodClient) CreateSync(pod *api.Pod) *api.Pod {
|
||||
p := c.Create(pod)
|
||||
ExpectNoError(c.f.WaitForPodRunning(p.Name))
|
||||
return p
|
||||
}
|
||||
|
||||
// CreateBatch create a batch of pods. All pods are created before waiting.
|
||||
func (c *PodClient) CreateBatch(pods []*api.Pod) []*api.Pod {
|
||||
ps := make([]*api.Pod, len(pods))
|
||||
for i, pod := range pods {
|
||||
ps[i] = f.CreatePodAsync(pod)
|
||||
ps[i] = c.Create(pod)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for _, pod := range ps {
|
||||
wg.Add(1)
|
||||
podName := pod.Name
|
||||
go func() {
|
||||
ExpectNoError(f.WaitForPodRunning(podName))
|
||||
ExpectNoError(c.f.WaitForPodRunning(podName))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
@@ -73,26 +76,27 @@ func (f *Framework) CreatePods(pods []*api.Pod) []*api.Pod {
|
||||
return ps
|
||||
}
|
||||
|
||||
// Apply test-suite specific transformations to the pod spec.
|
||||
// TODO: figure out a nicer, more generic way to tie this to framework instances.
|
||||
func (f *Framework) MungePodSpec(pod *api.Pod) {
|
||||
// MungeSpec apply test-suite specific transformations to the pod spec.
|
||||
// TODO: Refactor the framework to always use PodClient so that we can completely hide the munge logic
|
||||
// in the PodClient.
|
||||
func (c *PodClient) MungeSpec(pod *api.Pod) {
|
||||
if TestContext.NodeName != "" {
|
||||
Expect(pod.Spec.NodeName).To(Or(BeZero(), Equal(TestContext.NodeName)), "Test misconfigured")
|
||||
pod.Spec.NodeName = TestContext.NodeName
|
||||
}
|
||||
}
|
||||
|
||||
// UpdatePod updates the pod object. It retries if there is a conflict, throw out error if
|
||||
// Update updates the pod object. It retries if there is a conflict, throw out error if
|
||||
// there is any other errors. name is the pod name, updateFn is the function updating the
|
||||
// pod object.
|
||||
func (f *Framework) UpdatePod(name string, updateFn func(pod *api.Pod)) {
|
||||
func (c *PodClient) Update(name string, updateFn func(pod *api.Pod)) {
|
||||
ExpectNoError(wait.Poll(time.Millisecond*500, time.Second*30, func() (bool, error) {
|
||||
pod, err := f.PodClient().Get(name)
|
||||
pod, err := c.PodInterface.Get(name)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get pod %q: %v", name, err)
|
||||
}
|
||||
updateFn(pod)
|
||||
_, err = f.PodClient().Update(pod)
|
||||
_, err = c.PodInterface.Update(pod)
|
||||
if err == nil {
|
||||
Logf("Successfully updated pod %q", name)
|
||||
return true, nil
|
||||
@@ -104,3 +108,5 @@ func (f *Framework) UpdatePod(name string, updateFn func(pod *api.Pod)) {
|
||||
return false, fmt.Errorf("failed to update pod %q: %v", name, err)
|
||||
}))
|
||||
}
|
||||
|
||||
// TODO(random-liu): Move pod wait function into this file
|
||||
|
||||
@@ -103,6 +103,7 @@ func RegisterCommonFlags() {
|
||||
flag.BoolVar(&TestContext.GatherMetricsAfterTest, "gather-metrics-at-teardown", false, "If set to true framwork will gather metrics from all components after each test.")
|
||||
flag.StringVar(&TestContext.OutputPrintType, "output-print-type", "hr", "Comma separated list: 'hr' for human readable summaries '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.StringVar(&TestContext.Host, "host", "http://127.0.0.1:8080", "The host, or apiserver, to connect to")
|
||||
}
|
||||
|
||||
// Register flags specific to the cluster e2e test suite.
|
||||
@@ -118,7 +119,6 @@ func RegisterClusterFlags() {
|
||||
|
||||
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.Host, "host", "", "The host, or apiserver, to connect to")
|
||||
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, vagrant, etc.)")
|
||||
flag.StringVar(&TestContext.KubectlPath, "kubectl-path", "kubectl", "The kubectl binary to use. For development, you might use 'cluster/kubectl.sh' here.")
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
)
|
||||
|
||||
@@ -84,29 +83,12 @@ func (config *KubeletManagedHostConfig) setup() {
|
||||
|
||||
func (config *KubeletManagedHostConfig) createPodWithoutHostNetwork() {
|
||||
podSpec := config.createPodSpec(kubeletEtcHostsPodName)
|
||||
config.pod = config.createPod(podSpec)
|
||||
config.pod = config.f.PodClient().CreateSync(podSpec)
|
||||
}
|
||||
|
||||
func (config *KubeletManagedHostConfig) createPodWithHostNetwork() {
|
||||
podSpec := config.createPodSpecWithHostNetwork(kubeletEtcHostsHostNetworkPodName)
|
||||
config.hostNetworkPod = config.createPod(podSpec)
|
||||
}
|
||||
|
||||
func (config *KubeletManagedHostConfig) createPod(podSpec *api.Pod) *api.Pod {
|
||||
createdPod, err := config.getPodClient().Create(podSpec)
|
||||
if err != nil {
|
||||
framework.Failf("Failed to create %s pod: %v", podSpec.Name, err)
|
||||
}
|
||||
framework.ExpectNoError(config.f.WaitForPodRunning(podSpec.Name))
|
||||
createdPod, err = config.getPodClient().Get(podSpec.Name)
|
||||
if err != nil {
|
||||
framework.Failf("Failed to retrieve %s pod: %v", podSpec.Name, err)
|
||||
}
|
||||
return createdPod
|
||||
}
|
||||
|
||||
func (config *KubeletManagedHostConfig) getPodClient() client.PodInterface {
|
||||
return config.f.Client.Pods(config.f.Namespace.Name)
|
||||
config.hostNetworkPod = config.f.PodClient().CreateSync(podSpec)
|
||||
}
|
||||
|
||||
func assertEtcHostsIsKubeletManaged(etcHostsContent string) {
|
||||
|
||||
@@ -462,7 +462,7 @@ var _ = framework.KubeDescribe("Pods", func() {
|
||||
Expect(len(pods.Items)).To(Equal(1))
|
||||
|
||||
By("updating the pod")
|
||||
f.UpdatePod(name, func(pod *api.Pod) {
|
||||
f.PodClient().Update(name, func(pod *api.Pod) {
|
||||
value = strconv.Itoa(time.Now().Nanosecond())
|
||||
pod.Labels["time"] = value
|
||||
})
|
||||
@@ -530,7 +530,7 @@ var _ = framework.KubeDescribe("Pods", func() {
|
||||
Expect(len(pods.Items)).To(Equal(1))
|
||||
|
||||
By("updating the pod")
|
||||
f.UpdatePod(name, func(pod *api.Pod) {
|
||||
f.PodClient().Update(name, func(pod *api.Pod) {
|
||||
newDeadline := int64(5)
|
||||
pod.Spec.ActiveDeadlineSeconds = &newDeadline
|
||||
})
|
||||
@@ -1309,7 +1309,7 @@ var _ = framework.KubeDescribe("Pods", func() {
|
||||
delay1, delay2 := startPodAndGetBackOffs(f, pod, podName, containerName, buildBackOffDuration)
|
||||
|
||||
By("updating the image")
|
||||
f.UpdatePod(podName, func(pod *api.Pod) {
|
||||
f.PodClient().Update(podName, func(pod *api.Pod) {
|
||||
pod.Spec.Containers[0].Image = "gcr.io/google_containers/nginx-slim:0.7"
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user