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 |
|