diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 97b16640dcd..b18c52c5eae 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -84,6 +84,7 @@ go_library( "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library", "//pkg/client/unversioned:go_default_library", + "//pkg/controller:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/auth:go_default_library", "//pkg/kubectl/cmd/config:go_default_library", diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index 3e3e68b7359..34c316d9fe8 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -38,6 +38,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -266,40 +267,38 @@ func (o *DrainOptions) deleteOrEvictPodsSimple() error { return err } -func (o *DrainOptions) getController(sr *api.SerializedReference) (interface{}, error) { - switch sr.Reference.Kind { +func (o *DrainOptions) getController(namespace string, controllerRef *metav1.OwnerReference) (interface{}, error) { + switch controllerRef.Kind { case "ReplicationController": - return o.client.Core().ReplicationControllers(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}) + return o.client.Core().ReplicationControllers(namespace).Get(controllerRef.Name, metav1.GetOptions{}) case "DaemonSet": - return o.client.Extensions().DaemonSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}) + return o.client.Extensions().DaemonSets(namespace).Get(controllerRef.Name, metav1.GetOptions{}) case "Job": - return o.client.Batch().Jobs(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}) + return o.client.Batch().Jobs(namespace).Get(controllerRef.Name, metav1.GetOptions{}) case "ReplicaSet": - return o.client.Extensions().ReplicaSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}) + return o.client.Extensions().ReplicaSets(namespace).Get(controllerRef.Name, metav1.GetOptions{}) case "StatefulSet": - return o.client.Apps().StatefulSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}) + return o.client.Apps().StatefulSets(namespace).Get(controllerRef.Name, metav1.GetOptions{}) } - return nil, fmt.Errorf("Unknown controller kind %q", sr.Reference.Kind) + return nil, fmt.Errorf("Unknown controller kind %q", controllerRef.Kind) } -func (o *DrainOptions) getPodCreator(pod api.Pod) (*api.SerializedReference, error) { - creatorRef, found := pod.ObjectMeta.Annotations[api.CreatedByAnnotation] - if !found { +func (o *DrainOptions) getPodController(pod api.Pod) (*metav1.OwnerReference, error) { + controllerRef := controller.GetControllerOf(&pod) + if controllerRef == nil { return nil, nil } - // Now verify that the specified creator actually exists. - sr := &api.SerializedReference{} - if err := runtime.DecodeInto(o.Factory.Decoder(true), []byte(creatorRef), sr); err != nil { - return nil, err - } + // We assume the only reason for an error is because the controller is - // gone/missing, not for any other cause. TODO(mml): something more - // sophisticated than this - _, err := o.getController(sr) + // gone/missing, not for any other cause. + // TODO(mml): something more sophisticated than this + // TODO(juntee): determine if it's safe to remove getController(), + // so that drain can work for controller types that we don't know about + _, err := o.getController(pod.Namespace, controllerRef) if err != nil { return nil, err } - return sr, nil + return controllerRef, nil } func (o *DrainOptions) unreplicatedFilter(pod api.Pod) (bool, *warning, *fatal) { @@ -308,7 +307,7 @@ func (o *DrainOptions) unreplicatedFilter(pod api.Pod) (bool, *warning, *fatal) return true, nil, nil } - sr, err := o.getPodCreator(pod) + controllerRef, err := o.getPodController(pod) if err != nil { // if we're forcing, remove orphaned pods with a warning if apierrors.IsNotFound(err) && o.Force { @@ -316,7 +315,7 @@ func (o *DrainOptions) unreplicatedFilter(pod api.Pod) (bool, *warning, *fatal) } return false, nil, &fatal{err.Error()} } - if sr != nil { + if controllerRef != nil { return true, nil, nil } if !o.Force { @@ -333,7 +332,7 @@ func (o *DrainOptions) daemonsetFilter(pod api.Pod) (bool, *warning, *fatal) { // The exception is for pods that are orphaned (the referencing // management resource - including DaemonSet - is not found). // Such pods will be deleted if --force is used. - sr, err := o.getPodCreator(pod) + controllerRef, err := o.getPodController(pod) if err != nil { // if we're forcing, remove orphaned pods with a warning if apierrors.IsNotFound(err) && o.Force { @@ -341,10 +340,10 @@ func (o *DrainOptions) daemonsetFilter(pod api.Pod) (bool, *warning, *fatal) { } return false, nil, &fatal{err.Error()} } - if sr == nil || sr.Reference.Kind != "DaemonSet" { + if controllerRef == nil || controllerRef.Kind != "DaemonSet" { return true, nil, nil } - if _, err := o.client.Extensions().DaemonSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}); err != nil { + if _, err := o.client.Extensions().DaemonSets(pod.Namespace).Get(controllerRef.Name, metav1.GetOptions{}); err != nil { return false, nil, &fatal{err.Error()} } if !o.IgnoreDaemonsets { diff --git a/pkg/kubectl/cmd/drain_test.go b/pkg/kubectl/cmd/drain_test.go index 94560147e62..36ea363f48e 100644 --- a/pkg/kubectl/cmd/drain_test.go +++ b/pkg/kubectl/cmd/drain_test.go @@ -58,6 +58,8 @@ const ( var node *api.Node var cordoned_node *api.Node +func boolptr(b bool) *bool { return &b } + func TestMain(m *testing.M) { // Create a node. node = &api.Node{ @@ -244,7 +246,18 @@ func TestDrain(t *testing.T) { Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, + SelfLink: testapi.Default.SelfLink("pods", "bar"), Annotations: rc_anno, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "123", + BlockOwnerDeletion: boolptr(true), + Controller: boolptr(true), + }, + }, }, Spec: api.PodSpec{ NodeName: "node", @@ -256,7 +269,7 @@ func TestDrain(t *testing.T) { Name: "ds", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, - SelfLink: "/apis/extensions/v1beta1/namespaces/default/daemonsets/ds", + SelfLink: testapi.Default.SelfLink("daemonsets", "ds"), }, Spec: extensions.DaemonSetSpec{ Selector: &metav1.LabelSelector{MatchLabels: labels}, @@ -272,7 +285,17 @@ func TestDrain(t *testing.T) { Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, + SelfLink: testapi.Default.SelfLink("pods", "bar"), Annotations: ds_anno, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions/v1beta1", + Kind: "DaemonSet", + Name: "ds", + BlockOwnerDeletion: boolptr(true), + Controller: boolptr(true), + }, + }, }, Spec: api.PodSpec{ NodeName: "node", @@ -284,7 +307,7 @@ func TestDrain(t *testing.T) { Name: "missing-ds", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, - SelfLink: "/apis/extensions/v1beta1/namespaces/default/daemonsets/missing-ds", + SelfLink: testapi.Default.SelfLink("daemonsets", "missing-ds"), }, Spec: extensions.DaemonSetSpec{ Selector: &metav1.LabelSelector{MatchLabels: labels}, @@ -300,7 +323,17 @@ func TestDrain(t *testing.T) { Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, + SelfLink: testapi.Default.SelfLink("pods", "bar"), Annotations: missing_ds_anno, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions/v1beta1", + Kind: "DaemonSet", + Name: "missing-ds", + BlockOwnerDeletion: boolptr(true), + Controller: boolptr(true), + }, + }, }, Spec: api.PodSpec{ NodeName: "node", @@ -312,7 +345,7 @@ func TestDrain(t *testing.T) { Name: "job", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, - SelfLink: "/apis/batch/v1/namespaces/default/jobs/job", + SelfLink: testapi.Default.SelfLink("jobs", "job"), }, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{MatchLabels: labels}, @@ -325,7 +358,17 @@ func TestDrain(t *testing.T) { Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, + SelfLink: testapi.Default.SelfLink("pods", "bar"), Annotations: map[string]string{api.CreatedByAnnotation: refJson(t, &job)}, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Job", + Name: "job", + BlockOwnerDeletion: boolptr(true), + Controller: boolptr(true), + }, + }, }, } @@ -351,7 +394,17 @@ func TestDrain(t *testing.T) { Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, + SelfLink: testapi.Default.SelfLink("pods", "bar"), Annotations: rs_anno, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicaSet", + Name: "rs", + BlockOwnerDeletion: boolptr(true), + Controller: boolptr(true), + }, + }, }, Spec: api.PodSpec{ NodeName: "node",