github.com/pingcap/tiup@v1.15.1/components/dm/command/scale_in.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package command
    15  
    16  import (
    17  	"context"
    18  	"crypto/tls"
    19  	"fmt"
    20  	"time"
    21  
    22  	"github.com/pingcap/errors"
    23  	dm "github.com/pingcap/tiup/components/dm/spec"
    24  	dmtask "github.com/pingcap/tiup/components/dm/task"
    25  	"github.com/pingcap/tiup/pkg/cluster/api"
    26  	operator "github.com/pingcap/tiup/pkg/cluster/operation"
    27  	"github.com/pingcap/tiup/pkg/cluster/spec"
    28  	"github.com/pingcap/tiup/pkg/cluster/task"
    29  	"github.com/pingcap/tiup/pkg/set"
    30  	"github.com/pingcap/tiup/pkg/utils"
    31  	"github.com/spf13/cobra"
    32  )
    33  
    34  func newScaleInCmd() *cobra.Command {
    35  	cmd := &cobra.Command{
    36  		Use:   "scale-in <cluster-name>",
    37  		Short: "Scale in a DM cluster",
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			if len(args) != 1 {
    40  				return cmd.Help()
    41  			}
    42  
    43  			clusterName := args[0]
    44  
    45  			scale := func(b *task.Builder, imetadata spec.Metadata, tlsCfg *tls.Config) {
    46  				metadata := imetadata.(*dm.Metadata)
    47  				b.Func(
    48  					fmt.Sprintf("ScaleInCluster: options=%+v", gOpt),
    49  					func(ctx context.Context) error {
    50  						return ScaleInDMCluster(ctx, metadata.Topology, gOpt, tlsCfg)
    51  					},
    52  				).Serial(dmtask.NewUpdateDMMeta(clusterName, metadata, gOpt.Nodes))
    53  			}
    54  
    55  			return cm.ScaleIn(clusterName, skipConfirm, gOpt, scale)
    56  		},
    57  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    58  			switch len(args) {
    59  			case 0:
    60  				return shellCompGetClusterName(cm, toComplete)
    61  			default:
    62  				return nil, cobra.ShellCompDirectiveNoFileComp
    63  			}
    64  		},
    65  	}
    66  
    67  	cmd.Flags().StringSliceVarP(&gOpt.Nodes, "node", "N", nil, "Specify the nodes (required)")
    68  	cmd.Flags().BoolVar(&gOpt.Force, "force", false, "Force just try stop and destroy instance before removing the instance from topo")
    69  
    70  	_ = cmd.MarkFlagRequired("node")
    71  
    72  	return cmd
    73  }
    74  
    75  // ScaleInDMCluster scale in dm cluster.
    76  func ScaleInDMCluster(
    77  	ctx context.Context,
    78  	topo *dm.Specification,
    79  	options operator.Options,
    80  	tlsCfg *tls.Config,
    81  ) error {
    82  	// instances by uuid
    83  	instances := map[string]dm.Instance{}
    84  	instCount := map[string]int{}
    85  
    86  	// make sure all nodeIds exists in topology
    87  	for _, component := range topo.ComponentsByStartOrder() {
    88  		for _, instance := range component.Instances() {
    89  			instances[instance.ID()] = instance
    90  			instCount[instance.GetManageHost()]++
    91  		}
    92  	}
    93  
    94  	// Clean components
    95  	deletedDiff := map[string][]dm.Instance{}
    96  	deletedNodes := set.NewStringSet(options.Nodes...)
    97  	for nodeID := range deletedNodes {
    98  		inst, found := instances[nodeID]
    99  		if !found {
   100  			return errors.Errorf("cannot find node id '%s' in topology", nodeID)
   101  		}
   102  		deletedDiff[inst.ComponentName()] = append(deletedDiff[inst.ComponentName()], inst)
   103  	}
   104  
   105  	// Cannot delete all DM DMMaster servers
   106  	if len(deletedDiff[dm.ComponentDMMaster]) == len(topo.Masters) {
   107  		return errors.New("cannot delete all dm-master servers")
   108  	}
   109  
   110  	if options.Force {
   111  		for _, component := range topo.ComponentsByStartOrder() {
   112  			for _, instance := range component.Instances() {
   113  				if !deletedNodes.Exist(instance.ID()) {
   114  					continue
   115  				}
   116  				instCount[instance.GetManageHost()]--
   117  				if err := operator.StopAndDestroyInstance(ctx, topo, instance, options, false, instCount[instance.GetManageHost()] == 0, tlsCfg); err != nil {
   118  					log.Warnf("failed to stop/destroy %s: %v", component.Name(), err)
   119  				}
   120  			}
   121  		}
   122  		return nil
   123  	}
   124  
   125  	// At least a DMMaster server exists
   126  	var dmMasterClient *api.DMMasterClient
   127  	var dmMasterEndpoint []string
   128  	for _, instance := range (&dm.DMMasterComponent{Topology: topo}).Instances() {
   129  		if !deletedNodes.Exist(instance.ID()) {
   130  			dmMasterEndpoint = append(dmMasterEndpoint, utils.JoinHostPort(instance.GetManageHost(), instance.GetPort()))
   131  		}
   132  	}
   133  
   134  	if len(dmMasterEndpoint) == 0 {
   135  		return errors.New("cannot find available dm-master instance")
   136  	}
   137  
   138  	dmMasterClient = api.NewDMMasterClient(dmMasterEndpoint, 10*time.Second, tlsCfg)
   139  
   140  	noAgentHosts := set.NewStringSet()
   141  	topo.IterInstance(func(inst dm.Instance) {
   142  		if inst.IgnoreMonitorAgent() {
   143  			noAgentHosts.Insert(inst.GetManageHost())
   144  		}
   145  	})
   146  
   147  	// Delete member from cluster
   148  	for _, component := range topo.ComponentsByStartOrder() {
   149  		for _, instance := range component.Instances() {
   150  			if !deletedNodes.Exist(instance.ID()) {
   151  				continue
   152  			}
   153  
   154  			if err := operator.StopComponent(
   155  				ctx,
   156  				topo,
   157  				[]dm.Instance{instance},
   158  				noAgentHosts,
   159  				options,
   160  				false,
   161  				false,         /* evictLeader */
   162  				&tls.Config{}, /* not used as evictLeader is false */
   163  			); err != nil {
   164  				return errors.Annotatef(err, "failed to stop %s", component.Name())
   165  			}
   166  
   167  			switch component.Name() {
   168  			case dm.ComponentDMMaster:
   169  				name := instance.(*dm.MasterInstance).Name
   170  				err := dmMasterClient.OfflineMaster(name, nil)
   171  				if err != nil {
   172  					return err
   173  				}
   174  			case dm.ComponentDMWorker:
   175  				name := instance.(*dm.WorkerInstance).Name
   176  				err := dmMasterClient.OfflineWorker(name, nil)
   177  				if err != nil {
   178  					return err
   179  				}
   180  			}
   181  
   182  			if err := operator.DestroyComponent(ctx, []dm.Instance{instance}, topo, options); err != nil {
   183  				return errors.Annotatef(err, "failed to destroy %s", component.Name())
   184  			}
   185  
   186  			instCount[instance.GetManageHost()]--
   187  			if instCount[instance.GetManageHost()] == 0 {
   188  				if err := operator.DeletePublicKey(ctx, instance.GetManageHost()); err != nil {
   189  					return errors.Annotatef(err, "failed to delete public key")
   190  				}
   191  			}
   192  		}
   193  	}
   194  
   195  	return nil
   196  }