mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-22 21:10:22 +00:00
387 lines
13 KiB
Go
387 lines
13 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
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 cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang/glog"
|
|
"github.com/spf13/cobra"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/uuid"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"k8s.io/client-go/dynamic"
|
|
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
|
kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
|
|
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
|
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
|
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
|
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
|
)
|
|
|
|
var (
|
|
delete_long = templates.LongDesc(i18n.T(`
|
|
Delete resources by filenames, stdin, resources and names, or by resources and label selector.
|
|
|
|
JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
|
|
resources and names, or resources and label selector.
|
|
|
|
Some resources, such as pods, support graceful deletion. These resources define a default period
|
|
before they are forcibly terminated (the grace period) but you may override that value with
|
|
the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
|
|
represent entities in the cluster, deletion may not be acknowledged immediately. If the node
|
|
hosting a pod is down or cannot reach the API server, termination may take significantly longer
|
|
than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
|
|
the --force flag.
|
|
|
|
IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
|
|
terminated, which can leave those processes running until the node detects the deletion and
|
|
completes graceful deletion. If your processes use shared storage or talk to a remote API and
|
|
depend on the name of the pod to identify themselves, force deleting those pods may result in
|
|
multiple processes running on different machines using the same identification which may lead
|
|
to data corruption or inconsistency. Only force delete pods when you are sure the pod is
|
|
terminated, or if your application can tolerate multiple copies of the same pod running at once.
|
|
Also, if you force delete pods the scheduler may place new pods on those nodes before the node
|
|
has released those resources and causing those pods to be evicted immediately.
|
|
|
|
Note that the delete command does NOT do resource version checks, so if someone submits an
|
|
update to a resource right when you submit a delete, their update will be lost along with the
|
|
rest of the resource.`))
|
|
|
|
delete_example = templates.Examples(i18n.T(`
|
|
# Delete a pod using the type and name specified in pod.json.
|
|
kubectl delete -f ./pod.json
|
|
|
|
# Delete a pod based on the type and name in the JSON passed into stdin.
|
|
cat pod.json | kubectl delete -f -
|
|
|
|
# Delete pods and services with same names "baz" and "foo"
|
|
kubectl delete pod,service baz foo
|
|
|
|
# Delete pods and services with label name=myLabel.
|
|
kubectl delete pods,services -l name=myLabel
|
|
|
|
# Delete a pod with minimal delay
|
|
kubectl delete pod foo --now
|
|
|
|
# Force delete a pod on a dead node
|
|
kubectl delete pod foo --grace-period=0 --force
|
|
|
|
# Delete all pods
|
|
kubectl delete pods --all`))
|
|
)
|
|
|
|
type DeleteOptions struct {
|
|
resource.FilenameOptions
|
|
|
|
LabelSelector string
|
|
FieldSelector string
|
|
DeleteAll bool
|
|
IgnoreNotFound bool
|
|
Cascade bool
|
|
DeleteNow bool
|
|
ForceDeletion bool
|
|
WaitForDeletion bool
|
|
|
|
GracePeriod int
|
|
Timeout time.Duration
|
|
|
|
Output string
|
|
|
|
DynamicClient dynamic.Interface
|
|
Mapper meta.RESTMapper
|
|
Result *resource.Result
|
|
|
|
genericclioptions.IOStreams
|
|
}
|
|
|
|
func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
|
deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
|
|
DisableFlagsInUseLine: true,
|
|
Short: i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"),
|
|
Long: delete_long,
|
|
Example: delete_example,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
o := deleteFlags.ToOptions(nil, streams)
|
|
cmdutil.CheckErr(o.Complete(f, args, cmd))
|
|
cmdutil.CheckErr(o.Validate(cmd))
|
|
cmdutil.CheckErr(o.RunDelete())
|
|
},
|
|
SuggestFor: []string{"rm"},
|
|
}
|
|
|
|
deleteFlags.AddFlags(cmd)
|
|
|
|
cmdutil.AddIncludeUninitializedFlag(cmd)
|
|
return cmd
|
|
}
|
|
|
|
func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
|
|
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
|
|
if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
|
|
// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
|
|
o.IgnoreNotFound = true
|
|
}
|
|
}
|
|
if o.DeleteNow {
|
|
if o.GracePeriod != -1 {
|
|
return fmt.Errorf("--now and --grace-period cannot be specified together")
|
|
}
|
|
o.GracePeriod = 1
|
|
}
|
|
if o.GracePeriod == 0 && !o.ForceDeletion {
|
|
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
|
|
// into --grace-period=1. Users may provide --force to bypass this conversion.
|
|
o.GracePeriod = 1
|
|
}
|
|
|
|
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
|
|
r := f.NewBuilder().
|
|
Unstructured().
|
|
ContinueOnError().
|
|
NamespaceParam(cmdNamespace).DefaultNamespace().
|
|
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
|
LabelSelectorParam(o.LabelSelector).
|
|
FieldSelectorParam(o.FieldSelector).
|
|
IncludeUninitialized(includeUninitialized).
|
|
SelectAllParam(o.DeleteAll).
|
|
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
|
|
Flatten().
|
|
Do()
|
|
err = r.Err()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.Result = r
|
|
|
|
o.Mapper, err = f.ToRESTMapper()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
o.DynamicClient, err = f.DynamicClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
|
|
if o.Output != "" && o.Output != "name" {
|
|
return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", o.Output)
|
|
}
|
|
|
|
if o.DeleteAll && len(o.LabelSelector) > 0 {
|
|
return fmt.Errorf("cannot set --all and --selector at the same time")
|
|
}
|
|
if o.DeleteAll && len(o.FieldSelector) > 0 {
|
|
return fmt.Errorf("cannot set --all and --field-selector at the same time")
|
|
}
|
|
|
|
if o.GracePeriod == 0 && !o.ForceDeletion && !o.WaitForDeletion {
|
|
// With the explicit --wait flag we need extra validation for backward compatibility
|
|
return fmt.Errorf("--grace-period=0 must have either --force specified, or --wait to be set to true")
|
|
}
|
|
|
|
switch {
|
|
case o.GracePeriod == 0 && o.ForceDeletion:
|
|
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
|
|
case o.ForceDeletion:
|
|
fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *DeleteOptions) RunDelete() error {
|
|
return o.DeleteResult(o.Result)
|
|
}
|
|
|
|
func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
|
|
found := 0
|
|
if o.IgnoreNotFound {
|
|
r = r.IgnoreErrors(errors.IsNotFound)
|
|
}
|
|
deletedInfos := []*resource.Info{}
|
|
err := r.Visit(func(info *resource.Info, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deletedInfos = append(deletedInfos, info)
|
|
found++
|
|
|
|
options := &metav1.DeleteOptions{}
|
|
if o.GracePeriod >= 0 {
|
|
options = metav1.NewDeleteOptions(int64(o.GracePeriod))
|
|
}
|
|
policy := metav1.DeletePropagationBackground
|
|
if !o.Cascade {
|
|
policy = metav1.DeletePropagationOrphan
|
|
}
|
|
options.PropagationPolicy = &policy
|
|
|
|
return o.deleteResource(info, options)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found == 0 {
|
|
fmt.Fprintf(o.Out, "No resources found\n")
|
|
return nil
|
|
}
|
|
if !o.WaitForDeletion {
|
|
return nil
|
|
}
|
|
// if we don't have a dynamic client, we don't want to wait. Eventually when delete is cleaned up, this will likely
|
|
// drop out.
|
|
if o.DynamicClient == nil {
|
|
return nil
|
|
}
|
|
|
|
effectiveTimeout := o.Timeout
|
|
if effectiveTimeout == 0 {
|
|
// if we requested to wait forever, set it to a week.
|
|
effectiveTimeout = 168 * time.Hour
|
|
}
|
|
waitOptions := kubectlwait.WaitOptions{
|
|
ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
|
|
DynamicClient: o.DynamicClient,
|
|
Timeout: effectiveTimeout,
|
|
|
|
Printer: printers.NewDiscardingPrinter(),
|
|
ConditionFn: kubectlwait.IsDeleted,
|
|
IOStreams: o.IOStreams,
|
|
}
|
|
err = waitOptions.RunWait()
|
|
if errors.IsForbidden(err) {
|
|
// if we're forbidden from waiting, we shouldn't fail.
|
|
glog.V(1).Info(err)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
|
|
// TODO: Remove this in or after 1.12 release.
|
|
// Server version >= 1.11 no longer needs this hack.
|
|
mapping := info.ResourceMapping()
|
|
if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
|
|
mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
|
|
if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
|
|
return cmdutil.AddSourceToErr("deleting", info.Source, err)
|
|
}
|
|
|
|
o.PrintObj(info)
|
|
return nil
|
|
}
|
|
|
|
// TODO: Remove this in or after 1.12 release.
|
|
// Server version >= 1.11 no longer needs this hack.
|
|
func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
|
|
dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
|
|
obj, err := dsClient.Get(name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ds := &appsv1.DaemonSet{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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(uuid.NewUUID()): string(uuid.NewUUID()),
|
|
}
|
|
// force update to avoid version conflict
|
|
ds.ResourceVersion = ""
|
|
|
|
out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Wait for the daemon set controller to kill all the daemon pods.
|
|
if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
|
|
updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
updatedDS := &appsv1.DaemonSet{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
|
|
return false, nil
|
|
}
|
|
|
|
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// deletion printing is special because we do not have an object to print.
|
|
// This mirrors name printer behavior
|
|
func (o *DeleteOptions) PrintObj(info *resource.Info) {
|
|
operation := "deleted"
|
|
groupKind := info.Mapping.GroupVersionKind
|
|
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
|
|
if len(groupKind.Group) == 0 {
|
|
kindString = strings.ToLower(groupKind.Kind)
|
|
}
|
|
|
|
if o.GracePeriod == 0 {
|
|
operation = "force deleted"
|
|
}
|
|
|
|
if o.Output == "name" {
|
|
// -o name: prints resource/name
|
|
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
|
|
return
|
|
}
|
|
|
|
// understandable output by default
|
|
fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
|
|
}
|