Update deps after move to containerd org

This updates containerd to use the latest versions of cgroups, fifo,
console, and go-runc from the containerd org.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby
2017-05-10 16:35:32 -07:00
parent 79e389e549
commit 23b2b09d13
76 changed files with 244 additions and 54 deletions

24
vendor/github.com/containerd/cgroups/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2016 Michael Crosby. crosbymichael@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

135
vendor/github.com/containerd/cgroups/README.md generated vendored Normal file
View File

@@ -0,0 +1,135 @@
# cgroups
[![Build Status](https://travis-ci.org/containerd/cgroups.svg?branch=master)](https://travis-ci.org/containerd/cgroups)
[![codecov](https://codecov.io/gh/containerd/cgroups/branch/master/graph/badge.svg)](https://codecov.io/gh/containerd/cgroups)
Go package for creating, managing, inspecting, and destroying cgroups.
The resources format for settings on the cgroup uses the OCI runtime-spec found
[here](https://github.com/opencontainers/runtime-spec).
## Examples
### Create a new cgroup
This creates a new cgroup using a static path for all subsystems under `/test`.
* /sys/fs/cgroup/cpu/test
* /sys/fs/cgroup/memory/test
* etc....
It uses a single hierarchy and specifies cpu shares as a resource constraint and
uses the v1 implementation of cgroups.
```go
shares := uint64(100)
control, err := cgroups.New(cgroups.V1, cgroups.StaticPath("/test"), &specs.LinuxResources{
CPU: &specs.CPU{
Shares: &shares,
},
})
defer control.Delete()
```
### Create with systemd slice support
```go
control, err := cgroups.New(cgroups.Systemd, cgroups.Slice("system.slice", "runc-test"), &specs.LinuxResources{
CPU: &specs.CPU{
Shares: &shares,
},
})
```
### Load an existing cgroup
```go
control, err = cgroups.Load(cgroups.V1, cgroups.StaticPath("/test"))
```
### Add a process to the cgroup
```go
if err := control.Add(cgroups.Process{Pid:1234}); err != nil {
}
```
### Update the cgroup
To update the resources applied in the cgroup
```go
shares = uint64(200)
if err := control.Update(&specs.LinuxResources{
CPU: &specs.CPU{
Shares: &shares,
},
}); err != nil {
}
```
### Freeze and Thaw the cgroup
```go
if err := control.Freeze(); err != nil {
}
if err := control.Thaw(); err != nil {
}
```
### List all processes in the cgroup or recursively
```go
processes, err := control.Processes(cgroups.Devices, recursive)
```
### Get Stats on the cgroup
```go
stats, err := control.Stat()
```
### Move process across cgroups
This allows you to take processes from one cgroup and move them to another.
```go
err := control.MoveTo(destination)
```
### Create subcgroup
```go
subCgroup, err := control.New("child", resources)
```
## LICENSE
Copyright (c) 2016-2017 Michael Crosby. crosbymichael@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

323
vendor/github.com/containerd/cgroups/blkio.go generated vendored Normal file
View File

@@ -0,0 +1,323 @@
package cgroups
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewBlkio(root string) *blkioController {
return &blkioController{
root: filepath.Join(root, string(Blkio)),
}
}
type blkioController struct {
root string
}
func (b *blkioController) Name() Name {
return Blkio
}
func (b *blkioController) Path(path string) string {
return filepath.Join(b.root, path)
}
func (b *blkioController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(b.Path(path), defaultDirPerm); err != nil {
return err
}
if resources.BlockIO == nil {
return nil
}
for _, t := range createBlkioSettings(resources.BlockIO) {
if t.value != nil {
if err := ioutil.WriteFile(
filepath.Join(b.Path(path), fmt.Sprintf("blkio.%s", t.name)),
t.format(t.value),
defaultFilePerm,
); err != nil {
return err
}
}
}
return nil
}
func (b *blkioController) Update(path string, resources *specs.LinuxResources) error {
return b.Create(path, resources)
}
func (b *blkioController) Stat(path string, stats *Stats) error {
stats.Blkio = &BlkioStat{}
settings := []blkioStatSettings{
{
name: "throttle.io_serviced",
entry: &stats.Blkio.IoServicedRecursive,
},
{
name: "throttle.io_service_bytes",
entry: &stats.Blkio.IoServiceBytesRecursive,
},
}
// Try to read CFQ stats available on all CFQ enabled kernels first
if _, err := os.Lstat(filepath.Join(b.Path(path), fmt.Sprintf("blkio.io_serviced_recursive"))); err == nil {
settings = append(settings,
blkioStatSettings{
name: "sectors_recursive",
entry: &stats.Blkio.SectorsRecursive,
},
blkioStatSettings{
name: "io_service_bytes_recursive",
entry: &stats.Blkio.IoServiceBytesRecursive,
},
blkioStatSettings{
name: "io_serviced_recursive",
entry: &stats.Blkio.IoServicedRecursive,
},
blkioStatSettings{
name: "io_queued_recursive",
entry: &stats.Blkio.IoQueuedRecursive,
},
blkioStatSettings{
name: "io_service_time_recursive",
entry: &stats.Blkio.IoServiceTimeRecursive,
},
blkioStatSettings{
name: "io_wait_time_recursive",
entry: &stats.Blkio.IoWaitTimeRecursive,
},
blkioStatSettings{
name: "io_merged_recursive",
entry: &stats.Blkio.IoMergedRecursive,
},
blkioStatSettings{
name: "time_recursive",
entry: &stats.Blkio.IoTimeRecursive,
},
)
}
devices, err := getDevices("/dev")
if err != nil {
return err
}
for _, t := range settings {
if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
return err
}
}
return nil
}
func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]BlkioEntry) error {
f, err := os.Open(filepath.Join(b.Path(path), fmt.Sprintf("blkio.%s", name)))
if err != nil {
return err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
if err := sc.Err(); err != nil {
return err
}
// format: dev type amount
fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
if len(fields) < 3 {
if len(fields) == 2 && fields[0] == "Total" {
// skip total line
continue
} else {
return fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
}
}
major, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return err
}
minor, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
return err
}
op := ""
valueField := 2
if len(fields) == 4 {
op = fields[2]
valueField = 3
}
v, err := strconv.ParseUint(fields[valueField], 10, 64)
if err != nil {
return err
}
*entry = append(*entry, BlkioEntry{
Device: devices[deviceKey{major, minor}],
Major: major,
Minor: minor,
Op: op,
Value: v,
})
}
return nil
}
func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings {
settings := []blkioSettings{
{
name: "weight",
value: blkio.Weight,
format: uintf,
},
{
name: "leaf_weight",
value: blkio.LeafWeight,
format: uintf,
},
}
for _, wd := range blkio.WeightDevice {
settings = append(settings,
blkioSettings{
name: "weight_device",
value: wd,
format: weightdev,
},
blkioSettings{
name: "leaf_weight_device",
value: wd,
format: weightleafdev,
})
}
for _, t := range []struct {
name string
list []specs.LinuxThrottleDevice
}{
{
name: "throttle.read_bps_device",
list: blkio.ThrottleReadBpsDevice,
},
{
name: "throttle.read_iops_device",
list: blkio.ThrottleReadIOPSDevice,
},
{
name: "throttle.write_bps_device",
list: blkio.ThrottleWriteBpsDevice,
},
{
name: "throttle.write_iops_device",
list: blkio.ThrottleWriteIOPSDevice,
},
} {
for _, td := range t.list {
settings = append(settings, blkioSettings{
name: t.name,
value: td,
format: throttleddev,
})
}
}
return settings
}
type blkioSettings struct {
name string
value interface{}
format func(v interface{}) []byte
}
type blkioStatSettings struct {
name string
entry *[]BlkioEntry
}
func uintf(v interface{}) []byte {
return []byte(strconv.FormatUint(uint64(*v.(*uint16)), 10))
}
func weightdev(v interface{}) []byte {
wd := v.(specs.LinuxWeightDevice)
return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight))
}
func weightleafdev(v interface{}) []byte {
wd := v.(specs.LinuxWeightDevice)
return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight))
}
func throttleddev(v interface{}) []byte {
td := v.(specs.LinuxThrottleDevice)
return []byte(fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate))
}
func splitBlkioStatLine(r rune) bool {
return r == ' ' || r == ':'
}
type deviceKey struct {
major, minor uint64
}
// getDevices makes a best effort attempt to read all the devices into a map
// keyed by major and minor number. Since devices may be mapped multiple times,
// we err on taking the first occurrence.
func getDevices(path string) (map[deviceKey]string, error) {
// TODO(stevvooe): We are ignoring lots of errors. It might be kind of
// challenging to debug this if we aren't mapping devices correctly.
// Consider logging these errors.
devices := map[deviceKey]string{}
if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
switch {
case fi.IsDir():
switch fi.Name() {
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":
return filepath.SkipDir
default:
return nil
}
case fi.Name() == "console":
return nil
default:
if fi.Mode()&os.ModeDevice == 0 {
// skip non-devices
return nil
}
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("%s: unable to convert to system stat", p)
}
key := deviceKey{major(st.Rdev), minor(st.Rdev)}
if _, ok := devices[key]; ok {
return nil // skip it if we have already populated the path.
}
devices[key] = p
}
return nil
}); err != nil {
return nil, err
}
return devices, nil
}
func major(devNumber uint64) uint64 {
return (devNumber >> 8) & 0xfff
}
func minor(devNumber uint64) uint64 {
return (devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)
}

386
vendor/github.com/containerd/cgroups/cgroup.go generated vendored Normal file
View File

@@ -0,0 +1,386 @@
package cgroups
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// New returns a new control via the cgroup cgroups interface
func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources) (Cgroup, error) {
subsystems, err := hierarchy()
if err != nil {
return nil, err
}
for _, s := range subsystems {
if err := initializeSubsystem(s, path, resources); err != nil {
return nil, err
}
}
return &cgroup{
path: path,
subsystems: subsystems,
}, nil
}
// Load will load an existing cgroup and allow it to be controlled
func Load(hierarchy Hierarchy, path Path) (Cgroup, error) {
subsystems, err := hierarchy()
if err != nil {
return nil, err
}
// check the the subsystems still exist
for _, s := range pathers(subsystems) {
p, err := path(s.Name())
if err != nil {
return nil, err
}
if _, err := os.Lstat(s.Path(p)); err != nil {
if os.IsNotExist(err) {
return nil, ErrCgroupDeleted
}
return nil, err
}
}
return &cgroup{
path: path,
subsystems: subsystems,
}, nil
}
type cgroup struct {
path Path
subsystems []Subsystem
mu sync.Mutex
err error
}
// New returns a new sub cgroup
func (c *cgroup) New(name string, resources *specs.LinuxResources) (Cgroup, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return nil, c.err
}
path := subPath(c.path, name)
for _, s := range c.subsystems {
if err := initializeSubsystem(s, path, resources); err != nil {
return nil, err
}
}
return &cgroup{
path: path,
subsystems: c.subsystems,
}, nil
}
// Subsystems returns all the subsystems that are currently being
// consumed by the group
func (c *cgroup) Subsystems() []Subsystem {
return c.subsystems
}
// Add moves the provided process into the new cgroup
func (c *cgroup) Add(process Process) error {
if process.Pid <= 0 {
return ErrInvalidPid
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return c.err
}
return c.add(process)
}
func (c *cgroup) add(process Process) error {
for _, s := range pathers(c.subsystems) {
p, err := c.path(s.Name())
if err != nil {
return err
}
if err := ioutil.WriteFile(
filepath.Join(s.Path(p), cgroupProcs),
[]byte(strconv.Itoa(process.Pid)),
defaultFilePerm,
); err != nil {
return err
}
}
return nil
}
// Delete will remove the control group from each of the subsystems registered
func (c *cgroup) Delete() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return c.err
}
var errors []string
for _, s := range c.subsystems {
if d, ok := s.(deleter); ok {
sp, err := c.path(s.Name())
if err != nil {
return err
}
if err := d.Delete(sp); err != nil {
errors = append(errors, string(s.Name()))
}
continue
}
if p, ok := s.(pather); ok {
sp, err := c.path(s.Name())
if err != nil {
return err
}
path := p.Path(sp)
if err := remove(path); err != nil {
errors = append(errors, path)
}
}
}
if len(errors) > 0 {
return fmt.Errorf("cgroups: unable to remove paths %s", strings.Join(errors, ", "))
}
c.err = ErrCgroupDeleted
return nil
}
// Stat returns the current stats for the cgroup
func (c *cgroup) Stat(handlers ...ErrorHandler) (*Stats, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return nil, c.err
}
if len(handlers) == 0 {
handlers = append(handlers, errPassthrough)
}
var (
stats = &Stats{}
wg = &sync.WaitGroup{}
errs = make(chan error, len(c.subsystems))
)
for _, s := range c.subsystems {
if ss, ok := s.(stater); ok {
sp, err := c.path(s.Name())
if err != nil {
return nil, err
}
wg.Add(1)
go func() {
defer wg.Done()
if err := ss.Stat(sp, stats); err != nil {
for _, eh := range handlers {
if herr := eh(err); herr != nil {
errs <- herr
}
}
}
}()
}
}
wg.Wait()
close(errs)
for err := range errs {
return nil, err
}
return stats, nil
}
// Update updates the cgroup with the new resource values provided
//
// Be prepared to handle EBUSY when trying to update a cgroup with
// live processes and other operations like Stats being performed at the
// same time
func (c *cgroup) Update(resources *specs.LinuxResources) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return c.err
}
for _, s := range c.subsystems {
if u, ok := s.(updater); ok {
sp, err := c.path(s.Name())
if err != nil {
return err
}
if err := u.Update(sp, resources); err != nil {
return err
}
}
}
return nil
}
// Processes returns the processes running inside the cgroup along
// with the subsystem used, pid, and path
func (c *cgroup) Processes(subsystem Name, recursive bool) ([]Process, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return nil, c.err
}
return c.processes(subsystem, recursive)
}
func (c *cgroup) processes(subsystem Name, recursive bool) ([]Process, error) {
s := c.getSubsystem(subsystem)
sp, err := c.path(subsystem)
if err != nil {
return nil, err
}
path := s.(pather).Path(sp)
var processes []Process
err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !recursive && info.IsDir() {
return filepath.SkipDir
}
dir, name := filepath.Split(p)
if name != cgroupProcs {
return nil
}
procs, err := readPids(dir, subsystem)
if err != nil {
return err
}
processes = append(processes, procs...)
return nil
})
return processes, err
}
// Freeze freezes the entire cgroup and all the processes inside it
func (c *cgroup) Freeze() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return c.err
}
s := c.getSubsystem(Freezer)
if s == nil {
return ErrFreezerNotSupported
}
sp, err := c.path(Freezer)
if err != nil {
return err
}
return s.(*freezerController).Freeze(sp)
}
// Thaw thaws out the cgroup and all the processes inside it
func (c *cgroup) Thaw() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return c.err
}
s := c.getSubsystem(Freezer)
if s == nil {
return ErrFreezerNotSupported
}
sp, err := c.path(Freezer)
if err != nil {
return err
}
return s.(*freezerController).Thaw(sp)
}
// OOMEventFD returns the memory cgroup's out of memory event fd that triggers
// when processes inside the cgroup receive an oom event
func (c *cgroup) OOMEventFD() (uintptr, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return 0, c.err
}
s := c.getSubsystem(Memory)
if s == nil {
return 0, ErrMemoryNotSupported
}
sp, err := c.path(Memory)
if err != nil {
return 0, err
}
return s.(*memoryController).OOMEventFD(sp)
}
// State returns the state of the cgroup and its processes
func (c *cgroup) State() State {
c.mu.Lock()
defer c.mu.Unlock()
c.checkExists()
if c.err != nil && c.err == ErrCgroupDeleted {
return Deleted
}
s := c.getSubsystem(Freezer)
if s == nil {
return Thawed
}
sp, err := c.path(Freezer)
if err != nil {
return Unknown
}
state, err := s.(*freezerController).state(sp)
if err != nil {
return Unknown
}
return state
}
// MoveTo does a recursive move subsystem by subsystem of all the processes
// inside the group
func (c *cgroup) MoveTo(destination Cgroup) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return c.err
}
for _, s := range c.subsystems {
processes, err := c.processes(s.Name(), true)
if err != nil {
return err
}
for _, p := range processes {
if err := destination.Add(p); err != nil {
return err
}
}
}
return nil
}
func (c *cgroup) getSubsystem(n Name) Subsystem {
for _, s := range c.subsystems {
if s.Name() == n {
return s
}
}
return nil
}
func (c *cgroup) checkExists() {
for _, s := range pathers(c.subsystems) {
p, err := c.path(s.Name())
if err != nil {
return
}
if _, err := os.Lstat(s.Path(p)); err != nil {
if os.IsNotExist(err) {
c.err = ErrCgroupDeleted
return
}
}
}
}

