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 }