This is an automated email from the ASF dual-hosted git repository.
hulk pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks-controller.git
The following commit(s) were added to refs/heads/unstable by this push:
new 4685978 Add support of the terminal client for the server (#171)
4685978 is described below
commit 468597885763f92b7779682daeca138702d90898
Author: hulk <[email protected]>
AuthorDate: Fri May 3 08:40:26 2024 +0800
Add support of the terminal client for the server (#171)
---
Dockerfile | 4 +-
README.md | 25 ++++
build.sh | 8 +-
cmd/client/command/client.go | 86 +++++++++++++
cmd/client/{main.go => command/consts.go} | 10 +-
cmd/client/command/create.go | 207 ++++++++++++++++++++++++++++++
cmd/client/command/delete.go | 185 ++++++++++++++++++++++++++
cmd/client/command/get.go | 102 +++++++++++++++
cmd/client/command/helper.go | 62 +++++++++
cmd/client/command/import.go | 101 +++++++++++++++
cmd/client/command/list.go | 133 +++++++++++++++++++
cmd/client/command/migrate.go | 119 +++++++++++++++++
cmd/client/main.go | 40 ++++++
consts/context_key.go | 5 +-
consts/errors.go | 3 +-
controller/controller.go | 7 +-
go.mod | 57 ++++----
go.sum | 192 +++++++++++++++------------
server/api/cluster.go | 43 ++++++-
server/api/cluster_test.go | 1 +
server/api/namespace.go | 3 +-
server/api/node.go | 6 +-
server/api/shard.go | 2 +-
server/helper/helper.go | 2 +
server/middleware/middleware.go | 12 +-
server/route.go | 10 +-
store/cluster.go | 8 ++
store/cluster_node.go | 3 +-
store/store.go | 52 ++++++--
store/store_test.go | 11 ++
30 files changed, 1352 insertions(+), 147 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index a65dbde..08ba370 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
#
-FROM golang:1.17 as build
+FROM golang:1.20 as build
WORKDIR /kvctl
@@ -32,7 +32,7 @@ FROM ubuntu:focal
WORKDIR /kvctl
COPY --from=build /kvctl/_build/kvctl-server ./bin/
-COPY --from=build /kvctl/_build/kvctl-client ./bin/
+COPY --from=build /kvctl/_build/kvctl ./bin/
VOLUME /var/lib/kvctl
diff --git a/README.md b/README.md
index 07fca9b..6837793 100644
--- a/README.md
+++ b/README.md
@@ -39,4 +39,29 @@ $ ./_build/kvctl-server -c config/config.yaml
```

+### 2. Use the terminal client to interact with the controller server
+
+```shell
+# Show help
+$ ./_build/kvctl --help
+
+# Create namespace
+$ ./_build/kvctl create namespace test-ns
+
+# List namespaces
+$ ./_build/kvctl list namespaces
+
+# Create cluster in the namespace
+$ ./_build/kvctl create cluster test-cluster --nodes
127.0.0.1:6666,127.0.0.1:6667 -n test-ns
+
+# List clusters in the namespace
+$ ./_build/kvctl list clusters -n test-ns
+
+# Get cluster in the namespace
+$ ./_build/kvctl get cluster test-cluster -n test-ns
+
+# Migrate slot from source to target
+$ ./_build/kvctl migrate slot 123 --target 1 -n test-ns -c test-cluster
+```
+
For the HTTP API, you can find the [HTTP API(work in progress)](docs/API.md)
for more details.
diff --git a/build.sh b/build.sh
index 3751e57..44a82c8 100644
--- a/build.sh
+++ b/build.sh
@@ -79,10 +79,14 @@ BUILD_DATE=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
GIT_REVISION=`git rev-parse --short HEAD`
SERVER_TARGET_NAME=kvctl-server
-CLIENT_TARGET_NAME=kvctl-client
+CLIENT_TARGET_NAME=kvctl
for TARGET_NAME in "$SERVER_TARGET_NAME" "$CLIENT_TARGET_NAME"; do
- CMD_PATH="${GO_PROJECT}/cmd/${TARGET_NAME##*-}" # Remove everything to the
left of the last - in the TARGET_NAME variable
+ if [[ "$TARGET_NAME" == "$SERVER_TARGET_NAME" ]]; then
+ CMD_PATH="${GO_PROJECT}/cmd/server"
+ else
+ CMD_PATH="${GO_PROJECT}/cmd/client"
+ fi
if [[ "$BUILDER_IMAGE" == "none" ]]; then
GOOS="$TARGET_OS" GOARCH="$TARGET_ARCH" CGO_ENABLED=0 go build -v
-ldflags \
diff --git a/cmd/client/command/client.go b/cmd/client/command/client.go
new file mode 100644
index 0000000..12277a0
--- /dev/null
+++ b/cmd/client/command/client.go
@@ -0,0 +1,86 @@
+/*
+ * 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 command
+
+import (
+ "encoding/json"
+ "errors"
+ "reflect"
+
+ "github.com/go-resty/resty/v2"
+)
+
+const (
+ apiVersionV1 = "/api/v1"
+
+ defaultHost = "http://127.0.0.1:9379"
+)
+
+type client struct {
+ restyCli *resty.Client
+ host string
+}
+
+type ErrorMessage struct {
+ Message string `json:"message"`
+}
+
+type response struct {
+ Error *ErrorMessage `json:"error"`
+ Data any `json:"data"`
+}
+
+func newClient(host string) *client {
+ if host == "" {
+ host = defaultHost
+ }
+ restyCli := resty.New().SetBaseURL(host + apiVersionV1)
+ return &client{
+ restyCli: restyCli,
+ host: host,
+ }
+}
+
+func unmarshalData(body []byte, v any) error {
+ if len(body) == 0 {
+ return errors.New("empty response body")
+ }
+
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Ptr || rv.IsNil() {
+ return errors.New("unmarshal receiver was non-pointer")
+ }
+
+ var rsp response
+ rsp.Data = v
+ return json.Unmarshal(body, &rsp)
+}
+
+func unmarshalError(body []byte) error {
+ var rsp response
+ if err := json.Unmarshal(body, &rsp); err != nil {
+ return err
+ }
+ if rsp.Error != nil {
+ return errors.New(rsp.Error.Message)
+ }
+ return nil
+}
diff --git a/cmd/client/main.go b/cmd/client/command/consts.go
similarity index 84%
copy from cmd/client/main.go
copy to cmd/client/command/consts.go
index bac6575..59b6ef5 100644
--- a/cmd/client/main.go
+++ b/cmd/client/command/consts.go
@@ -18,7 +18,11 @@
*
*/
-package main
+package command
-func main() {
-}
+const (
+ ResourceNamespace = "namespace"
+ ResourceCluster = "cluster"
+ ResourceShard = "shard"
+ ResourceNode = "node"
+)
diff --git a/cmd/client/command/create.go b/cmd/client/command/create.go
new file mode 100644
index 0000000..fb5fcf9
--- /dev/null
+++ b/cmd/client/command/create.go
@@ -0,0 +1,207 @@
+/*
+ * 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 command
+
+import (
+ "errors"
+ "strconv"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+type CreateOptions struct {
+ namespace string
+ cluster string
+ shard int
+ replica int
+ nodes []string
+ password string
+}
+
+var createOptions CreateOptions
+
+var CreateCommand = &cobra.Command{
+ Use: "create",
+ Short: "Create a resource",
+ Example: `
+# Create a namespace
+kvctl create namespace <namespace>
+
+# Create a cluster in the namespace
+kvctl create cluster <cluster> -n <namespace> --replica 1 --nodes
127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381
+
+# Create a shard in the cluster
+kvctl create shard -n <namespace> -c <cluster> --nodes
127.0.0.1:6379,127.0.0.1:6380
+
+# Create nodes in the cluster
+kvctl create node 127.0.0.1:6379 -n <namespace> -c <cluster> --shard <shard>
+`,
+ PreRunE: createPreRun,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ host, _ := cmd.Flags().GetString("host")
+ client := newClient(host)
+ switch strings.ToLower(args[0]) {
+ case ResourceNamespace:
+ if len(args) < 2 {
+ return errors.New("missing namespace name")
+ }
+ return createNamespace(client, args[1])
+ case ResourceCluster:
+ if len(args) < 2 {
+ return errors.New("missing cluster name")
+ }
+ createOptions.cluster = args[1]
+ return createCluster(client, &createOptions)
+ case ResourceShard:
+ return createShard(client, &createOptions)
+ case ResourceNode:
+ if len(args) < 2 {
+ return errors.New("missing node address")
+ }
+ createOptions.nodes = []string{args[1]}
+ return createNodes(client, &createOptions)
+ default:
+ return errors.New("unsupported resource type, please
specify one of [namespace, cluster, shard, nodes]")
+ }
+ },
+ SilenceUsage: true,
+ SilenceErrors: true,
+}
+
+func createPreRun(_ *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return errors.New("missing resource type, please specify one of
[namespace, cluster, shard, node]")
+ }
+ resource := strings.ToLower(args[0])
+ if resource == ResourceNamespace {
+ return nil
+ }
+ if createOptions.namespace == "" {
+ return errors.New("missing namespace, please specify the
namespace via -n or --namespace option")
+ }
+ if resource != ResourceNode && createOptions.nodes == nil {
+ return errors.New("missing nodes, please specify the nodes via
--nodes option")
+ }
+ if resource == ResourceCluster {
+ return nil
+ }
+ if createOptions.cluster == "" {
+ return errors.New("missing cluster, please specify the cluster
via -c or --cluster option")
+ }
+ if resource == ResourceShard {
+ return nil
+ }
+ if createOptions.shard == -1 {
+ return errors.New("missing shard, please specify the shard via
-s or --shard option")
+ }
+ if createOptions.shard < 0 {
+ return errors.New("shard must be a positive number")
+ }
+ return nil
+}
+
+func createNamespace(cli *client, name string) error {
+ rsp, err := cli.restyCli.R().
+ SetBody(map[string]string{"namespace": name}).
+ Post("/namespaces")
+ if err != nil {
+ return err
+ }
+
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("create namespace: %s successfully.", name)
+ return nil
+}
+
+func createCluster(cli *client, options *CreateOptions) error {
+ rsp, err := cli.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetBody(map[string]interface{}{
+ "name": options.cluster,
+ "replica": options.replica,
+ "nodes": options.nodes,
+ "password": options.password,
+ }).
+ Post("/namespaces/{namespace}/clusters")
+ if err != nil {
+ return err
+ }
+
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("create cluster: %s successfully.", options.cluster)
+ return nil
+}
+
+func createShard(cli *client, options *CreateOptions) error {
+ rsp, err := cli.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetPathParam("cluster", options.cluster).
+ SetBody(map[string]interface{}{
+ "name": options.cluster,
+ "nodes": options.nodes,
+ "password": options.password,
+ }).
+ Post("/namespaces/{namespace}/clusters/{cluster}/shards")
+ if err != nil {
+ return err
+ }
+
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("create the new shard successfully.")
+ return nil
+}
+
+func createNodes(cli *client, options *CreateOptions) error {
+ rsp, err := cli.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetPathParam("cluster", options.cluster).
+ SetPathParam("shard", strconv.Itoa(options.shard)).
+ SetBody(map[string]interface{}{
+ "addr": options.nodes[0],
+ "password": options.password,
+ }).
+
Post("/namespaces/{namespace}/clusters/{cluster}/shards/{shard}/nodes")
+ if err != nil {
+ return err
+ }
+
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("create node: %v successfully.", options.nodes[0])
+ return nil
+}
+
+func init() {
+ CreateCommand.Flags().StringVarP(&createOptions.namespace, "namespace",
"n", "", "The namespace")
+ CreateCommand.Flags().StringVarP(&createOptions.cluster, "cluster",
"c", "", "The cluster")
+ CreateCommand.Flags().IntVarP(&createOptions.shard, "shard", "s", -1,
"The shard number")
+ CreateCommand.Flags().IntVarP(&createOptions.replica, "replica", "r",
1, "The replica number")
+ CreateCommand.Flags().StringSliceVarP(&createOptions.nodes, "nodes",
"", nil, "The node list")
+ CreateCommand.Flags().StringVarP(&createOptions.password, "password",
"", "", "The password")
+}
diff --git a/cmd/client/command/delete.go b/cmd/client/command/delete.go
new file mode 100644
index 0000000..377cf6a
--- /dev/null
+++ b/cmd/client/command/delete.go
@@ -0,0 +1,185 @@
+/*
+ * 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 command
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+type DeleteOptions struct {
+ namespace string
+ cluster string
+ shard int
+}
+
+var deleteOptions DeleteOptions
+
+var DeleteCommand = &cobra.Command{
+ Use: "delete",
+ Short: "Delete a resource",
+ Example: `
+# Delete a namespace
+kvctl delete namespace <namespace>
+
+# Delete a cluster in the namespace
+kvctl delete cluster <cluster> -n <namespace>
+
+# Delete a shard in the cluster
+kvctl delete shard <shard> -n <namespace> -c <cluster>
+
+# Delete a node in the cluster
+kvctl delete node <node_id> -n <namespace> -c <cluster> --shard <shard>
+`,
+ PreRunE: deletePreRun,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ resource := strings.ToLower(args[0])
+ if len(args) < 2 {
+ return fmt.Errorf("missing resource name")
+ }
+ host, _ := cmd.Flags().GetString("host")
+ client := newClient(host)
+ switch resource {
+ case ResourceNamespace:
+ namespace := args[1]
+ return deleteNamespace(client, namespace)
+ case ResourceCluster:
+ deleteOptions.cluster = args[1]
+ return deleteCluster(client, &deleteOptions)
+ case ResourceShard:
+ shard, err := strconv.Atoi(args[1])
+ if err != nil {
+ return err
+ }
+ deleteOptions.shard = shard
+ return deleteShard(client, &deleteOptions)
+ case ResourceNode:
+ nodeID := args[1]
+ return deleteNode(client, &deleteOptions, nodeID)
+ default:
+ return fmt.Errorf("unsupported resource type %s",
resource)
+ }
+ },
+ SilenceUsage: true,
+ SilenceErrors: true,
+}
+
+func deletePreRun(_ *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return fmt.Errorf("missing resource type")
+ }
+
+ resource := strings.ToLower(args[0])
+ if resource == ResourceNamespace {
+ return nil
+ }
+ if deleteOptions.namespace == "" {
+ return fmt.Errorf("missing namespace, please specify the
namespace via -n or --namespace option")
+ }
+ if resource == ResourceCluster {
+ return nil
+ }
+ if deleteOptions.cluster == "" {
+ return fmt.Errorf("missing cluster, please specify the cluster
via -c or --cluster option")
+ }
+ if resource == ResourceShard {
+ return nil
+ }
+ if deleteOptions.shard == -1 {
+ return fmt.Errorf("missing shard, please specify the shard via
-s or --shard option")
+ }
+ if deleteOptions.shard < 0 {
+ return fmt.Errorf("invalid shard %d", deleteOptions.shard)
+ }
+ return nil
+}
+
+func deleteNamespace(client *client, namespace string) error {
+ rsp, err := client.restyCli.R().
+ SetPathParam("namespace", namespace).
+ Delete("/namespaces/{namespace}")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("delete namespace: %s successfully.", namespace)
+ return nil
+}
+
+func deleteCluster(client *client, options *DeleteOptions) error {
+ rsp, err := client.restyCli.R().
+ SetPathParams(map[string]string{
+ "namespace": options.namespace,
+ "cluster": options.cluster,
+ }).Delete("/namespaces/{namespace}/clusters/{cluster}")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("delete cluster: %s successfully.", options.cluster)
+ return nil
+}
+
+func deleteShard(client *client, options *DeleteOptions) error {
+ rsp, err := client.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetPathParam("cluster", options.cluster).
+ SetPathParam("shard", strconv.Itoa(options.shard)).
+
Delete("/namespaces/{namespace}/clusters/{cluster}/shards/{shard}")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("delete shard %d successfully.", options.shard)
+ return nil
+}
+
+func deleteNode(client *client, options *DeleteOptions, nodeID string) error {
+ rsp, err := client.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetPathParam("cluster", options.cluster).
+ SetPathParam("shard", strconv.Itoa(options.shard)).
+ SetPathParam("node", nodeID).
+
Delete("/namespaces/{namespace}/clusters/{cluster}/shards/{shard}/nodes/{node}")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+ printLine("delete node: %s successfully.", nodeID)
+ return nil
+}
+
+func init() {
+ DeleteCommand.Flags().StringVarP(&deleteOptions.namespace, "namespace",
"n", "", "The namespace")
+ DeleteCommand.Flags().StringVarP(&deleteOptions.cluster, "cluster",
"c", "", "The cluster")
+ DeleteCommand.Flags().IntVarP(&deleteOptions.shard, "shard", "s", -1,
"The shard")
+}
diff --git a/cmd/client/command/get.go b/cmd/client/command/get.go
new file mode 100644
index 0000000..f2e965b
--- /dev/null
+++ b/cmd/client/command/get.go
@@ -0,0 +1,102 @@
+/*
+ * 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 command
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "github.com/apache/kvrocks-controller/store"
+)
+
+type GetOptions struct {
+ namespace string
+ cluster string
+}
+
+var getOptions GetOptions
+
+var GetCommand = &cobra.Command{
+ Use: "get",
+ Short: "Get a resource",
+ Example: `
+# Get a cluster
+kvctl get cluster <cluster> -n <namespace>
+`,
+ PreRunE: getPreRun,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ host, _ := cmd.Flags().GetString("host")
+ client := newClient(host)
+ if len(args) < 2 {
+ return fmt.Errorf("missing resource name, must be one
of [cluster, shard]")
+ }
+ resource := strings.ToLower(args[0])
+ switch resource {
+ case "cluster":
+ getOptions.cluster = args[1]
+ return getCluster(client, &getOptions)
+ default:
+ return fmt.Errorf("unsupported resource type %s",
resource)
+ }
+ },
+ SilenceUsage: true,
+ SilenceErrors: true,
+}
+
+func getPreRun(_ *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return fmt.Errorf("missing resource type")
+ }
+
+ if getOptions.namespace == "" {
+ return fmt.Errorf("missing namespace, please specify the
namespace via -n or --namespace option")
+ }
+ return nil
+}
+
+func getCluster(client *client, options *GetOptions) error {
+ rsp, err := client.restyCli.R().SetPathParams(map[string]string{
+ "namespace": options.namespace,
+ "cluster": options.cluster,
+ }).Get("/namespaces/{namespace}/clusters/{cluster}")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return unmarshalError(rsp.Body())
+ }
+
+ var result struct {
+ Cluster *store.Cluster `json:"cluster"`
+ }
+ if err := unmarshalData(rsp.Body(), &result); err != nil {
+ return err
+ }
+ printCluster(result.Cluster)
+ return nil
+}
+
+func init() {
+ GetCommand.Flags().StringVarP(&getOptions.namespace, "namespace", "n",
"", "The namespace of the resource")
+ GetCommand.Flags().StringVarP(&getOptions.cluster, "cluster", "c", "",
"The cluster of the resource")
+}
diff --git a/cmd/client/command/helper.go b/cmd/client/command/helper.go
new file mode 100644
index 0000000..1d07677
--- /dev/null
+++ b/cmd/client/command/helper.go
@@ -0,0 +1,62 @@
+/*
+ * 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 command
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/fatih/color"
+
+ "github.com/olekukonko/tablewriter"
+
+ "github.com/apache/kvrocks-controller/store"
+)
+
+func printLine(format string, a ...interface{}) {
+ boldColor := color.New(color.Bold)
+ _, _ = fmt.Fprintln(os.Stdout, boldColor.Sprintf(format, a...))
+}
+
+func printCluster(cluster *store.Cluster) {
+ writer := tablewriter.NewWriter(os.Stdout)
+ printLine("")
+ printLine("cluster: %s", cluster.Name)
+ printLine("version: %d\n", cluster.Version.Load())
+ writer.SetHeader([]string{"SHARD", "NODE_ID", "ADDRESS", "ROLE",
"MIGRATING"})
+ writer.SetCenterSeparator("|")
+ for i, shard := range cluster.Shards {
+ for _, node := range shard.Nodes {
+ role := strings.ToUpper(store.RoleSlave)
+ if node.IsMaster() {
+ role = strings.ToUpper(store.RoleMaster)
+ }
+ migratingStatus := "NO"
+ if shard.MigratingSlot != -1 {
+ migratingStatus = fmt.Sprintf("%d --> %d",
shard.MigratingSlot, shard.TargetShardIndex)
+ }
+ columns := []string{fmt.Sprintf("%d", i), node.ID(),
node.Addr(), role, migratingStatus}
+ writer.Append(columns)
+ }
+ }
+ writer.Render()
+}
diff --git a/cmd/client/command/import.go b/cmd/client/command/import.go
new file mode 100644
index 0000000..51107e6
--- /dev/null
+++ b/cmd/client/command/import.go
@@ -0,0 +1,101 @@
+/*
+ * 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 command
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+type ImportOptions struct {
+ namespace string
+ cluster string
+ nodes []string
+ password string
+}
+
+var importOptions ImportOptions
+
+var ImportCommand = &cobra.Command{
+ Use: "import",
+ Short: "Import data from a cluster",
+ Example: `
+# Import a cluster from nodes
+kvctl import cluster <cluster> --nodes 127.0.0.1:6379,127.0.0.1:6380
+`,
+ PreRunE: importPreRun,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ host, _ := cmd.Flags().GetString("host")
+ client := newClient(host)
+ resource := strings.ToLower(args[0])
+ switch resource {
+ case ResourceCluster:
+ importOptions.cluster = args[1]
+ return importCluster(client, &importOptions)
+ default:
+ return fmt.Errorf("unsupported resource type: %s",
resource)
+ }
+ },
+ SilenceUsage: true,
+ SilenceErrors: true,
+}
+
+func importPreRun(_ *cobra.Command, args []string) error {
+ if len(args) < 2 {
+ return errors.New("missing resource name")
+ }
+ if importOptions.namespace == "" {
+ return errors.New("missing namespace, please specify with -n or
--namespace")
+ }
+ if len(importOptions.nodes) == 0 {
+ return errors.New("missing nodes")
+ }
+ return nil
+}
+
+func importCluster(client *client, options *ImportOptions) error {
+ rsp, err := client.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetPathParam("cluster", options.cluster).
+ SetBody(map[string]interface{}{
+ "nodes": options.nodes,
+ "password": options.password,
+ }).
+ Post("/namespaces/{namespace}/clusters/{cluster}/import")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return errors.New(rsp.String())
+ }
+ printLine("import cluster: %s successfully.", options.cluster)
+ return nil
+}
+
+func init() {
+ ImportCommand.Flags().StringVarP(&importOptions.namespace, "namespace",
"n", "", "The namespace of the cluster")
+ ImportCommand.Flags().StringVarP(&importOptions.cluster, "cluster",
"c", "", "The cluster name")
+ ImportCommand.Flags().StringSliceVarP(&importOptions.nodes, "nodes",
"", nil, "The nodes to import from")
+ ImportCommand.Flags().StringVarP(&importOptions.password, "password",
"p", "", "The password of the cluster")
+}
diff --git a/cmd/client/command/list.go b/cmd/client/command/list.go
new file mode 100644
index 0000000..e7f628d
--- /dev/null
+++ b/cmd/client/command/list.go
@@ -0,0 +1,133 @@
+/*
+ * 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 command
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+var listOptions struct {
+ namespace string
+ cluster string
+}
+
+var ListCommand = &cobra.Command{
+ Use: "list",
+ Short: "Display all resources",
+ Example: `
+# Display all namespaces
+kvctl list namespaces
+
+# Display all clusters in the namespace
+kvctl list clusters -n <namespace>
+
+# Display all nodes in the cluster
+kvctl list nodes -n <namespace> -c <cluster>
+`,
+ ValidArgs: []string{"namespaces", "clusters", "shards", "nodes"},
+ RunE: func(cmd *cobra.Command, args []string) error {
+ host, _ := cmd.Flags().GetString("host")
+ client := newClient(host)
+ switch strings.ToLower(args[0]) {
+ case "namespaces":
+ return listNamespace(client)
+ case "clusters":
+ return listClusters(client)
+ default:
+ return fmt.Errorf("unsupported resource type %s",
args[0])
+ }
+ },
+ PreRunE: listPreRun,
+ SilenceUsage: true,
+ SilenceErrors: true,
+}
+
+func listPreRun(_ *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return fmt.Errorf("missing resource type, please specify one of
[namespaces, clusters, nodes]")
+ }
+
+ resource := strings.ToLower(args[0])
+ if resource == "namespaces" {
+ return nil
+ }
+ if listOptions.namespace == "" {
+ return fmt.Errorf("missing namespace, please specify the
namespace via -n or --namespace option")
+ }
+ if resource == "nodes" && listOptions.cluster == "" {
+ return fmt.Errorf("missing cluster, please specify the cluster
via -c or --cluster option")
+ }
+ return nil
+}
+
+func listNamespace(cli *client) error {
+ rsp, err := cli.restyCli.R().Get("/namespaces")
+ if err != nil {
+ return err
+ }
+
+ var result struct {
+ Namespaces []string `json:"namespaces"`
+ }
+ if err := unmarshalData(rsp.Body(), &result); err != nil {
+ return err
+ }
+ if len(result.Namespaces) == 0 {
+ printLine("no namespace found.")
+ return nil
+ }
+ for _, ns := range result.Namespaces {
+ printLine(ns)
+ }
+ return nil
+}
+
+func listClusters(cli *client) error {
+ rsp, err := cli.restyCli.R().
+ SetPathParam("namespace", listOptions.namespace).
+ Get("/namespaces/{namespace}/clusters")
+ if err != nil {
+ return err
+ }
+
+ var result struct {
+ Clusters []string `json:"clusters"`
+ }
+ if err := unmarshalData(rsp.Body(), &result); err != nil {
+ return err
+ }
+ if len(result.Clusters) == 0 {
+ printLine("no cluster found.")
+ return nil
+ }
+ for _, cluster := range result.Clusters {
+ printLine(cluster)
+ }
+ return nil
+}
+
+func init() {
+ ListCommand.Flags().StringVarP(&listOptions.namespace, "namespace",
"n", "", "The namespace")
+ ListCommand.Flags().StringVarP(&listOptions.cluster, "cluster", "c",
"", "The cluster")
+}
diff --git a/cmd/client/command/migrate.go b/cmd/client/command/migrate.go
new file mode 100644
index 0000000..c8ccbac
--- /dev/null
+++ b/cmd/client/command/migrate.go
@@ -0,0 +1,119 @@
+/*
+ * 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 command
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+type MigrationOptions struct {
+ namespace string
+ cluster string
+ slot int
+ target int
+ slotOnly bool
+}
+
+var migrateOptions MigrationOptions
+
+var MigrateCommand = &cobra.Command{
+ Use: "migrate",
+ Short: "Migrate slot to another node",
+ Example: `
+# Migrate slot between cluster shards
+kvctl migrate slot <slot> --target <target_shard_index> -n <namespace> -c
<cluster>
+`,
+ PreRunE: migrationPreRun,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ host, _ := cmd.Flags().GetString("host")
+ client := newClient(host)
+ resource := strings.ToLower(args[0])
+ switch resource {
+ case "slot":
+ return migrateSlot(client, &migrateOptions)
+ default:
+ return fmt.Errorf("unsupported resource type: %s",
resource)
+ }
+ },
+ SilenceUsage: true,
+ SilenceErrors: true,
+}
+
+func migrationPreRun(_ *cobra.Command, args []string) error {
+ if len(args) < 1 {
+ return fmt.Errorf("resource type should be specified")
+ }
+ if len(args) < 2 {
+ return fmt.Errorf("the slot number should be specified")
+ }
+ slot, err := strconv.Atoi(args[1])
+ if err != nil {
+ return fmt.Errorf("invalid slot number: %s", args[1])
+ }
+ if slot < 0 || slot > 16383 {
+ return errors.New("slot number should be in range [0, 16383]")
+ }
+ migrateOptions.slot = slot
+
+ if migrateOptions.namespace == "" {
+ return fmt.Errorf("namespace is required, please specify with
-n or --namespace")
+ }
+ if migrateOptions.cluster == "" {
+ return fmt.Errorf("cluster is required, please specify with -c
or --cluster")
+ }
+ if migrateOptions.target < 0 {
+ return fmt.Errorf("target is required, please specify with
--target")
+ }
+ return nil
+}
+
+func migrateSlot(client *client, options *MigrationOptions) error {
+ rsp, err := client.restyCli.R().
+ SetPathParam("namespace", options.namespace).
+ SetPathParam("cluster", options.cluster).
+ SetBody(map[string]interface{}{
+ "slot": options.slot,
+ "target": options.target,
+ "slotOnly": strconv.FormatBool(options.slotOnly),
+ }).
+ Post("/namespaces/{namespace}/clusters/{cluster}/migrate")
+ if err != nil {
+ return err
+ }
+ if rsp.IsError() {
+ return errors.New(rsp.String())
+ }
+ printLine("migrate slot[%d] task is submitted successfully.",
options.slot)
+ return nil
+}
+
+func init() {
+ MigrateCommand.Flags().IntVar(&migrateOptions.slot, "slot", -1, "The
slot to migrate")
+ MigrateCommand.Flags().IntVar(&migrateOptions.target, "target", -1,
"The target node")
+ MigrateCommand.Flags().StringVarP(&migrateOptions.namespace,
"namespace", "n", "", "The namespace")
+ MigrateCommand.Flags().StringVarP(&migrateOptions.cluster, "cluster",
"c", "", "The cluster")
+ MigrateCommand.Flags().BoolVar(&migrateOptions.slotOnly, "slot-only",
false, "Only migrate slot and ignore the existing data")
+}
diff --git a/cmd/client/main.go b/cmd/client/main.go
index bac6575..93e2251 100644
--- a/cmd/client/main.go
+++ b/cmd/client/main.go
@@ -20,5 +20,45 @@
package main
+import (
+ "fmt"
+ "os"
+
+ "github.com/fatih/color"
+
+ "github.com/spf13/cobra"
+
+ "github.com/apache/kvrocks-controller/cmd/client/command"
+)
+
+var rootCommand = &cobra.Command{
+ Use: "kvctl",
+ Short: "kvctl is a command line tool for the Kvrocks controller
service",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println(cmd.PersistentFlags().GetString("host"))
+ _, _ = color.New(color.Bold).Println("Run 'kvctl --help' for
usage.")
+ os.Exit(0)
+ },
+}
+
func main() {
+ if err := rootCommand.Execute(); err != nil {
+ color.Red("error: %v", err)
+ os.Exit(1)
+ }
+}
+
+func init() {
+ rootCommand.PersistentFlags().StringP("host", "H",
+ "http://127.0.0.1:9379", "The host of the Kvrocks controller
service")
+
+ rootCommand.AddCommand(command.ListCommand)
+ rootCommand.AddCommand(command.CreateCommand)
+ rootCommand.AddCommand(command.GetCommand)
+ rootCommand.AddCommand(command.DeleteCommand)
+ rootCommand.AddCommand(command.ImportCommand)
+ rootCommand.AddCommand(command.MigrateCommand)
+
+ rootCommand.SilenceUsage = true
+ rootCommand.SilenceErrors = true
}
diff --git a/consts/context_key.go b/consts/context_key.go
index f77ab01..d0d2080 100644
--- a/consts/context_key.go
+++ b/consts/context_key.go
@@ -23,9 +23,10 @@ const (
ContextKeyStore = "_context_key_storage"
ContextKeyCluster = "_context_key_cluster"
ContextKeyClusterShard = "_context_key_cluster_shard"
+ ContextKeyHost = "_context_key_host"
)
const (
- HeaderIsRedirect = "X-Is-Redirect"
- HeaderDontDetectHost = "X-Dont-Detect-Host"
+ HeaderIsRedirect = "X-Is-Redirect"
+ HeaderDontCheckClusterMode = "X-Dont-Check-Cluster-Mode"
)
diff --git a/consts/errors.go b/consts/errors.go
index a44c032..8ad1034 100644
--- a/consts/errors.go
+++ b/consts/errors.go
@@ -25,6 +25,7 @@ import "errors"
var (
ErrInvalidArgument = errors.New("invalid argument")
ErrNotFound = errors.New("not found")
+ ErrForbidden = errors.New("forbidden")
ErrAlreadyExists = errors.New("already exists")
ErrIndexOutOfRange = errors.New("index out of range")
ErrShardIsSame = errors.New("source and target shard is
same")
@@ -36,6 +37,4 @@ var (
ErrShardIsServicing = errors.New("shard is servicing")
ErrShardSlotIsMigrating = errors.New("shard slot is migrating")
ErrShardNoMatchNewMaster = errors.New("no match new master in shard")
- ErrMismatchMigrateSlot = errors.New("mismatch migrate slot")
- ErrMigrateSlotFailed = errors.New("migrate slot failed")
)
diff --git a/controller/controller.go b/controller/controller.go
index 933553d..cd302d7 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -126,6 +126,7 @@ func (c *Controller) syncLoop(ctx context.Context) {
prevTermLeader := ""
if c.clusterStore.IsLeader() {
c.becomeLeader(ctx, prevTermLeader)
+ prevTermLeader = c.clusterStore.ID()
}
c.readyCh <- struct{}{}
@@ -133,8 +134,10 @@ func (c *Controller) syncLoop(ctx context.Context) {
select {
case <-c.clusterStore.LeaderChange():
if c.clusterStore.IsLeader() {
- c.becomeLeader(ctx, prevTermLeader)
- prevTermLeader = c.clusterStore.ID()
+ if prevTermLeader != c.clusterStore.ID() {
+ c.becomeLeader(ctx, prevTermLeader)
+ prevTermLeader = c.clusterStore.ID()
+ }
} else {
if prevTermLeader != c.clusterStore.ID() {
continue
diff --git a/go.mod b/go.mod
index 2cbf015..d03e7a5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,28 +3,27 @@ module github.com/apache/kvrocks-controller
go 1.19
require (
- github.com/c-bata/go-prompt v0.2.6
- github.com/gin-gonic/gin v1.7.4
- github.com/go-playground/validator/v10 v10.9.0
+ github.com/gin-gonic/gin v1.9.1
+ github.com/go-playground/validator/v10 v10.14.0
github.com/go-redis/redis/v8 v8.11.5
- github.com/go-resty/resty/v2 v2.7.0
+ github.com/go-resty/resty/v2 v2.12.0
github.com/go-zookeeper/zk v1.0.3
- github.com/google/uuid v1.3.0
- github.com/jedib0t/go-pretty/v6 v6.4.6
+ github.com/olekukonko/tablewriter v0.0.5
github.com/prometheus/client_golang v1.11.1
- github.com/steinfletcher/apitest v1.5.15
- github.com/stretchr/testify v1.7.4
+ github.com/spf13/cobra v1.8.0
+ github.com/stretchr/testify v1.8.3
go.etcd.io/etcd v3.3.27+incompatible
go.etcd.io/etcd/client/v3 v3.5.4
go.uber.org/atomic v1.7.0
go.uber.org/zap v1.21.0
- golang.org/x/net v0.0.0-20211029224645-99673261e6eb
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 //
indirect
github.com/coreos/etcd v3.3.27+incompatible // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf //
indirect
@@ -32,36 +31,42 @@ require (
github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f //
indirect
+ github.com/fatih/color v1.16.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
- github.com/go-playground/locales v0.14.0 // indirect
- github.com/go-playground/universal-translator v0.18.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
- github.com/json-iterator/go v1.1.11 // indirect
- github.com/leodido/go-urn v1.2.1 // indirect
- github.com/mattn/go-colorable v0.1.7 // indirect
- github.com/mattn/go-isatty v0.0.12 // indirect
- github.com/mattn/go-runewidth v0.0.13 // indirect
- github.com/mattn/go-tty v0.0.3 // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd //
indirect
- github.com/modern-go/reflect2 v1.0.1 // indirect
- github.com/pkg/term v1.2.0-beta.2 // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
- github.com/rivo/uniseg v0.2.0 // indirect
- github.com/ugorji/go/codec v1.1.7 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
go.uber.org/multierr v1.6.0 // indirect
- golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
- golang.org/x/sys v0.1.0 // indirect
- golang.org/x/text v0.3.7 // indirect
+ golang.org/x/arch v0.3.0 // indirect
+ golang.org/x/crypto v0.21.0 // indirect
+ golang.org/x/net v0.22.0 // indirect
+ golang.org/x/sys v0.18.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c //
indirect
google.golang.org/grpc v1.38.0 // indirect
- google.golang.org/protobuf v1.26.0 // indirect
- gopkg.in/yaml.v2 v2.4.0 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 31d49c9..89003fb 100644
--- a/go.sum
+++ b/go.sum
@@ -13,12 +13,16 @@ github.com/beorn7/perks
v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod
h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod
h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/c-bata/go-prompt v0.2.6
h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI=
-github.com/c-bata/go-prompt v0.2.6/go.mod
h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
+github.com/bytedance/sonic v1.5.0/go.mod
h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1
h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod
h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod
h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2
h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod
h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311
h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod
h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/client9/misspell v0.3.4/go.mod
h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod
h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod
h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -32,7 +36,7 @@ github.com/coreos/go-systemd/v22 v22.3.2
h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzA
github.com/coreos/go-systemd/v22 v22.3.2/go.mod
h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23
h1:SrdboTJZnOqc2r4cT4wQCzQJjGYwkclLwx2sPrDsx7g=
github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23/go.mod
h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/creack/pty v1.1.9/go.mod
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+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
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -44,36 +48,38 @@ github.com/envoyproxy/go-control-plane
v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod
h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane
v0.9.9-0.20210217033140-668b12f5399d/go.mod
h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod
h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod
h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.4.9
h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/gabriel-vasile/mimetype v1.4.2
h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod
h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/ghodss/yaml v1.0.0/go.mod
h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0
h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod
h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
-github.com/gin-gonic/gin v1.7.4/go.mod
h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod
h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-kit/kit v0.8.0/go.mod
h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod
h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod
h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod
h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod
h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod
h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-playground/assert/v2 v2.0.1
h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod
h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.13.0/go.mod
h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/locales v0.14.0
h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod
h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
-github.com/go-playground/universal-translator v0.17.0/go.mod
h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/universal-translator v0.18.0
h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod
h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.4.1/go.mod
h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-playground/validator/v10 v10.9.0
h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
-github.com/go-playground/validator/v10 v10.9.0/go.mod
h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/go-playground/assert/v2 v2.2.0
h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1
h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod
h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1
h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod
h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0
h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod
h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.5
h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod
h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
-github.com/go-resty/resty/v2 v2.7.0
h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
-github.com/go-resty/resty/v2 v2.7.0/go.mod
h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
+github.com/go-resty/resty/v2 v2.12.0
h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
+github.com/go-resty/resty/v2 v2.12.0/go.mod
h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
github.com/go-stack/stack v1.8.0/go.mod
h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-zookeeper/zk v1.0.3
h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
github.com/go-zookeeper/zk v1.0.3/go.mod
h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/goccy/go-json v0.10.2
h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod
h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod
h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod
h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -105,70 +111,64 @@ github.com/google/go-cmp v0.5.5
h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod
h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod
h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod
h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/jedib0t/go-pretty/v6 v6.4.6
h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw=
-github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod
h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
+github.com/inconshreveable/mousetrap v1.1.0
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jpillora/backoff v1.0.0/go.mod
h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod
h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.9/go.mod
h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod
h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11
h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod
h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12
h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod
h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod
h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod
h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod
h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod
h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod
h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4
h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod
h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod
h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod
h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod
h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod
h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod
h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod
h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.0/go.mod
h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/leodido/go-urn v1.2.1
h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
-github.com/leodido/go-urn v1.2.1/go.mod
h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
-github.com/mattn/go-colorable v0.1.4/go.mod
h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.7
h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
-github.com/mattn/go-colorable v0.1.7/go.mod
h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-isatty v0.0.8/go.mod
h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10/go.mod
h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-isatty v0.0.12
h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
-github.com/mattn/go-isatty v0.0.12/go.mod
h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-runewidth v0.0.6/go.mod
h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/leodido/go-urn v1.2.4
h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod
h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/mattn/go-colorable v0.1.13
h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod
h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod
h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19
h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.20
h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.9
h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod
h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.13
h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
-github.com/mattn/go-runewidth v0.0.13/go.mod
h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
-github.com/mattn/go-tty v0.0.3/go.mod
h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/matttproud/golang_protobuf_extensions v1.0.1
h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod
h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod
h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1
h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod
h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2
h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod
h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod
h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod
h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/olekukonko/tablewriter v0.0.5
h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod
h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod
h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pelletier/go-toml/v2 v2.0.8
h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod
h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/errors v0.8.0/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.6.0/go.mod
h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
-github.com/pkg/term v1.2.0-beta.2
h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw=
-github.com/pkg/term v1.2.0-beta.2/go.mod
h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v1.0.0
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod
h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -190,35 +190,38 @@ github.com/prometheus/procfs v0.0.2/go.mod
h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.1.3/go.mod
h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0
h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod
h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-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/fastuuid v1.2.0/go.mod
h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.6.1/go.mod
h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.8.0
h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod
h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.2.0/go.mod
h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod
h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod
h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/steinfletcher/apitest v1.5.15
h1:AAdTN0yMbf0VMH/PMt9uB2I7jljepO6i+5uhm1PjH3c=
-github.com/steinfletcher/apitest v1.5.15/go.mod
h1:mF+KnYaIkuHM0C4JgGzkIIOJAEjo+EA5tTjJ+bHXnQc=
+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/stretchr/objx v0.1.0/go.mod
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/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.2.2/go.mod
h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod
h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod
h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.4
h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
-github.com/stretchr/testify v1.7.4/go.mod
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/ugorji/go v1.1.7/go.mod
h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
-github.com/ugorji/go/codec v1.1.7
h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
-github.com/ugorji/go/codec v1.1.7/go.mod
h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/stretchr/testify v1.8.0/go.mod
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3
h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/twitchyliquid64/golang-asm v0.15.1
h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod
h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11
h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod
h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.27/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod
h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod
h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/etcd v3.3.27+incompatible
h1:5hMrpf6REqTHV2LW2OclNpRtxI0k9ZplMemJsMSWju0=
go.etcd.io/etcd v3.3.27+incompatible/go.mod
h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
@@ -236,12 +239,17 @@ go.uber.org/multierr v1.6.0/go.mod
h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod
h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod
h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.19.0/go.mod
h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod
h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod
h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod
h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -252,6 +260,8 @@ golang.org/x/mod
v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -267,8 +277,12 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod
h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20211029224645-99673261e6eb
h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
-golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -280,43 +294,53 @@ golang.org/x/sync
v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod
h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0/go.mod
h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod
h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -328,6 +352,8 @@ golang.org/x/tools
v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod
h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -358,15 +384,14 @@ google.golang.org/protobuf v1.23.0/go.mod
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod
h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0
h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod
h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.30.0
h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod
h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/check.v1 v1.0.0-20201130134442-10cb98267c6c
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod
h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
@@ -385,4 +410,5 @@ gopkg.in/yaml.v3 v3.0.1
h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/server/api/cluster.go b/server/api/cluster.go
index 1a07327..3d1e896 100644
--- a/server/api/cluster.go
+++ b/server/api/cluster.go
@@ -17,10 +17,12 @@
* under the License.
*
*/
+
package api
import (
"errors"
+ "strings"
"github.com/gin-gonic/gin"
@@ -36,8 +38,8 @@ type MigrateSlotRequest struct {
}
type CreateClusterRequest struct {
- Name string `json:"name"`
- Nodes []string `json:"nodes"`
+ Name string `json:"name" validate:"required"`
+ Nodes []string `json:"nodes" validate:"required"`
Password string `json:"password"`
Replicas int `json:"replicas"`
}
@@ -49,7 +51,7 @@ type ClusterHandler struct {
func (handler *ClusterHandler) List(c *gin.Context) {
namespace := c.Param("namespace")
clusters, err := handler.s.ListCluster(c, namespace)
- if err != nil {
+ if err != nil && !errors.Is(err, consts.ErrNotFound) {
helper.ResponseError(c, err)
return
}
@@ -69,13 +71,35 @@ func (handler *ClusterHandler) Create(c *gin.Context) {
return
}
+ clusterStore := handler.s
+ if err := clusterStore.CheckNewNodes(c, req.Nodes); err != nil {
+ helper.ResponseError(c, err)
+ return
+ }
+
cluster, err := store.NewCluster(req.Name, req.Nodes, req.Replicas)
if err != nil {
helper.ResponseBadRequest(c, err)
return
}
cluster.SetPassword(req.Password)
- if err := handler.s.CreateCluster(c, namespace, cluster); err != nil {
+ checkClusterMode :=
strings.ToLower(c.GetHeader(consts.HeaderDontCheckClusterMode)) == "yes"
+ for _, node := range cluster.GetNodes() {
+ if !checkClusterMode {
+ break
+ }
+ version, err := node.CheckClusterMode(c)
+ if err != nil {
+ helper.ResponseError(c, err)
+ return
+ }
+ if version != -1 {
+ helper.ResponseBadRequest(c, errors.New("node is
already in cluster mode"))
+ return
+ }
+ }
+
+ if err := clusterStore.CreateCluster(c, namespace, cluster); err != nil
{
helper.ResponseError(c, err)
return
}
@@ -126,7 +150,7 @@ func (handler *ClusterHandler) Import(c *gin.Context) {
namespace := c.Param("namespace")
clusterName := c.Param("cluster")
var req struct {
- Nodes []string `json:"nodes"`
+ Nodes []string `json:"nodes" validate:"required"`
Password string `json:"password"`
}
if err := c.BindJSON(&req); err != nil {
@@ -151,6 +175,15 @@ func (handler *ClusterHandler) Import(c *gin.Context) {
}
cluster.SetPassword(req.Password)
+ newNodes := make([]string, 0)
+ for _, node := range cluster.GetNodes() {
+ newNodes = append(newNodes, node.Addr())
+ }
+ if err := handler.s.CheckNewNodes(c, newNodes); err != nil {
+ helper.ResponseError(c, err)
+ return
+ }
+
cluster.Name = clusterName
if err := handler.s.CreateCluster(c, namespace, cluster); err != nil {
helper.ResponseError(c, err)
diff --git a/server/api/cluster_test.go b/server/api/cluster_test.go
index 784128b..005ba4b 100644
--- a/server/api/cluster_test.go
+++ b/server/api/cluster_test.go
@@ -52,6 +52,7 @@ func TestClusterBasics(t *testing.T) {
body, err := json.Marshal(testCreateRequest)
require.NoError(t, err)
+ ctx.Header(consts.HeaderDontCheckClusterMode, "yes")
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body))
ctx.Params = []gin.Param{{Key: "namespace", Value: ns}}
diff --git a/server/api/namespace.go b/server/api/namespace.go
index 505b979..0541885 100644
--- a/server/api/namespace.go
+++ b/server/api/namespace.go
@@ -17,6 +17,7 @@
* under the License.
*
*/
+
package api
import (
@@ -60,7 +61,7 @@ func (handler *NamespaceHandler) Exists(c *gin.Context) {
func (handler *NamespaceHandler) Create(c *gin.Context) {
var request struct {
- Namespace string `json:"namespace"`
+ Namespace string `json:"namespace" validate:"required"`
}
if err := c.BindJSON(&request); err != nil {
helper.ResponseBadRequest(c, err)
diff --git a/server/api/node.go b/server/api/node.go
index 47d89e3..878798b 100644
--- a/server/api/node.go
+++ b/server/api/node.go
@@ -17,6 +17,7 @@
* under the License.
*
*/
+
package api
import (
@@ -43,13 +44,16 @@ func (handler *NodeHandler) Create(c *gin.Context) {
cluster, _ := c.MustGet(consts.ContextKeyCluster).(*store.Cluster)
var req struct {
Addr string `json:"addr" binding:"required"`
- Role string `json:"role" binding:"required"`
+ Role string `json:"role"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&req); err != nil {
helper.ResponseBadRequest(c, err)
return
}
+ if req.Role == "" {
+ req.Role = store.RoleSlave
+ }
shardIndex, _ := strconv.Atoi(c.Param("shard"))
err := cluster.AddNode(shardIndex, req.Addr, req.Role, req.Password)
if err != nil {
diff --git a/server/api/shard.go b/server/api/shard.go
index 9b36ced..9ccf258 100644
--- a/server/api/shard.go
+++ b/server/api/shard.go
@@ -56,7 +56,7 @@ func (handler *ShardHandler) Get(c *gin.Context) {
func (handler *ShardHandler) Create(c *gin.Context) {
ns := c.Param("namespace")
var req struct {
- Nodes []string `json:"nodes"`
+ Nodes []string `json:"nodes" validate:"required"`
Password string `json:"password"`
}
if err := c.BindJSON(&req); err != nil {
diff --git a/server/helper/helper.go b/server/helper/helper.go
index 22851e2..3f1eb4b 100644
--- a/server/helper/helper.go
+++ b/server/helper/helper.go
@@ -69,6 +69,8 @@ func ResponseError(c *gin.Context, err error) {
code = http.StatusBadRequest
} else if errors.Is(err, consts.ErrAlreadyExists) {
code = http.StatusConflict
+ } else if errors.Is(err, consts.ErrForbidden) {
+ code = http.StatusForbidden
} else if errors.Is(err, consts.ErrInvalidArgument) {
code = http.StatusBadRequest
}
diff --git a/server/middleware/middleware.go b/server/middleware/middleware.go
index 7955825..9ccfbe3 100644
--- a/server/middleware/middleware.go
+++ b/server/middleware/middleware.go
@@ -25,14 +25,13 @@ import (
"strconv"
"time"
- "github.com/apache/kvrocks-controller/server/helper"
+ "github.com/gin-gonic/gin"
+ "github.com/prometheus/client_golang/prometheus"
"github.com/apache/kvrocks-controller/consts"
"github.com/apache/kvrocks-controller/metrics"
+ "github.com/apache/kvrocks-controller/server/helper"
"github.com/apache/kvrocks-controller/store"
-
- "github.com/gin-gonic/gin"
- "github.com/prometheus/client_golang/prometheus"
)
func CollectMetrics(c *gin.Context) {
@@ -88,9 +87,10 @@ func RequiredNamespace(c *gin.Context) {
}
if !ok {
helper.ResponseBadRequest(c, errors.New("namespace not found"))
- return
+ c.Abort()
+ } else {
+ c.Next()
}
- c.Next()
}
func RequiredCluster(c *gin.Context) {
diff --git a/server/route.go b/server/route.go
index ba946a9..90d0455 100644
--- a/server/route.go
+++ b/server/route.go
@@ -23,6 +23,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
+ "github.com/apache/kvrocks-controller/server/helper"
+
"github.com/apache/kvrocks-controller/consts"
"github.com/apache/kvrocks-controller/server/api"
"github.com/apache/kvrocks-controller/server/middleware"
@@ -38,6 +40,10 @@ func (srv *Server) initHandlers() {
engine.Any("/debug/pprof/*profile", PProf)
engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
+ engine.NoRoute(func(c *gin.Context) {
+ helper.ResponseError(c, consts.ErrNotFound)
+ c.Abort()
+ })
apiV1 := engine.Group("/api/v1/")
{
@@ -53,9 +59,9 @@ func (srv *Server) initHandlers() {
{
clusters.GET("", middleware.RequiredNamespace,
handler.Cluster.List)
clusters.POST("", middleware.RequiredNamespace,
handler.Cluster.Create)
+ clusters.POST("/:cluster/import",
middleware.RequiredNamespace, handler.Cluster.Import)
clusters.GET("/:cluster", middleware.RequiredCluster,
handler.Cluster.Get)
- clusters.POST("/:cluster/import",
handler.Cluster.Import)
- clusters.DELETE("/:cluster", handler.Cluster.Remove)
+ clusters.DELETE("/:cluster",
middleware.RequiredCluster, handler.Cluster.Remove)
clusters.POST("/:cluster/migrate",
middleware.RequiredCluster, handler.Cluster.MigrateSlot)
}
diff --git a/store/cluster.go b/store/cluster.go
index ec72586..fe8240e 100644
--- a/store/cluster.go
+++ b/store/cluster.go
@@ -143,6 +143,14 @@ func (cluster *Cluster) SyncToNodes(ctx context.Context)
error {
return nil
}
+func (cluster *Cluster) GetNodes() []Node {
+ nodes := make([]Node, 0)
+ for i := 0; i < len(cluster.Shards); i++ {
+ nodes = append(nodes, cluster.Shards[i].Nodes...)
+ }
+ return nodes
+}
+
func (cluster *Cluster) Reset(ctx context.Context) error {
for i := 0; i < len(cluster.Shards); i++ {
for _, node := range cluster.Shards[i].Nodes {
diff --git a/store/cluster_node.go b/store/cluster_node.go
index 5ee9520..997a089 100644
--- a/store/cluster_node.go
+++ b/store/cluster_node.go
@@ -64,6 +64,7 @@ type Node interface {
GetClusterNodeInfo(ctx context.Context) (*ClusterNodeInfo, error)
GetClusterInfo(ctx context.Context) (*ClusterInfo, error)
SyncClusterInfo(ctx context.Context, cluster *Cluster) error
+ CheckClusterMode(ctx context.Context) (int64, error)
MigrateSlot(ctx context.Context, slot int, NodeID string) error
MarshalJSON() ([]byte, error)
@@ -167,7 +168,7 @@ func (n *ClusterNode) CheckClusterMode(ctx context.Context)
(int64, error) {
if strings.Contains(err.Error(), "cluster is not initialized") {
return -1, nil
}
- return -1, fmt.Errorf("error while checking node(%s) cluster
mode: %w", n.addr, err)
+ return -1, fmt.Errorf("error while checking node cluster mode:
%w", err)
}
return clusterInfo.CurrentEpoch, nil
}
diff --git a/store/store.go b/store/store.go
index 84dea95..5afeeef 100644
--- a/store/store.go
+++ b/store/store.go
@@ -22,7 +22,7 @@ package store
import (
"context"
"encoding/json"
- "errors"
+ "fmt"
"github.com/apache/kvrocks-controller/consts"
"github.com/apache/kvrocks-controller/store/engine"
@@ -42,6 +42,8 @@ type Store interface {
CreateCluster(ctx context.Context, ns string, cluster *Cluster) error
UpdateCluster(ctx context.Context, ns string, cluster *Cluster) error
SetCluster(ctx context.Context, ns string, clusterInfo *Cluster) error
+
+ CheckNewNodes(ctx context.Context, nodes []string) error
}
var _ Store = (*ClusterStore)(nil)
@@ -109,7 +111,7 @@ func (s *ClusterStore) RemoveNamespace(ctx context.Context,
ns string) error {
return err
}
if len(clusters) != 0 {
- return errors.New("namespace wasn't empty, please remove
clusters first")
+ return fmt.Errorf("%w: please delete clusters first",
consts.ErrForbidden)
}
if err := s.e.Delete(ctx, appendPrefix(ns)); err != nil {
return err
@@ -142,11 +144,11 @@ func (s *ClusterStore) existsCluster(ctx context.Context,
ns, cluster string) (b
func (s *ClusterStore) GetCluster(ctx context.Context, ns, cluster string)
(*Cluster, error) {
value, err := s.e.Get(ctx, buildClusterKey(ns, cluster))
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("cluster: %w", err)
}
var clusterInfo Cluster
if err = json.Unmarshal(value, &clusterInfo); err != nil {
- return nil, err
+ return nil, fmt.Errorf("cluster: %w", err)
}
return &clusterInfo, nil
}
@@ -155,7 +157,7 @@ func (s *ClusterStore) GetCluster(ctx context.Context, ns,
cluster string) (*Clu
func (s *ClusterStore) UpdateCluster(ctx context.Context, ns string,
clusterInfo *Cluster) error {
clusterInfo.Version.Inc()
if err := s.SetCluster(ctx, ns, clusterInfo); err != nil {
- return err
+ return fmt.Errorf("cluster: %w", err)
}
s.EmitEvent(EventPayload{
Namespace: ns,
@@ -168,18 +170,18 @@ func (s *ClusterStore) UpdateCluster(ctx context.Context,
ns string, clusterInfo
func (s *ClusterStore) SetCluster(ctx context.Context, ns string, clusterInfo
*Cluster) error {
if len(clusterInfo.Shards) == 0 {
- return errors.New("required at least one shard")
+ return fmt.Errorf("%w: required at least one shard",
consts.ErrInvalidArgument)
}
value, err := json.Marshal(clusterInfo)
if err != nil {
- return err
+ return fmt.Errorf("cluster: %w", err)
}
return s.e.Set(ctx, buildClusterKey(ns, clusterInfo.Name), value)
}
func (s *ClusterStore) CreateCluster(ctx context.Context, ns string,
clusterInfo *Cluster) error {
if exists, _ := s.existsCluster(ctx, ns, clusterInfo.Name); exists {
- return consts.ErrAlreadyExists
+ return fmt.Errorf("cluster: %w", consts.ErrAlreadyExists)
}
if err := s.SetCluster(ctx, ns, clusterInfo); err != nil {
return err
@@ -209,6 +211,40 @@ func (s *ClusterStore) RemoveCluster(ctx context.Context,
ns, cluster string) er
return nil
}
+func (s *ClusterStore) CheckNewNodes(ctx context.Context, nodes []string)
error {
+ newNodes := make(map[string]bool, 0)
+ for _, node := range nodes {
+ newNodes[node] = true
+ }
+
+ namespaces, err := s.ListNamespace(ctx)
+ if err != nil {
+ return err
+ }
+ existingNodes := make([]string, 0)
+ for _, ns := range namespaces {
+ clusters, err := s.ListCluster(ctx, ns)
+ if err != nil {
+ return err
+ }
+ for _, cluster := range clusters {
+ c, err := s.GetCluster(ctx, ns, cluster)
+ if err != nil {
+ return err
+ }
+ for _, existingNode := range c.GetNodes() {
+ if _, ok := newNodes[existingNode.Addr()]; ok {
+ existingNodes = append(existingNodes,
existingNode.Addr())
+ }
+ }
+ }
+ }
+ if len(existingNodes) > 0 {
+ return fmt.Errorf("node: %w: %v", consts.ErrAlreadyExists,
existingNodes)
+ }
+ return nil
+}
+
func (s *ClusterStore) Notify() <-chan EventPayload {
return s.eventNotifyCh
}
diff --git a/store/store_test.go b/store/store_test.go
index 496a013..59b0fdc 100644
--- a/store/store_test.go
+++ b/store/store_test.go
@@ -94,4 +94,15 @@ func TestClusterStore(t *testing.T) {
require.ErrorIs(t, err, consts.ErrNotFound)
}
})
+
+ t.Run("check nodes", func(t *testing.T) {
+ testCluster, err := NewCluster("test-cluster-another",
+ []string{"127.0.0.1:1111", "127.0.0.1:2222",
"127.0.0.1:3333"}, 1)
+ require.NoError(t, err)
+
+ require.NoError(t, store.CreateCluster(ctx, "test-ns",
testCluster))
+ require.NoError(t, store.CheckNewNodes(ctx,
[]string{"127.0.0.1:4444", "127.0.0.1:5555"}))
+ require.NotNil(t, store.CheckNewNodes(ctx,
[]string{"127.0.0.1:3333", "127.0.0.1:4444"}))
+ require.NotNil(t, store.CheckNewNodes(ctx,
[]string{"127.0.0.1:2222", "127.0.0.1:3333"}))
+ })
}