58
vendor/github.com/containerd/cgroups/control.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package cgroups
import (
"os"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
const (
cgroupProcs = "cgroup.procs"
defaultDirPerm = 0755
)
// defaultFilePerm is a var so that the test framework can change the filemode
// of all files created when the tests are running. The difference between the
// tests and real world use is that files like "cgroup.procs" will exist when writing
// to a read cgroup filesystem and do not exist prior when running in the tests.
// this is set to a non 0 value in the test code
var defaultFilePerm = os.FileMode(0)
type Process struct {
// Subsystem is the name of the subsystem that the process is in
Subsystem Name
// Pid is the process id of the process
Pid int
// Path is the full path of the subsystem and location that the process is in
Path string
}
// Cgroup handles interactions with the individual groups to perform
// actions on them as them main interface to this cgroup package
type Cgroup interface {
// New creates a new cgroup under the calling cgroup
New(string, *specs.LinuxResources) (Cgroup, error)
// Add adds a process to the cgroup
Add(Process) error
// Delete removes the cgroup as a whole
Delete() error
// MoveTo moves all the processes under the calling cgroup to the provided one
// subsystems are moved one at a time
MoveTo(Cgroup) error
// Stat returns the stats for all subsystems in the cgroup
Stat(...ErrorHandler) (*Stats, error)
// Update updates all the subsystems with the provided resource changes
Update(resources *specs.LinuxResources) error
// Processes returns all the processes in a select subsystem for the cgroup
Processes(Name, bool) ([]Process, error)
// Freeze freezes or pauses all processes inside the cgroup
Freeze() error
// Thaw thaw or resumes all processes inside the cgroup
Thaw() error
// OOMEventFD returns the memory subsystem's event fd for OOM events
OOMEventFD() (uintptr, error)
// State returns the cgroups current state
State() State
// Subsystems returns all the subsystems in the cgroup
Subsystems() []Subsystem
}

120
vendor/github.com/containerd/cgroups/cpu.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
package cgroups
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewCpu(root string) *cpuController {
return &cpuController{
root: filepath.Join(root, string(Cpu)),
}
}
type cpuController struct {
root string
}
func (c *cpuController) Name() Name {
return Cpu
}
func (c *cpuController) Path(path string) string {
return filepath.Join(c.root, path)
}
func (c *cpuController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(c.Path(path), defaultDirPerm); err != nil {
return err
}
if cpu := resources.CPU; cpu != nil {
for _, t := range []struct {
name string
ivalue *int64
uvalue *uint64
}{
{
name: "rt_period_us",
uvalue: cpu.RealtimePeriod,
},
{
name: "rt_runtime_us",
ivalue: cpu.RealtimeRuntime,
},
{
name: "shares",
uvalue: cpu.Shares,
},
{
name: "cfs_period_us",
uvalue: cpu.Period,
},
{
name: "cfs_quota_us",
ivalue: cpu.Quota,
},
} {
var value []byte
if t.uvalue != nil {
value = []byte(strconv.FormatUint(*t.uvalue, 10))
} else if t.ivalue != nil {
value = []byte(strconv.FormatInt(*t.ivalue, 10))
}
if value != nil {
if err := ioutil.WriteFile(
filepath.Join(c.Path(path), fmt.Sprintf("cpu.%s", t.name)),
value,
defaultFilePerm,
); err != nil {
return err
}
}
}
}
return nil
}
func (c *cpuController) Update(path string, resources *specs.LinuxResources) error {
return c.Create(path, resources)
}
func (c *cpuController) Stat(path string, stats *Stats) error {
f, err := os.Open(filepath.Join(c.Path(path), "cpu.stat"))
if err != nil {
return err
}
defer f.Close()
// get or create the cpu field because cpuacct can also set values on this struct
stats.cpuMu.Lock()
cpu := stats.Cpu
if cpu == nil {
cpu = &CpuStat{}
stats.Cpu = cpu
}
stats.cpuMu.Unlock()
sc := bufio.NewScanner(f)
for sc.Scan() {
if err := sc.Err(); err != nil {
return err
}
key, v, err := parseKV(sc.Text())
if err != nil {
return err
}
switch key {
case "nr_periods":
cpu.Throttling.Periods = v
case "nr_throttled":
cpu.Throttling.ThrottledPeriods = v
case "throttled_time":
cpu.Throttling.ThrottledTime = v
}
}
return nil
}

112
vendor/github.com/containerd/cgroups/cpuacct.go generated vendored Normal file
View File

@@ -0,0 +1,112 @@
package cgroups
import (
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"strings"
)
const nanosecondsInSecond = 1000000000
var clockTicks = getClockTicks()
func NewCpuacct(root string) *cpuacctController {
return &cpuacctController{
root: filepath.Join(root, string(Cpuacct)),
}
}
type cpuacctController struct {
root string
}
func (c *cpuacctController) Name() Name {
return Cpuacct
}
func (c *cpuacctController) Path(path string) string {
return filepath.Join(c.root, path)
}
func (c *cpuacctController) Stat(path string, stats *Stats) error {
user, kernel, err := c.getUsage(path)
if err != nil {
return err
}
total, err := readUint(filepath.Join(c.Path(path), "cpuacct.usage"))
if err != nil {
return err
}
percpu, err := c.percpuUsage(path)
if err != nil {
return err
}
stats.cpuMu.Lock()
cpu := stats.Cpu
if cpu == nil {
cpu = &CpuStat{}
stats.Cpu = cpu
}
stats.cpuMu.Unlock()
cpu.Usage.Total = total
cpu.Usage.User = user
cpu.Usage.Kernel = kernel
cpu.Usage.PerCpu = percpu
return nil
}
func (c *cpuacctController) percpuUsage(path string) ([]uint64, error) {
var usage []uint64
data, err := ioutil.ReadFile(filepath.Join(c.Path(path), "cpuacct.usage_percpu"))
if err != nil {
return nil, err
}
for _, v := range strings.Fields(string(data)) {
u, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, err
}
usage = append(usage, u)
}
return usage, nil
}
func (c *cpuacctController) getUsage(path string) (user uint64, kernel uint64, err error) {
statPath := filepath.Join(c.Path(path), "cpuacct.stat")
data, err := ioutil.ReadFile(statPath)
if err != nil {
return 0, 0, err
}
fields := strings.Fields(string(data))
if len(fields) != 4 {
return 0, 0, fmt.Errorf("%q is expected to have 4 fields", statPath)
}
for _, t := range []struct {
index int
name string
value *uint64
}{
{
index: 0,
name: "user",
value: &user,
},
{
index: 2,
name: "system",
value: &kernel,
},
} {
if fields[t.index] != t.name {
return 0, 0, fmt.Errorf("expected field %q but found %q in %q", t.name, fields[t.index], statPath)
}
v, err := strconv.ParseUint(fields[t.index+1], 10, 64)
if err != nil {
return 0, 0, err
}
*t.value = v
}
return (user * nanosecondsInSecond) / clockTicks, (kernel * nanosecondsInSecond) / clockTicks, nil
}

140
vendor/github.com/containerd/cgroups/cpuset.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
package cgroups
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewCputset(root string) *cpusetController {
return &cpusetController{
root: filepath.Join(root, string(Cpuset)),
}
}
type cpusetController struct {
root string
}
func (c *cpusetController) Name() Name {
return Cpuset
}
func (c *cpusetController) Path(path string) string {
return filepath.Join(c.root, path)
}
func (c *cpusetController) Create(path string, resources *specs.LinuxResources) error {
if err := c.ensureParent(c.Path(path), c.root); err != nil {
return err
}
if err := os.MkdirAll(c.Path(path), defaultDirPerm); err != nil {
return err
}
if err := c.copyIfNeeded(c.Path(path), filepath.Dir(c.Path(path))); err != nil {
return err
}
if resources.CPU != nil {
for _, t := range []struct {
name string
value *string
}{
{
name: "cpus",
value: &resources.CPU.Cpus,
},
{
name: "mems",
value: &resources.CPU.Mems,
},
} {
if t.value != nil {
if err := ioutil.WriteFile(
filepath.Join(c.Path(path), fmt.Sprintf("cpuset.%s", t.name)),
[]byte(*t.value),
defaultFilePerm,
); err != nil {
return err
}
}
}
}
return nil
}
func (c *cpusetController) getValues(path string) (cpus []byte, mems []byte, err error) {
if cpus, err = ioutil.ReadFile(filepath.Join(path, "cpuset.cpus")); err != nil && !os.IsNotExist(err) {
return
}
if mems, err = ioutil.ReadFile(filepath.Join(path, "cpuset.mems")); err != nil && !os.IsNotExist(err) {
return
}
return cpus, mems, nil
}
// ensureParent makes sure that the parent directory of current is created
// and populated with the proper cpus and mems files copied from
// it's parent.
func (c *cpusetController) ensureParent(current, root string) error {
parent := filepath.Dir(current)
if _, err := filepath.Rel(root, parent); err != nil {
return nil
}
if cleanPath(parent) == root {
return nil
}
// Avoid infinite recursion.
if parent == current {
return fmt.Errorf("cpuset: cgroup parent path outside cgroup root")
}
if err := c.ensureParent(parent, root); err != nil {
return err
}
if err := os.MkdirAll(current, defaultDirPerm); err != nil {
return err
}
return c.copyIfNeeded(current, parent)
}
// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
// directory to the current directory if the file's contents are 0
func (c *cpusetController) copyIfNeeded(current, parent string) error {
var (
err error
currentCpus, currentMems []byte
parentCpus, parentMems []byte
)
if currentCpus, currentMems, err = c.getValues(current); err != nil {
return err
}
if parentCpus, parentMems, err = c.getValues(parent); err != nil {
return err
}
if isEmpty(currentCpus) {
if err := ioutil.WriteFile(
filepath.Join(current, "cpuset.cpus"),
parentCpus,
defaultFilePerm,
); err != nil {
return err
}
}
if isEmpty(currentMems) {
if err := ioutil.WriteFile(
filepath.Join(current, "cpuset.mems"),
parentMems,
defaultFilePerm,
); err != nil {
return err
}
}
return nil
}
func isEmpty(b []byte) bool {
return len(bytes.Trim(b, "\n")) == 0
}

74
vendor/github.com/containerd/cgroups/devices.go generated vendored Normal file
View File

@@ -0,0 +1,74 @@
package cgroups
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
const (
allowDeviceFile = "devices.allow"
denyDeviceFile = "devices.deny"
wildcard = -1
)
func NewDevices(root string) *devicesController {
return &devicesController{
root: filepath.Join(root, string(Devices)),
}
}
type devicesController struct {
root string
}
func (d *devicesController) Name() Name {
return Devices
}
func (d *devicesController) Path(path string) string {
return filepath.Join(d.root, path)
}
func (d *devicesController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(d.Path(path), defaultDirPerm); err != nil {
return err
}
for _, device := range resources.Devices {
file := denyDeviceFile
if device.Allow {
file = allowDeviceFile
}
if err := ioutil.WriteFile(
filepath.Join(d.Path(path), file),
[]byte(deviceString(device)),
defaultFilePerm,
); err != nil {
return err
}
}
return nil
}
func (d *devicesController) Update(path string, resources *specs.LinuxResources) error {
return d.Create(path, resources)
}
func deviceString(device specs.LinuxDeviceCgroup) string {
return fmt.Sprintf("%c %s:%s %s",
&device.Type,
deviceNumber(device.Major),
deviceNumber(device.Minor),
&device.Access,
)
}
func deviceNumber(number *int64) string {
if number == nil || *number == wildcard {
return "*"
}
return fmt.Sprint(*number)
}

31
vendor/github.com/containerd/cgroups/errors.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package cgroups
import (
"errors"
"os"
)
var (
ErrInvalidPid = errors.New("cgroups: pid must be greater than 0")
ErrMountPointNotExist = errors.New("cgroups: cgroup mountpoint does not exist")
ErrInvalidFormat = errors.New("cgroups: parsing file with invalid format failed")
ErrFreezerNotSupported = errors.New("cgroups: freezer cgroup not supported on this system")
ErrMemoryNotSupported = errors.New("cgroups: memory cgroup not supported on this system")
ErrCgroupDeleted = errors.New("cgroups: cgroup deleted")
ErrNoCgroupMountDestination = errors.New("cgroups: cannot found cgroup mount destination")
)
// ErrorHandler is a function that handles and acts on errors
type ErrorHandler func(err error) error
// IgnoreNotExist ignores any errors that are for not existing files
func IgnoreNotExist(err error) error {
if os.IsNotExist(err) {
return nil
}
return err
}
func errPassthrough(err error) error {
return err
}

69
vendor/github.com/containerd/cgroups/freezer.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package cgroups
import (
"io/ioutil"
"path/filepath"
"strings"
"time"
)
func NewFreezer(root string) *freezerController {
return &freezerController{
root: filepath.Join(root, string(Freezer)),
}
}
type freezerController struct {
root string
}
func (f *freezerController) Name() Name {
return Freezer
}
func (f *freezerController) Path(path string) string {
return filepath.Join(f.root, path)
}
func (f *freezerController) Freeze(path string) error {
if err := f.changeState(path, Frozen); err != nil {
return err
}
return f.waitState(path, Frozen)
}
func (f *freezerController) Thaw(path string) error {
if err := f.changeState(path, Thawed); err != nil {
return err
}
return f.waitState(path, Thawed)
}
func (f *freezerController) changeState(path string, state State) error {
return ioutil.WriteFile(
filepath.Join(f.root, path, "freezer.state"),
[]byte(strings.ToUpper(string(state))),
defaultFilePerm,
)
}
func (f *freezerController) state(path string) (State, error) {
current, err := ioutil.ReadFile(filepath.Join(f.root, path, "freezer.state"))
if err != nil {
return "", err
}
return State(strings.ToLower(strings.TrimSpace(string(current)))), nil
}
func (f *freezerController) waitState(path string, state State) error {
for {
current, err := f.state(path)
if err != nil {
return err
}
if current == state {
return nil
}
time.Sleep(1 * time.Millisecond)
}
}

