vitess.io/vitess@v0.16.2/go/cmd/vtctldclient/command/tablets.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package command 18 19 import ( 20 "fmt" 21 "strconv" 22 "strings" 23 "time" 24 25 "github.com/spf13/cobra" 26 27 "vitess.io/vitess/go/cmd/vtctldclient/cli" 28 "vitess.io/vitess/go/protoutil" 29 "vitess.io/vitess/go/vt/topo/topoproto" 30 31 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 32 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 33 vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" 34 ) 35 36 var ( 37 // ChangeTabletType makes a ChangeTabletType gRPC call to a vtctld. 38 ChangeTabletType = &cobra.Command{ 39 Use: "ChangeTabletType [--dry-run] <alias> <tablet-type>", 40 Short: "Changes the db type for the specified tablet, if possible.", 41 Long: `Changes the db type for the specified tablet, if possible. 42 43 This command is used primarily to arrange replicas, and it will not convert a primary. 44 NOTE: This command automatically updates the serving graph.`, 45 DisableFlagsInUseLine: true, 46 Args: cobra.ExactArgs(2), 47 RunE: commandChangeTabletType, 48 } 49 // DeleteTablets makes a DeleteTablets gRPC call to a vtctld. 50 DeleteTablets = &cobra.Command{ 51 Use: "DeleteTablets <alias> [ <alias> ... ]", 52 Short: "Deletes tablet(s) from the topology.", 53 DisableFlagsInUseLine: true, 54 Args: cobra.MinimumNArgs(1), 55 RunE: commandDeleteTablets, 56 } 57 // ExecuteHook makes an ExecuteHook gRPC call to a vtctld. 58 ExecuteHook = &cobra.Command{ 59 Use: "ExecuteHook <alias> <hook_name> [<param1=value1> ...]", 60 Short: "Runs the specified hook on the given tablet.", 61 Long: `Runs the specified hook on the given tablet. 62 63 A hook is an executable script that resides in the ${VTROOT}/vthook directory. 64 For ExecuteHook, this is on the tablet requested, not on the vtctld or the host 65 running the vtctldclient. 66 67 Any key-value pairs passed after the hook name will be passed as parameters to 68 the hook on the tablet. 69 70 Note: hook names may not contain slash (/) characters. 71 `, 72 DisableFlagsInUseLine: true, 73 Args: cobra.MinimumNArgs(2), 74 RunE: commandExecuteHook, 75 } 76 // GetFullStatus makes a FullStatus gRPC call to a vttablet. 77 GetFullStatus = &cobra.Command{ 78 Use: "GetFullStatus <alias>", 79 Short: "Outputs a JSON structure that contains full status of MySQL including the replication information, semi-sync information, GTID information among others.", 80 DisableFlagsInUseLine: true, 81 Args: cobra.ExactArgs(1), 82 RunE: commandGetFullStatus, 83 } 84 // GetPermissions makes a GetPermissions gRPC call to a vtctld. 85 GetPermissions = &cobra.Command{ 86 Use: "GetPermissions <tablet_alias>", 87 Short: "Displays the permissions for a tablet.", 88 DisableFlagsInUseLine: true, 89 Args: cobra.ExactArgs(1), 90 RunE: commandGetPermissions, 91 } 92 // GetTablet makes a GetTablet gRPC call to a vtctld. 93 GetTablet = &cobra.Command{ 94 Use: "GetTablet <alias>", 95 Short: "Outputs a JSON structure that contains information about the tablet.", 96 DisableFlagsInUseLine: true, 97 Args: cobra.ExactArgs(1), 98 RunE: commandGetTablet, 99 } 100 // GetTablets makes a GetTablets gRPC call to a vtctld. 101 GetTablets = &cobra.Command{ 102 Use: "GetTablets [--strict] [{--cell $c1 [--cell $c2 ...], --keyspace $ks [--shard $shard], --tablet-alias $alias}]", 103 Short: "Looks up tablets according to filter criteria.", 104 Long: `Looks up tablets according to the filter criteria. 105 106 If --tablet-alias is passed, none of the other filters (keyspace, shard, cell) may 107 be passed, and tablets are looked up by tablet alias only. 108 109 If --keyspace is passed, then all tablets in the keyspace are retrieved. The 110 --shard flag may also be passed to further narrow the set of tablets to that 111 <keyspace/shard>. Passing --shard without also passing --keyspace will fail. 112 113 Passing --cell limits the set of tablets to those in the specified cells. The 114 --cell flag accepts a CSV argument (e.g. --cell "c1,c2") and may be repeated 115 (e.g. --cell "c1" --cell "c2"). 116 117 Valid output formats are "awk" and "json".`, 118 DisableFlagsInUseLine: true, 119 Args: cobra.NoArgs, 120 RunE: commandGetTablets, 121 } 122 // GetTabletVersion makes a GetVersion RPC to a vtctld. 123 GetTabletVersion = &cobra.Command{ 124 Use: "GetTabletVersion <alias>", 125 Short: "Print the version of a tablet from its debug vars.", 126 DisableFlagsInUseLine: true, 127 Aliases: []string{"GetVersion"}, 128 Args: cobra.ExactArgs(1), 129 RunE: commandGetTabletVersion, 130 } 131 // PingTablet makes a PingTablet gRPC call to a vtctld. 132 PingTablet = &cobra.Command{ 133 Use: "PingTablet <alias>", 134 Short: "Checks that the specified tablet is awake and responding to RPCs. This command can be blocked by other in-flight operations.", 135 DisableFlagsInUseLine: true, 136 Args: cobra.ExactArgs(1), 137 RunE: commandPingTablet, 138 } 139 // RefreshState makes a RefreshState gRPC call to a vtctld. 140 RefreshState = &cobra.Command{ 141 Use: "RefreshState <alias>", 142 Short: "Reloads the tablet record on the specified tablet.", 143 DisableFlagsInUseLine: true, 144 Args: cobra.ExactArgs(1), 145 RunE: commandRefreshState, 146 } 147 // RefreshStateByShard makes a RefreshStateByShard gRPC call to a vtcld. 148 RefreshStateByShard = &cobra.Command{ 149 Use: "RefreshStateByShard [--cell <cell1> ...] <keyspace/shard>", 150 Short: "Reloads the tablet record all tablets in the shard, optionally limited to the specified cells.", 151 DisableFlagsInUseLine: true, 152 Args: cobra.ExactArgs(1), 153 RunE: commandRefreshStateByShard, 154 } 155 // RunHealthCheck makes a RunHealthCheck gRPC call to a vtctld. 156 RunHealthCheck = &cobra.Command{ 157 Use: "RunHealthCheck <tablet_alias>", 158 Short: "Runs a healthcheck on the remote tablet.", 159 DisableFlagsInUseLine: true, 160 Aliases: []string{"RunHealthcheck"}, 161 Args: cobra.ExactArgs(1), 162 RunE: commandRunHealthCheck, 163 } 164 // SetWritable makes a SetWritable gRPC call to a vtctld. 165 SetWritable = &cobra.Command{ 166 Use: "SetWritable <alias> <true/false>", 167 Short: "Sets the specified tablet as writable or read-only.", 168 DisableFlagsInUseLine: true, 169 Args: cobra.ExactArgs(2), 170 RunE: commandSetWritable, 171 } 172 // SleepTablet makes a SleepTablet gRPC call to a vtctld. 173 SleepTablet = &cobra.Command{ 174 Use: "SleepTablet <alias> <duration>", 175 Short: "Blocks the action queue on the specified tablet for the specified amount of time. This is typically used for testing.", 176 Long: `SleepTablet <alias> <duration> 177 178 Blocks the action queue on the specified tablet for the specified duration. 179 This command is typically only used for testing. 180 181 The duration is the amount of time that the action queue should be blocked. 182 The value is a string that contains a possibly signed sequence of decimal numbers, 183 each with optional fraction and a unit suffix, such as “300ms” or “1h45m”. 184 See the definition of the Go language’s ParseDuration[1] function for more details. 185 Note that, in the SleepTablet implementation, the value should be positively-signed. 186 187 [1]: https://pkg.go.dev/time#ParseDuration 188 `, 189 DisableFlagsInUseLine: true, 190 Args: cobra.ExactArgs(2), 191 RunE: commandSleepTablet, 192 } 193 // StartReplication makes a StartReplication gRPC call to a vtctld. 194 StartReplication = &cobra.Command{ 195 Use: "StartReplication <alias>", 196 Short: "Starts replication on the specified tablet.", 197 DisableFlagsInUseLine: true, 198 Args: cobra.ExactArgs(1), 199 RunE: commandStartReplication, 200 } 201 // StopReplication makes a StopReplication gRPC call to a vtctld. 202 StopReplication = &cobra.Command{ 203 Use: "StopReplication <alias>", 204 Short: "Stops replication on the specified tablet.", 205 DisableFlagsInUseLine: true, 206 Args: cobra.ExactArgs(1), 207 RunE: commandStopReplication, 208 } 209 ) 210 211 var changeTabletTypeOptions = struct { 212 DryRun bool 213 }{} 214 215 func commandChangeTabletType(cmd *cobra.Command, args []string) error { 216 aliasStr := cmd.Flags().Arg(0) 217 typeStr := cmd.Flags().Arg(1) 218 219 alias, err := topoproto.ParseTabletAlias(aliasStr) 220 if err != nil { 221 return err 222 } 223 224 newType, err := topoproto.ParseTabletType(typeStr) 225 if err != nil { 226 return err 227 } 228 229 cli.FinishedParsing(cmd) 230 231 resp, err := client.ChangeTabletType(commandCtx, &vtctldatapb.ChangeTabletTypeRequest{ 232 TabletAlias: alias, 233 DbType: newType, 234 DryRun: changeTabletTypeOptions.DryRun, 235 }) 236 if err != nil { 237 return err 238 } 239 240 if resp.WasDryRun { 241 fmt.Println("--- DRY RUN ---") 242 } 243 244 fmt.Printf("- %v\n", cli.MarshalTabletAWK(resp.BeforeTablet)) 245 fmt.Printf("+ %v\n", cli.MarshalTabletAWK(resp.AfterTablet)) 246 247 return nil 248 } 249 250 var deleteTabletsOptions = struct { 251 AllowPrimary bool 252 }{} 253 254 func commandDeleteTablets(cmd *cobra.Command, args []string) error { 255 aliases, err := cli.TabletAliasesFromPosArgs(cmd.Flags().Args()) 256 if err != nil { 257 return err 258 } 259 260 cli.FinishedParsing(cmd) 261 262 _, err = client.DeleteTablets(commandCtx, &vtctldatapb.DeleteTabletsRequest{ 263 TabletAliases: aliases, 264 AllowPrimary: deleteTabletsOptions.AllowPrimary, 265 }) 266 267 if err != nil { 268 return fmt.Errorf("%w: while deleting %d tablets; please inspect the topo", err, len(aliases)) 269 } 270 271 fmt.Printf("Successfully deleted %d tablets\n", len(aliases)) 272 273 return nil 274 } 275 276 func commandExecuteHook(cmd *cobra.Command, args []string) error { 277 tabletAlias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 278 if err != nil { 279 return err 280 } 281 282 hookName := cmd.Flags().Arg(1) 283 if strings.Contains(hookName, "/") { 284 return fmt.Errorf("cannot execute hook named %s, ExecuteHook does not support hook names with slashes ('/')", hookName) 285 } 286 287 cli.FinishedParsing(cmd) 288 289 hookParams := cmd.Flags().Args()[2:] 290 resp, err := client.ExecuteHook(commandCtx, &vtctldatapb.ExecuteHookRequest{ 291 TabletAlias: tabletAlias, 292 TabletHookRequest: &tabletmanagerdatapb.ExecuteHookRequest{ 293 Name: hookName, 294 Parameters: hookParams, 295 }, 296 }) 297 if err != nil { 298 return err 299 } 300 301 data, err := cli.MarshalJSON(resp) 302 if err != nil { 303 return err 304 } 305 306 fmt.Printf("%s\n", data) 307 return nil 308 } 309 310 func commandGetFullStatus(cmd *cobra.Command, args []string) error { 311 aliasStr := cmd.Flags().Arg(0) 312 alias, err := topoproto.ParseTabletAlias(aliasStr) 313 if err != nil { 314 return err 315 } 316 317 cli.FinishedParsing(cmd) 318 319 resp, err := client.GetFullStatus(commandCtx, &vtctldatapb.GetFullStatusRequest{TabletAlias: alias}) 320 if err != nil { 321 return err 322 } 323 324 data, err := cli.MarshalJSON(resp.Status) 325 if err != nil { 326 return err 327 } 328 329 fmt.Printf("%s\n", data) 330 331 return nil 332 } 333 334 func commandGetPermissions(cmd *cobra.Command, args []string) error { 335 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 336 if err != nil { 337 return err 338 } 339 340 cli.FinishedParsing(cmd) 341 342 resp, err := client.GetPermissions(commandCtx, &vtctldatapb.GetPermissionsRequest{ 343 TabletAlias: alias, 344 }) 345 if err != nil { 346 return err 347 } 348 p, err := cli.MarshalJSON(resp.Permissions) 349 if err != nil { 350 return err 351 } 352 fmt.Printf("%s\n", p) 353 354 return nil 355 } 356 357 func commandGetTablet(cmd *cobra.Command, args []string) error { 358 aliasStr := cmd.Flags().Arg(0) 359 alias, err := topoproto.ParseTabletAlias(aliasStr) 360 if err != nil { 361 return err 362 } 363 364 cli.FinishedParsing(cmd) 365 366 resp, err := client.GetTablet(commandCtx, &vtctldatapb.GetTabletRequest{TabletAlias: alias}) 367 if err != nil { 368 return err 369 } 370 371 data, err := cli.MarshalJSON(resp.Tablet) 372 if err != nil { 373 return err 374 } 375 376 fmt.Printf("%s\n", data) 377 378 return nil 379 } 380 381 var getTabletsOptions = struct { 382 Cells []string 383 Keyspace string 384 Shard string 385 386 TabletAliasStrings []string 387 388 Format string 389 Strict bool 390 }{} 391 392 func commandGetTablets(cmd *cobra.Command, args []string) error { 393 format := strings.ToLower(getTabletsOptions.Format) 394 395 switch format { 396 case "awk", "json": 397 default: 398 return fmt.Errorf("invalid output format, got %s", getTabletsOptions.Format) 399 } 400 401 var aliases []*topodatapb.TabletAlias 402 403 if len(getTabletsOptions.TabletAliasStrings) > 0 { 404 switch { 405 case getTabletsOptions.Keyspace != "": 406 return fmt.Errorf("--keyspace (= %s) cannot be passed when using --tablet-alias (= %v)", getTabletsOptions.Keyspace, getTabletsOptions.TabletAliasStrings) 407 case getTabletsOptions.Shard != "": 408 return fmt.Errorf("--shard (= %s) cannot be passed when using --tablet-alias (= %v)", getTabletsOptions.Shard, getTabletsOptions.TabletAliasStrings) 409 case len(getTabletsOptions.Cells) > 0: 410 return fmt.Errorf("--cell (= %v) cannot be passed when using --tablet-alias (= %v)", getTabletsOptions.Cells, getTabletsOptions.TabletAliasStrings) 411 } 412 413 var err error 414 aliases, err = cli.TabletAliasesFromPosArgs(getTabletsOptions.TabletAliasStrings) 415 if err != nil { 416 return err 417 } 418 } 419 420 if getTabletsOptions.Keyspace == "" && getTabletsOptions.Shard != "" { 421 return fmt.Errorf("--shard (= %s) cannot be passed without also passing --keyspace", getTabletsOptions.Shard) 422 } 423 424 cli.FinishedParsing(cmd) 425 426 resp, err := client.GetTablets(commandCtx, &vtctldatapb.GetTabletsRequest{ 427 TabletAliases: aliases, 428 Cells: getTabletsOptions.Cells, 429 Keyspace: getTabletsOptions.Keyspace, 430 Shard: getTabletsOptions.Shard, 431 Strict: getTabletsOptions.Strict, 432 }) 433 if err != nil { 434 return err 435 } 436 437 switch format { 438 case "awk": 439 for _, t := range resp.Tablets { 440 fmt.Println(cli.MarshalTabletAWK(t)) 441 } 442 case "json": 443 data, err := cli.MarshalJSON(resp.Tablets) 444 if err != nil { 445 return err 446 } 447 448 fmt.Printf("%s\n", data) 449 } 450 451 return nil 452 } 453 454 func commandGetTabletVersion(cmd *cobra.Command, args []string) error { 455 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 456 if err != nil { 457 return err 458 } 459 460 cli.FinishedParsing(cmd) 461 462 resp, err := client.GetVersion(commandCtx, &vtctldatapb.GetVersionRequest{ 463 TabletAlias: alias, 464 }) 465 if err != nil { 466 return err 467 } 468 469 fmt.Println(resp.Version) 470 return nil 471 } 472 473 func commandPingTablet(cmd *cobra.Command, args []string) error { 474 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 475 if err != nil { 476 return err 477 } 478 479 cli.FinishedParsing(cmd) 480 481 _, err = client.PingTablet(commandCtx, &vtctldatapb.PingTabletRequest{ 482 TabletAlias: alias, 483 }) 484 return err 485 } 486 487 func commandRefreshState(cmd *cobra.Command, args []string) error { 488 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 489 if err != nil { 490 return err 491 } 492 493 cli.FinishedParsing(cmd) 494 495 _, err = client.RefreshState(commandCtx, &vtctldatapb.RefreshStateRequest{ 496 TabletAlias: alias, 497 }) 498 if err != nil { 499 return err 500 } 501 502 fmt.Printf("Refreshed state on %s\n", topoproto.TabletAliasString(alias)) 503 return nil 504 } 505 506 var refreshStateByShardOptions = struct { 507 Cells []string 508 }{} 509 510 func commandRefreshStateByShard(cmd *cobra.Command, args []string) error { 511 keyspace, shard, err := topoproto.ParseKeyspaceShard(cmd.Flags().Arg(0)) 512 if err != nil { 513 return err 514 } 515 516 cli.FinishedParsing(cmd) 517 518 resp, err := client.RefreshStateByShard(commandCtx, &vtctldatapb.RefreshStateByShardRequest{ 519 Keyspace: keyspace, 520 Shard: shard, 521 Cells: refreshStateByShardOptions.Cells, 522 }) 523 if err != nil { 524 return err 525 } 526 527 msg := &strings.Builder{} 528 msg.WriteString(fmt.Sprintf("Refreshed state on %s/%s", keyspace, shard)) 529 if len(refreshStateByShardOptions.Cells) > 0 { 530 msg.WriteString(fmt.Sprintf(" in cells %s", strings.Join(refreshStateByShardOptions.Cells, ", "))) 531 } 532 msg.WriteByte('\n') 533 if resp.IsPartialRefresh { 534 msg.WriteString("State refresh was partial; some tablets in the shard may not have succeeded.\n") 535 } 536 537 fmt.Print(msg.String()) 538 return nil 539 } 540 541 func commandRunHealthCheck(cmd *cobra.Command, args []string) error { 542 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 543 if err != nil { 544 return err 545 } 546 547 cli.FinishedParsing(cmd) 548 549 _, err = client.RunHealthCheck(commandCtx, &vtctldatapb.RunHealthCheckRequest{ 550 TabletAlias: alias, 551 }) 552 return err 553 } 554 555 func commandSetWritable(cmd *cobra.Command, args []string) error { 556 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 557 if err != nil { 558 return err 559 } 560 561 isWritable, err := strconv.ParseBool(cmd.Flags().Arg(1)) 562 if err != nil { 563 return err 564 } 565 566 cli.FinishedParsing(cmd) 567 568 _, err = client.SetWritable(commandCtx, &vtctldatapb.SetWritableRequest{ 569 TabletAlias: alias, 570 Writable: isWritable, 571 }) 572 return err 573 } 574 575 func commandSleepTablet(cmd *cobra.Command, args []string) error { 576 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 577 if err != nil { 578 return err 579 } 580 581 duration, err := time.ParseDuration(cmd.Flags().Arg(1)) 582 if err != nil { 583 return err 584 } 585 586 cli.FinishedParsing(cmd) 587 588 _, err = client.SleepTablet(commandCtx, &vtctldatapb.SleepTabletRequest{ 589 TabletAlias: alias, 590 Duration: protoutil.DurationToProto(duration), 591 }) 592 return err 593 } 594 595 func commandStartReplication(cmd *cobra.Command, args []string) error { 596 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 597 if err != nil { 598 return err 599 } 600 601 cli.FinishedParsing(cmd) 602 603 _, err = client.StartReplication(commandCtx, &vtctldatapb.StartReplicationRequest{ 604 TabletAlias: alias, 605 }) 606 return err 607 } 608 609 func commandStopReplication(cmd *cobra.Command, args []string) error { 610 alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0)) 611 if err != nil { 612 return err 613 } 614 615 cli.FinishedParsing(cmd) 616 617 _, err = client.StopReplication(commandCtx, &vtctldatapb.StopReplicationRequest{ 618 TabletAlias: alias, 619 }) 620 return err 621 } 622 623 func init() { 624 ChangeTabletType.Flags().BoolVarP(&changeTabletTypeOptions.DryRun, "dry-run", "d", false, "Shows the proposed change without actually executing it.") 625 Root.AddCommand(ChangeTabletType) 626 627 DeleteTablets.Flags().BoolVarP(&deleteTabletsOptions.AllowPrimary, "allow-primary", "p", false, "Allow the primary tablet of a shard to be deleted. Use with caution.") 628 Root.AddCommand(DeleteTablets) 629 630 Root.AddCommand(ExecuteHook) 631 Root.AddCommand(GetFullStatus) 632 Root.AddCommand(GetPermissions) 633 Root.AddCommand(GetTablet) 634 635 GetTablets.Flags().StringSliceVarP(&getTabletsOptions.TabletAliasStrings, "tablet-alias", "t", nil, "List of tablet aliases to filter by.") 636 GetTablets.Flags().StringSliceVarP(&getTabletsOptions.Cells, "cell", "c", nil, "List of cells to filter tablets by.") 637 GetTablets.Flags().StringVarP(&getTabletsOptions.Keyspace, "keyspace", "k", "", "Keyspace to filter tablets by.") 638 GetTablets.Flags().StringVarP(&getTabletsOptions.Shard, "shard", "s", "", "Shard to filter tablets by.") 639 GetTablets.Flags().StringVar(&getTabletsOptions.Format, "format", "awk", "Output format to use; valid choices are (json, awk).") 640 GetTablets.Flags().BoolVar(&getTabletsOptions.Strict, "strict", false, "Require all cells to return successful tablet data. Without --strict, tablet listings may be partial.") 641 Root.AddCommand(GetTablets) 642 643 Root.AddCommand(GetTabletVersion) 644 Root.AddCommand(PingTablet) 645 Root.AddCommand(RefreshState) 646 647 RefreshStateByShard.Flags().StringSliceVarP(&refreshStateByShardOptions.Cells, "cells", "c", nil, "If specified, only call RefreshState on tablets in the specified cells. If empty, all cells are considered.") 648 Root.AddCommand(RefreshStateByShard) 649 650 Root.AddCommand(RunHealthCheck) 651 Root.AddCommand(SetWritable) 652 Root.AddCommand(SleepTablet) 653 Root.AddCommand(StartReplication) 654 Root.AddCommand(StopReplication) 655 }