diff --git a/pkg/bkctl/autorecovery/autorecovery.go b/pkg/bkctl/autorecovery/autorecovery.go new file mode 100644 index 00000000..10da5a46 --- /dev/null +++ b/pkg/bkctl/autorecovery/autorecovery.go @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "github.com/streamnative/pulsarctl/pkg/cmdutils" + + "github.com/spf13/cobra" +) + +func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { + resourceCmd := cmdutils.NewResourceCmd( + "auto-recovery", + "Operations about auto recovering", + "", + "") + + commands := []func(*cmdutils.VerbCmd){ + recoverBookieCmd, + listUnderReplicatedLedgerCmd, + whoIsAuditorCmd, + triggerAuditCmd, + setLostBookieRecoveryDelayCmd, + getLostBookieRecoveryDelayCmd, + decommissionCmd, + } + + cmdutils.AddVerbCmds(flagGrouping, resourceCmd, commands...) + + return resourceCmd +} diff --git a/pkg/bkctl/autorecovery/decommission.go b/pkg/bkctl/autorecovery/decommission.go new file mode 100644 index 00000000..e1b33dd4 --- /dev/null +++ b/pkg/bkctl/autorecovery/decommission.go @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func decommissionCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for decommissioning a bookie." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + c := cmdutils.Example{ + Desc: "Decommission a bookie.", + Command: "pulsarctl bookkeeper auto-recovery (bk-ip:bk-port)", + } + examples = append(examples, c) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Successfully decommission a bookie.", + Out: "Successfully decommission the bookie (bookie-ip:bookie-port)", + } + + argError := cmdutils.Output{ + Desc: "The bookie address is not specified or the bookie address is specified more than one.", + Out: "[✖] the bookie address is not specified or the bookie address is specified more than one", + } + out = append(out, successOut, argError) + desc.CommandOutput = out + + vc.SetDescription( + "decommission", + "Decommission a bookie.", + desc.ToString(), + desc.ExampleToString()) + + vc.SetRunFuncWithNameArg(func() error { + return doDecommission(vc) + }, "the bookie address is not specified or the bookie address is specified more than one") +} + +func doDecommission(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewBookieClient() + err := admin.AutoRecovery().Decommission(vc.NameArg) + if err == nil { + vc.Command.Printf("Successfully decommission the bookie %s.\n", vc.NameArg) + } + + return err +} diff --git a/pkg/bkctl/autorecovery/decommission_test.go b/pkg/bkctl/autorecovery/decommission_test.go new file mode 100644 index 00000000..51ff60c6 --- /dev/null +++ b/pkg/bkctl/autorecovery/decommission_test.go @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecommissionArgsErr(t *testing.T) { + // no args specified + args := []string{"decommission"} + _, _, nameErr, err := testAutoRecoveryCommands(decommissionCmd, args) + if err != nil { + t.Fatal(err) + } + + assert.NotNil(t, nameErr) + assert.Equal(t, "the bookie address is not specified or the bookie address is specified more than one", + nameErr.Error()) + + // more than one args specified + args = []string{"decommission", "bookie-1:3181", "bookie-2:3181"} + _, _, nameErr, err = testAutoRecoveryCommands(decommissionCmd, args) + if err != nil { + t.Fatal(err) + } + + assert.NotNil(t, nameErr) + assert.Equal(t, "the bookie address is not specified or the bookie address is specified more than one", + nameErr.Error()) +} diff --git a/pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go b/pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go new file mode 100644 index 00000000..855818f1 --- /dev/null +++ b/pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func getLostBookieRecoveryDelayCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for getting the lost bookie recovery delay in second of a bookie." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get the lost Bookie Recovery Delay of a bookie.", + Command: "pulsarctl bookkeeper auto-recovery get-lost-bookie-recovery-delay", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Get the lost bookie recovery delay of a bookie. ", + Out: "lostBookieRecoveryDelay value: (delay)", + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "get-lost-bookie-recovery-delay", + "Get the lost bookie recovery delay of a bookie.", + desc.ToString(), + desc.ExampleToString()) + + vc.SetRunFunc(func() error { + return doGetLostBookieRecoveryDelay(vc) + }) +} + +func doGetLostBookieRecoveryDelay(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewBookieClient() + out, err := admin.AutoRecovery().GetLostBookieRecoveryDelay() + if err == nil { + vc.Command.Println(out) + } + + return err +} diff --git a/pkg/bkctl/autorecovery/list_under_replicated_ledger.go b/pkg/bkctl/autorecovery/list_under_replicated_ledger.go new file mode 100644 index 00000000..dcdac48f --- /dev/null +++ b/pkg/bkctl/autorecovery/list_under_replicated_ledger.go @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "github.com/streamnative/pulsarctl/pkg/cmdutils" + + "github.com/spf13/pflag" +) + +func listUnderReplicatedLedgerCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for getting all the under-replicated ledgers which have been marked " + + "for re-replication." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + list := cmdutils.Example{ + Desc: "Get all the under-replicated ledgers which have been marked for re-replication.", + Command: "pulsarctl bookkeeper auto-recovery list-under-replicated-ledger", + } + + li := cmdutils.Example{ + Desc: "Get all the under-replicated ledgers of a bookie which have been marked for re-replication.", + Command: "pulsarctl bookkeeper auto-recovery list-under-replicated-ledger --include (bookie-ip:bookie-port)", + } + + le := cmdutils.Example{ + Desc: "Get all the under-replicated ledgers except a bookie which have been marked for re-replication.", + Command: "pulsarctl bookkeeper auto-recovery list-under-replicated-ledger --exclude (bookie-ip:bookie-port)", + } + examples = append(examples, list, li, le) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Get the under-replicated ledgers successfully.", + Out: `{ + [ledgerId1, ledgerId2...] +}`, + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "list-under-replicated-ledger", + "Get all the under-replicated ledgers which have been marked for re-replication.", + desc.ToString(), + desc.ExampleToString()) + + var include string + var exclude string + var show bool + + vc.SetRunFunc(func() error { + return doListUnderReplicatedLedger(vc, include, exclude, show) + }) + + vc.FlagSetGroup.InFlagSet("List under replicated ledgers", func(set *pflag.FlagSet) { + set.StringVar(&include, "include", "", "Show the under-replicated ledger of the bookie.") + set.StringVar(&exclude, "exclude", "", "Show the under-replicated ledger exclude the bookie.") + set.BoolVar(&show, "show", false, "Show the replicate ledger list.") + }) +} + +func doListUnderReplicatedLedger(vc *cmdutils.VerbCmd, include, exclude string, show bool) error { + admin := cmdutils.NewBookieClient() + var l interface{} + var err error + if show { + l, err = admin.AutoRecovery().PrintListUnderReplicatedLedger(include, exclude) + } else { + l, err = admin.AutoRecovery().ListUnderReplicatedLedger(include, exclude) + } + + if err == nil { + cmdutils.PrintJSON(vc.Command.OutOrStdout(), l) + } + + return err +} diff --git a/pkg/bkctl/autorecovery/recover_bookie.go b/pkg/bkctl/autorecovery/recover_bookie.go new file mode 100644 index 00000000..cc644d07 --- /dev/null +++ b/pkg/bkctl/autorecovery/recover_bookie.go @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "errors" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" + + "github.com/spf13/pflag" +) + +func recoverBookieCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for recovering the ledger data of a failed bookie." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + rb := cmdutils.Example{ + Desc: "Recover the ledger data of a failed bookie.", + Command: "pulsarctl bookkeeper auto-recovery recover-bookie (bookie-1) (bookie-2)", + } + examples = append(examples, rb) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Recover the bookies successfully.", + Out: "Successfully recover the bookies (bookie-1) (bookie-2).", + } + + IDNotSpecified := cmdutils.Output{ + Desc: "The recover bookie id is not specified.", + Out: "[✖] you need to specify the recover bookies id", + } + out = append(out, successOut, IDNotSpecified) + desc.CommandOutput = out + + vc.SetDescription( + "recover-bookie", + "Recover the ledger data of a failed bookie.", + desc.ToString(), + desc.ExampleToString()) + + var deleteCookie bool + + vc.SetRunFuncWithMultiNameArgs(func() error { + return doRecoverBookie(vc, deleteCookie) + }, func(args []string) error { + if len(args) == 0 { + return errors.New("you need to specify the recover bookies id") + } + return nil + }) + + vc.FlagSetGroup.InFlagSet("Recover bookie", func(set *pflag.FlagSet) { + set.BoolVar(&deleteCookie, "delete-cookie", false, + "Delete cookie when recovering the failed bookies.") + }) +} + +func doRecoverBookie(vc *cmdutils.VerbCmd, deleteCookie bool) error { + admin := cmdutils.NewBookieClient() + err := admin.AutoRecovery().RecoverBookie(vc.NameArgs, deleteCookie) + if err == nil { + if deleteCookie { + vc.Command.Printf("Successfully recover the bookies %v and delete the cookie.\n", vc.NameArgs) + } else { + vc.Command.Printf("Successfully recover the bookie %v.\n", vc.NameArgs) + } + } + + return err +} diff --git a/pkg/bkctl/autorecovery/recover_bookie_test.go b/pkg/bkctl/autorecovery/recover_bookie_test.go new file mode 100644 index 00000000..d95038fa --- /dev/null +++ b/pkg/bkctl/autorecovery/recover_bookie_test.go @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRecoverBookieArgsErr(t *testing.T) { + args := []string{"recover-bookie"} + _, _, nameErr, err := testAutoRecoveryCommands(recoverBookieCmd, args) + if err != nil { + t.Fatal(err) + } + + assert.NotNil(t, nameErr) + assert.Equal(t, "you need to specify the recover bookies id", nameErr.Error()) +} diff --git a/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go b/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go new file mode 100644 index 00000000..5c3cd7f9 --- /dev/null +++ b/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "strconv" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" + + "github.com/pkg/errors" +) + +func setLostBookieRecoveryDelayCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for setting the lost bookie recovery delay in second." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + set := cmdutils.Example{ + Desc: "Set the lost bookie recovery delay.", + Command: "pulsarctl bookkeeper auto-recovery set-lost-bookie-recovery-delay (delay)", + } + examples = append(examples, set) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Set the lost bookie recovery delay to the new delay successfully.", + Out: "Successfully set the lost bookie recovery delay to (delay)(second)", + } + + argError := cmdutils.Output{ + Desc: "The specified delay time is not specified or the delay time is specified more than one.", + Out: "[✖] the specified delay time is not specified or the delay time is specified more than one", + } + out = append(out, successOut, argError) + desc.CommandOutput = out + + vc.SetDescription( + "set-lost-bookie-recovery-delay", + "Set the lost bookie recovery delay.", + desc.ToString(), + desc.ExampleToString()) + + vc.SetRunFuncWithNameArg(func() error { + return doLostBookieRecoveryDelay(vc) + }, "the delay time is not specified or the delay time is specified more than one") +} + +func doLostBookieRecoveryDelay(vc *cmdutils.VerbCmd) error { + delay, err := strconv.Atoi(vc.NameArg) + if err != nil || delay < 0 { + return errors.Errorf("invalid delay times %s", vc.NameArg) + } + + admin := cmdutils.NewBookieClient() + err = admin.AutoRecovery().SetLostBookieRecoveryDelay(delay) + if err == nil { + vc.Command.Printf("Successfully set the lost bookie recovery delay to %d(second).\n", delay) + } + + return err +} diff --git a/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay_test.go b/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay_test.go new file mode 100644 index 00000000..5ee61285 --- /dev/null +++ b/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay_test.go @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetLostBookieRecoveryDelayArgsErr(t *testing.T) { + // no args specified + args := []string{"set-lost-bookie-recovery-delay"} + _, _, nameErr, err := testAutoRecoveryCommands(setLostBookieRecoveryDelayCmd, args) + if err != nil { + t.Fatal(err) + } + + assert.NotNil(t, nameErr) + assert.Equal(t, "the delay time is not specified or the delay time is specified more than one", + nameErr.Error()) + + // specify more than one args + args = []string{"set-lost-bookie-recovery-delay", "1", "2"} + _, _, nameErr, err = testAutoRecoveryCommands(setLostBookieRecoveryDelayCmd, args) + if err != nil { + t.Fatal(err) + } + + assert.NotNil(t, nameErr) + assert.Equal(t, "the delay time is not specified or the delay time is specified more than one", + nameErr.Error()) + + // specify invalid args + args = []string{"set-lost-bookie-recovery-delay", "a"} + _, execErr, nameErr, err := testAutoRecoveryCommands(setLostBookieRecoveryDelayCmd, args) + if err != nil { + t.Fatal(err) + } + + if nameErr != nil { + t.Fatal(nameErr) + } + + assert.NotNil(t, execErr) + assert.Equal(t, "invalid delay times a", execErr.Error()) + + args = []string{"set-lost-bookie-recovery-delay", "--", "-1"} + _, execErr, nameErr, err = testAutoRecoveryCommands(setLostBookieRecoveryDelayCmd, args) + if err != nil { + t.Fatal(err) + } + + if nameErr != nil { + t.Fatal(nameErr) + } + + assert.NotNil(t, execErr) + assert.Equal(t, "invalid delay times -1", execErr.Error()) +} diff --git a/pkg/bkctl/autorecovery/test_help.go b/pkg/bkctl/autorecovery/test_help.go new file mode 100644 index 00000000..900286b5 --- /dev/null +++ b/pkg/bkctl/autorecovery/test_help.go @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "bytes" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" + + "github.com/spf13/cobra" +) + +func testAutoRecoveryCommands(newVerb func(cmd *cmdutils.VerbCmd), args []string) (out *bytes.Buffer, + execErr, nameErr, err error) { + + var execError error + cmdutils.ExecErrorHandler = func(err error) { + execError = err + } + + var nameError error + cmdutils.CheckNameArgError = func(err error) { + nameError = err + } + + var rootCmd = &cobra.Command{} + + buf := new(bytes.Buffer) + rootCmd.SetOut(buf) + rootCmd.SetArgs(append([]string{"auto-recovery"}, args...)) + + resourceCmd := cmdutils.NewResourceCmd( + "auto-recovery", + "Operations about auto recovering", + "", + "") + flagGrouping := cmdutils.NewGrouping() + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, newVerb) + rootCmd.AddCommand(resourceCmd) + err = rootCmd.Execute() + + return buf, execError, nameError, err +} diff --git a/pkg/bkctl/autorecovery/trigger_audit.go b/pkg/bkctl/autorecovery/trigger_audit.go new file mode 100644 index 00000000..e054a04e --- /dev/null +++ b/pkg/bkctl/autorecovery/trigger_audit.go @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func triggerAuditCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for triggering audit by resetting the lost bookie recovery delay." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + trigger := cmdutils.Example{ + Desc: "Trigger audit by resetting the lost bookie recovery delay", + Command: "pulsarctl bookkeeper auto-recovery trigger-audit", + } + examples = append(examples, trigger) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Trigger audit by resetting the lost bookie recovery delay successfully.", + Out: "Successfully trigger audit by resetting the lost bookie recovery delay.", + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "trigger-audit", + "Trigger audit by resetting the lost bookie recovery delay.", + desc.ToString(), + desc.ExampleToString()) + + vc.SetRunFunc(func() error { + return doTriggerAudit(vc) + }) +} + +func doTriggerAudit(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewBookieClient() + err := admin.AutoRecovery().TriggerAudit() + if err == nil { + vc.Command.Println("Successfully trigger audit by resetting the lost bookie recovery delay.") + } + + return err +} diff --git a/pkg/bkctl/autorecovery/who_is_auditor.go b/pkg/bkctl/autorecovery/who_is_auditor.go new file mode 100644 index 00000000..0693bf14 --- /dev/null +++ b/pkg/bkctl/autorecovery/who_is_auditor.go @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 autorecovery + +import ( + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func whoIsAuditorCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for getting who is the auditor." + desc.CommandPermission = "This command does not need any permission." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get who is the auditor", + Command: "pulsarctl bookkeeper auto-recovery who-is-auditor", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "Get the auditor successfully.", + Out: `{ + "Auditor": "hostname/hostAddress:Port" +}`, + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "who-is-auditor", + "Get the auditor of the bookie.", + desc.ToString(), + desc.ExampleToString()) + + vc.SetRunFunc(func() error { + return doWhoIsAuditor(vc) + }) +} + +func doWhoIsAuditor(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewBookieClient() + auditor, err := admin.AutoRecovery().WhoIsAuditor() + if err == nil { + cmdutils.PrintJSON(vc.Command.OutOrStdout(), auditor) + } + + return err +} diff --git a/pkg/bkctl/bk.go b/pkg/bkctl/bk.go index 982aba8a..c2224ff2 100644 --- a/pkg/bkctl/bk.go +++ b/pkg/bkctl/bk.go @@ -18,9 +18,11 @@ package bkctl import ( - "github.com/spf13/cobra" + "github.com/streamnative/pulsarctl/pkg/bkctl/autorecovery" "github.com/streamnative/pulsarctl/pkg/bkctl/ledger" "github.com/streamnative/pulsarctl/pkg/cmdutils" + + "github.com/spf13/cobra" ) func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { @@ -32,6 +34,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { ) resourceCmd.AddCommand(ledger.Command(flagGrouping)) + resourceCmd.AddCommand(autorecovery.Command(flagGrouping)) return resourceCmd } diff --git a/pkg/bookkeeper/admin.go b/pkg/bookkeeper/admin.go index 05836236..a7ca8107 100644 --- a/pkg/bookkeeper/admin.go +++ b/pkg/bookkeeper/admin.go @@ -29,6 +29,8 @@ import ( type Client interface { // Ledger related commands Ledger() Ledger + // AutoRecovery related commands + AutoRecovery() AutoRecovery } type bookieClient struct { diff --git a/pkg/bookkeeper/autorecovery.go b/pkg/bookkeeper/autorecovery.go new file mode 100644 index 00000000..f1d2e5f1 --- /dev/null +++ b/pkg/bookkeeper/autorecovery.go @@ -0,0 +1,124 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 bookkeeper + +import "github.com/streamnative/pulsarctl/pkg/bookkeeper/bkdata" + +type AutoRecovery interface { + // RecoverBookie is used to recovering ledger data for a failed bookie + RecoverBookie([]string, bool) error + + // ListUnderReplicatedLedger is used to listing all the underreplicated ledgers + // which have been marked for rereplication + ListUnderReplicatedLedger(string, string) ([]int64, error) + + // PrintListUnderReplicatedLedger is used to printing the replicate list of the replicated ledgers + PrintListUnderReplicatedLedger(string, string) (map[int64][]string, error) + + // WhoIsAuditor is used to getting which bookie is the auditor + WhoIsAuditor() (map[string]string, error) + + // TriggerAudit is used to triggering audit by resetting the lostBookieRecoveryDelay + TriggerAudit() error + + // GetLostBookieRecoveryDelay is used to getting the lostBookieRecoveryDelay of a bookie + GetLostBookieRecoveryDelay() (string, error) + + // SetLostBookieRecoveryDelay is used to setting the lastBookieRecoverDelay of a bookie + SetLostBookieRecoveryDelay(int) error + + // Decommission is used to decommissioning a bookie + Decommission(string) error +} + +type autoRecovery struct { + bk *bookieClient + basePath string + params map[string]string +} + +func (c *bookieClient) AutoRecovery() AutoRecovery { + return &autoRecovery{ + bk: c, + basePath: "/autorecovery", + params: make(map[string]string), + } +} + +func (a *autoRecovery) RecoverBookie(src []string, deleteCookie bool) error { + endpoint := a.bk.endpoint(a.basePath, "/bookie") + request := bkdata.RecoveryRequest{ + BookieSrc: src, + DeleteCookie: deleteCookie, + } + return a.bk.Client.Put(endpoint, &request) +} + +func (a *autoRecovery) ListUnderReplicatedLedger(missingReplica, excludingMissingReplica string) ([]int64, error) { + endpoint := a.bk.endpoint(a.basePath, "/list_under_replicated_ledger") + a.params["missingreplica"] = missingReplica + a.params["excludingmissingreplica"] = excludingMissingReplica + resp := make([]int64, 0) + _, err := a.bk.Client.GetWithQueryParams(endpoint, &resp, a.params, true) + return resp, err +} + +func (a *autoRecovery) PrintListUnderReplicatedLedger(missingReplica, + excludingMissingReplica string) (map[int64][]string, error) { + + endpoint := a.bk.endpoint(a.basePath, "/list_under_replicated_ledger") + a.params["missingreplica"] = missingReplica + a.params["excludingmissingreplica"] = excludingMissingReplica + a.params["printmissingreplica"] = "true" + resp := make(map[int64][]string) + _, err := a.bk.Client.GetWithQueryParams(endpoint, &resp, a.params, true) + return resp, err +} + +func (a *autoRecovery) WhoIsAuditor() (map[string]string, error) { + endpoint := a.bk.endpoint(a.basePath, "/who_is_auditor") + resp := make(map[string]string) + return resp, a.bk.Client.Get(endpoint, &resp) +} + +func (a *autoRecovery) TriggerAudit() error { + endpoint := a.bk.endpoint(a.basePath, "/trigger_audit") + return a.bk.Client.Put(endpoint, nil) +} + +func (a *autoRecovery) GetLostBookieRecoveryDelay() (string, error) { + endpoint := a.bk.endpoint(a.basePath, "/lost_bookie_recovery_delay") + resp, err := a.bk.Client.GetWithQueryParams(endpoint, nil, nil, false) + return string(resp), err +} + +func (a *autoRecovery) SetLostBookieRecoveryDelay(delay int) error { + endpoint := a.bk.endpoint(a.basePath, "/lost_bookie_recovery_delay") + req := bkdata.LostBookieRecoverDelayRequest{ + DelaySeconds: delay, + } + return a.bk.Client.Put(endpoint, &req) +} + +func (a *autoRecovery) Decommission(src string) error { + endpoint := a.bk.endpoint(a.basePath, "/decommission") + req := bkdata.DecommissionRequest{ + BookieSrc: src, + } + return a.bk.Client.Put(endpoint, &req) +} diff --git a/pkg/bookkeeper/bkdata/autorecovery_data.go b/pkg/bookkeeper/bkdata/autorecovery_data.go new file mode 100644 index 00000000..c838153e --- /dev/null +++ b/pkg/bookkeeper/bkdata/autorecovery_data.go @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 bkdata + +type RecoveryRequest struct { + BookieSrc []string `json:"bookie_src"` + DeleteCookie bool `json:"delete_cookie"` +} + +type LostBookieRecoverDelayRequest struct { + DelaySeconds int `json:"delay_seconds"` +} + +type DecommissionRequest struct { + BookieSrc string `json:"bookie_src"` +}