diff --git a/client/client.go b/client/client.go new file mode 100644 index 00000000..ad717b97 --- /dev/null +++ b/client/client.go @@ -0,0 +1,103 @@ +package client + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + "syscall" + "time" + + "github.com/kubernetes-incubator/cri-o/types" +) + +const ( + maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) +) + +// CrioClient is an interface to get information from crio daemon endpoint. +type CrioClient interface { + DaemonInfo() (types.CrioInfo, error) + ContainerInfo(string) (*types.ContainerInfo, error) +} + +type crioClientImpl struct { + client *http.Client + crioSocketPath string +} + +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + if len(addr) > maxUnixSocketPathSize { + return fmt.Errorf("Unix socket path %q is too long", addr) + } + // No need for compression in local communications. + tr.DisableCompression = true + tr.Dial = func(_, _ string) (net.Conn, error) { + return net.DialTimeout(proto, addr, 32*time.Second) + } + return nil +} + +// New returns a crio client +func New(crioSocketPath string) (CrioClient, error) { + tr := new(http.Transport) + configureUnixTransport(tr, "unix", crioSocketPath) + c := &http.Client{ + Transport: tr, + } + return &crioClientImpl{ + client: c, + crioSocketPath: crioSocketPath, + }, nil +} + +func (c *crioClientImpl) getRequest(path string) (*http.Request, error) { + req, err := http.NewRequest("GET", path, nil) + if err != nil { + return nil, err + } + // For local communications over a unix socket, it doesn't matter what + // the host is. We just need a valid and meaningful host name. + req.Host = "crio" + req.URL.Host = c.crioSocketPath + req.URL.Scheme = "http" + return req, nil +} + +// DaemonInfo return cri-o daemon info from the cri-o +// info endpoint. +func (c *crioClientImpl) DaemonInfo() (types.CrioInfo, error) { + info := types.CrioInfo{} + req, err := c.getRequest("/info") + if err != nil { + return info, err + } + resp, err := c.client.Do(req) + if err != nil { + return info, err + } + defer resp.Body.Close() + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + return info, err + } + return info, nil +} + +// ContainerInfo returns container info by querying +// the cri-o container endpoint. +func (c *crioClientImpl) ContainerInfo(id string) (*types.ContainerInfo, error) { + req, err := c.getRequest("/containers/" + id) + if err != nil { + return nil, err + } + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + cInfo := types.ContainerInfo{} + if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil { + return nil, err + } + return &cInfo, nil +} diff --git a/cmd/crioctl/container.go b/cmd/crioctl/container.go index e02ce9f8..7be5a7d6 100644 --- a/cmd/crioctl/container.go +++ b/cmd/crioctl/container.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "log" "net/url" @@ -8,6 +9,7 @@ import ( "strings" "time" + "github.com/kubernetes-incubator/cri-o/client" "github.com/urfave/cli" "golang.org/x/net/context" remocommandconsts "k8s.io/apimachinery/pkg/util/remotecommand" @@ -21,6 +23,7 @@ var containerCommand = cli.Command{ Aliases: []string{"ctr"}, Subcommands: []cli.Command{ createContainerCommand, + inspectContainerCommand, startContainerCommand, stopContainerCommand, removeContainerCommand, @@ -617,3 +620,37 @@ func ListContainers(client pb.RuntimeServiceClient, opts listOptions) error { } return nil } + +var inspectContainerCommand = cli.Command{ + Name: "inspect", + Usage: "get container info from crio daemon", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Value: "", + Usage: "id of the container", + }, + }, + Action: func(context *cli.Context) error { + ID := context.String("id") + if ID == "" { + return fmt.Errorf("ID cannot be empty") + } + c, err := client.New(context.GlobalString("connect")) + if err != nil { + return err + } + + cInfo, err := c.ContainerInfo(ID) + if err != nil { + return err + } + + jsonBytes, err := json.MarshalIndent(cInfo, "", " ") + if err != nil { + return err + } + fmt.Println(string(jsonBytes)) + return nil + }, +} diff --git a/cmd/crioctl/info.go b/cmd/crioctl/info.go new file mode 100644 index 00000000..1f06f594 --- /dev/null +++ b/cmd/crioctl/info.go @@ -0,0 +1,31 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/kubernetes-incubator/cri-o/client" + "github.com/urfave/cli" +) + +var infoCommand = cli.Command{ + Name: "info", + Usage: "get crio daemon info", + Action: func(context *cli.Context) error { + c, err := client.New(context.GlobalString("connect")) + if err != nil { + return err + } + di, err := c.DaemonInfo() + if err != nil { + return err + } + + jsonBytes, err := json.MarshalIndent(di, "", " ") + if err != nil { + return err + } + fmt.Println(string(jsonBytes)) + return nil + }, +} diff --git a/cmd/crioctl/main.go b/cmd/crioctl/main.go index 247906a9..3d77867f 100644 --- a/cmd/crioctl/main.go +++ b/cmd/crioctl/main.go @@ -91,6 +91,7 @@ func main() { containerCommand, runtimeVersionCommand, imageCommand, + infoCommand, } app.Flags = []cli.Flag{ diff --git a/server/inspect.go b/server/inspect.go index 8359d0a7..50de0c85 100644 --- a/server/inspect.go +++ b/server/inspect.go @@ -9,32 +9,12 @@ import ( "github.com/go-zoo/bone" "github.com/kubernetes-incubator/cri-o/libkpod/sandbox" "github.com/kubernetes-incubator/cri-o/oci" + "github.com/kubernetes-incubator/cri-o/types" "github.com/sirupsen/logrus" ) -// ContainerInfo stores information about containers -type ContainerInfo struct { - Name string `json:"name"` - Pid int `json:"pid"` - Image string `json:"image"` - CreatedTime int64 `json:"created_time"` - Labels map[string]string `json:"labels"` - Annotations map[string]string `json:"annotations"` - LogPath string `json:"log_path"` - Root string `json:"root"` - Sandbox string `json:"sandbox"` - IP string `json:"ip_address"` -} - -// CrioInfo stores information about the crio daemon -type CrioInfo struct { - StorageDriver string `json:"storage_driver"` - StorageRoot string `json:"storage_root"` - CgroupDriver string `json:"cgroup_driver"` -} - -func (s *Server) getInfo() CrioInfo { - return CrioInfo{ +func (s *Server) getInfo() types.CrioInfo { + return types.CrioInfo{ StorageDriver: s.config.Config.Storage, StorageRoot: s.config.Config.Root, CgroupDriver: s.config.Config.CgroupManager, @@ -47,25 +27,25 @@ var ( errSandboxNotFound = errors.New("sandbox for container not found") ) -func (s *Server) getContainerInfo(id string, getContainerFunc func(id string) *oci.Container, getInfraContainerFunc func(id string) *oci.Container, getSandboxFunc func(id string) *sandbox.Sandbox) (ContainerInfo, error) { +func (s *Server) getContainerInfo(id string, getContainerFunc func(id string) *oci.Container, getInfraContainerFunc func(id string) *oci.Container, getSandboxFunc func(id string) *sandbox.Sandbox) (types.ContainerInfo, error) { ctr := getContainerFunc(id) if ctr == nil { ctr = getInfraContainerFunc(id) if ctr == nil { - return ContainerInfo{}, errCtrNotFound + return types.ContainerInfo{}, errCtrNotFound } } // TODO(mrunalp): should we call UpdateStatus()? ctrState := ctr.State() if ctrState == nil { - return ContainerInfo{}, errCtrStateNil + return types.ContainerInfo{}, errCtrStateNil } sb := getSandboxFunc(ctr.Sandbox()) if sb == nil { logrus.Debugf("can't find sandbox %s for container %s", ctr.Sandbox(), id) - return ContainerInfo{}, errSandboxNotFound + return types.ContainerInfo{}, errSandboxNotFound } - return ContainerInfo{ + return types.ContainerInfo{ Name: ctr.Name(), Pid: ctrState.Pid, Image: ctr.Image(), diff --git a/types/types.go b/types/types.go new file mode 100644 index 00000000..73b9a4e6 --- /dev/null +++ b/types/types.go @@ -0,0 +1,22 @@ +package types + +// ContainerInfo stores information about containers +type ContainerInfo struct { + Name string `json:"name"` + Pid int `json:"pid"` + Image string `json:"image"` + CreatedTime int64 `json:"created_time"` + Labels map[string]string `json:"labels"` + Annotations map[string]string `json:"annotations"` + LogPath string `json:"log_path"` + Root string `json:"root"` + Sandbox string `json:"sandbox"` + IP string `json:"ip_address"` +} + +// CrioInfo stores information about the crio daemon +type CrioInfo struct { + StorageDriver string `json:"storage_driver"` + StorageRoot string `json:"storage_root"` + CgroupDriver string `json:"cgroup_driver"` +}