4
vendor/github.com/containerd/cgroups/hierarchy.go generated vendored Normal file
View File

@@ -0,0 +1,4 @@
package cgroups
// Hierarchy enableds both unified and split hierarchy for cgroups
type Hierarchy func() ([]Subsystem, error)

92
vendor/github.com/containerd/cgroups/hugetlb.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
package cgroups
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewHugetlb(root string) (*hugetlbController, error) {
sizes, err := hugePageSizes()
if err != nil {
return nil, nil
}
return &hugetlbController{
root: filepath.Join(root, string(Hugetlb)),
sizes: sizes,
}, nil
}
type hugetlbController struct {
root string
sizes []string
}
func (h *hugetlbController) Name() Name {
return Hugetlb
}
func (h *hugetlbController) Path(path string) string {
return filepath.Join(h.root, path)
}
func (h *hugetlbController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(h.Path(path), defaultDirPerm); err != nil {
return err
}
for _, limit := range resources.HugepageLimits {
if err := ioutil.WriteFile(
filepath.Join(h.Path(path), strings.Join([]string{"hugetlb", limit.Pagesize, "limit_in_bytes"}, ".")),
[]byte(strconv.FormatUint(limit.Limit, 10)),
defaultFilePerm,
); err != nil {
return err
}
}
return nil
}
func (h *hugetlbController) Stat(path string, stats *Stats) error {
stats.Hugetlb = make(map[string]HugetlbStat)
for _, size := range h.sizes {
s, err := h.readSizeStat(path, size)
if err != nil {
return err
}
stats.Hugetlb[size] = s
}
return nil
}
func (h *hugetlbController) readSizeStat(path, size string) (HugetlbStat, error) {
var s HugetlbStat
for _, t := range []struct {
name string
value *uint64
}{
{
name: "usage_in_bytes",
value: &s.Usage,
},
{
name: "max_usage_in_bytes",
value: &s.Max,
},
{
name: "failcnt",
value: &s.Failcnt,
},
} {
v, err := readUint(filepath.Join(h.Path(path), strings.Join([]string{"hugetlb", size, t.name}, ".")))
if err != nil {
return s, err
}
*t.value = v
}
return s, nil
}

304
vendor/github.com/containerd/cgroups/memory.go generated vendored Normal file
View File

@@ -0,0 +1,304 @@
package cgroups
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"golang.org/x/sys/unix"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewMemory(root string) *memoryController {
return &memoryController{
root: filepath.Join(root, string(Memory)),
}
}
type memoryController struct {
root string
}
func (m *memoryController) Name() Name {
return Memory
}
func (m *memoryController) Path(path string) string {
return filepath.Join(m.root, path)
}
func (m *memoryController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(m.Path(path), defaultDirPerm); err != nil {
return err
}
if resources.Memory == nil {
return nil
}
if resources.Memory.Kernel != nil {
// Check if kernel memory is enabled
// We have to limit the kernel memory here as it won't be accounted at all
// until a limit is set on the cgroup and limit cannot be set once the
// cgroup has children, or if there are already tasks in the cgroup.
for _, i := range []int64{1, -1} {
if err := ioutil.WriteFile(
filepath.Join(m.Path(path), "memory.kmem.limit_in_bytes"),
[]byte(strconv.FormatInt(i, 10)),
defaultFilePerm,
); err != nil {
return checkEBUSY(err)
}
}
}
return m.set(path, getMemorySettings(resources))
}
func (m *memoryController) Update(path string, resources *specs.LinuxResources) error {
if resources.Memory == nil {
return nil
}
g := func(v *uint64) bool {
return v != nil && *v > 0
}
settings := getMemorySettings(resources)
if g(resources.Memory.Limit) && g(resources.Memory.Swap) {
// if the updated swap value is larger than the current memory limit set the swap changes first
// then set the memory limit as swap must always be larger than the current limit
current, err := readUint(filepath.Join(m.Path(path), "memory.limit_in_bytes"))
if err != nil {
return err
}
if current < uint64(*resources.Memory.Swap) {
settings[0], settings[1] = settings[1], settings[0]
}
}
return m.set(path, settings)
}
func (m *memoryController) Stat(path string, stats *Stats) error {
f, err := os.Open(filepath.Join(m.Path(path), "memory.stat"))
if err != nil {
return err
}
defer f.Close()
stats.Memory = &MemoryStat{}
if err := m.parseStats(f, stats.Memory); err != nil {
return err
}
for _, t := range []struct {
module string
entry *MemoryEntry
}{
{
module: "",
entry: &stats.Memory.Usage,
},
{
module: "memsw",
entry: &stats.Memory.Swap,
},
{
module: "kmem",
entry: &stats.Memory.Kernel,
},
{
module: "kmem.tcp",
entry: &stats.Memory.KernelTCP,
},
} {
for _, tt := range []struct {
name string
value *uint64
}{
{
name: "usage_in_bytes",
value: &t.entry.Usage,
},
{
name: "max_usage_in_bytes",
value: &t.entry.Max,
},
{
name: "failcnt",
value: &t.entry.Failcnt,
},
{
name: "limit_in_bytes",
value: &t.entry.Limit,
},
} {
parts := []string{"memory"}
if t.module != "" {
parts = append(parts, t.module)
}
parts = append(parts, tt.name)
v, err := readUint(filepath.Join(m.Path(path), strings.Join(parts, ".")))
if err != nil {
return err
}
*tt.value = v
}
}
return nil
}
func (m *memoryController) OOMEventFD(path string) (uintptr, error) {
root := m.Path(path)
f, err := os.Open(filepath.Join(root, "memory.oom_control"))
if err != nil {
return 0, err
}
defer f.Close()
fd, _, serr := unix.RawSyscall(unix.SYS_EVENTFD2, 0, unix.FD_CLOEXEC, 0)
if serr != 0 {
return 0, serr
}
if err := writeEventFD(root, f.Fd(), fd); err != nil {
unix.Close(int(fd))
return 0, err
}
return fd, nil
}
func writeEventFD(root string, cfd, efd uintptr) error {
f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0)
if err != nil {
return err
}
_, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd))
f.Close()
return err
}
func (m *memoryController) parseStats(r io.Reader, stat *MemoryStat) error {
var (
raw = make(map[string]uint64)
sc = bufio.NewScanner(r)
line int
)
for sc.Scan() {
if err := sc.Err(); err != nil {
return err
}
key, v, err := parseKV(sc.Text())
if err != nil {
return fmt.Errorf("%d: %v", line, err)
}
raw[key] = v
line++
}
stat.Cache = raw["cache"]
stat.RSS = raw["rss"]
stat.RSSHuge = raw["rss_huge"]
stat.MappedFile = raw["mapped_file"]
stat.Dirty = raw["dirty"]
stat.Writeback = raw["writeback"]
stat.PgPgIn = raw["pgpgin"]
stat.PgPgOut = raw["pgpgout"]
stat.PgFault = raw["pgfault"]
stat.PgMajFault = raw["pgmajfault"]
stat.InactiveAnon = raw["inactive_anon"]
stat.ActiveAnon = raw["active_anon"]
stat.InactiveFile = raw["inactive_file"]
stat.ActiveFile = raw["active_file"]
stat.Unevictable = raw["unevictable"]
stat.HierarchicalMemoryLimit = raw["hierarchical_memory_limit"]
stat.HierarchicalSwapLimit = raw["hierarchical_memsw_limit"]
stat.TotalCache = raw["total_cache"]
stat.TotalRSS = raw["total_rss"]
stat.TotalRSSHuge = raw["total_rss_huge"]
stat.TotalMappedFile = raw["total_mapped_file"]
stat.TotalDirty = raw["total_dirty"]
stat.TotalWriteback = raw["total_writeback"]
stat.TotalPgPgIn = raw["total_pgpgin"]
stat.TotalPgPgOut = raw["total_pgpgout"]
stat.TotalPgFault = raw["total_pgfault"]
stat.TotalPgMajFault = raw["total_pgmajfault"]
stat.TotalInactiveAnon = raw["total_inactive_anon"]
stat.TotalActiveAnon = raw["total_active_anon"]
stat.TotalInactiveFile = raw["total_inactive_file"]
stat.TotalActiveFile = raw["total_active_file"]
stat.TotalUnevictable = raw["total_unevictable"]
return nil
}
func (m *memoryController) set(path string, settings []memorySettings) error {
for _, t := range settings {
if t.value != nil {
if err := ioutil.WriteFile(
filepath.Join(m.Path(path), fmt.Sprintf("memory.%s", t.name)),
[]byte(strconv.FormatUint(*t.value, 10)),
defaultFilePerm,
); err != nil {
return err
}
}
}
return nil
}
type memorySettings struct {
name string
value *uint64
}
func getMemorySettings(resources *specs.LinuxResources) []memorySettings {
mem := resources.Memory
var swappiness *uint64
if mem.Swappiness != nil {
v := uint64(*mem.Swappiness)
swappiness = &v
}
return []memorySettings{
{
name: "limit_in_bytes",
value: mem.Limit,
},
{
name: "memsw.limit_in_bytes",
value: mem.Swap,
},
{
name: "kmem.limit_in_bytes",
value: mem.Kernel,
},
{
name: "kmem.tcp.limit_in_bytes",
value: mem.KernelTCP,
},
{
name: "oom_control",
value: getOomControlValue(resources),
},
{
name: "swappiness",
value: swappiness,
},
}
}
func checkEBUSY(err error) error {
if pathErr, ok := err.(*os.PathError); ok {
if errNo, ok := pathErr.Err.(syscall.Errno); ok {
if errNo == unix.EBUSY {
return fmt.Errorf(
"failed to set memory.kmem.limit_in_bytes, because either tasks have already joined this cgroup or it has children")
}
}
}
return err
}
func getOomControlValue(resources *specs.LinuxResources) *uint64 {
if resources.DisableOOMKiller != nil && *resources.DisableOOMKiller {
i := uint64(1)
return &i
}
return nil
}

23
vendor/github.com/containerd/cgroups/named.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package cgroups
import "path/filepath"
func NewNamed(root string, name Name) *namedController {
return &namedController{
root: root,
name: name,
}
}
type namedController struct {
root string
name Name
}
func (n *namedController) Name() Name {
return n.name
}
func (n *namedController) Path(path string) string {
return filepath.Join(n.root, string(n.name), path)
}

42
vendor/github.com/containerd/cgroups/net_cls.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package cgroups
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewNetCls(root string) *netclsController {
return &netclsController{
root: filepath.Join(root, string(NetCLS)),
}
}
type netclsController struct {
root string
}
func (n *netclsController) Name() Name {
return NetCLS
}
func (n *netclsController) Path(path string) string {
return filepath.Join(n.root, path)
}
func (n *netclsController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(n.Path(path), defaultDirPerm); err != nil {
return err
}
if resources.Network != nil && resources.Network.ClassID != nil && *resources.Network.ClassID > 0 {
return ioutil.WriteFile(
filepath.Join(n.Path(path), "net_cls_classid_u"),
[]byte(strconv.FormatUint(uint64(*resources.Network.ClassID), 10)),
defaultFilePerm,
)
}
return nil
}

50
vendor/github.com/containerd/cgroups/net_prio.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
package cgroups
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewNetPrio(root string) *netprioController {
return &netprioController{
root: filepath.Join(root, string(NetPrio)),
}
}
type netprioController struct {
root string
}
func (n *netprioController) Name() Name {
return NetPrio
}
func (n *netprioController) Path(path string) string {
return filepath.Join(n.root, path)
}
func (n *netprioController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(n.Path(path), defaultDirPerm); err != nil {
return err
}
if resources.Network != nil {
for _, prio := range resources.Network.Priorities {
if err := ioutil.WriteFile(
filepath.Join(n.Path(path), "net_prio_ifpriomap"),
formatPrio(prio.Name, prio.Priority),
defaultFilePerm,
); err != nil {
return err
}
}
}
return nil
}
func formatPrio(name string, prio uint32) []byte {
return []byte(fmt.Sprintf("%s %d", name, prio))
}

85
vendor/github.com/containerd/cgroups/paths.go generated vendored Normal file
View File

@@ -0,0 +1,85 @@
package cgroups
import (
"fmt"
"path/filepath"
)
type Path func(subsystem Name) (string, error)
func RootPath(subsysem Name) (string, error) {
return "/", nil
}
// StaticPath returns a static path to use for all cgroups
func StaticPath(path string) Path {
return func(_ Name) (string, error) {
return path, nil
}
}
// NestedPath will nest the cgroups based on the calling processes cgroup
// placing its child processes inside its own path
func NestedPath(suffix string) Path {
paths, err := parseCgroupFile("/proc/self/cgroup")
if err != nil {
return errorPath(err)
}
return existingPath(paths, suffix)
}
// PidPath will return the correct cgroup paths for an existing process running inside a cgroup
// This is commonly used for the Load function to restore an existing container
func PidPath(pid int) Path {
paths, err := parseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", pid))
if err != nil {
return errorPath(err)
}
return existingPath(paths, "")
}
func existingPath(paths map[string]string, suffix string) Path {
// localize the paths based on the root mount dest for nested cgroups
for n, p := range paths {
dest, err := getCgroupDestination(string(n))
if err != nil {
return errorPath(err)
}
rel, err := filepath.Rel(dest, p)
if err != nil {
return errorPath(err)
}
if rel == "." {
rel = dest
}
paths[n] = filepath.Join("/", rel)
}
return func(name Name) (string, error) {
root, ok := paths[string(name)]
if !ok {
if root, ok = paths[fmt.Sprintf("name=%s", name)]; !ok {
return "", fmt.Errorf("unable to find %q in controller set", name)
}
}
if suffix != "" {
return filepath.Join(root, suffix), nil
}
return root, nil
}
}
func subPath(path Path, subName string) Path {
return func(name Name) (string, error) {
p, err := path(name)
if err != nil {
return "", err
}
return filepath.Join(p, subName), nil
}
}
func errorPath(err error) Path {
return func(_ Name) (string, error) {
return "", err
}
}

21
vendor/github.com/containerd/cgroups/perf_event.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package cgroups
import "path/filepath"
func NewPerfEvent(root string) *PerfEventController {
return &PerfEventController{
root: filepath.Join(root, string(PerfEvent)),
}
}
type PerfEventController struct {
root string
}
func (p *PerfEventController) Name() Name {
return PerfEvent
}
func (p *PerfEventController) Path(path string) string {
return filepath.Join(p.root, path)
}

