ctop/connector/runc.go

246 lines
5.3 KiB
Go
Raw Normal View History

2017-06-14 13:11:40 +00:00
// +build !darwin
2017-06-08 18:33:34 +00:00
package connector
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
2017-06-12 14:12:03 +00:00
"github.com/bcicen/ctop/connector/collector"
2017-11-22 14:26:01 +00:00
"github.com/bcicen/ctop/connector/manager"
2017-06-08 18:33:34 +00:00
"github.com/bcicen/ctop/container"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
)
type RuncOpts struct {
root string // runc root path
systemdCgroups bool // use systemd cgroups
}
2017-06-10 14:44:12 +00:00
func NewRuncOpts() (RuncOpts, error) {
var opts RuncOpts
// read runc root path
root := os.Getenv("RUNC_ROOT")
if root == "" {
root = "/run/runc"
}
abs, err := filepath.Abs(root)
if err != nil {
return opts, err
}
opts.root = abs
// ensure runc root path is readable
_, err = ioutil.ReadDir(opts.root)
if err != nil {
return opts, err
}
if os.Getenv("RUNC_SYSTEMD_CGROUP") == "1" {
opts.systemdCgroups = true
}
return opts, nil
}
2017-06-08 18:33:34 +00:00
type Runc struct {
2017-06-09 17:56:39 +00:00
opts RuncOpts
factory libcontainer.Factory
containers map[string]*container.Container
libContainers map[string]libcontainer.Container
needsRefresh chan string // container IDs requiring refresh
lock sync.RWMutex
2017-06-08 18:33:34 +00:00
}
2017-06-09 17:35:29 +00:00
func NewRunc() Connector {
2017-06-10 14:44:12 +00:00
opts, err := NewRuncOpts()
2017-06-08 18:33:34 +00:00
runcFailOnErr(err)
factory, err := getFactory(opts)
runcFailOnErr(err)
cm := &Runc{
2017-06-09 17:56:39 +00:00
opts: opts,
factory: factory,
containers: make(map[string]*container.Container),
libContainers: make(map[string]libcontainer.Container),
needsRefresh: make(chan string, 60),
lock: sync.RWMutex{},
2017-06-08 18:33:34 +00:00
}
2017-06-09 17:56:39 +00:00
go func() {
for {
cm.refreshAll()
time.Sleep(5 * time.Second)
}
}()
2017-06-08 18:33:34 +00:00
go cm.Loop()
return cm
}
2017-06-09 17:56:39 +00:00
func (cm *Runc) GetLibc(id string) libcontainer.Container {
// return previously loaded container
libc, ok := cm.libContainers[id]
if ok {
return libc
}
// load container
2017-06-08 18:33:34 +00:00
libc, err := cm.factory.Load(id)
if err != nil {
// remove container if no longer exists
if lerr, ok := err.(libcontainer.Error); ok && lerr.Code() == libcontainer.ContainerNotExists {
cm.delByID(id)
} else {
log.Warningf("failed to read container: %s\n", err)
}
return nil
}
return libc
}
2017-06-09 16:07:25 +00:00
// update a ctop container from libcontainer
2017-06-09 17:56:39 +00:00
func (cm *Runc) refresh(id string) {
libc := cm.GetLibc(id)
if libc == nil {
return
}
c := cm.MustGet(id)
2017-06-08 18:33:34 +00:00
// remove container if entered destroyed state on last refresh
// this gives adequate time for the collector to be shut down
if c.GetMeta("state") == "destroyed" {
cm.delByID(id)
return
}
2017-06-08 18:33:34 +00:00
status, err := libc.Status()
if err != nil {
log.Warningf("failed to read status for container: %s\n", err)
} else {
c.SetState(status.String())
}
state, err := libc.State()
if err != nil {
log.Warningf("failed to read state for container: %s\n", err)
} else {
c.SetMeta("created", state.BaseState.Created.Format("Mon Jan 2 15:04:05 2006"))
}
conf := libc.Config()
c.SetMeta("rootfs", conf.Rootfs)
}
2017-06-09 17:56:39 +00:00
// Read runc root, creating any new containers
2017-06-08 18:33:34 +00:00
func (cm *Runc) refreshAll() {
list, err := ioutil.ReadDir(cm.opts.root)
runcFailOnErr(err)
for _, i := range list {
if i.IsDir() {
2017-06-09 16:07:25 +00:00
name := i.Name()
2017-06-08 18:33:34 +00:00
// attempt to load
2017-06-09 17:56:39 +00:00
libc := cm.GetLibc(name)
2017-06-09 16:07:25 +00:00
if libc == nil {
2017-06-08 18:33:34 +00:00
continue
}
2017-06-09 17:56:39 +00:00
_ = cm.MustGet(i.Name()) // ensure container exists
2017-06-08 18:33:34 +00:00
}
}
2017-06-09 17:56:39 +00:00
// queue all existing containers for refresh
for id, _ := range cm.containers {
cm.needsRefresh <- id
}
log.Debugf("queued %d containers for refresh", len(cm.containers))
2017-06-08 18:33:34 +00:00
}
func (cm *Runc) Loop() {
2017-06-09 17:56:39 +00:00
for id := range cm.needsRefresh {
cm.refresh(id)
2017-06-08 18:33:34 +00:00
}
}
2017-06-09 16:07:25 +00:00
// Get a single ctop container in the map matching libc container, creating one anew if not existing
2017-06-09 17:56:39 +00:00
func (cm *Runc) MustGet(id string) *container.Container {
2017-06-08 18:33:34 +00:00
c, ok := cm.Get(id)
if !ok {
2017-06-09 17:56:39 +00:00
libc := cm.GetLibc(id)
2017-06-08 18:33:34 +00:00
// create collector
2017-06-12 14:12:03 +00:00
collector := collector.NewRunc(libc)
2017-06-09 16:07:25 +00:00
2017-06-08 18:33:34 +00:00
// create container
2017-11-22 14:26:01 +00:00
manager := manager.NewRunc()
c = container.New(id, collector, manager)
2017-06-09 16:07:25 +00:00
name := libc.ID()
// set initial metadata
if len(name) > 12 {
name = name[0:12]
}
c.SetMeta("name", name)
// add to map
2017-06-08 18:33:34 +00:00
cm.lock.Lock()
cm.containers[id] = c
2017-06-09 17:56:39 +00:00
cm.libContainers[id] = libc
2017-06-08 18:33:34 +00:00
cm.lock.Unlock()
2017-06-10 12:36:34 +00:00
log.Debugf("saw new container: %s", id)
2017-06-08 18:33:34 +00:00
}
2017-06-09 17:56:39 +00:00
2017-06-08 18:33:34 +00:00
return c
}
// Get a single container, by ID
func (cm *Runc) Get(id string) (*container.Container, bool) {
cm.lock.Lock()
2017-06-09 17:56:39 +00:00
defer cm.lock.Unlock()
2017-06-08 18:33:34 +00:00
c, ok := cm.containers[id]
return c, ok
}
// Remove containers by ID
func (cm *Runc) delByID(id string) {
cm.lock.Lock()
delete(cm.containers, id)
2017-06-09 17:56:39 +00:00
delete(cm.libContainers, id)
2017-06-08 18:33:34 +00:00
cm.lock.Unlock()
log.Infof("removed dead container: %s", id)
}
// Return array of all containers, sorted by field
func (cm *Runc) All() (containers container.Containers) {
cm.lock.Lock()
for _, c := range cm.containers {
containers = append(containers, c)
}
2017-06-12 13:40:52 +00:00
containers.Sort()
2017-06-08 18:33:34 +00:00
containers.Filter()
cm.lock.Unlock()
2017-06-08 18:33:34 +00:00
return containers
}
2017-06-09 17:56:39 +00:00
func getFactory(opts RuncOpts) (libcontainer.Factory, error) {
cgroupManager := libcontainer.Cgroupfs
if opts.systemdCgroups {
if systemd.UseSystemd() {
cgroupManager = libcontainer.SystemdCgroups
} else {
return nil, fmt.Errorf("systemd cgroup enabled, but systemd support for managing cgroups is not available")
}
}
return libcontainer.New(opts.root, cgroupManager)
2017-06-09 17:56:39 +00:00
}
2017-06-08 18:33:34 +00:00
func runcFailOnErr(err error) {
if err != nil {
panic(fmt.Errorf("fatal runc error: %s", err))
}
}