Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions cmd/memcached/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package memcached

import (
"fmt"
"os"
"path"
"sort"
"strings"

"github.com/go-zookeeper/zk"
"github.com/jam2in/arcus-cli/internal"
"github.com/spf13/cobra"
)

type serviceStatus struct {
serviceCode string
total int
online int
offline int
}

var listCmd = &cobra.Command{
Use: "list [serviceCode]",
Short: "list all servers in arcus cache cloud",
Run: func(cmd *cobra.Command, args []string) {

zkConn := cmd.Context().Value(internal.CtxZkConnKey{}).(*zk.Conn)

serviceCodeMap, err := buildServiceCodeMap(zkConn)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

var serviceCodes []string
if len(args) == 0 {
for sm := range serviceCodeMap {
serviceCodes = append(serviceCodes, sm)
}
sort.Strings(serviceCodes)
} else {
serviceCodes = []string{args[0]}
}

liveServerMaps := make(map[string]map[string]struct{})
var statuses []serviceStatus
for _, sc := range serviceCodes {
liveServers, _ := getLiveServers(zkConn, sc)
liveServerMaps[sc] = liveServers

onlineCnt := 0
for _, s := range serviceCodeMap[sc] {
if _, ok := liveServers[s]; ok {
onlineCnt++
}
}

statuses = append(statuses, serviceStatus{
serviceCode: sc,
total: len(serviceCodeMap[sc]),
online: onlineCnt,
offline: len(serviceCodeMap[sc]) - onlineCnt,
})
}

if len(args) == 1 {
serviceCode := args[0]
servers := serviceCodeMap[serviceCode]
liveServers := liveServerMaps[serviceCode]

fmt.Printf("Servers in service code '%s':\n", serviceCode)
for _, server := range servers {
status := "offline"
if _, isLive := liveServers[server]; isLive {
status = "online"
}
fmt.Printf(" - %-21s %s\n", server, status)
}
fmt.Println()
}

fmt.Printf("%-25s %-8s %-8s %-8s\n", "SERVICE CODE", "TOTAL", "ONLINE", "OFFLINE")
fmt.Println(strings.Repeat("-", 60))
for _, s := range statuses {
fmt.Printf("%-25s %-8d %-8d %-8d\n", s.serviceCode, s.total, s.online, s.offline)
}

},
}

func buildServiceCodeMap(zkConn *zk.Conn) (map[string][]string, error) {
serviceCodeMap := make(map[string][]string)
allServers, _, err := zkConn.Children(path.Join(internal.ArcusCacheServerMappingPath))
if err != nil {
return nil, err
}

for _, s := range allServers {
serviceCodeTags, _, err := zkConn.Children(path.Join(internal.ArcusCacheServerMappingPath, s))
if err != nil {
continue
}
for _, sc := range serviceCodeTags {
serviceCodeMap[sc] = append(serviceCodeMap[sc], s)
}
}

return serviceCodeMap, nil
}
61 changes: 61 additions & 0 deletions cmd/memcached/memcached.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package memcached

import (
"path"
"strings"

"github.com/go-zookeeper/zk"
"github.com/jam2in/arcus-cli/internal"
"github.com/spf13/cobra"
)

const (
memcachedStartCommandTemplate = "%s/bin/memcached -E %s/lib/default_engine.so -X %s/lib/syslog_logger.so -X %s/lib/ascii_scrub.so -P %s/memcached-%s.pid -d -v -r -R5 -U 0 -D: -b 8192 %s -z %s"
)