69
vendor/github.com/containerd/cgroups/pids.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package cgroups
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewPids(root string) *pidsController {
return &pidsController{
root: filepath.Join(root, string(Pids)),
}
}
type pidsController struct {
root string
}
func (p *pidsController) Name() Name {
return Pids
}
func (p *pidsController) Path(path string) string {
return filepath.Join(p.root, path)
}
func (p *pidsController) Create(path string, resources *specs.LinuxResources) error {
if err := os.MkdirAll(p.Path(path), defaultDirPerm); err != nil {
return err
}
if resources.Pids != nil && resources.Pids.Limit > 0 {
return ioutil.WriteFile(
filepath.Join(p.Path(path), "pids.max"),
[]byte(strconv.FormatInt(resources.Pids.Limit, 10)),
defaultFilePerm,
)
}
return nil
}
func (p *pidsController) Update(path string, resources *specs.LinuxResources) error {
return p.Create(path, resources)
}
func (p *pidsController) Stat(path string, stats *Stats) error {
current, err := readUint(filepath.Join(p.Path(path), "pids.current"))
if err != nil {
return err
}
var max uint64
maxData, err := ioutil.ReadFile(filepath.Join(p.Path(path), "pids.max"))
if err != nil {
return err
}
if maxS := strings.TrimSpace(string(maxData)); maxS != "max" {
if max, err = parseUint(maxS, 10, 64); err != nil {
return err
}
}
stats.Pids = &PidsStat{
Current: current,
Limit: max,
}
return nil
}

View File

@@ -0,0 +1,101 @@
package prometheus
import (
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
var blkioMetrics = []*metric{
{
name: "blkio_io_merged_recursive",
help: "The blkio io merged recursive",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.IoMergedRecursive)
},
},
{
name: "blkio_io_queued_recursive",
help: "The blkio io queued recursive",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.IoQueuedRecursive)
},
},
{
name: "blkio_io_service_bytes_recursive",
help: "The blkio io service bytes recursive",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.IoServiceBytesRecursive)
},
},
{
name: "blkio_io_service_time_recursive",
help: "The blkio io servie time recursive",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.IoServiceTimeRecursive)
},
},
{
name: "blkio_io_serviced_recursive",
help: "The blkio io servied recursive",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.IoServicedRecursive)
},
},
{
name: "blkio_io_time_recursive",
help: "The blkio io time recursive",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.IoTimeRecursive)
},
},
{
name: "blkio_sectors_recursive",
help: "The blkio sectors recursive",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"op", "device", "major", "minor"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Blkio == nil {
return nil
}
return blkioValues(stats.Blkio.SectorsRecursive)
},
},
}

128
vendor/github.com/containerd/cgroups/prometheus/cpu.go generated vendored Normal file
View File

@@ -0,0 +1,128 @@
package prometheus
import (
"strconv"
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
var cpuMetrics = []*metric{
{
name: "cpu_total",
help: "The total cpu time",
unit: metrics.Nanoseconds,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
return []value{
{
v: float64(stats.Cpu.Usage.Total),
},
}
},
},
{
name: "cpu_kernel",
help: "The total kernel cpu time",
unit: metrics.Nanoseconds,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
return []value{
{
v: float64(stats.Cpu.Usage.Kernel),
},
}
},
},
{
name: "cpu_user",
help: "The total user cpu time",
unit: metrics.Nanoseconds,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
return []value{
{
v: float64(stats.Cpu.Usage.User),
},
}
},
},
{
name: "per_cpu",
help: "The total cpu time per cpu",
unit: metrics.Nanoseconds,
vt: prometheus.GaugeValue,
labels: []string{"cpu"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
var out []value
for i, v := range stats.Cpu.Usage.PerCpu {
out = append(out, value{
v: float64(v),
l: []string{strconv.Itoa(i)},
})
}
return out
},
},
{
name: "cpu_throttle_periods",
help: "The total cpu throttle periods",
unit: metrics.Total,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
return []value{
{
v: float64(stats.Cpu.Throttling.Periods),
},
}
},
},
{
name: "cpu_throttled_periods",
help: "The total cpu throttled periods",
unit: metrics.Total,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
return []value{
{
v: float64(stats.Cpu.Throttling.ThrottledPeriods),
},
}
},
},
{
name: "cpu_throttled_time",
help: "The total cpu throttled time",
unit: metrics.Nanoseconds,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Cpu == nil {
return nil
}
return []value{
{
v: float64(stats.Cpu.Throttling.ThrottledTime),
},
}
},
},
}

View File

@@ -0,0 +1,70 @@
package prometheus
import (
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
var hugetlbMetrics = []*metric{
{
name: "hugetlb_usage",
help: "The hugetlb usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
labels: []string{"page"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Hugetlb == nil {
return nil
}
var out []value
for page, v := range stats.Hugetlb {
out = append(out, value{
v: float64(v.Usage),
l: []string{page},
})
}
return out
},
},
{
name: "hugetlb_failcnt",
help: "The hugetlb failcnt",
unit: metrics.Total,
vt: prometheus.GaugeValue,
labels: []string{"page"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Hugetlb == nil {
return nil
}
var out []value
for page, v := range stats.Hugetlb {
out = append(out, value{
v: float64(v.Failcnt),
l: []string{page},
})
}
return out
},
},
{
name: "hugetlb_max",
help: "The hugetlb maximum usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
labels: []string{"page"},
getValues: func(stats *cgroups.Stats) []value {
if stats.Hugetlb == nil {
return nil
}
var out []value
for page, v := range stats.Hugetlb {
out = append(out, value{
v: float64(v.Max),
l: []string{page},
})
}
return out
},
},
}

View File

@@ -0,0 +1,778 @@
package prometheus
import (
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
var memoryMetrics = []*metric{
{
name: "memory_cache",
help: "The cache amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Cache),
},
}
},
},
{
name: "memory_rss",
help: "The rss amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.RSS),
},
}
},
},
{
name: "memory_rss_huge",
help: "The rss_huge amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.RSSHuge),
},
}
},
},
{
name: "memory_mapped_file",
help: "The mapped_file amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.MappedFile),
},
}
},
},
{
name: "memory_dirty",
help: "The dirty amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Dirty),
},
}
},
},
{
name: "memory_writeback",
help: "The writeback amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Writeback),
},
}
},
},
{
name: "memory_pgpgin",
help: "The pgpgin amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.PgPgIn),
},
}
},
},
{
name: "memory_pgpgout",
help: "The pgpgout amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.PgPgOut),
},
}
},
},
{
name: "memory_pgfault",
help: "The pgfault amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.PgFault),
},
}
},
},
{
name: "memory_pgmajfault",
help: "The pgmajfault amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.PgMajFault),
},
}
},
},
{
name: "memory_inactive_anon",
help: "The inactive_anon amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.InactiveAnon),
},
}
},
},
{
name: "memory_active_anon",
help: "The active_anon amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.ActiveAnon),
},
}
},
},
{
name: "memory_inactive_file",
help: "The inactive_file amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.InactiveFile),
},
}
},
},
{
name: "memory_active_file",
help: "The active_file amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.ActiveFile),
},
}
},
},
{
name: "memory_unevictable",
help: "The unevictable amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Unevictable),
},
}
},
},
{
name: "memory_hierarchical_memory_limit",
help: "The hierarchical_memory_limit amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.HierarchicalMemoryLimit),
},
}
},
},
{
name: "memory_hierarchical_memsw_limit",
help: "The hierarchical_memsw_limit amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.HierarchicalSwapLimit),
},
}
},
},
{
name: "memory_total_cache",
help: "The total_cache amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalCache),
},
}
},
},
{
name: "memory_total_rss",
help: "The total_rss amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalRSS),
},
}
},
},
{
name: "memory_total_rss_huge",
help: "The total_rss_huge amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalRSSHuge),
},
}
},
},
{
name: "memory_total_mapped_file",
help: "The total_mapped_file amount used",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalMappedFile),
},
}
},
},
{
name: "memory_total_dirty",
help: "The total_dirty amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalDirty),
},
}
},
},
{
name: "memory_total_writeback",
help: "The total_writeback amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalWriteback),
},
}
},
},
{
name: "memory_total_pgpgin",
help: "The total_pgpgin amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalPgPgIn),
},
}
},
},
{
name: "memory_total_pgpgout",
help: "The total_pgpgout amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalPgPgOut),
},
}
},
},
{
name: "memory_total_pgfault",
help: "The total_pgfault amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalPgFault),
},
}
},
},
{
name: "memory_total_pgmajfault",
help: "The total_pgmajfault amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalPgMajFault),
},
}
},
},
{
name: "memory_total_inactive_anon",
help: "The total_inactive_anon amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalInactiveAnon),
},
}
},
},
{
name: "memory_total_active_anon",
help: "The total_active_anon amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalActiveAnon),
},
}
},
},
{
name: "memory_total_inactive_file",
help: "The total_inactive_file amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalInactiveFile),
},
}
},
},
{
name: "memory_total_active_file",
help: "The total_active_file amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalActiveFile),
},
}
},
},
{
name: "memory_total_unevictable",
help: "The total_unevictable amount",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.TotalUnevictable),
},
}
},
},
{
name: "memory_usage_failcnt",
help: "The usage failcnt",
unit: metrics.Total,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Usage.Failcnt),
},
}
},
},
{
name: "memory_usage_limit",
help: "The memory limit",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Usage.Limit),
},
}
},
},
{
name: "memory_usage_max",
help: "The memory maximum usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Usage.Max),
},
}
},
},
{
name: "memory_usage_usage",
help: "The memory usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Usage.Usage),
},
}
},
},
{
name: "memory_swap_failcnt",
help: "The swap failcnt",
unit: metrics.Total,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Swap.Failcnt),
},
}
},
},
{
name: "memory_swap_limit",
help: "The swap limit",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Swap.Limit),
},
}
},
},
{
name: "memory_swap_max",
help: "The swap maximum usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Swap.Max),
},
}
},
},
{
name: "memory_swap_usage",
help: "The swap usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Swap.Usage),
},
}
},
},
{
name: "memory_kernel_failcnt",
help: "The kernel failcnt",
unit: metrics.Total,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Kernel.Failcnt),
},
}
},
},
{
name: "memory_kernel_limit",
help: "The kernel limit",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Kernel.Limit),
},
}
},
},
{
name: "memory_kernel_max",
help: "The kernel maximum usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Kernel.Max),
},
}
},
},
{
name: "memory_kernel_usage",
help: "The kernel usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.Kernel.Usage),
},
}
},
},
{
name: "memory_kerneltcp_failcnt",
help: "The kerneltcp failcnt",
unit: metrics.Total,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.KernelTCP.Failcnt),
},
}
},
},
{
name: "memory_kerneltcp_limit",
help: "The kerneltcp limit",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.KernelTCP.Limit),
},
}
},
},
{
name: "memory_kerneltcp_max",
help: "The kerneltcp maximum usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.KernelTCP.Max),
},
}
},
},
{
name: "memory_kerneltcp_usage",
help: "The kerneltcp usage",
unit: metrics.Bytes,
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Memory == nil {
return nil
}
return []value{
{
v: float64(stats.Memory.KernelTCP.Usage),
},
}
},
},
}

View File

@@ -0,0 +1,33 @@
package prometheus
import (
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
type value struct {
v float64
l []string
}
type metric struct {
name string
help string
unit metrics.Unit
vt prometheus.ValueType
labels []string
// getValues returns the value and labels for the data
getValues func(stats *cgroups.Stats) []value
}
func (m *metric) desc(ns *metrics.Namespace) *prometheus.Desc {
return ns.NewDesc(m.name, m.help, m.unit, append([]string{"id"}, m.labels...)...)
}
func (m *metric) collect(id string, stats *cgroups.Stats, ns *metrics.Namespace, ch chan<- prometheus.Metric) {
values := m.getValues(stats)
for _, v := range values {
ch <- prometheus.MustNewConstMetric(m.desc(ns), m.vt, v.v, append([]string{id}, v.l...)...)
}
}

View File

@@ -0,0 +1,118 @@
package prometheus
import (
"errors"
"strconv"
"sync"
"github.com/Sirupsen/logrus"
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
var (
ErrAlreadyCollected = errors.New("cgroup is already being collected")
ErrCgroupNotExists = errors.New("cgroup does not exist in the collector")
)
// Trigger will be called when an event happens and provides the cgroup
// where the event originated from
type Trigger func(string, cgroups.Cgroup)
// New registers the Collector with the provided namespace and returns it so
// that cgroups can be added for collection
func New(ns *metrics.Namespace) *Collector {
// add machine cpus and memory info
c := &Collector{
ns: ns,
cgroups: make(map[string]cgroups.Cgroup),
}
c.metrics = append(c.metrics, pidMetrics...)
c.metrics = append(c.metrics, cpuMetrics...)
c.metrics = append(c.metrics, memoryMetrics...)
c.metrics = append(c.metrics, hugetlbMetrics...)
c.metrics = append(c.metrics, blkioMetrics...)
ns.Add(c)
return c
}
// Collector provides the ability to collect container stats and export
// them in the prometheus format
type Collector struct {
mu sync.RWMutex
cgroups map[string]cgroups.Cgroup
ns *metrics.Namespace
metrics []*metric
}
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
for _, m := range c.metrics {
ch <- m.desc(c.ns)
}
}
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
c.mu.RLock()
wg := &sync.WaitGroup{}
for id, cg := range c.cgroups {
wg.Add(1)
go c.collect(id, cg, ch, wg)
}
c.mu.RUnlock()
wg.Wait()
}
func (c *Collector) collect(id string, cg cgroups.Cgroup, ch chan<- prometheus.Metric, wg *sync.WaitGroup) {
defer wg.Done()
stats, err := cg.Stat(cgroups.IgnoreNotExist)
if err != nil {
logrus.WithError(err).Errorf("stat cgroup %s", id)
return
}
for _, m := range c.metrics {
m.collect(id, stats, c.ns, ch)
}
}
// Add adds the provided cgroup and id so that metrics are collected and exported
func (c *Collector) Add(id string, cg cgroups.Cgroup) error {
c.mu.Lock()
defer c.mu.Unlock()
if _, ok := c.cgroups[id]; ok {
return ErrAlreadyCollected
}
c.cgroups[id] = cg
return nil
}
// Get returns the cgroup that is being collected under the provided id
// returns ErrCgroupNotExists if the id is not being collected
func (c *Collector) Get(id string) (cgroups.Cgroup, error) {
c.mu.Lock()
defer c.mu.Unlock()
cg, ok := c.cgroups[id]
if !ok {
return nil, ErrCgroupNotExists
}
return cg, nil
}
// Remove removes the provided cgroup by id from the collector
func (c *Collector) Remove(id string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.cgroups, id)
}
func blkioValues(l []cgroups.BlkioEntry) []value {
var out []value
for _, e := range l {
out = append(out, value{
v: float64(e.Value),
l: []string{e.Op, e.Device, strconv.FormatUint(e.Major, 10), strconv.FormatUint(e.Minor, 10)},
})
}
return out
}

116
vendor/github.com/containerd/cgroups/prometheus/oom.go generated vendored Normal file
View File

