From b714858f6b4dcc88538105c4bc2ce9b51bfb8155 Mon Sep 17 00:00:00 2001 From: Yong Zhang Date: Thu, 14 Nov 2019 15:45:06 +0800 Subject: [PATCH 1/6] [BK-SUPPORT-PART-4] Support bookkeeper autorecovery commands --- Master Issue: #127 *Motivation* Support bookkeeper autorecovery commands in Pulsarctl. *Modifications* Add bookkeeper autorecovery commands. --- pkg/bkctl/autorecovery/autorecovery.go | 46 +++++++ pkg/bkctl/autorecovery/decommission.go | 69 ++++++++++ .../get_lost_bookie_recovery_delay.go | 64 +++++++++ .../list_under_replicated_ledger.go | 96 ++++++++++++++ pkg/bkctl/autorecovery/recover_bookie.go | 78 +++++++++++ .../set_lost_bookie_recovery_delay.go | 78 +++++++++++ pkg/bkctl/autorecovery/trigger_audit.go | 64 +++++++++ pkg/bkctl/autorecovery/who_is_auditor.go | 66 ++++++++++ pkg/bkctl/bk.go | 5 +- pkg/bookkeeper/admin.go | 2 + pkg/bookkeeper/autorecovery.go | 124 ++++++++++++++++++ pkg/bookkeeper/bkdata/autorecovery_data.go | 31 +++++ 12 files changed, 722 insertions(+), 1 deletion(-) create mode 100644 pkg/bkctl/autorecovery/autorecovery.go create mode 100644 pkg/bkctl/autorecovery/decommission.go create mode 100644 pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go create mode 100644 pkg/bkctl/autorecovery/list_under_replicated_ledger.go create mode 100644 pkg/bkctl/autorecovery/recover_bookie.go create mode 100644 pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go create mode 100644 pkg/bkctl/autorecovery/trigger_audit.go create mode 100644 pkg/bkctl/autorecovery/who_is_auditor.go create mode 100644 pkg/bookkeeper/autorecovery.go create mode 100644 pkg/bookkeeper/bkdata/autorecovery_data.go diff --git a/pkg/bkctl/autorecovery/autorecovery.go b/pkg/bkctl/autorecovery/autorecovery.go new file mode 100644 index 000000000..7fce86e5b --- /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( + "autorecovery", + "Operations about ledger", + "", + "") + + 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 000000000..bed469dc3 --- /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 decommission a bookie." + desc.CommandPermission = "none" + + var examples []cmdutils.Example + c := cmdutils.Example{ + Desc: "Decommission a bookie", + Command: "pulsarctl bookkeeper autorecovery (bk-ip:bk-port)", + } + examples = append(examples, c) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + 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/get_lost_bookie_recovery_delay.go b/pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go new file mode 100644 index 000000000..94f37484e --- /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 = "none" + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get the lost Bookie Recovery Delay of a bookie", + Command: "pulsarctl bookkeeeper autorecovery getdelay", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "lostBookieRecoveryDelay value: (delay)", + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "getdelay", + "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 000000000..715bcec17 --- /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 listing all the underreplicated ledgers which have been marked " + + "for rereplication." + desc.CommandPermission = "none" + + var examples []cmdutils.Example + list := cmdutils.Example{ + Desc: "List all the underreplicated ledgers which have been marked for rereplication", + Command: "pulsarctl bookkeeper autorecovery listunderreplicatedledger", + } + + li := cmdutils.Example{ + Desc: "List all the underreplicated ledgers of a bookie which have been marked for rereplication", + Command: "pulsarctl bookkeeper autorecovery listunderreplicatedledger --include (bookie-ip:bookie-port)", + } + + le := cmdutils.Example{ + Desc: "List all the underreplicated ledgers except a bookie which have been marked for rereplication", + Command: "pulsarctl bookkeeper autorecovery listunderreplicatedledger --exclude (bookie-ip:bookie-port)", + } + examples = append(examples, list, li, le) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: `{ + [ledgerId1, ledgerId2...] +}`, + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "listunderreplicatedledger", + "List all the underreplicated ledgers which have been marked for rereplication", + 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 Ledger", func(set *pflag.FlagSet) { + set.StringVar(&include, "include", "", "show the underreplicated ledger of the bookie") + set.StringVar(&exclude, "exclude", "", "show the underreplicated ledger exclude the bookie") + set.BoolVar(&show, "show", false, "show the ledgers replica list") + }) +} + +func doListUnderReplicatedLedger(vc *cmdutils.VerbCmd, include, exclude string, print bool) error { + admin := cmdutils.NewBookieClient() + var l interface{} + var err error + if print { + 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 000000000..e47e5739b --- /dev/null +++ b/pkg/bkctl/autorecovery/recover_bookie.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 ( + "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 = "none" + + var examples []cmdutils.Example + rb := cmdutils.Example{ + Desc: "Recover the ledger data of a failed bookie", + Command: "pulsarctl bookkeeper autorecovery recoverbookie (bookie-1) (bookie-2)", + } + examples = append(examples, rb) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully recover the bookies (bookie-1) (bookie-2)", + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "recoverbookie", + "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 { + return nil + }) + + vc.FlagSetGroup.InFlagSet("Recover Bookie", func(set *pflag.FlagSet) { + set.BoolVar(&deleteCookie, "delelte-cookie", false, "delete cookie") + }) +} + +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/set_lost_bookie_recovery_delay.go b/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go new file mode 100644 index 000000000..6415f3ca3 --- /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 = "none" + + var examples []cmdutils.Example + set := cmdutils.Example{ + Desc: "Set the lost Bookie Recovery Delay", + Command: "pulsarctl bookkeeper autorecovery setdelay (delay)", + } + examples = append(examples, set) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + 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( + "setdelay", + "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 { + 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/trigger_audit.go b/pkg/bkctl/autorecovery/trigger_audit.go new file mode 100644 index 000000000..f8610730c --- /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 lostBookieRecoveryDelay." + desc.CommandPermission = "none" + + var examples []cmdutils.Example + trigger := cmdutils.Example{ + Desc: "Trigger audit by resetting the lostBookieRecoveryDelay", + Command: "pulsarctl bookkeeper autorecovery triggeraudit", + } + examples = append(examples, trigger) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully trigger audit by resetting the lostBookieRecoveryDelay", + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "triggeraudit", + "Trigger audit by resetting the lostBookieRecoveryDelay", + 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 lostBookieRecoveryDelay") + } + + 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 000000000..365b2388f --- /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 = "none" + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get who is the auditor", + Command: "pulsarctl bookkeeper autorecovery whoisauditor", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: `{ + "Auditor": "hostname/hostAddress:Port" +}`, + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "whoisauditor", + "Get who is the auditor", + 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 982aba8a0..c2224ff2b 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 058362364..a7ca81076 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 000000000..f1d2e5f1f --- /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 000000000..c838153e3 --- /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"` +} From 7820a1dfadb5ab6a2b7690d597f4d39e59fad4f3 Mon Sep 17 00:00:00 2001 From: Yong Zhang Date: Mon, 6 Jan 2020 20:17:29 +0800 Subject: [PATCH 2/6] Using testcontainer to create a test framework for testing --- Master Issue: #163 *Motivation* Currently, we are using an external pulsar standalone to test the Pulsarctl. We need to enhancement the test to ensure the Pulsarctl can work well in a complete pulsar cluster. *Modifications* - Using testcontainer to test the pulsarctl. --- go.mod | 4 +- go.sum | 104 +++++++++++- pkg/test/base_container.go | 138 ++++++++++++++++ pkg/test/containers/bookie.go | 34 ++++ pkg/test/containers/broker.go | 35 +++++ pkg/test/containers/proxy.go | 35 +++++ pkg/test/containers/zookeeper.go | 34 ++++ pkg/test/pulsar/cluster.go | 235 ++++++++++++++++++++++++++++ pkg/test/pulsar/cluster_script.go | 45 ++++++ pkg/test/pulsar/cluster_spec.go | 48 ++++++ pkg/test/pulsar/cluster_test.go | 56 +++++++ pkg/test/pulsar/standalonoe.go | 30 ++++ pkg/test/pulsar/standalonoe_test.go | 53 +++++++ pkg/test/utils.go | 39 +++++ 14 files changed, 884 insertions(+), 6 deletions(-) create mode 100644 pkg/test/base_container.go create mode 100644 pkg/test/containers/bookie.go create mode 100644 pkg/test/containers/broker.go create mode 100644 pkg/test/containers/proxy.go create mode 100644 pkg/test/containers/zookeeper.go create mode 100644 pkg/test/pulsar/cluster.go create mode 100644 pkg/test/pulsar/cluster_script.go create mode 100644 pkg/test/pulsar/cluster_spec.go create mode 100644 pkg/test/pulsar/cluster_test.go create mode 100644 pkg/test/pulsar/standalonoe.go create mode 100644 pkg/test/pulsar/standalonoe_test.go create mode 100644 pkg/test/utils.go diff --git a/go.mod b/go.mod index 2fe9a182a..b98408637 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/streamnative/pulsarctl go 1.12 require ( - github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/docker/go-connections v0.4.0 github.com/fatih/color v1.7.0 // indirect github.com/golang/protobuf v1.3.1 github.com/google/go-cmp v0.3.1 // indirect @@ -16,7 +16,7 @@ require ( github.com/pkg/errors v0.8.1 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.3 - github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.3.0 + github.com/testcontainers/testcontainers-go v0.0.10 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 8182d15c9..f7eda5a0b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,17 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -9,16 +21,49 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190506211059-b20a14b54661 h1:ZuxGvIvF01nfc/G9RJ5Q7Va1zQE2WJyG18Zv3DqCEf4= +github.com/docker/docker v0.7.3-0.20190506211059-b20a14b54661/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= +github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06 h1:vN4d3jSss3ExzUn2cE0WctxztfOgiKvMKnDrydBsg00= github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06/go.mod h1:++9BgZujZd4v0ZTZCb5iPsaomXdZWyxotIAh1IiDm44= github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b h1:xYEM2oBUhBEhQjrV+KJ9lEWDWYZoNVZUaBF++Wyljq4= @@ -32,14 +77,32 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= @@ -49,21 +112,54 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/testcontainers/testcontainers-go v0.0.10 h1:WP99DOGWmkr+RXHURyAXnvZqZO0x3VXkRYAzQ+mwtpQ= +github.com/testcontainers/testcontainers-go v0.0.10/go.mod h1:2kePcwMHd3ix/BU3cTDuhvggUgMBAit+qcWwadeMXok= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0= +gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/test/base_container.go b/pkg/test/base_container.go new file mode 100644 index 000000000..4476a0173 --- /dev/null +++ b/pkg/test/base_container.go @@ -0,0 +1,138 @@ +// 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 test + +import ( + "context" + + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// BaseContainer provide the basic operations for a container. +type BaseContainer struct { + containerRequest testcontainers.GenericContainerRequest + container testcontainers.Container +} + +// NewContainer creates a container using the image. +func NewContainer(image string) *BaseContainer { + gcr := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: image, + }, + } + + return &BaseContainer{ + containerRequest: gcr, + } +} + +// WithNetwork uses a existent network for the container. +func (bc *BaseContainer) WithNetwork(network []string) *BaseContainer { + bc.containerRequest.Networks = append(bc.containerRequest.Networks, network...) + return bc +} + +// WithNetworkAliases creates some aliases for the container. +func (bc *BaseContainer) WithNetworkAliases(aliases map[string][]string) *BaseContainer { + if bc.containerRequest.NetworkAliases == nil { + bc.containerRequest.NetworkAliases = make(map[string][]string) + } + for k, v := range aliases { + bc.containerRequest.NetworkAliases[k] = append(bc.containerRequest.NetworkAliases[k], v...) + } + return bc +} + +// WithCmd sets the containers start up commands. +func (bc *BaseContainer) WithCmd(cmd []string) *BaseContainer { + bc.containerRequest.Cmd = append(bc.containerRequest.Cmd, cmd...) + return bc +} + +// WithEnv sets the environment variable to the container. +func (bc *BaseContainer) WithEnv(env map[string]string) *BaseContainer { + if bc.containerRequest.Env == nil { + bc.containerRequest.Env = make(map[string]string) + } + for k, v := range env { + bc.containerRequest.Env[k] = v + } + return bc +} + +// ExposedPorts exposes the ports from the container. +func (bc *BaseContainer) ExposedPorts(ports []string) *BaseContainer { + bc.containerRequest.ExposedPorts = append(bc.containerRequest.ExposedPorts, ports...) + return bc +} + +// WaitForPort waits for the container ports exposed. +func (bc *BaseContainer) WaitForPort(port string) *BaseContainer { + bc.containerRequest.WaitingFor = wait.ForListeningPort(nat.Port(port)) + return bc +} + +// WaitForLog waits for the log string appear. +func (bc *BaseContainer) WaitForLog(log string) *BaseContainer { + bc.containerRequest.WaitingFor = wait.ForLog(log) + return bc +} + +// WaitForHTTPPath waits for the path can be used. The Default access port is 80. +// TODO: support the specified path with a port. +func (bc *BaseContainer) WaitForHTTPPath(path string) *BaseContainer { + bc.containerRequest.WaitingFor = wait.ForHTTP(path) + return bc +} + +// Start starts the container. +func (bc *BaseContainer) Start(ctx context.Context) error { + c, err := testcontainers.GenericContainer(ctx, bc.containerRequest) + if err != nil { + return err + } + bc.container = c + err = c.Start(ctx) + return err +} + +// ExecCmd executes a command in the container. +func (bc *BaseContainer) ExecCmd(ctx context.Context, cmd []string) (int, error) { + return bc.container.Exec(ctx, cmd) +} + +// Stop stops the container. +func (bc *BaseContainer) Stop(ctx context.Context) error { + if bc.container != nil { + return bc.container.Terminate(ctx) + } + return nil +} + +// GetContainerID gets the container ID. +func (bc *BaseContainer) GetContainerID() string { + return bc.container.GetContainerID() +} + +// MappedPort gets the outside port. +func (bc *BaseContainer) MappedPort(ctx context.Context, port string) (nat.Port, error) { + return bc.container.MappedPort(ctx, nat.Port(port)) +} diff --git a/pkg/test/containers/bookie.go b/pkg/test/containers/bookie.go new file mode 100644 index 000000000..0fe813797 --- /dev/null +++ b/pkg/test/containers/bookie.go @@ -0,0 +1,34 @@ +// 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 containers + +import "github.com/streamnative/pulsarctl/pkg/test" + +const BookieName = "bookie" + +func NewBookieContainer(image, network string) *test.BaseContainer { + bk := test.NewContainer(image) + bk.WithNetwork([]string{network}) + bk.WithNetworkAliases(map[string][]string{network: {BookieName}}) + bk.WithCmd([]string{ + "bash", "-c", + "bin/apply-config-from-env.py conf/bookkeeper.conf && bin/pulsar bookie", + }) + bk.WaitForLog("Started component bookie-server") + return bk +} diff --git a/pkg/test/containers/broker.go b/pkg/test/containers/broker.go new file mode 100644 index 000000000..3a6af9556 --- /dev/null +++ b/pkg/test/containers/broker.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 containers + +import "github.com/streamnative/pulsarctl/pkg/test" + +const BrokerName = "broker" + +func NewBrokerContainer(image, network string) *test.BaseContainer { + broker := test.NewContainer(image) + broker.WithNetwork([]string{network}) + broker.WithNetworkAliases(map[string][]string{network: {BrokerName}}) + broker.ExposedPorts([]string{"8080", "6650"}) + broker.WithCmd([]string{ + "bash", "-c", + "bin/apply-config-from-env.py conf/broker.conf && bin/pulsar broker", + }) + broker.WaitForPort("8080") + return broker +} diff --git a/pkg/test/containers/proxy.go b/pkg/test/containers/proxy.go new file mode 100644 index 000000000..44424352a --- /dev/null +++ b/pkg/test/containers/proxy.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 containers + +import "github.com/streamnative/pulsarctl/pkg/test" + +const ProxyName = "proxy" + +func NewProxyContainer(image, network string) *test.BaseContainer { + proxy := test.NewContainer(image) + proxy.WithNetwork([]string{network}) + proxy.WithNetworkAliases(map[string][]string{network: {ProxyName}}) + proxy.ExposedPorts([]string{"8080", "6650"}) + proxy.WithCmd([]string{ + "bash", "-c", + "bin/apply-config-from-env.py conf/proxy.conf && bin/pulsar proxy", + }) + proxy.WaitForLog("Server started at end point") + return proxy +} diff --git a/pkg/test/containers/zookeeper.go b/pkg/test/containers/zookeeper.go new file mode 100644 index 000000000..75f543d27 --- /dev/null +++ b/pkg/test/containers/zookeeper.go @@ -0,0 +1,34 @@ +// 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 containers + +import "github.com/streamnative/pulsarctl/pkg/test" + +const ZookeeperName = "zookeeper" + +func NewZookeeperContainer(image, network string) *test.BaseContainer { + zookeeper := test.NewContainer(image) + zookeeper.WithNetwork([]string{network}) + zookeeper.WithNetworkAliases(map[string][]string{network: {ZookeeperName}}) + zookeeper.ExposedPorts([]string{"2181"}) + zookeeper.WithCmd([]string{ + "bin/pulsar", "zookeeper", + }) + zookeeper.WaitForPort("2181") + return zookeeper +} diff --git a/pkg/test/pulsar/cluster.go b/pkg/test/pulsar/cluster.go new file mode 100644 index 000000000..73c1ae268 --- /dev/null +++ b/pkg/test/pulsar/cluster.go @@ -0,0 +1,235 @@ +// 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 pulsar + +import ( + "context" + "fmt" + "strconv" + + "github.com/streamnative/pulsarctl/pkg/test" + "github.com/streamnative/pulsarctl/pkg/test/containers" + + "github.com/pkg/errors" + "github.com/testcontainers/testcontainers-go" +) + +var ( + InvalidPort = -1 + DefaultZKPort = 2181 + DefaultBookiePort = 3181 + DefaultBrokerPort = 6650 + DefaultBrokerHTTPPort = 8080 + + LatestImage = "apachepulsar/pulsar:latest" +) + +type ClusterDef struct { + clusterSpec *ClusterSpec + network testcontainers.Network + zkContainer *test.BaseContainer + proxyContainer *test.BaseContainer + bookieContainers map[string]*test.BaseContainer + brokerContainers map[string]*test.BaseContainer +} + +type Cluster interface { + // Start a pulsar cluster. + Start(ctx context.Context) error + + // Stop a pulsar cluster. + Stop(ctx context.Context) error + + // GetPlainTextServiceURL gets the pulsar service connect string. + GetPlainTextServiceURL(ctx context.Context) (string, error) + + // GetHTTPServiceURL gets the pulsar HTTP service connect string. + GetHTTPServiceURL(ctx context.Context) (string, error) + + // Close closes resources used for starting the cluster. + Close(ctx context.Context) +} + +// DefaultPulsarCluster creates a pulsar cluster using the default cluster spec. +func DefaultPulsarCluster() (Cluster, error) { + return NewPulsarCluster(DefaultClusterSpec()) +} + +// NewPulsarCluster creates a pulsar cluster using the spec. +func NewPulsarCluster(spec *ClusterSpec) (Cluster, error) { + networkName := spec.ClusterName + network, err := test.NewNetwork(networkName) + if err != nil { + return nil, err + } + + zookeeper := containers.NewZookeeperContainer(LatestImage, networkName) + bookies := getBookieContainers(networkName, spec.NumBookies) + brokers := getBrokerContainers(spec.ClusterName, networkName, spec.NumBrokers) + broker := getABrokerNetAlias(brokers) + proxy := containers.NewProxyContainer(LatestImage, networkName).WithEnv(map[string]string{ + "webServicePort": strconv.Itoa(spec.ProxyHTTPServicePort), + "servicePort": strconv.Itoa(spec.ProxyServicePort), + "brokerServiceURL": fmt.Sprintf("pulsar://%s:%d", broker, spec.BrokerServicePort), + "brokerWebServiceURL": fmt.Sprintf("http://%s:%d", broker, spec.BrokerHTTPServicePort), + }) + + return &ClusterDef{ + network: network, + clusterSpec: spec, + zkContainer: zookeeper, + proxyContainer: proxy, + bookieContainers: bookies, + brokerContainers: brokers, + }, nil +} + +func getBookieContainers(network string, num int) map[string]*test.BaseContainer { + bookies := make(map[string]*test.BaseContainer) + for i := 0; i < num; i++ { + name := fmt.Sprintf("%s-%d", containers.BookieName, i) + bookies[name] = containers.NewBookieContainer(LatestImage, network).WithEnv(map[string]string{ + "zkServers": containers.ZookeeperName, + }).WithNetworkAliases(map[string][]string{ + network: {name}, + }) + } + return bookies +} + +func getBrokerContainers(clusterName, network string, num int) map[string]*test.BaseContainer { + brokers := make(map[string]*test.BaseContainer) + for i := 0; i < num; i++ { + name := fmt.Sprintf("%s-%d", containers.BrokerName, i) + brokers[name] = containers.NewBrokerContainer(LatestImage, network).WithEnv(map[string]string{ + "zookeeperServers": containers.ZookeeperName, + "clusterName": clusterName, + }).WithNetworkAliases(map[string][]string{ + network: {name}, + }) + } + return brokers +} + +func getABrokerNetAlias(brokers map[string]*test.BaseContainer) string { + for k := range brokers { + return k + } + return containers.BrokerName +} + +func (c *ClusterDef) Start(ctx context.Context) error { + err := c.zkContainer.Start(ctx) + if err != nil { + return errors.WithMessage(err, "encountered errors when starting the zookeeper") + } + fmt.Printf("Zookeeper %s:%s started.\n", containers.ZookeeperName, c.zkContainer.GetContainerID()) + + init := InitCluster(&InitConf{ + ClusterName: c.clusterSpec.ClusterName, + ConfigurationStore: fmt.Sprintf("%s:%d", containers.ZookeeperName, DefaultZKPort), + Zookeeper: fmt.Sprintf("%s:%d", containers.ZookeeperName, DefaultZKPort), + Broker: fmt.Sprintf("%s:%d", + getABrokerNetAlias(c.brokerContainers), c.clusterSpec.BrokerHTTPServicePort), + }, LatestImage, c.clusterSpec.ClusterName) + err = init.Start(ctx) + if err != nil { + return errors.WithMessage(err, "encountered errors when initializing the pulsar cluster") + } + fmt.Printf("Initialize pulsar cluster %s successfully.\n", c.clusterSpec.ClusterName) + + for k, v := range c.bookieContainers { + err = v.Start(ctx) + if err != nil { + return errors.WithMessagef(err, "encountered errors when starting the bookie %s", k) + } + fmt.Printf("Bookie %s:%s started.\n", k, v.GetContainerID()) + } + + for k, v := range c.brokerContainers { + err = v.Start(ctx) + if err != nil { + return errors.WithMessagef(err, "encountered errors when starting the bookie %s", k) + } + fmt.Printf("Broker %s:%s started.\n", k, v.GetContainerID()) + } + + err = c.proxyContainer.Start(ctx) + if err != nil { + return errors.WithMessage(err, "encountered errors when starting the proxy") + } + fmt.Printf("Proxy %s:%s started.\n", containers.ProxyName, c.proxyContainer.GetContainerID()) + + return nil +} + +func (c *ClusterDef) Stop(ctx context.Context) error { + if c.zkContainer != nil { + err := c.zkContainer.Stop(ctx) + if err != nil { + return err + } + } + + if c.bookieContainers != nil { + for _, v := range c.bookieContainers { + err := v.Stop(ctx) + if err != nil { + return err + } + } + } + + if c.brokerContainers != nil { + for _, v := range c.brokerContainers { + err := v.Stop(ctx) + if err != nil { + return err + } + } + } + + if c.proxyContainer != nil { + err := c.proxyContainer.Stop(ctx) + if err != nil { + return err + } + } + + return nil +} + +func (c *ClusterDef) GetPlainTextServiceURL(ctx context.Context) (string, error) { + port, err := c.proxyContainer.MappedPort(ctx, strconv.Itoa(c.clusterSpec.BrokerHTTPServicePort)) + if err != nil { + return "", err + } + return "pulsar://localhost:" + port.Port(), nil +} + +func (c *ClusterDef) GetHTTPServiceURL(ctx context.Context) (string, error) { + port, err := c.proxyContainer.MappedPort(ctx, strconv.Itoa(c.clusterSpec.ProxyHTTPServicePort)) + if err != nil { + return "", err + } + return "http://localhost:" + port.Port(), nil +} + +func (c *ClusterDef) Close(ctx context.Context) { + c.network.Remove(ctx) +} diff --git a/pkg/test/pulsar/cluster_script.go b/pkg/test/pulsar/cluster_script.go new file mode 100644 index 000000000..3e786dd81 --- /dev/null +++ b/pkg/test/pulsar/cluster_script.go @@ -0,0 +1,45 @@ +// 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 pulsar + +import ( + "fmt" + + "github.com/streamnative/pulsarctl/pkg/test" +) + +// InitConf is a configuration for the initialize the pulsar cluster. +type InitConf struct { + ClusterName string + ConfigurationStore string + Zookeeper string + Broker string +} + +// InitCluster returns a container for executing init pulsar cluster. +func InitCluster(conf *InitConf, image, network string) *test.BaseContainer { + pulsarInit := test.NewContainer(image) + pulsarInit.WithNetwork([]string{network}) + pulsarInit.WaitForLog(fmt.Sprintf("Cluster metadata for '%s' setup correctly", conf.ClusterName)) + pulsarInit.WithCmd([]string{ + "bash", "-c", + fmt.Sprintf("bin/pulsar initialize-cluster-metadata -c %s -cs %s -uw %s -zk %s", + conf.ClusterName, conf.ConfigurationStore, conf.Broker, conf.Zookeeper), + }) + return pulsarInit +} diff --git a/pkg/test/pulsar/cluster_spec.go b/pkg/test/pulsar/cluster_spec.go new file mode 100644 index 000000000..73f4bb9fe --- /dev/null +++ b/pkg/test/pulsar/cluster_spec.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 pulsar + +// ClusterSpec is to build a pulsar cluster. +type ClusterSpec struct { + Image string + ClusterName string + BookiePort int + NumBookies int + BrokerServicePort int + BrokerHTTPServicePort int + NumBrokers int + ProxyServicePort int + ProxyHTTPServicePort int + NumProxies int +} + +// DefaultClusterSpec returns default configuration of a cluster. +func DefaultClusterSpec() *ClusterSpec { + return &ClusterSpec{ + Image: LatestImage, + ClusterName: "default-cluster", + BookiePort: DefaultBookiePort, + NumBookies: 2, + BrokerServicePort: DefaultBrokerPort, + BrokerHTTPServicePort: DefaultBrokerHTTPPort, + NumBrokers: 2, + ProxyServicePort: DefaultBrokerPort, + ProxyHTTPServicePort: DefaultBrokerHTTPPort, + NumProxies: 1, + } +} diff --git a/pkg/test/pulsar/cluster_test.go b/pkg/test/pulsar/cluster_test.go new file mode 100644 index 000000000..d896cf9ce --- /dev/null +++ b/pkg/test/pulsar/cluster_test.go @@ -0,0 +1,56 @@ +// 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 pulsar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDefaultPulsarCluster(t *testing.T) { + ctx := context.Background() + pulsar, err := DefaultPulsarCluster() + // nolint + defer pulsar.Close(ctx) + if err != nil { + t.Fatal(err) + } + + err = pulsar.Start(ctx) + defer pulsar.Stop(ctx) + if err != nil { + t.Fatal(err) + } + + path, err := pulsar.GetHTTPServiceURL(ctx) + if err != nil { + t.Fatal(err) + } + + resp, err := http.Get(path + "/admin/v2/tenants") + // nolint + defer resp.Body.Close() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, 200, resp.StatusCode) +} diff --git a/pkg/test/pulsar/standalonoe.go b/pkg/test/pulsar/standalonoe.go new file mode 100644 index 000000000..492fc18c6 --- /dev/null +++ b/pkg/test/pulsar/standalonoe.go @@ -0,0 +1,30 @@ +// 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 pulsar + +import "github.com/streamnative/pulsarctl/pkg/test" + +func NewStandalone(image string) *test.BaseContainer { + s := test.NewContainer(image) + s.ExposedPorts([]string{"8080", "6650"}) + s.WithCmd([]string{ + "bin/pulsar", "standalone", + }) + s.WaitForPort("8080") + return s +} diff --git a/pkg/test/pulsar/standalonoe_test.go b/pkg/test/pulsar/standalonoe_test.go new file mode 100644 index 000000000..0593c3c1b --- /dev/null +++ b/pkg/test/pulsar/standalonoe_test.go @@ -0,0 +1,53 @@ +// 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 pulsar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewStandalone(t *testing.T) { + ctx := context.Background() + standalone := NewStandalone("apachepulsar/pulsar:latest") + err := standalone.Start(ctx) + // nolint + defer standalone.Stop(ctx) + if err != nil { + t.Fatal(err) + } + + port, err := standalone.MappedPort(ctx, "8080") + if err != nil { + t.Fatal(err) + } + path := "http://localhost:" + port.Port() + "/admin/v2/tenants" + + resp, err := http.Get(path) + // nolint + defer resp.Body.Close() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, 200, resp.StatusCode) + +} diff --git a/pkg/test/utils.go b/pkg/test/utils.go new file mode 100644 index 000000000..0d542555d --- /dev/null +++ b/pkg/test/utils.go @@ -0,0 +1,39 @@ +// 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 test + +import ( + "context" + + "github.com/testcontainers/testcontainers-go" +) + +// NewNetwork creates a network. +func NewNetwork(name string) (testcontainers.Network, error) { + ctx := context.Background() + dp, err := testcontainers.NewDockerProvider() + if err != nil { + return nil, err + } + + net, err := dp.CreateNetwork(ctx, testcontainers.NetworkRequest{ + Name: name, + CheckDuplicate: true, + }) + return net, err +} From 88bfe0e0aa6dc4bbba49f06cc75a7f53721334de Mon Sep 17 00:00:00 2001 From: Yong Zhang Date: Wed, 8 Jan 2020 12:14:11 +0800 Subject: [PATCH 3/6] * Move direcotories --- pkg/test/pulsar/cluster.go | 2 +- pkg/test/{ => pulsar}/containers/bookie.go | 0 pkg/test/{ => pulsar}/containers/broker.go | 0 pkg/test/{ => pulsar}/containers/proxy.go | 0 pkg/test/{ => pulsar}/containers/zookeeper.go | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename pkg/test/{ => pulsar}/containers/bookie.go (100%) rename pkg/test/{ => pulsar}/containers/broker.go (100%) rename pkg/test/{ => pulsar}/containers/proxy.go (100%) rename pkg/test/{ => pulsar}/containers/zookeeper.go (100%) diff --git a/pkg/test/pulsar/cluster.go b/pkg/test/pulsar/cluster.go index 73c1ae268..92e25c11d 100644 --- a/pkg/test/pulsar/cluster.go +++ b/pkg/test/pulsar/cluster.go @@ -23,7 +23,7 @@ import ( "strconv" "github.com/streamnative/pulsarctl/pkg/test" - "github.com/streamnative/pulsarctl/pkg/test/containers" + "github.com/streamnative/pulsarctl/pkg/test/pulsar/containers" "github.com/pkg/errors" "github.com/testcontainers/testcontainers-go" diff --git a/pkg/test/containers/bookie.go b/pkg/test/pulsar/containers/bookie.go similarity index 100% rename from pkg/test/containers/bookie.go rename to pkg/test/pulsar/containers/bookie.go diff --git a/pkg/test/containers/broker.go b/pkg/test/pulsar/containers/broker.go similarity index 100% rename from pkg/test/containers/broker.go rename to pkg/test/pulsar/containers/broker.go diff --git a/pkg/test/containers/proxy.go b/pkg/test/pulsar/containers/proxy.go similarity index 100% rename from pkg/test/containers/proxy.go rename to pkg/test/pulsar/containers/proxy.go diff --git a/pkg/test/containers/zookeeper.go b/pkg/test/pulsar/containers/zookeeper.go similarity index 100% rename from pkg/test/containers/zookeeper.go rename to pkg/test/pulsar/containers/zookeeper.go From a2dbd7f1165e9ecdfb3a576152a74be16675dd92 Mon Sep 17 00:00:00 2001 From: Yong Zhang Date: Wed, 8 Jan 2020 14:05:32 +0800 Subject: [PATCH 4/6] Add bookkeeper cluster for test bkctl --- *Motivation* Using testcontainer to create a bookkeeper cluster with HTTP service for testing the bkctl function. *Modifications* - Using testcotainer to create a bookkeeper cluster *Verify this change* - Pass the cluster_test.go --- pkg/test/base_container.go | 5 + pkg/test/bookkeeper/cluster.go | 142 ++++++++++++++++++++ pkg/test/bookkeeper/cluster_script.go | 29 ++++ pkg/test/bookkeeper/cluster_spec.go | 40 ++++++ pkg/test/bookkeeper/cluster_test.go | 55 ++++++++ pkg/test/bookkeeper/containers/bookie.go | 36 +++++ pkg/test/bookkeeper/containers/zookeeper.go | 42 ++++++ pkg/test/cluster.go | 37 +++++ pkg/test/pulsar/cluster.go | 54 +++----- pkg/test/utils.go | 6 + 10 files changed, 410 insertions(+), 36 deletions(-) create mode 100644 pkg/test/bookkeeper/cluster.go create mode 100644 pkg/test/bookkeeper/cluster_script.go create mode 100644 pkg/test/bookkeeper/cluster_spec.go create mode 100644 pkg/test/bookkeeper/cluster_test.go create mode 100644 pkg/test/bookkeeper/containers/bookie.go create mode 100644 pkg/test/bookkeeper/containers/zookeeper.go create mode 100644 pkg/test/cluster.go diff --git a/pkg/test/base_container.go b/pkg/test/base_container.go index 4476a0173..cafd38a57 100644 --- a/pkg/test/base_container.go +++ b/pkg/test/base_container.go @@ -61,6 +61,11 @@ func (bc *BaseContainer) WithNetworkAliases(aliases map[string][]string) *BaseCo return bc } +// GetANetworkAlias returns a network alias of the container. +func (bc *BaseContainer) GetANetworkAlias(network string) string { + return bc.containerRequest.NetworkAliases[network][0] +} + // WithCmd sets the containers start up commands. func (bc *BaseContainer) WithCmd(cmd []string) *BaseContainer { bc.containerRequest.Cmd = append(bc.containerRequest.Cmd, cmd...) diff --git a/pkg/test/bookkeeper/cluster.go b/pkg/test/bookkeeper/cluster.go new file mode 100644 index 000000000..493eae9cb --- /dev/null +++ b/pkg/test/bookkeeper/cluster.go @@ -0,0 +1,142 @@ +// 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 ( + "context" + "fmt" + "strconv" + + "github.com/streamnative/pulsarctl/pkg/test" + "github.com/streamnative/pulsarctl/pkg/test/bookkeeper/containers" + + "github.com/pkg/errors" + "github.com/testcontainers/testcontainers-go" +) + +var ( + LatestImage = "apache/bookkeeper:latest" +) + +type ClusterDef struct { + clusterSpec *ClusterSpec + networkName string + network testcontainers.Network + zkContainer *test.BaseContainer + bookieContainers map[string]*test.BaseContainer +} + +func DefaultCluster() (test.Cluster, error) { + return NewBookieCluster(DefaultClusterSpec()) +} + +func NewBookieCluster(spec *ClusterSpec) (test.Cluster, error) { + c := &ClusterDef{clusterSpec: spec} + c.networkName = spec.ClusterName + test.RandomSuffix() + network, err := test.NewNetwork(c.networkName) + if err != nil { + return c, err + } + c.network = network + + c.zkContainer = containers.NewZookeeperContainer(spec.Image, c.networkName) + c.bookieContainers = getBookieContainers(spec, c.networkName, containers.DefaultZookeeperServiceString()) + + return c, nil +} + +func getBookieContainers(c *ClusterSpec, networkName, zkServers string) map[string]*test.BaseContainer { + bookies := make(map[string]*test.BaseContainer) + for i := 0; i < c.NumBookies; i++ { + name := fmt.Sprintf("%s-%d", containers.BookieName, i) + bookie := containers.NewBookieContainer(c.Image, networkName) + bookie.WithEnv(map[string]string{ + "BK_zkServers": zkServers, + "BK_httpServerEnabled": "true", + "BK_httpServerPort": strconv.Itoa(c.BookieHTTPServicePort), + "BK_ledgerDirectories": "bk/ledgers", + "BK_indexDirectories": "bk/ledgers", + "BK_journalDirectory": "bk/journal", + }) + bookies[name] = bookie + } + return bookies +} + +func (c *ClusterDef) Start(ctx context.Context) error { + err := c.zkContainer.Start(ctx) + if err != nil { + return errors.WithMessage(err, "encountering errors when starting the zookeeper") + } + fmt.Printf("Zookeeper %s:%s started.\n", + c.zkContainer.GetANetworkAlias(c.networkName), c.zkContainer.GetContainerID()) + zkName := c.zkContainer.GetANetworkAlias(c.networkName) + init := InitBookieCluster(c.clusterSpec.Image, c.networkName, fmt.Sprintf("%s:2181", zkName)) + err = init.Start(ctx) + if err != nil { + return errors.WithMessage(err, "encountering errors when formatting metadata for bookkeeper") + } + fmt.Println("BookKeeper metadata initialized successfully.") + + for k, v := range c.bookieContainers { + err = v.Start(ctx) + if err != nil { + return errors.WithMessagef(err, "encountering errors when starting the bookie %s\n", k) + } + fmt.Printf("Bookie %s:%s started.\n", k, v.GetContainerID()) + } + + return nil +} + +func (c *ClusterDef) Stop(ctx context.Context) error { + if c.bookieContainers != nil { + for _, v := range c.bookieContainers { + err := v.Stop(ctx) + if err != nil { + return err + } + } + } + return nil +} + +func (c *ClusterDef) GetPlainTextServiceURL(ctx context.Context) (string, error) { + return "", errors.New("unsupported operation") +} + +func (c *ClusterDef) GetHTTPServiceURL(ctx context.Context) (string, error) { + port, err := c.getABookie().MappedPort(ctx, strconv.Itoa(c.clusterSpec.BookieHTTPServicePort)) + if err != nil { + return "", err + } + return "http://localhost:" + port.Port(), nil +} + +func (c *ClusterDef) Close(ctx context.Context) { + if c.network != nil { + c.network.Remove(ctx) + } +} + +func (c *ClusterDef) getABookie() *test.BaseContainer { + for _, v := range c.bookieContainers { + return v + } + return nil +} diff --git a/pkg/test/bookkeeper/cluster_script.go b/pkg/test/bookkeeper/cluster_script.go new file mode 100644 index 000000000..deb92dd87 --- /dev/null +++ b/pkg/test/bookkeeper/cluster_script.go @@ -0,0 +1,29 @@ +// 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/test" + +func InitBookieCluster(image, network, zookeeper string) *test.BaseContainer { + bookieInit := test.NewContainer(image) + bookieInit.WithNetwork([]string{network}) + bookieInit.WithEnv(map[string]string{"BK_zkServers": zookeeper}) + bookieInit.WithCmd([]string{"/opt/bookkeeper/bin/bookkeeper", "shell", "metaformat"}) + bookieInit.WaitForLog("/opt/bookkeeper/bin/bookkeeper shell metaformat") + return bookieInit +} diff --git a/pkg/test/bookkeeper/cluster_spec.go b/pkg/test/bookkeeper/cluster_spec.go new file mode 100644 index 000000000..284a17621 --- /dev/null +++ b/pkg/test/bookkeeper/cluster_spec.go @@ -0,0 +1,40 @@ +// 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/test/bookkeeper/containers" + +type ClusterSpec struct { + Image string + ClusterName string + NumBookies int + BookieServicePort int + BookieHTTPServicePort int + ZookeeperServicePort int +} + +func DefaultClusterSpec() *ClusterSpec { + return &ClusterSpec{ + Image: LatestImage, + ClusterName: "default-bookie", + NumBookies: 1, + BookieServicePort: containers.DefaultBookieServicePort, + BookieHTTPServicePort: containers.DefaultBookieHTTPServicePort, + ZookeeperServicePort: containers.DefaultZookeeperServicePort, + } +} diff --git a/pkg/test/bookkeeper/cluster_test.go b/pkg/test/bookkeeper/cluster_test.go new file mode 100644 index 000000000..ab42e8358 --- /dev/null +++ b/pkg/test/bookkeeper/cluster_test.go @@ -0,0 +1,55 @@ +// 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 ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDefaultCluster(t *testing.T) { + ctx := context.Background() + bkCluster, err := DefaultCluster() + // nolint + defer bkCluster.Close(ctx) + if err != nil { + t.Fatal(err) + } + err = bkCluster.Start(ctx) + if err != nil { + t.Fatal(err) + } + defer bkCluster.Stop(ctx) + + path, err := bkCluster.GetHTTPServiceURL(ctx) + if err != nil { + t.Fatal(err) + } + + resp, err := http.Get(path + "/api/v1/bookie/list_bookie_info") + // nolint + defer resp.Body.Close() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, 200, resp.StatusCode) +} diff --git a/pkg/test/bookkeeper/containers/bookie.go b/pkg/test/bookkeeper/containers/bookie.go new file mode 100644 index 000000000..6d6a8af1e --- /dev/null +++ b/pkg/test/bookkeeper/containers/bookie.go @@ -0,0 +1,36 @@ +// 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 containers + +import "github.com/streamnative/pulsarctl/pkg/test" + +const ( + BookieName = "bookie" + DefaultBookieServicePort = 3181 + DefaultBookieHTTPServicePort = 8080 +) + +func NewBookieContainer(image, network string) *test.BaseContainer { + bk := test.NewContainer(image) + bk.WithNetwork([]string{network}) + bk.WithNetworkAliases(map[string][]string{network: {BookieName}}) + bk.ExposedPorts([]string{"8080"}) + bk.WithCmd([]string{"bookie"}) + bk.WaitForPort("8080") + return bk +} diff --git a/pkg/test/bookkeeper/containers/zookeeper.go b/pkg/test/bookkeeper/containers/zookeeper.go new file mode 100644 index 000000000..35732e0f3 --- /dev/null +++ b/pkg/test/bookkeeper/containers/zookeeper.go @@ -0,0 +1,42 @@ +// 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 containers + +import ( + "fmt" + + "github.com/streamnative/pulsarctl/pkg/test" +) + +const ( + ZookeeperName = "zookeeper" + DefaultZookeeperServicePort = 2181 +) + +func NewZookeeperContainer(image, network string) *test.BaseContainer { + zookeeper := test.NewContainer(image) + zookeeper.WithNetwork([]string{network}) + zookeeper.WithNetworkAliases(map[string][]string{network: {ZookeeperName}}) + zookeeper.WithCmd([]string{"zookeeper"}) + zookeeper.WaitForLog("binding to port 0.0.0.0/0.0.0.0:2181") + return zookeeper +} + +func DefaultZookeeperServiceString() string { + return fmt.Sprintf("%s:%d", ZookeeperName, DefaultZookeeperServicePort) +} diff --git a/pkg/test/cluster.go b/pkg/test/cluster.go new file mode 100644 index 000000000..33b1eb56f --- /dev/null +++ b/pkg/test/cluster.go @@ -0,0 +1,37 @@ +// 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 test + +import "context" + +type Cluster interface { + // Start a cluster. + Start(ctx context.Context) error + + // Stop a cluster. + Stop(ctx context.Context) error + + // GetPlainTextServiceURL gets the service connect string. + GetPlainTextServiceURL(ctx context.Context) (string, error) + + // GetHTTPServiceURL gets the HTTP service connect string. + GetHTTPServiceURL(ctx context.Context) (string, error) + + // Close closes the resources used for starting cluster. + Close(ctx context.Context) +} diff --git a/pkg/test/pulsar/cluster.go b/pkg/test/pulsar/cluster.go index 92e25c11d..44a8b2dce 100644 --- a/pkg/test/pulsar/cluster.go +++ b/pkg/test/pulsar/cluster.go @@ -41,6 +41,7 @@ var ( type ClusterDef struct { clusterSpec *ClusterSpec + networkName string network testcontainers.Network zkContainer *test.BaseContainer proxyContainer *test.BaseContainer @@ -48,55 +49,34 @@ type ClusterDef struct { brokerContainers map[string]*test.BaseContainer } -type Cluster interface { - // Start a pulsar cluster. - Start(ctx context.Context) error - - // Stop a pulsar cluster. - Stop(ctx context.Context) error - - // GetPlainTextServiceURL gets the pulsar service connect string. - GetPlainTextServiceURL(ctx context.Context) (string, error) - - // GetHTTPServiceURL gets the pulsar HTTP service connect string. - GetHTTPServiceURL(ctx context.Context) (string, error) - - // Close closes resources used for starting the cluster. - Close(ctx context.Context) -} - // DefaultPulsarCluster creates a pulsar cluster using the default cluster spec. -func DefaultPulsarCluster() (Cluster, error) { +func DefaultPulsarCluster() (test.Cluster, error) { return NewPulsarCluster(DefaultClusterSpec()) } // NewPulsarCluster creates a pulsar cluster using the spec. -func NewPulsarCluster(spec *ClusterSpec) (Cluster, error) { - networkName := spec.ClusterName - network, err := test.NewNetwork(networkName) +func NewPulsarCluster(spec *ClusterSpec) (test.Cluster, error) { + c := &ClusterDef{clusterSpec: spec} + c.networkName = spec.ClusterName + test.RandomSuffix() + network, err := test.NewNetwork(c.networkName) if err != nil { - return nil, err + return c, err } + c.network = network - zookeeper := containers.NewZookeeperContainer(LatestImage, networkName) - bookies := getBookieContainers(networkName, spec.NumBookies) - brokers := getBrokerContainers(spec.ClusterName, networkName, spec.NumBrokers) + c.zkContainer = containers.NewZookeeperContainer(LatestImage, c.networkName) + c.bookieContainers = getBookieContainers(c.networkName, spec.NumBookies) + brokers := getBrokerContainers(spec.ClusterName, c.networkName, spec.NumBrokers) broker := getABrokerNetAlias(brokers) - proxy := containers.NewProxyContainer(LatestImage, networkName).WithEnv(map[string]string{ + c.brokerContainers = brokers + c.proxyContainer = containers.NewProxyContainer(LatestImage, c.networkName).WithEnv(map[string]string{ "webServicePort": strconv.Itoa(spec.ProxyHTTPServicePort), "servicePort": strconv.Itoa(spec.ProxyServicePort), "brokerServiceURL": fmt.Sprintf("pulsar://%s:%d", broker, spec.BrokerServicePort), "brokerWebServiceURL": fmt.Sprintf("http://%s:%d", broker, spec.BrokerHTTPServicePort), }) - return &ClusterDef{ - network: network, - clusterSpec: spec, - zkContainer: zookeeper, - proxyContainer: proxy, - bookieContainers: bookies, - brokerContainers: brokers, - }, nil + return c, nil } func getBookieContainers(network string, num int) map[string]*test.BaseContainer { @@ -146,7 +126,7 @@ func (c *ClusterDef) Start(ctx context.Context) error { Zookeeper: fmt.Sprintf("%s:%d", containers.ZookeeperName, DefaultZKPort), Broker: fmt.Sprintf("%s:%d", getABrokerNetAlias(c.brokerContainers), c.clusterSpec.BrokerHTTPServicePort), - }, LatestImage, c.clusterSpec.ClusterName) + }, LatestImage, c.networkName) err = init.Start(ctx) if err != nil { return errors.WithMessage(err, "encountered errors when initializing the pulsar cluster") @@ -231,5 +211,7 @@ func (c *ClusterDef) GetHTTPServiceURL(ctx context.Context) (string, error) { } func (c *ClusterDef) Close(ctx context.Context) { - c.network.Remove(ctx) + if c.network != nil { + c.network.Remove(ctx) + } } diff --git a/pkg/test/utils.go b/pkg/test/utils.go index 0d542555d..af2d1864b 100644 --- a/pkg/test/utils.go +++ b/pkg/test/utils.go @@ -19,6 +19,8 @@ package test import ( "context" + "strconv" + "time" "github.com/testcontainers/testcontainers-go" ) @@ -37,3 +39,7 @@ func NewNetwork(name string) (testcontainers.Network, error) { }) return net, err } + +func RandomSuffix() string { + return "-" + strconv.FormatInt(time.Now().Unix(), 10) +} From 6ca514d23883e5d5174e3c8f96825970214fe2cb Mon Sep 17 00:00:00 2001 From: Yong Zhang Date: Wed, 8 Jan 2020 15:44:36 +0800 Subject: [PATCH 5/6] Address comments --- pkg/bkctl/autorecovery/autorecovery.go | 4 +- pkg/bkctl/autorecovery/decommission.go | 16 ++-- pkg/bkctl/autorecovery/decommission_test.go | 48 ++++++++++++ .../get_lost_bookie_recovery_delay.go | 12 +-- .../list_under_replicated_ledger.go | 36 ++++----- pkg/bkctl/autorecovery/recover_bookie.go | 35 ++++++--- pkg/bkctl/autorecovery/recover_bookie_test.go | 35 +++++++++ .../set_lost_bookie_recovery_delay.go | 18 ++--- .../set_lost_bookie_recovery_delay_test.go | 75 +++++++++++++++++++ pkg/bkctl/autorecovery/test_help.go | 58 ++++++++++++++ pkg/bkctl/autorecovery/trigger_audit.go | 18 ++--- pkg/bkctl/autorecovery/who_is_auditor.go | 10 +-- 12 files changed, 296 insertions(+), 69 deletions(-) create mode 100644 pkg/bkctl/autorecovery/decommission_test.go create mode 100644 pkg/bkctl/autorecovery/recover_bookie_test.go create mode 100644 pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay_test.go create mode 100644 pkg/bkctl/autorecovery/test_help.go diff --git a/pkg/bkctl/autorecovery/autorecovery.go b/pkg/bkctl/autorecovery/autorecovery.go index 7fce86e5b..10da5a463 100644 --- a/pkg/bkctl/autorecovery/autorecovery.go +++ b/pkg/bkctl/autorecovery/autorecovery.go @@ -25,8 +25,8 @@ import ( func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { resourceCmd := cmdutils.NewResourceCmd( - "autorecovery", - "Operations about ledger", + "auto-recovery", + "Operations about auto recovering", "", "") diff --git a/pkg/bkctl/autorecovery/decommission.go b/pkg/bkctl/autorecovery/decommission.go index bed469dc3..e1b33dd42 100644 --- a/pkg/bkctl/autorecovery/decommission.go +++ b/pkg/bkctl/autorecovery/decommission.go @@ -23,25 +23,25 @@ import ( func decommissionCmd(vc *cmdutils.VerbCmd) { var desc cmdutils.LongDescription - desc.CommandUsedFor = "This command is used for decommission a bookie." - desc.CommandPermission = "none" + 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 autorecovery (bk-ip:bk-port)", + 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: "normal 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", + 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) @@ -49,7 +49,7 @@ func decommissionCmd(vc *cmdutils.VerbCmd) { vc.SetDescription( "decommission", - "Decommission a bookie", + "Decommission a bookie.", desc.ToString(), desc.ExampleToString()) @@ -62,7 +62,7 @@ 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) + 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 000000000..51ff60c60 --- /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 index 94f37484e..855818f11 100644 --- a/pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go +++ b/pkg/bkctl/autorecovery/get_lost_bookie_recovery_delay.go @@ -24,27 +24,27 @@ import ( 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 = "none" + 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 bookkeeeper autorecovery getdelay", + 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: "normal output", + Desc: "Get the lost bookie recovery delay of a bookie. ", Out: "lostBookieRecoveryDelay value: (delay)", } out = append(out, successOut) desc.CommandOutput = out vc.SetDescription( - "getdelay", - "Get the lost bookie recovery delay of a bookie", + "get-lost-bookie-recovery-delay", + "Get the lost bookie recovery delay of a bookie.", desc.ToString(), desc.ExampleToString()) diff --git a/pkg/bkctl/autorecovery/list_under_replicated_ledger.go b/pkg/bkctl/autorecovery/list_under_replicated_ledger.go index 715bcec17..dcdac48ff 100644 --- a/pkg/bkctl/autorecovery/list_under_replicated_ledger.go +++ b/pkg/bkctl/autorecovery/list_under_replicated_ledger.go @@ -25,31 +25,31 @@ import ( func listUnderReplicatedLedgerCmd(vc *cmdutils.VerbCmd) { var desc cmdutils.LongDescription - desc.CommandUsedFor = "This command is used for listing all the underreplicated ledgers which have been marked " + - "for rereplication." - desc.CommandPermission = "none" + 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: "List all the underreplicated ledgers which have been marked for rereplication", - Command: "pulsarctl bookkeeper autorecovery listunderreplicatedledger", + 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: "List all the underreplicated ledgers of a bookie which have been marked for rereplication", - Command: "pulsarctl bookkeeper autorecovery listunderreplicatedledger --include (bookie-ip:bookie-port)", + 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: "List all the underreplicated ledgers except a bookie which have been marked for rereplication", - Command: "pulsarctl bookkeeper autorecovery listunderreplicatedledger --exclude (bookie-ip:bookie-port)", + 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: "normal output", + Desc: "Get the under-replicated ledgers successfully.", Out: `{ [ledgerId1, ledgerId2...] }`, @@ -58,8 +58,8 @@ func listUnderReplicatedLedgerCmd(vc *cmdutils.VerbCmd) { desc.CommandOutput = out vc.SetDescription( - "listunderreplicatedledger", - "List all the underreplicated ledgers which have been marked for rereplication", + "list-under-replicated-ledger", + "Get all the under-replicated ledgers which have been marked for re-replication.", desc.ToString(), desc.ExampleToString()) @@ -71,18 +71,18 @@ func listUnderReplicatedLedgerCmd(vc *cmdutils.VerbCmd) { return doListUnderReplicatedLedger(vc, include, exclude, show) }) - vc.FlagSetGroup.InFlagSet("List Under Replicated Ledger", func(set *pflag.FlagSet) { - set.StringVar(&include, "include", "", "show the underreplicated ledger of the bookie") - set.StringVar(&exclude, "exclude", "", "show the underreplicated ledger exclude the bookie") - set.BoolVar(&show, "show", false, "show the ledgers replica list") + 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, print bool) error { +func doListUnderReplicatedLedger(vc *cmdutils.VerbCmd, include, exclude string, show bool) error { admin := cmdutils.NewBookieClient() var l interface{} var err error - if print { + if show { l, err = admin.AutoRecovery().PrintListUnderReplicatedLedger(include, exclude) } else { l, err = admin.AutoRecovery().ListUnderReplicatedLedger(include, exclude) diff --git a/pkg/bkctl/autorecovery/recover_bookie.go b/pkg/bkctl/autorecovery/recover_bookie.go index e47e5739b..cc644d07f 100644 --- a/pkg/bkctl/autorecovery/recover_bookie.go +++ b/pkg/bkctl/autorecovery/recover_bookie.go @@ -18,6 +18,8 @@ package autorecovery import ( + "errors" + "github.com/streamnative/pulsarctl/pkg/cmdutils" "github.com/spf13/pflag" @@ -26,27 +28,32 @@ import ( 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 = "none" + 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 autorecovery recoverbookie (bookie-1) (bookie-2)", + 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: "normal output", - Out: "Successfully recover the bookies (bookie-1) (bookie-2)", + 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) + out = append(out, successOut, IDNotSpecified) desc.CommandOutput = out vc.SetDescription( - "recoverbookie", - "Recover the ledger data of a failed bookie", + "recover-bookie", + "Recover the ledger data of a failed bookie.", desc.ToString(), desc.ExampleToString()) @@ -55,11 +62,15 @@ func recoverBookieCmd(vc *cmdutils.VerbCmd) { 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, "delelte-cookie", false, "delete cookie") + vc.FlagSetGroup.InFlagSet("Recover bookie", func(set *pflag.FlagSet) { + set.BoolVar(&deleteCookie, "delete-cookie", false, + "Delete cookie when recovering the failed bookies.") }) } @@ -68,9 +79,9 @@ func doRecoverBookie(vc *cmdutils.VerbCmd, deleteCookie bool) error { 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) + 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) + vc.Command.Printf("Successfully recover the bookie %v.\n", vc.NameArgs) } } diff --git a/pkg/bkctl/autorecovery/recover_bookie_test.go b/pkg/bkctl/autorecovery/recover_bookie_test.go new file mode 100644 index 000000000..d95038fa5 --- /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 index 6415f3ca3..5c3cd7f95 100644 --- a/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go +++ b/pkg/bkctl/autorecovery/set_lost_bookie_recovery_delay.go @@ -28,32 +28,32 @@ import ( 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 = "none" + 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 autorecovery setdelay (delay)", + 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: "normal 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", + 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( - "setdelay", - "Set the lost bookie recovery delay", + "set-lost-bookie-recovery-delay", + "Set the lost bookie recovery delay.", desc.ToString(), desc.ExampleToString()) @@ -64,14 +64,14 @@ func setLostBookieRecoveryDelayCmd(vc *cmdutils.VerbCmd) { func doLostBookieRecoveryDelay(vc *cmdutils.VerbCmd) error { delay, err := strconv.Atoi(vc.NameArg) - if err != nil { + 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) + 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 000000000..5ee612852 --- /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 000000000..900286b57 --- /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 index f8610730c..e054a04e8 100644 --- a/pkg/bkctl/autorecovery/trigger_audit.go +++ b/pkg/bkctl/autorecovery/trigger_audit.go @@ -23,28 +23,28 @@ import ( func triggerAuditCmd(vc *cmdutils.VerbCmd) { var desc cmdutils.LongDescription - desc.CommandUsedFor = "This command is used for triggering audit by resetting the lostBookieRecoveryDelay." - desc.CommandPermission = "none" + 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 lostBookieRecoveryDelay", - Command: "pulsarctl bookkeeper autorecovery triggeraudit", + 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: "normal output", - Out: "Successfully trigger audit by resetting the lostBookieRecoveryDelay", + 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( - "triggeraudit", - "Trigger audit by resetting the lostBookieRecoveryDelay", + "trigger-audit", + "Trigger audit by resetting the lost bookie recovery delay.", desc.ToString(), desc.ExampleToString()) @@ -57,7 +57,7 @@ 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 lostBookieRecoveryDelay") + 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 index 365b2388f..0693bf149 100644 --- a/pkg/bkctl/autorecovery/who_is_auditor.go +++ b/pkg/bkctl/autorecovery/who_is_auditor.go @@ -24,19 +24,19 @@ import ( func whoIsAuditorCmd(vc *cmdutils.VerbCmd) { var desc cmdutils.LongDescription desc.CommandUsedFor = "This command is used for getting who is the auditor." - desc.CommandPermission = "none" + 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 autorecovery whoisauditor", + Command: "pulsarctl bookkeeper auto-recovery who-is-auditor", } examples = append(examples, get) desc.CommandExamples = examples var out []cmdutils.Output successOut := cmdutils.Output{ - Desc: "normal output", + Desc: "Get the auditor successfully.", Out: `{ "Auditor": "hostname/hostAddress:Port" }`, @@ -45,8 +45,8 @@ func whoIsAuditorCmd(vc *cmdutils.VerbCmd) { desc.CommandOutput = out vc.SetDescription( - "whoisauditor", - "Get who is the auditor", + "who-is-auditor", + "Get the auditor of the bookie.", desc.ToString(), desc.ExampleToString()) From 649153f7a87454ce8ea899a0c531f2084e4cf5ea Mon Sep 17 00:00:00 2001 From: Yong Zhang Date: Thu, 9 Jan 2020 22:40:13 +0800 Subject: [PATCH 6/6] Add test for the auto recovery commmand --- (If this PR fixes a github issue, please add `Fixes #`) *Modifications* Add test for the auto recovery command --- go.mod | 2 + go.sum | 1 + pkg/bkctl/autorecovery/decommission.go | 2 +- .../lost_bookie_recovery_delay_test.go | 72 +++++++++++++++++++ pkg/bkctl/autorecovery/test_help.go | 1 + pkg/bkctl/autorecovery/trigger_audit_test.go | 70 ++++++++++++++++++ pkg/bkctl/autorecovery/who_is_auditor.go | 2 +- pkg/bkctl/autorecovery/who_is_auditor_test.go | 72 +++++++++++++++++++ pkg/bookkeeper/autorecovery.go | 8 +-- pkg/test/bookkeeper/bkctl.go | 55 ++++++++++++++ pkg/test/bookkeeper/cluster.go | 19 ++++- pkg/test/bookkeeper/cluster_spec.go | 43 ++++++++++- pkg/test/utils.go | 8 +++ 13 files changed, 344 insertions(+), 11 deletions(-) create mode 100644 pkg/bkctl/autorecovery/lost_bookie_recovery_delay_test.go create mode 100644 pkg/bkctl/autorecovery/trigger_audit_test.go create mode 100644 pkg/bkctl/autorecovery/who_is_auditor_test.go create mode 100644 pkg/test/bookkeeper/bkctl.go diff --git a/go.mod b/go.mod index b98408637..13fc77cbb 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/go-cmp v0.3.1 // indirect github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06 github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b // indirect + github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect github.com/olekukonko/tablewriter v0.0.1 @@ -19,4 +20,5 @@ require ( github.com/stretchr/testify v1.3.0 github.com/testcontainers/testcontainers-go v0.0.10 gopkg.in/yaml.v2 v2.2.2 + gotest.tools v0.0.0-20181223230014-1083505acf35 ) diff --git a/go.sum b/go.sum index f7eda5a0b..614a7ce3c 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,7 @@ github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06 h1:vN4d3jSss3ExzU github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06/go.mod h1:++9BgZujZd4v0ZTZCb5iPsaomXdZWyxotIAh1IiDm44= github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b h1:xYEM2oBUhBEhQjrV+KJ9lEWDWYZoNVZUaBF++Wyljq4= github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b/go.mod h1:V0HF/ZBlN86HqewcDC/cVxMmYDiRukWjSrgKLUAn9Js= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= diff --git a/pkg/bkctl/autorecovery/decommission.go b/pkg/bkctl/autorecovery/decommission.go index e1b33dd42..517852043 100644 --- a/pkg/bkctl/autorecovery/decommission.go +++ b/pkg/bkctl/autorecovery/decommission.go @@ -29,7 +29,7 @@ func decommissionCmd(vc *cmdutils.VerbCmd) { var examples []cmdutils.Example c := cmdutils.Example{ Desc: "Decommission a bookie.", - Command: "pulsarctl bookkeeper auto-recovery (bk-ip:bk-port)", + Command: "pulsarctl bookkeeper auto-recovery decommission (bk-ip:bk-port)", } examples = append(examples, c) desc.CommandExamples = examples diff --git a/pkg/bkctl/autorecovery/lost_bookie_recovery_delay_test.go b/pkg/bkctl/autorecovery/lost_bookie_recovery_delay_test.go new file mode 100644 index 000000000..49048bce6 --- /dev/null +++ b/pkg/bkctl/autorecovery/lost_bookie_recovery_delay_test.go @@ -0,0 +1,72 @@ +// 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 ( + "context" + "testing" + + "github.com/streamnative/pulsarctl/pkg/test/bookkeeper" + + "github.com/stretchr/testify/assert" +) + +func TestGetLostBookieRecoveryDelayCmd(t *testing.T) { + // prepare the bookkeeper cluster environment + ctx := context.Background() + bk, err := bookkeeper.NewBookieCluster(&bookkeeper.ClusterSpec{ + ClusterName: "test-lost-bookie-recovery-delay", + NumBookies: 1, + BookieEnv: map[string]string{ + "BK_autoRecoveryDaemonEnabled": "true", + }, + }) + // nolint + defer bk.Close(ctx) + if err != nil { + t.Fatal(err) + } + + err = bk.Start(ctx) + // nolint + defer bk.Stop(ctx) + if err != nil { + t.Fatal(err) + } + + httpAddr, err := bk.GetHTTPServiceURL(ctx) + if err != nil { + t.Fatal(err) + } + + args := []string{"--bookie-service-url", httpAddr, "get-lost-bookie-recovery-delay"} + out, execErr, nameErr, err := testAutoRecoveryCommands(getLostBookieRecoveryDelayCmd, args) + if err != nil { + t.Fatal(err) + } + + if nameErr != nil { + t.Fatal(nameErr) + } + + if execErr != nil { + t.Fatal(execErr) + } + + assert.Equal(t, "lostBookieRecoveryDelay value: 0\n", out.String()) +} diff --git a/pkg/bkctl/autorecovery/test_help.go b/pkg/bkctl/autorecovery/test_help.go index 900286b57..e34de7ffc 100644 --- a/pkg/bkctl/autorecovery/test_help.go +++ b/pkg/bkctl/autorecovery/test_help.go @@ -43,6 +43,7 @@ func testAutoRecoveryCommands(newVerb func(cmd *cmdutils.VerbCmd), args []string buf := new(bytes.Buffer) rootCmd.SetOut(buf) rootCmd.SetArgs(append([]string{"auto-recovery"}, args...)) + rootCmd.PersistentFlags().AddFlagSet(cmdutils.PulsarCtlConfig.FlagSet()) resourceCmd := cmdutils.NewResourceCmd( "auto-recovery", diff --git a/pkg/bkctl/autorecovery/trigger_audit_test.go b/pkg/bkctl/autorecovery/trigger_audit_test.go new file mode 100644 index 000000000..9cbe021af --- /dev/null +++ b/pkg/bkctl/autorecovery/trigger_audit_test.go @@ -0,0 +1,70 @@ +// 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 ( + "context" + "testing" + + "github.com/streamnative/pulsarctl/pkg/test/bookkeeper" + + "github.com/stretchr/testify/assert" +) + +func TestTriggerAuditCmd(t *testing.T) { + // prepare the bookkeeper cluster environment + ctx := context.Background() + bk, err := bookkeeper.NewBookieCluster(&bookkeeper.ClusterSpec{ + ClusterName: "test-trigger-audit", + NumBookies: 1, + BookieEnv: map[string]string{ + "BK_autoRecoveryDaemonEnabled": "true", + }, + }) + // nolint + defer bk.Close(ctx) + if err != nil { + t.Fatal(err) + } + + err = bk.Start(ctx) + // nolint + defer bk.Stop(ctx) + if err != nil { + t.Fatal(err) + } + + httpAddr, err := bk.GetHTTPServiceURL(ctx) + if err != nil { + t.Fatal(err) + } + + args := []string{"--bookie-service-url", httpAddr, "trigger-audit"} + out, execErr, nameErr, err := testAutoRecoveryCommands(triggerAuditCmd, args) + if err != nil { + t.Fatal(err) + } + if nameErr != nil { + t.Fatal(nameErr) + } + if execErr != nil { + t.Fatal(execErr) + } + + assert.Equal(t, "Successfully trigger audit by resetting the lost bookie recovery delay.\n", out.String()) +} diff --git a/pkg/bkctl/autorecovery/who_is_auditor.go b/pkg/bkctl/autorecovery/who_is_auditor.go index 0693bf149..deffe9074 100644 --- a/pkg/bkctl/autorecovery/who_is_auditor.go +++ b/pkg/bkctl/autorecovery/who_is_auditor.go @@ -59,7 +59,7 @@ func doWhoIsAuditor(vc *cmdutils.VerbCmd) error { admin := cmdutils.NewBookieClient() auditor, err := admin.AutoRecovery().WhoIsAuditor() if err == nil { - cmdutils.PrintJSON(vc.Command.OutOrStdout(), auditor) + vc.Command.Println(auditor) } return err diff --git a/pkg/bkctl/autorecovery/who_is_auditor_test.go b/pkg/bkctl/autorecovery/who_is_auditor_test.go new file mode 100644 index 000000000..f43950864 --- /dev/null +++ b/pkg/bkctl/autorecovery/who_is_auditor_test.go @@ -0,0 +1,72 @@ +// 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 ( + "context" + "testing" + + "github.com/streamnative/pulsarctl/pkg/test/bookkeeper" + + "github.com/stretchr/testify/assert" +) + +func TestWhoIsAuditor(t *testing.T) { + // prepare the bookkeeper cluster environment + ctx := context.Background() + bk, err := bookkeeper.NewBookieCluster(&bookkeeper.ClusterSpec{ + ClusterName: "test-who-is-auditor", + NumBookies: 1, + BookieEnv: map[string]string{ + "BK_autoRecoveryDaemonEnabled": "true", + }, + }) + // nolint + defer bk.Close(ctx) + if err != nil { + t.Fatal(err) + } + + err = bk.Start(ctx) + // nolint + defer bk.Stop(ctx) + if err != nil { + t.Fatal(err) + } + + allBK := bk.GetAllBookieContainerID() + + httpAddr, err := bk.GetHTTPServiceURL(ctx) + if err != nil { + t.Fatal(err) + } + + args := []string{"--bookie-service-url", httpAddr, "who-is-auditor"} + out, execErr, nameErr, err := testAutoRecoveryCommands(whoIsAuditorCmd, args) + if err != nil { + t.Fatal(err) + } + if nameErr != nil { + t.Fatal(nameErr) + } + if execErr != nil { + t.Fatal(execErr) + } + + assert.Contains(t, out.String(), allBK[0][:12]) +} diff --git a/pkg/bookkeeper/autorecovery.go b/pkg/bookkeeper/autorecovery.go index f1d2e5f1f..3e1d14bf0 100644 --- a/pkg/bookkeeper/autorecovery.go +++ b/pkg/bookkeeper/autorecovery.go @@ -31,7 +31,7 @@ type AutoRecovery interface { PrintListUnderReplicatedLedger(string, string) (map[int64][]string, error) // WhoIsAuditor is used to getting which bookie is the auditor - WhoIsAuditor() (map[string]string, error) + WhoIsAuditor() (string, error) // TriggerAudit is used to triggering audit by resetting the lostBookieRecoveryDelay TriggerAudit() error @@ -90,10 +90,10 @@ func (a *autoRecovery) PrintListUnderReplicatedLedger(missingReplica, return resp, err } -func (a *autoRecovery) WhoIsAuditor() (map[string]string, error) { +func (a *autoRecovery) WhoIsAuditor() (string, error) { endpoint := a.bk.endpoint(a.basePath, "/who_is_auditor") - resp := make(map[string]string) - return resp, a.bk.Client.Get(endpoint, &resp) + resp, err := a.bk.Client.GetWithQueryParams(endpoint, nil, nil, false) + return string(resp), err } func (a *autoRecovery) TriggerAudit() error { diff --git a/pkg/test/bookkeeper/bkctl.go b/pkg/test/bookkeeper/bkctl.go new file mode 100644 index 000000000..31ade9d25 --- /dev/null +++ b/pkg/test/bookkeeper/bkctl.go @@ -0,0 +1,55 @@ +// 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 ( + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/streamnative/pulsarctl/pkg/test" +) + +const bkctl = "/opt/bookkeeper/bin/bkctl" + +func ListBookies(containerID string) ([]string, error) { + bookies := make([]string, 0) + bookieStr, err := test.ExecCmd(containerID, []string{bkctl, "bookies", "list"}) + if err != nil { + return nil, err + } + fmt.Println(bookieStr) + lines := strings.Split(bookieStr, "\n") + for _, v := range lines { + if strings.TrimSpace(v) == "ReadWrite Bookies :" || + strings.TrimSpace(v) == "All Bookies :" || + strings.TrimSpace(v) == "" { + continue + } + bookieAddr := strings.Split(v, ":") + if len(bookieAddr) != 2 { + return nil, errors.Errorf("get bookies address '%s' encountered unexpected error", v) + } + bookieIP := strings.Split(bookieAddr[0], "(") + if len(bookieIP) != 2 { + return nil, errors.Errorf("get bookies address '%s' encountered unexpected error", v) + } + bookies = append(bookies, fmt.Sprintf("%s:%s", bookieIP[0], bookieAddr[1])) + } + return bookies, nil +} diff --git a/pkg/test/bookkeeper/cluster.go b/pkg/test/bookkeeper/cluster.go index 493eae9cb..c7deafec9 100644 --- a/pkg/test/bookkeeper/cluster.go +++ b/pkg/test/bookkeeper/cluster.go @@ -30,10 +30,12 @@ import ( ) var ( - LatestImage = "apache/bookkeeper:latest" + LatestImage = "apache/bookkeeper:latest" + BookKeeper4_10_0 = "apache/bookkeeper:4.10.0" ) type ClusterDef struct { + test.Cluster clusterSpec *ClusterSpec networkName string network testcontainers.Network @@ -41,11 +43,12 @@ type ClusterDef struct { bookieContainers map[string]*test.BaseContainer } -func DefaultCluster() (test.Cluster, error) { +func DefaultCluster() (*ClusterDef, error) { return NewBookieCluster(DefaultClusterSpec()) } -func NewBookieCluster(spec *ClusterSpec) (test.Cluster, error) { +func NewBookieCluster(spec *ClusterSpec) (*ClusterDef, error) { + spec = GetClusterSpec(spec) c := &ClusterDef{clusterSpec: spec} c.networkName = spec.ClusterName + test.RandomSuffix() network, err := test.NewNetwork(c.networkName) @@ -69,10 +72,12 @@ func getBookieContainers(c *ClusterSpec, networkName, zkServers string) map[stri "BK_zkServers": zkServers, "BK_httpServerEnabled": "true", "BK_httpServerPort": strconv.Itoa(c.BookieHTTPServicePort), + "BK_httpServerClass": "org.apache.bookkeeper.http.vertx.VertxHttpServer", "BK_ledgerDirectories": "bk/ledgers", "BK_indexDirectories": "bk/ledgers", "BK_journalDirectory": "bk/journal", }) + bookie.WithEnv(c.BookieEnv) bookies[name] = bookie } return bookies @@ -140,3 +145,11 @@ func (c *ClusterDef) getABookie() *test.BaseContainer { } return nil } + +func (c *ClusterDef) GetAllBookieContainerID() []string { + containerIDs := make([]string, 0) + for _, v := range c.bookieContainers { + containerIDs = append(containerIDs, v.GetContainerID()) + } + return containerIDs +} diff --git a/pkg/test/bookkeeper/cluster_spec.go b/pkg/test/bookkeeper/cluster_spec.go index 284a17621..5a228b4f6 100644 --- a/pkg/test/bookkeeper/cluster_spec.go +++ b/pkg/test/bookkeeper/cluster_spec.go @@ -17,7 +17,11 @@ package bookkeeper -import "github.com/streamnative/pulsarctl/pkg/test/bookkeeper/containers" +import ( + "strings" + + "github.com/streamnative/pulsarctl/pkg/test/bookkeeper/containers" +) type ClusterSpec struct { Image string @@ -26,11 +30,12 @@ type ClusterSpec struct { BookieServicePort int BookieHTTPServicePort int ZookeeperServicePort int + BookieEnv map[string]string } func DefaultClusterSpec() *ClusterSpec { return &ClusterSpec{ - Image: LatestImage, + Image: BookKeeper4_10_0, ClusterName: "default-bookie", NumBookies: 1, BookieServicePort: containers.DefaultBookieServicePort, @@ -38,3 +43,37 @@ func DefaultClusterSpec() *ClusterSpec { ZookeeperServicePort: containers.DefaultZookeeperServicePort, } } + +func GetClusterSpec(spec *ClusterSpec) *ClusterSpec { + newSpec := DefaultClusterSpec() + + if spec.NumBookies > 0 { + newSpec.NumBookies = spec.NumBookies + } + + if strings.TrimSpace(spec.ClusterName) != "" { + newSpec.ClusterName = spec.ClusterName + } + + if strings.TrimSpace(spec.Image) != "" { + newSpec.Image = spec.Image + } + + if spec.ZookeeperServicePort > 0 { + newSpec.ZookeeperServicePort = spec.ZookeeperServicePort + } + + if spec.BookieServicePort > 0 { + newSpec.BookieServicePort = spec.BookieServicePort + } + + if spec.BookieHTTPServicePort > 0 { + newSpec.BookieHTTPServicePort = spec.BookieHTTPServicePort + } + + if spec.BookieEnv != nil { + newSpec.BookieEnv = spec.BookieEnv + } + + return newSpec +} diff --git a/pkg/test/utils.go b/pkg/test/utils.go index af2d1864b..35e6ed1ac 100644 --- a/pkg/test/utils.go +++ b/pkg/test/utils.go @@ -19,6 +19,7 @@ package test import ( "context" + "os/exec" "strconv" "time" @@ -43,3 +44,10 @@ func NewNetwork(name string) (testcontainers.Network, error) { func RandomSuffix() string { return "-" + strconv.FormatInt(time.Now().Unix(), 10) } + +func ExecCmd(containerID string, cmd []string) (string, error) { + args := []string{"exec", containerID} + args = append(args, cmd...) + out, err := exec.Command("docker", args...).Output() + return string(out), err +}