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>]
... | ... |
@@ -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 |
|