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 +}