@@ -0,0 +1,116 @@
package prometheus
import (
"sync"
"golang.org/x/sys/unix"
"github.com/Sirupsen/logrus"
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
)
func NewOOMCollector(ns *metrics.Namespace) (*OOMCollector, error) {
fd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if err != nil {
return nil, err
}
c := &OOMCollector{
fd: fd,
memoryOOM: ns.NewLabeledGauge("memory_oom", "The number of times a container received an oom event", metrics.Total, "id"),
set: make(map[uintptr]*oom),
}
go c.start()
return c, nil
}
type OOMCollector struct {
mu sync.Mutex
memoryOOM metrics.LabeledGauge
fd int
set map[uintptr]*oom
}
type oom struct {
id string
c cgroups.Cgroup
triggers []Trigger
}
func (o *OOMCollector) Add(id string, cg cgroups.Cgroup, triggers ...Trigger) error {
o.mu.Lock()
defer o.mu.Unlock()
fd, err := cg.OOMEventFD()
if err != nil {
return err
}
o.set[fd] = &oom{
id: id,
c: cg,
triggers: triggers,
}
// set the gauge's default value
o.memoryOOM.WithValues(id).Set(0)
event := unix.EpollEvent{
Fd: int32(fd),
Events: unix.EPOLLHUP | unix.EPOLLIN | unix.EPOLLERR,
}
if err := unix.EpollCtl(o.fd, unix.EPOLL_CTL_ADD, int(fd), &event); err != nil {
return err
}
return nil
}
// Close closes the epoll fd
func (o *OOMCollector) Close() error {
return unix.Close(int(o.fd))
}
func (o *OOMCollector) start() {
var events [128]unix.EpollEvent
for {
n, err := unix.EpollWait(o.fd, events[:], -1)
if err != nil {
if err == unix.EINTR {
continue
}
logrus.WithField("error", err).Fatal("cgroups: epoll wait")
}
for i := 0; i < n; i++ {
o.process(uintptr(events[i].Fd), events[i].Events)
}
}
}
func (o *OOMCollector) process(fd uintptr, event uint32) {
// make sure to always flush the fd
flush(fd)
o.mu.Lock()
info, ok := o.set[fd]
if !ok {
o.mu.Unlock()
return
}
o.mu.Unlock()
// if we received an event but it was caused by the cgroup being deleted and the fd
// being closed make sure we close our copy and remove the container from the set
if info.c.State() == cgroups.Deleted {
o.mu.Lock()
delete(o.set, fd)
o.mu.Unlock()
unix.Close(int(fd))
return
}
o.memoryOOM.WithValues(info.id).Inc(1)
for _, t := range info.triggers {
t(info.id, info.c)
}
}
func flush(fd uintptr) error {
buf := make([]byte, 8)
_, err := unix.Read(int(fd), buf)
return err
}

View File

@@ -0,0 +1,42 @@
package prometheus
import (
"github.com/containerd/cgroups"
metrics "github.com/docker/go-metrics"
"github.com/prometheus/client_golang/prometheus"
)
var pidMetrics = []*metric{
{
name: "pids",
help: "The limit to the number of pids allowed",
unit: metrics.Unit("limit"),
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Pids == nil {
return nil
}
return []value{
{
v: float64(stats.Pids.Limit),
},
}
},
},
{
name: "pids",
help: "The current number of pids",
unit: metrics.Unit("current"),
vt: prometheus.GaugeValue,
getValues: func(stats *cgroups.Stats) []value {
if stats.Pids == nil {
return nil
}
return []value{
{
v: float64(stats.Pids.Current),
},
}
},
},
}

12
vendor/github.com/containerd/cgroups/state.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
package cgroups
// State is a type that represents the state of the current cgroup
type State string
const (
Unknown State = ""
Thawed State = "thawed"
Frozen State = "frozen"
Freezing State = "freezing"
Deleted State = "deleted"
)

109
vendor/github.com/containerd/cgroups/stats.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
package cgroups
import "sync"
type Stats struct {
cpuMu sync.Mutex
Hugetlb map[string]HugetlbStat
Pids *PidsStat
Cpu *CpuStat
Memory *MemoryStat
Blkio *BlkioStat
}
type HugetlbStat struct {
Usage uint64
Max uint64
Failcnt uint64
}
type PidsStat struct {
Current uint64
Limit uint64
}
type CpuStat struct {
Usage CpuUsage
Throttling Throttle
}
type CpuUsage struct {
// Units: nanoseconds.
Total uint64
PerCpu []uint64
Kernel uint64
User uint64
}
type Throttle struct {
Periods uint64
ThrottledPeriods uint64
ThrottledTime uint64
}
type MemoryStat struct {
Cache uint64
RSS uint64
RSSHuge uint64
MappedFile uint64
Dirty uint64
Writeback uint64
PgPgIn uint64
PgPgOut uint64
PgFault uint64
PgMajFault uint64
InactiveAnon uint64
ActiveAnon uint64
InactiveFile uint64
ActiveFile uint64
Unevictable uint64
HierarchicalMemoryLimit uint64
HierarchicalSwapLimit uint64
TotalCache uint64
TotalRSS uint64
TotalRSSHuge uint64
TotalMappedFile uint64
TotalDirty uint64
TotalWriteback uint64
TotalPgPgIn uint64
TotalPgPgOut uint64
TotalPgFault uint64
TotalPgMajFault uint64
TotalInactiveAnon uint64
TotalActiveAnon uint64
TotalInactiveFile uint64
TotalActiveFile uint64
TotalUnevictable uint64
Usage MemoryEntry
Swap MemoryEntry
Kernel MemoryEntry
KernelTCP MemoryEntry
}
type MemoryEntry struct {
Limit uint64
Usage uint64
Max uint64
Failcnt uint64
}
type BlkioStat struct {
IoServiceBytesRecursive []BlkioEntry
IoServicedRecursive []BlkioEntry
IoQueuedRecursive []BlkioEntry
IoServiceTimeRecursive []BlkioEntry
IoWaitTimeRecursive []BlkioEntry
IoMergedRecursive []BlkioEntry
IoTimeRecursive []BlkioEntry
SectorsRecursive []BlkioEntry
}
type BlkioEntry struct {
Op string
Device string
Major uint64
Minor uint64
Value uint64
}

94
vendor/github.com/containerd/cgroups/subsystem.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
package cgroups
import (
"fmt"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// Name is a typed name for a cgroup subsystem
type Name string
const (
Devices Name = "devices"
Hugetlb Name = "hugetlb"
Freezer Name = "freezer"
Pids Name = "pids"
NetCLS Name = "net_cls"
NetPrio Name = "net_prio"
PerfEvent Name = "perf_event"
Cpuset Name = "cpuset"
Cpu Name = "cpu"
Cpuacct Name = "cpuacct"
Memory Name = "memory"
Blkio Name = "blkio"
)
// Subsystems returns a complete list of the default cgroups
// avaliable on most linux systems
func Subsystems() []Name {
n := []Name{
Hugetlb,
Freezer,
Pids,
NetCLS,
NetPrio,
PerfEvent,
Cpuset,
Cpu,
Cpuacct,
Memory,
Blkio,
}
if !isUserNS {
n = append(n, Devices)
}
return n
}
type Subsystem interface {
Name() Name
}
type pather interface {
Subsystem
Path(path string) string
}
type creator interface {
Subsystem
Create(path string, resources *specs.LinuxResources) error
}
type deleter interface {
Subsystem
Delete(path string) error
}
type stater interface {
Subsystem
Stat(path string, stats *Stats) error
}
type updater interface {
Subsystem
Update(path string, resources *specs.LinuxResources) error
}
// SingleSubsystem returns a single cgroup subsystem within the base Hierarchy
func SingleSubsystem(baseHierarchy Hierarchy, subsystem Name) Hierarchy {
return func() ([]Subsystem, error) {
subsystems, err := baseHierarchy()
if err != nil {
return nil, err
}
for _, s := range subsystems {
if s.Name() == subsystem {
return []Subsystem{
s,
}, nil
}
}
return nil, fmt.Errorf("unable to find subsystem %s", subsystem)
}
}

101
vendor/github.com/containerd/cgroups/systemd.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package cgroups
import (
"fmt"
"path/filepath"
"strings"
"sync"
systemdDbus "github.com/coreos/go-systemd/dbus"
"github.com/godbus/dbus"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
const (
SystemdDbus Name = "systemd"
defaultSlice = "system.slice"
)
func Systemd() ([]Subsystem, error) {
root, err := v1MountPoint()
if err != nil {
return nil, err
}
defaultSubsystems, err := defaults(root)
if err != nil {
return nil, err
}
s, err := NewSystemd(root)
if err != nil {
return nil, err
}
// make sure the systemd controller is added first
return append([]Subsystem{s}, defaultSubsystems...), nil
}
func Slice(slice, name string) Path {
if slice == "" {
slice = defaultSlice
}
return func(subsystem Name) (string, error) {
return filepath.Join(slice, unitName(name)), nil
}
}
func NewSystemd(root string) (*SystemdController, error) {
conn, err := systemdDbus.New()
if err != nil {
return nil, err
}
return &SystemdController{
root: root,
conn: conn,
}, nil
}
type SystemdController struct {
mu sync.Mutex
conn *systemdDbus.Conn
root string
}
func (s *SystemdController) Name() Name {
return SystemdDbus
}
func (s *SystemdController) Create(path string, resources *specs.LinuxResources) error {
slice, name := splitName(path)
properties := []systemdDbus.Property{
systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
systemdDbus.PropWants(slice),
newProperty("DefaultDependencies", false),
newProperty("Delegate", true),
newProperty("MemoryAccounting", true),
newProperty("CPUAccounting", true),
newProperty("BlockIOAccounting", true),
}
_, err := s.conn.StartTransientUnit(name, "replace", properties, nil)
return err
}
func (s *SystemdController) Delete(path string) error {
_, name := splitName(path)
_, err := s.conn.StopUnit(name, "replace", nil)
return err
}
func newProperty(name string, units interface{}) systemdDbus.Property {
return systemdDbus.Property{
Name: name,
Value: dbus.MakeVariant(units),
}
}
func unitName(name string) string {
return fmt.Sprintf("%s.slice", name)
}
func splitName(path string) (slice string, unit string) {
slice, unit = filepath.Split(path)
return strings.TrimSuffix(slice, "/"), unit
}

10
vendor/github.com/containerd/cgroups/ticks.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package cgroups
/*
#include <unistd.h>
*/
import "C"
func getClockTicks() uint64 {
return uint64(C.sysconf(C._SC_CLK_TCK))
}

274
vendor/github.com/containerd/cgroups/utils.go generated vendored Normal file
View File

@@ -0,0 +1,274 @@
package cgroups
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
units "github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
var isUserNS = runningInUserNS()
// runningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
func runningInUserNS() bool {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return false
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return false
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return false
}
return true
}
// defaults returns all known groups
func defaults(root string) ([]Subsystem, error) {
h, err := NewHugetlb(root)
if err != nil {
return nil, err
}
s := []Subsystem{
NewNamed(root, "systemd"),
h,
NewFreezer(root),
NewPids(root),
NewNetCls(root),
NewNetPrio(root),
NewPerfEvent(root),
NewCputset(root),
NewCpu(root),
NewCpuacct(root),
NewMemory(root),
NewBlkio(root),
}
// only add the devices cgroup if we are not in a user namespace
// because modifications are not allowed
if !isUserNS {
s = append(s, NewDevices(root))
}
return s, nil
}
// remove will remove a cgroup path handling EAGAIN and EBUSY errors and
// retrying the remove after a exp timeout
func remove(path string) error {
for i := 0; i < 5; i++ {
delay := 10 * time.Millisecond
if i != 0 {
time.Sleep(delay)
delay *= 2
}
if err := os.RemoveAll(path); err == nil {
return nil
}
}
return fmt.Errorf("cgroups: unable to remove path %q", path)
}
// readPids will read all the pids in a cgroup by the provided path
func readPids(path string, subsystem Name) ([]Process, error) {
f, err := os.Open(filepath.Join(path, cgroupProcs))
if err != nil {
return nil, err
}
defer f.Close()
var (
out []Process
s = bufio.NewScanner(f)
)
for s.Scan() {
if t := s.Text(); t != "" {
pid, err := strconv.Atoi(t)
if err != nil {
return nil, err
}
out = append(out, Process{
Pid: pid,
Subsystem: subsystem,
Path: path,
})
}
}
return out, nil
}
func hugePageSizes() ([]string, error) {
var (
pageSizes []string
sizeList = []string{"B", "kB", "MB", "GB", "TB", "PB"}
)
files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
if err != nil {
return nil, err
}
for _, st := range files {
nameArray := strings.Split(st.Name(), "-")
pageSize, err := units.RAMInBytes(nameArray[1])
if err != nil {
return nil, err
}
pageSizes = append(pageSizes, units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList))
}
return pageSizes, nil
}
func readUint(path string) (uint64, error) {
v, err := ioutil.ReadFile(path)
if err != nil {
return 0, err
}
return parseUint(strings.TrimSpace(string(v)), 10, 64)
}
func parseUint(s string, base, bitSize int) (uint64, error) {
v, err := strconv.ParseUint(s, base, bitSize)
if err != nil {
intValue, intErr := strconv.ParseInt(s, base, bitSize)
// 1. Handle negative values greater than MinInt64 (and)
// 2. Handle negative values lesser than MinInt64
if intErr == nil && intValue < 0 {
return 0, nil
} else if intErr != nil &&
intErr.(*strconv.NumError).Err == strconv.ErrRange &&
intValue < 0 {
return 0, nil
}
return 0, err
}
return v, nil
}
func parseKV(raw string) (string, uint64, error) {
parts := strings.Fields(raw)
switch len(parts) {
case 2:
v, err := parseUint(parts[1], 10, 64)
if err != nil {
return "", 0, err
}
return parts[0], v, nil
default:
return "", 0, ErrInvalidFormat
}
}
func parseCgroupFile(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return parseCgroupFromReader(f)
}
func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
var (
cgroups = make(map[string]string)
s = bufio.NewScanner(r)
)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
var (
text = s.Text()
parts = strings.SplitN(text, ":", 3)
)
if len(parts) < 3 {
return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text)
}
for _, subs := range strings.Split(parts[1], ",") {
cgroups[subs] = parts[2]
}
}
return cgroups, nil
}
func getCgroupDestination(subsystem string) (string, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", err
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
return "", err
}
fields := strings.Fields(s.Text())
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[3], nil
}
}
}
return "", ErrNoCgroupMountDestination
}
func pathers(subystems []Subsystem) []pather {
var out []pather
for _, s := range subystems {
if p, ok := s.(pather); ok {
out = append(out, p)
}
}
return out
}
func initializeSubsystem(s Subsystem, path Path, resources *specs.LinuxResources) error {
if c, ok := s.(creator); ok {
p, err := path(s.Name())
if err != nil {
return err
}
if err := c.Create(p, resources); err != nil {
return err
}
} else if c, ok := s.(pather); ok {
p, err := path(s.Name())
if err != nil {
return err
}
// do the default create if the group does not have a custom one
if err := os.MkdirAll(c.Path(p), defaultDirPerm); err != nil {
return err
}
}
return nil
}
func cleanPath(path string) string {
if path == "" {
return ""
}
path = filepath.Clean(path)
if !filepath.IsAbs(path) {
path, _ = filepath.Rel(string(os.PathSeparator), filepath.Clean(string(os.PathSeparator)+path))
}
return filepath.Clean(path)
}

