2015-01-22 17:46:38 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2015-01-22 17:46:38 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package kubectl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-06-30 01:29:53 +00:00
|
|
|
"strings"
|
2015-01-22 17:46:38 +00:00
|
|
|
"time"
|
|
|
|
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/meta"
|
2015-10-29 10:07:00 +00:00
|
|
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
2015-08-13 19:01:50 +00:00
|
|
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
2015-10-13 10:11:48 +00:00
|
|
|
"k8s.io/kubernetes/pkg/fields"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/labels"
|
2015-10-08 22:52:11 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util"
|
2015-10-29 10:07:00 +00:00
|
|
|
utilerrors "k8s.io/kubernetes/pkg/util/errors"
|
2015-08-27 17:18:16 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/wait"
|
2015-01-22 17:46:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-05-29 23:16:30 +00:00
|
|
|
Interval = time.Second * 1
|
2015-05-22 15:05:24 +00:00
|
|
|
Timeout = time.Minute * 5
|
2015-01-22 17:46:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// A Reaper handles terminating an object as gracefully as possible.
|
2015-05-29 23:16:30 +00:00
|
|
|
// timeout is how long we'll wait for the termination to be successful
|
|
|
|
// gracePeriod is time given to an API object for it to delete itself cleanly (e.g. pod shutdown)
|
2015-01-22 17:46:38 +00:00
|
|
|
type Reaper interface {
|
2015-11-12 14:42:29 +00:00
|
|
|
Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
|
|
|
|
2015-04-23 04:15:15 +00:00
|
|
|
type NoSuchReaperError struct {
|
|
|
|
kind string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NoSuchReaperError) Error() string {
|
|
|
|
return fmt.Sprintf("no reaper has been implemented for %q", n.kind)
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsNoSuchReaperError(err error) bool {
|
|
|
|
_, ok := err.(*NoSuchReaperError)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2015-08-31 16:29:18 +00:00
|
|
|
func ReaperFor(kind string, c client.Interface) (Reaper, error) {
|
2015-01-22 17:46:38 +00:00
|
|
|
switch kind {
|
|
|
|
case "ReplicationController":
|
2015-05-19 14:07:58 +00:00
|
|
|
return &ReplicationControllerReaper{c, Interval, Timeout}, nil
|
2015-09-12 16:46:10 +00:00
|
|
|
case "DaemonSet":
|
|
|
|
return &DaemonSetReaper{c, Interval, Timeout}, nil
|
2015-01-22 17:46:38 +00:00
|
|
|
case "Pod":
|
|
|
|
return &PodReaper{c}, nil
|
|
|
|
case "Service":
|
|
|
|
return &ServiceReaper{c}, nil
|
2015-09-16 15:32:59 +00:00
|
|
|
case "Job":
|
|
|
|
return &JobReaper{c, Interval, Timeout}, nil
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
2015-04-23 04:15:15 +00:00
|
|
|
return nil, &NoSuchReaperError{kind}
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
|
|
|
|
2015-05-25 08:20:34 +00:00
|
|
|
func ReaperForReplicationController(c client.Interface, timeout time.Duration) (Reaper, error) {
|
|
|
|
return &ReplicationControllerReaper{c, Interval, timeout}, nil
|
|
|
|
}
|
|
|
|
|
2015-01-22 17:46:38 +00:00
|
|
|
type ReplicationControllerReaper struct {
|
|
|
|
client.Interface
|
|
|
|
pollInterval, timeout time.Duration
|
|
|
|
}
|
2015-09-12 16:46:10 +00:00
|
|
|
type DaemonSetReaper struct {
|
2015-08-27 17:18:16 +00:00
|
|
|
client.Interface
|
|
|
|
pollInterval, timeout time.Duration
|
|
|
|
}
|
2015-09-16 15:32:59 +00:00
|
|
|
type JobReaper struct {
|
|
|
|
client.Interface
|
|
|
|
pollInterval, timeout time.Duration
|
|
|
|
}
|
2015-01-22 17:46:38 +00:00
|
|
|
type PodReaper struct {
|
|
|
|
client.Interface
|
|
|
|
}
|
|
|
|
type ServiceReaper struct {
|
|
|
|
client.Interface
|
|
|
|
}
|
|
|
|
|
|
|
|
type objInterface interface {
|
|
|
|
Delete(name string) error
|
|
|
|
Get(name string) (meta.Interface, error)
|
|
|
|
}
|
|
|
|
|
2015-06-30 01:29:53 +00:00
|
|
|
// getOverlappingControllers finds rcs that this controller overlaps, as well as rcs overlapping this controller.
|
|
|
|
func getOverlappingControllers(c client.ReplicationControllerInterface, rc *api.ReplicationController) ([]api.ReplicationController, error) {
|
2015-10-13 10:11:48 +00:00
|
|
|
rcs, err := c.List(labels.Everything(), fields.Everything())
|
2015-06-30 01:29:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error getting replication controllers: %v", err)
|
|
|
|
}
|
|
|
|
var matchingRCs []api.ReplicationController
|
|
|
|
rcLabels := labels.Set(rc.Spec.Selector)
|
|
|
|
for _, controller := range rcs.Items {
|
|
|
|
newRCLabels := labels.Set(controller.Spec.Selector)
|
|
|
|
if labels.SelectorFromSet(newRCLabels).Matches(rcLabels) || labels.SelectorFromSet(rcLabels).Matches(newRCLabels) {
|
|
|
|
matchingRCs = append(matchingRCs, controller)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return matchingRCs, nil
|
|
|
|
}
|
|
|
|
|
2015-11-12 14:42:29 +00:00
|
|
|
func (reaper *ReplicationControllerReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
|
2015-01-22 17:46:38 +00:00
|
|
|
rc := reaper.ReplicationControllers(namespace)
|
2015-09-16 15:32:59 +00:00
|
|
|
scaler, err := ScalerFor("ReplicationController", *reaper)
|
2015-03-07 06:30:56 +00:00
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-03-07 06:30:56 +00:00
|
|
|
}
|
2015-06-30 01:29:53 +00:00
|
|
|
ctrl, err := rc.Get(name)
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-06-30 01:29:53 +00:00
|
|
|
}
|
2015-05-29 23:16:30 +00:00
|
|
|
if timeout == 0 {
|
2015-06-30 01:29:53 +00:00
|
|
|
timeout = Timeout + time.Duration(10*ctrl.Spec.Replicas)*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
// The rc manager will try and detect all matching rcs for a pod's labels,
|
|
|
|
// and only sync the oldest one. This means if we have a pod with labels
|
2015-08-12 13:33:08 +00:00
|
|
|
// [(k1: v1), (k2: v2)] and two rcs: rc1 with selector [(k1=v1)], and rc2 with selector [(k1=v1),(k2=v2)],
|
2015-06-30 01:29:53 +00:00
|
|
|
// the rc manager will sync the older of the two rcs.
|
|
|
|
//
|
|
|
|
// If there are rcs with a superset of labels, eg:
|
2015-08-12 13:33:08 +00:00
|
|
|
// deleting: (k1=v1), superset: (k2=v2, k1=v1)
|
2015-06-30 01:29:53 +00:00
|
|
|
// - It isn't safe to delete the rc because there could be a pod with labels
|
2015-08-12 13:33:08 +00:00
|
|
|
// (k1=v1) that isn't managed by the superset rc. We can't scale it down
|
|
|
|
// either, because there could be a pod (k2=v2, k1=v1) that it deletes
|
2015-06-30 01:29:53 +00:00
|
|
|
// causing a fight with the superset rc.
|
|
|
|
// If there are rcs with a subset of labels, eg:
|
2015-08-12 13:33:08 +00:00
|
|
|
// deleting: (k2=v2, k1=v1), subset: (k1=v1), superset: (k2=v2, k1=v1, k3=v3)
|
|
|
|
// - Even if it's safe to delete this rc without a scale down because all it's pods
|
|
|
|
// are being controlled by the subset rc the code returns an error.
|
|
|
|
|
2015-06-30 01:29:53 +00:00
|
|
|
// In theory, creating overlapping controllers is user error, so the loop below
|
|
|
|
// tries to account for this logic only in the common case, where we end up
|
|
|
|
// with multiple rcs that have an exact match on selectors.
|
|
|
|
|
|
|
|
overlappingCtrls, err := getOverlappingControllers(rc, ctrl)
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return fmt.Errorf("error getting replication controllers: %v", err)
|
2015-06-30 01:29:53 +00:00
|
|
|
}
|
|
|
|
exactMatchRCs := []api.ReplicationController{}
|
|
|
|
overlapRCs := []string{}
|
|
|
|
for _, overlappingRC := range overlappingCtrls {
|
|
|
|
if len(overlappingRC.Spec.Selector) == len(ctrl.Spec.Selector) {
|
|
|
|
exactMatchRCs = append(exactMatchRCs, overlappingRC)
|
|
|
|
} else {
|
|
|
|
overlapRCs = append(overlapRCs, overlappingRC.Name)
|
2015-05-29 23:16:30 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-30 01:29:53 +00:00
|
|
|
if len(overlapRCs) > 0 {
|
2015-11-12 14:42:29 +00:00
|
|
|
return fmt.Errorf(
|
2015-06-30 01:29:53 +00:00
|
|
|
"Detected overlapping controllers for rc %v: %v, please manage deletion individually with --cascade=false.",
|
|
|
|
ctrl.Name, strings.Join(overlapRCs, ","))
|
|
|
|
}
|
|
|
|
if len(exactMatchRCs) == 1 {
|
|
|
|
// No overlapping controllers.
|
|
|
|
retry := NewRetryParams(reaper.pollInterval, reaper.timeout)
|
|
|
|
waitForReplicas := NewRetryParams(reaper.pollInterval, timeout)
|
|
|
|
if err = scaler.Scale(namespace, name, 0, nil, retry, waitForReplicas); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-06-30 01:29:53 +00:00
|
|
|
}
|
2015-05-14 14:49:43 +00:00
|
|
|
}
|
2015-01-22 17:46:38 +00:00
|
|
|
if err := rc.Delete(name); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
2015-11-12 14:42:29 +00:00
|
|
|
return nil
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
|
|
|
|
2015-11-12 14:42:29 +00:00
|
|
|
func (reaper *DaemonSetReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
|
2015-10-12 18:18:50 +00:00
|
|
|
ds, err := reaper.Extensions().DaemonSets(namespace).Get(name)
|
2015-08-27 17:18:16 +00:00
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-08-27 17:18:16 +00:00
|
|
|
}
|
|
|
|
|
2015-10-08 22:52:11 +00:00
|
|
|
// We set the nodeSelector to a random label. This label is nearly guaranteed
|
|
|
|
// to not be set on any node so the DameonSetController will start deleting
|
|
|
|
// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
|
|
|
|
// the DaemonSet.
|
|
|
|
ds.Spec.Template.Spec.NodeSelector = map[string]string{
|
|
|
|
string(util.NewUUID()): string(util.NewUUID()),
|
2015-08-27 17:18:16 +00:00
|
|
|
}
|
2015-10-05 21:14:22 +00:00
|
|
|
// force update to avoid version conflict
|
|
|
|
ds.ResourceVersion = ""
|
2015-09-12 16:46:10 +00:00
|
|
|
|
2015-10-12 18:18:50 +00:00
|
|
|
if ds, err = reaper.Extensions().DaemonSets(namespace).Update(ds); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-10-05 21:14:22 +00:00
|
|
|
}
|
2015-09-12 16:46:10 +00:00
|
|
|
|
|
|
|
// Wait for the daemon set controller to kill all the daemon pods.
|
|
|
|
if err := wait.Poll(reaper.pollInterval, reaper.timeout, func() (bool, error) {
|
2015-10-12 18:18:50 +00:00
|
|
|
updatedDS, err := reaper.Extensions().DaemonSets(namespace).Get(name)
|
2015-08-27 17:18:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
2015-09-12 16:46:10 +00:00
|
|
|
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
|
|
|
|
}); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-08-27 17:18:16 +00:00
|
|
|
}
|
|
|
|
|
2015-10-12 18:18:50 +00:00
|
|
|
if err := reaper.Extensions().DaemonSets(namespace).Delete(name); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-08-27 17:18:16 +00:00
|
|
|
}
|
2015-11-12 14:42:29 +00:00
|
|
|
return nil
|
2015-08-27 17:18:16 +00:00
|
|
|
}
|
|
|
|
|
2015-11-12 14:42:29 +00:00
|
|
|
func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
|
2015-10-12 18:18:50 +00:00
|
|
|
jobs := reaper.Extensions().Jobs(namespace)
|
2015-10-29 10:07:00 +00:00
|
|
|
pods := reaper.Pods(namespace)
|
2015-09-16 15:32:59 +00:00
|
|
|
scaler, err := ScalerFor("Job", *reaper)
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-09-16 15:32:59 +00:00
|
|
|
}
|
|
|
|
job, err := jobs.Get(name)
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-09-16 15:32:59 +00:00
|
|
|
}
|
|
|
|
if timeout == 0 {
|
|
|
|
// we will never have more active pods than job.Spec.Parallelism
|
|
|
|
parallelism := *job.Spec.Parallelism
|
|
|
|
timeout = Timeout + time.Duration(10*parallelism)*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle overlapping jobs
|
|
|
|
retry := NewRetryParams(reaper.pollInterval, reaper.timeout)
|
|
|
|
waitForJobs := NewRetryParams(reaper.pollInterval, timeout)
|
|
|
|
if err = scaler.Scale(namespace, name, 0, nil, retry, waitForJobs); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-09-16 15:32:59 +00:00
|
|
|
}
|
2015-10-29 10:07:00 +00:00
|
|
|
// at this point only dead pods are left, that should be removed
|
|
|
|
selector, _ := extensions.PodSelectorAsSelector(job.Spec.Selector)
|
|
|
|
podList, err := pods.List(selector, fields.Everything())
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-10-29 10:07:00 +00:00
|
|
|
}
|
|
|
|
errList := []error{}
|
|
|
|
for _, pod := range podList.Items {
|
|
|
|
if err := pods.Delete(pod.Name, gracePeriod); err != nil {
|
|
|
|
errList = append(errList, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(errList) > 0 {
|
2015-11-12 14:42:29 +00:00
|
|
|
return utilerrors.NewAggregate(errList)
|
2015-10-29 10:07:00 +00:00
|
|
|
}
|
|
|
|
// once we have all the pods removed we can safely remove the job itself
|
2015-09-16 15:32:59 +00:00
|
|
|
if err := jobs.Delete(name, gracePeriod); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-09-16 15:32:59 +00:00
|
|
|
}
|
2015-11-12 14:42:29 +00:00
|
|
|
return nil
|
2015-09-16 15:32:59 +00:00
|
|
|
}
|
|
|
|
|
2015-11-12 14:42:29 +00:00
|
|
|
func (reaper *PodReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
|
2015-01-22 17:46:38 +00:00
|
|
|
pods := reaper.Pods(namespace)
|
|
|
|
_, err := pods.Get(name)
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
2015-04-28 12:21:57 +00:00
|
|
|
if err := pods.Delete(name, gracePeriod); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
2015-04-28 12:21:57 +00:00
|
|
|
|
2015-11-12 14:42:29 +00:00
|
|
|
return nil
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
|
|
|
|
2015-11-12 14:42:29 +00:00
|
|
|
func (reaper *ServiceReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
|
2015-01-22 17:46:38 +00:00
|
|
|
services := reaper.Services(namespace)
|
|
|
|
_, err := services.Get(name)
|
|
|
|
if err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
|
|
|
if err := services.Delete(name); err != nil {
|
2015-11-12 14:42:29 +00:00
|
|
|
return err
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|
2015-11-12 14:42:29 +00:00
|
|
|
return nil
|
2015-01-22 17:46:38 +00:00
|
|
|
}
|