package node
import (
"fmt"
"github.com/golang/glog"
"github.com/spf13/cobra"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
kerrors "k8s.io/kubernetes/pkg/util/errors"
)
const (
flagGracePeriod = "grace-period"
flagDryRun = "dry-run"
flagForce = "force"
)
type EvacuateOptions struct {
Options *NodeOptions
// Optional params
DryRun bool
Force bool
GracePeriod int64
}
// NewEvacuateOptions creates a new EvacuateOptions with default values.
func NewEvacuateOptions(nodeOptions *NodeOptions) *EvacuateOptions {
return &EvacuateOptions{
Options: nodeOptions,
DryRun: false,
Force: false,
GracePeriod: 30,
}
}
func (e *EvacuateOptions) AddFlags(cmd *cobra.Command) {
flags := cmd.Flags()
flags.BoolVar(&e.DryRun, flagDryRun, e.DryRun, "Show pods that will be migrated. Optional param for --evacuate")
flags.BoolVar(&e.Force, flagForce, e.Force, "Delete pods not backed by replication controller. Optional param for --evacuate")
flags.Int64Var(&e.GracePeriod, flagGracePeriod, e.GracePeriod, "Grace period (seconds) for pods being deleted. Ignored if negative. Optional param for --evacuate")
}
func (e *EvacuateOptions) Run() error {
nodes, err := e.Options.GetNodes()
if err != nil {
return err
}
errList := []error{}
for _, node := range nodes {
err := e.RunEvacuate(node)
if err != nil {
// Don't bail out if one node fails
errList = append(errList, err)
}
}
return kerrors.NewAggregate(errList)
}
func (e *EvacuateOptions) RunEvacuate(node *kapi.Node) error {
if e.DryRun {
listpodsOp := ListPodsOptions{Options: e.Options}
return listpodsOp.Run()
}
// We do *not* automatically mark the node unschedulable to perform evacuation.
// Rationale: If we unschedule the node and later the operation is unsuccessful (stopped by user, network error, etc.),
// we may not be able to recover in some cases to mark the node back to schedulable. To avoid these cases, we recommend
// user to explicitly set the node to schedulable/unschedulable.
if !node.Spec.Unschedulable {
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)
}
labelSelector, err := labels.Parse(e.Options.PodSelector)
if err != nil {
return err
}
fieldSelector := fields.Set{GetPodHostFieldLabel(node.TypeMeta.APIVersion): node.ObjectMeta.Name}.AsSelector()
// Filter all pods that satisfies pod label selector and belongs to the given node
pods, err := e.Options.KubeClient.Pods(kapi.NamespaceAll).List(kapi.ListOptions{LabelSelector: labelSelector, FieldSelector: fieldSelector})
if err != nil {
return err
}
if len(pods.Items) == 0 {
fmt.Fprint(e.Options.ErrWriter, "\nNo pods found on node: ", node.ObjectMeta.Name, "\n\n")
return nil
}
rcs, err := e.Options.KubeClient.ReplicationControllers(kapi.NamespaceAll).List(kapi.ListOptions{})
if err != nil {
return err
}
rss, err := e.Options.KubeClient.ReplicaSets(kapi.NamespaceAll).List(kapi.ListOptions{})
if err != nil {
return err
}
dss, err := e.Options.KubeClient.DaemonSets(kapi.NamespaceAll).List(kapi.ListOptions{})
if err != nil {
return err
}
jobs, err := e.Options.KubeClient.Batch().Jobs(kapi.NamespaceAll).List(kapi.ListOptions{})
if err != nil {
return err
}
printer, err := e.Options.GetPrintersByResource(unversioned.GroupVersionResource{Resource: "pod"})
if err != nil {
return err
}
errList := []error{}
firstPod := true
numUnmanagedPods := 0
var deleteOptions *kapi.DeleteOptions
if e.GracePeriod >= 0 {
deleteOptions = e.makeDeleteOptions()
}
for _, pod := range pods.Items {
isManaged := false
for _, rc := range rcs.Items {
selector := labels.SelectorFromSet(rc.Spec.Selector)
if selector.Matches(labels.Set(pod.Labels)) {
isManaged = true
break
}
}
for _, rs := range rss.Items {
selector := labels.SelectorFromSet(rs.Spec.Selector.MatchLabels)
if selector.Matches(labels.Set(pod.Labels)) {
isManaged = true
break
}
}
for _, ds := range dss.Items {
selector := labels.SelectorFromSet(ds.Spec.Selector.MatchLabels)
if selector.Matches(labels.Set(pod.Labels)) {
isManaged = true
break
}
}
for _, job := range jobs.Items {
selector := labels.SelectorFromSet(job.Spec.Selector.MatchLabels)
if selector.Matches(labels.Set(pod.Labels)) {
isManaged = true
break
}
}
if firstPod {
fmt.Fprint(e.Options.ErrWriter, "\nMigrating these pods on node: ", node.ObjectMeta.Name, "\n\n")
firstPod = false
}
printer.PrintObj(&pod, e.Options.Writer)
if isManaged || e.Force {
if err := e.Options.KubeClient.Pods(pod.Namespace).Delete(pod.Name, deleteOptions); err != nil {
glog.Errorf("Unable to delete a pod: %+v, error: %v", pod, err)
errList = append(errList, err)
continue
}
} else { // Pods without replication controller and no --force option
numUnmanagedPods++
}
}
if numUnmanagedPods > 0 {
err := fmt.Errorf(`Unable to evacuate some pods because they are not managed by replication controller or replica set or deaemon set.
Suggested options:
- You can list bare pods in json/yaml format using '--list-pods -o json|yaml'
- Force deletion of bare pods with --force option to --evacuate
- Optionally recreate these bare pods by massaging the json/yaml output from above list pods
`)
errList = append(errList, err)
}
if len(errList) != 0 {
return kerrors.NewAggregate(errList)
}
return nil
}
// makeDeleteOptions creates the delete options that will be used for pod evacuation.
func (e *EvacuateOptions) makeDeleteOptions() *kapi.DeleteOptions {
return &kapi.DeleteOptions{GracePeriodSeconds: &e.GracePeriod}
}