Skip to content

Commit

Permalink
Implement swctl status command
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Motičák <[email protected]>
  • Loading branch information
pemoticak committed Jul 6, 2023
1 parent 7285bb9 commit 68d1043
Show file tree
Hide file tree
Showing 11 changed files with 947 additions and 45 deletions.
202 changes: 195 additions & 7 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,70 @@
// SPDX-License-Identifier: Apache-2.0

// Copyright 2021 PANTHEON.tech
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"strings"
"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"
linux_nsplugin "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin"

"go.pantheon.tech/stonework/plugins/cnfreg"
)

const (
DefaultHost = "127.0.0.1"
DefaultHTTPClientTimeout = 60 * time.Second
DefaultPortGRPC = 9991
DefaultPortHTTP = 9191
DockerComposeServiceLabel = "com.docker.compose.service"
)

// Option is a function that customizes a Client.
type Option func(*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
return nil
}
}

func WithHTTPTLS(cert, key, ca string, skipVerify bool) Option {
return func(c *Client) (err error) {
c.httpTLS, err = withTLS(cert, key, ca, skipVerify)
return err
}
}

// API defines client API. It is supposed to be used by various client
// applications, such as swctl or other user applications interacting with
Expand All @@ -16,25 +75,154 @@ type API interface {

// Client implements API interface.
type Client struct {
components []Component
nsPlugin *linux_nsplugin.NsPlugin
dockerClient *docker.Client
httpClient *http.Client
host string
scheme string
protocol string
httpPort uint16
httpTLS *tls.Config
customHTTPHeaders map[string]string
}

// NewClient creates a new client that implements API. The client can be
// customized by options.
func NewClient(opts ...Option) (*Client, error) {
c := &Client{}
c := &Client{
host: DefaultHost,
scheme: "http",
protocol: "tcp",
httpPort: DefaultPortHTTP,
}
var err error
c.dockerClient, err = docker.NewClientFromEnv()
if err != nil {
return nil, err
}
for _, o := range opts {
o(c)
}

return c, nil
}

// GetComponents returns list of components.
func (c *Client) DockerClient() *docker.Client {
return c.dockerClient
}

// HTTPClient returns configured HTTP client.
func (c *Client) HTTPClient() *http.Client {
if c.httpClient == nil {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = c.httpTLS
c.httpClient = &http.Client{
Transport: tr,
Timeout: DefaultHTTPClientTimeout,
}
}
return c.httpClient
}

func withTLS(cert, key, ca string, skipVerify bool) (*tls.Config, error) {
var options []tlsconfig.Option
if cert != "" && key != "" {
options = append(options, tlsconfig.CertKey(cert, key))
}
if ca != "" {
options = append(options, tlsconfig.CA(ca))
}
if skipVerify {
options = append(options, tlsconfig.SkipServerVerification())
}
return tlsconfig.New(options...)
}

func (c *Client) StatusInfo(ctx context.Context) ([]cnfreg.Info, error) {
resp, err := c.get(ctx, "/status/info", nil, nil)
if err != nil {
return nil, fmt.Errorf("HTTP request failed: %w", err)
}
var infos []cnfreg.Info
if err := json.NewDecoder(resp.body).Decode(&infos); err != nil {
return nil, fmt.Errorf("decoding reply failed: %w", err)
}
return infos, nil
}

func (c *Client) GetComponents() ([]Component, error) {
var components []Component
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

infos, err := c.StatusInfo(ctx)
if err != nil {
return nil, err
}

dc := c.DockerClient()
containerInfo, err := dc.ListContainers(docker.ListContainersOptions{})
if err != nil {
return nil, err
}

// TODO: implement retrieval of components
var containers []*docker.Container
for _, container := range containerInfo {
c, err := dc.InspectContainerWithOptions(docker.InspectContainerOptions{ID: container.ID})
if err != nil {
return nil, err
}
containers = append(containers, c)
logrus.Warnf("[WLL] container name: %s, PID: %d, ENV: %+v", c.Name, c.State.Pid, c.Config.Env)
}
logrus.Warnf("[WLL] number of containers: &d", len(containers))

return components, fmt.Errorf("NOT IMPLEMENTED YET")
cnfInfos := make(map[string]cnfreg.Info)
for _, info := range infos {
cnfInfos[info.MsLabel] = info
}

var components []Component
var foreignContainers []*docker.Container
for _, container := range containers {
after, found := containsPrefix(container.Config.Env, "MICROSERVICE_LABEL=")
if !found {
foreignContainers = append(foreignContainers, container)
continue
}
info, ok := cnfInfos[after]
if !ok {
foreignContainers = append(foreignContainers, container)
continue
}
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.Mode),
Info: &info,
}
components = append(components, compo)
}

