diff --git a/docs/2-cluster-guide.md b/docs/2-cluster-guide.md index 524ebc1..8426860 100644 --- a/docs/2-cluster-guide.md +++ b/docs/2-cluster-guide.md @@ -362,9 +362,9 @@ arcusctl cluster list 현재 운영 장비에 저장된 배포 정보를 기준으로 Arcus 클러스터 목록을 다음과 같이 출력합니다. ```text -SERVICECODE VERSION EDITION NODES DEPLOYED_AT -my-cluster 1.16.1 community 3 2026-08-01 12:30:02 -my-repl-cluster 0.12.1-E enterprise 4 2026-09-01 01:00:13 +SERVICECODE VERSION EDITION NODES GROUPS DEPLOYED_AT +my-cluster 1.16.1 community 3 - 2026-08-01 12:30:02 +my-repl-cluster 0.12.1-E enterprise 4 2 2026-09-01 01:00:13 ``` 다음 정보를 표시합니다. @@ -373,6 +373,7 @@ my-repl-cluster 0.12.1-E enterprise 4 2026-09-01 01:00:13 - Arcus 버전 - Edition - 캐시 서버 수 +- replication group 수 - 배포 시간 `list`는 실행 중인 캐시 서버를 자동으로 탐색하지 않습니다. diff --git a/internal/cluster/list.go b/internal/cluster/list.go index 80a444d..a084972 100644 --- a/internal/cluster/list.go +++ b/internal/cluster/list.go @@ -3,9 +3,11 @@ package cluster import ( "fmt" "os" + "strconv" "text/tabwriter" "github.com/jam2in/arcusctl/internal/store" + "github.com/jam2in/arcusctl/internal/topology" ) func List() error { @@ -20,26 +22,30 @@ func List() error { } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "SERVICECODE\tVERSION\tEDITION\tNODES\tDEPLOYED_AT") + fmt.Fprintln(w, "SERVICECODE\tVERSION\tEDITION\tNODES\tGROUPS\tDEPLOYED_AT") for _, serviceCode := range serviceCodes { meta, err := store.LoadClusterMeta(serviceCode) if err != nil { - fmt.Fprintf(w, "%s\t\t\t\t%v\n", serviceCode, err) + fmt.Fprintf(w, "%s\t\t\t\t\t%v\n", serviceCode, err) continue } edition := "" servers := 0 + groups := "-" if topo, err := store.LoadClusterTopology(serviceCode); err == nil { servers = len(topo.Servers) if e, err := topo.Edition(); err == nil { edition = string(e) + if e == topology.EnterpriseEdition { + groups = strconv.Itoa(topo.GroupCount()) + } } } - fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\n", - serviceCode, meta.Version, edition, servers, + fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%s\n", + serviceCode, meta.Version, edition, servers, groups, meta.DeployedAt.Format("2006-01-02 15:04:05"), ) } diff --git a/internal/topology/cluster.go b/internal/topology/cluster.go index 54b27e0..6306161 100644 --- a/internal/topology/cluster.go +++ b/internal/topology/cluster.go @@ -152,3 +152,14 @@ func (s *CacheServer) IsMaster() bool { func (s *CacheServer) Host() string { return strings.SplitN(s.Address, ":", 2)[0] } + +func (topo *ClusterTopology) GroupCount() int { + names := make(map[string]struct{}, len(topo.Servers)) + for _, server := range topo.Servers { + if server.Group != nil { + names[server.Group.Name] = struct{}{} + } + } + + return len(names) +} diff --git a/internal/topology/cluster_test.go b/internal/topology/cluster_test.go new file mode 100644 index 0000000..ab2feeb --- /dev/null +++ b/internal/topology/cluster_test.go @@ -0,0 +1,57 @@ +package topology + +import "testing" + +func TestClusterTopology_GroupCount(t *testing.T) { + tests := []struct { + name string + servers []CacheServer + want int + }{ + { + name: "no groups", + servers: []CacheServer{ + {Address: "10.0.0.1:11211"}, + {Address: "10.0.0.2:11211"}, + }, + want: 0, + }, + { + name: "duplicate group names are counted once", + servers: []CacheServer{ + {Address: "10.0.0.1:11211", Group: &GroupInfo{Name: "g1", Role: "master", Port: 22322}}, + {Address: "10.0.0.2:11211", Group: &GroupInfo{Name: "g1", Role: "slave", Port: 22322}}, + }, + want: 1, + }, + { + name: "multiple groups", + servers: []CacheServer{ + {Address: "10.0.0.1:11211", Group: &GroupInfo{Name: "g1", Role: "master"}}, + {Address: "10.0.0.2:11211", Group: &GroupInfo{Name: "g1", Role: "slave"}}, + {Address: "10.0.0.3:11211", Group: &GroupInfo{Name: "g2", Role: "master"}}, + {Address: "10.0.0.4:11211", Group: &GroupInfo{Name: "g2", Role: "slave"}}, + }, + want: 2, + }, + { + name: "multiple groups only masters", + servers: []CacheServer{ + {Address: "10.0.0.1:11211", Group: &GroupInfo{Name: "g1", Role: "master"}}, + {Address: "10.0.0.2:11211", Group: &GroupInfo{Name: "g2", Role: "master"}}, + {Address: "10.0.0.3:11211", Group: &GroupInfo{Name: "g3", Role: "master"}}, + }, + want: 3, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + topo := &ClusterTopology{Servers: test.servers} + + if got := topo.GroupCount(); got != test.want { + t.Errorf("GroupCount() = %d, want %d", got, test.want) + } + }) + } +}