From 851dc6c59641c12817792f373c286cdbbf6919f7 Mon Sep 17 00:00:00 2001 From: Ericwai <921223516@qq.com> Date: Mon, 4 Dec 2023 11:24:18 +0800 Subject: [PATCH] playbook(memcache): support instances and instances_sequence Signed-off-by: Ericwai <921223516@qq.com> docs: Quickly start developing curves based on cobra Signed-off-by: Liao PengFei <136953902+hdbdn77@users.noreply.github.com> Feature(cluster add): add deploy type parameter Signed-off-by: WhereAreBugs <2572414306@qq.com> Fix(typo): fix an spelling mistake in code. Signed-off-by: WhereAreBugs <2572414306@qq.com> Fix(deploy): sync version 2 tool config successed. Signed-off-by: caoxianfei1 change host to name in hosts moudle Signed-off-by: noobcoder Impove(enter): enter leader mds directly without id option Signed-off-by: lyp <1647564982@qq.com> --- cli/cli/cli.go | 16 +- cli/command/cluster/add.go | 30 ++- cli/command/cluster/import.go | 2 +- cli/command/deploy.go | 1 + cli/command/enter.go | 71 +++++- cli/command/hosts/list_test.go | 2 +- cli/command/hosts/playbook.go | 2 +- docs/en/curveadm-architecture-with-cobra.md | 258 ++++++++++++++++++++ docs/zh/curveadm-architecture-with-cobra.md | 258 ++++++++++++++++++++ internal/common/common.go | 1 + internal/configure/hosts/hc_get.go | 19 +- internal/configure/hosts/hc_item.go | 171 +++++++++++-- internal/configure/hosts/hosts.go | 239 ++++++++++++++++-- internal/configure/hosts/variables.go | 46 ++++ internal/configure/monitor.go | 2 +- internal/configure/topology/variables.go | 6 + internal/errno/errno.go | 9 +- internal/playbook/factory.go | 3 + internal/storage/sql.go | 13 +- internal/storage/storage.go | 32 ++- internal/task/step/file.go | 2 +- internal/task/task/common/client_status.go | 2 +- internal/task/task/common/collect_client.go | 2 +- internal/task/task/common/service_status.go | 80 ++++++ internal/tui/hosts.go | 6 +- internal/utils/common.go | 4 + playbook/memcached/hosts.yaml | 6 +- playbook/memcached/hosts_instances.yaml | 46 ++++ playbook/memcached/scripts/clean.sh | 16 ++ playbook/memcached/scripts/deploy.sh | 49 +++- playbook/memcached/scripts/start.sh | 5 + playbook/memcached/scripts/status.sh | 25 +- playbook/memcached/scripts/stop.sh | 12 + 33 files changed, 1345 insertions(+), 91 deletions(-) create mode 100644 docs/en/curveadm-architecture-with-cobra.md create mode 100644 docs/zh/curveadm-architecture-with-cobra.md create mode 100644 internal/configure/hosts/variables.go create mode 100644 playbook/memcached/hosts_instances.yaml diff --git a/cli/cli/cli.go b/cli/cli/cli.go index 48139b4a8..ba255b050 100644 --- a/cli/cli/cli.go +++ b/cli/cli/cli.go @@ -69,6 +69,7 @@ type CurveAdm struct { clusterName string // current cluster name clusterTopologyData string // cluster topology clusterPoolData string // cluster pool + clusterType string // cluster type like develop, production, etc. monitor storage.Monitor } @@ -195,7 +196,7 @@ func (curveadm *CurveAdm) init() error { curveadm.clusterTopologyData = cluster.Topology curveadm.clusterPoolData = cluster.Pool curveadm.monitor = monitor - + curveadm.clusterType = cluster.Type return nil } @@ -276,12 +277,13 @@ func (curveadm *CurveAdm) ClusterUUId() string { return curveadm.c func (curveadm *CurveAdm) ClusterName() string { return curveadm.clusterName } func (curveadm *CurveAdm) ClusterTopologyData() string { return curveadm.clusterTopologyData } func (curveadm *CurveAdm) ClusterPoolData() string { return curveadm.clusterPoolData } +func (curveadm *CurveAdm) ClusterType() string { return curveadm.clusterType } func (curveadm *CurveAdm) Monitor() storage.Monitor { return curveadm.monitor } -func (curveadm *CurveAdm) GetHost(host string) (*hosts.HostConfig, error) { +func (curveadm *CurveAdm) GetHost(name string) (*hosts.HostConfig, error) { if len(curveadm.Hosts()) == 0 { return nil, errno.ERR_HOST_NOT_FOUND. - F("host: %s", host) + F("host: %s", name) } hcs, err := hosts.ParseHosts(curveadm.Hosts()) if err != nil { @@ -289,12 +291,12 @@ func (curveadm *CurveAdm) GetHost(host string) (*hosts.HostConfig, error) { } for _, hc := range hcs { - if hc.GetHost() == host { + if hc.GetName() == name { return hc, nil } } return nil, errno.ERR_HOST_NOT_FOUND. - F("host: %s", host) + F("host: %s", name) } func (curveadm *CurveAdm) ParseTopologyData(data string) ([]*topology.DeployConfig, error) { @@ -304,7 +306,7 @@ func (curveadm *CurveAdm) ParseTopologyData(data string) ([]*topology.DeployConf return nil, err } for _, hc := range hcs { - ctx.Add(hc.GetHost(), hc.GetHostname()) + ctx.Add(hc.GetName(), hc.GetHostname()) } dcs, err := topology.ParseTopology(data, ctx) @@ -464,7 +466,7 @@ func (curveadm *CurveAdm) DiffTopology(data1, data2 string) ([]topology.Topology return nil, err } for _, hc := range hcs { - ctx.Add(hc.GetHost(), hc.GetHostname()) + ctx.Add(hc.GetName(), hc.GetHostname()) } if len(data1) == 0 { diff --git a/cli/command/cluster/add.go b/cli/command/cluster/add.go index 40d413a59..2504d94de 100644 --- a/cli/command/cluster/add.go +++ b/cli/command/cluster/add.go @@ -47,12 +47,18 @@ var ( CHECK_TOPOLOGY_PLAYBOOK_STEPS = []int{ playbook.CHECK_TOPOLOGY, } + SUPPORTED_DEPLOY_TYPES = []string{ + "production", + "test", + "develop", + } ) type addOptions struct { name string - descriotion string + description string filename string + deployType string } func NewAddCommand(curveadm *cli.CurveAdm) *cobra.Command { @@ -63,6 +69,9 @@ func NewAddCommand(curveadm *cli.CurveAdm) *cobra.Command { Short: "Add cluster", Args: utils.ExactArgs(1), Example: ADD_EXAMPLE, + PreRunE: func(cmd *cobra.Command, args []string) error { + return checkAddOptions(cmd) + }, RunE: func(cmd *cobra.Command, args []string) error { options.name = args[0] return runAdd(curveadm, options) @@ -71,9 +80,9 @@ func NewAddCommand(curveadm *cli.CurveAdm) *cobra.Command { } flags := cmd.Flags() - flags.StringVarP(&options.descriotion, "description", "m", "", "Description for cluster") + flags.StringVarP(&options.description, "description", "m", "", "Description for cluster") flags.StringVarP(&options.filename, "topology", "f", "", "Specify the path of topology file") - + flags.StringVar(&options.deployType, "type", "develop", "Specify the type of cluster") return cmd } @@ -134,6 +143,19 @@ func checkTopology(curveadm *cli.CurveAdm, data string, options addOptions) erro return pb.Run() } +func checkAddOptions(cmd *cobra.Command) error { + deployType, err := cmd.Flags().GetString("deploy-type") + if err != nil { + return err + } + for _, t := range SUPPORTED_DEPLOY_TYPES { + if deployType == t { + return nil + } + } + return errno.ERR_UNSUPPORT_DEPLOY_TYPE.F("deploy type: %s", deployType) +} + func runAdd(curveadm *cli.CurveAdm, options addOptions) error { // 1) check wether cluster already exist name := options.name @@ -163,7 +185,7 @@ func runAdd(curveadm *cli.CurveAdm, options addOptions) error { // 4) insert cluster (with topology) into database uuid := uuid.NewString() - err = storage.InsertCluster(name, uuid, options.descriotion, data) + err = storage.InsertCluster(name, uuid, options.description, data, options.deployType) if err != nil { return errno.ERR_INSERT_CLUSTER_FAILED.E(err) } diff --git a/cli/command/cluster/import.go b/cli/command/cluster/import.go index e4f8de0eb..e0c4f6b2f 100644 --- a/cli/command/cluster/import.go +++ b/cli/command/cluster/import.go @@ -100,7 +100,7 @@ func importCluster(storage *storage.Storage, dbfile, name string) error { } // insert cluster - err = storage.InsertCluster(name, cluster.UUId, cluster.Description, cluster.Topology) + err = storage.InsertCluster(name, cluster.UUId, cluster.Description, cluster.Topology, cluster.Type) if err != nil { return err } diff --git a/cli/command/deploy.go b/cli/command/deploy.go index a193d88f3..52ab5d3f1 100644 --- a/cli/command/deploy.go +++ b/cli/command/deploy.go @@ -302,6 +302,7 @@ func displayDeployTitle(curveadm *cli.CurveAdm, dcs []*topology.DeployConfig) { curveadm.WriteOutln("Cluster Name : %s", curveadm.ClusterName()) curveadm.WriteOutln("Cluster Kind : %s", dcs[0].GetKind()) curveadm.WriteOutln("Cluster Services: %s", serviceStats(dcs)) + curveadm.WriteOutln("Cluster Type : %s", curveadm.ClusterType()) curveadm.WriteOutln("") } diff --git a/cli/command/enter.go b/cli/command/enter.go index e7d54c10d..2f8e5706e 100644 --- a/cli/command/enter.go +++ b/cli/command/enter.go @@ -26,13 +26,20 @@ package command import ( "github.com/opencurve/curveadm/cli/cli" + comm "github.com/opencurve/curveadm/internal/common" "github.com/opencurve/curveadm/internal/configure/topology" "github.com/opencurve/curveadm/internal/errno" + "github.com/opencurve/curveadm/internal/playbook" + "github.com/opencurve/curveadm/internal/task/task/common" "github.com/opencurve/curveadm/internal/tools" "github.com/opencurve/curveadm/internal/utils" "github.com/spf13/cobra" ) +var ( + ATTACH_LEADER_OR_RANDOM_CONTAINER = []int{playbook.ATTACH_LEADER_OR_RANDOM_CONTAINER} +) + type enterOptions struct { id string } @@ -43,8 +50,11 @@ func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command { cmd := &cobra.Command{ Use: "enter ID", Short: "Enter service container", - Args: utils.ExactArgs(1), + Args: utils.RequiresMaxArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return nil + } options.id = args[0] return curveadm.CheckId(options.id) }, @@ -57,6 +67,51 @@ func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command { return cmd } +func genLeaderOrRandomPlaybook(curveadm *cli.CurveAdm, + dcs []*topology.DeployConfig) (*playbook.Playbook, error) { + if len(dcs) == 0 { + return nil, errno.ERR_NO_SERVICES_MATCHED + } + + steps := ATTACH_LEADER_OR_RANDOM_CONTAINER + pb := playbook.NewPlaybook(curveadm) + for _, step := range steps { + pb.AddStep(&playbook.PlaybookStep{ + Type: step, + Configs: dcs, + ExecOptions: playbook.ExecOptions{ + SilentSubBar: true, + SilentMainBar: true, + SkipError: true, + }, + }) + } + return pb, nil +} + +func checkOrGetId(curveadm *cli.CurveAdm, dcs []*topology.DeployConfig, options enterOptions) (string, error) { + id := options.id + if id != "" { + return id, nil + } + pb, err := genLeaderOrRandomPlaybook(curveadm, dcs) + if err != nil { + return "", err + } + // run playground + err = pb.Run() + if err != nil { + return "", err + } + // get leader or random container id + value := curveadm.MemStorage().Get(comm.LEADER_OR_RANDOM_ID) + if value == nil { + return "", errno.ERR_NO_LEADER_OR_RANDOM_CONTAINER_FOUND + } + id = value.(common.Leader0rRandom).Id + return id, nil +} + func runEnter(curveadm *cli.CurveAdm, options enterOptions) error { // 1) parse cluster topology dcs, err := curveadm.ParseTopology() @@ -64,9 +119,15 @@ func runEnter(curveadm *cli.CurveAdm, options enterOptions) error { return err } - // 2) filter service + // 2) check id options + id, err := checkOrGetId(curveadm, dcs, options) + if err != nil { + return err + } + + // 3) filter service dcs = curveadm.FilterDeployConfig(dcs, topology.FilterOption{ - Id: options.id, + Id: id, Role: "*", Host: "*", }) @@ -74,7 +135,7 @@ func runEnter(curveadm *cli.CurveAdm, options enterOptions) error { return errno.ERR_NO_SERVICES_MATCHED } - // 3) get container id + // 4) get container id dc := dcs[0] serviceId := curveadm.GetServiceId(dc.GetId()) containerId, err := curveadm.GetContainerId(serviceId) @@ -82,7 +143,7 @@ func runEnter(curveadm *cli.CurveAdm, options enterOptions) error { return err } - // 4) attch remote container + // 5) attach remote container home := dc.GetProjectLayout().ServiceRootDir return tools.AttachRemoteContainer(curveadm, dc.GetHost(), containerId, home) } diff --git a/cli/command/hosts/list_test.go b/cli/command/hosts/list_test.go index 2f52ba707..a02041e7b 100644 --- a/cli/command/hosts/list_test.go +++ b/cli/command/hosts/list_test.go @@ -37,7 +37,7 @@ func run(t *testing.T, data string, labels []string, out []string) { assert.Nil(err) assert.Equal(len(hcs), len(out)) for i, hc := range hcs { - assert.Equal(hc.GetHost(), out[i]) + assert.Equal(hc.GetName(), out[i]) } } diff --git a/cli/command/hosts/playbook.go b/cli/command/hosts/playbook.go index 8bb1f0336..7feab82ee 100644 --- a/cli/command/hosts/playbook.go +++ b/cli/command/hosts/playbook.go @@ -91,7 +91,7 @@ func NewPlaybookCommand(curveadm *cli.CurveAdm) *cobra.Command { func execute(curveadm *cli.CurveAdm, options playbookOptions, idx int, hc *hosts.HostConfig) { defer func() { wg.Done() }() - name := hc.GetHost() + name := hc.GetName() target := path.Join("/tmp", utils.RandString(8)) err := tools.Scp(curveadm, name, options.filepath, target) if err != nil { diff --git a/docs/en/curveadm-architecture-with-cobra.md b/docs/en/curveadm-architecture-with-cobra.md new file mode 100644 index 000000000..6b5758b9a --- /dev/null +++ b/docs/en/curveadm-architecture-with-cobra.md @@ -0,0 +1,258 @@ +# Curveadm CLI Development + +## Cobra library + +Curveadm CLI is developed based on the [Cobra](https://github.com/spf13/cobra) library, a Go language library for creating CLI command line programs. + +### Basic use of Cobra + +Create a root command using Cobra (print `root command` on the command line): +``` +package main + +import ( + "fmt" + "os" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with + love by spf13 and friends in Go. + Complete documentation is available at https://gohugo.io/documentation/`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("root command") + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func main() { + rootCmd.Execute() +} +``` +- Use field sets the name of the command +- Short field sets a short description +- Long field setting detailed description +- The Run field sets the function when executing the command + +For more details on `Command` object fields and usage, see [Cobra library--command.go](https://github.com/spf13/cobra/blob/main/command.go). + +### flag usage + +Cobra supports parsing of custom parameters: +``` +cmd.PersistentFlags().BoolP("help", "h", false, "Print usage") +cmd.Flags().BoolVarP(&options.debug, "debug", "d", false, "Print debug information") +``` +PersistentFlags() is used for global flags and can be used by the current command and its subcommands. + +Flags() is used to define local flags, only for the current command. + +For more details on the `flag` function and usage, see [Cobra library--command.go](https://github.com/spf13/cobra/blob/main/command.go) and [pflag library](https:// github.com/spf13/pflag). + +### hook function + +Cobra's Command object supports custom hook functions (PreRun and PostRun fields), and the hook function is run before and after the `run` command is executed. As follows: +``` +cmd := &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, +} +``` +The hook function will be executed in the order of (`PersistentPreRun`, `PreRun`, `Run`, `PostRun`, `PersistentPostRun`). Note: If the subcommand does not set `Persistent*Run`, it will automatically inherit the function definition of the parent command. + +### Subcommands +cobra allows nested commands, through the `AddCommand` function of the current command object. As follows: +``` +rootCmd.AddCommand(versionCmd) +``` +The recommended hierarchical command nesting structure is as follows: +``` +├── cmd +│ ├── root.go +│ └── sub1 +│ ├── sub1.go +│ └── sub2 +│ ├── leafA.go +│ └── leafB.go +└── main.go +``` +Add the commands defined in leafA.go and leafB.go to the sub2 command. + +Add the commands defined in sub2.go to the sub1 command. + +Add the commands defined in sub1.go to the root command. + +The main structure of the final command call is as follows: +``` +root options +root sub1 options +root sub1 sub2 options +root sub1 sub2 leafA options +root sub1 sub2 leafB options +``` + + +## curveadm cli project structure +``` +cli +├── cli +│ ├── cli.go +│ └── version.go +├── command +│  ├── audit.go +│ ├── clean.go +│ ├── client +│ ├── cluster +│ ... +└── curveadm.go +``` +The `cli.go` in the cli folder defines the `curveadm` object and related methods, which run through all curveadm cli command development. +``` +type CurveAdm struct { + rootDir string + dataDir string + ... +} +func NewCurveAdm() (*CurveAdm, error) { + curveadm := &CurveAdm{ + rootDir: rootDir, + ... + } + ... + return curveadm, nil +} +``` + +The command directory stores command implementations at each level. +``` +├── audit.go +├── client +│ ├── cmd.go +│ ├── enter.go +│ └── unmap.go +├── cluster +│ ├── add.go +│ ├── cmd.go +├── cmd.go +├── deploy.go +``` +In curveadm cli, the root command of each layer is defined in `cmd.go`. The root command is only responsible for registering subcommands and providing help information, and does not participate in actual work operations. +``` +cli\command\cmd.go + +func addSubCommands(cmd *cobra.Command, curveadm *cli.CurveAdm) { + cmd.AddCommand( + client.NewClientCommand(curveadm), // curveadm client + ... + ) +} +func NewCurveAdmCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "curveadm [OPTIONS] COMMAND [ARGS...]", + ... + } + ... + addSubCommands(cmd, curveadm) + return cmd +} +################################################ ############## +cli\command\client\cmd.go + +func NewClientCommand(curveadm *cli.CurveAdm) *cobra.Command { + cmd := &cobra.Command{ + Use: "client", + ... + } + cmd.AddCommand( + NewMapCommand(curveadm), + ... + ) + ... +} +################################################ ############## +cli\command\client\enter.go + +func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "enter ID", + ... + } + ... +} +``` +The final call structure of the enter command is as follows: +``` +curveadm client enter ID +``` + +curveadm.go defines the execution function of the `curveadm` root command and performs related audit work. +``` +func Execute() { + curveadm, err := cli.NewCurveAdm() + ... + id := curveadm.PreAudit(time.Now(), os.Args[1:]) + cmd := command.NewCurveAdmCommand(curveadm) + err = cmd.Execute() + curveadm.PostAudit(id, err) + if err != nil { + os.Exit(1) + } +} +``` + +The entrance to the `curveadm` main program is under the [curveadm folder](https://github.com/opencurve/curveadm/tree/develop/cmd/curveadm). You can execute the operation and execution of `curveadm` in this directory. compile +``` +func main() { + cli.Execute() +} +``` + +### curveadm general tools +For command development of curveadm cli, curveadm provides general tools, such as: + +- cliutil.NoArgs: used to determine whether the command does not contain parameters + +- cliutil.ShowHelp: used to display help information when the command is run + +In the [curveadm/internal directory](https://github.com/opencurve/curveadm/tree/develop/internal). As follows: +``` +import ( + cliutil "github.com/opencurve/curveadm/internal/utils" + ... +) + +cmd := &cobra.Command{ + Use: "client", + Args: cliutil.NoArgs, + RunE: cliutil.ShowHelp(curveadm.Err()), +} +``` +`cliutil.NoArgs` specifies that the `curveadm client` command does not contain any arguments (except subcommands); the `cliutil.ShowHelp` function displays the defined help options when running the `curveadm client` command directly. + +For more common commands and usage, please refer to [internal folder](https://github.com/opencurve/curveadm/tree/develop/internal). \ No newline at end of file diff --git a/docs/zh/curveadm-architecture-with-cobra.md b/docs/zh/curveadm-architecture-with-cobra.md new file mode 100644 index 000000000..5cb5f8fff --- /dev/null +++ b/docs/zh/curveadm-architecture-with-cobra.md @@ -0,0 +1,258 @@ +# Curveadm CLI 开发 + +## Cobra库 + +Curveadm CLI是基于[Cobra](https://github.com/spf13/cobra)库(一个用于创建CLI命令行程序的Go语言库)开发的。 + +### Cobra的基本使用 + +使用Cobra创建一个根命令(在命令行打印`root command`): +``` +package main + +import ( + "fmt" + "os" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with + love by spf13 and friends in Go. + Complete documentation is available at https://gohugo.io/documentation/`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("root command") + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func main() { + rootCmd.Execute() +} +``` +- Use字段设置命令的名字 +- Short字段设置简短描述 +- Long字段设置详细描述 +- Run字段设置执行该命令时的函数 + +更多`Command`对象字段及用法详见[Cobra库--command.go](https://github.com/spf13/cobra/blob/main/command.go)。 + +### flag使用 + +Cobra支持自定义参数的解析: +``` +cmd.PersistentFlags().BoolP("help", "h", false, "Print usage") +cmd.Flags().BoolVarP(&options.debug, "debug", "d", false, "Print debug information") +``` +PersistentFlags()用于全局flag,可以被当前命令及其子命令使用。 + +Flags()则用于定义本地flag,仅用于当前命令。 + +更多`flag`函数及用法详见[Cobra库--command.go](https://github.com/spf13/cobra/blob/main/command.go)和[pflag库](https://github.com/spf13/pflag)。 + +### hook函数 + +cobra的Command对象支持自定义hook函数(PreRun和PostRun字段),在`run`命令执行前后运行hook函数。如下所示: +``` +cmd := &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, +} +``` +hook函数会按(`PersistentPreRun`、`PreRun`、`Run`、`PostRun`、`PersistentPostRun`)的顺序依次执行。注意:如果子命令没有设置`Persistent*Run`,则会自动继承父命令的函数定义。 + +### 子命令 +cobra允许嵌套命令,通过当前命令对象的`AddCommand`函数。如下所示: +``` +rootCmd.AddCommand(versionCmd) +``` +推荐的层级命令嵌套结构如下: +``` +├── cmd +│   ├── root.go +│   └── sub1 +│   ├── sub1.go +│   └── sub2 +│   ├── leafA.go +│   └── leafB.go +└── main.go +``` +将 leafA.go 和 leafB.go 中定义的命令添加到 sub2 命令中。 + +将 sub2.go 中定义的命令添加到 sub1 命令中。 + +将 sub1.go 中定义的命令添加到 root 命令中。 + +最终的命令调用的主体结构如下: +``` +root options +root sub1 options +root sub1 sub2 options +root sub1 sub2 leafA options +root sub1 sub2 leafB options +``` + + +## curveadm cli项目结构 +``` +cli +├── cli +│   ├── cli.go +│   └── version.go +├── command +│   ├── audit.go +│   ├── clean.go +│   ├── client +│   ├── cluster +│   ... +└── curveadm.go +``` +cli 文件夹的`cli.go`定义了`curveadm`对象及相关方法,贯穿所有curveadm cli的命令开发。 +``` +type CurveAdm struct { + rootDir string + dataDir string + ... +} +func NewCurveAdm() (*CurveAdm, error) { + curveadm := &CurveAdm{ + rootDir: rootDir, + ... + } + ... + return curveadm, nil +} +``` + +command 目录中存放各层级命令实现。 +``` +├── audit.go +├── client +│   ├── cmd.go +│   ├── enter.go +│   └── unmap.go +├── cluster +│   ├── add.go +│   ├── cmd.go +├── cmd.go +├── deploy.go +``` +在curveadm cli中,每层的根命令都在`cmd.go`定义。根命令只负责注册子命令以及提供帮助信息,并不参与实际工作操作。 +``` +cli\command\cmd.go + +func addSubCommands(cmd *cobra.Command, curveadm *cli.CurveAdm) { + cmd.AddCommand( + client.NewClientCommand(curveadm), // curveadm client + ... + ) +} +func NewCurveAdmCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "curveadm [OPTIONS] COMMAND [ARGS...]", + ... + } + ... + addSubCommands(cmd, curveadm) + return cmd +} +################################################################ +cli\command\client\cmd.go + +func NewClientCommand(curveadm *cli.CurveAdm) *cobra.Command { + cmd := &cobra.Command{ + Use: "client", + ... + } + cmd.AddCommand( + NewMapCommand(curveadm), + ... + ) + ... +} +################################################################ +cli\command\client\enter.go + +func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "enter ID", + ... + } + ... +} +``` +最终enter命令的调用结构如下: +``` +curveadm client enter ID +``` + +curveadm.go 定义了`curveadm` 根命令的执行函数同时执行相关审计工作。 +``` +func Execute() { + curveadm, err := cli.NewCurveAdm() + ... + id := curveadm.PreAudit(time.Now(), os.Args[1:]) + cmd := command.NewCurveAdmCommand(curveadm) + err = cmd.Execute() + curveadm.PostAudit(id, err) + if err != nil { + os.Exit(1) + } +} +``` + +`curveadm` 主程序的入口则是在[curveadm文件夹下](https://github.com/opencurve/curveadm/tree/develop/cmd/curveadm),可以在该目录下执行`curveadm`的运行及编译 +``` +func main() { + cli.Execute() +} +``` + +### curveadm 通用工具 +对于curveadm cli的命令开发,curveadm 提供了通用工具,如: + +- cliutil.NoArgs:用于判断命令是否不包含参数 + +- cliutil.ShowHelp:用于在命令运行时展示帮助信息 + +在[curveadm/internal目录下](https://github.com/opencurve/curveadm/tree/develop/internal)。如下所示: +``` +import ( + cliutil "github.com/opencurve/curveadm/internal/utils" + ... +) + +cmd := &cobra.Command{ + Use: "client", + Args: cliutil.NoArgs, + RunE: cliutil.ShowHelp(curveadm.Err()), +} +``` +`cliutil.NoArgs`指明`curveadm client`命令不包含任何参数(子命令除外);`cliutil.ShowHelp`函数在直接运行`curveadm client`命令时展示定义的帮助选项。 + +更多通用命令及用法请参考[internal文件夹](https://github.com/opencurve/curveadm/tree/develop/internal)。 \ No newline at end of file diff --git a/internal/common/common.go b/internal/common/common.go index 8e67c6485..ff726c663 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -49,6 +49,7 @@ const ( POOLSET = "poolset" POOLSET_DISK_TYPE = "poolset-disktype" KEY_NUMBER_OF_CHUNKSERVER = "NUMBER_OF_CHUNKSERVER" + LEADER_OR_RANDOM_ID = "LEADER_OR_RANDOM_ID" // format KEY_ALL_FORMAT_STATUS = "ALL_FORMAT_STATUS" diff --git a/internal/configure/hosts/hc_get.go b/internal/configure/hosts/hc_get.go index c7c0c6a07..92eab9435 100644 --- a/internal/configure/hosts/hc_get.go +++ b/internal/configure/hosts/hc_get.go @@ -25,25 +25,25 @@ package hosts import ( - comm "github.com/opencurve/curveadm/internal/configure/common" "github.com/opencurve/curveadm/internal/configure/curveadm" "github.com/opencurve/curveadm/internal/utils" "github.com/opencurve/curveadm/pkg/module" + "github.com/opencurve/curveadm/pkg/variable" ) -func (hc *HostConfig) get(i *comm.Item) interface{} { +func (hc *HostConfig) get(i *item) interface{} { if v, ok := hc.config[i.Key()]; ok { return v } - defaultValue := i.DefaultValue() + defaultValue := i.defaultValue if defaultValue != nil && utils.IsFunc(defaultValue) { return defaultValue.(func(*HostConfig) interface{})(hc) } return defaultValue } -func (hc *HostConfig) getString(i *comm.Item) string { +func (hc *HostConfig) getString(i *item) string { v := hc.get(i) if v == nil { return "" @@ -51,7 +51,7 @@ func (hc *HostConfig) getString(i *comm.Item) string { return v.(string) } -func (hc *HostConfig) getInt(i *comm.Item) int { +func (hc *HostConfig) getInt(i *item) int { v := hc.get(i) if v == nil { return 0 @@ -59,7 +59,7 @@ func (hc *HostConfig) getInt(i *comm.Item) int { return v.(int) } -func (hc *HostConfig) getBool(i *comm.Item) bool { +func (hc *HostConfig) getBool(i *item) bool { v := hc.get(i) if v == nil { return false @@ -67,7 +67,7 @@ func (hc *HostConfig) getBool(i *comm.Item) bool { return v.(bool) } -func (hc *HostConfig) GetHost() string { return hc.getString(CONFIG_HOST) } +func (hc *HostConfig) GetName() string { return hc.getString(CONFIG_NAME) } func (hc *HostConfig) GetHostname() string { return hc.getString(CONFIG_HOSTNAME) } func (hc *HostConfig) GetSSHHostname() string { return hc.getString(CONFIG_SSH_HOSTNAME) } func (hc *HostConfig) GetSSHPort() int { return hc.getInt(CONFIG_SSH_PORT) } @@ -77,6 +77,10 @@ func (hc *HostConfig) GetBecomeUser() string { return hc.getString(CONFIG_BE func (hc *HostConfig) GetLabels() []string { return hc.labels } func (hc *HostConfig) GetEnvs() []string { return hc.envs } +func (hc *HostConfig) GetInstances() int { return hc.instances } +func (hc *HostConfig) GetInstancesSequence() int { return hc.instancesSequence } +func (hc *HostConfig) GetVariables() *variable.Variables { return hc.variables } + func (hc *HostConfig) GetUser() string { user := hc.getString(CONFIG_USER) if user == "${user}" { @@ -84,7 +88,6 @@ func (hc *HostConfig) GetUser() string { } return user } - func (hc *HostConfig) GetSSHConfig() *module.SSHConfig { hostname := hc.GetSSHHostname() if len(hostname) == 0 { diff --git a/internal/configure/hosts/hc_item.go b/internal/configure/hosts/hc_item.go index 6900cbd63..960894e7f 100644 --- a/internal/configure/hosts/hc_item.go +++ b/internal/configure/hosts/hc_item.go @@ -26,75 +26,210 @@ package hosts import ( "fmt" + "github.com/opencurve/curveadm/internal/errno" - comm "github.com/opencurve/curveadm/internal/configure/common" "github.com/opencurve/curveadm/internal/utils" ) const ( + REQUIRE_ANY = iota + REQUIRE_INT + REQUIRE_STRING + REQUIRE_BOOL + REQUIRE_POSITIVE_INTEGER + REQUIRE_STRING_SLICE + DEFAULT_SSH_PORT = 22 ) +type ( + // config item + item struct { + key string + require int + exclude bool // exclude for service config + defaultValue interface{} // nil means no default value + } + + itemSet struct { + items []*item + key2item map[string]*item + } +) + var ( - itemset = comm.NewItemSet() + itemset = &itemSet{ + items: []*item{}, + key2item: map[string]*item{}, + } - CONFIG_HOST = itemset.Insert( + CONFIG_HOST = itemset.insert( "host", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) - CONFIG_HOSTNAME = itemset.Insert( + CONFIG_NAME = itemset.insert( + "name", + REQUIRE_STRING, + false, + func(hc *HostConfig) interface{} { + return hc.getString(CONFIG_HOST) + }, + ) + + CONFIG_HOSTNAME = itemset.insert( "hostname", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) - CONFIG_SSH_HOSTNAME = itemset.Insert( + CONFIG_SSH_HOSTNAME = itemset.insert( "ssh_hostname", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) - CONFIG_USER = itemset.Insert( + CONFIG_USER = itemset.insert( "user", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, func(hc *HostConfig) interface{} { return utils.GetCurrentUser() }, ) - CONFIG_SSH_PORT = itemset.Insert( + CONFIG_SSH_PORT = itemset.insert( "ssh_port", - comm.REQUIRE_POSITIVE_INTEGER, + REQUIRE_POSITIVE_INTEGER, false, DEFAULT_SSH_PORT, ) - CONFIG_PRIVATE_CONFIG_FILE = itemset.Insert( + CONFIG_PRIVATE_CONFIG_FILE = itemset.insert( "private_key_file", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, func(hc *HostConfig) interface{} { return fmt.Sprintf("%s/.ssh/id_rsa", utils.GetCurrentHomeDir()) }, ) - CONFIG_FORWARD_AGENT = itemset.Insert( + CONFIG_FORWARD_AGENT = itemset.insert( "forward_agent", - comm.REQUIRE_BOOL, + REQUIRE_BOOL, false, false, ) - CONFIG_BECOME_USER = itemset.Insert( + CONFIG_BECOME_USER = itemset.insert( "become_user", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) ) + +func convertSlice[T any](key, value any) ([]T, error) { + var slice []T + if !utils.IsAnySlice(value) || len(value.([]any)) == 0 { + return slice, errno.ERR_CONFIGURE_VALUE_REQUIRES_NONEMPTY_SLICE + } + anySlice := value.([]any) + switch anySlice[0].(type) { + case T: + for _, element := range anySlice { + slice = append(slice, element.(T)) + } + default: + return slice, errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", key, value) + } + + return slice, nil +} + +func (i *item) Key() string { + return i.key +} + +func (itemset *itemSet) insert(key string, require int, exclude bool, defaultValue interface{}) *item { + i := &item{key, require, exclude, defaultValue} + itemset.key2item[key] = i + itemset.items = append(itemset.items, i) + return i +} + +func (itemset *itemSet) get(key string) *item { + return itemset.key2item[key] +} + +func (itemset *itemSet) getAll() []*item { + return itemset.items +} + +func (itemset *itemSet) Build(key string, value interface{}) (interface{}, error) { + item := itemset.get(key) + if item == nil { + return value, nil + } + + v, ok := utils.All2Str(value) + if !ok { + if !utils.IsAnySlice(value) { + return nil, errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", key, value) + } + } + + switch item.require { + case REQUIRE_ANY: + // do nothing + + case REQUIRE_STRING: + if len(v) == 0 { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_NON_EMPTY_STRING. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_INT: + if v, ok := utils.Str2Int(v); !ok { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_POSITIVE_INTEGER: + if v, ok := utils.Str2Int(v); !ok { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", key, value) + } else if v <= 0 { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_BOOL: + if v, ok := utils.Str2Bool(v); !ok { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_BOOL. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_STRING_SLICE: + return convertSlice[string](key, value) + + default: + // do nothing + } + + return value, nil +} diff --git a/internal/configure/hosts/hosts.go b/internal/configure/hosts/hosts.go index c8fa86011..f1ca5ca75 100644 --- a/internal/configure/hosts/hosts.go +++ b/internal/configure/hosts/hosts.go @@ -26,18 +26,21 @@ package hosts import ( "bytes" + "github.com/opencurve/curveadm/pkg/variable" "strings" "github.com/opencurve/curveadm/internal/build" "github.com/opencurve/curveadm/internal/configure/os" "github.com/opencurve/curveadm/internal/errno" "github.com/opencurve/curveadm/internal/utils" + log "github.com/opencurve/curveadm/pkg/log/glg" "github.com/spf13/viper" ) const ( - KEY_LABELS = "labels" - KEY_ENVS = "envs" + KEY_LABELS = "labels" + KEY_ENVS = "envs" + KEY_INSTANCES = "instances" PERMISSIONS_600 = 384 // -rw------- (256 + 128 = 384) ) @@ -49,10 +52,16 @@ type ( } HostConfig struct { - sequence int - config map[string]interface{} - labels []string - envs []string + sequence int + config map[string]interface{} + labels []string + envs []string + variables *variable.Variables + //instances and instancesSequence only used in the memcached deploy + //instances is the num of memcached servers will be deployed in the same host + instances int + //instancesSquence is the sequence num of memcached servers in the same host + instancesSequence int } ) @@ -111,6 +120,80 @@ func (hc *HostConfig) convertEnvs() error { return nil } +// read the instances value from hc.config +func (hc *HostConfig) convertInstances() error { + value := hc.config[KEY_INSTANCES] + v, ok := utils.All2Str(value) + if !ok { + if !utils.IsAnySlice(value) { + return errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("hosts[%d].%s = %v", hc.sequence, KEY_INSTANCES, value) + } + } + if v, ok := utils.Str2Int(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("hosts[%d].%s = %v", hc.sequence, KEY_INSTANCES, value) + } else if v <= 0 { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER. + F("hosts[%d].%s = %v", hc.sequence, KEY_INSTANCES, value) + } else { + hc.instances = v + return nil + } +} + +// convert config item to its require type after rendering, +// return error if convert failed +func (hc *HostConfig) convert() error { + for _, item := range itemset.getAll() { + k := item.key + value := hc.get(item) // return config value or default value + if value == nil { + continue + } + v, ok := utils.All2Str(value) + if !ok { + return errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", k, value) + } + + switch item.require { + case REQUIRE_ANY: + // do nothing + case REQUIRE_INT: + if intv, ok := utils.Str2Int(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", k, value) + } else { + hc.config[k] = intv + } + case REQUIRE_STRING: + if len(v) == 0 { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_NON_EMPTY_STRING. + F("%s: %v", k, value) + } + case REQUIRE_BOOL: + if boolv, ok := utils.Str2Bool(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_BOOL. + F("%s: %v", k, value) + } else { + hc.config[k] = boolv + } + case REQUIRE_POSITIVE_INTEGER: + if intv, ok := utils.Str2Int(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", k, value) + } else if intv <= 0 { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER. + F("%s: %v", k, value) + } else { + hc.config[k] = intv + } + } + } + return nil +} + func (hc *HostConfig) Build() error { for key, value := range hc.config { if key == KEY_LABELS { // convert labels @@ -123,11 +206,17 @@ func (hc *HostConfig) Build() error { if err := hc.convertEnvs(); err != nil { return err } - hc.config[key] = nil // delete labels section + hc.config[key] = nil // delete envs section + continue + } else if key == KEY_INSTANCES { // convert instances + if err := hc.convertInstances(); err != nil { + return err + } + hc.config[key] = nil // delete instances section continue } - if itemset.Get(key) == nil { + if itemset.get(key) == nil { return errno.ERR_UNSUPPORT_HOSTS_CONFIGURE_ITEM. F("hosts[%d].%s = %v", hc.sequence, key, value) } @@ -141,9 +230,9 @@ func (hc *HostConfig) Build() error { } privateKeyFile := hc.GetPrivateKeyFile() - if len(hc.GetHost()) == 0 { + if len(hc.GetName()) == 0 { return errno.ERR_HOST_FIELD_MISSING. - F("hosts[%d].host = nil", hc.sequence) + F("hosts[%d].host/name = nil", hc.sequence) } else if len(hc.GetHostname()) == 0 { return errno.ERR_HOSTNAME_FIELD_MISSING. F("hosts[%d].hostname = nil", hc.sequence) @@ -158,7 +247,7 @@ func (hc *HostConfig) Build() error { F("hosts[%d].private_key_file = %s", hc.sequence, privateKeyFile) } - if hc.GetForwardAgent() == false { + if !hc.GetForwardAgent() { if !utils.PathExist(privateKeyFile) { return errno.ERR_PRIVATE_KEY_FILE_NOT_EXIST. F("%s: no such file", privateKeyFile) @@ -170,11 +259,108 @@ func (hc *HostConfig) Build() error { return nil } +// "PORT=112${instancesSquence}" -> "PORT=11201" +func (hc *HostConfig) renderVariables() error { + //0. get vars + vars := hc.GetVariables() + if err := vars.Build(); err != nil { + log.Error("Build variables failed", + log.Field("error", err)) + return errno.ERR_RESOLVE_VARIABLE_FAILED.E(err) + } + //1. all config to str + for k, v := range hc.config { + if v == nil { + continue + } + if strv, ok := utils.All2Str(v); ok { + hc.config[k] = strv + } else { + return errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", k, v) + } + } + //2. rendering + //render labels + for i := range hc.labels { + err := func(value *string) error { + realValue, err := vars.Rendering(*value) + if err != nil { + return err + } + *value = realValue + return nil + }(&hc.labels[i]) + if err != nil { + return errno.ERR_RENDERING_VARIABLE_FAILED.E(err) + } + } + //render envs + for i := range hc.envs { + err := func(value *string) error { + realValue, err := vars.Rendering(*value) + if err != nil { + return err + } + *value = realValue + return nil + }(&hc.envs[i]) + if err != nil { + return errno.ERR_RENDERING_VARIABLE_FAILED.E(err) + } + } + //render config + for k, v := range hc.config { + if v == nil { + continue + } + realv, err := vars.Rendering(v.(string)) + if err != nil { + return errno.ERR_RENDERING_VARIABLE_FAILED.E(err) + } + hc.config[k] = realv + build.DEBUG(build.DEBUG_TOPOLOGY, + build.Field{Key: k, Value: v}, + build.Field{Key: k, Value: realv}) + } + //3. convert config item to its require type after rendering, + // return error if convert failed + return hc.convert() +} + func NewHostConfig(sequence int, config map[string]interface{}) *HostConfig { + vars := variable.NewVariables() + return &HostConfig{ - sequence: sequence, - config: config, - labels: []string{}, + sequence: sequence, + config: config, + labels: []string{}, + envs: []string{}, + variables: vars, + //instances and instancesSquence only used in the memcached deploy + instances: 1, + instancesSequence: 1, + } +} + +// deepcopy a HostConfig with instancesSquence and return it (new variables) +func copyHostConfig(src *HostConfig, instancesSquence int) *HostConfig { + //deepcopy labels + newlabels := make([]string, len(src.labels)) + copy(newlabels, src.labels) + //deepcopy envs + newenvs := make([]string, len(src.envs)) + copy(newenvs, src.envs) + //create a new variables + vars := variable.NewVariables() + return &HostConfig{ + sequence: src.sequence, + config: utils.DeepCopy(src.config), + labels: newlabels, + envs: newenvs, + variables: vars, + instances: src.instances, + instancesSequence: instancesSquence, } } @@ -182,7 +368,6 @@ func ParseHosts(data string) ([]*HostConfig, error) { if len(data) == 0 { return nil, errno.ERR_EMPTY_HOSTS } - parser := viper.NewWithOptions(viper.KeyDelimiter("::")) parser.SetConfigType("yaml") err := parser.ReadConfig(bytes.NewBuffer([]byte(data))) @@ -206,12 +391,26 @@ func ParseHosts(data string) ([]*HostConfig, error) { return nil, err } - if _, ok := exist[hc.GetHost()]; ok { - return nil, errno.ERR_DUPLICATE_HOST. - F("duplicate host: %s", hc.GetHost()) + if _, ok := exist[hc.GetName()]; ok { + return nil, errno.ERR_DUPLICATE_NAME. + F("duplicate host: %s", hc.GetName()) + } + //produce the instances of hc, append to hcs. + instances := hc.GetInstances() + for instancesSquence := 1; instancesSquence <= instances; instancesSquence++ { + hc_new := copyHostConfig(hc, instancesSquence) + hcs = append(hcs, hc_new) + } + exist[hc.GetName()] = true + } + //add Variables and Rendering + for idx, hc := range hcs { + if err = AddHostVariables(hcs, idx); err != nil { + return nil, err // already is error code + } else if err = hc.renderVariables(); err != nil { + return nil, err // already is error code } - hcs = append(hcs, hc) - exist[hc.GetHost()] = true + hc.GetVariables().Debug() } build.DEBUG(build.DEBUG_HOSTS, hosts) return hcs, nil diff --git a/internal/configure/hosts/variables.go b/internal/configure/hosts/variables.go new file mode 100644 index 000000000..4d31bfb66 --- /dev/null +++ b/internal/configure/hosts/variables.go @@ -0,0 +1,46 @@ +package hosts + +import ( + "fmt" + "github.com/opencurve/curveadm/internal/errno" + "github.com/opencurve/curveadm/pkg/variable" +) + +type Var struct { + name string + resolved bool +} + +var ( + hostVars = []Var{ + {name: "instances_sequence"}, + } +) + +func addVariables(hcs []*HostConfig, idx int, vars []Var) error { + hc := hcs[idx] + for _, v := range vars { + err := hc.GetVariables().Register(variable.Variable{ + Name: v.name, + Value: getValue(v.name, hcs, idx), + }) + if err != nil { + return errno.ERR_REGISTER_VARIABLE_FAILED.E(err) + } + } + + return nil +} + +func AddHostVariables(hcs []*HostConfig, idx int) error { + return addVariables(hcs, idx, hostVars) +} + +func getValue(name string, hcs []*HostConfig, idx int) string { + hc := hcs[idx] + switch name { + case "instances_sequence": + return fmt.Sprintf("%02d", hc.GetInstancesSequence()) + } + return "" +} diff --git a/internal/configure/monitor.go b/internal/configure/monitor.go index 6d7f34a94..2a4339b21 100644 --- a/internal/configure/monitor.go +++ b/internal/configure/monitor.go @@ -265,7 +265,7 @@ func ParseMonitorConfig(curveadm *cli.CurveAdm, filename string, data string, hs return nil, err } for _, hc := range hcs { - ctx.Add(hc.GetHost(), hc.GetHostname()) + ctx.Add(hc.GetName(), hc.GetHostname()) } mkind := dcs[0].GetKind() diff --git a/internal/configure/topology/variables.go b/internal/configure/topology/variables.go index 02cb4bb65..a2b246805 100644 --- a/internal/configure/topology/variables.go +++ b/internal/configure/topology/variables.go @@ -122,6 +122,8 @@ var ( {name: "cluster_snapshotclone_proxy_addr", kind: []string{KIND_CURVEBS}}, {name: "cluster_snapshotclone_dummy_port", kind: []string{KIND_CURVEBS}}, {name: "cluster_snapshotclone_nginx_upstream", kind: []string{KIND_CURVEBS}}, + {name: "cluster_snapshot_addr"}, // tools-v2: compatible with some old version image + {name: "cluster_snapshot_dummy_addr"}, // tools-v2 {name: "cluster_metaserver_addr", kind: []string{KIND_CURVEFS}}, } ) @@ -301,6 +303,10 @@ func getValue(name string, dcs []*DeployConfig, idx int) string { return joinDummyPort(dcs, ROLE_SNAPSHOTCLONE) case "cluster_snapshotclone_nginx_upstream": return joinNginxUpstreamServer(dcs) + case "cluster_snapshot_addr": + return joinPeer(dcs, ROLE_SNAPSHOTCLONE, SELECT_LISTEN_PORT) + case "cluster_snapshot_dummy_addr": + return joinPeer(dcs, ROLE_SNAPSHOTCLONE, SELECT_LISTEN_DUMMY_PORT) case "cluster_metaserver_addr": return joinPeer(dcs, ROLE_METASERVER, SELECT_LISTEN_PORT) } diff --git a/internal/errno/errno.go b/internal/errno/errno.go index 46b8228c5..11312e0fa 100644 --- a/internal/errno/errno.go +++ b/internal/errno/errno.go @@ -253,8 +253,10 @@ var ( ERR_UNSUPPORT_CLEAN_ITEM = EC(210005, "unsupport clean item") ERR_NO_SERVICES_MATCHED = EC(210006, "no services matched") // TODO: please check pool set disk type - ERR_INVALID_DISK_TYPE = EC(210007, "poolset disk type must be lowercase and can only be one of ssd, hdd and nvme") - + ERR_INVALID_DISK_TYPE = EC(210007, "poolset disk type must be lowercase and can only be one of ssd, hdd and nvme") + ERR_UNSUPPORT_DEPLOY_TYPE = EC(210008, "unknown deploy type") + ERR_NO_LEADER_OR_RANDOM_CONTAINER_FOUND = EC(210009, "no leader or random container found") + // 220: commad options (client common) ERR_UNSUPPORT_CLIENT_KIND = EC(220000, "unsupport client kind") // 221: command options (client/bs) @@ -287,6 +289,7 @@ var ( ERR_CONFIGURE_VALUE_REQUIRES_NON_EMPTY_STRING = EC(301004, "configure value requires non-empty string") ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER = EC(301005, "configure value requires positive integer") ERR_CONFIGURE_VALUE_REQUIRES_STRING_SLICE = EC(301006, "configure value requires string array") + ERR_CONFIGURE_VALUE_REQUIRES_NONEMPTY_SLICE = EC(301007, "configure value requires nonempty array") ERR_UNSUPPORT_VARIABLE_VALUE_TYPE = EC(301100, "unsupport variable value type") ERR_INVALID_VARIABLE_VALUE = EC(301101, "invalid variable value") @@ -310,7 +313,7 @@ var ( ERR_PRIVATE_KEY_FILE_REQUIRE_ABSOLUTE_PATH = EC(321004, "SSH private key file needs to be an absolute path") ERR_PRIVATE_KEY_FILE_NOT_EXIST = EC(321005, "SSH private key file not exist") ERR_PRIVATE_KEY_FILE_REQUIRE_600_PERMISSIONS = EC(321006, "SSH private key file require 600 permissions") - ERR_DUPLICATE_HOST = EC(321007, "host is duplicate") + ERR_DUPLICATE_NAME = EC(321007, "name is duplicate") ERR_HOSTNAME_REQUIRES_VALID_IP_ADDRESS = EC(321008, "hostname requires valid IP address") // 322: configure (monitor.yaml: parse failed) diff --git a/internal/playbook/factory.go b/internal/playbook/factory.go index f62a7b3e5..b4a52e4f5 100644 --- a/internal/playbook/factory.go +++ b/internal/playbook/factory.go @@ -83,6 +83,7 @@ const ( GET_CLIENT_STATUS INSTALL_CLIENT UNINSTALL_CLIENT + ATTACH_LEADER_OR_RANDOM_CONTAINER // bs FORMAT_CHUNKFILE_POOL @@ -225,6 +226,8 @@ func (p *Playbook) createTasks(step *PlaybookStep) (*tasks.Tasks, error) { t, err = comm.NewInitServiceStatusTask(curveadm, config.GetDC(i)) case GET_SERVICE_STATUS: t, err = comm.NewGetServiceStatusTask(curveadm, config.GetDC(i)) + case ATTACH_LEADER_OR_RANDOM_CONTAINER: + t, err = comm.NewAttachLeaderOrRandomContainerTask(curveadm, config.GetDC(i)) case CLEAN_SERVICE: t, err = comm.NewCleanServiceTask(curveadm, config.GetDC(i)) case INIT_SUPPORT: diff --git a/internal/storage/sql.go b/internal/storage/sql.go index 0ce916aa1..22e994ee6 100644 --- a/internal/storage/sql.go +++ b/internal/storage/sql.go @@ -103,6 +103,7 @@ type Cluster struct { Topology string Pool string Current bool + Type string } var ( @@ -116,15 +117,21 @@ var ( topology TEXT NULL, pool TEXT NULL, create_time DATE NOT NULL, - current INTEGER DEFAULT 0 + current INTEGER DEFAULT 0, + type TEXT NOT NULL ) ` // insert cluster InsertCluster = ` - INSERT INTO clusters(uuid, name, description, topology, pool, create_time) - VALUES(?, ?, ?, ?, "", datetime('now','localtime')) + INSERT INTO clusters(uuid, name, description, topology, type, pool, create_time) + VALUES(?, ?, ?, ?, ?, "", datetime('now','localtime')) ` + // check new cluster column + GetTypeFiled = `SELECT COUNT(*) FROM pragma_table_info('clusters') WHERE name = 'type'` + + // update new cluster column + UpdateCluster = `ALTER TABLE clusters ADD COLUMN type TEXT NOT NULL DEFAULT 'develop'` // delete cluster DeleteCluster = `DELETE from clusters WHERE name = ?` diff --git a/internal/storage/storage.go b/internal/storage/storage.go index bd1f69a13..49e6834b4 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -91,7 +91,14 @@ func (s *Storage) init() error { } } - return nil + flag, err := s.CheckTypeFiledExist() + if err != nil { + return err + } + if !flag { + _, err = s.db.Write(UpdateCluster) + } + return err } func (s *Storage) write(query string, args ...any) error { @@ -160,8 +167,26 @@ func (s *Storage) GetHostses() ([]Hosts, error) { } // cluster -func (s *Storage) InsertCluster(name, uuid, description, topology string) error { - return s.write(InsertCluster, uuid, name, description, topology) +func (s *Storage) InsertCluster(name, uuid, description, topology, deployType string) error { + return s.write(InsertCluster, uuid, name, description, topology, deployType) +} + +func (s *Storage) CheckTypeFiledExist() (bool, error) { + result, err := s.db.Query(GetTypeFiled) + if err != nil { + return false, err + } + defer result.Close() + + var isFiledExist bool + for result.Next() { + err = result.Scan(&isFiledExist) + if err != nil { + return false, err + } + break + } + return isFiledExist, nil } func (s *Storage) DeleteCluster(name string) error { @@ -187,6 +212,7 @@ func (s *Storage) getClusters(query string, args ...interface{}) ([]Cluster, err &cluster.Pool, &cluster.CreateTime, &cluster.Current, + &cluster.Type, ) if err != nil { return nil, err diff --git a/internal/task/step/file.go b/internal/task/step/file.go index cfd4fc575..8dc4afbc7 100644 --- a/internal/task/step/file.go +++ b/internal/task/step/file.go @@ -196,7 +196,7 @@ func (s *InstallFile) Execute(ctx *context.Context) error { } func (s *Filter) kvSplit(line string, key, value *string) error { - pattern := fmt.Sprintf(REGEX_KV_SPLIT, s.KVFieldSplit, s.KVFieldSplit) + pattern := fmt.Sprintf(REGEX_KV_SPLIT, strings.TrimSpace(s.KVFieldSplit), s.KVFieldSplit) regex, err := regexp.Compile(pattern) if err != nil { return errno.ERR_BUILD_REGEX_FAILED.E(err) diff --git a/internal/task/task/common/client_status.go b/internal/task/task/common/client_status.go index 87628814a..845fa3c6e 100644 --- a/internal/task/task/common/client_status.go +++ b/internal/task/task/common/client_status.go @@ -163,7 +163,7 @@ func NewGetClientStatusTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, containerId := client.ContainerId subname := fmt.Sprintf("host=%s kind=%s containerId=%s", - hc.GetHost(), client.Kind, tui.TrimContainerId(containerId)) + hc.GetName(), client.Kind, tui.TrimContainerId(containerId)) t := task.NewTask("Get Client Status", subname, hc.GetSSHConfig()) // add step diff --git a/internal/task/task/common/collect_client.go b/internal/task/task/common/collect_client.go index f9085cf20..527e452ee 100644 --- a/internal/task/task/common/collect_client.go +++ b/internal/task/task/common/collect_client.go @@ -46,7 +46,7 @@ func NewCollectClientTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, er // new task containerId := client.ContainerId subname := fmt.Sprintf("host=%s kind=%s containerId=%s", - hc.GetHost(), client.Kind, tui.TrimContainerId(containerId)) + hc.GetName(), client.Kind, tui.TrimContainerId(containerId)) t := task.NewTask("Collect Client", subname, hc.GetSSHConfig()) // add step to task diff --git a/internal/task/task/common/service_status.go b/internal/task/task/common/service_status.go index 660fd68c7..bc3ccdf16 100644 --- a/internal/task/task/common/service_status.go +++ b/internal/task/task/common/service_status.go @@ -80,6 +80,13 @@ type ( memStorage *utils.SafeMap } + step2SetLeaderOrRandom struct { + serviceId string + status *string + isLeader *bool + memStorage *utils.SafeMap + } + ServiceStatus struct { Id string ParentId string @@ -94,6 +101,11 @@ type ( DataDir string Config *topology.DeployConfig } + + Leader0rRandom struct { + IsLeader bool + Id string + } ) func setServiceStatus(memStorage *utils.SafeMap, id string, status ServiceStatus) { @@ -218,6 +230,27 @@ func (s *step2FormatServiceStatus) Execute(ctx *context.Context) error { return nil } +func (s *step2SetLeaderOrRandom) Execute(ctx *context.Context) error { + id := s.serviceId + IsLeader := *s.isLeader + if !strings.HasPrefix(*s.status, "Up") { + return nil + } + s.memStorage.TX(func(kv *utils.SafeMap) error { + m := Leader0rRandom{false, id} + v := kv.Get(comm.LEADER_OR_RANDOM_ID) + if v != nil && v.(Leader0rRandom).IsLeader { + return nil + } + if IsLeader { + m = Leader0rRandom{true, id} + } + kv.Set(comm.LEADER_OR_RANDOM_ID, m) + return nil + }) + return nil +} + func NewInitServiceStatusTask(curveadm *cli.CurveAdm, dc *topology.DeployConfig) (*task.Task, error) { serviceId := curveadm.GetServiceId(dc.GetId()) containerId, err := curveadm.GetContainerId(serviceId) @@ -306,3 +339,50 @@ func NewGetServiceStatusTask(curveadm *cli.CurveAdm, dc *topology.DeployConfig) return t, nil } + +func NewAttachLeaderOrRandomContainerTask(curveadm *cli.CurveAdm, dc *topology.DeployConfig) (*task.Task, error) { + serviceId := curveadm.GetServiceId(dc.GetId()) + containerId, err := curveadm.GetContainerId(serviceId) + if curveadm.IsSkip(dc) { + return nil, nil + } else if err != nil { + return nil, err + } + hc, err := curveadm.GetHost(dc.GetHost()) + if err != nil { + return nil, err + } + + // new task + subname := fmt.Sprintf("host=%s role=%s containerId=%s", + dc.GetHost(), dc.GetRole(), tui.TrimContainerId(containerId)) + t := task.NewTask("Enter Leader container", subname, hc.GetSSHConfig()) + + // add step to task + var status string + var isLeader bool + t.AddStep(&step.ListContainers{ + Format: `"{{.Status}}"`, + Filter: fmt.Sprintf("id=%s", containerId), + Out: &status, + ExecOptions: curveadm.ExecOptions(), + }) + t.AddStep(&step.Lambda{ + Lambda: TrimContainerStatus(&status), + }) + t.AddStep(&step2GetLeader{ + dc: dc, + containerId: containerId, + status: &status, + isLeader: &isLeader, + execOptions: curveadm.ExecOptions(), + }) + t.AddStep(&step2SetLeaderOrRandom{ + serviceId: serviceId, + status: &status, + isLeader: &isLeader, + memStorage: curveadm.MemStorage(), + }) + + return t, nil +} diff --git a/internal/tui/hosts.go b/internal/tui/hosts.go index 9a19ba983..938ce80db 100644 --- a/internal/tui/hosts.go +++ b/internal/tui/hosts.go @@ -41,7 +41,7 @@ const ( func FormatHosts(hcs []*configure.HostConfig, verbose bool) string { lines := [][]interface{}{} title := []string{ - "Host", + "Name", "Hostname", "User", "Port", @@ -58,7 +58,7 @@ func FormatHosts(hcs []*configure.HostConfig, verbose bool) string { for i := 0; i < len(hcs); i++ { hc := hcs[i] - host := hc.GetHost() + name := hc.GetName() hostname := hc.GetHostname() user := hc.GetUser() port := strconv.Itoa(hc.GetSSHPort()) @@ -74,7 +74,7 @@ func FormatHosts(hcs []*configure.HostConfig, verbose bool) string { } lines = append(lines, []interface{}{ - host, + name, hostname, user, port, diff --git a/internal/utils/common.go b/internal/utils/common.go index 056a649a8..91515a81c 100644 --- a/internal/utils/common.go +++ b/internal/utils/common.go @@ -97,6 +97,10 @@ func IsStringAnyMap(v interface{}) bool { return Type(v) == "string_interface_map" } +func IsAnySlice(v interface{}) bool { + return Type(v) == "any_slice" +} + func IsFunc(v interface{}) bool { return reflect.TypeOf(v).Kind() == reflect.Func } diff --git a/playbook/memcached/hosts.yaml b/playbook/memcached/hosts.yaml index ee69994cc..06ae3ddce 100644 --- a/playbook/memcached/hosts.yaml +++ b/playbook/memcached/hosts.yaml @@ -4,7 +4,7 @@ global: private_key_file: /home/curve/.ssh/id_rsa hosts: - - host: server-host1 + - name: server-host1 hostname: 10.0.1.1 labels: - memcached @@ -23,7 +23,7 @@ hosts: - VERBOSE="v" # v: verbose (print errors/warnings while in event loop) # vv: very verbose (also print client commands/responses) # vvv: extremely verbose (internal state transitions) - - host: server-host2 + - name: server-host2 hostname: 10.0.1.2 labels: - memcached @@ -42,7 +42,7 @@ hosts: - VERBOSE="v" # v: verbose (print errors/warnings while in event loop) # vv: very verbose (also print client commands/responses) # vvv: extremely verbose (internal state transitions) - - host: server-host3 + - name: server-host3 hostname: 10.0.1.3 labels: - memcached diff --git a/playbook/memcached/hosts_instances.yaml b/playbook/memcached/hosts_instances.yaml new file mode 100644 index 000000000..2f6d8220b --- /dev/null +++ b/playbook/memcached/hosts_instances.yaml @@ -0,0 +1,46 @@ +global: + user: curve + ssh_port: 22 + private_key_file: /home/curve/.ssh/id_rsa + +hosts: + - name: server-host1 + hostname: 10.0.1.1 + labels: + - memcached + envs: + - SUDO_ALIAS=sudo + - ENGINE=docker + - IMAGE=memcached:1.6.17 + - EXPORTER_IMAGE=quay.io/prometheus/memcached-exporter:v0.13.0 + - LISTEN=10.0.1.1 + - PORT=112${instances_sequence} + - EXPORTER_PORT=91${instances_sequence} + - USER=root + - MEMORY_LIMIT=32768 # item memory in megabytes + - MAX_ITEM_SIZE=8m # adjusts max item size (default: 1m, min: 1k, max: 1024m) + - EXT_PATH=/mnt/memcachefile/cachefile:10${instances_sequence}G + - EXT_WBUF_SIZE=8 # size in megabytes of page write buffers. + - EXT_ITEM_AGE=1 # store items idle at least this long (seconds, default: no age limit) + - VERBOSE="v" + instances: 3 + + - name: server-host2 + hostname: 10.0.1.2 + labels: + - memcached + envs: + - SUDO_ALIAS=sudo + - ENGINE=docker + - IMAGE=memcached:1.6.17 + - EXPORTER_IMAGE=quay.io/prometheus/memcached-exporter:v0.13.0 + - LISTEN=10.0.1.2 + - PORT=11211 + - EXPORTER_PORT=9151 + - USER=root + - MEMORY_LIMIT=32768 # item memory in megabytes + - MAX_ITEM_SIZE=8m # adjusts max item size (default: 1m, min: 1k, max: 1024m) + - EXT_PATH=/mnt/memcachefile/cachefile:1024G + - EXT_WBUF_SIZE=8 # size in megabytes of page write buffers. + - EXT_ITEM_AGE=1 # store items idle at least this long (seconds, default: no age limit) + - VERBOSE="v" \ No newline at end of file diff --git a/playbook/memcached/scripts/clean.sh b/playbook/memcached/scripts/clean.sh index 84e0f2a6a..96b36554a 100644 --- a/playbook/memcached/scripts/clean.sh +++ b/playbook/memcached/scripts/clean.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_rm_cmd="${SUDO_ALIAS} rm -rf" @@ -23,6 +24,13 @@ precheck() { die "container [${g_container_name}] not exists!!!\n" exit 1 fi + if [ "${EXPORTER_PORT}" ];then + container_id=`${g_docker_cmd} ps --all --format "{{.ID}}" --filter name=${g_exporter_container_name}` + if [ -z ${container_id} ]; then + die "container [${g_exporter_container_name}] not exists!!!\n" + exit 1 + fi + fi } stop_container() { @@ -32,6 +40,14 @@ stop_container() { exit 1 fi success "rm container[${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + msg=`${g_docker_cmd} rm ${g_exporter_container_name}` + if [ $? -ne 0 ];then + die "${msg}\n" + exit 1 + fi + success "rm container[${g_exporter_container_name}]\n" + fi } rm_cachefile() { diff --git a/playbook/memcached/scripts/deploy.sh b/playbook/memcached/scripts/deploy.sh index f2c48b165..7df5a2c25 100644 --- a/playbook/memcached/scripts/deploy.sh +++ b/playbook/memcached/scripts/deploy.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_start_args="" +g_exporter_start_args="" g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_lsof_cmd="${SUDO_ALIAS} lsof" g_rm_cmd="${SUDO_ALIAS} rm -rf" @@ -28,7 +30,21 @@ precheck() { container_id=`${g_docker_cmd} ps --format "{{.ID}}" --filter name=${g_container_name} --all` if [ "${container_id}" ]; then success "container [${g_container_name}] already exists, skip\n" - exit 0 + exit 1 + fi + + if [ "${EXPORTER_PORT}" ];then + container_id=`${g_docker_cmd} ps --format "{{.ID}}" --filter name=${g_exporter_container_name} --all` + if [ "${container_id}" ]; then + success "container [${g_exporter_container_name}] already exists, skip\n" + exit 1 + fi + + ${g_lsof_cmd} -i:${EXPORTER_PORT} >& /dev/null + if [ $? -eq 0 ];then + die "port[${EXPORTER_PORT}] is in use!\n" + exit 1 + fi fi # check port @@ -79,14 +95,28 @@ init() { if [ "${VERBOSE}" ];then g_start_args="${g_start_args} -${VERBOSE}" fi + + + if [ "${EXPORTER_PORT}" ];then + g_exporter_start_args="${g_exporter_start_args} --memcached.address=${LISTEN}:${PORT}" + g_exporter_start_args="${g_exporter_start_args} --web.listen-address=${LISTEN}:${EXPORTER_PORT}" + fi } create_container() { - success "create container [${g_container_name}]\n" ${g_docker_cmd} create --name ${g_container_name} ${g_user} --network host ${g_volume_bind} ${IMAGE} memcached ${g_start_args} >& /dev/null - - success "start container [${g_container_name}]\n" + success "create container [${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} create --name ${g_exporter_container_name} --network host ${EXPORTER_IMAGE} ${g_exporter_start_args} >& /dev/null + success "create container [${g_exporter_container_name}]\n" + fi ${g_docker_cmd} start ${g_container_name} >& /dev/null + success "start container [${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} start ${g_exporter_container_name} >& /dev/null + success "start container [${g_exporter_container_name}]\n" + fi + success "wait 3 seconds, check container status...\n" sleep 3 @@ -95,14 +125,25 @@ create_container() { if [ ${g_status} != "running" ]; then exit 1 fi + if [ "${EXPORTER_PORT}" ];then + if [ ${g_exporter_status} != "running" ]; then + exit 1 + fi + fi } get_status_container() { g_status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_container_name}` + if [ "${EXPORTER_PORT}" ];then + g_exporter_status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_exporter_container_name}` + fi } show_info_container() { ${g_docker_cmd} ps --all --filter "name=${g_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} ps --all --filter "name=${g_exporter_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + fi } precheck diff --git a/playbook/memcached/scripts/start.sh b/playbook/memcached/scripts/start.sh index 9c7c380fa..bd9e33455 100644 --- a/playbook/memcached/scripts/start.sh +++ b/playbook/memcached/scripts/start.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_rm_cmd="${SUDO_ALIAS} rm -rf" g_mkdir_cmd="${SUDO_ALIAS} mkdir -p" @@ -34,6 +35,10 @@ precheck() { start_container() { ${g_docker_cmd} start ${g_container_name} >& /dev/null success "start container[${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} start ${g_exporter_container_name} >& /dev/null + success "start container[${g_exporter_container_name}]\n" + fi } get_status_container() { diff --git a/playbook/memcached/scripts/status.sh b/playbook/memcached/scripts/status.sh index ed875ec85..32507bb5a 100644 --- a/playbook/memcached/scripts/status.sh +++ b/playbook/memcached/scripts/status.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_start_args="" g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_volume_bind="" g_container_id="" -g_status="running" function msg() { printf '%b' "$1" >&2 @@ -26,20 +26,39 @@ precheck() { success "container [${g_container_name}] not exists!!!" exit 1 fi + if [ "${EXPORTER_PORT}" ];then + g_container_id=`${g_docker_cmd} ps --all --format "{{.ID}}" --filter name=${g_exporter_container_name}` + if [ -z ${g_container_id} ]; then + success "container [${g_exporter_container_name}] not exists!!!" + exit 1 + fi + fi } show_info_container() { ${g_docker_cmd} ps --all --filter "name=${g_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} ps --all --filter "name=${g_exporter_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + fi } show_ip_port() { printf "memcached addr:\t%s:%d\n" ${LISTEN} ${PORT} + if [ "${EXPORTER_PORT}" ];then + printf "memcached-exporter addr:\t%s:%d\n" ${LISTEN} ${EXPORTER_PORT} + fi } get_status_container() { - g_status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_container_name}` - if [ ${g_status} != "running" ]; then + status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_container_name}` + if [ ${status} != "running" ]; then + exit 1 + fi + if [ "${EXPORTER_PORT}" ];then + status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_exporter_container_name}` + if [ ${status} != "running" ]; then exit 1 + fi fi } diff --git a/playbook/memcached/scripts/stop.sh b/playbook/memcached/scripts/stop.sh index 2dc84e53f..2d51a2b5b 100644 --- a/playbook/memcached/scripts/stop.sh +++ b/playbook/memcached/scripts/stop.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" function msg() { @@ -22,11 +23,22 @@ precheck() { die "container [${g_container_name}] not exists!!!\n" exit 1 fi + if [ "${EXPORTER_PORT}" ];then + container_id=`${g_docker_cmd} ps --all --format "{{.ID}}" --filter name=${g_exporter_container_name}` + if [ -z ${container_id} ]; then + die "container [${g_exporter_container_name}] not exists!!!\n" + exit 1 + fi + fi } stop_container() { ${g_docker_cmd} stop ${g_container_name} >& /dev/null success "stop container[${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} stop ${g_exporter_container_name} >& /dev/null + success "stop container[${g_exporter_container_name}]\n" + fi } precheck