for _, fcontainer := range foreignContainers {
compo := &component{
Name: fcontainer.Config.Labels[DockerComposeServiceLabel],
Mode: ComponentForeign,
}
components = append(components, compo)
}
return components, nil
}

func containsPrefix(strs []string, prefix string) (string, bool) {
for _, str := range strs {
found := strings.HasPrefix(str, prefix)
if found {
return strings.TrimPrefix(str, prefix), found
}
}
return "", false
}
102 changes: 93 additions & 9 deletions client/component.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,107 @@
package client

import "go.pantheon.tech/stonework/proto/cnfreg"
import (
"context"
"fmt"

"go.ligato.io/cn-infra/v2/health/probe"
"go.ligato.io/vpp-agent/v3/cmd/agentctl/api/types"
"go.ligato.io/vpp-agent/v3/cmd/agentctl/client"

"go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler"
"go.pantheon.tech/stonework/plugins/cnfreg"
cnfregpb "go.pantheon.tech/stonework/proto/cnfreg"
)

type ComponentMode int32

const (
// Foreign means the component is not managed by StoneWork
ComponentForeign ComponentMode = iota

// Stonework means the component is a StoneWork instance
ComponentStonework

// StoneworkModule means the component is a StoneWork module managed by StoneWork
ComponentStoneworkModule
)

// Component is a component of StoneWork. It can be StoneWork instance itself,
// a CNF connected to it or other Ligato service in connected to StoneWork.
type Component interface {
Name() string
Mode() cnfreg.CnfMode
// Client() *client.Client
GetName() string
GetMode() ComponentMode
GetInfo() *cnfreg.Info
GetMetadata() map[string]string
SchedulerValues() ([]*kvscheduler.BaseValueStatus, error)
}

type component struct {
name string
mode cnfreg.CnfMode
agentclient *client.Client
Name string
Mode ComponentMode
Info *cnfreg.Info
Metadata map[string]string
}

func (c *component) GetName() string {
return c.Name
}

func (c *component) GetMode() ComponentMode {
return c.Mode
}

func (c *component) Name() string {
return c.name
func (c *component) GetInfo() *cnfreg.Info {
return c.Info
}

func (c *component) Mode() cnfreg.CnfMode {
return c.mode
func (c *component) Client() *client.Client {
return c.agentclient
}

func (c *component) GetMetadata() map[string]string {
return c.Metadata
}

func (c *component) SchedulerValues() ([]*kvscheduler.BaseValueStatus, error) {
if c.Mode == ComponentForeign {
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())
defer cancel()

values, err := c.agentclient.SchedulerValues(ctx, types.SchedulerValuesOptions{})
if err != nil {
return nil, err
}
return values, nil
}

func (c *component) Readiness() (*probe.ExposedStatus, error) {
if c.Mode == ComponentForeign {
return nil, fmt.Errorf("cannot get readiness of component %s, this component in not managed by StoneWork", c.Name)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

status, err := c.agentclient.Status(ctx)
if err != nil {
return nil, err
}
return status, nil
}

func cnfModeToCompoMode(cm cnfregpb.CnfMode) ComponentMode {
switch cm {
case cnfregpb.CnfMode_STANDALONE:
return ComponentForeign
case cnfregpb.CnfMode_STONEWORK_MODULE:
return ComponentStoneworkModule
case cnfregpb.CnfMode_STONEWORK:
return ComponentStonework
default:
return ComponentForeign
}
}
Loading

0 comments on commit 68d1043

Please sign in to comment.