Merge pull request #32247 from mml/evict.test

Automatic merge from submit-queue

Add eviction e2e tests.

This also disables the tests on GKE.
This commit is contained in:
Kubernetes Submit Queue 2016-09-07 19:56:54 -07:00 committed by GitHub
commit cbd2a21e63
15 changed files with 183 additions and 61 deletions

View File

@ -18,6 +18,7 @@ package fake
import ( import (
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
policy "k8s.io/kubernetes/pkg/apis/policy/v1alpha1"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/testing/core" "k8s.io/kubernetes/pkg/client/testing/core"
) )
@ -44,3 +45,14 @@ func (c *FakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Requ
_, _ = c.Fake.Invokes(action, &v1.Pod{}) _, _ = c.Fake.Invokes(action, &v1.Pod{})
return &restclient.Request{} return &restclient.Request{}
} }
func (c *FakePods) Evict(eviction *policy.Eviction) error {
action := core.CreateActionImpl{}
action.Verb = "create"
action.Resource = podsResource
action.Subresource = "evictions"
action.Object = eviction
_, err := c.Fake.Invokes(action, eviction)
return err
}

View File

@ -19,12 +19,14 @@ package v1
import ( import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
policy "k8s.io/kubernetes/pkg/apis/policy/v1alpha1"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
) )
// The PodExpansion interface allows manually adding extra methods to the PodInterface. // The PodExpansion interface allows manually adding extra methods to the PodInterface.
type PodExpansion interface { type PodExpansion interface {
Bind(binding *v1.Binding) error Bind(binding *v1.Binding) error
Evict(eviction *policy.Eviction) error
GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request
} }
@ -33,6 +35,10 @@ func (c *pods) Bind(binding *v1.Binding) error {
return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error() return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error()
} }
func (c *pods) Evict(eviction *policy.Eviction) error {
return c.client.Post().Namespace(c.ns).Resource("pods").Name(eviction.Name).SubResource("eviction").Body(eviction).Do().Error()
}
// Get constructs a request for getting the logs for a pod // Get constructs a request for getting the logs for a pod
func (c *pods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { func (c *pods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request {
return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec)

View File

@ -1,6 +1,6 @@
{ {
"ImportPath": "k8s.io/client-go/1.4", "ImportPath": "k8s.io/client-go/1.4",
"GoVersion": "go1.6", "GoVersion": "go1.7",
"GodepVersion": "v74", "GodepVersion": "v74",
"Packages": [ "Packages": [
"./..." "./..."

View File

@ -18,6 +18,7 @@ package fake
import ( import (
"k8s.io/client-go/1.4/pkg/api/v1" "k8s.io/client-go/1.4/pkg/api/v1"
policy "k8s.io/client-go/1.4/pkg/apis/policy/v1alpha1"
"k8s.io/client-go/1.4/rest" "k8s.io/client-go/1.4/rest"
"k8s.io/client-go/1.4/testing" "k8s.io/client-go/1.4/testing"
) )
@ -44,3 +45,14 @@ func (c *FakePods) GetLogs(name string, opts *v1.PodLogOptions) *rest.Request {
_, _ = c.Fake.Invokes(action, &v1.Pod{}) _, _ = c.Fake.Invokes(action, &v1.Pod{})
return &rest.Request{} return &rest.Request{}
} }
func (c *FakePods) Evict(eviction *policy.Eviction) error {
action := testing.CreateActionImpl{}
action.Verb = "create"
action.Resource = podsResource
action.Subresource = "evictions"
action.Object = eviction
_, err := c.Fake.Invokes(action, eviction)
return err
}

View File

@ -19,12 +19,14 @@ package v1
import ( import (
"k8s.io/client-go/1.4/pkg/api" "k8s.io/client-go/1.4/pkg/api"
"k8s.io/client-go/1.4/pkg/api/v1" "k8s.io/client-go/1.4/pkg/api/v1"
policy "k8s.io/client-go/1.4/pkg/apis/policy/v1alpha1"
"k8s.io/client-go/1.4/rest" "k8s.io/client-go/1.4/rest"
) )
// The PodExpansion interface allows manually adding extra methods to the PodInterface. // The PodExpansion interface allows manually adding extra methods to the PodInterface.
type PodExpansion interface { type PodExpansion interface {
Bind(binding *v1.Binding) error Bind(binding *v1.Binding) error
Evict(eviction *policy.Eviction) error
GetLogs(name string, opts *v1.PodLogOptions) *rest.Request GetLogs(name string, opts *v1.PodLogOptions) *rest.Request
} }
@ -33,6 +35,10 @@ func (c *pods) Bind(binding *v1.Binding) error {
return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error() return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error()
} }
func (c *pods) Evict(eviction *policy.Eviction) error {
return c.client.Post().Namespace(c.ns).Resource("pods").Name(eviction.Name).SubResource("eviction").Body(eviction).Do().Error()
}
// Get constructs a request for getting the logs for a pod // Get constructs a request for getting the logs for a pod
func (c *pods) GetLogs(name string, opts *v1.PodLogOptions) *rest.Request { func (c *pods) GetLogs(name string, opts *v1.PodLogOptions) *rest.Request {
return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec)

View File

@ -3435,9 +3435,11 @@ func validateEndpointAddress(address *api.EndpointAddress, fldPath *field.Path,
if len(address.Hostname) > 0 { if len(address.Hostname) > 0 {
allErrs = append(allErrs, ValidateDNS1123Label(address.Hostname, fldPath.Child("hostname"))...) allErrs = append(allErrs, ValidateDNS1123Label(address.Hostname, fldPath.Child("hostname"))...)
} }
// During endpoint update, validate NodeName is DNS1123 compliant and transition rules allow the update // During endpoint update, verify that NodeName is a DNS subdomain and transition rules allow the update
if address.NodeName != nil { if address.NodeName != nil {
allErrs = append(allErrs, ValidateDNS1123Label(*address.NodeName, fldPath.Child("nodeName"))...) for _, msg := range ValidateNodeName(*address.NodeName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg))
}
} }
allErrs = append(allErrs, validateEpAddrNodeNameTransition(address, ipToNodeName, fldPath.Child("nodeName"))...) allErrs = append(allErrs, validateEpAddrNodeNameTransition(address, ipToNodeName, fldPath.Child("nodeName"))...)
if len(allErrs) > 0 { if len(allErrs) > 0 {

View File

@ -50,6 +50,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&PetSet{}, &PetSet{},
&PetSetList{}, &PetSetList{},
&api.ListOptions{}, &api.ListOptions{},
&api.DeleteOptions{},
) )
return nil return nil
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// +groupName=certificates.k8s.io
// +k8s:deepcopy-gen=package,register // +k8s:deepcopy-gen=package,register
// +groupName=certificates.k8s.io
package certificates package certificates

View File

@ -72,7 +72,7 @@ type PodDisruptionBudgetList struct {
// Eviction evicts a pod from its node subject to certain policies and safety constraints. // Eviction evicts a pod from its node subject to certain policies and safety constraints.
// This is a subresource of Pod. A request to cause such an eviction is // This is a subresource of Pod. A request to cause such an eviction is
// created by POSTing to .../pods/foo/evictions. // created by POSTing to .../pods/<pod name>/evictions.
type Eviction struct { type Eviction struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`

View File

@ -32,7 +32,7 @@ option go_package = "v1alpha1";
// Eviction evicts a pod from its node subject to certain policies and safety constraints. // Eviction evicts a pod from its node subject to certain policies and safety constraints.
// This is a subresource of Pod. A request to cause such an eviction is // This is a subresource of Pod. A request to cause such an eviction is
// created by POSTing to .../pods/foo/evictions. // created by POSTing to .../pods/<pod name>/evictions.
message Eviction { message Eviction {
// ObjectMeta describes the pod that is being evicted. // ObjectMeta describes the pod that is being evicted.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1; optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;

View File

@ -71,7 +71,7 @@ type PodDisruptionBudgetList struct {
// Eviction evicts a pod from its node subject to certain policies and safety constraints. // Eviction evicts a pod from its node subject to certain policies and safety constraints.
// This is a subresource of Pod. A request to cause such an eviction is // This is a subresource of Pod. A request to cause such an eviction is
// created by POSTing to .../pods/foo/evictions. // created by POSTing to .../pods/<pod name>/evictions.
type Eviction struct { type Eviction struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`

View File

@ -28,7 +28,7 @@ package v1alpha1
// AUTO-GENERATED FUNCTIONS START HERE // AUTO-GENERATED FUNCTIONS START HERE
var map_Eviction = map[string]string{ var map_Eviction = map[string]string{
"": "Eviction evicts a pod from its node subject to certain policies and safety constraints. This is a subresource of Pod. A request to cause such an eviction is created by POSTing to .../pods/foo/evictions.", "": "Eviction evicts a pod from its node subject to certain policies and safety constraints. This is a subresource of Pod. A request to cause such an eviction is created by POSTing to .../pods/<pod name>/evictions.",
"metadata": "ObjectMeta describes the pod that is being evicted.", "metadata": "ObjectMeta describes the pod that is being evicted.",
"deleteOptions": "DeleteOptions may be provided", "deleteOptions": "DeleteOptions may be provided",
} }

View File

@ -31,11 +31,23 @@ type Aggregate interface {
// NewAggregate converts a slice of errors into an Aggregate interface, which // NewAggregate converts a slice of errors into an Aggregate interface, which
// is itself an implementation of the error interface. If the slice is empty, // is itself an implementation of the error interface. If the slice is empty,
// this returns nil. // this returns nil.
// It will check if any of the element of input error list is nil, to avoid
// nil pointer panic when call Error().
func NewAggregate(errlist []error) Aggregate { func NewAggregate(errlist []error) Aggregate {
if len(errlist) == 0 { if len(errlist) == 0 {
return nil return nil
} }
return aggregate(errlist) // In case of input error list contains nil
var errs []error
for _, e := range errlist {
if e != nil {
errs = append(errs, e)
}
}
if len(errs) == 0 {
return nil
}
return aggregate(errs)
} }
// This helper implements the error and Errors interfaces. Keeping it private // This helper implements the error and Errors interfaces. Keeping it private

View File

@ -55,7 +55,7 @@ type Reactor interface {
type WatchReactor interface { type WatchReactor interface {
// Handles indicates whether or not this Reactor deals with a given action // Handles indicates whether or not this Reactor deals with a given action
Handles(action Action) bool Handles(action Action) bool
// React handles a watch action and returns results. It may choose to delegate by indicated handled=false // React handles a watch action and returns results. It may choose to delegate by indicating handled=false
React(action Action) (handled bool, ret watch.Interface, err error) React(action Action) (handled bool, ret watch.Interface, err error)
} }
@ -63,20 +63,20 @@ type WatchReactor interface {
type ProxyReactor interface { type ProxyReactor interface {
// Handles indicates whether or not this Reactor deals with a given action // Handles indicates whether or not this Reactor deals with a given action
Handles(action Action) bool Handles(action Action) bool
// React handles a watch action and returns results. It may choose to delegate by indicated handled=false // React handles a watch action and returns results. It may choose to delegate by indicating handled=false
React(action Action) (handled bool, ret rest.ResponseWrapper, err error) React(action Action) (handled bool, ret rest.ResponseWrapper, err error)
} }
// ReactionFunc is a function that returns an object or error for a given Action. If "handled" is false, // ReactionFunc is a function that returns an object or error for a given Action. If "handled" is false,
// then the test client will continue ignore the results and continue to the next ReactionFunc // then the test client will ignore the results and continue to the next ReactionFunc
type ReactionFunc func(action Action) (handled bool, ret runtime.Object, err error) type ReactionFunc func(action Action) (handled bool, ret runtime.Object, err error)
// WatchReactionFunc is a function that returns a watch interface. If "handled" is false, // WatchReactionFunc is a function that returns a watch interface. If "handled" is false,
// then the test client will continue ignore the results and continue to the next ReactionFunc // then the test client will ignore the results and continue to the next ReactionFunc
type WatchReactionFunc func(action Action) (handled bool, ret watch.Interface, err error) type WatchReactionFunc func(action Action) (handled bool, ret watch.Interface, err error)
// ProxyReactionFunc is a function that returns a ResponseWrapper interface for a given Action. If "handled" is false, // ProxyReactionFunc is a function that returns a ResponseWrapper interface for a given Action. If "handled" is false,
// then the test client will continue ignore the results and continue to the next ProxyReactionFunc // then the test client will ignore the results and continue to the next ProxyReactionFunc
type ProxyReactionFunc func(action Action) (handled bool, ret rest.ResponseWrapper, err error) type ProxyReactionFunc func(action Action) (handled bool, ret rest.ResponseWrapper, err error)
// AddReactor appends a reactor to the end of the chain // AddReactor appends a reactor to the end of the chain
@ -184,7 +184,7 @@ func (c *Fake) ClearActions() {
c.actions = make([]Action, 0) c.actions = make([]Action, 0)
} }
// Actions returns a chronologically ordered slice fake actions called on the fake client // Actions returns a chronologically ordered slice fake actions called on the fake client.
func (c *Fake) Actions() []Action { func (c *Fake) Actions() []Action {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()

View File

@ -31,32 +31,111 @@ import (
"k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework"
) )
var _ = framework.KubeDescribe("DisruptionController [Feature:PodDisruptionbudget]", func() { var _ = framework.KubeDescribe("DisruptionController", func() {
f := framework.NewDefaultFramework("disruption") f := framework.NewDefaultFramework("disruption")
var ns string var ns string
var cs *release_1_4.Clientset var cs *release_1_4.Clientset
BeforeEach(func() { BeforeEach(func() {
// skip on GKE since alpha features are disabled
framework.SkipIfProviderIs("gke")
cs = f.StagingClient cs = f.StagingClient
ns = f.Namespace.Name ns = f.Namespace.Name
}) })
It("should create a PodDisruptionBudget", func() { It("should create a PodDisruptionBudget", func() {
pdb := policy.PodDisruptionBudget{ createPodDisruptionBudgetOrDie(cs, ns, intstr.FromString("1%"))
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
MinAvailable: intstr.FromString("1%"),
},
}
_, err := cs.Policy().PodDisruptionBudgets(ns).Create(&pdb)
Expect(err).NotTo(HaveOccurred())
}) })
It("should update PodDisruptionBudget status", func() { It("should update PodDisruptionBudget status", func() {
createPodDisruptionBudgetOrDie(cs, ns, intstr.FromInt(2))
createPodsOrDie(cs, ns, 2)
// Since disruptionAllowed starts out false, if we see it ever become true,
// that means the controller is working.
err := wait.PollImmediate(framework.Poll, 60*time.Second, func() (bool, error) {
pdb, err := cs.Policy().PodDisruptionBudgets(ns).Get("foo")
if err != nil {
return false, err
}
return pdb.Status.PodDisruptionAllowed, nil
})
Expect(err).NotTo(HaveOccurred())
})
It("should allow an eviction when there is no PDB", func() {
createPodsOrDie(cs, ns, 1)
pod, err := cs.Pods(ns).Get("pod-0")
Expect(err).NotTo(HaveOccurred())
e := &policy.Eviction{
ObjectMeta: api.ObjectMeta{
Name: pod.Name,
Namespace: ns,
},
}
err = cs.Pods(ns).Evict(e)
Expect(err).NotTo(HaveOccurred())
})
It("should not allow an eviction when too few pods", func() {
createPodDisruptionBudgetOrDie(cs, ns, intstr.FromInt(2))
createPodsOrDie(cs, ns, 1)
pod, err := cs.Pods(ns).Get("pod-0")
Expect(err).NotTo(HaveOccurred())
e := &policy.Eviction{
ObjectMeta: api.ObjectMeta{
Name: pod.Name,
Namespace: ns,
},
}
// Since disruptionAllowed starts out false, wait at least 60s hoping that
// this gives the controller enough time to have truly set the status.
time.Sleep(60 * time.Second)
err = cs.Pods(ns).Evict(e)
Expect(err).Should(MatchError("Cannot evict pod as it would violate the pod's disruption budget."))
})
It("should allow an eviction when enough pods", func() {
createPodDisruptionBudgetOrDie(cs, ns, intstr.FromInt(2))
createPodsOrDie(cs, ns, 2)
pod, err := cs.Pods(ns).Get("pod-0")
Expect(err).NotTo(HaveOccurred())
e := &policy.Eviction{
ObjectMeta: api.ObjectMeta{
Name: pod.Name,
Namespace: ns,
},
}
// Since disruptionAllowed starts out false, if an eviction is ever allowed,
// that means the controller is working.
err = wait.PollImmediate(framework.Poll, 60*time.Second, func() (bool, error) {
err = cs.Pods(ns).Evict(e)
if err != nil {
return false, nil
} else {
return true, nil
}
})
Expect(err).NotTo(HaveOccurred())
})
})
func createPodDisruptionBudgetOrDie(cs *release_1_4.Clientset, ns string, minAvailable intstr.IntOrString) {
pdb := policy.PodDisruptionBudget{ pdb := policy.PodDisruptionBudget{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
@ -64,12 +143,15 @@ var _ = framework.KubeDescribe("DisruptionController [Feature:PodDisruptionbudge
}, },
Spec: policy.PodDisruptionBudgetSpec{ Spec: policy.PodDisruptionBudgetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
MinAvailable: intstr.FromInt(2), MinAvailable: minAvailable,
}, },
} }
_, err := cs.Policy().PodDisruptionBudgets(ns).Create(&pdb) _, err := cs.Policy().PodDisruptionBudgets(ns).Create(&pdb)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
for i := 0; i < 2; i++ { }
func createPodsOrDie(cs *release_1_4.Clientset, ns string, n int) {
for i := 0; i < n; i++ {
pod := &api.Pod{ pod := &api.Pod{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: fmt.Sprintf("pod-%d", i), Name: fmt.Sprintf("pod-%d", i),
@ -90,15 +172,4 @@ var _ = framework.KubeDescribe("DisruptionController [Feature:PodDisruptionbudge
_, err := cs.Pods(ns).Create(pod) _, err := cs.Pods(ns).Create(pod)
framework.ExpectNoError(err, "Creating pod %q in namespace %q", pod.Name, ns) framework.ExpectNoError(err, "Creating pod %q in namespace %q", pod.Name, ns)
} }
err = wait.PollImmediate(framework.Poll, 60*time.Second, func() (bool, error) { }
pdb, err := cs.Policy().PodDisruptionBudgets(ns).Get("foo")
if err != nil {
return false, err
}
return pdb.Status.PodDisruptionAllowed, nil
})
Expect(err).NotTo(HaveOccurred())
})
})