Skip to content

Commit

Permalink
*: add CRI-O handler
Browse files Browse the repository at this point in the history
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
  • Loading branch information
runcom committed Sep 5, 2017
1 parent 1df21ea commit 4b002b3
Show file tree
Hide file tree
Showing 9 changed files with 892 additions and 0 deletions.
1 change: 1 addition & 0 deletions container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
ContainerTypeDocker
ContainerTypeRkt
ContainerTypeSystemd
ContainerTypeCrio
)

// Interface for container operation handlers.
Expand Down
130 changes: 130 additions & 0 deletions container/crio/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 crio

import (
"encoding/json"
"fmt"
"net"
"net/http"
"syscall"
"time"
)

const (
CrioSocket = "/var/run/crio.sock"
maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
)

// Info represents CRI-O information as sent by the CRI-O server
type Info struct {
StorageDriver string `json:"storage_driver"`
StorageRoot string `json:"storage_root"`
}

// ContainerInfo represents a given container information
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"`
IP string `json:"ip_address"`
}

type crioClient interface {
Info() (Info, error)
ContainerInfo(string) (*ContainerInfo, error)
}

type crioClientImpl struct {
client *http.Client
}

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
}

// Client returns a new configured CRI-O client
func Client() (crioClient, error) {
tr := new(http.Transport)
configureUnixTransport(tr, "unix", CrioSocket)
c := &http.Client{
Transport: tr,
}
return &crioClientImpl{
client: c,
}, nil
}

func 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 = CrioSocket
req.URL.Scheme = "http"
return req, nil
}

// Info returns generic info from the CRI-O server
func (c *crioClientImpl) Info() (Info, error) {
info := Info{}
req, err := 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 information about a given container
func (c *crioClientImpl) ContainerInfo(id string) (*ContainerInfo, error) {
req, err := 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 := ContainerInfo{}
if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil {
return nil, err
}
return &cInfo, nil
}
49 changes: 49 additions & 0 deletions container/crio/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 crio

import "fmt"

type crioClientMock struct {
info Info
containersInfo map[string]*ContainerInfo
err error
}

func (c *crioClientMock) Info() (Info, error) {
if c.err != nil {
return Info{}, c.err
}
return c.info, nil
}

func (c *crioClientMock) ContainerInfo(id string) (*ContainerInfo, error) {
if c.err != nil {
return nil, c.err
}
cInfo, ok := c.containersInfo[id]
if !ok {
return nil, fmt.Errorf("no container with id %s", id)
}
return cInfo, nil
}

func mockCrioClient(info Info, containersInfo map[string]*ContainerInfo, err error) crioClient {
return &crioClientMock{
err: err,
info: info,
containersInfo: containersInfo,
}
}
170 changes: 170 additions & 0 deletions container/crio/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 crio

import (
"fmt"
"path"
"regexp"
"strings"

"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/manager/watcher"

"github.com/golang/glog"
)

// The namespace under which crio aliases are unique.
const CrioNamespace = "crio"

// Regexp that identifies CRI-O cgroups
var crioCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`)

type storageDriver string

const (
// TODO add full set of supported drivers in future..
overlayStorageDriver storageDriver = "overlay"
overlay2StorageDriver storageDriver = "overlay2"
)

type crioFactory struct {
machineInfoFactory info.MachineInfoFactory

storageDriver storageDriver
storageDir string

// Information about the mounted cgroup subsystems.
cgroupSubsystems libcontainer.CgroupSubsystems

// Information about mounted filesystems.
fsInfo fs.FsInfo

ignoreMetrics container.MetricSet

client crioClient
}

func (self *crioFactory) String() string {
return CrioNamespace
}

func (self *crioFactory) NewContainerHandler(name string, inHostNamespace bool) (handler container.ContainerHandler, err error) {
client, err := Client()
if err != nil {
return
}
// TODO are there any env vars we need to white list, if so, do it here...
metadataEnvs := []string{}
handler, err = newCrioContainerHandler(
client,
name,
self.machineInfoFactory,
self.fsInfo,
self.storageDriver,
self.storageDir,
&self.cgroupSubsystems,
inHostNamespace,
metadataEnvs,
self.ignoreMetrics,
)
return
}

// Returns the CRIO ID from the full container name.
func ContainerNameToCrioId(name string) string {
id := path.Base(name)

if matches := crioCgroupRegexp.FindStringSubmatch(id); matches != nil {
return matches[1]
}

return id
}

// isContainerName returns true if the cgroup with associated name
// corresponds to a crio container.
func isContainerName(name string) bool {
// always ignore .mount cgroup even if associated with crio and delegate to systemd
if strings.HasSuffix(name, ".mount") {
return false
}
return crioCgroupRegexp.MatchString(path.Base(name))
}

// crio handles all containers under /crio
func (self *crioFactory) CanHandleAndAccept(name string) (bool, bool, error) {
if strings.HasPrefix(path.Base(name), "crio-conmon") {
// TODO(runcom): should we include crio-conmon cgroups?
return false, false, nil
}
if !strings.HasPrefix(path.Base(name), CrioNamespace) {
return false, false, nil
}
// if the container is not associated with CRI-O, we can't handle it or accept it.
if !isContainerName(name) {
return false, false, nil
}
return true, true, nil
}

func (self *crioFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}

var (
// TODO(runcom): handle versioning in CRI-O
version_regexp_string = `(\d+)\.(\d+)\.(\d+)`
version_re = regexp.MustCompile(version_regexp_string)
apiversion_regexp_string = `(\d+)\.(\d+)`
apiversion_re = regexp.MustCompile(apiversion_regexp_string)
)

// Register root container before running this function!
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error {
client, err := Client()
if err != nil {
return err
}

info, err := client.Info()
if err != nil {
return err
}

// TODO determine crio version so we can work differently w/ future versions if needed

cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}

glog.Infof("Registering CRI-O factory")
f := &crioFactory{
client: client,
cgroupSubsystems: cgroupSubsystems,
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(info.StorageDriver),
storageDir: info.StorageRoot,
ignoreMetrics: ignoreMetrics,
}

container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}
Loading

0 comments on commit 4b002b3

Please sign in to comment.