diff --git a/client/client.go b/client/client.go index c2d2496a..62e6d429 100644 --- a/client/client.go +++ b/client/client.go @@ -26,6 +26,7 @@ import ( "time" docker "github.com/fsouza/go-dockerclient" + "github.com/sirupsen/logrus" vppagent "go.ligato.io/vpp-agent/v3/cmd/agentctl/client" "go.ligato.io/vpp-agent/v3/cmd/agentctl/client/tlsconfig" @@ -33,7 +34,7 @@ import ( ) const ( - DefaultHost = "127.0.0.1" + FallbackHost = "127.0.0.1" DefaultHTTPClientTimeout = 60 * time.Second DefaultPortHTTP = 9191 DockerComposeServiceLabel = "com.docker.compose.service" @@ -42,13 +43,6 @@ const ( // Option is a function that customizes a Client. type Option func(*Client) error -func WithHost(h string) Option { - return func(c *Client) error { - c.host = h - return nil - } -} - func WithHTTPPort(p uint16) Option { return func(c *Client) error { c.httpPort = p @@ -86,21 +80,51 @@ type Client struct { // customized by options. func NewClient(opts ...Option) (*Client, error) { c := &Client{ - host: DefaultHost, scheme: "http", protocol: "tcp", httpPort: DefaultPortHTTP, } var err error + c.dockerClient, err = docker.NewClientFromEnv() if err != nil { return nil, err } + + containers, err := c.dockerClient.ListContainers(docker.ListContainersOptions{}) + if err != nil { + return nil, err + } + + // find IP address of the StoneWork service + for _, container := range containers { + if container.Labels[DockerComposeServiceLabel] != "stonework" { + continue + } + cont, err := c.dockerClient.InspectContainerWithOptions(docker.InspectContainerOptions{ID: container.ID}) + if err != nil { + return nil, err + } + for _, nw := range cont.NetworkSettings.Networks { + if nw.IPAddress != "" { + c.host = nw.IPAddress + break + } + } + break + } + for _, o := range opts { if err = o(c); err != nil { return nil, err } } + if c.host == "" { + logrus.Warnf("could not find StoneWork service management IP address falling back to: %s", FallbackHost) + c.host = FallbackHost + } else { + logrus.Debugf("found StoneWork service management IP address: %s", c.host) + } return c, nil } @@ -178,36 +202,49 @@ func (c *Client) GetComponents() ([]Component, error) { } var components []Component - var foreignContainers []*docker.Container for _, container := range containers { + + metadata := make(map[string]string) + metadata["containerID"] = container.ID + metadata["containerName"] = container.Name + metadata["containerServiceName"] = container.Config.Labels[DockerComposeServiceLabel] + metadata["dockerImage"] = container.Config.Image + if container.NetworkSettings.IPAddress != "" { + metadata["containerIPAddress"] = container.NetworkSettings.IPAddress + } else { + for _, nw := range container.NetworkSettings.Networks { + if nw.IPAddress != "" { + metadata["containerIPAddress"] = nw.IPAddress + break + } + } + } + + logrus.Tracef("found metadata for container: %s, data: %+v", container.Name, metadata) + + compo := &component{Metadata: metadata} after, found := containsPrefix(container.Config.Env, "MICROSERVICE_LABEL=") if !found { - foreignContainers = append(foreignContainers, container) + compo.Name = container.Config.Labels[DockerComposeServiceLabel] + compo.Mode = ComponentAuxiliary + components = append(components, compo) continue } info, ok := cnfInfos[after] - if !ok { - foreignContainers = append(foreignContainers, container) - continue + if ok { + compo.Name = info.MsLabel + compo.Info = &info + compo.Mode = cnfModeToCompoMode(info.CnfMode) + } else { + compo.Name = container.Config.Labels[DockerComposeServiceLabel] + compo.Mode = ComponentStandalone } + client, err := vppagent.NewClientWithOpts(vppagent.WithHost(info.IPAddr), vppagent.WithHTTPPort(info.HTTPPort)) if err != nil { return components, err } - compo := &component{ - agentclient: client, - Name: info.MsLabel, - Mode: cnfModeToCompoMode(info.CnfMode), - Info: &info, - } - components = append(components, compo) - } - - for _, fcontainer := range foreignContainers { - compo := &component{ - Name: fcontainer.Config.Labels[DockerComposeServiceLabel], - Mode: ComponentForeign, - } + compo.agentclient = client components = append(components, compo) } return components, nil diff --git a/client/component.go b/client/component.go index 4232e837..16858d8a 100644 --- a/client/component.go +++ b/client/component.go @@ -19,6 +19,7 @@ package client import ( "context" "fmt" + "strings" "go.ligato.io/vpp-agent/v3/cmd/agentctl/api/types" "go.ligato.io/vpp-agent/v3/cmd/agentctl/client" @@ -31,10 +32,15 @@ import ( type ComponentMode int32 const ( - // Foreign means the component is not managed by StoneWork - ComponentForeign ComponentMode = iota + ComponentUnknown ComponentMode = iota - // StoneworkModule means the component is a StoneWork module managed by StoneWork + // Auxiliary means the component is not a CNF and is not managed by StoneWork + ComponentAuxiliary + + // Standalone means the component is a standalone CNF + ComponentStandalone + + // ComponentStonework means the component is a StoneWork module managed by StoneWork ComponentStoneworkModule // Stonework means the component is a StoneWork instance @@ -48,7 +54,7 @@ type Component interface { GetMode() ComponentMode GetInfo() *cnfreg.Info GetMetadata() map[string]string - SchedulerValues() ([]*kvscheduler.BaseValueStatus, error) + ConfigStatus() (*ConfigCounts, error) } type component struct { @@ -79,8 +85,8 @@ func (c *component) GetMetadata() map[string]string { return c.Metadata } -func (c *component) SchedulerValues() ([]*kvscheduler.BaseValueStatus, error) { - if c.Mode == ComponentForeign { +func (c *component) ConfigStatus() (*ConfigCounts, error) { + if c.Mode == ComponentAuxiliary || c.Mode == ComponentUnknown { return nil, fmt.Errorf("cannot get scheduler values of component %s, this component in not managed by StoneWork", c.Name) } ctx, cancel := context.WithCancel(context.Background()) @@ -90,18 +96,94 @@ func (c *component) SchedulerValues() ([]*kvscheduler.BaseValueStatus, error) { if err != nil { return nil, err } - return values, nil + + var allVals []*kvscheduler.ValueStatus + for _, baseVal := range values { + allVals = append(allVals, baseVal.Value) + allVals = append(allVals, baseVal.DerivedValues...) + } + + var res ConfigCounts + for _, val := range allVals { + switch val.State { + case kvscheduler.ValueState_INVALID, kvscheduler.ValueState_FAILED: + res.Err++ + case kvscheduler.ValueState_MISSING: + res.Missing++ + case kvscheduler.ValueState_PENDING: + res.Pending++ + case kvscheduler.ValueState_RETRYING: + res.Retrying++ + case kvscheduler.ValueState_UNIMPLEMENTED: + res.Unimplemented++ + case kvscheduler.ValueState_CONFIGURED, kvscheduler.ValueState_DISCOVERED, kvscheduler.ValueState_OBTAINED, kvscheduler.ValueState_REMOVED, kvscheduler.ValueState_NONEXISTENT: + res.Ok++ + } + } + + return &res, nil +} + +type ConfigCounts struct { + Ok int + Err int + Missing int + Pending int + Retrying int + Unimplemented int +} + +func (cc ConfigCounts) String() string { + var fields []string + if cc.Ok != 0 { + fields = append(fields, fmt.Sprintf("%d OK", cc.Ok)) + } + if cc.Err != 0 { + errStr := fmt.Sprintf("%d errors", cc.Ok) + if cc.Err == 1 { + errStr = errStr[:len(errStr)-1] + } + fields = append(fields, errStr) + } + if cc.Missing != 0 { + fields = append(fields, fmt.Sprintf("%d missing", cc.Missing)) + } + if cc.Pending != 0 { + fields = append(fields, fmt.Sprintf("%d pending", cc.Pending)) + } + if cc.Retrying != 0 { + fields = append(fields, fmt.Sprintf("%d retrying", cc.Retrying)) + } + if cc.Unimplemented != 0 { + fields = append(fields, fmt.Sprintf("%d unimplemented", cc.Unimplemented)) + } + return strings.Join(fields, ", ") +} + +func (c ComponentMode) String() string { + switch c { + case ComponentAuxiliary: + return "auxiliary" + case ComponentStandalone: + return "standalone CNF" + case ComponentStonework: + return "StoneWork" + case ComponentStoneworkModule: + return "StoneWork module" + default: + return "unknown" + } } func cnfModeToCompoMode(cm cnfregpb.CnfMode) ComponentMode { switch cm { case cnfregpb.CnfMode_STANDALONE: - return ComponentForeign + return ComponentStandalone case cnfregpb.CnfMode_STONEWORK_MODULE: return ComponentStoneworkModule case cnfregpb.CnfMode_STONEWORK: return ComponentStonework default: - return ComponentForeign + return ComponentUnknown } } diff --git a/cmd/swctl/cmd_status.go b/cmd/swctl/cmd_status.go index 6fb2f407..bcedc70e 100644 --- a/cmd/swctl/cmd_status.go +++ b/cmd/swctl/cmd_status.go @@ -26,12 +26,11 @@ import ( "github.com/gookit/color" "github.com/olekukonko/tablewriter" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "golang.org/x/exp/slices" - "go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler" - "go.pantheon.tech/stonework/client" ) @@ -73,28 +72,33 @@ func NewStatusCmd(cli Cli) *cobra.Command { type statusInfo struct { client.Component - ConfigCounts configCounts + ConfigCounts *client.ConfigCounts } func runStatusCmd(cli Cli, opts StatusOptions) error { + resp, err := cli.Client().GetComponents() + if err != nil { + return err + } + if opts.ShowInterfaces { - cmd := fmt.Sprintf("vpp-probe --env=%s --query label=%s=stonework discover", defaultVppProbeEnv, client.DockerComposeServiceLabel) - formatArg := fmt.Sprintf("--format=%s", opts.Format) - out, err := cli.Exec(cmd, []string{formatArg}) - if err != nil { - if ee, ok := err.(*exec.ExitError); ok { - return fmt.Errorf("%v: %s", ee.String(), ee.Stderr) + for _, compo := range resp { + if sn, ok := compo.GetMetadata()["containerServiceName"]; ok { + cmd := fmt.Sprintf("vpp-probe --env=%s --query label=%s=%s discover", defaultVppProbeEnv, client.DockerComposeServiceLabel, sn) + formatArg := fmt.Sprintf("--format=%s", opts.Format) + out, err := cli.Exec(cmd, []string{formatArg}) + if err != nil { + if ee, ok := err.(*exec.ExitError); ok { + logrus.Tracef("vpp-probe discover failed for service %s with error: %v: %s", sn, ee.String(), ee.Stderr) + continue + } + } + fmt.Fprintln(cli.Out(), out) } } - fmt.Fprintln(cli.Out(), out) return nil } - resp, err := cli.Client().GetComponents() - if err != nil { - return err - } - type infoWithErr struct { statusInfo error @@ -107,13 +111,12 @@ func runStatusCmd(cli Cli, opts StatusOptions) error { wg.Add(1) go func(compo client.Component) { defer wg.Done() - var counts configCounts - if compo.GetMode() != client.ComponentForeign { - vals, err := compo.SchedulerValues() + var counts *client.ConfigCounts + if compo.GetMode() != client.ComponentAuxiliary { + counts, err = compo.ConfigStatus() if err != nil { infoCh <- infoWithErr{error: err} } - counts = countConfig(vals) } infoCh <- infoWithErr{ statusInfo: statusInfo{ @@ -146,33 +149,6 @@ func runStatusCmd(cli Cli, opts StatusOptions) error { return nil } -func countConfig(baseVals []*kvscheduler.BaseValueStatus) configCounts { - var allVals []*kvscheduler.ValueStatus - for _, baseVal := range baseVals { - allVals = append(allVals, baseVal.Value) - allVals = append(allVals, baseVal.DerivedValues...) - } - - var res configCounts - for _, val := range allVals { - switch val.State { - case kvscheduler.ValueState_INVALID, kvscheduler.ValueState_FAILED: - res.Err++ - case kvscheduler.ValueState_MISSING: - res.Missing++ - case kvscheduler.ValueState_PENDING: - res.Pending++ - case kvscheduler.ValueState_RETRYING: - res.Retrying++ - case kvscheduler.ValueState_UNIMPLEMENTED: - res.Unimplemented++ - case kvscheduler.ValueState_CONFIGURED, kvscheduler.ValueState_DISCOVERED, kvscheduler.ValueState_OBTAINED, kvscheduler.ValueState_REMOVED, kvscheduler.ValueState_NONEXISTENT: - res.Ok++ - } - } - return res -} - func cmpStatus(a, b statusInfo) bool { greater := a.GetMode() > b.GetMode() if !greater && a.GetMode() == b.GetMode() { @@ -200,9 +176,9 @@ func printStatusTable(out io.Writer, infos []statusInfo) { table.SetBorder(false) table.SetTablePadding("\t") for _, info := range infos { - row := []string{info.GetName(), compoModeString(info.GetMode())} + row := []string{info.GetName(), info.GetMode().String()} var clrs []tablewriter.Colors - if info.GetMode() == client.ComponentForeign { + if info.GetMode() == client.ComponentAuxiliary { clrs = []tablewriter.Colors{{}, {}} for i := range header[2:] { clrs = append(clrs, []int{tablewriter.FgHiBlackColor}) @@ -211,8 +187,8 @@ func printStatusTable(out io.Writer, infos []statusInfo) { table.Rich(row, clrs) continue } - config := info.ConfigCounts.string() - configColor := info.ConfigCounts.color() + config := info.ConfigCounts.String() + configColor := configColor(info.ConfigCounts) compoInfo := info.GetInfo() grpcState := compoInfo.GRPCConnState.String() var statusClr int @@ -235,68 +211,20 @@ func printStatusTable(out io.Writer, infos []statusInfo) { table.Render() } -func compoModeString(c client.ComponentMode) string { - switch c { - case client.ComponentForeign: - return "foreign" - case client.ComponentStonework: - return "StoneWork" - case client.ComponentStoneworkModule: - return "StoneWork module" - } - return "unknown" -} - -type configCounts struct { - Ok int - Err int - Missing int - Pending int - Retrying int - Unimplemented int -} - -func (c configCounts) string() string { - var fields []string - if c.Ok != 0 { - fields = append(fields, fmt.Sprintf("%d OK", c.Ok)) - } - if c.Err != 0 { - errStr := fmt.Sprintf("%d errors", c.Ok) - if c.Err == 1 { - errStr = errStr[:len(errStr)-1] - } - fields = append(fields, errStr) - } - if c.Missing != 0 { - fields = append(fields, fmt.Sprintf("%d missing", c.Missing)) - } - if c.Pending != 0 { - fields = append(fields, fmt.Sprintf("%d pending", c.Pending)) - } - if c.Retrying != 0 { - fields = append(fields, fmt.Sprintf("%d retrying", c.Retrying)) - } - if c.Unimplemented != 0 { - fields = append(fields, fmt.Sprintf("%d unimplemented", c.Unimplemented)) - } - return strings.Join(fields, ", ") -} - -func (c configCounts) color() int { - if c.Err > 0 { +func configColor(cc *client.ConfigCounts) int { + if cc.Err > 0 { return tablewriter.FgHiRedColor } - if c.Retrying > 0 || c.Pending > 0 { + if cc.Retrying > 0 || cc.Pending > 0 { return tablewriter.FgYellowColor } - if c.Unimplemented > 0 { + if cc.Unimplemented > 0 { return tablewriter.FgMagentaColor } - if c.Missing > 0 { + if cc.Missing > 0 { return tablewriter.FgHiYellowColor } - if c.Ok > 0 { + if cc.Ok > 0 { return tablewriter.FgGreenColor } return 0