Browse code

OpenShift admin command to manage node operations

Supported node operations:
openshift admin manage-node <nodes>|--selector=<node-selector> --schedulable=<true|false>
openshift admin manage-node <nodes>|--selector=<node-selector> --list-pods [--pod-selector=<selector>]
openshift admin manage-node <nodes>|--selector=<node-selector> --evacuate [--dry-run] [--force] [--pod-selector=<selector>]

Ravi Sankar Penta authored on 2015/02/03 01:41:51
Showing 8 changed files
... ...
@@ -431,6 +431,19 @@ osc delete is/ruby-20-centos7-buildcli
431 431
 osc delete bc/ruby-sample-build-validtag
432 432
 osc delete bc/ruby-sample-build-invalidtag
433 433
 
434
+# Test admin manage-node operations
435
+[ "$(openshift admin manage-node --help 2>&1 | grep 'Manage node operations')" ]
436
+[ "$(osadm manage-node --schedulable=true | grep --text 'Ready' | grep -v 'Sched')" ]
437
+osc create -f examples/hello-openshift/hello-pod.json
438
+[ "$(osadm manage-node --list-pods | grep 'hello-openshift' | grep -v 'unassigned')" ]
439
+[ "$(osadm manage-node --evacuate --dry-run | grep 'hello-openshift')" ]
440
+[ "$(osadm manage-node --schedulable=false | grep 'SchedulingDisabled')" ]
441
+[ "$(osadm manage-node --evacuate 2>&1 | grep 'Unable to evacuate')" ]
442
+[ "$(osadm manage-node --evacuate --force | grep 'hello-openshift')" ]
443
+[ ! "$(osadm manage-node --list-pods | grep 'hello-openshift')" ]
444
+osc delete pods hello-openshift
445
+echo "manage-node: ok"
446
+
434 447
 openshift admin policy add-role-to-group cluster-admin system:unauthenticated
435 448
 openshift admin policy remove-role-from-group cluster-admin system:unauthenticated
436 449
 openshift admin policy remove-role-from-group-from-project system:unauthenticated
