From 4344a9169cd2734359586cad0f094ba0435b0326 Mon Sep 17 00:00:00 2001
From: Benevor <2647311844@qq.com>
Date: Wed, 11 Oct 2023 18:48:04 +0800
Subject: [PATCH] feat: add cluster status monitoring and improve error
handling
Signed-off-by: Benevor <2647311844@qq.com>
---
Makefile | 2 +-
Makefile.common | 2 +-
README.md | 24 +-
cmd/cluster.go | 59 ---
cmd/cluster/cluster.go | 101 ++++
cmd/cluster/install.go | 64 +++
cmd/cluster/start.go | 71 +++
cmd/cluster/status.go | 63 +++
cmd/cluster/stop.go | 63 +++
cmd/cluster/uninstall.go | 64 +++
cmd/cluster/upgrade.go | 83 ++++
cmd/deploy.go | 48 --
cmd/destroy.go | 48 --
cmd/install.go | 45 +-
cmd/list.go | 23 +-
cmd/root.go | 54 +-
cmd/start.go | 47 --
cmd/stop.go | 48 --
cmd/upgrade.go | 59 ---
cmd/version.go | 26 +-
.../cluster/topology.example.yaml | 2 +-
go.mod | 2 +-
main.go | 20 +-
pkg/{ => cluster}/config/config.go | 37 +-
pkg/{ => cluster}/config/gen_conf.go | 44 +-
pkg/cluster/config/remote_host.go | 41 ++
pkg/cluster/config/toml.go | 112 +++++
pkg/{ => cluster}/config/yaml.go | 47 ++
pkg/cluster/manager/install.go | 370 ++++++++++++++
pkg/cluster/manager/start.go | 425 ++++++++++++++++
pkg/cluster/manager/status.go | 394 +++++++++++++++
pkg/{stop => cluster/manager}/stop.go | 81 ++-
pkg/cluster/manager/uninstall.go | 184 +++++++
.../operation}/download.go | 135 +++--
pkg/{exec => cluster/operation}/exec.go | 39 +-
pkg/config/remote_host.go | 27 -
pkg/config/toml.go | 70 ---
pkg/deploy/deploy.go | 465 ------------------
scripts/conf_gen.sh | 53 --
scripts/install.sh | 7 +-
test.yaml | 25 -
test/config/gen_conf_test.go | 66 ---
test/config/toml_test.go | 21 -
test/config/yaml_test.go | 18 -
test/example/openGenimi-template.conf | 31 --
test/example/topology.example1.yaml | 166 -------
test/example/topology.example2.yaml | 33 --
test/example/topology.example3.yaml | 28 --
util/connect.go | 20 +-
util/const.go | 107 ++--
util/error.go | 22 +-
util/version.go | 50 ++
52 files changed, 2576 insertions(+), 1560 deletions(-)
delete mode 100644 cmd/cluster.go
create mode 100644 cmd/cluster/cluster.go
create mode 100644 cmd/cluster/install.go
create mode 100644 cmd/cluster/start.go
create mode 100644 cmd/cluster/status.go
create mode 100644 cmd/cluster/stop.go
create mode 100644 cmd/cluster/uninstall.go
create mode 100644 cmd/cluster/upgrade.go
delete mode 100644 cmd/deploy.go
delete mode 100644 cmd/destroy.go
delete mode 100644 cmd/start.go
delete mode 100644 cmd/stop.go
delete mode 100644 cmd/upgrade.go
rename topology.example.yaml => examples/cluster/topology.example.yaml (99%)
rename pkg/{ => cluster}/config/config.go (71%)
rename pkg/{ => cluster}/config/gen_conf.go (77%)
create mode 100644 pkg/cluster/config/remote_host.go
create mode 100644 pkg/cluster/config/toml.go
rename pkg/{ => cluster}/config/yaml.go (67%)
create mode 100644 pkg/cluster/manager/install.go
create mode 100644 pkg/cluster/manager/start.go
create mode 100644 pkg/cluster/manager/status.go
rename pkg/{stop => cluster/manager}/stop.go (62%)
create mode 100644 pkg/cluster/manager/uninstall.go
rename pkg/{download => cluster/operation}/download.go (61%)
rename pkg/{exec => cluster/operation}/exec.go (71%)
delete mode 100644 pkg/config/remote_host.go
delete mode 100644 pkg/config/toml.go
delete mode 100644 pkg/deploy/deploy.go
delete mode 100644 scripts/conf_gen.sh
delete mode 100644 test.yaml
delete mode 100644 test/config/gen_conf_test.go
delete mode 100644 test/config/toml_test.go
delete mode 100644 test/config/yaml_test.go
delete mode 100644 test/example/openGenimi-template.conf
delete mode 100644 test/example/topology.example1.yaml
delete mode 100644 test/example/topology.example2.yaml
delete mode 100644 test/example/topology.example3.yaml
create mode 100644 util/version.go
diff --git a/Makefile b/Makefile
index 1d75bc4..a026d36 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-# Copyright 2022 Huawei Cloud Computing Technologies Co., Ltd.
+# Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/Makefile.common b/Makefile.common
index d9e3756..1c61388 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1,4 +1,4 @@
-# Copyright 2022 Huawei Cloud Computing Technologies Co., Ltd.
+# Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index f3c8fcb..fffe404 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# openGemini-UP
+# Gemix
Cluster deployment and upgrade tool.
@@ -20,20 +20,20 @@ The following table describes some commonly used basic commands.
| command | description | parameter | example |
| --- | --- | --- | --- |
-| `version` | display version number of openGemini-UP | no para | `./openGemini-UP version` |
-| `list` | display the version information of all components currently downloaded | no para | `./openGemini-UP list` |
-| `install` | install database components | --version | `./openGemini-UP install --version v1.0.0` |
-| `cluster` | deploying and managing openGemini clusters | have subcommand | |
+| `version` | display version number of gemix | no para | `./gemix version` |
+| `list` | display the version information of all components currently downloaded | no para | `./gemix list` |
+| `cluster` | deploying and managing openGemini clusters | have subcommand | |
The following table describes the subcommands of the `cluster` command.
| command | description | parameter | example |
| --- | --- | --- | --- |
-| `deploy` | deploy an openGemini cluster| --version
--yaml
--user
--key
--password | `./openGemini-UP cluster deploy --version v1.0.0 --yaml ./topology.example.yaml --user root --key ~/.ssh/id_rsa` |
-| `stop` | stop an openGemini cluster | --yaml
--user
--key
--password | `./openGemini-UP cluster stop --yaml ./topology.example.yaml --user root --password xxxxxx` |
-| `start` | start an openGemini cluster which is stopped | --yaml
--user
--key
--password | `./openGemini-UP cluster start --yaml ./topology.example.yaml --user root --password xxxxxx` |
-| `destroy` | destroy an openGemini cluster which means stopping services and clearing data| --yaml
--user
--key
--password | `./openGemini-UP cluster destroy --yaml ./topology.example.yaml --user root --password xxxxxx` |
-| `upgrade` | upgrade an openGemini cluster to the specified version | --version
--yaml
--user
--key
--password | `./openGemini-UP cluster upgrade --version v1.0.0 --yaml ./topology.example.yaml --user root --password xxxxxx` |
+| `install` | install an openGemini cluster | --version
--yaml
--user
--key
--password | `./gemix cluster install --version v1.0.0 --yaml ./topology.example.yaml --user root --key ~/.ssh/id_rsa` |
+| `start` | start an openGemini cluster and check the running status after startup | --version
--yaml
--user
--key
--password | `./gemix cluster start --version v1.0.0 --yaml ./topology.example.yaml --user root --key ~/.ssh/id_rsa` |
+| `stop` | stop an openGemini cluster | --yaml
--user
--key
--password | `./gemix cluster stop --yaml ./topology.example.yaml --user root --password xxxxxx` |
+| `uninstall` | uninstall an openGemini cluster which means clearing data | --version
--yaml
--user
--key
--password | `./gemix cluster uninstall --version v1.0.0 --yaml ./topology.example.yaml --user root --password xxxxxx` |
+| `status` | Check the running status of the openGemini cluster, including port occupancy, disk capacity, etc | --yaml
--user
--key
--password | `./gemix cluster status --yaml ./topology.example.yaml --user liujibo --key ~/.ssh/id_rsa` |
+| `upgrade` | upgrade an openGemini cluster to the specified version and uninstall the old one | --version
--old_version
--yaml
--user
--key
--password | `./gemix cluster upgrade --old_version v1.0.0 --version v1.0.1 --yaml ./topology.example.yaml --user root --password xxxxxx` |
## topology.example.yaml
@@ -41,7 +41,7 @@ The `topology.example.yaml` is written by the user and contains the necessary in
The meaning of each part is as follows:
-* `global`: Default values for some options.
+* `global`: Default values for some options. These options are mandatory.
* `ts-meta`: Deployment information for `ts-meta`, users can modify some options in `openGemini.conf` here.
* `ts-sql`: Deployment information for `ts-sql`, users can modify some options in `openGemini.conf` here.
* `ts-store`: Deployment information for `ts-store`, users can modify some options in `openGemini.conf` here.
@@ -56,7 +56,7 @@ global:
log_dir: "/gemini-deploy/logs"
# Storage directory for cluster deployment files, startup scripts, and configuration files.
deploy_dir: "/gemini-deploy"
- # operating system, linux/darwin/windows.
+ # operating system, linux/darwin.
os: "linux"
# Supported values: "amd64", "arm64" (default: "amd64").
arch: "amd64"
diff --git a/cmd/cluster.go b/cmd/cluster.go
deleted file mode 100644
index 3f6c7b3..0000000
--- a/cmd/cluster.go
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-Copyright © 2023 NAME HERE
-*/
-package cmd
-
-import (
- "fmt"
- "openGemini-UP/pkg/config"
- "openGemini-UP/pkg/deploy"
- "openGemini-UP/util"
-
- "github.com/spf13/cobra"
-)
-
-// clusterCmd represents the cluster command
-var clusterCmd = &cobra.Command{
- Use: "cluster",
- Short: "manage cluster",
- Long: `Manage openGemini clusters, including deploying, stopping, destroying, monitoring, etc.`,
- Run: func(cmd *cobra.Command, args []string) {},
-}
-
-func init() {
- rootCmd.AddCommand(clusterCmd)
-}
-
-func getClusterOptions(cmd *cobra.Command) (deploy.ClusterOptions, error) {
- var ops deploy.ClusterOptions
- if version, _ := cmd.Flags().GetString("version"); version == "" {
- ops.Version = util.Download_default_version
- } else {
- ops.Version = version
- }
- if user, _ := cmd.Flags().GetString("user"); user == "" {
- return ops, fmt.Errorf("the user is required")
- } else {
- ops.User = user
- }
- password, _ := cmd.Flags().GetString("password")
- key, _ := cmd.Flags().GetString("key")
- if password == "" && key == "" || password != "" && key != "" {
- return ops, fmt.Errorf("the password and key need one and only one")
- } else {
- ops.Key = key
- ops.Password = password
- if key != "" {
- ops.SshType = config.SSH_KEY
- } else {
- ops.SshType = config.SSH_PW
- }
- }
-
- if yPath, _ := cmd.Flags().GetString("yaml"); yPath == "" {
- return ops, fmt.Errorf("the path of cluster configuration file must be specified")
- } else {
- ops.YamlPath = yPath
- }
- return ops, nil
-}
diff --git a/cmd/cluster/cluster.go b/cmd/cluster/cluster.go
new file mode 100644
index 0000000..4adf05d
--- /dev/null
+++ b/cmd/cluster/cluster.go
@@ -0,0 +1,101 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/openGemini/gemix/util"
+ "github.com/spf13/cobra"
+)
+
+// clusterCmd represents the cluster command
+var ClusterCmd = &cobra.Command{
+ Use: "cluster",
+ Short: "manage cluster",
+ Long: `Manage openGemini cluster, including install, stop, uninstall, status, etc.`,
+ Run: func(cmd *cobra.Command, args []string) {},
+}
+
+func getClusterOptions(cmd *cobra.Command) (manager.ClusterOptions, error) {
+ var ops manager.ClusterOptions
+ if version, _ := cmd.Flags().GetString("version"); version == "" {
+ latestVer, err := util.GetLatestVerFromCurl()
+ if err != nil {
+ return ops, err
+ } else {
+ ops.Version = latestVer
+ }
+ } else {
+ ops.Version = version
+ }
+ if user, _ := cmd.Flags().GetString("user"); user == "" {
+ has, value := GetEnv(util.SshEnvUser)
+ if has {
+ ops.User = value
+ } else {
+ return ops, fmt.Errorf("the user is required")
+ }
+ } else {
+ ops.User = user
+ }
+ password, _ := cmd.Flags().GetString("password")
+ key, _ := cmd.Flags().GetString("key")
+ if password == "" && key == "" {
+ hasKey, key := GetEnv(util.SshEnvKey)
+ if hasKey {
+ ops.Key = key
+ ops.SshType = config.SSH_KEY
+ } else {
+ hasPW, pw := GetEnv(util.SshEnvPW)
+ if hasPW {
+ ops.Password = pw
+ ops.SshType = config.SSH_PW
+ } else {
+ return ops, fmt.Errorf("the password and key need at least one")
+ }
+ }
+
+ } else if password != "" && key != "" {
+ return ops, fmt.Errorf("the password and key need only one")
+ } else {
+ ops.Key = key
+ ops.Password = password
+ if key != "" {
+ ops.SshType = config.SSH_KEY
+ } else {
+ ops.SshType = config.SSH_PW
+ }
+ }
+
+ if yPath, _ := cmd.Flags().GetString("yaml"); yPath == "" {
+ return ops, fmt.Errorf("the path of cluster configuration file must be specified")
+ } else {
+ ops.YamlPath = yPath
+ }
+ return ops, nil
+}
+
+func GetEnv(envVar string) (bool, string) {
+ value := os.Getenv(envVar)
+ if value == "" {
+ return false, value
+ } else {
+ return true, value
+ }
+}
diff --git a/cmd/cluster/install.go b/cmd/cluster/install.go
new file mode 100644
index 0000000..15fef41
--- /dev/null
+++ b/cmd/cluster/install.go
@@ -0,0 +1,64 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/spf13/cobra"
+)
+
+// installCmd represents the install command
+var installCmd = &cobra.Command{
+ Use: "install",
+ Short: "install cluster",
+ Long: `Install an openGemini cluster based on configuration files and version numbers.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ ops, err := getClusterOptions(cmd)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ err = InstallCluster(ops)
+ if err != nil {
+ fmt.Println(err)
+ }
+ },
+}
+
+func InstallCluster(ops manager.ClusterOptions) error {
+ installer := manager.NewGeminiInstaller(ops)
+ defer installer.Close()
+
+ if err := installer.PrepareForInstall(); err != nil {
+ return err
+ }
+ if err := installer.Install(); err != nil {
+ return err
+ }
+ fmt.Printf("Successfully installed the openGemini cluster with version : %s\n", ops.Version)
+ return nil
+}
+
+func init() {
+ ClusterCmd.AddCommand(installCmd)
+ installCmd.Flags().StringP("version", "v", "", "component version")
+ installCmd.Flags().StringP("yaml", "y", "", "The path to cluster topology yaml file")
+ installCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
+ installCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
+ installCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
+}
diff --git a/cmd/cluster/start.go b/cmd/cluster/start.go
new file mode 100644
index 0000000..922dc07
--- /dev/null
+++ b/cmd/cluster/start.go
@@ -0,0 +1,71 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/spf13/cobra"
+)
+
+// startCmd represents the start command
+var startCmd = &cobra.Command{
+ Use: "start",
+ Short: "start cluster",
+ Long: `Start an openGemini cluster based on configuration files and version numbers.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ ops, err := getClusterOptions(cmd)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ err = StartCluster(ops)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ fmt.Printf("\nCheck the status of openGemini cluster\n")
+ err = PatrolCluster(ops)
+ if err != nil {
+ fmt.Println(err)
+ }
+ },
+}
+
+func StartCluster(ops manager.ClusterOptions) error {
+ starter := manager.NewGeminiStarter(ops)
+ defer starter.Close()
+
+ if err := starter.PrepareForStart(); err != nil {
+ return err
+ }
+ if err := starter.Start(); err != nil {
+ return err
+ }
+ fmt.Printf("Successfully started the openGemini cluster with version : %s\n", ops.Version)
+ return nil
+}
+
+func init() {
+ ClusterCmd.AddCommand(startCmd)
+ startCmd.Flags().StringP("version", "v", "", "component version")
+ startCmd.Flags().StringP("yaml", "y", "", "The path to cluster topology yaml file")
+ startCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
+ startCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
+ startCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
+}
diff --git a/cmd/cluster/status.go b/cmd/cluster/status.go
new file mode 100644
index 0000000..c5d9ed9
--- /dev/null
+++ b/cmd/cluster/status.go
@@ -0,0 +1,63 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/spf13/cobra"
+)
+
+// statusCmd
+var statusCmd = &cobra.Command{
+ Use: "status",
+ Short: "check cluster status",
+ Long: `Check the current running status of an openGemini cluster.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ ops, err := getClusterOptions(cmd)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ err = PatrolCluster(ops)
+ if err != nil {
+ fmt.Println(err)
+ }
+ },
+}
+
+func PatrolCluster(ops manager.ClusterOptions) error {
+ patroler := manager.NewGeminiStatusPatroller(ops)
+ defer patroler.Close()
+
+ if err := patroler.PrepareForPatrol(); err != nil {
+ return err
+ }
+ if err := patroler.Patrol(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func init() {
+ ClusterCmd.AddCommand(statusCmd)
+ statusCmd.Flags().StringP("version", "v", "", "component version")
+ statusCmd.Flags().StringP("yaml", "y", "", "The path to cluster topology yaml file")
+ statusCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
+ statusCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
+ statusCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
+}
diff --git a/cmd/cluster/stop.go b/cmd/cluster/stop.go
new file mode 100644
index 0000000..390e6f1
--- /dev/null
+++ b/cmd/cluster/stop.go
@@ -0,0 +1,63 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/spf13/cobra"
+)
+
+// stopCmd represents the stop command
+var stopCmd = &cobra.Command{
+ Use: "stop",
+ Short: "stop cluster",
+ Long: `Stop an openGemini cluster based on configuration files.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ ops, err := getClusterOptions(cmd)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ err = StopCluster(ops)
+ if err != nil {
+ fmt.Println(err)
+ }
+ },
+}
+
+func StopCluster(ops manager.ClusterOptions) error {
+ stop := manager.NewGeminiStop(ops)
+ defer stop.Close()
+
+ if err := stop.Prepare(); err != nil {
+ return err
+ }
+ if err := stop.Run(); err != nil {
+ return err
+ }
+ fmt.Printf("Successfully stopped the openGemini cluster\n")
+ return nil
+}
+
+func init() {
+ ClusterCmd.AddCommand(stopCmd)
+ stopCmd.Flags().StringP("yaml", "y", "", "The path to cluster topology yaml file")
+ stopCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
+ stopCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
+ stopCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
+}
diff --git a/cmd/cluster/uninstall.go b/cmd/cluster/uninstall.go
new file mode 100644
index 0000000..84081c7
--- /dev/null
+++ b/cmd/cluster/uninstall.go
@@ -0,0 +1,64 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/spf13/cobra"
+)
+
+// uninstallCmd represents the uninstall command
+var uninstallCmd = &cobra.Command{
+ Use: "uninstall",
+ Short: "uninstall cluster",
+ Long: `uninstall an openGemini cluster based on configuration files.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ ops, err := getClusterOptions(cmd)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ err = UninstallCluster(ops)
+ if err != nil {
+ fmt.Println(err)
+ }
+ },
+}
+
+func UninstallCluster(ops manager.ClusterOptions) error {
+ uninstaller := manager.NewGeminiUninstaller(ops)
+ defer uninstaller.Close()
+
+ if err := uninstaller.Prepare(); err != nil {
+ return err
+ }
+ if err := uninstaller.Run(); err != nil {
+ return err
+ }
+ fmt.Printf("Successfully uninstalled the openGemini cluster with version : %s\n", ops.Version)
+ return nil
+}
+
+func init() {
+ ClusterCmd.AddCommand(uninstallCmd)
+ uninstallCmd.Flags().StringP("version", "v", "", "component version")
+ uninstallCmd.Flags().StringP("yaml", "y", "", "The path to cluster topology yaml file")
+ uninstallCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
+ uninstallCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
+ uninstallCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
+}
diff --git a/cmd/cluster/upgrade.go b/cmd/cluster/upgrade.go
new file mode 100644
index 0000000..f128def
--- /dev/null
+++ b/cmd/cluster/upgrade.go
@@ -0,0 +1,83 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 cluster
+
+import (
+ "fmt"
+
+ "github.com/openGemini/gemix/pkg/cluster/manager"
+ "github.com/spf13/cobra"
+)
+
+var upgradeCmd = &cobra.Command{
+ Use: "upgrade",
+ Short: "upgrade cluster",
+ Long: `upgrade an openGemini cluster to the specified version`,
+ Run: func(cmd *cobra.Command, args []string) {
+ ops, err := getClusterOptions(cmd)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ var old_version string
+ if old_version, _ = cmd.Flags().GetString("old_version"); old_version == "" {
+ fmt.Println("the old_version is required")
+ return
+ }
+
+ err = UpgradeCluster(ops, old_version)
+ if err != nil {
+ fmt.Println(err)
+ }
+ },
+}
+
+func UpgradeCluster(ops manager.ClusterOptions, oldV string) error {
+ oldOps := ops
+ oldOps.Version = oldV
+
+ // stop all services
+ if err := StopCluster(oldOps); err != nil {
+ return err
+ }
+
+ // uninstall openGeini
+ if err := UninstallCluster(oldOps); err != nil {
+ return err
+ }
+
+ // install new cluster
+ if err := InstallCluster(ops); err != nil {
+ return err
+ }
+
+ // start new cluster
+ if err := StartCluster(ops); err != nil {
+ return err
+ }
+ fmt.Printf("Successfully upgraded the openGemini cluster from %s to %s\n", oldV, ops.Version)
+ return nil
+}
+
+func init() {
+ ClusterCmd.AddCommand(upgradeCmd)
+ upgradeCmd.Flags().StringP("version", "v", "", "component version")
+ upgradeCmd.Flags().StringP("old_version", "o", "", "component name")
+ upgradeCmd.Flags().StringP("yaml", "y", "", "The path to cluster topology yaml file")
+ upgradeCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
+ upgradeCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
+ upgradeCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
+}
diff --git a/cmd/deploy.go b/cmd/deploy.go
deleted file mode 100644
index 9328cd4..0000000
--- a/cmd/deploy.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-Copyright © 2023 NAME HERE
-*/
-package cmd
-
-import (
- "fmt"
- "openGemini-UP/pkg/deploy"
-
- "github.com/spf13/cobra"
-)
-
-// deployCmd represents the deploy command
-var deployCmd = &cobra.Command{
- Use: "deploy",
- Short: "deploy cluster",
- Long: `Deploy an openGemini cluster based on configuration files and version numbers.`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("--------------- Cluster deploying! ---------------")
-
- ops, err := getClusterOptions(cmd)
- if err != nil {
- fmt.Println(err)
- return
- }
-
- deployer := deploy.NewGeminiDeployer(ops)
- defer deployer.Close()
-
- if err := deployer.PrepareForDeploy(); err != nil {
- fmt.Println(err)
- return
- }
- if err := deployer.Deploy(); err != nil {
- fmt.Println(err)
- }
- fmt.Println("--------------- Successfully completed cluster deployment! ---------------")
- },
-}
-
-func init() {
- clusterCmd.AddCommand(deployCmd)
- deployCmd.Flags().StringP("version", "v", "", "component name")
- deployCmd.Flags().StringP("yaml", "y", "", "The path to cluster configuration yaml file")
- deployCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
- deployCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
- deployCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
-}
diff --git a/cmd/destroy.go b/cmd/destroy.go
deleted file mode 100644
index 9e6519e..0000000
--- a/cmd/destroy.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-Copyright © 2023 NAME HERE
-*/
-package cmd
-
-import (
- "fmt"
- "openGemini-UP/pkg/stop"
-
- "github.com/spf13/cobra"
-)
-
-// destroyCmd represents the list command
-var destroyCmd = &cobra.Command{
- Use: "destroy",
- Short: "destroy cluster",
- Long: `destroy an openGemini cluster based on configuration files. Stop all services and delete all logs and data`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("--------------- Cluster destroying! ---------------")
-
- ops, err := getClusterOptions(cmd)
- if err != nil {
- fmt.Println(err)
- return
- }
-
- // stop all services and delete all logs and data
- stop := stop.NewGeminiStop(ops, true)
- defer stop.Close()
-
- if err := stop.Prepare(); err != nil {
- fmt.Println(err)
- return
- }
- if err := stop.Run(); err != nil {
- fmt.Println(err)
- }
- fmt.Println("--------------- Successfully completed cluster destroy! ---------------")
- },
-}
-
-func init() {
- clusterCmd.AddCommand(destroyCmd)
- destroyCmd.Flags().StringP("yaml", "y", "", "The path to cluster configuration yaml file")
- destroyCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
- destroyCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
- destroyCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
-}
diff --git a/cmd/install.go b/cmd/install.go
index 1c4146f..40d5dd5 100644
--- a/cmd/install.go
+++ b/cmd/install.go
@@ -1,13 +1,24 @@
-/*
-Copyright © 2023 NAME HERE
-*/
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 (
"fmt"
- "openGemini-UP/pkg/download"
- "openGemini-UP/util"
+ "github.com/openGemini/gemix/pkg/cluster/operation"
+ "github.com/openGemini/gemix/util"
"github.com/spf13/cobra"
)
@@ -21,23 +32,29 @@ var installCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
version, _ := cmd.Flags().GetString("version")
if version == "" {
- version = util.Download_default_version
+ latestVer, err := util.GetLatestVerFromCurl()
+ if err != nil {
+ fmt.Println(err)
+ return
+ } else {
+ version = latestVer
+ }
}
os, _ := cmd.Flags().GetString("os")
if os == "" {
- os = util.Download_default_os
+ os = util.DownloadDefaultOs
}
arch, _ := cmd.Flags().GetString("arch")
if arch == "" {
- arch = util.Download_default_arch
+ arch = util.DownloadDefaultArch
}
- dOps := download.DownloadOptions{
+ dOps := operation.DownloadOptions{
Version: version,
Os: os,
Arch: arch,
}
- downloader := download.NewGeminiDownloader(dOps)
+ downloader := operation.NewGeminiDownloader(dOps)
if err := downloader.Run(); err != nil {
fmt.Println(err)
}
@@ -45,8 +62,8 @@ var installCmd = &cobra.Command{
}
func init() {
- rootCmd.AddCommand(installCmd)
- installCmd.Flags().StringP("version", "v", "", "component version; default is v1.0.0")
- installCmd.Flags().StringP("os", "o", "", "operating system, linux/darwin/windows; default is linux")
- installCmd.Flags().StringP("arch", "a", "", "Supported values: amd64, arm64; default is amd64")
+ RootCmd.AddCommand(installCmd)
+ installCmd.Flags().StringP("version", "v", "", "component version; default is the latest version")
+ installCmd.Flags().StringP("os", "o", "", "operating system, supported values: linux/darwin; default is linux")
+ installCmd.Flags().StringP("arch", "a", "", "system architecture, supported values: amd64/arm64; default is amd64")
}
diff --git a/cmd/list.go b/cmd/list.go
index ad2284c..e87c687 100644
--- a/cmd/list.go
+++ b/cmd/list.go
@@ -1,15 +1,26 @@
-/*
-Copyright © 2023 NAME HERE
-*/
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 (
"io/ioutil"
- "openGemini-UP/util"
"os"
"path/filepath"
"github.com/olekukonko/tablewriter"
+ "github.com/openGemini/gemix/util"
"github.com/spf13/cobra"
)
@@ -19,7 +30,7 @@ var listCmd = &cobra.Command{
Short: "list of database components",
Long: `Display the version information of all components currently downloaded.`,
Run: func(cmd *cobra.Command, args []string) {
- result := list(util.Download_dst)
+ result := list(util.DownloadDst)
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Version", "Component"})
@@ -36,7 +47,7 @@ var listCmd = &cobra.Command{
}
func init() {
- rootCmd.AddCommand(listCmd)
+ RootCmd.AddCommand(listCmd)
}
func list(path string) map[string][]string {
diff --git a/cmd/root.go b/cmd/root.go
index b9cb226..de97ac7 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,25 +1,63 @@
-/*
-Copyright © 2023 NAME HERE
-*/
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 (
"os"
+ "time"
+ "github.com/openGemini/gemix/cmd/cluster"
"github.com/spf13/cobra"
)
-// rootCmd represents the base command when called without any subcommands
-var rootCmd = &cobra.Command{
- Use: "openGemini-UP",
+// RootCmd represents the base command when called without any subcommands
+var RootCmd = &cobra.Command{
+ Use: "gemix",
Short: "One-click deployment and upgrade tool for openGemini",
- Long: `openGemini-UP is a one-click deployment and upgrade openGemini tool for users.
+ Long: `gemix is a one-click deployment and upgrade openGemini tool for users.
It can help users easily deploy openGemini clusters based on configuration files written by users.`,
}
func Execute() {
- err := rootCmd.Execute()
+ RootCmd.AddCommand(cluster.ClusterCmd)
+ err := RootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
+
+var (
+ Version string
+ Commit string
+ Branch string
+ BuildTime string
+)
+
+func init() {
+ // If commit, branch, or build time are not set, make that clear.
+ if Version == "" {
+ Version = "unknown"
+ }
+ if Commit == "" {
+ Commit = "unknown"
+ }
+ if Branch == "" {
+ Branch = "unknown"
+ }
+
+ if BuildTime == "" {
+ BuildTime = time.Now().UTC().String()
+ }
+}
diff --git a/cmd/start.go b/cmd/start.go
deleted file mode 100644
index 79db355..0000000
--- a/cmd/start.go
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-Copyright © 2023 NAME HERE
-*/
-package cmd
-
-import (
- "fmt"
- "openGemini-UP/pkg/deploy"
-
- "github.com/spf13/cobra"
-)
-
-// startCmd represents the start command
-var startCmd = &cobra.Command{
- Use: "start",
- Short: "start cluster",
- Long: `Start an openGemini cluster based on configuration files and version numbers after this cluster was stopped.`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("--------------- Cluster starting! ---------------")
-
- ops, err := getClusterOptions(cmd)
- if err != nil {
- fmt.Println(err)
- return
- }
-
- deployer := deploy.NewGeminiDeployer(ops)
- defer deployer.Close()
-
- if err := deployer.PrepareForStart(); err != nil {
- fmt.Println(err)
- return
- }
- if err := deployer.Start(); err != nil {
- fmt.Println(err)
- }
- fmt.Println("--------------- Successfully completed cluster start! ---------------")
- },
-}
-
-func init() {
- clusterCmd.AddCommand(startCmd)
- startCmd.Flags().StringP("yaml", "y", "", "The path to cluster configuration yaml file")
- startCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
- startCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
- startCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
-}
diff --git a/cmd/stop.go b/cmd/stop.go
deleted file mode 100644
index b6d612c..0000000
--- a/cmd/stop.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-Copyright © 2023 NAME HERE
-*/
-package cmd
-
-import (
- "fmt"
- "openGemini-UP/pkg/stop"
-
- "github.com/spf13/cobra"
-)
-
-// stopCmd represents the list command
-var stopCmd = &cobra.Command{
- Use: "stop",
- Short: "stop cluster",
- Long: `Stop an openGemini cluster based on configuration files.`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("--------------- Cluster stopping! ---------------")
-
- ops, err := getClusterOptions(cmd)
- if err != nil {
- fmt.Println(err)
- return
- }
-
- // stop all services
- stop := stop.NewGeminiStop(ops, false)
- defer stop.Close()
-
- if err := stop.Prepare(); err != nil {
- fmt.Println(err)
- return
- }
- if err := stop.Run(); err != nil {
- fmt.Println(err)
- }
- fmt.Println("--------------- Successfully completed cluster stop! ---------------")
- },
-}
-
-func init() {
- clusterCmd.AddCommand(stopCmd)
- stopCmd.Flags().StringP("yaml", "y", "", "The path to cluster configuration yaml file")
- stopCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
- stopCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
- stopCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
-}
diff --git a/cmd/upgrade.go b/cmd/upgrade.go
deleted file mode 100644
index 7e77041..0000000
--- a/cmd/upgrade.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package cmd
-
-import (
- "fmt"
- "openGemini-UP/pkg/deploy"
- "openGemini-UP/pkg/stop"
-
- "github.com/spf13/cobra"
-)
-
-var upgradeCmd = &cobra.Command{
- Use: "upgrade",
- Short: "upgrade cluster",
- Long: `upgrade an openGemini cluster to the specified version`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("--------------- Cluster upgrading! ---------------")
-
- ops, err := getClusterOptions(cmd)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println("upgrade to cluster version: ", ops.Version)
-
- // stop all services
- stop := stop.NewGeminiStop(ops, false)
- defer stop.Close()
- if err := stop.Prepare(); err != nil {
- fmt.Println(err)
- return
- }
- if err := stop.Run(); err != nil {
- fmt.Println(err)
- }
-
- // upload new bin files and start new services
- deployer := deploy.NewGeminiDeployer(ops)
- defer deployer.Close()
-
- if err := deployer.PrepareForDeploy(); err != nil {
- fmt.Println(err)
- return
- }
- if err := deployer.Deploy(); err != nil {
- fmt.Println(err)
- }
-
- fmt.Println("--------------- Successfully completed cluster upgrade! ---------------")
- },
-}
-
-func init() {
- clusterCmd.AddCommand(upgradeCmd)
- upgradeCmd.Flags().StringP("version", "v", "", "component name")
- upgradeCmd.Flags().StringP("yaml", "y", "", "The path to cluster configuration yaml file")
- upgradeCmd.Flags().StringP("user", "u", "", "The user name to login via SSH. The user must has root (or sudo) privilege.")
- upgradeCmd.Flags().StringP("key", "k", "", "The path of the SSH identity file. If specified, public key authentication will be used.")
- upgradeCmd.Flags().StringP("password", "p", "", "The password of target hosts. If specified, password authentication will be used.")
-}
diff --git a/cmd/version.go b/cmd/version.go
index 4aa42b8..00896ff 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -1,6 +1,17 @@
-/*
-Copyright © 2023 NAME HERE
-*/
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 (
@@ -12,14 +23,13 @@ import (
// versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
- Short: "openGemini-UP version",
- Long: `Display the version number of the management deployment tool openGemini-UP,
- currently there is only one version, the default is v0.0.1.`,
+ Short: "gemix version",
+ Long: `Display the version number of the management deployment tool gemix.`,
Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("openGemini-UP version 0.01")
+ fmt.Printf("version: %s, branch: %s, commit: %s, build time: %s\n", Version, Branch, Commit, BuildTime)
},
}
func init() {
- rootCmd.AddCommand(versionCmd)
+ RootCmd.AddCommand(versionCmd)
}
diff --git a/topology.example.yaml b/examples/cluster/topology.example.yaml
similarity index 99%
rename from topology.example.yaml
rename to examples/cluster/topology.example.yaml
index 5942f33..38458a0 100644
--- a/topology.example.yaml
+++ b/examples/cluster/topology.example.yaml
@@ -7,7 +7,7 @@ global:
log_dir: "/gemini-deploy/logs"
# Storage directory for cluster deployment files, startup scripts, and configuration files.
deploy_dir: "/gemini-deploy"
- # operating system, linux/darwin/windows.
+ # operating system, linux/darwin.
os: "linux"
# Supported values: "amd64", "arm64" (default: "amd64").
arch: "amd64"
diff --git a/go.mod b/go.mod
index 034e70b..2d65c7a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module openGemini-UP
+module github.com/openGemini/gemix
go 1.18
diff --git a/main.go b/main.go
index efde7c8..b7a4170 100644
--- a/main.go
+++ b/main.go
@@ -1,10 +1,22 @@
-/*
-Copyright © 2023 NAME HERE
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 "openGemini-UP/cmd"
+import (
+ "github.com/openGemini/gemix/cmd"
+)
func main() {
cmd.Execute()
diff --git a/pkg/config/config.go b/pkg/cluster/config/config.go
similarity index 71%
rename from pkg/config/config.go
rename to pkg/cluster/config/config.go
index f49db3a..8f8af4a 100644
--- a/pkg/config/config.go
+++ b/pkg/cluster/config/config.go
@@ -1,3 +1,17 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 config
type CommonConfig struct {
@@ -21,8 +35,8 @@ type Config struct {
}
type Configurator interface {
- Run() error
- RunWithoutGen() error
+ GenClusterConfs() error
+ BuildConfig() error
GetConfig() *Config
}
@@ -30,16 +44,15 @@ type GeminiConfigurator struct {
yamlPath string
tomlPath string
genPath string
- version string
conf *Config
+ yaml *Yaml
}
-func NewGeminiConfigurator(yPath, tPath, gPath, v string) Configurator {
+func NewGeminiConfigurator(yPath, tPath, gPath string) Configurator {
return &GeminiConfigurator{
yamlPath: yPath,
tomlPath: tPath,
genPath: gPath,
- version: v,
conf: &Config{
CommonConfig: &CommonConfig{},
SSHConfig: make(map[string]SSHConfig),
@@ -47,28 +60,24 @@ func NewGeminiConfigurator(yPath, tPath, gPath, v string) Configurator {
}
}
-func (c *GeminiConfigurator) Run() error {
+func (c *GeminiConfigurator) GenClusterConfs() error {
var err error
var t Toml
- var y Yaml
- if y, err = ReadFromYaml(c.yamlPath); err != nil {
- return err
- }
+ // generate new toml files
if t, err = ReadFromToml(c.tomlPath); err != nil {
return err
}
- GenConfs(y, t, c.genPath)
- c.buildFromYaml(y)
- return err
+ return GenConfs(*c.yaml, t, c.genPath)
}
-func (c *GeminiConfigurator) RunWithoutGen() error {
+func (c *GeminiConfigurator) BuildConfig() error {
var err error
var y Yaml
if y, err = ReadFromYaml(c.yamlPath); err != nil {
return err
}
c.buildFromYaml(y)
+ c.yaml = &y
return err
}
diff --git a/pkg/config/gen_conf.go b/pkg/cluster/config/gen_conf.go
similarity index 77%
rename from pkg/config/gen_conf.go
rename to pkg/cluster/config/gen_conf.go
index 3d5ad2e..ca56792 100644
--- a/pkg/config/gen_conf.go
+++ b/pkg/cluster/config/gen_conf.go
@@ -1,10 +1,25 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 config
import (
- "openGemini-UP/util"
"path/filepath"
"strconv"
"strings"
+
+ "github.com/openGemini/gemix/util"
)
type HostFile struct {
@@ -23,7 +38,7 @@ type MetaPorts struct {
Gossip int
}
-func GenConfs(y Yaml, template Toml, path string) {
+func GenConfs(y Yaml, template Toml, path string) error {
hosts := make(map[string]HostFile)
var metaPorts []MetaPorts
@@ -74,9 +89,8 @@ func GenConfs(y Yaml, template Toml, path string) {
}
}
- // generate corresponding openGemini.conf for ervery host.
+ // generate corresponding config files for every host.
for _, host := range hosts {
- fileName := filepath.Join(path, host.Ip+util.Remote_conf_suffix)
newToml := template
addr := host.Ip
@@ -160,6 +174,26 @@ func GenConfs(y Yaml, template Toml, path string) {
newToml.Gossip.BindAddress = addr
newToml.Gossip.Members = gossipMembers
- GenNewToml(newToml, fileName)
+ if host.HasMeta {
+ fileName := filepath.Join(path, host.Ip, util.RemoteMetaConfName)
+ if err := GenNewToml(newToml, fileName); err != nil {
+ return err
+ }
+ }
+
+ if host.HasSql {
+ fileName := filepath.Join(path, host.Ip, util.RemoteSqlConfName)
+ if err := GenNewToml(newToml, fileName); err != nil {
+ return err
+ }
+ }
+
+ if host.HasStore {
+ fileName := filepath.Join(path, host.Ip, util.RemoteStoreConfName)
+ if err := GenNewToml(newToml, fileName); err != nil {
+ return err
+ }
+ }
}
+ return nil
}
diff --git a/pkg/cluster/config/remote_host.go b/pkg/cluster/config/remote_host.go
new file mode 100644
index 0000000..d3dc2b9
--- /dev/null
+++ b/pkg/cluster/config/remote_host.go
@@ -0,0 +1,41 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 config
+
+type SSHType int32
+
+const (
+ SSH_UNKNOW SSHType = 0
+ SSH_PW SSHType = 1
+ SSH_KEY SSHType = 2
+)
+
+// used by install, exe, stop .etc
+type RemoteHost struct {
+ Ip string
+ SSHPort int
+ User string
+ Password string
+ KeyPath string
+ Typ SSHType
+ UpDataPath string
+ LogPath string
+}
+
+type UploadInfo struct {
+ LocalPath string
+ RemotePath string
+ FileName string
+}
diff --git a/pkg/cluster/config/toml.go b/pkg/cluster/config/toml.go
new file mode 100644
index 0000000..319f6c9
--- /dev/null
+++ b/pkg/cluster/config/toml.go
@@ -0,0 +1,112 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 config
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/BurntSushi/toml"
+)
+
+type Toml struct {
+ Common CommonToml `toml:"common"`
+ Meta MetaToml `toml:"meta"`
+ Http HttpToml `toml:"http"`
+ Data DataToml `toml:"data"`
+ Logging LoggingToml `toml:"logging"`
+ Gossip GossipToml `toml:"gossip"`
+}
+
+type CommonToml struct {
+ MetaJoin []string `toml:"meta-join"`
+}
+
+type MetaToml struct {
+ BindAddress string `toml:"bind-address"`
+ HttpBindAddress string `toml:"http-bind-address"`
+ RpcBindAddress string `toml:"rpc-bind-address"`
+ Dir string `toml:"dir"`
+}
+
+type HttpToml struct {
+ BindAddress string `toml:"bind-address"`
+}
+
+type DataToml struct {
+ StoreIngestAddr string `toml:"store-ingest-addr"`
+ StoreSelectAddr string `toml:"store-select-addr"`
+ StoreDataDir string `toml:"store-data-dir"`
+ StoreWalDir string `toml:"store-wal-dir"`
+ StoreMetaDir string `toml:"store-meta-dir"`
+ CacheTableDataBlock bool `toml:"cache-table-data-block"`
+ CacheTableMetaBlock bool `toml:"cache-table-meta-block"`
+ ReadCacheLimit int `toml:"read-cache-limit"`
+}
+
+type LoggingToml struct {
+ Path string `toml:"path"`
+}
+
+type GossipToml struct {
+ BindAddress string `toml:"bind-address"`
+ StoreBindPort int `toml:"store-bind-port"`
+ MetaBindPort int `toml:"meta-bind-port"`
+ Members []string `toml:"members"`
+}
+
+func ReadFromToml(tomlPath string) (Toml, error) {
+ t := Toml{}
+ var err error
+ if _, err = toml.DecodeFile(tomlPath, &t); err != nil {
+ return t, err
+ }
+ return t, nil
+}
+
+func ConvertToml(hostToml Toml, pwd string) Toml {
+ if len(hostToml.Meta.Dir) > 1 && hostToml.Meta.Dir[:1] == "~" {
+ hostToml.Meta.Dir = filepath.Join(pwd, hostToml.Meta.Dir[1:])
+ }
+ if len(hostToml.Data.StoreDataDir) > 1 && hostToml.Data.StoreDataDir[:1] == "~" {
+ hostToml.Data.StoreDataDir = filepath.Join(pwd, hostToml.Data.StoreDataDir[1:])
+ }
+ if len(hostToml.Data.StoreWalDir) > 1 && hostToml.Data.StoreWalDir[:1] == "~" {
+ hostToml.Data.StoreWalDir = filepath.Join(pwd, hostToml.Data.StoreWalDir[1:])
+ }
+ if len(hostToml.Data.StoreMetaDir) > 1 && hostToml.Data.StoreMetaDir[:1] == "~" {
+ hostToml.Data.StoreMetaDir = filepath.Join(pwd, hostToml.Data.StoreMetaDir[1:])
+ }
+ if len(hostToml.Logging.Path) > 1 && hostToml.Logging.Path[:1] == "~" {
+ hostToml.Logging.Path = filepath.Join(pwd, hostToml.Logging.Path[1:])
+ }
+ return hostToml
+}
+
+func GenNewToml(t Toml, path string) error {
+ dir := filepath.Dir(path)
+ if err := os.MkdirAll(dir, 0750); err != nil {
+ return err
+ }
+
+ f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ e := toml.NewEncoder(f)
+ return e.Encode(t)
+}
diff --git a/pkg/config/yaml.go b/pkg/cluster/config/yaml.go
similarity index 67%
rename from pkg/config/yaml.go
rename to pkg/cluster/config/yaml.go
index 5d33ae5..995ba40 100644
--- a/pkg/config/yaml.go
+++ b/pkg/cluster/config/yaml.go
@@ -1,6 +1,21 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 config
import (
+ "errors"
"io/ioutil"
"gopkg.in/yaml.v2"
@@ -64,6 +79,33 @@ type StoreYaml struct {
MetaDir string `yaml:"meta_dir"`
}
+func checkRequiredOptions(y Yaml) bool {
+ if y.Global.OS == "" || y.Global.Arch == "" {
+ return false
+ }
+ if y.Global.LogDir == "" || y.Global.DeployDir == "" || y.Global.SSHPort == 0 {
+ return false
+ }
+
+ for _, meta := range y.TsMeta {
+ if meta.Host == "" || meta.DataDir == "" {
+ return false
+ }
+ }
+ for _, sql := range y.TsSql {
+ if sql.Host == "" {
+ return false
+ }
+ }
+ for _, store := range y.TsStore {
+ if store.Host == "" || store.DataDir == "" || store.MetaDir == "" {
+ return false
+ }
+ }
+
+ return true
+}
+
func updataWithGlobalDefaults(y *Yaml) {
for i := range y.TsMeta {
if y.TsMeta[i].SSHPort == 0 {
@@ -111,6 +153,11 @@ func ReadFromYaml(yamlPath string) (Yaml, error) {
return Yaml{}, err
}
+ // check required options
+ if pass := checkRequiredOptions(y); !pass {
+ return Yaml{}, errors.New("missing requitred options for yaml configuration file")
+ }
+
// Update with default values
updataWithGlobalDefaults(&y)
diff --git a/pkg/cluster/manager/install.go b/pkg/cluster/manager/install.go
new file mode 100644
index 0000000..ec52ba4
--- /dev/null
+++ b/pkg/cluster/manager/install.go
@@ -0,0 +1,370 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 manager
+
+import (
+ "errors"
+ "fmt"
+ "path/filepath"
+ "sync"
+
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/pkg/cluster/operation"
+ "github.com/openGemini/gemix/util"
+ "github.com/pkg/sftp"
+ "golang.org/x/crypto/ssh"
+)
+
+type ClusterOptions struct {
+ Version string
+ User string
+ Key string
+ Password string
+ SshType config.SSHType
+ YamlPath string
+}
+
+type UploadAction struct {
+ uploadInfo []*config.UploadInfo
+ remoteHost *config.RemoteHost
+}
+
+type Installer interface {
+ PrepareForInstall() error
+ Install() error
+ Close()
+}
+
+type GeminiInstaller struct {
+ version string
+ // ip -> remotes
+ remotes map[string]*config.RemoteHost
+ uploads map[string]*UploadAction
+
+ // ip -> ssh clients
+ sshClients map[string]*ssh.Client
+ sftpClients map[string]*sftp.Client
+
+ configurator config.Configurator // conf reader
+ executor operation.Executor // execute commands on remote host
+
+ clusterOptions ClusterOptions
+
+ wg sync.WaitGroup
+}
+
+func NewGeminiInstaller(ops ClusterOptions) Installer {
+ return &GeminiInstaller{
+ remotes: make(map[string]*config.RemoteHost),
+ uploads: make(map[string]*UploadAction),
+ sshClients: make(map[string]*ssh.Client),
+ sftpClients: make(map[string]*sftp.Client),
+ version: ops.Version,
+ configurator: config.NewGeminiConfigurator(ops.YamlPath, filepath.Join(util.DownloadDst, ops.Version, util.LocalEtcRelPath, util.LocalConfName), filepath.Join(util.DownloadDst, ops.Version, util.LocalEtcRelPath)),
+ clusterOptions: ops,
+ }
+}
+
+func (d *GeminiInstaller) PrepareForInstall() error {
+ var err error
+ if err = d.configurator.BuildConfig(); err != nil {
+ return err
+ }
+ conf := d.configurator.GetConfig()
+
+ dOps := operation.DownloadOptions{
+ Version: d.version,
+ Os: conf.CommonConfig.Os,
+ Arch: conf.CommonConfig.Arch,
+ }
+ downloader := operation.NewGeminiDownloader(dOps)
+ if err = downloader.Run(); err != nil {
+ return err
+ }
+
+ if err = d.configurator.GenClusterConfs(); err != nil {
+ return err
+ }
+
+ // check the internet with all the remote servers
+ if err = d.prepareRemotes(conf, true); err != nil {
+ fmt.Printf("Failed to establish SSH connections with all remote servers. The specific error is: %s\n", err)
+ return err
+ }
+ fmt.Println("Success to establish SSH connections with all remote servers.")
+
+ d.executor = operation.NewGeminiExecutor(d.sshClients)
+
+ if err = d.prepareForUpload(); err != nil {
+ return err
+ }
+
+ if err = d.prepareUploadActions(conf); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *GeminiInstaller) prepareRemotes(c *config.Config, needSftp bool) error {
+ if c == nil {
+ return util.ErrUnexpectedNil
+ }
+
+ for ip, ssh := range c.SSHConfig {
+ d.remotes[ip] = &config.RemoteHost{
+ Ip: ip,
+ SSHPort: ssh.Port,
+ UpDataPath: ssh.UpDataPath,
+ LogPath: ssh.LogPath,
+ User: d.clusterOptions.User,
+ Typ: d.clusterOptions.SshType,
+ Password: d.clusterOptions.Password,
+ KeyPath: d.clusterOptions.Key,
+ }
+ }
+
+ if err := d.tryConnect(c, needSftp); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *GeminiInstaller) tryConnect(c *config.Config, needSftp bool) error {
+ for ip, r := range d.remotes {
+ var err error
+ var sshClient *ssh.Client
+ switch r.Typ {
+ case config.SSH_PW:
+ sshClient, err = util.NewSSH_PW(r.User, r.Password, r.Ip, r.SSHPort)
+ case config.SSH_KEY:
+ sshClient, err = util.NewSSH_Key(r.User, r.KeyPath, r.Ip, r.SSHPort)
+
+ }
+ if err != nil {
+ return err
+ }
+ d.sshClients[ip] = sshClient
+
+ if needSftp {
+ sftpClient, err := util.NewSftpClient(sshClient)
+ if err != nil {
+ return err
+ }
+ d.sftpClients[ip] = sftpClient
+
+ pwd, _ := sftpClient.Getwd()
+ // Convert relative paths to absolute paths.
+ if len(r.UpDataPath) > 1 && r.UpDataPath[:1] == "~" {
+ r.UpDataPath = filepath.Join(pwd, r.UpDataPath[1:])
+ }
+ }
+ }
+ if needSftp {
+ for _, host := range c.CommonConfig.MetaHosts {
+ pwd, _ := d.sftpClients[host].Getwd()
+ confPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, host, util.RemoteMetaConfName)
+ hostToml, _ := config.ReadFromToml(confPath)
+ // Convert relative paths in openGemini.conf to absolute paths.
+ hostToml = config.ConvertToml(hostToml, pwd)
+ if err := config.GenNewToml(hostToml, confPath); err != nil {
+ return err
+ }
+ }
+ for _, host := range c.CommonConfig.SqlHosts {
+ pwd, _ := d.sftpClients[host].Getwd()
+ confPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, host, util.RemoteSqlConfName)
+ hostToml, _ := config.ReadFromToml(confPath)
+ // Convert relative paths in openGemini.conf to absolute paths.
+ hostToml = config.ConvertToml(hostToml, pwd)
+ if err := config.GenNewToml(hostToml, confPath); err != nil {
+ return err
+ }
+ }
+ for _, host := range c.CommonConfig.StoreHosts {
+ pwd, _ := d.sftpClients[host].Getwd()
+ confPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, host, util.RemoteStoreConfName)
+ hostToml, _ := config.ReadFromToml(confPath)
+ // Convert relative paths in openGemini.conf to absolute paths.
+ hostToml = config.ConvertToml(hostToml, pwd)
+ if err := config.GenNewToml(hostToml, confPath); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (d *GeminiInstaller) prepareForUpload() error {
+ if d.executor == nil {
+ return util.ErrUnexpectedNil
+ }
+ for ip, r := range d.remotes {
+ binPath := filepath.Join(r.UpDataPath, d.version, util.RemoteBinRelPath)
+ etcPath := filepath.Join(r.UpDataPath, d.version, util.RemoteEtcRelPath)
+ command := fmt.Sprintf("mkdir -p %s; mkdir -p %s;", binPath, etcPath)
+ if _, err := d.executor.ExecCommand(ip, command); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (d *GeminiInstaller) prepareUploadActions(c *config.Config) error {
+ // ts-meta(bin and config files)
+ for _, host := range c.CommonConfig.MetaHosts {
+ if d.uploads[host] == nil {
+ d.uploads[host] = &UploadAction{
+ remoteHost: d.remotes[host],
+ }
+ }
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: filepath.Join(util.DownloadDst, d.version, util.LocalBinRelPath, util.TsMeta),
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteBinRelPath),
+ FileName: util.TsMeta,
+ })
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, host, util.RemoteMetaConfName),
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath),
+ FileName: util.RemoteMetaConfName,
+ })
+ }
+
+ // ts-sql(bin and config files)
+ for _, host := range c.CommonConfig.SqlHosts {
+ if d.uploads[host] == nil {
+ d.uploads[host] = &UploadAction{
+ remoteHost: d.remotes[host],
+ }
+ }
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: filepath.Join(util.DownloadDst, d.version, util.LocalBinRelPath, util.TsSql),
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteBinRelPath),
+ FileName: util.TsSql,
+ })
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, host, util.RemoteSqlConfName),
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath),
+ FileName: util.RemoteSqlConfName,
+ })
+ }
+
+ // ts-store(bin and config files)
+ for _, host := range c.CommonConfig.StoreHosts {
+ if d.uploads[host] == nil {
+ d.uploads[host] = &UploadAction{
+ remoteHost: d.remotes[host],
+ }
+ }
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: filepath.Join(util.DownloadDst, d.version, util.LocalBinRelPath, util.TsStore),
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteBinRelPath),
+ FileName: util.TsStore,
+ })
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, host, util.RemoteStoreConfName),
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath),
+ FileName: util.RemoteStoreConfName,
+ })
+ }
+
+ // script
+ for host := range c.SSHConfig {
+ if d.uploads[host] == nil {
+ d.uploads[host] = &UploadAction{
+ remoteHost: d.remotes[host],
+ }
+ }
+ d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
+ LocalPath: util.InstallScriptPath,
+ RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath),
+ FileName: util.InstallScript,
+ })
+ }
+
+ return nil
+}
+
+func (d *GeminiInstaller) Install() error {
+ fmt.Println("Start to install openGemini...")
+ errChan := make(chan error, len(d.uploads))
+ var wgp sync.WaitGroup
+ wgp.Add(2)
+
+ go func() {
+ defer wgp.Done()
+ d.wg.Add(len(d.uploads))
+ for ip, action := range d.uploads {
+ go func(ip string, action *UploadAction, errChan chan error) {
+ defer d.wg.Done()
+ for _, c := range action.uploadInfo {
+ // check whether need to upload the file
+ // only support Linux
+ cmd := fmt.Sprintf("if [ -f %s ]; then echo 'File exists'; else echo 'File not found'; fi", filepath.Join(c.RemotePath, c.FileName))
+ output, err := d.executor.ExecCommand(ip, cmd)
+ if string(output) == "File exists\n" && err == nil {
+ fmt.Printf("%s exists on %s.\n", c.FileName, c.RemotePath)
+ } else {
+ if err := util.UploadFile(action.remoteHost.Ip, c.LocalPath, c.RemotePath, d.sftpClients[action.remoteHost.Ip]); err != nil {
+ fmt.Printf("upload %s to %s error: %v\n", c.LocalPath, action.remoteHost.Ip, err)
+ errChan <- err
+ }
+ }
+ }
+
+ }(ip, action, errChan)
+ }
+ d.wg.Wait()
+ close(errChan)
+ }()
+
+ var has_err = false
+ go func() {
+ defer wgp.Done()
+ for {
+ err, ok := <-errChan
+ if !ok {
+ break
+ }
+ fmt.Println(err)
+ has_err = true
+ }
+ }()
+
+ wgp.Wait()
+ if has_err {
+ return errors.New("install cluster failed")
+ } else {
+ return nil
+ }
+}
+
+func (d *GeminiInstaller) Close() {
+ var err error
+ for _, sftp := range d.sftpClients {
+ if sftp != nil {
+ if err = sftp.Close(); err != nil {
+ fmt.Println(err)
+ }
+ }
+ }
+
+ for _, ssh := range d.sshClients {
+ if err = ssh.Close(); err != nil {
+ fmt.Println(err)
+ }
+ }
+}
diff --git a/pkg/cluster/manager/start.go b/pkg/cluster/manager/start.go
new file mode 100644
index 0000000..afebc42
--- /dev/null
+++ b/pkg/cluster/manager/start.go
@@ -0,0 +1,425 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 manager
+
+import (
+ "errors"
+ "fmt"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/pkg/cluster/operation"
+ "github.com/openGemini/gemix/util"
+ "golang.org/x/crypto/ssh"
+)
+
+type Starter interface {
+ PrepareForStart() error
+ Start() error
+ Close()
+}
+
+type GeminiStarter struct {
+ version string
+ // ip -> remotes
+ remotes map[string]*config.RemoteHost
+ runs *operation.RunActions
+
+ // ip -> ssh clients
+ sshClients map[string]*ssh.Client
+
+ configurator config.Configurator // conf reader
+ executor operation.Executor // execute commands on remote host
+
+ clusterOptions ClusterOptions
+
+ wg sync.WaitGroup
+}
+
+func NewGeminiStarter(ops ClusterOptions) Starter {
+ return &GeminiStarter{
+ remotes: make(map[string]*config.RemoteHost),
+ sshClients: make(map[string]*ssh.Client),
+ version: ops.Version,
+ configurator: config.NewGeminiConfigurator(ops.YamlPath, filepath.Join(util.DownloadDst, ops.Version, util.LocalEtcRelPath, util.LocalConfName), filepath.Join(util.DownloadDst, ops.Version, util.LocalEtcRelPath)),
+ runs: &operation.RunActions{},
+ clusterOptions: ops,
+ }
+}
+
+func (d *GeminiStarter) PrepareForStart() error {
+ var err error
+ if err = d.configurator.BuildConfig(); err != nil {
+ return err
+ }
+ conf := d.configurator.GetConfig()
+
+ if err = d.prepareRemotes(conf, false); err != nil {
+ fmt.Printf("Failed to establish SSH connections with all remote servers. The specific error is: %s\n", err)
+ return err
+ }
+ fmt.Println("Success to establish SSH connections with all remote servers.")
+
+ d.executor = operation.NewGeminiExecutor(d.sshClients)
+
+ // check process conflict and port conflict
+ if d.checkProcessConflict() {
+ return errors.New("process conflict before starting")
+ }
+ if conflicted, port, ip := d.checkPortConflict(conf); conflicted {
+ return fmt.Errorf("port %d conflict in %s before starting", port, ip)
+ }
+
+ if err = d.prepareRunActions(conf); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (d *GeminiStarter) prepareRemotes(c *config.Config, needSftp bool) error {
+ if c == nil {
+ return util.ErrUnexpectedNil
+ }
+
+ for ip, ssh := range c.SSHConfig {
+ d.remotes[ip] = &config.RemoteHost{
+ Ip: ip,
+ SSHPort: ssh.Port,
+ UpDataPath: ssh.UpDataPath,
+ LogPath: ssh.LogPath,
+ User: d.clusterOptions.User,
+ Typ: d.clusterOptions.SshType,
+ Password: d.clusterOptions.Password,
+ KeyPath: d.clusterOptions.Key,
+ }
+ }
+
+ if err := d.tryConnect(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *GeminiStarter) tryConnect() error {
+ for ip, r := range d.remotes {
+ var err error
+ var sshClient *ssh.Client
+ switch r.Typ {
+ case config.SSH_PW:
+ sshClient, err = util.NewSSH_PW(r.User, r.Password, r.Ip, r.SSHPort)
+ case config.SSH_KEY:
+ sshClient, err = util.NewSSH_Key(r.User, r.KeyPath, r.Ip, r.SSHPort)
+
+ }
+ if err != nil {
+ return err
+ }
+ d.sshClients[ip] = sshClient
+ }
+ return nil
+}
+
+func (d *GeminiStarter) prepareRunActions(c *config.Config) error {
+ // ts-meta
+ i := 1
+ for _, host := range c.CommonConfig.MetaHosts {
+ d.runs.MetaAction = append(d.runs.MetaAction, &operation.RunAction{
+ Info: &operation.RunInfo{
+ ScriptPath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath, util.InstallScript),
+ Args: []string{util.TsMeta, d.remotes[host].LogPath,
+ filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteBinRelPath, util.TsMeta),
+ filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath, util.RemoteMetaConfName),
+ filepath.Join(d.remotes[host].LogPath, util.RemotePidPath, util.TsMeta+util.RemotePidSuffix),
+ filepath.Join(d.remotes[host].LogPath, util.MetaExtraLog+util.RemoteLogSuffix)},
+ },
+ Remote: d.remotes[host],
+ })
+ i++
+ }
+
+ // ts-sql
+ i = 1
+ for _, host := range c.CommonConfig.SqlHosts {
+ d.runs.SqlAction = append(d.runs.SqlAction, &operation.RunAction{
+ Info: &operation.RunInfo{
+ ScriptPath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath, util.InstallScript),
+ Args: []string{util.TsSql, d.remotes[host].LogPath,
+ filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteBinRelPath, util.TsSql),
+ filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath, util.RemoteSqlConfName),
+ filepath.Join(d.remotes[host].LogPath, util.RemotePidPath, util.TsSql+util.RemotePidSuffix),
+ filepath.Join(d.remotes[host].LogPath, util.SqlExtraLog+util.RemoteLogSuffix)},
+ },
+ Remote: d.remotes[host],
+ })
+ i++
+ }
+
+ // ts-store
+ i = 1
+ for _, host := range c.CommonConfig.StoreHosts {
+ d.runs.StoreAction = append(d.runs.StoreAction, &operation.RunAction{
+ Info: &operation.RunInfo{
+ ScriptPath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath, util.InstallScript),
+ Args: []string{util.TsStore, d.remotes[host].LogPath,
+ filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteBinRelPath, util.TsStore),
+ filepath.Join(d.remotes[host].UpDataPath, d.version, util.RemoteEtcRelPath, util.RemoteStoreConfName),
+ filepath.Join(d.remotes[host].LogPath, util.RemotePidPath, util.TsStore+util.RemotePidSuffix),
+ filepath.Join(d.remotes[host].LogPath, util.StoreExtraLog+util.RemoteLogSuffix)},
+ },
+ Remote: d.remotes[host],
+ })
+ i++
+ }
+
+ return nil
+}
+
+func (d *GeminiStarter) checkProcessConflict() bool {
+ for ip := range d.remotes {
+ output, err := d.executor.ExecCommand(ip, CheckProcessCommand)
+ if err != nil {
+ fmt.Println(err)
+ return true
+ } else {
+ if output != "" {
+ fmt.Printf("process conflict in remote %s\n", ip)
+ fmt.Println(output)
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func (d *GeminiStarter) checkPortConflict(conf *config.Config) (bool, int, string) {
+ // check port conflict about ts-meta
+ for _, ip := range conf.CommonConfig.MetaHosts {
+ tomlPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, ip, util.RemoteMetaConfName)
+ t, err := config.ReadFromToml(tomlPath)
+ if err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ }
+
+ if conflicted, port, err := d.checkOnePortWithStr(ip, t.Meta.BindAddress); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+
+ if conflicted, port, err := d.checkOnePortWithStr(ip, t.Meta.HttpBindAddress); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+
+ if conflicted, port, err := d.checkOnePortWithStr(ip, t.Meta.RpcBindAddress); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+
+ if conflicted, port, err := d.checkOnePortWithInt(ip, t.Gossip.MetaBindPort); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+ }
+
+ // check port conflict about ts-sql
+ for _, ip := range conf.CommonConfig.SqlHosts {
+ tomlPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, ip, util.RemoteSqlConfName)
+ t, err := config.ReadFromToml(tomlPath)
+ if err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ }
+
+ if conflicted, port, err := d.checkOnePortWithStr(ip, t.Http.BindAddress); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+ }
+
+ // check port conflict about ts-store
+ for _, ip := range conf.CommonConfig.StoreHosts {
+ tomlPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, ip, util.RemoteStoreConfName)
+ t, err := config.ReadFromToml(tomlPath)
+ if err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ }
+
+ if conflicted, port, err := d.checkOnePortWithStr(ip, t.Data.StoreIngestAddr); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+
+ if conflicted, port, err := d.checkOnePortWithStr(ip, t.Data.StoreSelectAddr); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+
+ if conflicted, port, err := d.checkOnePortWithInt(ip, t.Gossip.StoreBindPort); err != nil {
+ fmt.Println(err)
+ return true, 0, ip
+ } else {
+ if conflicted {
+ return true, port, ip
+ }
+ }
+ }
+
+ return false, 0, ""
+}
+
+func (d *GeminiStarter) checkOnePortWithStr(ip, inputStr string) (bool, int, error) {
+ parts := strings.Split(inputStr, ":")
+ if len(parts) < 2 {
+ return true, 0, errors.New("invalid inputStr format when check one port conflict")
+ }
+ portStr := parts[1]
+
+ port, err := strconv.Atoi(portStr)
+ if err != nil {
+ return true, port, err
+ }
+
+ output, err := d.executor.ExecCommand(ip, GenCheckPortCommand(port))
+ if err != nil {
+ return true, port, err
+ } else {
+ if strings.Contains(output, "yes") {
+ return true, port, nil
+ } else if strings.Contains(output, "no") {
+ return false, port, nil
+ }
+ }
+ return true, port, errors.New("unexpected port conflict check result")
+}
+
+func (d *GeminiStarter) checkOnePortWithInt(ip string, port int) (bool, int, error) {
+ output, err := d.executor.ExecCommand(ip, GenCheckPortCommand(port))
+ if err != nil {
+ return true, port, err
+ } else {
+ if strings.Contains(output, "yes") {
+ return true, port, nil
+ } else if strings.Contains(output, "no") {
+ return false, port, nil
+ }
+ }
+ return true, port, errors.New("unexpected port conflict check result")
+}
+
+func (d *GeminiStarter) Start() error {
+ if d.executor == nil {
+ return nil
+ }
+ errChan := make(chan error, len(d.runs.MetaAction)+len(d.runs.SqlAction)+len(d.runs.StoreAction))
+ var wgp sync.WaitGroup
+ wgp.Add(2)
+
+ go func() {
+ defer wgp.Done()
+ // start all ts-meta concurrently
+ d.wg.Add(len(d.runs.MetaAction))
+ for _, action := range d.runs.MetaAction {
+ go func(action *operation.RunAction, errChan chan error) {
+ defer d.wg.Done()
+ d.executor.ExecRunAction(action, errChan)
+ }(action, errChan)
+ }
+ d.wg.Wait()
+
+ // time for ts-meta campaign
+ time.Sleep(time.Second)
+
+ // start all ts-store and ts-sql concurrently
+ d.wg.Add(len(d.runs.SqlAction) + len(d.runs.StoreAction))
+ for _, action := range d.runs.StoreAction {
+ go func(action *operation.RunAction, errChan chan error) {
+ defer d.wg.Done()
+ d.executor.ExecRunAction(action, errChan)
+ }(action, errChan)
+ }
+ for _, action := range d.runs.SqlAction {
+ go func(action *operation.RunAction, errChan chan error) {
+ defer d.wg.Done()
+ d.executor.ExecRunAction(action, errChan)
+ }(action, errChan)
+ }
+ d.wg.Wait()
+ close(errChan)
+ }()
+
+ var has_err = false
+ go func() {
+ defer wgp.Done()
+ for {
+ err, ok := <-errChan
+ if !ok {
+ break
+ }
+ fmt.Println(err)
+ has_err = true
+ }
+ }()
+
+ wgp.Wait()
+ if has_err {
+ return errors.New("start cluster failed")
+ } else {
+ return nil
+ }
+}
+
+func (d *GeminiStarter) Close() {
+ var err error
+ for _, ssh := range d.sshClients {
+ if err = ssh.Close(); err != nil {
+ fmt.Println(err)
+ }
+ }
+}
diff --git a/pkg/cluster/manager/status.go b/pkg/cluster/manager/status.go
new file mode 100644
index 0000000..761e904
--- /dev/null
+++ b/pkg/cluster/manager/status.go
@@ -0,0 +1,394 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 manager
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/olekukonko/tablewriter"
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/pkg/cluster/operation"
+ "github.com/openGemini/gemix/util"
+ "golang.org/x/crypto/ssh"
+)
+
+const CheckProcessCommand = "ps aux | grep -E '(ts-meta|ts-sql|ts-store)' | grep -v grep | awk '{print $11}'"
+const CheckDiskCapacityCommand = "df -h | grep '^/dev/'"
+
+func GenCheckPortCommand(port int) string {
+ return fmt.Sprintf("ss -tln | grep -q ':%d' && echo 'yes' || echo 'no'", port)
+}
+
+type ClusterStatusPerServer struct {
+ Ip string
+ RunningProcesses []string // ts-meta,ts-sql,ts-store
+ PortOccupancy map[int]bool // port->occupancy or not
+ DiskCapacity []string // disk->capacity
+}
+
+type StatusPatroller interface {
+ PrepareForPatrol() error
+ Patrol() error
+ Close()
+}
+
+type GeminiStatusPatroller struct {
+ version string
+ conf *config.Config
+ // ip -> remotes
+ remotes map[string]*config.RemoteHost
+
+ // ip -> ssh clients
+ sshClients map[string]*ssh.Client
+
+ configurator config.Configurator // conf reader
+ executor operation.Executor // execute commands on remote host
+
+ clusterOptions ClusterOptions
+
+ wg sync.WaitGroup
+}
+
+func NewGeminiStatusPatroller(ops ClusterOptions) StatusPatroller {
+ return &GeminiStatusPatroller{
+ version: ops.Version,
+ remotes: make(map[string]*config.RemoteHost),
+ sshClients: make(map[string]*ssh.Client),
+ configurator: config.NewGeminiConfigurator(ops.YamlPath, "", ""),
+ clusterOptions: ops,
+ }
+}
+
+func (d *GeminiStatusPatroller) PrepareForPatrol() error {
+ var err error
+ if err = d.configurator.BuildConfig(); err != nil {
+ return err
+ }
+ d.conf = d.configurator.GetConfig()
+
+ // check the internet with all the remote servers
+ if err = d.prepareRemotes(d.conf); err != nil {
+ fmt.Printf("Failed to establish SSH connections with all remote servers. The specific error is: %s\n", err)
+ return err
+ }
+ fmt.Println("Success to establish SSH connections with all remote servers.")
+
+ d.executor = operation.NewGeminiExecutor(d.sshClients)
+ return nil
+}
+
+func (d *GeminiStatusPatroller) prepareRemotes(c *config.Config) error {
+ if c == nil {
+ return util.ErrUnexpectedNil
+ }
+
+ for ip, ssh := range c.SSHConfig {
+ d.remotes[ip] = &config.RemoteHost{
+ Ip: ip,
+ SSHPort: ssh.Port,
+ UpDataPath: ssh.UpDataPath,
+ LogPath: ssh.LogPath,
+ User: d.clusterOptions.User,
+ Typ: d.clusterOptions.SshType,
+ Password: d.clusterOptions.Password,
+ KeyPath: d.clusterOptions.Key,
+ }
+ }
+
+ if err := d.tryConnect(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *GeminiStatusPatroller) tryConnect() error {
+ for ip, r := range d.remotes {
+ var err error
+ var sshClient *ssh.Client
+ switch r.Typ {
+ case config.SSH_PW:
+ sshClient, err = util.NewSSH_PW(r.User, r.Password, r.Ip, r.SSHPort)
+ case config.SSH_KEY:
+ sshClient, err = util.NewSSH_Key(r.User, r.KeyPath, r.Ip, r.SSHPort)
+
+ }
+ if err != nil {
+ return err
+ }
+ d.sshClients[ip] = sshClient
+ }
+ return nil
+}
+
+func (d *GeminiStatusPatroller) Patrol() error {
+ statusChan := make(chan ClusterStatusPerServer, len(d.remotes))
+ errChan := make(chan error, len(d.remotes))
+ var wgp sync.WaitGroup
+ wgp.Add(3)
+ go func() {
+ defer wgp.Done()
+ d.wg.Add(len(d.remotes))
+ for ip := range d.remotes {
+ go d.patrolOneServer(ip, statusChan, errChan)
+ }
+ d.wg.Wait()
+ close(errChan)
+ close(statusChan)
+ }()
+
+ var has_err = false
+ go func() {
+ defer wgp.Done()
+ for {
+ err, ok := <-errChan
+ if !ok {
+ return
+ }
+ fmt.Println(err)
+ has_err = true
+ }
+ }()
+
+ go func() {
+ defer wgp.Done()
+ for {
+ status, ok := <-statusChan
+ if !ok {
+ return
+ }
+ displayGeminiStatus(status)
+ }
+ }()
+
+ wgp.Wait()
+ if has_err {
+ return errors.New("check cluster status failed")
+ }
+ return nil
+}
+
+func (d *GeminiStatusPatroller) patrolOneServer(ip string, statusChan chan ClusterStatusPerServer, errChan chan error) {
+ defer d.wg.Done()
+ var status = ClusterStatusPerServer{
+ Ip: ip,
+ PortOccupancy: make(map[int]bool),
+ }
+ var err error
+
+ // check running process
+ output, err := d.executor.ExecCommand(ip, CheckProcessCommand)
+ if err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.RunningProcesses = strings.Split(output, "\n")
+ }
+
+ // check port status about ts-meta
+ for _, i := range d.conf.CommonConfig.MetaHosts {
+ if ip == i {
+ tomlPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, ip, util.RemoteMetaConfName)
+ t, err := config.ReadFromToml(tomlPath)
+ if err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ }
+
+ if occupied, port, err := d.checkOnePortWithStr(ip, t.Meta.BindAddress); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+
+ if occupied, port, err := d.checkOnePortWithStr(ip, t.Meta.HttpBindAddress); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+
+ if occupied, port, err := d.checkOnePortWithStr(ip, t.Meta.RpcBindAddress); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+
+ if occupied, port, err := d.checkOnePortWithInt(ip, t.Gossip.MetaBindPort); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+ }
+ }
+ // check port status about ts-sql
+ for _, i := range d.conf.CommonConfig.SqlHosts {
+ if ip == i {
+ tomlPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, ip, util.RemoteSqlConfName)
+ t, err := config.ReadFromToml(tomlPath)
+ if err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ }
+
+ if occupied, port, err := d.checkOnePortWithStr(ip, t.Http.BindAddress); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+ }
+ }
+ // check port status about ts-store
+ for _, i := range d.conf.CommonConfig.StoreHosts {
+ if ip == i {
+ tomlPath := filepath.Join(util.DownloadDst, d.version, util.LocalEtcRelPath, ip, util.RemoteStoreConfName)
+ t, err := config.ReadFromToml(tomlPath)
+ if err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ }
+
+ if occupied, port, err := d.checkOnePortWithStr(ip, t.Data.StoreIngestAddr); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+
+ if occupied, port, err := d.checkOnePortWithStr(ip, t.Data.StoreSelectAddr); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+
+ if occupied, port, err := d.checkOnePortWithInt(ip, t.Gossip.StoreBindPort); err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.PortOccupancy[port] = occupied
+ }
+ }
+ }
+
+ // check disk capacity
+ output, err = d.executor.ExecCommand(ip, CheckDiskCapacityCommand)
+ if err != nil {
+ fmt.Println(err)
+ errChan <- err
+ return
+ } else {
+ status.DiskCapacity = strings.Split(output, "\n")
+ }
+ statusChan <- status
+}
+
+func (d *GeminiStatusPatroller) checkOnePortWithStr(ip, inputStr string) (bool, int, error) {
+ parts := strings.Split(inputStr, ":")
+ if len(parts) < 2 {
+ return true, 0, errors.New("invalid inputStr format when check one port status")
+ }
+ portStr := parts[1]
+
+ port, err := strconv.Atoi(portStr)
+ if err != nil {
+ return true, port, err
+ }
+
+ output, err := d.executor.ExecCommand(ip, GenCheckPortCommand(port))
+ if err != nil {
+ return true, port, err
+ } else {
+ if strings.Contains(output, "yes") {
+ return true, port, nil
+ } else if strings.Contains(output, "no") {
+ return false, port, nil
+ }
+ }
+ return true, port, errors.New("unexpected port status result")
+}
+
+func (d *GeminiStatusPatroller) checkOnePortWithInt(ip string, port int) (bool, int, error) {
+ output, err := d.executor.ExecCommand(ip, GenCheckPortCommand(port))
+ if err != nil {
+ return true, port, err
+ } else {
+ if strings.Contains(output, "yes") {
+ return true, port, nil
+ } else if strings.Contains(output, "no") {
+ return false, port, nil
+ }
+ }
+ return true, port, errors.New("unexpected port status result")
+}
+
+func (d *GeminiStatusPatroller) Close() {
+ var err error
+ for _, ssh := range d.sshClients {
+ if err = ssh.Close(); err != nil {
+ fmt.Println(err)
+ }
+ }
+}
+
+func displayGeminiStatus(status ClusterStatusPerServer) {
+ fmt.Printf("\nGemini status of server %s\n", status.Ip)
+
+ // Create a new table for Running Processes
+ runningProcessesTable := tablewriter.NewWriter(os.Stdout)
+ runningProcessesTable.SetHeader([]string{"Running Processes"})
+ for _, process := range status.RunningProcesses {
+ runningProcessesTable.Append([]string{fmt.Sprintf("%v", process)})
+ }
+ runningProcessesTable.Render()
+
+ // Create a new table for Port Occupancy
+ portOccupancyTable := tablewriter.NewWriter(os.Stdout)
+ portOccupancyTable.SetHeader([]string{"Port Occupancy"})
+ for port, occupied := range status.PortOccupancy {
+ portOccupancyTable.Append([]string{fmt.Sprintf("Port %d: Occupied: %v", port, occupied)})
+ }
+ portOccupancyTable.Render()
+
+ // Create a new table for Disk Capacity
+ diskCapacityTable := tablewriter.NewWriter(os.Stdout)
+ diskCapacityTable.SetHeader([]string{"Disk Capacity"})
+ for _, capacity := range status.DiskCapacity {
+ diskCapacityTable.Append([]string{fmt.Sprintf("%v", capacity)})
+ }
+ diskCapacityTable.Render()
+}
diff --git a/pkg/stop/stop.go b/pkg/cluster/manager/stop.go
similarity index 62%
rename from pkg/stop/stop.go
rename to pkg/cluster/manager/stop.go
index 9468cc2..45a838d 100644
--- a/pkg/stop/stop.go
+++ b/pkg/cluster/manager/stop.go
@@ -1,13 +1,26 @@
-package stop
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 manager
import (
"fmt"
- "openGemini-UP/pkg/config"
- "openGemini-UP/pkg/deploy"
- "openGemini-UP/pkg/exec"
- "openGemini-UP/util"
"sync"
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/pkg/cluster/operation"
+ "github.com/openGemini/gemix/util"
"golang.org/x/crypto/ssh"
)
@@ -22,30 +35,25 @@ type GeminiStop struct {
remotes map[string]*config.RemoteHost
// ip -> actions
- stops map[string]*exec.StopAction
+ stops map[string]*operation.StopAction
// ip -> ssh clients
sshClients map[string]*ssh.Client
configurator config.Configurator // conf reader
- executor exec.Executor // execute commands on remote host
-
- needDelete bool // whether to delete logs and data
- upDataPath map[string]string // ip->up path
+ executor operation.Executor // execute commands on remote host
wg sync.WaitGroup
- clusterOptions deploy.ClusterOptions
+ clusterOptions ClusterOptions
}
-func NewGeminiStop(ops deploy.ClusterOptions, delete bool) Stop {
+func NewGeminiStop(ops ClusterOptions) Stop {
new := &GeminiStop{
remotes: make(map[string]*config.RemoteHost),
- stops: make(map[string]*exec.StopAction),
+ stops: make(map[string]*operation.StopAction),
sshClients: make(map[string]*ssh.Client),
- configurator: config.NewGeminiConfigurator(ops.YamlPath, "", "", ""),
- needDelete: delete,
- upDataPath: make(map[string]string),
+ configurator: config.NewGeminiConfigurator(ops.YamlPath, "", ""),
clusterOptions: ops,
}
return new
@@ -53,16 +61,18 @@ func NewGeminiStop(ops deploy.ClusterOptions, delete bool) Stop {
func (s *GeminiStop) Prepare() error {
var err error
- if err = s.configurator.RunWithoutGen(); err != nil {
+ if err = s.configurator.BuildConfig(); err != nil {
return err
}
conf := s.configurator.GetConfig()
if err = s.prepareRemotes(conf); err != nil {
+ fmt.Printf("Failed to establish SSH connections with all remote servers. The specific error is: %s\n", err)
return err
}
+ fmt.Println("Success to establish SSH connections with all remote servers.")
- s.executor = exec.NewGeminiExecutor(s.sshClients)
+ s.executor = operation.NewGeminiExecutor(s.sshClients)
if err = s.prepareStopActions(conf); err != nil {
return err
@@ -73,7 +83,7 @@ func (s *GeminiStop) Prepare() error {
func (s *GeminiStop) prepareRemotes(c *config.Config) error {
if c == nil {
- return util.UnexpectedNil
+ return util.ErrUnexpectedNil
}
for ip, ssh := range c.SSHConfig {
@@ -85,8 +95,6 @@ func (s *GeminiStop) prepareRemotes(c *config.Config) error {
KeyPath: s.clusterOptions.Key,
Typ: s.clusterOptions.SshType,
}
-
- s.upDataPath[ip] = ssh.UpDataPath
}
if err := s.tryConnect(); err != nil {
@@ -108,7 +116,6 @@ func (s *GeminiStop) tryConnect() error {
}
if err != nil {
- // TODO(Benevor):close all connection and exit
return err
}
s.sshClients[ip] = sshClient
@@ -121,61 +128,47 @@ func (s *GeminiStop) prepareStopActions(c *config.Config) error {
// ts-meta
for ip := range c.SSHConfig {
if s.stops[ip] == nil {
- s.stops[ip] = &exec.StopAction{
+ s.stops[ip] = &operation.StopAction{
Remote: s.remotes[ip],
}
}
- s.stops[ip].ProcessNames = append(s.stops[ip].ProcessNames, util.TS_META)
+ s.stops[ip].ProcessNames = append(s.stops[ip].ProcessNames, util.TsMeta)
}
// ts-sql
for ip := range c.SSHConfig {
if s.stops[ip] == nil {
- s.stops[ip] = &exec.StopAction{
+ s.stops[ip] = &operation.StopAction{
Remote: s.remotes[ip],
}
}
- s.stops[ip].ProcessNames = append(s.stops[ip].ProcessNames, util.TS_SQL)
+ s.stops[ip].ProcessNames = append(s.stops[ip].ProcessNames, util.TsSql)
}
// ts-store
for ip := range c.SSHConfig {
if s.stops[ip] == nil {
- s.stops[ip] = &exec.StopAction{
+ s.stops[ip] = &operation.StopAction{
Remote: s.remotes[ip],
}
}
- s.stops[ip].ProcessNames = append(s.stops[ip].ProcessNames, util.TS_STORE)
+ s.stops[ip].ProcessNames = append(s.stops[ip].ProcessNames, util.TsStore)
}
return nil
}
func (s *GeminiStop) Run() error {
if s.executor == nil {
- return util.UnexpectedNil
+ return util.ErrUnexpectedNil
}
-
s.wg.Add(len(s.stops))
for _, action := range s.stops {
- go func(action *exec.StopAction) {
+ go func(action *operation.StopAction) {
defer s.wg.Done()
s.executor.ExecStopAction(action)
}(action)
}
s.wg.Wait()
-
- // need to delete all logs and data on remote hosts
- if s.needDelete {
- s.wg.Add(len(s.stops))
- for ip := range s.stops {
- go func(ip string) {
- defer s.wg.Done()
- command := fmt.Sprintf("rm -rf %s;", s.upDataPath[ip])
- s.executor.ExecCommand(ip, command)
- }(ip)
- }
- s.wg.Wait()
- }
return nil
}
diff --git a/pkg/cluster/manager/uninstall.go b/pkg/cluster/manager/uninstall.go
new file mode 100644
index 0000000..2f35aad
--- /dev/null
+++ b/pkg/cluster/manager/uninstall.go
@@ -0,0 +1,184 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 manager
+
+import (
+ "errors"
+ "fmt"
+ "path/filepath"
+ "sync"
+
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/pkg/cluster/operation"
+ "github.com/openGemini/gemix/util"
+ "golang.org/x/crypto/ssh"
+)
+
+type Uninstall interface {
+ Prepare() error
+ Run() error
+ Close()
+}
+
+type GeminiUninstaller struct {
+ // ip -> remotes
+ remotes map[string]*config.RemoteHost
+ // ip -> ssh clients
+ sshClients map[string]*ssh.Client
+
+ configurator config.Configurator // conf reader
+ executor operation.Executor // execute commands on remote host
+ upDataPath map[string]string // ip->up path
+
+ wg sync.WaitGroup
+
+ clusterOptions ClusterOptions
+}
+
+func NewGeminiUninstaller(ops ClusterOptions) Uninstall {
+ new := &GeminiUninstaller{
+ remotes: make(map[string]*config.RemoteHost),
+ sshClients: make(map[string]*ssh.Client),
+ configurator: config.NewGeminiConfigurator(ops.YamlPath, "", ""),
+ upDataPath: make(map[string]string),
+ clusterOptions: ops,
+ }
+ return new
+}
+
+func (s *GeminiUninstaller) Prepare() error {
+ var err error
+ if err = s.configurator.BuildConfig(); err != nil {
+ return err
+ }
+ conf := s.configurator.GetConfig()
+
+ if err = s.prepareRemotes(conf); err != nil {
+ fmt.Printf("Failed to establish SSH connections with all remote servers. The specific error is: %s\n", err)
+ return err
+ }
+ fmt.Println("Success to establish SSH connections with all remote servers.")
+
+ s.executor = operation.NewGeminiExecutor(s.sshClients)
+
+ return nil
+}
+
+func (s *GeminiUninstaller) prepareRemotes(c *config.Config) error {
+ if c == nil {
+ return util.ErrUnexpectedNil
+ }
+
+ for ip, ssh := range c.SSHConfig {
+ s.remotes[ip] = &config.RemoteHost{
+ Ip: ip,
+ SSHPort: ssh.Port,
+ User: s.clusterOptions.User,
+ Password: s.clusterOptions.Password,
+ KeyPath: s.clusterOptions.Key,
+ Typ: s.clusterOptions.SshType,
+ }
+
+ s.upDataPath[ip] = ssh.UpDataPath
+ }
+
+ if err := s.tryConnect(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *GeminiUninstaller) tryConnect() error {
+ for ip, r := range s.remotes {
+ var err error
+ var sshClient *ssh.Client
+ switch r.Typ {
+ case config.SSH_PW:
+ sshClient, err = util.NewSSH_PW(r.User, r.Password, r.Ip, r.SSHPort)
+ case config.SSH_KEY:
+ sshClient, err = util.NewSSH_Key(r.User, r.KeyPath, r.Ip, r.SSHPort)
+
+ }
+ if err != nil {
+ return err
+ }
+ s.sshClients[ip] = sshClient
+ }
+ return nil
+}
+
+func (s *GeminiUninstaller) Run() error {
+ if s.executor == nil {
+ return util.ErrUnexpectedNil
+ }
+
+ errChan := make(chan error, len(s.remotes))
+ var wgp sync.WaitGroup
+ wgp.Add(2)
+
+ go func() {
+ defer wgp.Done()
+ s.wg.Add(len(s.remotes))
+ for ip := range s.remotes {
+ go func(ip string, errChan chan error) {
+ defer s.wg.Done()
+ filePath := filepath.Join(s.upDataPath[ip], s.clusterOptions.Version)
+ if filePath == "/" || filePath == "/root" {
+ errChan <- fmt.Errorf("can not remove %s on %s", filePath, ip)
+ return
+ }
+ command := fmt.Sprintf("rm -rf %s;", filePath)
+ _, err := s.executor.ExecCommand(ip, command)
+ if err != nil {
+ errChan <- err
+ }
+ }(ip, errChan)
+ }
+ s.wg.Wait()
+ close(errChan)
+ }()
+
+ var has_err = false
+ go func() {
+ defer wgp.Done()
+ for {
+ err, ok := <-errChan
+ if !ok {
+ break
+ }
+ fmt.Println(err)
+ has_err = true
+ }
+ }()
+
+ wgp.Wait()
+ if has_err {
+ return errors.New("uninstall cluster failed")
+ } else {
+ return nil
+ }
+}
+
+func (s *GeminiUninstaller) Close() {
+ var err error
+ for _, ssh := range s.sshClients {
+ if ssh != nil {
+ if err = ssh.Close(); err != nil {
+ fmt.Println(err)
+ }
+ }
+ }
+}
diff --git a/pkg/download/download.go b/pkg/cluster/operation/download.go
similarity index 61%
rename from pkg/download/download.go
rename to pkg/cluster/operation/download.go
index e728f96..e49a0cc 100644
--- a/pkg/download/download.go
+++ b/pkg/cluster/operation/download.go
@@ -1,4 +1,18 @@
-package download
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 (
"archive/tar"
@@ -7,11 +21,12 @@ import (
"io"
"net/http"
"net/url"
- "openGemini-UP/util"
"os"
"path/filepath"
"strings"
"time"
+
+ "github.com/openGemini/gemix/util"
)
type DownloadOptions struct {
@@ -38,54 +53,35 @@ type GeminiDownloader struct {
func NewGeminiDownloader(ops DownloadOptions) Downloader {
return &GeminiDownloader{
- website: util.Download_web,
+ website: util.DownloadWeb,
version: ops.Version,
- typ: "-" + ops.Os + "-" + ops.Arch + util.Download_pkg_suffix,
- destination: util.Download_dst,
- timeout: util.Download_timeout,
+ typ: "-" + ops.Os + "-" + ops.Arch + util.DownloadPkgSuffix,
+ destination: util.DownloadDst,
+ timeout: util.DownloadTimeout,
}
}
-func (d *GeminiDownloader) setVersion(v string) {
- d.version = v
-}
-
-func (d *GeminiDownloader) setType(t string) {
- d.typ = t
-}
-
-func (d *GeminiDownloader) setDestination(dst string) {
- d.destination = dst
-}
-
-func (d *GeminiDownloader) setTimeout(t time.Duration) {
- d.timeout = t
-}
-
func (d *GeminiDownloader) spliceUrl() error {
if d.website == "" {
- d.website = util.Download_web
+ d.website = util.DownloadWeb
}
if d.version == "" {
- d.version = util.Download_default_version
- }
-
- if err := d.checkVersion(); err != nil {
- return err
+ latestVer, err := util.GetLatestVerFromCurl()
+ if err != nil {
+ return err
+ } else {
+ d.version = latestVer
+ }
}
- d.Url = d.website + "/" + d.version + "/" + util.Download_fill_char + d.version[1:] + d.typ
- return nil
-}
-
-func (d *GeminiDownloader) checkVersion() error {
+ d.Url = d.website + "/" + d.version + "/" + util.DownloadFillChar + d.version[1:] + d.typ
return nil
}
func (d *GeminiDownloader) Run() error {
- if _, err := os.Stat(util.Download_dst); os.IsNotExist(err) {
- errDir := os.MkdirAll(util.Download_dst, 0755)
+ if _, err := os.Stat(util.DownloadDst); os.IsNotExist(err) {
+ errDir := os.MkdirAll(util.DownloadDst, 0750)
if errDir != nil {
return errDir
}
@@ -115,7 +111,7 @@ func (d *GeminiDownloader) Run() error {
func (d *GeminiDownloader) isMissing() bool {
dir := filepath.Join(d.destination, d.version)
_, err := os.Stat(dir)
- return os.IsNotExist(err)
+ return err != nil
}
func (d *GeminiDownloader) downloadFile() error {
@@ -126,6 +122,7 @@ func (d *GeminiDownloader) downloadFile() error {
// get HTTP_PROXY and parse
httpProxy := os.Getenv("HTTP_PROXY")
if httpProxy != "" {
+ fmt.Printf("use HTTP_PROXY: %s\n", httpProxy)
proxyParsedURL, err := url.Parse(httpProxy)
if err != nil {
fmt.Printf("parse httpProxy failed! %v\n", err)
@@ -160,9 +157,13 @@ func (d *GeminiDownloader) downloadFile() error {
}
defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("GET request failed, status code: %d", resp.StatusCode)
+ }
+
// create local file
d.CleanFile(dir)
- if err = os.Mkdir(dir, 0755); err != nil {
+ if err = os.Mkdir(dir, 0750); err != nil {
return err
}
fmt.Printf("mkdir: %s\n", dir)
@@ -186,58 +187,56 @@ func (d *GeminiDownloader) decompressFile() error {
targetPath := filepath.Join(d.destination, d.version)
fmt.Printf("start decompressing %s to %s\n", d.fileName, targetPath)
- // open .tar.gz file
file, err := os.Open(d.fileName)
if err != nil {
- fmt.Println(err)
+ fmt.Println("Error opening source file:", err)
return err
}
defer file.Close()
- // new gzip.Reader
- gzReader, err := gzip.NewReader(file)
+ gzipReader, err := gzip.NewReader(file)
if err != nil {
- fmt.Println(err)
+ fmt.Println("Error creating gzip reader:", err)
return err
}
- defer gzReader.Close()
+ defer gzipReader.Close()
+
+ tarReader := tar.NewReader(gzipReader)
- // new tar.Reader
- tarReader := tar.NewReader(gzReader)
for {
header, err := tarReader.Next()
+
if err == io.EOF {
break
}
+
if err != nil {
- fmt.Println(err)
+ fmt.Println("Error reading tar header:", err)
return err
}
- targetFilePath := filepath.Join(targetPath, header.Name)
- // If it is a directory, create the corresponding directory
- if header.Typeflag == tar.TypeDir {
- err = os.MkdirAll(targetFilePath, header.FileInfo().Mode())
- if err != nil {
- fmt.Println(err)
- return err
- }
+ targetFile := filepath.Join(targetPath, header.Name)
+
+ if header.FileInfo().IsDir() {
continue
}
- // If it is a file, create and copy the contents of the file
- if header.Typeflag == tar.TypeReg {
- targetFile, err := os.OpenFile(targetFilePath, os.O_CREATE|os.O_RDWR, header.FileInfo().Mode())
- if err != nil {
- fmt.Println(err)
- return err
- }
- defer targetFile.Close()
-
- _, err = io.Copy(targetFile, tarReader)
- if err != nil {
- fmt.Println(err)
- return err
- }
+
+ targetDir := filepath.Dir(targetFile)
+
+ if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
+ return err
+ }
+ file, err := os.Create(targetFile)
+ if err != nil {
+ fmt.Println("Error creating target file:", err)
+ return err
+ }
+ defer file.Close()
+
+ _, err = io.Copy(file, tarReader)
+ if err != nil {
+ fmt.Println("Error extracting file:", err)
+ return err
}
}
diff --git a/pkg/exec/exec.go b/pkg/cluster/operation/exec.go
similarity index 71%
rename from pkg/exec/exec.go
rename to pkg/cluster/operation/exec.go
index d707b65..531bab0 100644
--- a/pkg/exec/exec.go
+++ b/pkg/cluster/operation/exec.go
@@ -1,15 +1,29 @@
-package exec
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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"
- "openGemini-UP/pkg/config"
- "openGemini-UP/util"
+ "github.com/openGemini/gemix/pkg/cluster/config"
+ "github.com/openGemini/gemix/util"
"golang.org/x/crypto/ssh"
)
type Executor interface {
- ExecRunAction(action *RunAction) (string, error)
+ ExecRunAction(action *RunAction, errChan chan error) string
ExecStopAction(action *StopAction) (string, error)
ExecCommand(ip string, command string) (string, error)
}
@@ -29,7 +43,7 @@ func (e *GeminiExecutor) ExecCommand(ip string, command string) (string, error)
sshClient := e.sshClients[ip]
if sshClient == nil {
fmt.Printf("no ssh client for %s\n", ip)
- return "", util.NoSshClient
+ return "", util.ErrNoSshClient
}
sshSession, err := util.NewSshSession(sshClient)
@@ -43,7 +57,6 @@ func (e *GeminiExecutor) ExecCommand(ip string, command string) (string, error)
fmt.Printf("exec: %s on %s failed! %v\n", command, ip, err)
return "", err
}
- fmt.Printf("exec: %s on %s\noutput: %s\n", command, ip, string(combo))
return string(combo), nil
}
@@ -63,12 +76,13 @@ type RunActions struct {
StoreAction []*RunAction
}
-func (e *GeminiExecutor) ExecRunAction(action *RunAction) (string, error) {
+func (e *GeminiExecutor) ExecRunAction(action *RunAction, errChan chan error) string {
ip := action.Remote.Ip
sshClient := e.sshClients[ip]
if sshClient == nil {
fmt.Printf("no ssh client for %s\n", ip)
- return "", util.NoSshClient
+ errChan <- util.ErrNoSshClient
+ return ""
}
sshSession, err := util.NewSshSession(sshClient)
@@ -86,10 +100,10 @@ func (e *GeminiExecutor) ExecRunAction(action *RunAction) (string, error) {
combo, err := sshSession.CombinedOutput(command)
if err != nil {
fmt.Printf("exec: %s on %s failed! %v\n", command, ip, err)
- return "", err
+ errChan <- err
+ return ""
}
- fmt.Printf("exec: %s on %s\noutput: %s\n", command, ip, string(combo))
- return string(combo), nil
+ return string(combo)
}
type StopAction struct {
@@ -102,7 +116,7 @@ func (e *GeminiExecutor) ExecStopAction(action *StopAction) (string, error) {
sshClient := e.sshClients[ip]
if sshClient == nil {
fmt.Printf("no ssh client for %s\n", ip)
- return "", util.NoSshClient
+ return "", util.ErrNoSshClient
}
command := ""
@@ -120,7 +134,6 @@ func (e *GeminiExecutor) ExecStopAction(action *StopAction) (string, error) {
fmt.Printf("exec: %s on %s failed! %v\n", command, ip, err)
return "", err
}
- fmt.Printf("exec: %s on %s\noutput: %s\n", command, ip, string(combo))
return string(combo), nil
}
diff --git a/pkg/config/remote_host.go b/pkg/config/remote_host.go
deleted file mode 100644
index e15bd19..0000000
--- a/pkg/config/remote_host.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package config
-
-type SSHType int32
-
-const (
- SSH_UNKNOW SSHType = 0
- SSH_PW SSHType = 1
- SSH_KEY SSHType = 2
-)
-
-// used by deploy, exe, stop .etc
-type RemoteHost struct {
- Ip string
- SSHPort int
- User string
- Password string
- KeyPath string
- Typ SSHType
- UpDataPath string
- LogPath string
-}
-
-type UploadInfo struct {
- LocalPath string
- RemotePath string
- FileName string
-}
diff --git a/pkg/config/toml.go b/pkg/config/toml.go
deleted file mode 100644
index 579ffda..0000000
--- a/pkg/config/toml.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package config
-
-import (
- "os"
-
- "github.com/BurntSushi/toml"
-)
-
-type Toml struct {
- Common CommonToml `toml:"common"`
- Meta MetaToml `toml:"meta"`
- Http HttpToml `toml:"http"`
- Data DataToml `toml:"data"`
- Logging LoggingToml `toml:"logging"`
- Gossip GossipToml `toml:"gossip"`
-}
-
-type CommonToml struct {
- MetaJoin []string `toml:"meta-join"`
-}
-
-type MetaToml struct {
- BindAddress string `toml:"bind-address"`
- HttpBindAddress string `toml:"http-bind-address"`
- RpcBindAddress string `toml:"rpc-bind-address"`
- Dir string `toml:"dir"`
-}
-
-type HttpToml struct {
- BindAddress string `toml:"bind-address"`
-}
-
-type DataToml struct {
- StoreIngestAddr string `toml:"store-ingest-addr"`
- StoreSelectAddr string `toml:"store-select-addr"`
- StoreDataDir string `toml:"store-data-dir"`
- StoreWalDir string `toml:"store-wal-dir"`
- StoreMetaDir string `toml:"store-meta-dir"`
- CacheTableDataBlock bool `toml:"cache-table-data-block"`
- CacheTableMetaBlock bool `toml:"cache-table-meta-block"`
- ReadCacheLimit int `toml:"read-cache-limit"`
-}
-
-type LoggingToml struct {
- Path string `toml:"path"`
-}
-
-type GossipToml struct {
- BindAddress string `toml:"bind-address"`
- StoreBindPort int `toml:"store-bind-port"`
- MetaBindPort int `toml:"meta-bind-port"`
- Members []string `toml:"members"`
-}
-
-func ReadFromToml(tomlPath string) (Toml, error) {
- t := Toml{}
- var err error
- if _, err = toml.DecodeFile(tomlPath, &t); err != nil {
- return t, err
- }
- return t, nil
-}
-
-func GenNewToml(t Toml, path string) {
- f, _ := os.Create(path)
- defer f.Close()
-
- e := toml.NewEncoder(f)
- e.Encode(t)
-}
diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go
deleted file mode 100644
index 7dd308a..0000000
--- a/pkg/deploy/deploy.go
+++ /dev/null
@@ -1,465 +0,0 @@
-package deploy
-
-import (
- "fmt"
- "io/ioutil"
- "openGemini-UP/pkg/config"
- "openGemini-UP/pkg/download"
- "openGemini-UP/pkg/exec"
- "openGemini-UP/util"
- "os"
- "path/filepath"
- "strconv"
- "sync"
- "time"
-
- "github.com/pkg/sftp"
- "golang.org/x/crypto/ssh"
-)
-
-type ClusterOptions struct {
- Version string
- User string
- Key string
- Password string
- SshType config.SSHType
- YamlPath string
-}
-
-type UploadAction struct {
- uploadInfo []*config.UploadInfo
- remoteHost *config.RemoteHost
-}
-
-type Deployer interface {
- PrepareForDeploy() error
- Deploy() error
- PrepareForStart() error
- Start() error
- Close()
-}
-
-type GeminiDeployer struct {
- version string
- // ip -> remotes
- remotes map[string]*config.RemoteHost
- uploads map[string]*UploadAction
- runs *exec.RunActions
-
- // ip -> ssh clients
- sshClients map[string]*ssh.Client
- sftpClients map[string]*sftp.Client
-
- configurator config.Configurator // conf reader
- executor exec.Executor // execute commands on remote host
-
- clusterOptions ClusterOptions
-
- wg sync.WaitGroup
-}
-
-func NewGeminiDeployer(ops ClusterOptions) Deployer {
- return &GeminiDeployer{
- remotes: make(map[string]*config.RemoteHost),
- uploads: make(map[string]*UploadAction),
- sshClients: make(map[string]*ssh.Client),
- sftpClients: make(map[string]*sftp.Client),
- version: ops.Version,
- configurator: config.NewGeminiConfigurator(ops.YamlPath, filepath.Join(util.Download_dst, ops.Version, util.Local_etc_rel_path, util.Local_conf_name), filepath.Join(util.Download_dst, util.Local_etc_rel_path), ops.Version),
- runs: &exec.RunActions{},
- clusterOptions: ops,
- }
-}
-
-func (d *GeminiDeployer) PrepareForDeploy() error {
- var err error
- if err = d.configurator.Run(); err != nil {
- return err
- }
- conf := d.configurator.GetConfig()
-
- dOps := download.DownloadOptions{
- Version: d.version,
- Os: conf.CommonConfig.Os,
- Arch: conf.CommonConfig.Arch,
- }
- downloader := download.NewGeminiDownloader(dOps)
- if err = downloader.Run(); err != nil {
- return err
- }
-
- if err = d.prepareRemotes(conf, true); err != nil {
- return err
- }
-
- d.executor = exec.NewGeminiExecutor(d.sshClients)
-
- if err = d.prepareForUpload(); err != nil {
- return err
- }
-
- if err = d.prepareUploadActions(conf); err != nil {
- return err
- }
-
- if err = d.prepareRunActions(conf); err != nil {
- return err
- }
-
- return nil
-}
-
-func (d *GeminiDeployer) PrepareForStart() error {
- var err error
- var version string
- if version, err = d.getVersion(); err != nil {
- return err
- }
- d.version = version
-
- if err = d.configurator.RunWithoutGen(); err != nil {
- return err
- }
- conf := d.configurator.GetConfig()
-
- if err = d.prepareRemotes(conf, false); err != nil {
- return err
- }
-
- d.executor = exec.NewGeminiExecutor(d.sshClients)
-
- if err = d.prepareRunActions(conf); err != nil {
- return err
- }
-
- return nil
-}
-
-func (d *GeminiDeployer) prepareRemotes(c *config.Config, needSftp bool) error {
- if c == nil {
- return util.UnexpectedNil
- }
-
- for ip, ssh := range c.SSHConfig {
- d.remotes[ip] = &config.RemoteHost{
- Ip: ip,
- SSHPort: ssh.Port,
- UpDataPath: ssh.UpDataPath,
- LogPath: ssh.LogPath,
- User: d.clusterOptions.User,
- Typ: d.clusterOptions.SshType,
- Password: d.clusterOptions.Password,
- KeyPath: d.clusterOptions.Key,
- }
- }
-
- if err := d.tryConnect(needSftp); err != nil {
- return err
- }
-
- return nil
-}
-
-func (d *GeminiDeployer) tryConnect(needSftp bool) error {
- for ip, r := range d.remotes {
- var err error
- var sshClient *ssh.Client
- switch r.Typ {
- case config.SSH_PW:
- sshClient, err = util.NewSSH_PW(r.User, r.Password, r.Ip, r.SSHPort)
- case config.SSH_KEY:
- sshClient, err = util.NewSSH_Key(r.User, r.KeyPath, r.Ip, r.SSHPort)
-
- }
- if err != nil {
- // TODO(Benevor):close all connection and exit
- return err
- }
- d.sshClients[ip] = sshClient
-
- if needSftp {
- sftpClient, err := util.NewSftpClient(sshClient)
- if err != nil {
- // TODO(Benevor):close all connection and exit
- return err
- }
- d.sftpClients[ip] = sftpClient
-
- pwd, _ := sftpClient.Getwd()
- // Convert relative paths to absolute paths.
- if len(r.UpDataPath) > 1 && r.UpDataPath[:1] == "~" {
- r.UpDataPath = filepath.Join(pwd, r.UpDataPath[1:])
- }
-
- // Convert relative paths in openGemini.conf to absolute paths.
- confPath := filepath.Join(util.Download_dst, util.Local_etc_rel_path, r.Ip+util.Remote_conf_suffix)
- hostToml, _ := config.ReadFromToml(confPath)
- if len(hostToml.Meta.Dir) > 1 && hostToml.Meta.Dir[:1] == "~" {
- hostToml.Meta.Dir = filepath.Join(pwd, hostToml.Meta.Dir[1:])
- }
- if len(hostToml.Data.StoreDataDir) > 1 && hostToml.Data.StoreDataDir[:1] == "~" {
- hostToml.Data.StoreDataDir = filepath.Join(pwd, hostToml.Data.StoreDataDir[1:])
- }
- if len(hostToml.Data.StoreWalDir) > 1 && hostToml.Data.StoreWalDir[:1] == "~" {
- hostToml.Data.StoreWalDir = filepath.Join(pwd, hostToml.Data.StoreWalDir[1:])
- }
- if len(hostToml.Data.StoreMetaDir) > 1 && hostToml.Data.StoreMetaDir[:1] == "~" {
- hostToml.Data.StoreMetaDir = filepath.Join(pwd, hostToml.Data.StoreMetaDir[1:])
- }
- if len(hostToml.Logging.Path) > 1 && hostToml.Logging.Path[:1] == "~" {
- hostToml.Logging.Path = filepath.Join(pwd, hostToml.Logging.Path[1:])
- }
- config.GenNewToml(hostToml, confPath)
- }
- }
- return nil
-}
-
-func (d *GeminiDeployer) prepareForUpload() error {
- if d.executor == nil {
- return util.UnexpectedNil
- }
- for ip, r := range d.remotes {
- binPath := filepath.Join(r.UpDataPath, d.version, util.Remote_bin_rel_path)
- etcPath := filepath.Join(r.UpDataPath, d.version, util.Remote_etc_rel_path)
- command := fmt.Sprintf("mkdir -p %s; mkdir -p %s;", binPath, etcPath)
- if _, err := d.executor.ExecCommand(ip, command); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (d *GeminiDeployer) prepareUploadActions(c *config.Config) error {
- // ts-meta
- for _, host := range c.CommonConfig.MetaHosts {
- if d.uploads[host] == nil {
- d.uploads[host] = &UploadAction{
- remoteHost: d.remotes[host],
- }
- }
- d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
- LocalPath: filepath.Join(util.Download_dst, d.version, util.Local_bin_rel_path, util.TS_META),
- RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_bin_rel_path),
- FileName: util.TS_META,
- })
- }
-
- // ts-sql
- for _, host := range c.CommonConfig.SqlHosts {
- if d.uploads[host] == nil {
- d.uploads[host] = &UploadAction{
- remoteHost: d.remotes[host],
- }
- }
- d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
- LocalPath: filepath.Join(util.Download_dst, d.version, util.Local_bin_rel_path, util.TS_SQL),
- RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_bin_rel_path),
- FileName: util.TS_SQL,
- })
- }
-
- // ts-store
- for _, host := range c.CommonConfig.StoreHosts {
- if d.uploads[host] == nil {
- d.uploads[host] = &UploadAction{
- remoteHost: d.remotes[host],
- }
- }
- d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
- LocalPath: filepath.Join(util.Download_dst, d.version, util.Local_bin_rel_path, util.TS_STORE),
- RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_bin_rel_path),
- FileName: util.TS_STORE,
- })
- }
-
- // conf and script
- for host := range c.SSHConfig {
- if d.uploads[host] == nil {
- d.uploads[host] = &UploadAction{
- remoteHost: d.remotes[host],
- }
- }
- d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
- LocalPath: filepath.Join(util.Download_dst, util.Local_etc_rel_path, host+util.Remote_conf_suffix),
- RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path),
- FileName: host + util.Remote_conf_suffix,
- })
-
- d.uploads[host].uploadInfo = append(d.uploads[host].uploadInfo, &config.UploadInfo{
- LocalPath: util.Install_script_path,
- RemotePath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path),
- FileName: util.Install_Script,
- })
- }
-
- return nil
-}
-
-func (d *GeminiDeployer) prepareRunActions(c *config.Config) error {
- // ts-meta
- i := 1
- for _, host := range c.CommonConfig.MetaHosts {
- d.runs.MetaAction = append(d.runs.MetaAction, &exec.RunAction{
- Info: &exec.RunInfo{
- ScriptPath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path, util.Install_Script),
- Args: []string{util.TS_META, d.remotes[host].LogPath,
- filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_bin_rel_path, util.TS_META),
- filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path, host+util.Remote_conf_suffix),
- filepath.Join(d.remotes[host].LogPath, util.Remote_pid_path, util.META+strconv.Itoa(i)+util.Remote_pid_suffix),
- filepath.Join(d.remotes[host].LogPath, strconv.Itoa(i), util.META_extra_log+strconv.Itoa(i)+util.Remote_log_suffix),
- strconv.Itoa(i)},
- },
- Remote: d.remotes[host],
- })
- i++
- }
-
- // ts-sql
- i = 1
- for _, host := range c.CommonConfig.SqlHosts {
- d.runs.SqlAction = append(d.runs.SqlAction, &exec.RunAction{
- Info: &exec.RunInfo{
- ScriptPath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path, util.Install_Script),
- Args: []string{util.TS_SQL, d.remotes[host].LogPath,
- filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_bin_rel_path, util.TS_SQL),
- filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path, host+util.Remote_conf_suffix),
- filepath.Join(d.remotes[host].LogPath, util.Remote_pid_path, util.SQL+strconv.Itoa(i)+util.Remote_pid_suffix),
- filepath.Join(d.remotes[host].LogPath, strconv.Itoa(i), util.SQL_extra_log+strconv.Itoa(i)+util.Remote_log_suffix),
- strconv.Itoa(i)},
- },
- Remote: d.remotes[host],
- })
- i++
- }
-
- // ts-store
- i = 1
- for _, host := range c.CommonConfig.StoreHosts {
- d.runs.StoreAction = append(d.runs.StoreAction, &exec.RunAction{
- Info: &exec.RunInfo{
- ScriptPath: filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path, util.Install_Script),
- Args: []string{util.TS_STORE, d.remotes[host].LogPath,
- filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_bin_rel_path, util.TS_STORE),
- filepath.Join(d.remotes[host].UpDataPath, d.version, util.Remote_etc_rel_path, host+util.Remote_conf_suffix),
- filepath.Join(d.remotes[host].LogPath, util.Remote_pid_path, util.STORE+strconv.Itoa(i)+util.Remote_pid_suffix),
- filepath.Join(d.remotes[host].LogPath, strconv.Itoa(i), util.STORE_extra_log+strconv.Itoa(i)+util.Remote_log_suffix),
- strconv.Itoa(i)},
- },
- Remote: d.remotes[host],
- })
- i++
- }
-
- return nil
-}
-
-func (d *GeminiDeployer) Deploy() error {
- d.uploadFiles()
- d.startCluster()
- d.saveVersion()
- return nil
-}
-
-func (d *GeminiDeployer) Start() error {
- d.startCluster()
- return nil
-}
-
-func (d *GeminiDeployer) saveVersion() error {
- filePath := filepath.Join(util.Download_dst, util.VersionFile)
- file, err := os.Create(filePath)
- if err != nil {
- return err
- }
- defer file.Close()
-
- _, err = file.WriteString(d.version)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (d *GeminiDeployer) getVersion() (string, error) {
- filePath := filepath.Join(util.Download_dst, util.VersionFile)
- data, err := ioutil.ReadFile(filePath)
- if err != nil {
- return "", err
- }
-
- return string(data), nil
-}
-
-func (d *GeminiDeployer) uploadFiles() {
- d.wg.Add(len(d.uploads))
- for ip, action := range d.uploads {
- go func(ip string, action *UploadAction) {
- defer d.wg.Done()
- for _, c := range action.uploadInfo {
- // check whether need to upload the file
- // only support Linux
- cmd := fmt.Sprintf("if [ -f %s ]; then echo 'File exists'; else echo 'File not found'; fi", filepath.Join(c.RemotePath, c.FileName))
- output, err := d.executor.ExecCommand(ip, cmd)
- if string(output) == "File exists\n" && err == nil {
- fmt.Printf("%s exists on %s.\n", c.FileName, c.RemotePath)
- } else {
- util.UploadFile(action.remoteHost.Ip, c.LocalPath, c.RemotePath, d.sftpClients[action.remoteHost.Ip])
- }
- }
- }(ip, action)
- }
- d.wg.Wait()
-}
-
-func (d *GeminiDeployer) startCluster() {
- if d.executor == nil {
- return
- }
-
- // start all ts-meta concurrently
- d.wg.Add(len(d.runs.MetaAction))
- for _, action := range d.runs.MetaAction {
- go func(action *exec.RunAction) {
- defer d.wg.Done()
- d.executor.ExecRunAction(action)
- }(action)
- }
- d.wg.Wait()
-
- // time for campaign
- time.Sleep(time.Second)
-
- // start all ts-store and ts-sql concurrently
- d.wg.Add(len(d.runs.SqlAction) + len(d.runs.StoreAction))
- for _, action := range d.runs.StoreAction {
- go func(action *exec.RunAction) {
- defer d.wg.Done()
- d.executor.ExecRunAction(action)
- }(action)
- }
- for _, action := range d.runs.SqlAction {
- go func(action *exec.RunAction) {
- defer d.wg.Done()
- d.executor.ExecRunAction(action)
- }(action)
- }
- d.wg.Wait()
-}
-
-func (d *GeminiDeployer) Close() {
- var err error
- for _, sftp := range d.sftpClients {
- if sftp != nil {
- if err = sftp.Close(); err != nil {
- fmt.Println(err)
- }
- }
- }
-
- for _, ssh := range d.sshClients {
- if err = ssh.Close(); err != nil {
- fmt.Println(err)
- }
- }
-}
diff --git a/scripts/conf_gen.sh b/scripts/conf_gen.sh
deleted file mode 100644
index 9821109..0000000
--- a/scripts/conf_gen.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-# bash filename value_for_node1 value_for_node2 value_for_node3
-
-#!/usr/bin/env bash
-#
-# Shell script to install openGemini as a cluster at one node.
-#
-
-if [ $# -ne 4 ]; then
- echo "Error: Please provide exactly 3 arguments for nodes"
- exit 1
-fi
-
-path=$1
-
-echo $path
-
-declare -a nodes[3]
-nodes[1]=$2
-nodes[2]=$3
-nodes[3]=$4
-
-if [ "$(uname)" == "Darwin" ]; then
- # generate config
- for((num = 1; num <= 3; num++))
- do
- rm -rf $path/etc/${nodes[num]}-openGemini.conf
- cp $path/v1.0.0/etc/openGemini.conf $path/etc/${nodes[num]}-openGemini.conf
-
- sed -i "" "s/{{meta_addr_1}}/${nodes[1]}/g" $path/etc/${nodes[num]}-openGemini.conf
- sed -i "" "s/{{meta_addr_2}}/${nodes[2]}/g" $path/etc/${nodes[num]}-openGemini.conf
- sed -i "" "s/{{meta_addr_3}}/${nodes[3]}/g" $path/etc/${nodes[num]}-openGemini.conf
- sed -i "" "s/{{addr}}/${nodes[$num]}/g" $path/etc/${nodes[num]}-openGemini.conf
-
- sed -i "" "s/{{id}}/$num/g" $path/etc/${nodes[num]}-openGemini.conf
- done
-elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
- # generate config
- for((num = 1; num <= 3; num++))
- do
- rm -rf $path/etc/${nodes[num]}-openGemini.conf
- cp $path/v1.0.0/etc/openGemini.conf $path/etc/${nodes[num]}-openGemini.conf
-
- sed -i "s/{{meta_addr_1}}/${nodes[1]}/g" $path/etc/${nodes[num]}-openGemini.conf
- sed -i "s/{{meta_addr_2}}/${nodes[2]}/g" $path/etc/${nodes[num]}-openGemini.conf
- sed -i "s/{{meta_addr_3}}/${nodes[3]}/g" $path/etc/${nodes[num]}-openGemini.conf
- sed -i "s/{{addr}}/${nodes[$num]}/g" $path/etc/${nodes[num]}-openGemini.conf
-
- sed -i "s/{{id}}/$num/g" $path/etc/${nodes[num]}-openGemini.conf
- done
-else
- echo "not support the platform": $(uname)
- exit 1
-fi
\ No newline at end of file
diff --git a/scripts/install.sh b/scripts/install.sh
index c2afdff..2eb2f4d 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,7 +1,7 @@
-if [ $# -ne 7 ]; then
- echo "Error: Please provide exactly 7 arguments for nodes"
+if [ $# -ne 6 ]; then
+ echo "Error: Please provide exactly 6 arguments for nodes"
exit 1
fi
@@ -12,7 +12,6 @@ bin_file=$3
conf_file=$4
pid_file=$5
extra_file=$6
-index=$7
pid=$(pgrep $bin_name)
@@ -27,7 +26,7 @@ pid=$(pgrep $bin_name)
sleep 3
# rm -rf $opengemini_log_path
-mkdir -p $opengemini_log_path/$index
+mkdir -p $opengemini_log_path
nohup $bin_file -config $conf_file -pidfile $pid_file> $extra_file 2>&1 &
diff --git a/test.yaml b/test.yaml
deleted file mode 100644
index 8743f10..0000000
--- a/test.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-global:
- ssh_port: 22
- log_dir: "~/gemini-deploy/logs"
- deploy_dir: "~/gemini-deploy"
- os: "linux"
- arch: "amd64"
-
-ts-meta:
- - host: 121.48.161.82
- data_dir: "~/gemini-data/meta"
- - host: 121.48.161.84
- data_dir: "~/gemini-data/meta"
- - host: 121.48.161.85
- data_dir: "~/gemini-data/meta"
-
-ts-sql:
- - host: 121.48.161.82
-
-ts-store:
- - host: 121.48.161.82
- data_dir: "~/gemini-data/data"
- meta_dir: "~/gemini-data/data/meta"
- - host: 121.48.161.84
- data_dir: "~/gemini-data/data"
- meta_dir: "~/gemini-data/data/meta"
\ No newline at end of file
diff --git a/test/config/gen_conf_test.go b/test/config/gen_conf_test.go
deleted file mode 100644
index df55fe2..0000000
--- a/test/config/gen_conf_test.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package test
-
-import (
- "openGemini-UP/pkg/config"
- "testing"
-)
-
-func TestGenConf(t *testing.T) {
- tomlPath := "../example/openGenimi-template.conf"
- yamlPath := "../example/topology.example1.yaml"
-
- var conf config.Toml
- var y config.Yaml
- var err error
- conf, err = config.ReadFromToml(tomlPath)
- if err != nil {
- t.Fatalf("Read from toml failed: %v", err)
- }
-
- y, err = config.ReadFromYaml(yamlPath)
- if err != nil {
- t.Fatalf("Read from yaml failed: %v", err)
- }
-
- config.GenConfs(y, conf, "../../data/etc")
-}
-
-func TestGenConfWithGlobalDefaults(t *testing.T) {
- tomlPath := "../example/openGenimi-template.conf"
- yamlPath := "../example/topology.example2.yaml"
-
- var conf config.Toml
- var y config.Yaml
- var err error
- conf, err = config.ReadFromToml(tomlPath)
- if err != nil {
- t.Fatalf("Read from toml failed: %v", err)
- }
-
- y, err = config.ReadFromYaml(yamlPath)
- if err != nil {
- t.Fatalf("Read from yaml failed: %v", err)
- }
-
- config.GenConfs(y, conf, "../../data/etc")
-}
-
-func TestGenConfWith3Hosts(t *testing.T) {
- tomlPath := "../example/openGenimi-template.conf"
- yamlPath := "../example/topology.example3.yaml"
-
- var conf config.Toml
- var y config.Yaml
- var err error
- conf, err = config.ReadFromToml(tomlPath)
- if err != nil {
- t.Fatalf("Read from toml failed: %v", err)
- }
-
- y, err = config.ReadFromYaml(yamlPath)
- if err != nil {
- t.Fatalf("Read from yaml failed: %v", err)
- }
-
- config.GenConfs(y, conf, "../../data/etc")
-}
diff --git a/test/config/toml_test.go b/test/config/toml_test.go
deleted file mode 100644
index 69b9996..0000000
--- a/test/config/toml_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package test
-
-import (
- "openGemini-UP/pkg/config"
- "testing"
-)
-
-func TestToml(t *testing.T) {
- tomlPath := "/Users/liujibo/Desktop/openGemini-UP/data/v1.0.0/etc/openGemini.conf"
- newPath := "/Users/liujibo/Desktop/openGemini-UP/test.conf"
-
- var conf config.Toml
- var err error
- conf, err = config.ReadFromToml(tomlPath)
-
- if err != nil {
- t.Fatalf("Read from toml failed: %v", err)
- }
-
- config.GenNewToml(conf, newPath)
-}
diff --git a/test/config/yaml_test.go b/test/config/yaml_test.go
deleted file mode 100644
index 477f2e7..0000000
--- a/test/config/yaml_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package test
-
-import (
- "openGemini-UP/pkg/config"
- "testing"
-)
-
-func TestYaml(t *testing.T) {
- yamlPath := "/Users/liujibo/Desktop/openGemini-UP/topology.example.yaml"
-
- var conf config.Yaml
- var err error
- conf, err = config.ReadFromYaml(yamlPath)
-
- if err != nil || conf.Global.SSHPort == 0 {
- t.Fatalf("Read from yaml failed: %v", err)
- }
-}
diff --git a/test/example/openGenimi-template.conf b/test/example/openGenimi-template.conf
deleted file mode 100644
index ce52f43..0000000
--- a/test/example/openGenimi-template.conf
+++ /dev/null
@@ -1,31 +0,0 @@
-[common]
- meta-join = ["{{meta_addr_1}}:8092", "{{meta_addr_2}}:8092", "{{meta_addr_3}}:8092"]
-
-[meta]
- bind-address = "{{addr}}:8088"
- http-bind-address = "{{addr}}:8091"
- rpc-bind-address = "{{addr}}:8092"
- dir = "/tmp/openGemini/data/meta/{{id}}"
-
-[http]
- bind-address = "{{addr}}:8086"
-
-
-[data]
- store-ingest-addr = "{{addr}}:8400"
- store-select-addr = "{{addr}}:8401"
- store-data-dir = "/tmp/openGemini/data"
- store-wal-dir = "/tmp/openGemini/data"
- store-meta-dir = "/tmp/openGemini/data/meta/{{id}}"
- cache-table-data-block = false
- cache-table-meta-block = false
- read-cache-limit = 0
-
-[logging]
- path = "/tmp/openGemini/logs/{{id}}"
-
-[gossip]
- bind-address = "{{addr}}"
- store-bind-port = 8011
- meta-bind-port = 8010
- members = ["{{meta_addr_1}}:8010", "{{meta_addr_2}}:8010", "{{meta_addr_3}}:8010"]
\ No newline at end of file
diff --git a/test/example/topology.example1.yaml b/test/example/topology.example1.yaml
deleted file mode 100644
index 0fbdec8..0000000
--- a/test/example/topology.example1.yaml
+++ /dev/null
@@ -1,166 +0,0 @@
-# Global variables are applied to all deployments and used as the default value of
-# the deployments if a specific deployment value is missing.
-global:
- # The user who runs the openGemini cluster.
- user: "gemini"
- # group is used to specify the group name the user belong to,if it's not the same as user.
- group: "gemini"
- # SSH port of servers in the managed cluster.
- ssh_port: 22
- # openGemini Cluster data storage directory.
- base_data_dir: "/gemini-data"
- # openGemini Cluster log file storage directory.
- log_dir: "/gemini-deploy/logs"
- # Storage directory for cluster deployment files, startup scripts, and configuration files.
- deploy_dir: "/gemini-deploy"
- # operating system, linux/darwin/windows.
- os: "linux"
- # Supported values: "amd64", "arm64" (default: "amd64").
- arch: "amd64"
-
-# Server configs are used to specify the configuration of ts-meta Servers.
-ts-meta:
- # The ip address of the ts-meta Server.
- - host: 10.0.1.11
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [meta].http-bind-address in openGemini.conf.
- client_port: 8091
- # [meta].rpc-bind-address in openGemini.conf.
- peer_port: 8092
- # [meta].bind-address in openGemini.conf.
- raft_port: 8088
- # [gossip].meta-bind-port in openGemini.conf.
- gossip_port: 8010
- # [meta].dir in openGemini.conf.
- data_dir: "/gemini-data/meta"
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
- - host: 10.0.1.12
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [meta].http-bind-address in openGemini.conf.
- client_port: 8091
- # [meta].rpc-bind-address in openGemini.conf.
- peer_port: 8092
- # [meta].bind-address in openGemini.conf.
- raft_port: 8088
- # [gossip].meta-bind-port in openGemini.conf.
- gossip_port: 8010
- # [meta].dir in openGemini.conf.
- data_dir: "/gemini-data/meta"
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
- - host: 10.0.1.13
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [meta].http-bind-address in openGemini.conf.
- client_port: 8091
- # [meta].rpc-bind-address in openGemini.conf.
- peer_port: 8092
- # [meta].bind-address in openGemini.conf.
- raft_port: 8088
- # [gossip].meta-bind-port in openGemini.conf.
- gossip_port: 8010
- # [meta].dir in openGemini.conf.
- data_dir: "/gemini-data/meta"
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
-
-# Server configs are used to specify the configuration of ts-sql Servers.
-ts-sql:
- # The ip address of the ts-sql Server.
- - host: 10.0.1.14
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [http].bind-address in openGemini.conf.
- port: 8086
- # [http].flight-address in openGemini.conf.
- flight_port: 8087
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
- - host: 10.0.1.15
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [http].bind-address in openGemini.conf.
- port: 8086
- # [http].flight-address in openGemini.conf.
- flight_port: 8087
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
- - host: 10.0.1.16
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [http].bind-address in openGemini.conf.
- port: 8086
- # [http].flight-address in openGemini.conf.
- flight_port: 8087
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
-
-# Server configs are used to specify the configuration of ts-store Servers.
-ts-store:
- # The ip address of the ts-store Server.
- - host: 10.0.1.17
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [data].store-ingest-addr in openGemini.conf.
- ingest_port: 8400
- # [data].store-select-addr in openGemini.conf.
- select_port: 8401
- # [gossip].store-bind-port in openGemini.conf.
- gossip_port: 8011
- # [data].store-data-dir & [data].store-wal-dir in openGemini.conf.
- data_dir: "/gemini-data/data"
- # [data].store-meta-dir in openGemini.conf.
- meta_dir: "/gemini-data/data/meta"
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
- - host: 10.0.1.18
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [data].store-ingest-addr in openGemini.conf.
- ingest_port: 8400
- # [data].store-select-addr in openGemini.conf.
- select_port: 8401
- # [gossip].store-bind-port in openGemini.conf.
- gossip_port: 8011
- # [data].store-data-dir & [data].store-wal-dir in openGemini.conf.
- data_dir: "/gemini-data/data"
- # [data].store-meta-dir in openGemini.conf.
- meta_dir: "/gemini-data/data/meta"
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
- - host: 10.0.1.19
- # SSH port of the server. (same on same server)
- ssh_port: 22
- # [data].store-ingest-addr in openGemini.conf.
- ingest_port: 8400
- # [data].store-select-addr in openGemini.conf.
- select_port: 8401
- # [gossip].store-bind-port in openGemini.conf.
- gossip_port: 8011
- # [data].store-data-dir & [data].store-wal-dir in openGemini.conf.
- data_dir: "/gemini-data/data"
- # [data].store-meta-dir in openGemini.conf.
- meta_dir: "/gemini-data/data/meta"
- # openGemini Cluster log file storage directory. (same on same server)
- log_dir: "/gemini-deploy/log"
- # Storage directory for cluster deployment files, startup scripts, and configuration files. (same on same server)
- deploy_dir: "/gemini-deploy"
\ No newline at end of file
diff --git a/test/example/topology.example2.yaml b/test/example/topology.example2.yaml
deleted file mode 100644
index f296cef..0000000
--- a/test/example/topology.example2.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-global:
- user: "gemini"
- group: "gemini"
- ssh_port: 22
- base_data_dir: "/gemini-data"
- log_dir: "/gemini-deploy/logs"
- deploy_dir: "/gemini-deploy"
- os: "linux"
- arch: "amd64"
-
-ts-meta:
- - host: 10.0.1.11
- data_dir: "/gemini-data/meta/1"
- - host: 10.0.1.12
- data_dir: "/gemini-data/meta/2"
- - host: 10.0.1.13
- data_dir: "/gemini-data/meta/3"
-
-ts-sql:
- - host: 10.0.1.14
- - host: 10.0.1.15
- - host: 10.0.1.16
-
-ts-store:
- - host: 10.0.1.17
- data_dir: "/gemini-data/data/7"
- meta_dir: "/gemini-data/data/meta/7"
- - host: 10.0.1.18
- data_dir: "/gemini-data/data/8"
- meta_dir: "/gemini-data/data/meta/8"
- - host: 10.0.1.19
- data_dir: "/gemini-data/data/9"
- meta_dir: "/gemini-data/data/meta/9"
\ No newline at end of file
diff --git a/test/example/topology.example3.yaml b/test/example/topology.example3.yaml
deleted file mode 100644
index 7a296dc..0000000
--- a/test/example/topology.example3.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-global:
- user: "gemini"
- group: "gemini"
- ssh_port: 22
- base_data_dir: "/gemini-data"
- log_dir: "/gemini-deploy/logs"
- deploy_dir: "/gemini-deploy"
- os: "linux"
- arch: "amd64"
-
-ts-meta:
- - host: 121.48.161.82
- data_dir: "/gemini-data/meta"
- - host: 121.48.161.84
- data_dir: "/gemini-data/meta"
- - host: 121.48.161.85
- data_dir: "/gemini-data/meta"
-
-ts-sql:
- - host: 121.48.161.82
-
-ts-store:
- - host: 121.48.161.82
- data_dir: "/gemini-data/data"
- meta_dir: "/gemini-data/data/meta"
- - host: 121.48.161.84
- data_dir: "/gemini-data/data"
- meta_dir: "/gemini-data/data/meta"
\ No newline at end of file
diff --git a/util/connect.go b/util/connect.go
index da9d070..e7fe43b 100644
--- a/util/connect.go
+++ b/util/connect.go
@@ -1,3 +1,17 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 util
import (
@@ -85,11 +99,9 @@ func NewSftpClient(sshClient *ssh.Client) (*sftp.Client, error) {
return sftpClient, nil
}
-// TODO(Benevor):timeout interval
func UploadFile(ip string, localFilePath string, remoteDir string, sftpClient *sftp.Client) error {
- fmt.Printf("start uploading %s to %s:%s \n", localFilePath, ip, remoteDir)
if sftpClient == nil {
- return NoSftpSession
+ return ErrNoSftpSession
}
srcFile, err := os.Open(localFilePath)
if err != nil {
@@ -109,7 +121,7 @@ func UploadFile(ip string, localFilePath string, remoteDir string, sftpClient *s
fmt.Printf("%s:%s read from %s failed! %v\n", ip, path.Join(remoteDir, remoteFileName), localFilePath, err)
return err
}
- fmt.Printf("finish uploading %s to %s:%s \n", localFilePath, ip, remoteDir)
+ fmt.Printf("upload %s to %s:%s \n", localFilePath, ip, remoteDir)
return nil
}
diff --git a/util/const.go b/util/const.go
index 6c0bd5d..492bbd6 100644
--- a/util/const.go
+++ b/util/const.go
@@ -1,66 +1,101 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 util
-import "time"
+import (
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+ "time"
+)
+
+var DownloadDst string
+var InstallScriptPath string
+
+func init() {
+ execPath, _ := os.Getwd()
+ currentUser, err := user.Current()
+ if err != nil {
+ fmt.Println(err)
+ }
+ homeDirectory := currentUser.HomeDir
+ DownloadDst = filepath.Join(homeDirectory, ".gemix")
+ InstallScriptPath = filepath.Join(execPath, "scripts/install.sh")
+
+ if err = os.MkdirAll(DownloadDst, 0750); err != nil {
+ fmt.Println(err)
+ }
+}
-// TODO:这里的大多数后面要改成从配置文件中读取,这里仅仅存储一个默认值
+// env
+const (
+ SshEnvUser = "OPENGEMINI_SSH_USER"
+ SshEnvKey = "OPENGEMINI_SSH_KEY"
+ SshEnvPW = "OPENGEMINI_SSH_PW"
+)
// downloader
const (
- Download_web = "https://github.com/openGemini/openGemini/releases/download"
- Download_fill_char = "openGemini-"
- Download_pkg_suffix = ".tar.gz"
+ DownloadWeb = "https://github.com/openGemini/openGemini/releases/download"
+ DownloadFillChar = "openGemini-"
+ DownloadPkgSuffix = ".tar.gz"
// fixed values
- Download_dst = "./data"
- Download_timeout = 2 * time.Minute
+ DownloadTimeout = 2 * time.Minute
// default values
- Download_default_version = "v1.0.0"
- Download_default_os = "linux"
- Download_default_arch = "amd64"
+ DownloadLatestUrl = "https://github.com/openGemini/openGemini/releases/latest"
+ DownloadDefaultOs = "linux"
+ DownloadDefaultArch = "amd64"
)
// local
const (
- Local_bin_rel_path = "usr/bin"
- Local_etc_rel_path = "etc"
- Local_conf_name = "openGemini.conf"
+ LocalBinRelPath = "usr/bin"
+ LocalEtcRelPath = "etc"
+ LocalConfName = "openGemini.conf"
)
// config
const (
- Install_script_path = "./scripts/install.sh"
- Remote_conf_suffix = "-openGemini.conf"
+ RemoteMetaConfName = "openGemini-meta.conf"
+ RemoteSqlConfName = "openGemini-sql.conf"
+ RemoteStoreConfName = "openGemini-store.conf"
)
// file name
const (
- TS_META = "ts-meta" // process name & bin file name
- TS_SQL = "ts-sql"
- TS_STORE = "ts-store"
- Install_Script = "install.sh"
+ TsMeta = "ts-meta" // process name & bin file name
+ TsSql = "ts-sql"
+ TsStore = "ts-store"
+ InstallScript = "install.sh"
)
// remote
const (
- // openGemini-UP
- Remote_bin_rel_path = "bin"
- Remote_etc_rel_path = "etc"
+ // gemix
+ RemoteBinRelPath = "bin"
+ RemoteEtcRelPath = "etc"
// openGemini
- Remote_pid_path = "pid"
- Remote_pid_suffix = ".pid"
- Remote_log_suffix = ".log"
+ RemotePidPath = "pid"
+ RemotePidSuffix = ".pid"
+ RemoteLogSuffix = ".log"
- META_extra_log = "meta_extra"
- SQL_extra_log = "sql_extra"
- STORE_extra_log = "store_extra"
- META = "meta"
- SQL = "sql"
- STORE = "store"
-)
-
-// version
-const (
- VersionFile = "version"
+ MetaExtraLog = "meta_extra"
+ SqlExtraLog = "sql_extra"
+ StoreExtraLog = "store_extra"
)
diff --git a/util/error.go b/util/error.go
index 5531d24..2fb1d66 100644
--- a/util/error.go
+++ b/util/error.go
@@ -1,12 +1,26 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 util
import "errors"
var (
- UnexpectedNil = errors.New("unexpected nil value")
+ ErrUnexpectedNil = errors.New("unexpected nil value")
- NoSshClient = errors.New("no ssh client")
- NoSftpSession = errors.New("no sftp session")
+ ErrNoSshClient = errors.New("no ssh client")
+ ErrNoSftpSession = errors.New("no sftp session")
- UnknowSSHType = errors.New("unknow ssh type")
+ ErrUnknowSSHType = errors.New("unknow ssh type")
)
diff --git a/util/version.go b/util/version.go
new file mode 100644
index 0000000..e535e6a
--- /dev/null
+++ b/util/version.go
@@ -0,0 +1,50 @@
+// Copyright 2023 Huawei Cloud Computing Technologies Co., Ltd.
+//
+// Licensed 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 util
+
+import (
+ "fmt"
+ "net/url"
+ "os/exec"
+ "path"
+ "regexp"
+ "strings"
+)
+
+func GetLatestVerFromCurl() (string, error) {
+ cmd := exec.Command("curl", "-i", "-k", DownloadLatestUrl)
+ output, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ response := string(output)
+
+ re := regexp.MustCompile(`location:\s*(.*?)\n`)
+ matches := re.FindStringSubmatch(response)
+
+ if len(matches) < 2 {
+ return "", fmt.Errorf("location header not found")
+ }
+ locationValue := strings.TrimSpace(matches[1])
+
+ parsedURL, err := url.Parse(locationValue)
+ if err != nil {
+ return "", err
+ }
+ urlPath := parsedURL.Path
+ lastSegment := path.Base(urlPath)
+
+ return lastSegment, nil
+}