From a1041cd97c243ae7ca90836b7741dc8bf4451a8b Mon Sep 17 00:00:00 2001 From: "chunshao.rcs" Date: Mon, 22 Jan 2024 17:21:25 +0800 Subject: [PATCH] feat(horaectl): initial commit (#1450) ## Rationale Introduce command line tool for horaedb, `horaectl`. ## Detailed Changes * Initial commit. * Support `list clusters` and `cluster diagnose`. ``` go run main.go --meta_addr 127.0.0.1:8080 --cluster_name defaultCluster 127.0.0.1:8080(defaultCluster) > c +----+----------------+------------+--------------+-----------------------------+-------------------------+-------------------------+ | ID | NAME | SHARDTOTAL | TOPOLOGYTYPE | PROCEDUREEXECUTINGBATCHSIZE | CREATEDAT | MODIFIEDAT | +----+----------------+------------+--------------+-----------------------------+-------------------------+-------------------------+ | 0 | defaultCluster | 8 | static | 4294967295 | 2024-01-19 15:18:57.034 | 2024-01-19 15:18:57.034 | +----+----------------+------------+--------------+-----------------------------+-------------------------+-------------------------+ 127.0.0.1:8080(defaultCluster) > c d +---------------------+-------------------------+--------------------------+-----------------------+ | UNREGISTERED_SHARDS | UNREADY_SHARDS:SHARD_ID | UNREADY_SHARDS:NODE_NAME | UNREADY_SHARDS:STATUS | +---------------------+-------------------------+--------------------------+-----------------------+ | [] | | | | +---------------------+-------------------------+--------------------------+-----------------------+ ``` ## Test Plan Manual test. --- ctl/README.md | 14 ++++ ctl/cmd/cluster.go | 38 +++++++++++ ctl/cmd/diagnose.go | 38 +++++++++++ ctl/cmd/quit.go | 40 ++++++++++++ ctl/cmd/root.go | 130 ++++++++++++++++++++++++++++++++++++++ ctl/cmd/root_test.go | 91 ++++++++++++++++++++++++++ ctl/go.mod | 38 +++++++++++ ctl/go.sum | 83 ++++++++++++++++++++++++ ctl/licenserc.toml | 22 +++++++ ctl/main.go | 26 ++++++++ ctl/operation/clusters.go | 98 ++++++++++++++++++++++++++++ ctl/operation/const.go | 34 ++++++++++ ctl/operation/util.go | 58 +++++++++++++++++ 13 files changed, 710 insertions(+) create mode 100644 ctl/README.md create mode 100644 ctl/cmd/cluster.go create mode 100644 ctl/cmd/diagnose.go create mode 100644 ctl/cmd/quit.go create mode 100644 ctl/cmd/root.go create mode 100644 ctl/cmd/root_test.go create mode 100644 ctl/go.mod create mode 100644 ctl/go.sum create mode 100644 ctl/licenserc.toml create mode 100644 ctl/main.go create mode 100644 ctl/operation/clusters.go create mode 100644 ctl/operation/const.go create mode 100644 ctl/operation/util.go diff --git a/ctl/README.md b/ctl/README.md new file mode 100644 index 0000000000..929d16810a --- /dev/null +++ b/ctl/README.md @@ -0,0 +1,14 @@ +# HoraeCTL + +![License](https://img.shields.io/badge/license-Apache--2.0-green.svg) + +HoraeCTL is the operation tool for managing the HoraeDB cluster. + +## Quick Start +TODO + +## Contributing +The project is under rapid development so that any contribution is welcome. + +## License +HoraeCTL is under [Apache License 2.0](./LICENSE). diff --git a/ctl/cmd/cluster.go b/ctl/cmd/cluster.go new file mode 100644 index 0000000000..4f59ab9754 --- /dev/null +++ b/ctl/cmd/cluster.go @@ -0,0 +1,38 @@ +/* + * 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 cmd + +import ( + "github.com/apache/incubator-horaedb/ctl/operation" + "github.com/spf13/cobra" +) + +var clusterCmd = &cobra.Command{ + Use: "cluster", + Aliases: []string{"c"}, + Short: "Operations on cluster", + Run: func(cmd *cobra.Command, args []string) { + operation.ClustersList() + }, +} + +func init() { + rootCmd.AddCommand(clusterCmd) +} diff --git a/ctl/cmd/diagnose.go b/ctl/cmd/diagnose.go new file mode 100644 index 0000000000..044ed5177e --- /dev/null +++ b/ctl/cmd/diagnose.go @@ -0,0 +1,38 @@ +/* + * 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 cmd + +import ( + "github.com/apache/incubator-horaedb/ctl/operation" + "github.com/spf13/cobra" +) + +var diagnoseCmd = &cobra.Command{ + Use: "diagnose", + Aliases: []string{"d"}, + Short: "Cluster diagnose", + Run: func(cmd *cobra.Command, args []string) { + operation.ClusterDiagnose() + }, +} + +func init() { + clusterCmd.AddCommand(diagnoseCmd) +} diff --git a/ctl/cmd/quit.go b/ctl/cmd/quit.go new file mode 100644 index 0000000000..faf3d70ce1 --- /dev/null +++ b/ctl/cmd/quit.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 cmd + +import ( + "github.com/spf13/cobra" + + "os" +) + +var quitCmd = &cobra.Command{ + Use: "quit", + Aliases: []string{"q", "exit"}, + Short: "Quit horaectl", + Run: func(cmd *cobra.Command, args []string) { + println("Bye!") + os.Exit(0) + }, +} + +func init() { + rootCmd.AddCommand(quitCmd) +} diff --git a/ctl/cmd/root.go b/ctl/cmd/root.go new file mode 100644 index 0000000000..e964d6ab00 --- /dev/null +++ b/ctl/cmd/root.go @@ -0,0 +1,130 @@ +/* + * 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 cmd + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + + "github.com/apache/incubator-horaedb/ctl/operation" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var rootCmd = &cobra.Command{ + Use: "horaectl", + Short: "horaectl is a command line tool for HoraeDB", + Run: func(cmd *cobra.Command, args []string) {}, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } + + for _, arg := range os.Args { + if arg == "-h" || arg == "--help" { + os.Exit(0) + } + } + + for { + printPrompt(viper.GetString(operation.RootMetaAddr), viper.GetString(operation.RootCluster)) + err = ReadArgs(os.Stdin) + if err != nil { + fmt.Println(err) + continue + } + if err = rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Args = []string{} + } + } +} + +func init() { + rootCmd.PersistentFlags().String(operation.RootMetaAddr, "127.0.0.1:8080", "meta addr is used to connect to meta server") + viper.BindPFlag(operation.RootMetaAddr, rootCmd.PersistentFlags().Lookup(operation.RootMetaAddr)) + + rootCmd.PersistentFlags().StringP(operation.RootCluster, "c", "defaultCluster", "") + viper.BindPFlag(operation.RootCluster, rootCmd.PersistentFlags().Lookup(operation.RootCluster)) + + rootCmd.CompletionOptions = cobra.CompletionOptions{ + DisableDefaultCmd: true, + DisableNoDescFlag: true, + DisableDescriptions: true, + HiddenDefaultCmd: true, + } +} + +func printPrompt(address, cluster string) { + fmt.Printf("%s(%s) > ", address, cluster) +} + +// ReadArgs Forked from https://github.com/apache/incubator-seata-ctl/blob/8427314e04cdc435b925ed41573b37e3addeea34/action/common/args.go#L29 +func ReadArgs(in io.Reader) error { + os.Args = []string{""} + + scanner := bufio.NewScanner(in) + + var lines []string + + for scanner.Scan() { + line := strings.Trim(scanner.Text(), "\r\n ") + if line == "" { + return nil + } + if line[len(line)-1] == '\\' { + line = line[:len(line)-1] + lines = append(lines, line) + } else { + lines = append(lines, line) + break + } + } + + argsStr := strings.Join(lines, " ") + rawArgs := strings.Split(argsStr, "'") + + if len(rawArgs) != 1 && len(rawArgs) != 3 { + return errors.New("read args from input error") + } + + args := strings.Split(rawArgs[0], " ") + + if len(rawArgs) == 3 { + args = append(args, rawArgs[1]) + args = append(args, strings.Split(rawArgs[2], " ")...) + } + + for _, arg := range args { + if arg != "" { + os.Args = append(os.Args, strings.TrimSpace(arg)) + } + } + return nil +} diff --git a/ctl/cmd/root_test.go b/ctl/cmd/root_test.go new file mode 100644 index 0000000000..e7027c3896 --- /dev/null +++ b/ctl/cmd/root_test.go @@ -0,0 +1,91 @@ +/* + * 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. + */ + +// Forked from https://github.com/apache/incubator-seata-ctl/blob/8427314e04cdc435b925ed41573b37e3addeea34/action/common/args_test.go. + +package cmd + +import ( + "bytes" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +var argsTestCases = []struct { + input string + args []string + valid bool +}{ + { + `-a xxx -b yyy -c ' { "a": "b", "c": "d" }' -d -e`, + []string{"-a", "xxx", "-b", "yyy", "-c", `{ "a": "b", "c": "d" }`, "-d", "-e"}, + true, + }, + { + `-a xxx -b yyy \ +-c \ +' { \ + "a": "b", \ + "c": "d" \ +}' \ +-d \ +-e`, + []string{"-a", "xxx", "-b", "yyy", "-c", `{ "a": "b", "c": "d" }`, "-d", "-e"}, + true, + }, + { + `-a xxx -b yyy +-c \ +' { \ + "a": "b", \ + "c": "d" \ +}' \ +-d \ +-e`, + []string{"-a", "xxx", "-b", "yyy"}, + true, + }, + { + `-a \ +' { \ + "a": "b" \ +-b`, + []string{}, + false, + }, +} + +func TestReadArgs(t *testing.T) { + var stdin bytes.Buffer + for _, testCase := range argsTestCases { + stdin.Reset() + stdin.Write([]byte(testCase.input)) + if !testCase.valid { + assert.NotNil(t, ReadArgs(&stdin)) + continue + } + assert.Nil(t, ReadArgs(&stdin)) + assert.Equal(t, len(os.Args), len(testCase.args)) + for i := 0; i < len(os.Args); i++ { + assert.Equal(t, os.Args[i], testCase.args[i]) + } + } +} diff --git a/ctl/go.mod b/ctl/go.mod new file mode 100644 index 0000000000..6a93d08f22 --- /dev/null +++ b/ctl/go.mod @@ -0,0 +1,38 @@ +module github.com/apache/incubator-horaedb/ctl + +go 1.21.2 + +require ( + github.com/jedib0t/go-pretty/v6 v6.5.3 + github.com/pkg/errors v0.9.1 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/ctl/go.sum b/ctl/go.sum new file mode 100644 index 0000000000..4e8617b57a --- /dev/null +++ b/ctl/go.sum @@ -0,0 +1,83 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jedib0t/go-pretty/v6 v6.5.3 h1:GIXn6Er/anHTkVUoufs7ptEvxdD6KIhR7Axa2wYCPF0= +github.com/jedib0t/go-pretty/v6 v6.5.3/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ctl/licenserc.toml b/ctl/licenserc.toml new file mode 100644 index 0000000000..b8df11c8a9 --- /dev/null +++ b/ctl/licenserc.toml @@ -0,0 +1,22 @@ +# 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. + +headerPath = "Apache-2.0-ASF.txt" + +excludes = [ + # Derived +] diff --git a/ctl/main.go b/ctl/main.go new file mode 100644 index 0000000000..6f3695d807 --- /dev/null +++ b/ctl/main.go @@ -0,0 +1,26 @@ +/* + * 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 main + +import "github.com/apache/incubator-horaedb/ctl/cmd" + +func main() { + cmd.Execute() +} diff --git a/ctl/operation/clusters.go b/ctl/operation/clusters.go new file mode 100644 index 0000000000..ad5a1df0ee --- /dev/null +++ b/ctl/operation/clusters.go @@ -0,0 +1,98 @@ +/* + * 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 operation + +import ( + "fmt" + "net/http" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/viper" +) + +type Cluster struct { + ID uint32 `json:"id"` + Name string `json:"name"` + MinNodeCount uint32 `json:"minNodeCount"` + ShardTotal uint32 `json:"shardTotal"` + TopologyType string `json:"topologyType"` + ProcedureExecutingBatchSize uint32 `json:"procedureExecutingBatchSize"` + CreatedAt uint64 `json:"createdAt"` + ModifiedAt uint64 `json:"modifiedAt"` +} + +type ClusterResponse struct { + Status string `json:"status"` + Data []Cluster `json:"data"` +} + +type DiagnoseShardStatus struct { + NodeName string `json:"node_name"` + Status string `json:"status"` +} + +type DiagnoseShardResponse struct { + // shardID -> nodeName + UnregisteredShards []uint32 `json:"unregistered_shards"` + UnreadyShards map[uint32]DiagnoseShardStatus `json:"unready_shards"` +} + +func clusterUrl() string { + return HTTP + viper.GetString(RootMetaAddr) + APIClusters +} +func diagnoseUrl() string { + return HTTP + viper.GetString(RootMetaAddr) + APIClustersDiagnose + viper.GetString(RootCluster) + "/shards" +} + +func ClustersList() { + url := clusterUrl() + var response ClusterResponse + err := HttpUtil(http.MethodGet, url, nil, &response) + if err != nil { + fmt.Println(err) + } + + t := tableWriter(clustersListHeader) + for _, data := range response.Data { + row := table.Row{data.ID, data.Name, data.ShardTotal, data.TopologyType, data.ProcedureExecutingBatchSize, FormatTimeMilli(int64(data.CreatedAt)), FormatTimeMilli(int64(data.ModifiedAt))} + t.AppendRow(row) + } + fmt.Println(t.Render()) + t.Style() +} + +func ClusterDiagnose() { + url := diagnoseUrl() + var response DiagnoseShardResponse + err := HttpUtil(http.MethodGet, url, nil, &response) + if err != nil { + fmt.Println(err) + } + + t := tableWriter(clustersDiagnoseHeader) + row := table.Row{response.UnregisteredShards} + t.AppendRow(row) + for shardID, data := range response.UnreadyShards { + row := table.Row{"", shardID, data.NodeName, data.Status} + t.AppendRow(row) + } + fmt.Println(t.Render()) + t.Style() +} diff --git a/ctl/operation/const.go b/ctl/operation/const.go new file mode 100644 index 0000000000..21eb5bd381 --- /dev/null +++ b/ctl/operation/const.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 operation + +const ( + HTTP = "http://" + API = "/api/v1" + + APIClusters = API + "/clusters" + APIClustersDiagnose = API + "/clusters/diagnose" + + RootMetaAddr = "meta_addr" + RootCluster = "cluster_name" +) + +var clustersListHeader = []string{"ID", "Name", "ShardTotal", "TopologyType", "ProcedureExecutingBatchSize", "CreatedAt", "ModifiedAt"} +var clustersDiagnoseHeader = []string{"unregistered_shards", "unready_shards:shard_id", "unready_shards:node_name", "unready_shards:status"} diff --git a/ctl/operation/util.go b/ctl/operation/util.go new file mode 100644 index 0000000000..adc528075d --- /dev/null +++ b/ctl/operation/util.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 operation + +import ( + "encoding/json" + "io" + "net/http" + "time" + + "github.com/jedib0t/go-pretty/v6/table" +) + +func tableWriter(headers []string) table.Writer { + header := table.Row{} + for _, s := range headers { + header = append(header, s) + } + t := table.NewWriter() + t.AppendHeader(header) + return t +} + +func HttpUtil(method, url string, body io.Reader, response interface{}) error { + request, _ := http.NewRequest(method, url, body) + resp, err := (&http.Client{}).Do(request) + if err != nil { + return err + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + err = json.Unmarshal(b, &response) + return err +} + +func FormatTimeMilli(milli int64) string { + return time.UnixMilli(milli).Format("2006-01-02 15:04:05.000") +}