65
vendor/github.com/containerd/cgroups/v1.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
package cgroups
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)
// V1 returns all the groups in the default cgroups mountpoint in a single hierarchy
func V1() ([]Subsystem, error) {
root, err := v1MountPoint()
if err != nil {
return nil, err
}
subsystems, err := defaults(root)
if err != nil {
return nil, err
}
var enabled []Subsystem
for _, s := range pathers(subsystems) {
// check and remove the default groups that do not exist
if _, err := os.Lstat(s.Path("/")); err == nil {
enabled = append(enabled, s)
}
}
return enabled, nil
}
// v1MountPoint returns the mount point where the cgroup
// mountpoints are mounted in a single hiearchy
func v1MountPoint() (string, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if err := scanner.Err(); err != nil {
return "", err
}
var (
text = scanner.Text()
fields = strings.Split(text, " ")
// safe as mountinfo encodes mountpoints with spaces as \040.
index = strings.Index(text, " - ")
postSeparatorFields = strings.Fields(text[index+3:])
numPostFields = len(postSeparatorFields)
)
// this is an error as we can't detect if the mount is for "cgroup"
if numPostFields == 0 {
return "", fmt.Errorf("Found no fields post '-' in %q", text)
}
if postSeparatorFields[0] == "cgroup" {
// check that the mount is properly formated.
if numPostFields < 3 {
return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}
return filepath.Dir(fields[4]), nil
}
}
return "", ErrMountPointNotExist
}

24
vendor/github.com/containerd/console/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2017-infinity Michael Crosby. crosbymichael@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

44
vendor/github.com/containerd/console/README.md generated vendored Normal file
View File

@@ -0,0 +1,44 @@
# console
[![Build Status](https://travis-ci.org/containerd/console.svg?branch=master)](https://travis-ci.org/containerd/console)
Golang package for dealing with consoles. Light on deps and a simple API.
## Modifying the current process
```go
current := console.Current()
defer current.Reset()
if err := current.SetRaw(); err != nil {
}
ws, err := current.Size()
current.Resize(ws)
```
## License - MIT
Copyright (c) 2017-infinity Michael Crosby. crosbymichael@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

52
vendor/github.com/containerd/console/console.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package console
import (
"errors"
"io"
"os"
)
var ErrNotAConsole = errors.New("provided file is not a console")
type Console interface {
io.Reader
io.Writer
io.Closer
// Resize resizes the console to the provided window size
Resize(WinSize) error
// ResizeFrom resizes the calling console to the size of the
// provided console
ResizeFrom(Console) error
// SetRaw sets the console in raw mode
SetRaw() error
// DisableEcho disables echo on the console
DisableEcho() error
// Reset restores the console to its orignal state
Reset() error
// Size returns the window size of the console
Size() (WinSize, error)
}
// WinSize specifies the window size of the console
type WinSize struct {
// Height of the console
Height uint16
// Width of the console
Width uint16
x uint16
y uint16
}
// Current returns the current processes console
func Current() Console {
return newMaster(os.Stdin)
}
// ConsoleFromFile returns a console using the provided file
func ConsoleFromFile(f *os.File) (Console, error) {
if err := checkConsole(f); err != nil {
return nil, err
}
return newMaster(f), nil
}

133
vendor/github.com/containerd/console/console_unix.go generated vendored Normal file
View File

@@ -0,0 +1,133 @@
// +build darwin freebsd linux
package console
// #include <termios.h>
import "C"
import (
"os"
"unsafe"
"golang.org/x/sys/unix"
)
// NewPty creates a new pty pair
// The master is returned as the first console and a string
// with the path to the pty slave is returned as the second
func NewPty() (Console, string, error) {
f, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
if err != nil {
return nil, "", err
}
if err := saneTerminal(f); err != nil {
return nil, "", err
}
slave, err := ptsname(f)
if err != nil {
return nil, "", err
}
if err := unlockpt(f); err != nil {
return nil, "", err
}
return &master{
f: f,
}, slave, nil
}
type master struct {
f *os.File
original *unix.Termios
}
func (m *master) Read(b []byte) (int, error) {
return m.f.Read(b)
}
func (m *master) Write(b []byte) (int, error) {
return m.f.Write(b)
}
func (m *master) Close() error {
return m.f.Close()
}
func (m *master) Resize(ws WinSize) error {
return ioctl(
m.f.Fd(),
uintptr(unix.TIOCSWINSZ),
uintptr(unsafe.Pointer(&ws)),
)
}
func (m *master) ResizeFrom(c Console) error {
ws, err := c.Size()
if err != nil {
return err
}
return m.Resize(ws)
}
func (m *master) Reset() error {
if m.original == nil {
return nil
}
return tcset(m.f.Fd(), m.original)
}
func (m *master) getCurrent() (unix.Termios, error) {
var termios unix.Termios
if err := tcget(m.f.Fd(), &termios); err != nil {
return unix.Termios{}, err
}
if m.original == nil {
m.original = &termios
}
return termios, nil
}
func (m *master) SetRaw() error {
rawState, err := m.getCurrent()
if err != nil {
return err
}
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&rawState)))
rawState.Oflag = rawState.Oflag | C.OPOST
return tcset(m.f.Fd(), &rawState)
}
func (m *master) DisableEcho() error {
rawState, err := m.getCurrent()
if err != nil {
return err
}
rawState.Lflag = rawState.Lflag &^ unix.ECHO
return tcset(m.f.Fd(), &rawState)
}
func (m *master) Size() (WinSize, error) {
var ws WinSize
if err := ioctl(
m.f.Fd(),
uintptr(unix.TIOCGWINSZ),
uintptr(unsafe.Pointer(&ws)),
); err != nil {
return ws, err
}
return ws, nil
}
// checkConsole checks if the provided file is a console
func checkConsole(f *os.File) error {
var termios unix.Termios
if tcget(f.Fd(), &termios) != nil {
return ErrNotAConsole
}
return nil
}
func newMaster(f *os.File) Console {
return &master{
f: f,
}
}

199
vendor/github.com/containerd/console/console_windows.go generated vendored Normal file
View File

@@ -0,0 +1,199 @@
package console
import (
"fmt"
"os"
"github.com/Azure/go-ansiterm/winterm"
"github.com/pkg/errors"
)
const (
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
enableVirtualTerminalInput = 0x0200
enableVirtualTerminalProcessing = 0x0004
disableNewlineAutoReturn = 0x0008
)
var (
vtInputSupported bool
ErrNotImplemented = errors.New("not implemented")
)
func (m *master) initStdios() {
m.in = os.Stdin.Fd()
if mode, err := winterm.GetConsoleMode(m.in); err == nil {
m.inMode = mode
// Validate that enableVirtualTerminalInput is supported, but do not set it.
if err = winterm.SetConsoleMode(m.in, mode|enableVirtualTerminalInput); err == nil {
vtInputSupported = true
}
// Unconditionally set the console mode back even on failure because SetConsoleMode
// remembers invalid bits on input handles.
winterm.SetConsoleMode(m.in, mode)
} else {
fmt.Printf("failed to get console mode for stdin: %v\n", err)
}
m.out = os.Stdout.Fd()
if mode, err := winterm.GetConsoleMode(m.out); err == nil {
m.outMode = mode
if err := winterm.SetConsoleMode(m.out, mode|enableVirtualTerminalProcessing); err == nil {
m.outMode |= enableVirtualTerminalProcessing
} else {
winterm.SetConsoleMode(m.out, m.outMode)
}
} else {
fmt.Printf("failed to get console mode for stdout: %v\n", err)
}
m.err = os.Stderr.Fd()
if mode, err := winterm.GetConsoleMode(m.err); err == nil {
m.errMode = mode
if err := winterm.SetConsoleMode(m.err, mode|enableVirtualTerminalProcessing); err == nil {
m.errMode |= enableVirtualTerminalProcessing
} else {
winterm.SetConsoleMode(m.err, m.errMode)
}
} else {
fmt.Printf("failed to get console mode for stderr: %v\n", err)
}
}
type master struct {
in uintptr
inMode uint32
out uintptr
outMode uint32
err uintptr
errMode uint32
}
func (m *master) SetRaw() error {
if err := makeInputRaw(m.in, m.inMode); err != nil {
return err
}
// Set StdOut and StdErr to raw mode, we ignore failures since
// disableNewlineAutoReturn might not be supported on this version of
// Windows.
winterm.SetConsoleMode(m.out, m.outMode|disableNewlineAutoReturn)
winterm.SetConsoleMode(m.err, m.errMode|disableNewlineAutoReturn)
return nil
}
func (m *master) Reset() error {
for _, s := range []struct {
fd uintptr
mode uint32
}{
{m.in, m.inMode},
{m.out, m.outMode},
{m.err, m.errMode},
} {
if err := winterm.SetConsoleMode(s.fd, s.mode); err != nil {
return errors.Wrap(err, "unable to restore console mode")
}
}
return nil
}
func (m *master) Size() (WinSize, error) {
info, err := winterm.GetConsoleScreenBufferInfo(m.out)
if err != nil {
return WinSize{}, errors.Wrap(err, "unable to get console info")
}
winsize := WinSize{
Width: uint16(info.Window.Right - info.Window.Left + 1),
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
}
return winsize, nil
}
func (m *master) Resize(ws WinSize) error {
return ErrNotImplemented
}
func (m *master) ResizeFrom(c Console) error {
return ErrNotImplemented
}
func (m *master) DisableEcho() error {
mode := m.inMode &^ winterm.ENABLE_ECHO_INPUT
mode |= winterm.ENABLE_PROCESSED_INPUT
mode |= winterm.ENABLE_LINE_INPUT
if err := winterm.SetConsoleMode(m.in, mode); err != nil {
return errors.Wrap(err, "unable to set console to disable echo")
}
return nil
}
func (m *master) Close() error {
return nil
}
func (m *master) Read(b []byte) (int, error) {
panic("not implemented on windows")
}
func (m *master) Write(b []byte) (int, error) {
panic("not implemented on windows")
}
// makeInputRaw puts the terminal (Windows Console) connected to the given
// file descriptor into raw mode
func makeInputRaw(fd uintptr, mode uint32) error {
// See
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
// Disable these modes
mode &^= winterm.ENABLE_ECHO_INPUT
mode &^= winterm.ENABLE_LINE_INPUT
mode &^= winterm.ENABLE_MOUSE_INPUT
mode &^= winterm.ENABLE_WINDOW_INPUT
mode &^= winterm.ENABLE_PROCESSED_INPUT
// Enable these modes
mode |= winterm.ENABLE_EXTENDED_FLAGS
mode |= winterm.ENABLE_INSERT_MODE
mode |= winterm.ENABLE_QUICK_EDIT_MODE
if vtInputSupported {
mode |= enableVirtualTerminalInput
}
if err := winterm.SetConsoleMode(fd, mode); err != nil {
return errors.Wrap(err, "unable to set console to raw mode")
}
return nil
}
func checkConsole(f *os.File) error {
if _, err := winterm.GetConsoleMode(f.Fd()); err != nil {
return err
}
return nil
}
func newMaster(f *os.File) Console {
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
panic("creating a console from a file is not supported on windows")
}
m := &master{}
m.initStdios()
return m
}

51
vendor/github.com/containerd/console/tc_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package console
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
func tcget(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TIOCGETA, uintptr(unsafe.Pointer(p)))
}
func tcset(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TIOCSETA, uintptr(unsafe.Pointer(p)))
}
func ioctl(fd, flag, data uintptr) error {
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
var u int32
return ioctl(f.Fd(), unix.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
var n int32
if err := ioctl(f.Fd(), unix.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n))); err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}
func saneTerminal(f *os.File) error {
// Go doesn't have a wrapper for any of the termios ioctls.
var termios unix.Termios
if err := tcget(f.Fd(), &termios); err != nil {
return err
}
// Set -onlcr so we don't have to deal with \r.
termios.Oflag &^= unix.ONLCR
return tcset(f.Fd(), &termios)
}

