From d4d789a829ebf941aa7665106e4e855fe1f705d6 Mon Sep 17 00:00:00 2001 From: Ericwai <921223516@qq.com> Date: Mon, 4 Dec 2023 11:24:18 +0800 Subject: [PATCH] playbook(memcache): support instances and instances_sequence Signed-off-by: Ericwai <921223516@qq.com> --- cli/cli/cli.go | 12 +- cli/command/hosts/list_test.go | 2 +- cli/command/hosts/playbook.go | 2 +- internal/configure/hosts/hc_get.go | 19 +- internal/configure/hosts/hc_item.go | 171 ++++++++++++-- internal/configure/hosts/hosts.go | 237 ++++++++++++++++++-- internal/configure/hosts/variables.go | 46 ++++ internal/configure/monitor.go | 2 +- internal/errno/errno.go | 3 +- internal/task/task/common/client_status.go | 2 +- internal/task/task/common/collect_client.go | 2 +- internal/tui/hosts.go | 4 +- internal/utils/common.go | 4 + playbook/memcached/hosts.yaml | 6 +- playbook/memcached/hosts_instances.yaml | 46 ++++ playbook/memcached/scripts/clean.sh | 16 ++ playbook/memcached/scripts/deploy.sh | 49 +++- playbook/memcached/scripts/start.sh | 5 + playbook/memcached/scripts/status.sh | 25 ++- playbook/memcached/scripts/stop.sh | 12 + 20 files changed, 596 insertions(+), 69 deletions(-) create mode 100644 internal/configure/hosts/variables.go create mode 100644 playbook/memcached/hosts_instances.yaml diff --git a/cli/cli/cli.go b/cli/cli/cli.go index 48139b4a8..5882350cd 100644 --- a/cli/cli/cli.go +++ b/cli/cli/cli.go @@ -278,10 +278,10 @@ func (curveadm *CurveAdm) ClusterTopologyData() string { return curveadm.c func (curveadm *CurveAdm) ClusterPoolData() string { return curveadm.clusterPoolData } func (curveadm *CurveAdm) Monitor() storage.Monitor { return curveadm.monitor } -func (curveadm *CurveAdm) GetHost(host string) (*hosts.HostConfig, error) { +func (curveadm *CurveAdm) GetHost(name string) (*hosts.HostConfig, error) { if len(curveadm.Hosts()) == 0 { return nil, errno.ERR_HOST_NOT_FOUND. - F("host: %s", host) + F("host: %s", name) } hcs, err := hosts.ParseHosts(curveadm.Hosts()) if err != nil { @@ -289,12 +289,12 @@ func (curveadm *CurveAdm) GetHost(host string) (*hosts.HostConfig, error) { } for _, hc := range hcs { - if hc.GetHost() == host { + if hc.GetName() == name { return hc, nil } } return nil, errno.ERR_HOST_NOT_FOUND. - F("host: %s", host) + F("host: %s", name) } func (curveadm *CurveAdm) ParseTopologyData(data string) ([]*topology.DeployConfig, error) { @@ -304,7 +304,7 @@ func (curveadm *CurveAdm) ParseTopologyData(data string) ([]*topology.DeployConf return nil, err } for _, hc := range hcs { - ctx.Add(hc.GetHost(), hc.GetHostname()) + ctx.Add(hc.GetName(), hc.GetHostname()) } dcs, err := topology.ParseTopology(data, ctx) @@ -464,7 +464,7 @@ func (curveadm *CurveAdm) DiffTopology(data1, data2 string) ([]topology.Topology return nil, err } for _, hc := range hcs { - ctx.Add(hc.GetHost(), hc.GetHostname()) + ctx.Add(hc.GetName(), hc.GetHostname()) } if len(data1) == 0 { diff --git a/cli/command/hosts/list_test.go b/cli/command/hosts/list_test.go index 2f52ba707..a02041e7b 100644 --- a/cli/command/hosts/list_test.go +++ b/cli/command/hosts/list_test.go @@ -37,7 +37,7 @@ func run(t *testing.T, data string, labels []string, out []string) { assert.Nil(err) assert.Equal(len(hcs), len(out)) for i, hc := range hcs { - assert.Equal(hc.GetHost(), out[i]) + assert.Equal(hc.GetName(), out[i]) } } diff --git a/cli/command/hosts/playbook.go b/cli/command/hosts/playbook.go index 8bb1f0336..7feab82ee 100644 --- a/cli/command/hosts/playbook.go +++ b/cli/command/hosts/playbook.go @@ -91,7 +91,7 @@ func NewPlaybookCommand(curveadm *cli.CurveAdm) *cobra.Command { func execute(curveadm *cli.CurveAdm, options playbookOptions, idx int, hc *hosts.HostConfig) { defer func() { wg.Done() }() - name := hc.GetHost() + name := hc.GetName() target := path.Join("/tmp", utils.RandString(8)) err := tools.Scp(curveadm, name, options.filepath, target) if err != nil { diff --git a/internal/configure/hosts/hc_get.go b/internal/configure/hosts/hc_get.go index c7c0c6a07..92eab9435 100644 --- a/internal/configure/hosts/hc_get.go +++ b/internal/configure/hosts/hc_get.go @@ -25,25 +25,25 @@ package hosts import ( - comm "github.com/opencurve/curveadm/internal/configure/common" "github.com/opencurve/curveadm/internal/configure/curveadm" "github.com/opencurve/curveadm/internal/utils" "github.com/opencurve/curveadm/pkg/module" + "github.com/opencurve/curveadm/pkg/variable" ) -func (hc *HostConfig) get(i *comm.Item) interface{} { +func (hc *HostConfig) get(i *item) interface{} { if v, ok := hc.config[i.Key()]; ok { return v } - defaultValue := i.DefaultValue() + defaultValue := i.defaultValue if defaultValue != nil && utils.IsFunc(defaultValue) { return defaultValue.(func(*HostConfig) interface{})(hc) } return defaultValue } -func (hc *HostConfig) getString(i *comm.Item) string { +func (hc *HostConfig) getString(i *item) string { v := hc.get(i) if v == nil { return "" @@ -51,7 +51,7 @@ func (hc *HostConfig) getString(i *comm.Item) string { return v.(string) } -func (hc *HostConfig) getInt(i *comm.Item) int { +func (hc *HostConfig) getInt(i *item) int { v := hc.get(i) if v == nil { return 0 @@ -59,7 +59,7 @@ func (hc *HostConfig) getInt(i *comm.Item) int { return v.(int) } -func (hc *HostConfig) getBool(i *comm.Item) bool { +func (hc *HostConfig) getBool(i *item) bool { v := hc.get(i) if v == nil { return false @@ -67,7 +67,7 @@ func (hc *HostConfig) getBool(i *comm.Item) bool { return v.(bool) } -func (hc *HostConfig) GetHost() string { return hc.getString(CONFIG_HOST) } +func (hc *HostConfig) GetName() string { return hc.getString(CONFIG_NAME) } func (hc *HostConfig) GetHostname() string { return hc.getString(CONFIG_HOSTNAME) } func (hc *HostConfig) GetSSHHostname() string { return hc.getString(CONFIG_SSH_HOSTNAME) } func (hc *HostConfig) GetSSHPort() int { return hc.getInt(CONFIG_SSH_PORT) } @@ -77,6 +77,10 @@ func (hc *HostConfig) GetBecomeUser() string { return hc.getString(CONFIG_BE func (hc *HostConfig) GetLabels() []string { return hc.labels } func (hc *HostConfig) GetEnvs() []string { return hc.envs } +func (hc *HostConfig) GetInstances() int { return hc.instances } +func (hc *HostConfig) GetInstancesSequence() int { return hc.instancesSequence } +func (hc *HostConfig) GetVariables() *variable.Variables { return hc.variables } + func (hc *HostConfig) GetUser() string { user := hc.getString(CONFIG_USER) if user == "${user}" { @@ -84,7 +88,6 @@ func (hc *HostConfig) GetUser() string { } return user } - func (hc *HostConfig) GetSSHConfig() *module.SSHConfig { hostname := hc.GetSSHHostname() if len(hostname) == 0 { diff --git a/internal/configure/hosts/hc_item.go b/internal/configure/hosts/hc_item.go index 6900cbd63..960894e7f 100644 --- a/internal/configure/hosts/hc_item.go +++ b/internal/configure/hosts/hc_item.go @@ -26,75 +26,210 @@ package hosts import ( "fmt" + "github.com/opencurve/curveadm/internal/errno" - comm "github.com/opencurve/curveadm/internal/configure/common" "github.com/opencurve/curveadm/internal/utils" ) const ( + REQUIRE_ANY = iota + REQUIRE_INT + REQUIRE_STRING + REQUIRE_BOOL + REQUIRE_POSITIVE_INTEGER + REQUIRE_STRING_SLICE + DEFAULT_SSH_PORT = 22 ) +type ( + // config item + item struct { + key string + require int + exclude bool // exclude for service config + defaultValue interface{} // nil means no default value + } + + itemSet struct { + items []*item + key2item map[string]*item + } +) + var ( - itemset = comm.NewItemSet() + itemset = &itemSet{ + items: []*item{}, + key2item: map[string]*item{}, + } - CONFIG_HOST = itemset.Insert( + CONFIG_HOST = itemset.insert( "host", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) - CONFIG_HOSTNAME = itemset.Insert( + CONFIG_NAME = itemset.insert( + "name", + REQUIRE_STRING, + false, + func(hc *HostConfig) interface{} { + return hc.getString(CONFIG_HOST) + }, + ) + + CONFIG_HOSTNAME = itemset.insert( "hostname", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) - CONFIG_SSH_HOSTNAME = itemset.Insert( + CONFIG_SSH_HOSTNAME = itemset.insert( "ssh_hostname", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) - CONFIG_USER = itemset.Insert( + CONFIG_USER = itemset.insert( "user", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, func(hc *HostConfig) interface{} { return utils.GetCurrentUser() }, ) - CONFIG_SSH_PORT = itemset.Insert( + CONFIG_SSH_PORT = itemset.insert( "ssh_port", - comm.REQUIRE_POSITIVE_INTEGER, + REQUIRE_POSITIVE_INTEGER, false, DEFAULT_SSH_PORT, ) - CONFIG_PRIVATE_CONFIG_FILE = itemset.Insert( + CONFIG_PRIVATE_CONFIG_FILE = itemset.insert( "private_key_file", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, func(hc *HostConfig) interface{} { return fmt.Sprintf("%s/.ssh/id_rsa", utils.GetCurrentHomeDir()) }, ) - CONFIG_FORWARD_AGENT = itemset.Insert( + CONFIG_FORWARD_AGENT = itemset.insert( "forward_agent", - comm.REQUIRE_BOOL, + REQUIRE_BOOL, false, false, ) - CONFIG_BECOME_USER = itemset.Insert( + CONFIG_BECOME_USER = itemset.insert( "become_user", - comm.REQUIRE_STRING, + REQUIRE_STRING, false, nil, ) ) + +func convertSlice[T any](key, value any) ([]T, error) { + var slice []T + if !utils.IsAnySlice(value) || len(value.([]any)) == 0 { + return slice, errno.ERR_CONFIGURE_VALUE_REQUIRES_NONEMPTY_SLICE + } + anySlice := value.([]any) + switch anySlice[0].(type) { + case T: + for _, element := range anySlice { + slice = append(slice, element.(T)) + } + default: + return slice, errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", key, value) + } + + return slice, nil +} + +func (i *item) Key() string { + return i.key +} + +func (itemset *itemSet) insert(key string, require int, exclude bool, defaultValue interface{}) *item { + i := &item{key, require, exclude, defaultValue} + itemset.key2item[key] = i + itemset.items = append(itemset.items, i) + return i +} + +func (itemset *itemSet) get(key string) *item { + return itemset.key2item[key] +} + +func (itemset *itemSet) getAll() []*item { + return itemset.items +} + +func (itemset *itemSet) Build(key string, value interface{}) (interface{}, error) { + item := itemset.get(key) + if item == nil { + return value, nil + } + + v, ok := utils.All2Str(value) + if !ok { + if !utils.IsAnySlice(value) { + return nil, errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", key, value) + } + } + + switch item.require { + case REQUIRE_ANY: + // do nothing + + case REQUIRE_STRING: + if len(v) == 0 { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_NON_EMPTY_STRING. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_INT: + if v, ok := utils.Str2Int(v); !ok { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_POSITIVE_INTEGER: + if v, ok := utils.Str2Int(v); !ok { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", key, value) + } else if v <= 0 { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_BOOL: + if v, ok := utils.Str2Bool(v); !ok { + return nil, errno.ERR_CONFIGURE_VALUE_REQUIRES_BOOL. + F("%s: %v", key, value) + } else { + return v, nil + } + + case REQUIRE_STRING_SLICE: + return convertSlice[string](key, value) + + default: + // do nothing + } + + return value, nil +} diff --git a/internal/configure/hosts/hosts.go b/internal/configure/hosts/hosts.go index c8fa86011..879bd9ff4 100644 --- a/internal/configure/hosts/hosts.go +++ b/internal/configure/hosts/hosts.go @@ -26,18 +26,21 @@ package hosts import ( "bytes" + "github.com/opencurve/curveadm/pkg/variable" "strings" "github.com/opencurve/curveadm/internal/build" "github.com/opencurve/curveadm/internal/configure/os" "github.com/opencurve/curveadm/internal/errno" "github.com/opencurve/curveadm/internal/utils" + log "github.com/opencurve/curveadm/pkg/log/glg" "github.com/spf13/viper" ) const ( - KEY_LABELS = "labels" - KEY_ENVS = "envs" + KEY_LABELS = "labels" + KEY_ENVS = "envs" + KEY_INSTANCES = "instances" PERMISSIONS_600 = 384 // -rw------- (256 + 128 = 384) ) @@ -49,10 +52,16 @@ type ( } HostConfig struct { - sequence int - config map[string]interface{} - labels []string - envs []string + sequence int + config map[string]interface{} + labels []string + envs []string + variables *variable.Variables + //instances and instancesSequence only used in the memcached deploy + //instances is the num of memcached servers will be deployed in the same host + instances int + //instancesSquence is the sequence num of memcached servers in the same host + instancesSequence int } ) @@ -111,6 +120,80 @@ func (hc *HostConfig) convertEnvs() error { return nil } +// read the instances value from hc.config +func (hc *HostConfig) convertInstances() error { + value := hc.config[KEY_INSTANCES] + v, ok := utils.All2Str(value) + if !ok { + if !utils.IsAnySlice(value) { + return errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("hosts[%d].%s = %v", hc.sequence, KEY_INSTANCES, value) + } + } + if v, ok := utils.Str2Int(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("hosts[%d].%s = %v", hc.sequence, KEY_INSTANCES, value) + } else if v <= 0 { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER. + F("hosts[%d].%s = %v", hc.sequence, KEY_INSTANCES, value) + } else { + hc.instances = v + return nil + } +} + +// convert config item to its require type after rendering, +// return error if convert failed +func (hc *HostConfig) convert() error { + for _, item := range itemset.getAll() { + k := item.key + value := hc.get(item) // return config value or default value + if value == nil { + continue + } + v, ok := utils.All2Str(value) + if !ok { + return errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", k, value) + } + + switch item.require { + case REQUIRE_ANY: + // do nothing + case REQUIRE_INT: + if intv, ok := utils.Str2Int(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", k, value) + } else { + hc.config[k] = intv + } + case REQUIRE_STRING: + if len(v) == 0 { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_NON_EMPTY_STRING. + F("%s: %v", k, value) + } + case REQUIRE_BOOL: + if boolv, ok := utils.Str2Bool(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_BOOL. + F("%s: %v", k, value) + } else { + hc.config[k] = boolv + } + case REQUIRE_POSITIVE_INTEGER: + if intv, ok := utils.Str2Int(v); !ok { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_INTEGER. + F("%s: %v", k, value) + } else if intv <= 0 { + return errno.ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER. + F("%s: %v", k, value) + } else { + hc.config[k] = intv + } + } + } + return nil +} + func (hc *HostConfig) Build() error { for key, value := range hc.config { if key == KEY_LABELS { // convert labels @@ -123,11 +206,17 @@ func (hc *HostConfig) Build() error { if err := hc.convertEnvs(); err != nil { return err } - hc.config[key] = nil // delete labels section + hc.config[key] = nil // delete envs section + continue + } else if key == KEY_INSTANCES { // convert instances + if err := hc.convertInstances(); err != nil { + return err + } + hc.config[key] = nil // delete instances section continue } - if itemset.Get(key) == nil { + if itemset.get(key) == nil { return errno.ERR_UNSUPPORT_HOSTS_CONFIGURE_ITEM. F("hosts[%d].%s = %v", hc.sequence, key, value) } @@ -141,7 +230,7 @@ func (hc *HostConfig) Build() error { } privateKeyFile := hc.GetPrivateKeyFile() - if len(hc.GetHost()) == 0 { + if len(hc.GetName()) == 0 { return errno.ERR_HOST_FIELD_MISSING. F("hosts[%d].host = nil", hc.sequence) } else if len(hc.GetHostname()) == 0 { @@ -158,7 +247,7 @@ func (hc *HostConfig) Build() error { F("hosts[%d].private_key_file = %s", hc.sequence, privateKeyFile) } - if hc.GetForwardAgent() == false { + if !hc.GetForwardAgent() { if !utils.PathExist(privateKeyFile) { return errno.ERR_PRIVATE_KEY_FILE_NOT_EXIST. F("%s: no such file", privateKeyFile) @@ -170,11 +259,108 @@ func (hc *HostConfig) Build() error { return nil } +// "PORT=112${instancesSquence}" -> "PORT=11201" +func (hc *HostConfig) renderVariables() error { + //0. get vars + vars := hc.GetVariables() + if err := vars.Build(); err != nil { + log.Error("Build variables failed", + log.Field("error", err)) + return errno.ERR_RESOLVE_VARIABLE_FAILED.E(err) + } + //1. all config to str + for k, v := range hc.config { + if v == nil { + continue + } + if strv, ok := utils.All2Str(v); ok { + hc.config[k] = strv + } else { + return errno.ERR_UNSUPPORT_CONFIGURE_VALUE_TYPE. + F("%s: %v", k, v) + } + } + //2. rendering + //render labels + for i := range hc.labels { + err := func(value *string) error { + realValue, err := vars.Rendering(*value) + if err != nil { + return err + } + *value = realValue + return nil + }(&hc.labels[i]) + if err != nil { + return errno.ERR_RENDERING_VARIABLE_FAILED.E(err) + } + } + //render envs + for i := range hc.envs { + err := func(value *string) error { + realValue, err := vars.Rendering(*value) + if err != nil { + return err + } + *value = realValue + return nil + }(&hc.envs[i]) + if err != nil { + return errno.ERR_RENDERING_VARIABLE_FAILED.E(err) + } + } + //render config + for k, v := range hc.config { + if v == nil { + continue + } + realv, err := vars.Rendering(v.(string)) + if err != nil { + return errno.ERR_RENDERING_VARIABLE_FAILED.E(err) + } + hc.config[k] = realv + build.DEBUG(build.DEBUG_TOPOLOGY, + build.Field{Key: k, Value: v}, + build.Field{Key: k, Value: realv}) + } + //3. convert config item to its require type after rendering, + // return error if convert failed + return hc.convert() +} + func NewHostConfig(sequence int, config map[string]interface{}) *HostConfig { + vars := variable.NewVariables() + return &HostConfig{ - sequence: sequence, - config: config, - labels: []string{}, + sequence: sequence, + config: config, + labels: []string{}, + envs: []string{}, + variables: vars, + //instances and instancesSquence only used in the memcached deploy + instances: 1, + instancesSequence: 1, + } +} + +// deepcopy a HostConfig with instancesSquence and return it (new variables) +func copyHostConfig(src *HostConfig, instancesSquence int) *HostConfig { + //deepcopy labels + newlabels := make([]string, len(src.labels)) + copy(newlabels, src.labels) + //deepcopy envs + newenvs := make([]string, len(src.envs)) + copy(newenvs, src.envs) + //create a new variables + vars := variable.NewVariables() + return &HostConfig{ + sequence: src.sequence, + config: utils.DeepCopy(src.config), + labels: newlabels, + envs: newenvs, + variables: vars, + instances: src.instances, + instancesSequence: instancesSquence, } } @@ -182,7 +368,6 @@ func ParseHosts(data string) ([]*HostConfig, error) { if len(data) == 0 { return nil, errno.ERR_EMPTY_HOSTS } - parser := viper.NewWithOptions(viper.KeyDelimiter("::")) parser.SetConfigType("yaml") err := parser.ReadConfig(bytes.NewBuffer([]byte(data))) @@ -206,12 +391,26 @@ func ParseHosts(data string) ([]*HostConfig, error) { return nil, err } - if _, ok := exist[hc.GetHost()]; ok { - return nil, errno.ERR_DUPLICATE_HOST. - F("duplicate host: %s", hc.GetHost()) + if _, ok := exist[hc.GetName()]; ok { + return nil, errno.ERR_DUPLICATE_NAME. + F("duplicate host: %s", hc.GetName()) + } + //produce the instances of hc, append to hcs. + instances := hc.GetInstances() + for instancesSquence := 1; instancesSquence <= instances; instancesSquence++ { + hc_new := copyHostConfig(hc, instancesSquence) + hcs = append(hcs, hc_new) + } + exist[hc.GetName()] = true + } + //add Variables and Rendering + for idx, hc := range hcs { + if err = AddHostVariables(hcs, idx); err != nil { + return nil, err // already is error code + } else if err = hc.renderVariables(); err != nil { + return nil, err // already is error code } - hcs = append(hcs, hc) - exist[hc.GetHost()] = true + hc.GetVariables().Debug() } build.DEBUG(build.DEBUG_HOSTS, hosts) return hcs, nil diff --git a/internal/configure/hosts/variables.go b/internal/configure/hosts/variables.go new file mode 100644 index 000000000..4d31bfb66 --- /dev/null +++ b/internal/configure/hosts/variables.go @@ -0,0 +1,46 @@ +package hosts + +import ( + "fmt" + "github.com/opencurve/curveadm/internal/errno" + "github.com/opencurve/curveadm/pkg/variable" +) + +type Var struct { + name string + resolved bool +} + +var ( + hostVars = []Var{ + {name: "instances_sequence"}, + } +) + +func addVariables(hcs []*HostConfig, idx int, vars []Var) error { + hc := hcs[idx] + for _, v := range vars { + err := hc.GetVariables().Register(variable.Variable{ + Name: v.name, + Value: getValue(v.name, hcs, idx), + }) + if err != nil { + return errno.ERR_REGISTER_VARIABLE_FAILED.E(err) + } + } + + return nil +} + +func AddHostVariables(hcs []*HostConfig, idx int) error { + return addVariables(hcs, idx, hostVars) +} + +func getValue(name string, hcs []*HostConfig, idx int) string { + hc := hcs[idx] + switch name { + case "instances_sequence": + return fmt.Sprintf("%02d", hc.GetInstancesSequence()) + } + return "" +} diff --git a/internal/configure/monitor.go b/internal/configure/monitor.go index 6d7f34a94..2a4339b21 100644 --- a/internal/configure/monitor.go +++ b/internal/configure/monitor.go @@ -265,7 +265,7 @@ func ParseMonitorConfig(curveadm *cli.CurveAdm, filename string, data string, hs return nil, err } for _, hc := range hcs { - ctx.Add(hc.GetHost(), hc.GetHostname()) + ctx.Add(hc.GetName(), hc.GetHostname()) } mkind := dcs[0].GetKind() diff --git a/internal/errno/errno.go b/internal/errno/errno.go index 46b8228c5..c8334d4b9 100644 --- a/internal/errno/errno.go +++ b/internal/errno/errno.go @@ -287,6 +287,7 @@ var ( ERR_CONFIGURE_VALUE_REQUIRES_NON_EMPTY_STRING = EC(301004, "configure value requires non-empty string") ERR_CONFIGURE_VALUE_REQUIRES_POSITIVE_INTEGER = EC(301005, "configure value requires positive integer") ERR_CONFIGURE_VALUE_REQUIRES_STRING_SLICE = EC(301006, "configure value requires string array") + ERR_CONFIGURE_VALUE_REQUIRES_NONEMPTY_SLICE = EC(301007, "configure value requires nonempty array") ERR_UNSUPPORT_VARIABLE_VALUE_TYPE = EC(301100, "unsupport variable value type") ERR_INVALID_VARIABLE_VALUE = EC(301101, "invalid variable value") @@ -310,7 +311,7 @@ var ( ERR_PRIVATE_KEY_FILE_REQUIRE_ABSOLUTE_PATH = EC(321004, "SSH private key file needs to be an absolute path") ERR_PRIVATE_KEY_FILE_NOT_EXIST = EC(321005, "SSH private key file not exist") ERR_PRIVATE_KEY_FILE_REQUIRE_600_PERMISSIONS = EC(321006, "SSH private key file require 600 permissions") - ERR_DUPLICATE_HOST = EC(321007, "host is duplicate") + ERR_DUPLICATE_NAME = EC(321007, "name is duplicate") ERR_HOSTNAME_REQUIRES_VALID_IP_ADDRESS = EC(321008, "hostname requires valid IP address") // 322: configure (monitor.yaml: parse failed) diff --git a/internal/task/task/common/client_status.go b/internal/task/task/common/client_status.go index 87628814a..845fa3c6e 100644 --- a/internal/task/task/common/client_status.go +++ b/internal/task/task/common/client_status.go @@ -163,7 +163,7 @@ func NewGetClientStatusTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, containerId := client.ContainerId subname := fmt.Sprintf("host=%s kind=%s containerId=%s", - hc.GetHost(), client.Kind, tui.TrimContainerId(containerId)) + hc.GetName(), client.Kind, tui.TrimContainerId(containerId)) t := task.NewTask("Get Client Status", subname, hc.GetSSHConfig()) // add step diff --git a/internal/task/task/common/collect_client.go b/internal/task/task/common/collect_client.go index f9085cf20..527e452ee 100644 --- a/internal/task/task/common/collect_client.go +++ b/internal/task/task/common/collect_client.go @@ -46,7 +46,7 @@ func NewCollectClientTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, er // new task containerId := client.ContainerId subname := fmt.Sprintf("host=%s kind=%s containerId=%s", - hc.GetHost(), client.Kind, tui.TrimContainerId(containerId)) + hc.GetName(), client.Kind, tui.TrimContainerId(containerId)) t := task.NewTask("Collect Client", subname, hc.GetSSHConfig()) // add step to task diff --git a/internal/tui/hosts.go b/internal/tui/hosts.go index 9a19ba983..54c9381ce 100644 --- a/internal/tui/hosts.go +++ b/internal/tui/hosts.go @@ -58,7 +58,7 @@ func FormatHosts(hcs []*configure.HostConfig, verbose bool) string { for i := 0; i < len(hcs); i++ { hc := hcs[i] - host := hc.GetHost() + name := hc.GetName() hostname := hc.GetHostname() user := hc.GetUser() port := strconv.Itoa(hc.GetSSHPort()) @@ -74,7 +74,7 @@ func FormatHosts(hcs []*configure.HostConfig, verbose bool) string { } lines = append(lines, []interface{}{ - host, + name, hostname, user, port, diff --git a/internal/utils/common.go b/internal/utils/common.go index 056a649a8..91515a81c 100644 --- a/internal/utils/common.go +++ b/internal/utils/common.go @@ -97,6 +97,10 @@ func IsStringAnyMap(v interface{}) bool { return Type(v) == "string_interface_map" } +func IsAnySlice(v interface{}) bool { + return Type(v) == "any_slice" +} + func IsFunc(v interface{}) bool { return reflect.TypeOf(v).Kind() == reflect.Func } diff --git a/playbook/memcached/hosts.yaml b/playbook/memcached/hosts.yaml index ee69994cc..06ae3ddce 100644 --- a/playbook/memcached/hosts.yaml +++ b/playbook/memcached/hosts.yaml @@ -4,7 +4,7 @@ global: private_key_file: /home/curve/.ssh/id_rsa hosts: - - host: server-host1 + - name: server-host1 hostname: 10.0.1.1 labels: - memcached @@ -23,7 +23,7 @@ hosts: - VERBOSE="v" # v: verbose (print errors/warnings while in event loop) # vv: very verbose (also print client commands/responses) # vvv: extremely verbose (internal state transitions) - - host: server-host2 + - name: server-host2 hostname: 10.0.1.2 labels: - memcached @@ -42,7 +42,7 @@ hosts: - VERBOSE="v" # v: verbose (print errors/warnings while in event loop) # vv: very verbose (also print client commands/responses) # vvv: extremely verbose (internal state transitions) - - host: server-host3 + - name: server-host3 hostname: 10.0.1.3 labels: - memcached diff --git a/playbook/memcached/hosts_instances.yaml b/playbook/memcached/hosts_instances.yaml new file mode 100644 index 000000000..2f6d8220b --- /dev/null +++ b/playbook/memcached/hosts_instances.yaml @@ -0,0 +1,46 @@ +global: + user: curve + ssh_port: 22 + private_key_file: /home/curve/.ssh/id_rsa + +hosts: + - name: server-host1 + hostname: 10.0.1.1 + labels: + - memcached + envs: + - SUDO_ALIAS=sudo + - ENGINE=docker + - IMAGE=memcached:1.6.17 + - EXPORTER_IMAGE=quay.io/prometheus/memcached-exporter:v0.13.0 + - LISTEN=10.0.1.1 + - PORT=112${instances_sequence} + - EXPORTER_PORT=91${instances_sequence} + - USER=root + - MEMORY_LIMIT=32768 # item memory in megabytes + - MAX_ITEM_SIZE=8m # adjusts max item size (default: 1m, min: 1k, max: 1024m) + - EXT_PATH=/mnt/memcachefile/cachefile:10${instances_sequence}G + - EXT_WBUF_SIZE=8 # size in megabytes of page write buffers. + - EXT_ITEM_AGE=1 # store items idle at least this long (seconds, default: no age limit) + - VERBOSE="v" + instances: 3 + + - name: server-host2 + hostname: 10.0.1.2 + labels: + - memcached + envs: + - SUDO_ALIAS=sudo + - ENGINE=docker + - IMAGE=memcached:1.6.17 + - EXPORTER_IMAGE=quay.io/prometheus/memcached-exporter:v0.13.0 + - LISTEN=10.0.1.2 + - PORT=11211 + - EXPORTER_PORT=9151 + - USER=root + - MEMORY_LIMIT=32768 # item memory in megabytes + - MAX_ITEM_SIZE=8m # adjusts max item size (default: 1m, min: 1k, max: 1024m) + - EXT_PATH=/mnt/memcachefile/cachefile:1024G + - EXT_WBUF_SIZE=8 # size in megabytes of page write buffers. + - EXT_ITEM_AGE=1 # store items idle at least this long (seconds, default: no age limit) + - VERBOSE="v" \ No newline at end of file diff --git a/playbook/memcached/scripts/clean.sh b/playbook/memcached/scripts/clean.sh index 84e0f2a6a..96b36554a 100644 --- a/playbook/memcached/scripts/clean.sh +++ b/playbook/memcached/scripts/clean.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_rm_cmd="${SUDO_ALIAS} rm -rf" @@ -23,6 +24,13 @@ precheck() { die "container [${g_container_name}] not exists!!!\n" exit 1 fi + if [ "${EXPORTER_PORT}" ];then + container_id=`${g_docker_cmd} ps --all --format "{{.ID}}" --filter name=${g_exporter_container_name}` + if [ -z ${container_id} ]; then + die "container [${g_exporter_container_name}] not exists!!!\n" + exit 1 + fi + fi } stop_container() { @@ -32,6 +40,14 @@ stop_container() { exit 1 fi success "rm container[${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + msg=`${g_docker_cmd} rm ${g_exporter_container_name}` + if [ $? -ne 0 ];then + die "${msg}\n" + exit 1 + fi + success "rm container[${g_exporter_container_name}]\n" + fi } rm_cachefile() { diff --git a/playbook/memcached/scripts/deploy.sh b/playbook/memcached/scripts/deploy.sh index f2c48b165..7df5a2c25 100644 --- a/playbook/memcached/scripts/deploy.sh +++ b/playbook/memcached/scripts/deploy.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_start_args="" +g_exporter_start_args="" g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_lsof_cmd="${SUDO_ALIAS} lsof" g_rm_cmd="${SUDO_ALIAS} rm -rf" @@ -28,7 +30,21 @@ precheck() { container_id=`${g_docker_cmd} ps --format "{{.ID}}" --filter name=${g_container_name} --all` if [ "${container_id}" ]; then success "container [${g_container_name}] already exists, skip\n" - exit 0 + exit 1 + fi + + if [ "${EXPORTER_PORT}" ];then + container_id=`${g_docker_cmd} ps --format "{{.ID}}" --filter name=${g_exporter_container_name} --all` + if [ "${container_id}" ]; then + success "container [${g_exporter_container_name}] already exists, skip\n" + exit 1 + fi + + ${g_lsof_cmd} -i:${EXPORTER_PORT} >& /dev/null + if [ $? -eq 0 ];then + die "port[${EXPORTER_PORT}] is in use!\n" + exit 1 + fi fi # check port @@ -79,14 +95,28 @@ init() { if [ "${VERBOSE}" ];then g_start_args="${g_start_args} -${VERBOSE}" fi + + + if [ "${EXPORTER_PORT}" ];then + g_exporter_start_args="${g_exporter_start_args} --memcached.address=${LISTEN}:${PORT}" + g_exporter_start_args="${g_exporter_start_args} --web.listen-address=${LISTEN}:${EXPORTER_PORT}" + fi } create_container() { - success "create container [${g_container_name}]\n" ${g_docker_cmd} create --name ${g_container_name} ${g_user} --network host ${g_volume_bind} ${IMAGE} memcached ${g_start_args} >& /dev/null - - success "start container [${g_container_name}]\n" + success "create container [${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} create --name ${g_exporter_container_name} --network host ${EXPORTER_IMAGE} ${g_exporter_start_args} >& /dev/null + success "create container [${g_exporter_container_name}]\n" + fi ${g_docker_cmd} start ${g_container_name} >& /dev/null + success "start container [${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} start ${g_exporter_container_name} >& /dev/null + success "start container [${g_exporter_container_name}]\n" + fi + success "wait 3 seconds, check container status...\n" sleep 3 @@ -95,14 +125,25 @@ create_container() { if [ ${g_status} != "running" ]; then exit 1 fi + if [ "${EXPORTER_PORT}" ];then + if [ ${g_exporter_status} != "running" ]; then + exit 1 + fi + fi } get_status_container() { g_status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_container_name}` + if [ "${EXPORTER_PORT}" ];then + g_exporter_status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_exporter_container_name}` + fi } show_info_container() { ${g_docker_cmd} ps --all --filter "name=${g_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} ps --all --filter "name=${g_exporter_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + fi } precheck diff --git a/playbook/memcached/scripts/start.sh b/playbook/memcached/scripts/start.sh index 9c7c380fa..bd9e33455 100644 --- a/playbook/memcached/scripts/start.sh +++ b/playbook/memcached/scripts/start.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_rm_cmd="${SUDO_ALIAS} rm -rf" g_mkdir_cmd="${SUDO_ALIAS} mkdir -p" @@ -34,6 +35,10 @@ precheck() { start_container() { ${g_docker_cmd} start ${g_container_name} >& /dev/null success "start container[${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} start ${g_exporter_container_name} >& /dev/null + success "start container[${g_exporter_container_name}]\n" + fi } get_status_container() { diff --git a/playbook/memcached/scripts/status.sh b/playbook/memcached/scripts/status.sh index ed875ec85..32507bb5a 100644 --- a/playbook/memcached/scripts/status.sh +++ b/playbook/memcached/scripts/status.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_start_args="" g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" g_volume_bind="" g_container_id="" -g_status="running" function msg() { printf '%b' "$1" >&2 @@ -26,20 +26,39 @@ precheck() { success "container [${g_container_name}] not exists!!!" exit 1 fi + if [ "${EXPORTER_PORT}" ];then + g_container_id=`${g_docker_cmd} ps --all --format "{{.ID}}" --filter name=${g_exporter_container_name}` + if [ -z ${g_container_id} ]; then + success "container [${g_exporter_container_name}] not exists!!!" + exit 1 + fi + fi } show_info_container() { ${g_docker_cmd} ps --all --filter "name=${g_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} ps --all --filter "name=${g_exporter_container_name}" --format="table {{.ID}}\t{{.Names}}\t{{.Status}}" + fi } show_ip_port() { printf "memcached addr:\t%s:%d\n" ${LISTEN} ${PORT} + if [ "${EXPORTER_PORT}" ];then + printf "memcached-exporter addr:\t%s:%d\n" ${LISTEN} ${EXPORTER_PORT} + fi } get_status_container() { - g_status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_container_name}` - if [ ${g_status} != "running" ]; then + status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_container_name}` + if [ ${status} != "running" ]; then + exit 1 + fi + if [ "${EXPORTER_PORT}" ];then + status=`${g_docker_cmd} inspect --format='{{.State.Status}}' ${g_exporter_container_name}` + if [ ${status} != "running" ]; then exit 1 + fi fi } diff --git a/playbook/memcached/scripts/stop.sh b/playbook/memcached/scripts/stop.sh index 2dc84e53f..2d51a2b5b 100644 --- a/playbook/memcached/scripts/stop.sh +++ b/playbook/memcached/scripts/stop.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash g_container_name="memcached-"${PORT} +g_exporter_container_name="memcached-exporter-"${EXPORTER_PORT} g_docker_cmd="${SUDO_ALIAS} ${ENGINE}" function msg() { @@ -22,11 +23,22 @@ precheck() { die "container [${g_container_name}] not exists!!!\n" exit 1 fi + if [ "${EXPORTER_PORT}" ];then + container_id=`${g_docker_cmd} ps --all --format "{{.ID}}" --filter name=${g_exporter_container_name}` + if [ -z ${container_id} ]; then + die "container [${g_exporter_container_name}] not exists!!!\n" + exit 1 + fi + fi } stop_container() { ${g_docker_cmd} stop ${g_container_name} >& /dev/null success "stop container[${g_container_name}]\n" + if [ "${EXPORTER_PORT}" ];then + ${g_docker_cmd} stop ${g_exporter_container_name} >& /dev/null + success "stop container[${g_exporter_container_name}]\n" + fi } precheck