Merge pull request #875 from wawa0210/karmadactl-wait-flag

Introduce --wait flag for karmada unjoin command
This commit is contained in:
karmada-bot 2021-11-01 14:46:54 +08:00 committed by GitHub
commit 209be7f540
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 12 deletions

View File

@ -198,6 +198,7 @@ func (d *ClusterDetector) unJoinClusterAPICluster(clusterName string) error {
DryRun: false, DryRun: false,
}, },
ClusterName: clusterName, ClusterName: clusterName,
Wait: options.DefaultKarmadactlCommandDuration,
} }
err := karmadactl.UnJoinCluster(d.ControllerPlaneConfig, nil, opts) err := karmadactl.UnJoinCluster(d.ControllerPlaneConfig, nil, opts)
if err != nil { if err != nil {

View File

@ -1,11 +1,18 @@
package options package options
import "github.com/spf13/pflag" import (
"time"
"github.com/spf13/pflag"
)
// DefaultKarmadaClusterNamespace defines the default namespace where the member cluster objects are stored. // DefaultKarmadaClusterNamespace defines the default namespace where the member cluster objects are stored.
// The secret owns by cluster objects will be stored in the namespace too. // The secret owns by cluster objects will be stored in the namespace too.
const DefaultKarmadaClusterNamespace = "karmada-cluster" const DefaultKarmadaClusterNamespace = "karmada-cluster"
// DefaultKarmadactlCommandDuration defines the default timeout for karmadactl execute
const DefaultKarmadactlCommandDuration = 60 * time.Second
// GlobalCommandOptions holds the configuration shared by the all sub-commands of `karmadactl`. // GlobalCommandOptions holds the configuration shared by the all sub-commands of `karmadactl`.
type GlobalCommandOptions struct { type GlobalCommandOptions struct {
// KubeConfig holds the control plane KUBECONFIG file path. // KubeConfig holds the control plane KUBECONFIG file path.

View File

@ -11,6 +11,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
kubeclient "k8s.io/client-go/kubernetes" kubeclient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -26,7 +27,11 @@ var (
unjoinShort = `Remove the registration of a cluster from control plane` unjoinShort = `Remove the registration of a cluster from control plane`
unjoinLong = `Unjoin removes the registration of a cluster from control plane.` unjoinLong = `Unjoin removes the registration of a cluster from control plane.`
unjoinExample = ` unjoinExample = `
# Unjoin cluster from karamada control plane
%s unjoin CLUSTER_NAME --cluster-kubeconfig=<KUBECONFIG> %s unjoin CLUSTER_NAME --cluster-kubeconfig=<KUBECONFIG>
# Unjoin cluster from karamada control plane with timeout
%s unjoin CLUSTER_NAME --cluster-kubeconfig=<KUBECONFIG> --wait 2m
` `
) )
@ -38,12 +43,15 @@ func NewCmdUnjoin(cmdOut io.Writer, karmadaConfig KarmadaConfig, cmdStr string)
Use: "unjoin CLUSTER_NAME --cluster-kubeconfig=<KUBECONFIG>", Use: "unjoin CLUSTER_NAME --cluster-kubeconfig=<KUBECONFIG>",
Short: unjoinShort, Short: unjoinShort,
Long: unjoinLong, Long: unjoinLong,
Example: fmt.Sprintf(unjoinExample, cmdStr), Example: getUnjoinExample(cmdStr),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := opts.Complete(args) err := opts.Complete(args)
if err != nil { if err != nil {
klog.Fatalf("Error: %v", err) klog.Fatalf("Error: %v", err)
} }
if errs := opts.Validate(); len(errs) != 0 {
klog.Fatalf("Error: %v", utilerrors.NewAggregate(errs).Error())
}
err = RunUnjoin(cmdOut, karmadaConfig, opts) err = RunUnjoin(cmdOut, karmadaConfig, opts)
if err != nil { if err != nil {
@ -58,6 +66,10 @@ func NewCmdUnjoin(cmdOut io.Writer, karmadaConfig KarmadaConfig, cmdStr string)
return cmd return cmd
} }
func getUnjoinExample(cmdStr string) string {
return fmt.Sprintf(unjoinExample, cmdStr, cmdStr)
}
// CommandUnjoinOption holds all command options. // CommandUnjoinOption holds all command options.
type CommandUnjoinOption struct { type CommandUnjoinOption struct {
options.GlobalCommandOptions options.GlobalCommandOptions
@ -72,6 +84,9 @@ type CommandUnjoinOption struct {
ClusterKubeConfig string ClusterKubeConfig string
forceDeletion bool forceDeletion bool
// Wait tells maximum command execution time
Wait time.Duration
} }
// Complete ensures that options are valid and marshals them if necessary. // Complete ensures that options are valid and marshals them if necessary.
@ -90,6 +105,16 @@ func (j *CommandUnjoinOption) Complete(args []string) error {
return nil return nil
} }
// Validate ensures that command unjoin options are valid.
func (j *CommandUnjoinOption) Validate() []error {
var errs []error
if j.Wait < 0 {
errs = append(errs, fmt.Errorf(" --wait %v must be a positive duration, e.g. 1m0s ", j.Wait))
}
return errs
}
// AddFlags adds flags to the specified FlagSet. // AddFlags adds flags to the specified FlagSet.
func (j *CommandUnjoinOption) AddFlags(flags *pflag.FlagSet) { func (j *CommandUnjoinOption) AddFlags(flags *pflag.FlagSet) {
j.GlobalCommandOptions.AddFlags(flags) j.GlobalCommandOptions.AddFlags(flags)
@ -100,6 +125,8 @@ func (j *CommandUnjoinOption) AddFlags(flags *pflag.FlagSet) {
"Path of the cluster's kubeconfig.") "Path of the cluster's kubeconfig.")
flags.BoolVar(&j.forceDeletion, "force", false, flags.BoolVar(&j.forceDeletion, "force", false,
"Delete cluster and secret resources even if resources in the cluster targeted for unjoin are not removed successfully.") "Delete cluster and secret resources even if resources in the cluster targeted for unjoin are not removed successfully.")
flags.DurationVar(&j.Wait, "wait", 60*time.Second, "wait for the unjoin command execution process(default 60s), if there is no success after this time, timeout will be returned.")
} }
// RunUnjoin is the implementation of the 'unjoin' command. // RunUnjoin is the implementation of the 'unjoin' command.
@ -133,7 +160,7 @@ func UnJoinCluster(controlPlaneRestConfig, clusterConfig *rest.Config, opts Comm
controlPlaneKarmadaClient := karmadaclientset.NewForConfigOrDie(controlPlaneRestConfig) controlPlaneKarmadaClient := karmadaclientset.NewForConfigOrDie(controlPlaneRestConfig)
// delete the cluster object in host cluster that associates the unjoining cluster // delete the cluster object in host cluster that associates the unjoining cluster
err = deleteClusterObject(controlPlaneKarmadaClient, opts.ClusterName, opts.DryRun) err = deleteClusterObject(controlPlaneKarmadaClient, opts)
if err != nil { if err != nil {
klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", opts.ClusterName, err) klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", opts.ClusterName, err)
return err return err
@ -236,35 +263,35 @@ func deleteNamespaceFromUnjoinCluster(clusterKubeClient kubeclient.Interface, na
} }
// deleteClusterObject delete the cluster object in host cluster that associates the unjoining cluster // deleteClusterObject delete the cluster object in host cluster that associates the unjoining cluster
func deleteClusterObject(controlPlaneKarmadaClient *karmadaclientset.Clientset, clusterName string, dryRun bool) error { func deleteClusterObject(controlPlaneKarmadaClient *karmadaclientset.Clientset, opts CommandUnjoinOption) error {
if dryRun { if opts.DryRun {
return nil return nil
} }
err := controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Delete(context.TODO(), clusterName, metav1.DeleteOptions{}) err := controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Delete(context.TODO(), opts.ClusterName, metav1.DeleteOptions{})
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
return nil return nil
} }
if err != nil { if err != nil {
klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", clusterName, err) klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", opts.ClusterName, err)
return err return err
} }
// make sure the given cluster object has been deleted // make sure the given cluster object has been deleted
err = wait.Poll(1*time.Second, 1*time.Minute, func() (done bool, err error) { err = wait.Poll(1*time.Second, opts.Wait, func() (done bool, err error) {
_, err = controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Get(context.TODO(), clusterName, metav1.GetOptions{}) _, err = controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Get(context.TODO(), opts.ClusterName, metav1.GetOptions{})
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
return true, nil return true, nil
} }
if err != nil { if err != nil {
klog.Errorf("Failed to get cluster %s. err: %v", clusterName, err) klog.Errorf("Failed to get cluster %s. err: %v", opts.ClusterName, err)
return false, err return false, err
} }
klog.Infof("Waiting for the cluster object %s to be deleted", clusterName) klog.Infof("Waiting for the cluster object %s to be deleted", opts.ClusterName)
return false, nil return false, nil
}) })
if err != nil { if err != nil {
klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", clusterName, err) klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", opts.ClusterName, err)
return err return err
} }

View File

@ -168,6 +168,7 @@ var _ = ginkgo.Describe("[namespace auto-provision] namespace auto-provision tes
ClusterName: clusterName, ClusterName: clusterName,
ClusterContext: clusterContext, ClusterContext: clusterContext,
ClusterKubeConfig: kubeConfigPath, ClusterKubeConfig: kubeConfigPath,
Wait: options.DefaultKarmadactlCommandDuration,
} }
err := karmadactl.RunUnjoin(os.Stdout, karmadaConfig, opts) err := karmadactl.RunUnjoin(os.Stdout, karmadaConfig, opts)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(err).ShouldNot(gomega.HaveOccurred())