From 04ff951ad3ad5e8004292c1701a1757a465f1dd4 Mon Sep 17 00:00:00 2001 From: f1v3-dev Date: Fri, 8 May 2026 10:51:18 +0900 Subject: [PATCH] FEATURE: Add foundation for zk and cluster management --- .github/workflows/ci.yml | 2 +- cmd/cluster/cluster.go | 17 ++++ cmd/cluster/delete.go | 12 +++ cmd/cluster/deploy.go | 12 +++ cmd/cluster/list.go | 12 +++ cmd/cluster/start.go | 16 ++++ cmd/cluster/status.go | 12 +++ cmd/cluster/stop.go | 16 ++++ cmd/connect.go | 2 +- cmd/root.go | 4 + cmd/zk/delete.go | 12 +++ cmd/zk/deploy.go | 12 +++ cmd/zk/list.go | 12 +++ cmd/zk/start.go | 16 ++++ cmd/zk/status.go | 12 +++ cmd/zk/stop.go | 16 ++++ cmd/zk/zk.go | 17 ++++ go.mod | 11 +-- go.sum | 14 +-- internal/config.go | 7 ++ internal/ssh/ssh.go | 93 +++++++++++++++++++ internal/store/store.go | 169 +++++++++++++++++++++++++++++++++++ internal/topology/cluster.go | 47 ++++++++++ internal/topology/loader.go | 35 ++++++++ internal/topology/zk.go | 35 ++++++++ 25 files changed, 600 insertions(+), 13 deletions(-) create mode 100644 cmd/cluster/cluster.go create mode 100644 cmd/cluster/delete.go create mode 100644 cmd/cluster/deploy.go create mode 100644 cmd/cluster/list.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/zk/delete.go create mode 100644 cmd/zk/deploy.go create mode 100644 cmd/zk/list.go create mode 100644 cmd/zk/start.go create mode 100644 cmd/zk/status.go create mode 100644 cmd/zk/stop.go create mode 100644 cmd/zk/zk.go create mode 100644 internal/ssh/ssh.go create mode 100644 internal/store/store.go create mode 100644 internal/topology/cluster.go create mode 100644 internal/topology/loader.go create mode 100644 internal/topology/zk.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8500823..fe42941 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24.6' + go-version: '1.25.0' - name: Cache Go modules uses: actions/cache@v4 with: diff --git a/cmd/cluster/cluster.go b/cmd/cluster/cluster.go new file mode 100644 index 0000000..ae1cf52 --- /dev/null +++ b/cmd/cluster/cluster.go @@ -0,0 +1,17 @@ +package cluster + +import "github.com/spf13/cobra" + +var ClusterCmd = &cobra.Command{ + Use: "cluster", + Short: "Manage Arcus clusters", +} + +func init() { + ClusterCmd.AddCommand(deployCmd) + ClusterCmd.AddCommand(startCmd) + ClusterCmd.AddCommand(stopCmd) + ClusterCmd.AddCommand(deleteCmd) + ClusterCmd.AddCommand(listCmd) + ClusterCmd.AddCommand(statusCmd) +} diff --git a/cmd/cluster/delete.go b/cmd/cluster/delete.go new file mode 100644 index 0000000..0a11d2f --- /dev/null +++ b/cmd/cluster/delete.go @@ -0,0 +1,12 @@ +package cluster + +import "github.com/spf13/cobra" + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete an Arcus cluster", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: delete 구현 + }, +} diff --git a/cmd/cluster/deploy.go b/cmd/cluster/deploy.go new file mode 100644 index 0000000..b13c721 --- /dev/null +++ b/cmd/cluster/deploy.go @@ -0,0 +1,12 @@ +package cluster + +import "github.com/spf13/cobra" + +var deployCmd = &cobra.Command{ + Use: "deploy ", + Short: "Deploy a new Arcus cluster", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + // TODO: deploy 구현 + }, +} diff --git a/cmd/cluster/list.go b/cmd/cluster/list.go new file mode 100644 index 0000000..e69e035 --- /dev/null +++ b/cmd/cluster/list.go @@ -0,0 +1,12 @@ +package cluster + +import "github.com/spf13/cobra" + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List managed Arcus clusters", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + // TODO: list 구현 + }, +} diff --git a/cmd/cluster/start.go b/cmd/cluster/start.go new file mode 100644 index 0000000..fa3f5e8 --- /dev/null +++ b/cmd/cluster/start.go @@ -0,0 +1,16 @@ +package cluster + +import "github.com/spf13/cobra" + +var startCmd = &cobra.Command{ + Use: "start [--node
]", + Short: "Start an Arcus cluster", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: start 구현 + }, +} + +func init() { + startCmd.Flags().String("node", "", "address of the specific node to start") +} diff --git a/cmd/cluster/status.go b/cmd/cluster/status.go new file mode 100644 index 0000000..61a1cc6 --- /dev/null +++ b/cmd/cluster/status.go @@ -0,0 +1,12 @@ +package cluster + +import "github.com/spf13/cobra" + +var statusCmd = &cobra.Command{ + Use: "status ", + Short: "Show status of an Arcus cluster", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: status 구현 + }, +} diff --git a/cmd/cluster/stop.go b/cmd/cluster/stop.go new file mode 100644 index 0000000..372ac12 --- /dev/null +++ b/cmd/cluster/stop.go @@ -0,0 +1,16 @@ +package cluster + +import "github.com/spf13/cobra" + +var stopCmd = &cobra.Command{ + Use: "stop [--node
]", + Short: "Stop an Arcus cluster", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: stop 구현 + }, +} + +func init() { + stopCmd.Flags().String("node", "", "address of the specific node to stop") +} diff --git a/cmd/connect.go b/cmd/connect.go index 6faee76..f6f78bc 100644 --- a/cmd/connect.go +++ b/cmd/connect.go @@ -38,7 +38,7 @@ credentials are provided, SASL SCRAM-SHA-256 authentication will be performed.`, if err != nil { panic(err) } - defer conn.Close() + defer func() { _ = conn.Close() }() if len(args) == 2 { username, password, _ := strings.Cut(args[1], ":") diff --git a/cmd/root.go b/cmd/root.go index 8945896..fa3fa34 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,8 @@ import ( "runtime/debug" "github.com/jam2in/arcusctl/cmd/acl" + "github.com/jam2in/arcusctl/cmd/cluster" + "github.com/jam2in/arcusctl/cmd/zk" "github.com/jam2in/arcusctl/internal" "github.com/spf13/cobra" ) @@ -35,6 +37,8 @@ func init() { rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(acl.RootCmd) rootCmd.AddCommand(connectCmd) + rootCmd.AddCommand(zk.ZKCmd) + rootCmd.AddCommand(cluster.ClusterCmd) cobra.OnInitialize(internal.InitConfig) } diff --git a/cmd/zk/delete.go b/cmd/zk/delete.go new file mode 100644 index 0000000..9574657 --- /dev/null +++ b/cmd/zk/delete.go @@ -0,0 +1,12 @@ +package zk + +import "github.com/spf13/cobra" + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a ZooKeeper ensemble", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: delete 구현 + }, +} diff --git a/cmd/zk/deploy.go b/cmd/zk/deploy.go new file mode 100644 index 0000000..9d9b520 --- /dev/null +++ b/cmd/zk/deploy.go @@ -0,0 +1,12 @@ +package zk + +import "github.com/spf13/cobra" + +var deployCmd = &cobra.Command{ + Use: "deploy ", + Short: "Deploy a new ZooKeeper ensemble", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + // TODO: deploy 구현 + }, +} diff --git a/cmd/zk/list.go b/cmd/zk/list.go new file mode 100644 index 0000000..c8de6d4 --- /dev/null +++ b/cmd/zk/list.go @@ -0,0 +1,12 @@ +package zk + +import "github.com/spf13/cobra" + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List ZooKeeper ensembles", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + // TODO: list 구현 + }, +} diff --git a/cmd/zk/start.go b/cmd/zk/start.go new file mode 100644 index 0000000..dbd9482 --- /dev/null +++ b/cmd/zk/start.go @@ -0,0 +1,16 @@ +package zk + +import "github.com/spf13/cobra" + +var startCmd = &cobra.Command{ + Use: "start [--node ]", + Short: "Start a ZooKeeper ensemble", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: start 구현 + }, +} + +func init() { + startCmd.Flags().Int("node", 0, "myid of the specific node to start") +} diff --git a/cmd/zk/status.go b/cmd/zk/status.go new file mode 100644 index 0000000..b1567fe --- /dev/null +++ b/cmd/zk/status.go @@ -0,0 +1,12 @@ +package zk + +import "github.com/spf13/cobra" + +var statusCmd = &cobra.Command{ + Use: "status ", + Short: "Show status of a ZooKeeper ensemble", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: status 구현 + }, +} diff --git a/cmd/zk/stop.go b/cmd/zk/stop.go new file mode 100644 index 0000000..5453a86 --- /dev/null +++ b/cmd/zk/stop.go @@ -0,0 +1,16 @@ +package zk + +import "github.com/spf13/cobra" + +var stopCmd = &cobra.Command{ + Use: "stop [--node ]", + Short: "Stop a ZooKeeper ensemble", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: stop 구현 + }, +} + +func init() { + stopCmd.Flags().Int("node", 0, "myid of the specific node to stop") +} diff --git a/cmd/zk/zk.go b/cmd/zk/zk.go new file mode 100644 index 0000000..43cb5ca --- /dev/null +++ b/cmd/zk/zk.go @@ -0,0 +1,17 @@ +package zk + +import "github.com/spf13/cobra" + +var ZKCmd = &cobra.Command{ + Use: "zk", + Short: "Manage ZooKeeper ensembles", +} + +func init() { + ZKCmd.AddCommand(deployCmd) + ZKCmd.AddCommand(startCmd) + ZKCmd.AddCommand(stopCmd) + ZKCmd.AddCommand(deleteCmd) + ZKCmd.AddCommand(listCmd) + ZKCmd.AddCommand(statusCmd) +} diff --git a/go.mod b/go.mod index a0fb8b5..f3c88f7 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,15 @@ module github.com/jam2in/arcusctl -go 1.24.6 +go 1.25.0 require ( github.com/cybergarage/go-sasl v1.2.6 github.com/go-zookeeper/zk v1.0.4 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.21.0 - golang.org/x/term v0.35.0 + go.yaml.in/yaml/v3 v3.0.4 + golang.org/x/crypto v0.50.0 + golang.org/x/term v0.42.0 ) require ( @@ -22,7 +24,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect ) diff --git a/go.sum b/go.sum index 8e2d813..d094604 100644 --- a/go.sum +++ b/go.sum @@ -49,12 +49,14 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config.go b/internal/config.go index 196a7ab..c81d6fd 100644 --- a/internal/config.go +++ b/internal/config.go @@ -16,6 +16,7 @@ var ( Config = struct { ZooKeeper string + Home string }{} ) @@ -34,6 +35,12 @@ func InitConfig() { v.SetConfigName("config") } + homeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + v.SetDefault("home", filepath.Join(homeDir, ".arcusctl")) + v.SetEnvPrefix("ARCUSCTL") v.AutomaticEnv() diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go new file mode 100644 index 0000000..50ca078 --- /dev/null +++ b/internal/ssh/ssh.go @@ -0,0 +1,93 @@ +package ssh + +import ( + "fmt" + "os" + "os/exec" + "os/user" + "path/filepath" + + gossh "golang.org/x/crypto/ssh" +) + +func currentUser() (string, error) { + u, err := user.Current() + if err != nil { + return "", err + } + + return u.Username, nil +} + +func newClient(host string) (*gossh.Client, error) { + username, err := currentUser() + if err != nil { + return nil, err + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, err + } + + keyPath := filepath.Join(homeDir, ".ssh", "id_rsa") + key, err := os.ReadFile(keyPath) + if err != nil { + return nil, err + } + + signer, err := gossh.ParsePrivateKey(key) + if err != nil { + return nil, err + } + + config := &gossh.ClientConfig{ + User: username, + Auth: []gossh.AuthMethod{ + gossh.PublicKeys(signer), + }, + HostKeyCallback: gossh.InsecureIgnoreHostKey(), + } + + return gossh.Dial("tcp", host+":22", config) +} + +func Run(host string, command string) error { + client, err := newClient(host) + if err != nil { + return err + } + defer func() { _ = client.Close() }() + + session, err := client.NewSession() + if err != nil { + return err + } + defer func() { _ = session.Close() }() + + session.Stdout = os.Stdout + session.Stderr = os.Stderr + return session.Run(command) +} + +func Copy(localPath string, host string, remotePath string) error { + username, err := currentUser() + if err != nil { + return err + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return err + } + + dest := fmt.Sprintf("%s@%s:%s", username, host, remotePath) + cmd := exec.Command("scp", + "-i", filepath.Join(homeDir, ".ssh", "id_rsa"), + "-o", "StrictHostKeyChecking=no", + localPath, + dest) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100644 index 0000000..aab5e06 --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,169 @@ +package store + +import ( + "os" + "path/filepath" + "time" + + "github.com/jam2in/arcusctl/internal" + "go.yaml.in/yaml/v3" +) + +const ( + metaYML = "meta.yml" + topologyYML = "topology.yml" +) + +type ZKMeta struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + DeployedAt time.Time `yaml:"deployed_at"` +} + +type ClusterMeta struct { + ServiceCode string `yaml:"servicecode"` + Version string `yaml:"version"` + DeployedAt time.Time `yaml:"deployed_at"` +} + +func SaveZK(ensembleName string, version string, topologyData []byte) error { + dir := zkDir(ensembleName) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + meta := ZKMeta{ + Name: ensembleName, + Version: version, + DeployedAt: time.Now(), + } + metaData, err := yaml.Marshal(meta) + if err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(dir, metaYML), metaData, 0644); err != nil { + return err + } + + return os.WriteFile(filepath.Join(dir, topologyYML), topologyData, 0644) +} + +func LoadZKMeta(ensembleName string) (*ZKMeta, error) { + data, err := os.ReadFile(filepath.Join(zkDir(ensembleName), metaYML)) + if err != nil { + return nil, err + } + + var meta ZKMeta + if err := yaml.Unmarshal(data, &meta); err != nil { + return nil, err + } + + return &meta, nil +} + +func ZKExists(ensembleName string) bool { + _, err := os.Stat(filepath.Join(zkDir(ensembleName), metaYML)) + return err == nil +} + +func ListZK() ([]string, error) { + dir := zkBaseDir() + entries, err := os.ReadDir(dir) + if os.IsNotExist(err) { + return nil, nil + } + + if err != nil { + return nil, err + } + + var names []string + for _, e := range entries { + if e.IsDir() && ZKExists(e.Name()) { + names = append(names, e.Name()) + } + } + + return names, nil +} + +func SaveCluster(serviceCode string, version string, topologyData []byte) error { + dir := clusterDir(serviceCode) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + meta := ClusterMeta{ + ServiceCode: serviceCode, + Version: version, + DeployedAt: time.Now(), + } + metaData, err := yaml.Marshal(meta) + if err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(dir, metaYML), metaData, 0644); err != nil { + return err + } + + return os.WriteFile(filepath.Join(dir, topologyYML), topologyData, 0644) +} + +func LoadClusterMeta(serviceCode string) (*ClusterMeta, error) { + data, err := os.ReadFile(filepath.Join(clusterDir(serviceCode), metaYML)) + if err != nil { + return nil, err + } + + var meta ClusterMeta + if err := yaml.Unmarshal(data, &meta); err != nil { + return nil, err + } + + return &meta, nil +} + +func ClusterExists(serviceCode string) bool { + _, err := os.Stat(filepath.Join(clusterDir(serviceCode), metaYML)) + return err == nil +} + +func ListCluster() ([]string, error) { + dir := clusterBaseDir() + entries, err := os.ReadDir(dir) + if os.IsNotExist(err) { + return nil, nil + } + + if err != nil { + return nil, err + } + + var names []string + for _, e := range entries { + if e.IsDir() && ClusterExists(e.Name()) { + names = append(names, e.Name()) + } + } + + return names, nil +} + +func zkBaseDir() string { + return filepath.Join(internal.Config.Home, "clusters", "zk") +} + +func zkDir(ensembleName string) string { + return filepath.Join(zkBaseDir(), ensembleName) +} + +func clusterBaseDir() string { + return filepath.Join(internal.Config.Home, "clusters", "arcus") +} + +func clusterDir(serviceCode string) string { + return filepath.Join(clusterBaseDir(), serviceCode) +} diff --git a/internal/topology/cluster.go b/internal/topology/cluster.go new file mode 100644 index 0000000..1186829 --- /dev/null +++ b/internal/topology/cluster.go @@ -0,0 +1,47 @@ +package topology + +import "strings" + +type ClusterTopology struct { + ServiceCode string `yaml:"servicecode"` + Path string `yaml:"path"` + ZooKeeper string `yaml:"zookeeper"` + Servers []CacheServer `yaml:"servers"` + GlobalConfig CacheConfig `yaml:"global_config"` +} + +type CacheServer struct { + Address string `yaml:"address"` + Group *GroupInfo `yaml:"group,omitempty"` + Config *CacheConfig `yaml:"config,omitempty"` +} + +type GroupInfo struct { + Name string `yaml:"name"` + Role string `yaml:"role"` +} + +type CacheConfig struct { + Threads int `yaml:"threads,omitempty"` + MaxConnections int `yaml:"max_connections,omitempty"` + Listen string `yaml:"listen,omitempty"` + Engine *EngineConfig `yaml:"engine,omitempty"` +} + +type EngineConfig struct { + MemoryLimit int `yaml:"memory_limit,omitempty"` + Eviction bool `yaml:"eviction,omitempty"` +} + +func (t *ClusterTopology) IsEnterprise() bool { + for _, s := range t.Servers { + if s.Group != nil { + return true + } + } + return false +} + +func (s *CacheServer) Host() string { + return strings.SplitN(s.Address, ":", 2)[0] +} diff --git a/internal/topology/loader.go b/internal/topology/loader.go new file mode 100644 index 0000000..3aa8134 --- /dev/null +++ b/internal/topology/loader.go @@ -0,0 +1,35 @@ +package topology + +import ( + "os" + + "go.yaml.in/yaml/v3" +) + +func LoadZK(path string) (*ZKTopology, []byte, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, nil, err + } + + var topo ZKTopology + if err := yaml.Unmarshal(data, &topo); err != nil { + return nil, nil, err + } + + return &topo, data, nil +} + +func LoadCluster(path string) (*ClusterTopology, []byte, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, nil, err + } + + var topo ClusterTopology + if err := yaml.Unmarshal(data, &topo); err != nil { + return nil, nil, err + } + + return &topo, data, nil +} diff --git a/internal/topology/zk.go b/internal/topology/zk.go new file mode 100644 index 0000000..999d06e --- /dev/null +++ b/internal/topology/zk.go @@ -0,0 +1,35 @@ +package topology + +import "strings" + +type ZKTopology struct { + Name string `yaml:"name"` + Path string `yaml:"path"` + Servers []ZKServer `yaml:"servers"` + GlobalConfig ZKConfig `yaml:"global_config"` +} + +type ZKServer struct { + MyID int `yaml:"myid"` + Address string `yaml:"address"` + Config *ZKConfig `yaml:"config,omitempty"` +} + +type ZKConfig struct { + TickTime int `yaml:"tick_time,omitempty"` + InitLimit int `yaml:"init_limit,omitempty"` + SyncLimit int `yaml:"sync_limit,omitempty"` + DataDir string `yaml:"data_dir,omitempty"` + DataLogDir string `yaml:"data_log_dir,omitempty"` + Properties map[string]string `yaml:"properties,omitempty"` +} + +func (s *ZKServer) ParseAddress() (host, clientPort, quorumPort, electionPort string) { + parts := strings.SplitN(s.Address, ":", 4) + return parts[0], parts[1], parts[2], parts[3] +} + +func (s *ZKServer) Host() string { + host, _, _, _ := s.ParseAddress() + return host +}