... ...
@@ -6,6 +6,7 @@ import (
6 6
 
7 7
 	"github.com/spf13/cobra"
8 8
 
9
+	"github.com/openshift/origin/pkg/cmd/admin/node"
9 10
 	"github.com/openshift/origin/pkg/cmd/admin/policy"
10 11
 	"github.com/openshift/origin/pkg/cmd/admin/project"
11 12
 	"github.com/openshift/origin/pkg/cmd/cli/cmd"
... ...
@@ -46,6 +47,7 @@ func NewCommandAdmin(name, fullName string, out io.Writer) *cobra.Command {
46 46
 	cmds.AddCommand(exrouter.NewCmdRouter(f, fullName, "router", out))
47 47
 	cmds.AddCommand(exregistry.NewCmdRegistry(f, fullName, "registry", out))
48 48
 	cmds.AddCommand(buildchain.NewCmdBuildChain(f, fullName, "build-chain"))
49
+	cmds.AddCommand(node.NewCommandManageNode(f, node.ManageNodeCommandName, fullName+" "+node.ManageNodeCommandName, out))
49 50
 	cmds.AddCommand(cmd.NewCmdConfig(fullName, "config"))
50 51
 
51 52
 	// TODO: these probably belong in a sub command
52 53
new file mode 100644
... ...
@@ -0,0 +1,134 @@
0
+package node
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/golang/glog"
6
+	"github.com/spf13/cobra"
7
+
8
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
11
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
12
+)
13
+
14
+type EvacuateOptions struct {
15
+	Options *NodeOptions
16
+
17
+	// Optional params
18
+	DryRun bool
19
+	Force  bool
20
+}
21
+
22
+func (e *EvacuateOptions) AddFlags(cmd *cobra.Command) {
23
+	flags := cmd.Flags()
24
+
25
+	flags.BoolVar(&e.DryRun, "dry-run", false, "Show pods that will be migrated. Optional param for --evacuate")
26
+	flags.BoolVar(&e.Force, "force", false, "Delete pods not backed by replication controller. Optional param for --evacuate")
27
+}
28
+
29
+func (e *EvacuateOptions) Run() error {
30
+	nodes, err := e.Options.GetNodes()
31
+	if err != nil {
32
+		return err
33
+	}
34
+
35
+	errList := []error{}
36
+	for _, node := range nodes {
37
+		err := e.RunEvacuate(node)
38
+		if err != nil {
39
+			// Don't bail out if one node fails
40
+			errList = append(errList, err)
41
+		}
42
+	}
43
+	if len(errList) != 0 {
44
+		return kerrors.NewAggregate(errList)
45
+	}
46
+	return nil
47
+}
48
+
49
+func (e *EvacuateOptions) RunEvacuate(node *kapi.Node) error {
50
+	if e.DryRun {
51
+		listpodsOp := ListPodsOptions{Options: e.Options}
52
+		return listpodsOp.Run()
53
+	}
54
+
55
+	// We do *not* automatically mark the node unschedulable to perform evacuation.
56
+	// Rationale: If we unschedule the node and later the operation is unsuccessful (stopped by user, network error, etc.),
57
+	// we may not be able to recover in some cases to mark the node back to schedulable. To avoid these cases, we recommend
58
+	// user to explicitly set the node to schedulable/unschedulable.
59
+	if !node.Spec.Unschedulable {
60
+		return fmt.Errorf("Node '%s' must be unschedulable to perform evacuation.\nYou can mark the node unschedulable with 'openshift admin manage-node %s --schedulable=false'", node.ObjectMeta.Name, node.ObjectMeta.Name)
61
+	}
62
+
63
+	labelSelector, err := labels.Parse(e.Options.PodSelector)
64
+	if err != nil {
65
+		return err
66
+	}
67
+	fieldSelector := fields.Set{GetPodHostFieldLabel(node.TypeMeta.APIVersion): node.ObjectMeta.Name}.AsSelector()
68
+
69
+	// Filter all pods that satisfies pod label selector and belongs to the given node
70
+	pods, err := e.Options.Kclient.Pods(kapi.NamespaceAll).List(labelSelector, fieldSelector)
71
+	if err != nil {
72
+		return err
73
+	}
74
+	rcs, err := e.Options.Kclient.ReplicationControllers(kapi.NamespaceAll).List(labels.Everything())
75
+	if err != nil {
76
+		return err
77
+	}
78
+
79
+	printerWithHeaders, printerNoHeaders, err := e.Options.GetPrintersByResource("pod")
80
+	if err != nil {
81
+		return err
82
+	}
83
+
84
+	errList := []error{}
85
+	firstPod := true
86
+	numPodsWithNoRC := 0
87
+	// grace = 0 implies delete the pod immediately
88
+	grace := int64(0)
89
+	deleteOptions := &kapi.DeleteOptions{GracePeriodSeconds: &grace}
90
+
91
+	for _, pod := range pods.Items {
92
+		foundrc := false
93
+		for _, rc := range rcs.Items {
94
+			selector := labels.SelectorFromSet(rc.Spec.Selector)
95
+			if selector.Matches(labels.Set(pod.Labels)) {
96
+				foundrc = true
97
+				break
98
+			}
99
+		}
100
+
101
+		if firstPod {
102
+			fmt.Fprintln(e.Options.Writer, "\nMigrating these pods on node: ", node.ObjectMeta.Name, "\n")
103
+			firstPod = false
104
+			printerWithHeaders.PrintObj(&pod, e.Options.Writer)
105
+		} else {
106
+			printerNoHeaders.PrintObj(&pod, e.Options.Writer)
107
+		}
108
+
109
+		if foundrc || e.Force {
110
+			if err := e.Options.Kclient.Pods(pod.Namespace).Delete(pod.Name, deleteOptions); err != nil {
111
+				glog.Errorf("Unable to delete a pod: %+v, error: %v", pod, err)
112
+				errList = append(errList, err)
113
+				continue
114
+			}
115
+		} else { // Pods without replication controller and no --force option
116
+			numPodsWithNoRC++
117
+		}
118
+	}
119
+	if numPodsWithNoRC > 0 {
120
+		err := fmt.Errorf(`Unable to evacuate some pods because they are not backed by replication controller.
121
+Suggested options:
122
+- You can list bare pods in json/yaml format using '--list-pods -o json|yaml'
123
+- Force deletion of bare pods with --force option to --evacuate
124
+- Optionally recreate these bare pods by massaging the json/yaml output from above list pods
125
+`)
126
+		errList = append(errList, err)
127
+	}
128
+
129
+	if len(errList) != 0 {
130
+		return kerrors.NewAggregate(errList)
131
+	}
132
+	return nil
133
+}
0 134
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+package node
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/spf13/cobra"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
10
+	kcmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
11
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
12
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
13
+)
14
+
15
+type ListPodsOptions struct {
16
+	Options *NodeOptions
17
+}
18
+
19
+func (l *ListPodsOptions) AddFlags(cmd *cobra.Command) {
20
+	kcmdutil.AddPrinterFlags(cmd)
21
+}
22
+
23
+func (l *ListPodsOptions) Run() error {
24
+	nodes, err := l.Options.GetNodes()
25
+	if err != nil {
26
+		return err
27
+	}
28
+
29
+	errList := []error{}
30
+	for _, node := range nodes {
31
+		err := l.RunListPods(node)
32
+		if err != nil {
33
+			// Don't bail out if one node fails
34
+			errList = append(errList, err)
35
+		}
36
+	}
37
+	if len(errList) != 0 {
38
+		return kerrors.NewAggregate(errList)
39
+	}
40
+	return nil
41
+}
42
+
43
+func (l *ListPodsOptions) RunListPods(node *kapi.Node) error {
44
+	labelSelector, err := labels.Parse(l.Options.PodSelector)
45
+	if err != nil {
46
+		return err
47
+	}
48
+	fieldSelector := fields.Set{GetPodHostFieldLabel(node.TypeMeta.APIVersion): node.ObjectMeta.Name}.AsSelector()
49
+
50
+	// Filter all pods that satisfies pod label selector and belongs to the given node
51
+	pods, err := l.Options.Kclient.Pods(kapi.NamespaceAll).List(labelSelector, fieldSelector)
52
+	if err != nil {
53
+		return err
54
+	}
55
+
56
+	var printerWithHeaders, printerNoHeaders kubectl.ResourcePrinter
57
+	if l.Options.CmdPrinterOutput {
58
+		printerWithHeaders = l.Options.CmdPrinter
59
+		printerNoHeaders = l.Options.CmdPrinter
60
+	} else {
61
+		printerWithHeaders, printerNoHeaders, err = l.Options.GetPrintersByResource("pod")
62
+		if err != nil {
63
+			return err
64
+		}
65
+	}
66
+	firstPod := true
67
+
68
+	for _, pod := range pods.Items {
69
+		if firstPod {
70
+			fmt.Fprintln(l.Options.Writer, "\nListing matched pods on node: ", node.ObjectMeta.Name, "\n")
71
+			printerWithHeaders.PrintObj(&pod, l.Options.Writer)
72
+			firstPod = false
73
+		} else {
74
+			printerNoHeaders.PrintObj(&pod, l.Options.Writer)
75
+		}
76
+	}
77
+	return err
78
+}
0 79
new file mode 100644
... ...
@@ -0,0 +1,125 @@
0
+package node
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io"
6
+
7
+	"github.com/spf13/cobra"
8
+
9
+	kcmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
10
+
11
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
12
+)
13
+
14
+const (
15
+	ManageNodeCommandName = "manage-node"
16
+
17
+	manageNodeLong = `Manage node operations
18
+
19
+schedulable: Marking node schedulable will allow pods to be schedulable on the node and
20
+	     marking node unschedulable will block pods to be scheduled on the node.
21
+
22
+evacuate: Migrate all/selected pods. There is an option to delete the bare pods.
23
+          It can list all pods that will be migrated before performing evacuation.
24
+
25
+list-pods: List all/selected pods on given/selected nodes. It can list the output in json/yaml format.`
26
+
27
+	manageNodeExample = `	// Block accepting any pods on given nodes
28
+	$ %[1]s <mynode> --schedulable=false
29
+
30
+	// Mark selected nodes as schedulable
31
+	$ %[1]s --selector="<env=dev>" --schedulable=true
32
+
33
+	// Migrate selected pods
34
+	$ %[1]s <mynode> --evacuate --pod-selector="<service=myapp>"
35
+
36
+	// Show pods that will be migrated
37
+	$ %[1]s <mynode> --evacuate --dry-run --pod-selector="<service=myapp>"
38
+
39
+	// List all pods on given nodes
40
+	$ %[1]s <mynode1> <mynode2> --list-pods`
41
+)
42
+
43
+var schedulable, evacuate, listpods bool
44
+
45
+func NewCommandManageNode(f *clientcmd.Factory, commandName, fullName string, out io.Writer) *cobra.Command {
46
+	opts := &NodeOptions{}
47
+	schedulableOp := &SchedulableOptions{Options: opts}
48
+	evacuateOp := &EvacuateOptions{Options: opts}
49
+	listpodsOp := &ListPodsOptions{Options: opts}
50
+
51
+	cmd := &cobra.Command{
52
+		Use:     commandName,
53
+		Short:   "Manage node operations: schedulable, evacuate, list-pods",
54
+		Long:    manageNodeLong,
55
+		Example: fmt.Sprintf(manageNodeExample, fullName),
56
+		Run: func(c *cobra.Command, args []string) {
57
+
58
+			if err := ValidOperation(c); err != nil {
59
+				kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error()))
60
+			}
61
+
62
+			if err := opts.Complete(f, c, args, out); err != nil {
63
+				kcmdutil.CheckErr(err)
64
+			}
65
+
66
+			if err := opts.Validate(); err != nil {
67
+				kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error()))
68
+			}
69
+
70
+			// Cross op validations
71
+			if evacuateOp.DryRun && !evacuate {
72
+				err := errors.New("--dry-run is only applicable for --evacuate")
73
+				kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error()))
74
+			}
75
+
76
+			var err error
77
+			if c.Flag("schedulable").Changed {
78
+				schedulableOp.Schedulable = schedulable
79
+				err = schedulableOp.Run()
80
+			} else if evacuate {
81
+				err = evacuateOp.Run()
82
+			} else if listpods {
83
+				err = listpodsOp.Run()
84
+			}
85
+			kcmdutil.CheckErr(err)
86
+		},
87
+	}
88
+	flags := cmd.Flags()
89
+
90
+	// Supported operations
91
+	flags.BoolVar(&schedulable, "schedulable", false, "Control pod schedulability on the node.")
92
+	flags.BoolVar(&evacuate, "evacuate", false, "Migrate all/selected pods on the node.")
93
+	flags.BoolVar(&listpods, "list-pods", false, "List all/selected pods on the node. Printer flags --output, etc. are only valid for this option.")
94
+
95
+	// Common optional params
96
+	flags.StringVar(&opts.PodSelector, "pod-selector", "", "Label selector to filter pods on the node. Optional param for --evacuate or --list-pods")
97
+	flags.StringVar(&opts.Selector, "selector", "", "Label selector to filter nodes. Either pass one/more nodes as arguments or use this node selector")
98
+
99
+	// Operation specific params
100
+	evacuateOp.AddFlags(cmd)
101
+	listpodsOp.AddFlags(cmd)
102
+
103
+	return cmd
104
+}
105
+
106
+func ValidOperation(c *cobra.Command) error {
107
+	numOps := 0
108
+	if c.Flag("schedulable").Changed {
109
+		numOps++
110
+	}
111
+	if evacuate {
112
+		numOps++
113
+	}
114
+	if listpods {
115
+		numOps++
116
+	}
117
+
118
+	if numOps == 0 {
119
+		return errors.New("must provide a node operation. Supported operations: --schedulable, --evacuate and --list-pods")
120
+	} else if numOps != 1 {
121
+		return errors.New("must provide only one node operation at a time")
122
+	}
123
+	return nil
124
+}
0 125
new file mode 100644
... ...
@@ -0,0 +1,190 @@
0
+package node
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io"
6
+	"reflect"
7
+	"strings"
8
+
9
+	"github.com/spf13/cobra"
10
+
11
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
12
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
13
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
14
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
15
+	kcmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
16
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
17
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
18
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
19
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
20
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
21
+
22
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
23
+)
24
+
25
+type NodeOptions struct {
26
+	DefaultNamespace string
27
+	Kclient          *client.Client
28
+	Writer           io.Writer
29
+
30
+	Mapper            meta.RESTMapper
31
+	Typer             runtime.ObjectTyper
32
+	RESTClientFactory func(mapping *meta.RESTMapping) (resource.RESTClient, error)
33
+	Printer           func(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
34
+
35
+	CmdPrinter       kubectl.ResourcePrinter
36
+	CmdPrinterOutput bool
37
+
38
+	NodeNames []string
39
+
40
+	// Common optional params
41
+	Selector    string
42
+	PodSelector string
43
+}
44
+
45
+func (n *NodeOptions) Complete(f *clientcmd.Factory, c *cobra.Command, args []string, out io.Writer) error {
46
+	defaultNamespace, err := f.DefaultNamespace()
47
+	if err != nil {
48
+		return err
49
+	}
50
+	_, kc, err := f.Clients()
51
+	if err != nil {
52
+		return err
53
+	}
54
+	cmdPrinter, output, err := kcmdutil.PrinterForCommand(c)
55
+	if err != nil {
56
+		return err
57
+	}
58
+	mapper, typer := f.Object()
59
+
60
+	n.DefaultNamespace = defaultNamespace
61
+	n.Kclient = kc
62
+	n.Writer = out
63
+	n.Mapper = mapper
64
+	n.Typer = typer
65
+	n.RESTClientFactory = f.Factory.RESTClient
66
+	n.Printer = f.Printer
67
+	n.NodeNames = []string{}
68
+	n.CmdPrinter = cmdPrinter
69
+	n.CmdPrinterOutput = false
70
+
71
+	if output {
72
+		n.CmdPrinterOutput = true
73
+	}
74
+	if len(args) != 0 {
75
+		n.NodeNames = append(n.NodeNames, args...)
76
+	}
77
+	return nil
78
+}
79
+
80
+func (n *NodeOptions) Validate() error {
81
+	errList := []error{}
82
+	if len(n.Selector) > 0 {
83
+		if _, err := labels.Parse(n.Selector); err != nil {
84
+			errList = append(errList, errors.New("--selector=<node_selector> must be a valid label selector"))
85
+		}
86
+		if len(n.NodeNames) != 0 {
87
+			errList = append(errList, errors.New("either specify --selector=<node_selector> or nodes but not both"))
88
+		}
89
+	}
90
+
91
+	if len(n.PodSelector) > 0 {
92
+		if _, err := labels.Parse(n.PodSelector); err != nil {
93
+			errList = append(errList, errors.New("--pod-selector=<pod_selector> must be a valid label selector"))
94
+		}
95
+	}
96
+	return kerrors.NewAggregate(errList)
97
+}
98
+
99
+func (n *NodeOptions) GetNodes() ([]*kapi.Node, error) {
100
+	nameArgs := []string{"nodes"}
101
+	if len(n.NodeNames) != 0 {
102
+		nameArgs = append(nameArgs, n.NodeNames...)
103
+	}
104
+
105
+	r := resource.NewBuilder(n.Mapper, n.Typer, resource.ClientMapperFunc(n.RESTClientFactory)).
106
+		ContinueOnError().
107
+		NamespaceParam(n.DefaultNamespace).
108
+		SelectorParam(n.Selector).
109
+		ResourceTypeOrNameArgs(true, nameArgs...).
110
+		Flatten().
111
+		Do()
112
+	if r.Err() != nil {
113
+		return nil, r.Err()
114
+	}
115
+
116
+	errList := []error{}
117
+	nodeList := []*kapi.Node{}
118
+	_ = r.Visit(func(info *resource.Info) error {
119
+		node, ok := info.Object.(*kapi.Node)
120
+		if !ok {
121
+			err := fmt.Errorf("cannot convert input to Node: ", reflect.TypeOf(info.Object))
122
+			errList = append(errList, err)
123
+			// Don't bail out if one node fails
124
+			return nil
125
+		}
126
+		nodeList = append(nodeList, node)
127
+		return nil
128
+	})
129
+	if len(errList) != 0 {
130
+		return nodeList, kerrors.NewAggregate(errList)
131
+	}
132
+
133
+	if len(nodeList) == 0 {
134
+		return nodeList, fmt.Errorf("No nodes found")
135
+	} else {
136
+		givenNodeNames := util.NewStringSet(n.NodeNames...)
137
+		foundNodeNames := util.StringSet{}
138
+		for _, node := range nodeList {
139
+			foundNodeNames.Insert(node.ObjectMeta.Name)
140
+		}
141
+		skippedNodeNames := givenNodeNames.Difference(foundNodeNames)
142
+		if skippedNodeNames.Len() > 0 {
143
+			return nodeList, fmt.Errorf("Nodes %v not found", strings.Join(skippedNodeNames.List(), ", "))
144
+		}
145
+	}
146
+	return nodeList, nil
147
+}
148
+
149
+func (n *NodeOptions) GetPrintersByObject(obj runtime.Object) (kubectl.ResourcePrinter, kubectl.ResourcePrinter, error) {
150
+	version, kind, err := kapi.Scheme.ObjectVersionAndKind(obj)
151
+	if err != nil {
152
+		return nil, nil, err
153
+	}
154
+	return n.GetPrinters(kind, version)
155
+}
156
+
157
+func (n *NodeOptions) GetPrintersByResource(resource string) (kubectl.ResourcePrinter, kubectl.ResourcePrinter, error) {
158
+	version, kind, err := n.Mapper.VersionAndKindForResource(resource)
159
+	if err != nil {
160
+		return nil, nil, err
161
+	}
162
+	return n.GetPrinters(kind, version)
163
+}
164
+
165
+func (n *NodeOptions) GetPrinters(kind, version string) (kubectl.ResourcePrinter, kubectl.ResourcePrinter, error) {
166
+	mapping, err := n.Mapper.RESTMapping(kind, version)
167
+	if err != nil {
168
+		return nil, nil, err
169
+	}
170
+
171
+	printerWithHeaders, err := n.Printer(mapping, false)
172
+	if err != nil {
173
+		return nil, nil, err
174
+	}
175
+	printerNoHeaders, err := n.Printer(mapping, true)
176
+	if err != nil {
177
+		return nil, nil, err
178
+	}
179
+	return printerWithHeaders, printerNoHeaders, nil
180
+}
181
+
182
+func GetPodHostFieldLabel(apiVersion string) string {
183
+	switch apiVersion {
184
+	case "v1beta1", "v1beta2":
185
+		return "DesiredState.Host"
186
+	default:
187
+		return "spec.host"
188
+	}
189
+}
0 190
new file mode 100644
... ...
@@ -0,0 +1,60 @@
0
+package node
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
5
+)
6
+
7
+type SchedulableOptions struct {
8
+	Options *NodeOptions
9
+
10
+	Schedulable bool
11
+}
12
+
13
+func (s *SchedulableOptions) Run() error {
14
+	nodes, err := s.Options.GetNodes()
15
+	if err != nil {
16
+		return err
17
+	}
18
+
19
+	errList := []error{}
20
+	ignoreHeaders := false
21
+	for _, node := range nodes {
22
+		err := s.RunSchedulable(node, &ignoreHeaders)
23
+		if err != nil {
24
+			// Don't bail out if one node fails
25
+			errList = append(errList, err)
26
+		}
27
+	}
28
+	if len(errList) != 0 {
29
+		return kerrors.NewAggregate(errList)
30
+	}
31
+	return nil
32
+}
33
+
34
+func (s *SchedulableOptions) RunSchedulable(node *kapi.Node, ignoreHeaders *bool) error {
35
+	var updatedNode *kapi.Node
36
+	var err error
37
+
38
+	if node.Spec.Unschedulable != !s.Schedulable {
39
+		node.Spec.Unschedulable = !s.Schedulable
40
+		updatedNode, err = s.Options.Kclient.Nodes().Update(node)
41
+		if err != nil {
42
+			return err
43
+		}
44
+	} else {
45
+		updatedNode = node
46
+	}
47
+
48
+	printerWithHeaders, printerNoHeaders, err := s.Options.GetPrintersByObject(updatedNode)
49
+	if err != nil {
50
+		return err
51
+	}
52
+	if *ignoreHeaders {
53
+		printerNoHeaders.PrintObj(updatedNode, s.Options.Writer)
54
+	} else {
55
+		printerWithHeaders.PrintObj(updatedNode, s.Options.Writer)
56
+		*ignoreHeaders = true
57
+	}
58
+	return nil
59
+}
... ...
@@ -127,9 +127,7 @@ func RunProcess(f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []
127 127
 
128 128
 		version, kind, err := mapper.VersionAndKindForResource("template")
129 129
 		if mapping, err = mapper.RESTMapping(kind, version); err != nil {
130
-			if err != nil {
131
-				return err
132
-			}
130
+			return err
133 131
 		}
134 132
 	} else {
135 133
 		obj, err := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
... ...
@@ -154,9 +152,7 @@ func RunProcess(f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []
154 154
 			return err
155 155
 		}
156 156
 		if mapping, err = mapper.RESTMapping(kind, version); err != nil {
157
-			if err != nil {
158
-				return err
159
-			}
157
+			return err
160 158
 		}
161 159
 	}
162 160