51
vendor/github.com/containerd/console/tc_freebsd.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package console
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
func tcget(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TIOCGETA, uintptr(unsafe.Pointer(p)))
}
func tcset(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TIOCSETA, uintptr(unsafe.Pointer(p)))
}
func ioctl(fd, flag, data uintptr) error {
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
// This does not exist on FreeBSD, it does not allocate controlling terminals on open
func unlockpt(f *os.File) error {
return nil
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
var n int32
if err := ioctl(f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}
func saneTerminal(f *os.File) error {
// Go doesn't have a wrapper for any of the termios ioctls.
var termios unix.Termios
if err := tcget(f.Fd(), &termios); err != nil {
return err
}
// Set -onlcr so we don't have to deal with \r.
termios.Oflag &^= unix.ONLCR
return tcset(f.Fd(), &termios)
}

51
vendor/github.com/containerd/console/tc_linux.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package console
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
func tcget(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TCGETS, uintptr(unsafe.Pointer(p)))
}
func tcset(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TCSETS, uintptr(unsafe.Pointer(p)))
}
func ioctl(fd, flag, data uintptr) error {
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
var u int32
return ioctl(f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
var n int32
if err := ioctl(f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}
func saneTerminal(f *os.File) error {
// Go doesn't have a wrapper for any of the termios ioctls.
var termios unix.Termios
if err := tcget(f.Fd(), &termios); err != nil {
return err
}
// Set -onlcr so we don't have to deal with \r.
termios.Oflag &^= unix.ONLCR
return tcset(f.Fd(), &termios)
}

21
vendor/github.com/containerd/fifo/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT
Copyright (C) 2016 Tõnis Tiigi <tonistiigi@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

216
vendor/github.com/containerd/fifo/fifo.go generated vendored Normal file
View File

@@ -0,0 +1,216 @@
package fifo
import (
"io"
"os"
"runtime"
"sync"
"syscall"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
type fifo struct {
flag int
opened chan struct{}
closed chan struct{}
closing chan struct{}
err error
file *os.File
closingOnce sync.Once // close has been called
closedOnce sync.Once // fifo is closed
handle *handle
}
var leakCheckWg *sync.WaitGroup
// OpenFifo opens a fifo. Returns io.ReadWriteCloser.
// Context can be used to cancel this function until open(2) has not returned.
// Accepted flags:
// - syscall.O_CREAT - create new fifo if one doesn't exist
// - syscall.O_RDONLY - open fifo only from reader side
// - syscall.O_WRONLY - open fifo only from writer side
// - syscall.O_RDWR - open fifo from both sides, never block on syscall level
// - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the
// fifo isn't open. read/write will be connected after the actual fifo is
// open or after fifo is closed.
func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
if _, err := os.Stat(fn); err != nil {
if os.IsNotExist(err) && flag&syscall.O_CREAT != 0 {
if err := mkfifo(fn, uint32(perm&os.ModePerm)); err != nil && !os.IsExist(err) {
return nil, errors.Wrapf(err, "error creating fifo %v", fn)
}
} else {
return nil, err
}
}
block := flag&syscall.O_NONBLOCK == 0 || flag&syscall.O_RDWR != 0
flag &= ^syscall.O_CREAT
flag &= ^syscall.O_NONBLOCK
h, err := getHandle(fn)
if err != nil {
return nil, err
}
f := &fifo{
handle: h,
flag: flag,
opened: make(chan struct{}),
closed: make(chan struct{}),
closing: make(chan struct{}),
}
wg := leakCheckWg
if wg != nil {
wg.Add(2)
}
go func() {
if wg != nil {
defer wg.Done()
}
select {
case <-ctx.Done():
f.Close()
case <-f.opened:
case <-f.closed:
}
}()
go func() {
if wg != nil {
defer wg.Done()
}
var file *os.File
fn, err := h.Path()
if err == nil {
file, err = os.OpenFile(fn, flag, 0)
}
select {
case <-f.closing:
if err == nil {
select {
case <-ctx.Done():
err = ctx.Err()
default:
err = errors.Errorf("fifo %v was closed before opening", h.Name())
}
if file != nil {
file.Close()
}
}
default:
}
if err != nil {
f.closedOnce.Do(func() {
f.err = err
close(f.closed)
})
return
}
f.file = file
close(f.opened)
}()
if block {
select {
case <-f.opened:
case <-f.closed:
return nil, f.err
}
}
return f, nil
}
// Read from a fifo to a byte array.
func (f *fifo) Read(b []byte) (int, error) {
if f.flag&syscall.O_WRONLY > 0 {
return 0, errors.New("reading from write-only fifo")
}
select {
case <-f.opened:
return f.file.Read(b)
default:
}
select {
case <-f.opened:
return f.file.Read(b)
case <-f.closed:
return 0, errors.New("reading from a closed fifo")
}
}
// Write from byte array to a fifo.
func (f *fifo) Write(b []byte) (int, error) {
if f.flag&(syscall.O_WRONLY|syscall.O_RDWR) == 0 {
return 0, errors.New("writing to read-only fifo")
}
select {
case <-f.opened:
return f.file.Write(b)
default:
}
select {
case <-f.opened:
return f.file.Write(b)
case <-f.closed:
return 0, errors.New("writing to a closed fifo")
}
}
// Close the fifo. Next reads/writes will error. This method can also be used
// before open(2) has returned and fifo was never opened.
func (f *fifo) Close() (retErr error) {
for {
select {
case <-f.closed:
f.handle.Close()
return
default:
select {
case <-f.opened:
f.closedOnce.Do(func() {
retErr = f.file.Close()
f.err = retErr
close(f.closed)
})
default:
if f.flag&syscall.O_RDWR != 0 {
runtime.Gosched()
break
}
f.closingOnce.Do(func() {
close(f.closing)
})
reverseMode := syscall.O_WRONLY
if f.flag&syscall.O_WRONLY > 0 {
reverseMode = syscall.O_RDONLY
}
fn, err := f.handle.Path()
// if Close() is called concurrently(shouldn't) it may cause error
// because handle is closed
select {
case <-f.closed:
default:
if err != nil {
// Path has become invalid. We will leak a goroutine.
// This case should not happen in linux.
f.closedOnce.Do(func() {
f.err = err
close(f.closed)
})
<-f.closed
break
}
f, err := os.OpenFile(fn, reverseMode|syscall.O_NONBLOCK, 0)
if err == nil {
f.Close()
}
runtime.Gosched()
}
}
}
}
}

76
vendor/github.com/containerd/fifo/handle_linux.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
// +build linux
package fifo
import (
"fmt"
"os"
"sync"
"syscall"
"github.com/pkg/errors"
)
const O_PATH = 010000000
type handle struct {
f *os.File
dev uint64
ino uint64
closeOnce sync.Once
name string
}
func getHandle(fn string) (*handle, error) {
f, err := os.OpenFile(fn, O_PATH, 0)
if err != nil {
return nil, errors.Wrapf(err, "failed to open %v with O_PATH", fn)
}
var stat syscall.Stat_t
if err := syscall.Fstat(int(f.Fd()), &stat); err != nil {
f.Close()
return nil, errors.Wrapf(err, "failed to stat handle %v", f.Fd())
}
h := &handle{
f: f,
name: fn,
dev: uint64(stat.Dev),
ino: stat.Ino,
}
// check /proc just in case
if _, err := os.Stat(h.procPath()); err != nil {
f.Close()
return nil, errors.Wrapf(err, "couldn't stat %v", h.procPath())
}
return h, nil
}
func (h *handle) procPath() string {
return fmt.Sprintf("/proc/self/fd/%d", h.f.Fd())
}
func (h *handle) Name() string {
return h.name
}
func (h *handle) Path() (string, error) {
var stat syscall.Stat_t
if err := syscall.Stat(h.procPath(), &stat); err != nil {
return "", errors.Wrapf(err, "path %v could not be statted", h.procPath())
}
if uint64(stat.Dev) != h.dev || stat.Ino != h.ino {
return "", errors.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino)
}
return h.procPath(), nil
}
func (h *handle) Close() error {
h.closeOnce.Do(func() {
h.f.Close()
})
return nil
}

49
vendor/github.com/containerd/fifo/handle_nolinux.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
// +build !linux
package fifo
import (
"syscall"
"github.com/pkg/errors"
)
type handle struct {
fn string
dev uint64
ino uint64
}
func getHandle(fn string) (*handle, error) {
var stat syscall.Stat_t
if err := syscall.Stat(fn, &stat); err != nil {
return nil, errors.Wrapf(err, "failed to stat %v", fn)
}
h := &handle{
fn: fn,
dev: uint64(stat.Dev),
ino: uint64(stat.Ino),
}
return h, nil
}
func (h *handle) Path() (string, error) {
var stat syscall.Stat_t
if err := syscall.Stat(h.fn, &stat); err != nil {
return "", errors.Wrapf(err, "path %v could not be statted", h.fn)
}
if uint64(stat.Dev) != h.dev || uint64(stat.Ino) != h.ino {
return "", errors.Errorf("failed to verify handle %v/%v %v/%v for %v", stat.Dev, h.dev, stat.Ino, h.ino, h.fn)
}
return h.fn, nil
}
func (h *handle) Name() string {
return h.fn
}
func (h *handle) Close() error {
return nil
}

View File

@@ -0,0 +1,9 @@
// +build !solaris
package fifo
import "syscall"
func mkfifo(path string, mode uint32) (err error) {
return syscall.Mkfifo(path, mode)
}

11
vendor/github.com/containerd/fifo/mkfifo_solaris.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// +build solaris
package fifo
import (
"golang.org/x/sys/unix"
)
func mkfifo(path string, mode uint32) (err error) {
return unix.Mkfifo(path, mode)
}

32
vendor/github.com/containerd/fifo/readme.md generated vendored Normal file
View File

@@ -0,0 +1,32 @@
### fifo
[![Build Status](https://travis-ci.org/containerd/fifo.svg?branch=master)](https://travis-ci.org/containerd/fifo)
Go package for handling fifos in a sane way.
```
// OpenFifo opens a fifo. Returns io.ReadWriteCloser.
// Context can be used to cancel this function until open(2) has not returned.
// Accepted flags:
// - syscall.O_CREAT - create new fifo if one doesn't exist
// - syscall.O_RDONLY - open fifo only from reader side
// - syscall.O_WRONLY - open fifo only from writer side
// - syscall.O_RDWR - open fifo from both sides, never block on syscall level
// - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the
// fifo isn't open. read/write will be connected after the actual fifo is
// open or after fifo is closed.
func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
// Read from a fifo to a byte array.
func (f *fifo) Read(b []byte) (int, error)
// Write from byte array to a fifo.
func (f *fifo) Write(b []byte) (int, error)
// Close the fifo. Next reads/writes will error. This method can also be used
// before open(2) has returned and fifo was never opened.
func (f *fifo) Close() error
```

24
vendor/github.com/containerd/go-runc/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2016 Michael Crosby. crosbymichael@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

42
vendor/github.com/containerd/go-runc/README.md generated vendored Normal file
View File

@@ -0,0 +1,42 @@
# go-runc
[![Build Status](https://travis-ci.org/containerd/go-runc.svg?branch=master)](https://travis-ci.org/containerd/go-runc)
This is a package for consuming the [runc](https://github.com/opencontainers/runc) binary in your Go applications.
It tries to expose all the settings and features of the runc CLI. If there is something missing then add it, its opensource!
This needs runc @ [a9610f2c0](https://github.com/opencontainers/runc/commit/a9610f2c0237d2636d05a031ec8659a70e75ffeb)
or greater.
## Docs
Docs can be found at [godoc.org](https://godoc.org/github.com/containerd/go-runc).
## LICENSE - MIT
Copyright (c) 2016 Michael Crosby. crosbymichael@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

98
vendor/github.com/containerd/go-runc/console.go generated vendored Normal file
View File

@@ -0,0 +1,98 @@
// +build cgo
package runc
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"github.com/containerd/console"
"github.com/opencontainers/runc/libcontainer/utils"
)
// NewConsoleSocket creates a new unix socket at the provided path to accept a
// pty master created by runc for use by the container
func NewConsoleSocket(path string) (*Socket, error) {
abs, err := filepath.Abs(path)
if err != nil {
return nil, err
}
l, err := net.Listen("unix", abs)
if err != nil {
return nil, err
}
return &Socket{
l: l,
path: abs,
}, nil
}
// NewTempConsoleSocket returns a temp console socket for use with a container
// On Close(), the socket is deleted
func NewTempConsoleSocket() (*Socket, error) {
dir, err := ioutil.TempDir("", "pty")
if err != nil {
return nil, err
}
abs, err := filepath.Abs(filepath.Join(dir, "pty.sock"))
if err != nil {
return nil, err
}
l, err := net.Listen("unix", abs)
if err != nil {
return nil, err
}
return &Socket{
l: l,
rmdir: true,
path: abs,
}, nil
}
// Socket is a unix socket that accepts the pty master created by runc
type Socket struct {
path string
rmdir bool
l net.Listener
}
// Path returns the path to the unix socket on disk
func (c *Socket) Path() string {
return c.path
}
// ReceiveMaster blocks until the socket receives the pty master
func (c *Socket) ReceiveMaster() (console.Console, error) {
conn, err := c.l.Accept()
if err != nil {
return nil, err
}
defer conn.Close()
unix, ok := conn.(*net.UnixConn)
if !ok {
return nil, fmt.Errorf("received connection which was not a unix socket")
}
sock, err := unix.File()
if err != nil {
return nil, err
}
f, err := utils.RecvFd(sock)
if err != nil {
return nil, err
}
return console.ConsoleFromFile(f)
}
// Close closes the unix socket
func (c *Socket) Close() error {
err := c.l.Close()
if c.rmdir {
if rerr := os.RemoveAll(filepath.Dir(c.path)); err == nil {
err = rerr
}
}
return err
}

14
vendor/github.com/containerd/go-runc/container.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
package runc
import "time"
// Container hold information for a runc container
type Container struct {
ID string `json:"id"`
Pid int `json:"pid"`
Status string `json:"status"`
Bundle string `json:"bundle"`
Rootfs string `json:"rootfs"`
Created time.Time `json:"created"`
Annotations map[string]string `json:"annotations"`
}

84
vendor/github.com/containerd/go-runc/events.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
package runc
type Event struct {
// Type are the event type generated by runc
// If the type is "error" then check the Err field on the event for
// the actual error
Type string `json:"type"`
ID string `json:"id"`
Stats *Stats `json:"data,omitempty"`
// Err has a read error if we were unable to decode the event from runc
Err error `json:"-"`
}
type Stats struct {
Cpu Cpu `json:"cpu"`
Memory Memory `json:"memory"`
Pids Pids `json:"pids"`
Blkio Blkio `json:"blkio"`
Hugetlb map[string]Hugetlb `json:"hugetlb"`
}
type Hugetlb struct {
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
type BlkioEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,omitempty"`
Value uint64 `json:"value,omitempty"`
}
type Blkio struct {
IoServiceBytesRecursive []BlkioEntry `json:"ioServiceBytesRecursive,omitempty"`
IoServicedRecursive []BlkioEntry `json:"ioServicedRecursive,omitempty"`
IoQueuedRecursive []BlkioEntry `json:"ioQueueRecursive,omitempty"`
IoServiceTimeRecursive []BlkioEntry `json:"ioServiceTimeRecursive,omitempty"`
IoWaitTimeRecursive []BlkioEntry `json:"ioWaitTimeRecursive,omitempty"`
IoMergedRecursive []BlkioEntry `json:"ioMergedRecursive,omitempty"`
IoTimeRecursive []BlkioEntry `json:"ioTimeRecursive,omitempty"`
SectorsRecursive []BlkioEntry `json:"sectorsRecursive,omitempty"`
}
type Pids struct {
Current uint64 `json:"current,omitempty"`
Limit uint64 `json:"limit,omitempty"`
}
type Throttling struct {
Periods uint64 `json:"periods,omitempty"`
ThrottledPeriods uint64 `json:"throttledPeriods,omitempty"`
ThrottledTime uint64 `json:"throttledTime,omitempty"`
}
type CpuUsage struct {
// Units: nanoseconds.
Total uint64 `json:"total,omitempty"`
Percpu []uint64 `json:"percpu,omitempty"`
Kernel uint64 `json:"kernel"`
User uint64 `json:"user"`
}
type Cpu struct {
Usage CpuUsage `json:"usage,omitempty"`
Throttling Throttling `json:"throttling,omitempty"`
}
type MemoryEntry struct {
Limit uint64 `json:"limit"`
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
type Memory struct {
Cache uint64 `json:"cache,omitempty"`
Usage MemoryEntry `json:"usage,omitempty"`
Swap MemoryEntry `json:"swap,omitempty"`
Kernel MemoryEntry `json:"kernel,omitempty"`
KernelTCP MemoryEntry `json:"kernelTCP,omitempty"`
Raw map[string]uint64 `json:"raw,omitempty"`
}

165
vendor/github.com/containerd/go-runc/io.go generated vendored Normal file
View File

@@ -0,0 +1,165 @@
package runc
import (
"io"
"os"
"os/exec"
"golang.org/x/sys/unix"
)
type IO interface {
io.Closer
Stdin() io.WriteCloser
Stdout() io.ReadCloser
Stderr() io.ReadCloser
Set(*exec.Cmd)
}
type StartCloser interface {
CloseAfterStart() error
}
// NewPipeIO creates pipe pairs to be used with runc
func NewPipeIO(uid, gid int) (i IO, err error) {
var pipes []*pipe
// cleanup in case of an error
defer func() {
if err != nil {
for _, p := range pipes {
p.Close()
}
}
}()
stdin, err := newPipe(uid, gid)
if err != nil {
return nil, err
}
pipes = append(pipes, stdin)
stdout, err := newPipe(uid, gid)
if err != nil {
return nil, err
}
pipes = append(pipes, stdout)
stderr, err := newPipe(uid, gid)
if err != nil {
return nil, err
}
pipes = append(pipes, stderr)
return &pipeIO{
in: stdin,
out: stdout,
err: stderr,
}, nil
}
func newPipe(uid, gid int) (*pipe, error) {
r, w, err := os.Pipe()
if err != nil {
return nil, err
}
if err := unix.Fchown(int(r.Fd()), uid, gid); err != nil {
return nil, err
}
if err := unix.Fchown(int(w.Fd()), uid, gid); err != nil {
return nil, err
}
return &pipe{
r: r,
w: w,
}, nil
}
type pipe struct {
r *os.File
w *os.File
}
func (p *pipe) Close() error {
err := p.r.Close()
if werr := p.w.Close(); err == nil {
err = werr
}
return err
}
type pipeIO struct {
in *pipe
out *pipe
err *pipe
}
func (i *pipeIO) Stdin() io.WriteCloser {
return i.in.w
}
func (i *pipeIO) Stdout() io.ReadCloser {
return i.out.r
}
func (i *pipeIO) Stderr() io.ReadCloser {
return i.err.r
}
func (i *pipeIO) Close() error {
var err error
for _, v := range []*pipe{
i.in,
i.out,
i.err,
} {
if cerr := v.Close(); err == nil {
err = cerr
}
}
return err
}
func (i *pipeIO) CloseAfterStart() error {
for _, f := range []*os.File{
i.out.w,
i.err.w,
} {
f.Close()
}
return nil
}
// Set sets the io to the exec.Cmd
func (i *pipeIO) Set(cmd *exec.Cmd) {
cmd.Stdin = i.in.r
cmd.Stdout = i.out.w
cmd.Stderr = i.err.w
}
func NewSTDIO() (IO, error) {
return &stdio{}, nil
}
type stdio struct {
}
func (s *stdio) Close() error {
return nil
}
func (s *stdio) Set(cmd *exec.Cmd) {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
func (s *stdio) Stdin() io.WriteCloser {
return os.Stdin
}
func (s *stdio) Stdout() io.ReadCloser {
return os.Stdout
}
func (s *stdio) Stderr() io.ReadCloser {
return os.Stderr
}

50
vendor/github.com/containerd/go-runc/monitor.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
package runc
import (
"os/exec"
"syscall"
)
var Monitor ProcessMonitor = &defaultMonitor{}
// ProcessMonitor is an interface for process monitoring
//
// It allows daemons using go-runc to have a SIGCHLD handler
// to handle exits without introducing races between the handler
// and go's exec.Cmd
// These methods should match the methods exposed by exec.Cmd to provide
// a consistent experience for the caller
type ProcessMonitor interface {
Output(*exec.Cmd) ([]byte, error)
CombinedOutput(*exec.Cmd) ([]byte, error)
Run(*exec.Cmd) error
Start(*exec.Cmd) error
Wait(*exec.Cmd) (int, error)
}
type defaultMonitor struct {
}
func (m *defaultMonitor) Output(c *exec.Cmd) ([]byte, error) {
return c.Output()
}
func (m *defaultMonitor) CombinedOutput(c *exec.Cmd) ([]byte, error) {
return c.CombinedOutput()
}
func (m *defaultMonitor) Run(c *exec.Cmd) error {
return c.Run()
}
func (m *defaultMonitor) Start(c *exec.Cmd) error {
return c.Start()
}
func (m *defaultMonitor) Wait(c *exec.Cmd) (int, error) {
status, err := c.Process.Wait()
if err != nil {
return -1, err
}
return status.Sys().(syscall.WaitStatus).ExitStatus(), nil
}

553
vendor/github.com/containerd/go-runc/runc.go generated vendored Normal file
View File

@@ -0,0 +1,553 @@
package runc
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"time"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// Format is the type of log formatting options avaliable
type Format string
const (
none Format = ""
JSON Format = "json"
Text Format = "text"
// DefaultCommand is the default command for Runc
DefaultCommand = "runc"
)
// Runc is the client to the runc cli
type Runc struct {
//If command is empty, DefaultCommand is used
Command string
Root string
Debug bool
Log string
LogFormat Format
PdeathSignal syscall.Signal
Criu string
}
// List returns all containers created inside the provided runc root directory
func (r *Runc) List(context context.Context) ([]*Container, error) {
data, err := Monitor.Output(r.command(context, "list", "--format=json"))
if err != nil {
return nil, err
}
var out []*Container
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return out, nil
}
// State returns the state for the container provided by id
func (r *Runc) State(context context.Context, id string) (*Container, error) {
data, err := Monitor.CombinedOutput(r.command(context, "state", id))
if err != nil {
return nil, fmt.Errorf("%s: %s", err, data)
}
var c Container
if err := json.Unmarshal(data, &c); err != nil {
return nil, err
}
return &c, nil
}
type ConsoleSocket interface {
Path() string
}
type CreateOpts struct {
IO
// PidFile is a path to where a pid file should be created
PidFile string
ConsoleSocket ConsoleSocket
Detach bool
NoPivot bool
NoNewKeyring bool
ExtraFiles []*os.File
}
func (o *CreateOpts) args() (out []string, err error) {
if o.PidFile != "" {
abs, err := filepath.Abs(o.PidFile)
if err != nil {
return nil, err
}
out = append(out, "--pid-file", abs)
}
if o.ConsoleSocket != nil {
out = append(out, "--console-socket", o.ConsoleSocket.Path())
}
if o.NoPivot {
out = append(out, "--no-pivot")
}
if o.NoNewKeyring {
out = append(out, "--no-new-keyring")
}
if o.Detach {
out = append(out, "--detach")
}
if o.ExtraFiles != nil {
out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
}
return out, nil
}
// Create creates a new container and returns its pid if it was created successfully
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
args := []string{"create", "--bundle", bundle}
if opts != nil {
oargs, err := opts.args()
if err != nil {
return err
}
args = append(args, oargs...)
}
cmd := r.command(context, append(args, id)...)
if opts != nil && opts.IO != nil {
opts.Set(cmd)
}
cmd.ExtraFiles = opts.ExtraFiles
if cmd.Stdout == nil && cmd.Stderr == nil {
data, err := Monitor.CombinedOutput(cmd)
if err != nil {
return fmt.Errorf("%s: %s", err, data)
}
return nil
}
if err := Monitor.Start(cmd); err != nil {
return err
}
if opts != nil && opts.IO != nil {
if c, ok := opts.IO.(StartCloser); ok {
if err := c.CloseAfterStart(); err != nil {
return err
}
}
}
_, err := Monitor.Wait(cmd)
return err
}
// Start will start an already created container
func (r *Runc) Start(context context.Context, id string) error {
return r.runOrError(r.command(context, "start", id))
}
type ExecOpts struct {
IO
PidFile string
ConsoleSocket ConsoleSocket
Detach bool
}
func (o *ExecOpts) args() (out []string, err error) {
if o.ConsoleSocket != nil {
out = append(out, "--console-socket", o.ConsoleSocket.Path())
}
if o.Detach {
out = append(out, "--detach")
}
if o.PidFile != "" {
abs, err := filepath.Abs(o.PidFile)
if err != nil {
return nil, err
}
out = append(out, "--pid-file", abs)
}
return out, nil
}
// Exec executres and additional process inside the container based on a full
// OCI Process specification
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
f, err := ioutil.TempFile("", "runc-process")
if err != nil {
return err
}
defer os.Remove(f.Name())
err = json.NewEncoder(f).Encode(spec)
f.Close()
if err != nil {
return err
}
args := []string{"exec", "--process", f.Name()}
if opts != nil {
oargs, err := opts.args()
if err != nil {
return err
}
args = append(args, oargs...)
}
cmd := r.command(context, append(args, id)...)
if opts != nil && opts.IO != nil {
opts.Set(cmd)
}
if cmd.Stdout == nil && cmd.Stderr == nil {
data, err := Monitor.CombinedOutput(cmd)
if err != nil {
return fmt.Errorf("%s: %s", err, data)
}
return nil
}
if err := Monitor.Start(cmd); err != nil {
return err
}
if opts != nil && opts.IO != nil {
if c, ok := opts.IO.(StartCloser); ok {
if err := c.CloseAfterStart(); err != nil {
return err
}
}
}
_, err = Monitor.Wait(cmd)
return err
}
// Run runs the create, start, delete lifecycle of the container
// and returns its exit status after it has exited
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
args := []string{"run", "--bundle", bundle}
if opts != nil {
oargs, err := opts.args()
if err != nil {
return -1, err
}
args = append(args, oargs...)
}
cmd := r.command(context, append(args, id)...)
if opts != nil {
opts.Set(cmd)
}
if err := Monitor.Start(cmd); err != nil {
return -1, err
}
return Monitor.Wait(cmd)
}
// Delete deletes the container
func (r *Runc) Delete(context context.Context, id string) error {
return r.runOrError(r.command(context, "delete", id))
}
// KillOpts specifies options for killing a container and its processes
type KillOpts struct {
All bool
}
func (o *KillOpts) args() (out []string) {
if o.All {
out = append(out, "--all")
}
return out
}
// Kill sends the specified signal to the container
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
args := []string{
"kill",
}
if opts != nil {
args = append(args, opts.args()...)
}
return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
}
// Stats return the stats for a container like cpu, memory, and io
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
cmd := r.command(context, "events", "--stats", id)
rd, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
defer func() {
rd.Close()
Monitor.Wait(cmd)
}()
if err := Monitor.Start(cmd); err != nil {
return nil, err
}
var e Event
if err := json.NewDecoder(rd).Decode(&e); err != nil {
return nil, err
}
return e.Stats, nil
}
// Events returns an event stream from runc for a container with stats and OOM notifications
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
rd, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := Monitor.Start(cmd); err != nil {
rd.Close()
return nil, err
}
var (
dec = json.NewDecoder(rd)
c = make(chan *Event, 128)
)
go func() {
defer func() {
close(c)
rd.Close()
Monitor.Wait(cmd)
}()
for {
var e Event
if err := dec.Decode(&e); err != nil {
if err == io.EOF {
return
}
e = Event{
Type: "error",
Err: err,
}
}
c <- &e
}
}()
return c, nil
}
// Pause the container with the provided id
func (r *Runc) Pause(context context.Context, id string) error {
return r.runOrError(r.command(context, "pause", id))
}
// Resume the container with the provided id
func (r *Runc) Resume(context context.Context, id string) error {
return r.runOrError(r.command(context, "resume", id))
}
// Ps lists all the processes inside the container returning their pids
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
data, err := Monitor.CombinedOutput(r.command(context, "ps", "--format", "json", id))
if err != nil {
return nil, fmt.Errorf("%s: %s", err, data)
}
var pids []int
if err := json.Unmarshal(data, &pids); err != nil {
return nil, err
}
return pids, nil
}
type CheckpointOpts struct {
// ImagePath is the path for saving the criu image file
ImagePath string
// WorkDir is the working directory for criu
WorkDir string
// ParentPath is the path for previous image files from a pre-dump
ParentPath string
// AllowOpenTCP allows open tcp connections to be checkpointed
AllowOpenTCP bool
// AllowExternalUnixSockets allows external unix sockets to be checkpointed
AllowExternalUnixSockets bool
// AllowTerminal allows the terminal(pty) to be checkpointed with a container
AllowTerminal bool
// CriuPageServer is the address:port for the criu page server
CriuPageServer string
// FileLocks handle file locks held by the container
FileLocks bool
// Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
Cgroups CgroupMode
// EmptyNamespaces creates a namespace for the container but does not save its properties
// Provide the namespaces you wish to be checkpointed without their settings on restore
EmptyNamespaces []string
}
type CgroupMode string
const (
Soft CgroupMode = "soft"
Full CgroupMode = "full"
Strict CgroupMode = "strict"
)
func (o *CheckpointOpts) args() (out []string) {
if o.ImagePath != "" {
out = append(out, "--image-path", o.ImagePath)
}
if o.WorkDir != "" {
out = append(out, "--work-path", o.WorkDir)
}
if o.ParentPath != "" {
out = append(out, "--parent-path", o.ParentPath)
}
if o.AllowOpenTCP {
out = append(out, "--tcp-established")
}
if o.AllowExternalUnixSockets {
out = append(out, "--ext-unix-sk")
}
if o.AllowTerminal {
out = append(out, "--shell-job")
}
if o.CriuPageServer != "" {
out = append(out, "--page-server", o.CriuPageServer)
}
if o.FileLocks {
out = append(out, "--file-locks")
}
if string(o.Cgroups) != "" {
out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
}
for _, ns := range o.EmptyNamespaces {
out = append(out, "--empty-ns", ns)
}
return out
}
type CheckpointAction func([]string) []string
// LeaveRunning keeps the container running after the checkpoint has been completed
func LeaveRunning(args []string) []string {
return append(args, "--leave-running")
}
// PreDump allows a pre-dump of the checkpoint to be made and completed later
func PreDump(args []string) []string {
return append(args, "--pre-dump")
}
// Checkpoint allows you to checkpoint a container using criu
func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
args := []string{"checkpoint"}
if opts != nil {
args = append(args, opts.args()...)
}
for _, a := range actions {
args = a(args)
}
return r.runOrError(r.command(context, append(args, id)...))
}
type RestoreOpts struct {
CheckpointOpts
IO
Detach bool
PidFile string
NoSubreaper bool
NoPivot bool
}
func (o *RestoreOpts) args() ([]string, error) {
out := o.CheckpointOpts.args()
if o.Detach {
out = append(out, "--detach")
}
if o.PidFile != "" {
abs, err := filepath.Abs(o.PidFile)
if err != nil {
return nil, err
}
out = append(out, "--pid-file", abs)
}
if o.NoPivot {
out = append(out, "--no-pivot")
}
if o.NoSubreaper {
out = append(out, "-no-subreaper")
}
return out, nil
}
// Restore restores a container with the provide id from an existing checkpoint
func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
args := []string{"restore"}
if opts != nil {
oargs, err := opts.args()
if err != nil {
return -1, err
}
args = append(args, oargs...)
}
args = append(args, "--bundle", bundle)
cmd := r.command(context, append(args, id)...)
if opts != nil {
opts.Set(cmd)
}
if err := Monitor.Start(cmd); err != nil {
return -1, err
}
return Monitor.Wait(cmd)
}
// Update updates the current container with the provided resource spec
func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
buf := bytes.NewBuffer(nil)
if err := json.NewEncoder(buf).Encode(resources); err != nil {
return err
}
args := []string{"update", "--resources", "-", id}
cmd := r.command(context, args...)
cmd.Stdin = buf
return r.runOrError(cmd)
}
func (r *Runc) args() (out []string) {
if r.Root != "" {
out = append(out, "--root", r.Root)
}
if r.Debug {
out = append(out, "--debug")
}
if r.Log != "" {
out = append(out, "--log", r.Log)
}
if r.LogFormat != none {
out = append(out, "--log-format", string(r.LogFormat))
}
if r.Criu != "" {
out = append(out, "--criu", r.Criu)
}
return out
}
func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
command := r.Command
if command == "" {
command = DefaultCommand
}
cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
if r.PdeathSignal != 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: r.PdeathSignal,
}
}
return cmd
}
// runOrError will run the provided command. If an error is
// encountered and neither Stdout or Stderr was set the error and the
// stderr of the command will be returned in the format of <error>:
// <stderr>
func (r *Runc) runOrError(cmd *exec.Cmd) error {
if cmd.Stdout != nil || cmd.Stderr != nil {
return Monitor.Run(cmd)
}
data, err := Monitor.CombinedOutput(cmd)
if err != nil {
return fmt.Errorf("%s: %s", err, data)
}
return nil
}

28
vendor/github.com/containerd/go-runc/utils.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package runc
import (
"io/ioutil"
"strconv"
"syscall"
)
// ReadPidFile reads the pid file at the provided path and returns
// the pid or an error if the read and conversion is unsuccessful
func ReadPidFile(path string) (int, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return -1, err
}
return strconv.Atoi(string(data))
}
const exitSignalOffset = 128
// exitStatus returns the correct exit status for a process based on if it
// was signaled or exited cleanly
func exitStatus(status syscall.WaitStatus) int {
if status.Signaled() {
return exitSignalOffset + int(status.Signal())
}
return status.ExitStatus()
}