var MemcachedCmd = &cobra.Command{
Use: "memcached",
Short: "Memcached command",
Expand Down Expand Up @@ -33,4 +40,58 @@ func init() {
MemcachedCmd.AddCommand(removeCmd)
MemcachedCmd.AddCommand(configCmd)
MemcachedCmd.AddCommand(connectCmd)
MemcachedCmd.AddCommand(listCmd)
MemcachedCmd.AddCommand(startCmd)
MemcachedCmd.AddCommand(stopCmd)
}

func getServiceCodeServers(zkConn *zk.Conn, serviceCode string) ([]string, error) {
servers, _, err := zkConn.Children(internal.ArcusCacheServerMappingPath)
if err != nil {
return nil, err
}

var result []string
for _, server := range servers {
children, _, err := zkConn.Children(path.Join(internal.ArcusCacheServerMappingPath, server))
if err != nil {
continue
}
for _, child := range children {
if child == serviceCode {
result = append(result, server)
break
}
}
}
return result, nil
}

func getLiveServers(zkConn *zk.Conn, serviceCode string) (map[string]struct{}, error) {
liveNodes, _, err := zkConn.Children(path.Join(internal.ArcusCacheListPath, serviceCode))
if err != nil {
return nil, err
}

liveServers := make(map[string]struct{})
for _, liveNode := range liveNodes {
addr, _, _ := strings.Cut(liveNode, "-")
liveServers[addr] = struct{}{}
}
return liveServers, nil
}

func filterServers(allServers []string, targets []string) []string {
targetSet := make(map[string]struct{})
for _, t := range targets {
targetSet[t] = struct{}{}
}

result := make([]string, 0)
for _, s := range allServers {
if _, ok := targetSet[s]; ok {
result = append(result, s)
}
}
return result
}
92 changes: 92 additions & 0 deletions cmd/memcached/start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package memcached

import (
"context"
"fmt"
"os"
"path"
"strings"
"time"

"github.com/go-zookeeper/zk"
"github.com/jam2in/arcus-cli/internal"
"github.com/spf13/cobra"
)

var startCmd = &cobra.Command{
Use: "start <serviceCode> [ip:port...]",
Short: "start all servers or specific servers in service code",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
serviceCode := args[0]
targetServers := args[1:]
zkConn := cmd.Context().Value(internal.CtxZkConnKey{}).(*zk.Conn)

globalConfig, _, err := zkConn.Get(path.Join(internal.ArcusCacheListPath, serviceCode))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

serviceCodeServers, err := getServiceCodeServers(zkConn, serviceCode)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

var serversToStart []string
if len(targetServers) > 0 {
serversToStart = filterServers(serviceCodeServers, targetServers)
if len(serversToStart) == 0 {
fmt.Fprintln(os.Stderr, "No servers found in service code")
os.Exit(1)
}
} else {
serversToStart = serviceCodeServers
}

for _, serverAddress := range serversToStart {
ip, port, flag := strings.Cut(serverAddress, ":")
if !flag {
fmt.Fprintln(os.Stderr, "Invalid server address:", serverAddress)
os.Exit(1)
}

client, err := internal.NewSSHClient(ip)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer client.Close()

session, err := client.NewSession()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer session.Close()

memcachedPath := os.Getenv("ARCUS_PATH")
command := fmt.Sprintf(memcachedStartCommandTemplate,
memcachedPath, memcachedPath, memcachedPath, memcachedPath, memcachedPath,
port, string(globalConfig), os.Getenv("ZK_ADDR"))

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
errChan := make(chan error, 1)
go func() {
errChan <- session.Run(command)
}()
select {
case err := <-errChan:
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
case <-ctx.Done():
continue
}
Comment on lines +70 to +88
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nohup 대신 timeout 사용하기로 하였나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 방안 모두 무방하다고 하셔서, 기존 레거시코드 구현을 그대로 유지하고자 timeout으로 구현했습니다.

fmt.Printf(" - Start command sent to %s successfully.\n", serverAddress)
}
},
}
68 changes: 68 additions & 0 deletions cmd/memcached/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package memcached

import (
"fmt"
"os"
"strings"

"github.com/go-zookeeper/zk"
"github.com/jam2in/arcus-cli/internal"
"github.com/spf13/cobra"
)

var stopCmd = &cobra.Command{
Use: "stop <serviceCode> [ip:port...]",
Short: "stop all servers or specific servers in service code",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
serviceCode := args[0]
targetServers := args[1:]
zkConn := cmd.Context().Value(internal.CtxZkConnKey{}).(*zk.Conn)

serviceCodeServers, err := getServiceCodeServers(zkConn, serviceCode)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var serversToStop []string
if len(targetServers) > 0 {
serversToStop = filterServers(serviceCodeServers, targetServers)
if len(serversToStop) == 0 {
fmt.Fprintln(os.Stderr, "No servers found in service code")
os.Exit(1)
}
} else {
serversToStop = serviceCodeServers
}

for _, serverAddress := range serversToStop {
ip, port, flag := strings.Cut(serverAddress, ":")
if !flag {
fmt.Fprintln(os.Stderr, "Invalid server address:", serverAddress)
os.Exit(1)
}

client, err := internal.NewSSHClient(ip)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer client.Close()

session, err := client.NewSession()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer session.Close()

pidFilePath := fmt.Sprintf("%s/memcached-%s.pid", os.Getenv("ARCUS_PATH"), port)
command := fmt.Sprintf("kill -INT $(cat %s)", pidFilePath)
if err := session.Run(command); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf(" - Stop command sent to %s successfully.\n", serverAddress)
}
},
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ require (
github.com/cybergarage/go-sasl v1.2.6
github.com/go-zookeeper/zk v1.0.4
github.com/spf13/cobra v1.9.1
golang.org/x/term v0.34.0
golang.org/x/term v0.35.0
)

require (
github.com/cybergarage/go-safecast v1.3.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/sys v0.36.0 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading
Loading