From 656fccc80e227e377aac48778fe6ed5c9a663ce2 Mon Sep 17 00:00:00 2001 From: montaguelhz <1443171175@qq.com> Date: Mon, 18 Dec 2023 23:24:44 +0800 Subject: [PATCH] feature(client status): add address column Signed-off-by: montaguelhz <1443171175@qq.com> --- internal/errno/errno.go | 1 + internal/task/step/container.go | 12 +++ internal/task/task/common/client_status.go | 98 ++++++++++++++++++++-- internal/tui/client/status.go | 6 +- pkg/module/docker_cli.go | 7 ++ 5 files changed, 116 insertions(+), 8 deletions(-) diff --git a/internal/errno/errno.go b/internal/errno/errno.go index 74c5f3b6b..c399f44aa 100644 --- a/internal/errno/errno.go +++ b/internal/errno/errno.go @@ -547,6 +547,7 @@ var ( ERR_INSPECT_CONTAINER_FAILED = EC(630012, "get container low-level information failed") ERR_GET_CONTAINER_LOGS_FAILED = EC(630013, "get container logs failed") ERR_UPDATE_CONTAINER_FAILED = EC(630014, "update container failed") + ERR_TOP_CONTAINER_FAILED = EC(630015, "top container failed") // 690: execuetr task (others) ERR_START_CRONTAB_IN_CONTAINER_FAILED = EC(690000, "start crontab in container failed") diff --git a/internal/task/step/container.go b/internal/task/step/container.go index 61ba9ae18..8635ec81b 100644 --- a/internal/task/step/container.go +++ b/internal/task/step/container.go @@ -155,6 +155,12 @@ type ( Success *bool module.ExecOptions } + + TopContainer struct { + ContainerId string + Out *string + module.ExecOptions + } ) func (s *EngineInfo) Execute(ctx *context.Context) error { @@ -324,3 +330,9 @@ func (s *ContainerLogs) Execute(ctx *context.Context) error { out, err := cli.Execute(s.ExecOptions) return PostHandle(s.Success, s.Out, out, err, errno.ERR_GET_CONTAINER_LOGS_FAILED.FD("(%s logs ID)", s.ExecWithEngine)) } + +func (s *TopContainer) Execute(ctx *context.Context) error { + cli := ctx.Module().DockerCli().TopContainer(s.ContainerId) + out, err := cli.Execute(s.ExecOptions) + return PostHandle(nil, s.Out, out, err, errno.ERR_TOP_CONTAINER_FAILED.FD("(%s top ID)", s.ExecWithEngine)) +} diff --git a/internal/task/task/common/client_status.go b/internal/task/task/common/client_status.go index 845fa3c6e..36917993f 100644 --- a/internal/task/task/common/client_status.go +++ b/internal/task/task/common/client_status.go @@ -24,6 +24,8 @@ package common import ( "fmt" + "regexp" + "strings" "github.com/opencurve/curveadm/cli/cli" comm "github.com/opencurve/curveadm/internal/common" @@ -34,6 +36,7 @@ import ( "github.com/opencurve/curveadm/internal/task/task" tui "github.com/opencurve/curveadm/internal/tui/common" "github.com/opencurve/curveadm/internal/utils" + "github.com/opencurve/curveadm/pkg/module" ) const ( @@ -48,9 +51,17 @@ type ( } step2FormatClientStatus struct { - client storage.Client - status *string - memStorage *utils.SafeMap + client storage.Client + status *string + memStorage *utils.SafeMap + containerId string + address *string + } + + step2GetAddress struct { + containerId string + address *string + execOptions module.ExecOptions } ClientStatus struct { @@ -59,6 +70,7 @@ type ( Kind string ContainerId string Status string + Address string AuxInfo string CfgPath string } @@ -109,6 +121,7 @@ func (s *step2InitClientStatus) Execute(ctx *context.Context) error { Kind: client.Kind, ContainerId: client.ContainerId, Status: comm.CLIENT_STATUS_UNKNOWN, + Address: "", AuxInfo: client.AuxInfo, CfgPath: *s.cfgPath, }) @@ -117,6 +130,7 @@ func (s *step2InitClientStatus) Execute(ctx *context.Context) error { func (s *step2FormatClientStatus) Execute(ctx *context.Context) error { status := *s.status + address := *s.address if len(status) == 0 { // container losed status = comm.CLIENT_STATUS_LOSED } @@ -129,6 +143,7 @@ func (s *step2FormatClientStatus) Execute(ctx *context.Context) error { // update the status s := m[id] s.Status = status + s.Address = address m[id] = s kv.Set(comm.KEY_ALL_CLIENT_STATUS, m) return nil @@ -154,6 +169,69 @@ func NewInitClientStatusTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, return t, nil } +func (s *step2GetAddress) Execute(ctx *context.Context) error { + cmd := ctx.Module().DockerCli().TopContainer(s.containerId) + out, err := cmd.Execute(s.execOptions) + if err != nil { + return nil + } + + lines := strings.Split(out, "\n") + var pid string + if len(lines) > 1 { + reg := regexp.MustCompile(`\s+`) + res := reg.Split(lines[1], -1) + if len(res) > 1 { + pid = res[1] + } + } + + if len(pid) == 0 { + return nil + } + + // execute "ss" command in container + cli := ctx.Module().Shell().SocketStatistics("") + cli.AddOption("--no-header") + cli.AddOption("--processes") + cli.AddOption("--listening") + command, err := cli.String() + if err != nil { + return nil + } + + cmd = ctx.Module().DockerCli().ContainerExec(s.containerId, command) + out, err = cmd.Execute(s.execOptions) + if err != nil { + return nil + } + + // handle output + lines = strings.Split(out, "\n") + for _, line := range lines { + address := s.extractAddress(line, pid) + if len(address) > 0 { + *s.address = address + return nil + } + } + + return nil +} + +// e.g: tcp LISTEN 0 128 10.246.159.123:2379 *:* users:(("etcd",pid=7,fd=5)) +// e.g: tcp LISTEN 0 128 *:2379 *:* users:(("etcd",pid=7,fd=5)) +func (s *step2GetAddress) extractAddress(line, pid string) string { + regex, err := regexp.Compile(`^.* ((\d+\.\d+\.\d+\.\d+)|\*:\d+).*pid=` + pid + ".*$") + if err == nil { + mu := regex.FindStringSubmatch(line) + if len(mu) > 1 { + return mu[1] + } + } + return "" +} + func NewGetClientStatusTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, error) { client := v.(storage.Client) hc, err := curveadm.GetHost(client.Host) @@ -168,6 +246,7 @@ func NewGetClientStatusTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, // add step var status string + var address string t.AddStep(&step.ListContainers{ ShowAll: true, Format: `"{{.Status}}"`, @@ -175,10 +254,17 @@ func NewGetClientStatusTask(curveadm *cli.CurveAdm, v interface{}) (*task.Task, Out: &status, ExecOptions: curveadm.ExecOptions(), }) + t.AddStep(&step2GetAddress{ + containerId: containerId, + address: &address, + execOptions: curveadm.ExecOptions(), + }) t.AddStep(&step2FormatClientStatus{ - client: client, - status: &status, - memStorage: curveadm.MemStorage(), + client: client, + status: &status, + memStorage: curveadm.MemStorage(), + containerId: containerId, + address: &address, }) return t, nil diff --git a/internal/tui/client/status.go b/internal/tui/client/status.go index 8544707e1..f5f2d483b 100644 --- a/internal/tui/client/status.go +++ b/internal/tui/client/status.go @@ -39,7 +39,7 @@ func statusDecorate(status string) string { return status } -func sortStatues(statuses []task.ClientStatus) { +func sortStatuses(statuses []task.ClientStatus) { sort.Slice(statuses, func(i, j int) bool { s1, s2 := statuses[i], statuses[j] if s1.Kind == s2.Kind { @@ -59,6 +59,7 @@ func FormatStatus(statuses []task.ClientStatus, verbose bool) string { "Host", "Container Id", "Status", + "Address", "Aux Info", } if verbose { @@ -69,7 +70,7 @@ func FormatStatus(statuses []task.ClientStatus, verbose bool) string { lines = append(lines, second) // status - sortStatues(statuses) + sortStatuses(statuses) for _, status := range statuses { // line line := []interface{}{ @@ -78,6 +79,7 @@ func FormatStatus(statuses []task.ClientStatus, verbose bool) string { status.Host, tui.TrimContainerId(status.ContainerId), tui.DecorateMessage{Message: status.Status, Decorate: statusDecorate}, + status.Address, status.AuxInfo, } if verbose { diff --git a/pkg/module/docker_cli.go b/pkg/module/docker_cli.go index 43974faf8..0df7e2c91 100644 --- a/pkg/module/docker_cli.go +++ b/pkg/module/docker_cli.go @@ -46,6 +46,7 @@ const ( TEMPLATE_INSPECT_CONTAINER = "{{.engine}} inspect {{.options}} {{.container}}" TEMPLATE_CONTAINER_LOGS = "{{.engine}} logs {{.options}} {{.container}}" TEMPLATE_UPDATE_CONTAINER = "{{.engine}} update {{.options}} {{.container}}" + TEMPLATE_TOP_CONTAINER = "{{.engine}} top {{.container}}" ) type DockerCli struct { @@ -162,3 +163,9 @@ func (cli *DockerCli) ContainerLogs(containerId string) *DockerCli { cli.data["container"] = containerId return cli } + +func (cli *DockerCli) TopContainer(containerId string) *DockerCli { + cli.tmpl = template.Must(template.New("TopContainer").Parse(TEMPLATE_TOP_CONTAINER)) + cli.data["container"] = containerId + return cli +}