Update libcontainer dep to fb67bb80b4205bece36ff70

Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@docker.com> (github: crosbymichael)
This commit is contained in:
Michael Crosby 2014-07-14 12:30:19 -07:00
parent 7ebd49c49a
commit 3b4a1c54d8
50 changed files with 917 additions and 347 deletions

View File

@ -63,4 +63,4 @@ mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
clone git github.com/godbus/dbus v1
clone git github.com/coreos/go-systemd v2
clone git github.com/docker/libcontainer 53cfe0a1eba9145bf5329abbb52b0072ccab8a00
clone git github.com/docker/libcontainer fb67bb80b4205bece36ff7096ee745ab0cee7e06

View File

@ -1,12 +1,25 @@
language: go
# let us have pretty experimental Docker-based Travis workers
sudo: false
env:
- TRAVIS_GLOBAL_WTF=1
- GOOS=linux GOARCH=amd64
- GOOS=linux GOARCH=386
- GOOS=linux GOARCH=arm
- GOOS=darwin GOARCH=amd64
- GOOS=darwin GOARCH=386
- GOOS=freebsd GOARCH=amd64
install:
- go get -d ./...
- go get -d github.com/dotcloud/docker # just to be sure
- go get -d -v ./...
- go get -d -v github.com/dotcloud/docker # just to be sure
- DOCKER_PATH="${GOPATH%%:*}/src/github.com/dotcloud/docker"
- sed -i 's!dotcloud/docker!docker/libcontainer!' "$DOCKER_PATH/hack/make/.validate"
script:
- bash "$DOCKER_PATH/hack/make/validate-dco"
- bash "$DOCKER_PATH/hack/make/validate-gofmt"
- go test
- if [ "$TRAVIS_GLOBAL_WTF" ]; then bash "$DOCKER_PATH/hack/make/validate-dco"; fi
- if [ "$TRAVIS_GLOBAL_WTF" ]; then bash "$DOCKER_PATH/hack/make/validate-gofmt"; fi
- if [ -z "$TRAVIS_GLOBAL_WTF" ]; then go build -v ./...; fi
- if [ -z "$TRAVIS_GLOBAL_WTF" ]; then go test -v ./...; fi

View File

@ -1,23 +0,0 @@
package libcontainer
import (
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/network"
)
// Returns all available stats for the given container.
func GetStats(container *Config, state *State) (*ContainerStats, error) {
var containerStats ContainerStats
stats, err := fs.GetStats(container.Cgroups)
if err != nil {
return &containerStats, err
}
containerStats.CgroupStats = stats
networkStats, err := network.GetStats(&state.NetworkState)
if err != nil {
return &containerStats, err
}
containerStats.NetworkStats = networkStats
return &containerStats, nil
}

View File

@ -0,0 +1,34 @@
/*
Temporary API endpoint for libcontainer while the full API is finalized (api.go).
*/
package libcontainer
import (
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/network"
)
// TODO(vmarmol): Complete Stats() in final libcontainer API and move users to that.
// DEPRECATED: The below portions are only to be used during the transition to the official API.
// Returns all available stats for the given container.
func GetStats(container *Config, state *State) (*ContainerStats, error) {
var (
err error
stats = &ContainerStats{}
)
if systemd.UseSystemd() {
stats.CgroupStats, err = systemd.GetStats(container.Cgroups)
} else {
stats.CgroupStats, err = fs.GetStats(container.Cgroups)
}
if err != nil {
return stats, err
}
stats.NetworkStats, err = network.GetStats(&state.NetworkState)
return stats, err
}

View File

@ -0,0 +1,264 @@
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"syscall"
"time"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
)
var createCommand = cli.Command{
Name: "create",
Usage: "Create a cgroup container using the supplied configuration and initial process.",
Flags: []cli.Flag{
cli.StringFlag{"config, c", "cgroup.json", "path to container configuration (cgroups.Cgroup object)"},
cli.IntFlag{"pid, p", 0, "pid of the initial process in the container"},
},
Action: createAction,
}
var destroyCommand = cli.Command{
Name: "destroy",
Usage: "Destroy an existing cgroup container.",
Flags: []cli.Flag{
cli.StringFlag{"name, n", "", "container name"},
cli.StringFlag{"parent, p", "", "container parent"},
},
Action: destroyAction,
}
var statsCommand = cli.Command{
Name: "stats",
Usage: "Get stats for cgroup",
Flags: []cli.Flag{
cli.StringFlag{"name, n", "", "container name"},
cli.StringFlag{"parent, p", "", "container parent"},
},
Action: statsAction,
}
var pauseCommand = cli.Command{
Name: "pause",
Usage: "Pause cgroup",
Flags: []cli.Flag{
cli.StringFlag{"name, n", "", "container name"},
cli.StringFlag{"parent, p", "", "container parent"},
},
Action: pauseAction,
}
var resumeCommand = cli.Command{
Name: "resume",
Usage: "Resume a paused cgroup",
Flags: []cli.Flag{
cli.StringFlag{"name, n", "", "container name"},
cli.StringFlag{"parent, p", "", "container parent"},
},
Action: resumeAction,
}
var psCommand = cli.Command{
Name: "ps",
Usage: "Get list of pids for a cgroup",
Flags: []cli.Flag{
cli.StringFlag{"name, n", "", "container name"},
cli.StringFlag{"parent, p", "", "container parent"},
},
Action: psAction,
}
func getConfigFromFile(c *cli.Context) (*cgroups.Cgroup, error) {
f, err := os.Open(c.String("config"))
if err != nil {
return nil, err
}
defer f.Close()
var config *cgroups.Cgroup
if err := json.NewDecoder(f).Decode(&config); err != nil {
log.Fatal(err)
}
return config, nil
}
func openLog(name string) error {
f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755)
if err != nil {
return err
}
log.SetOutput(f)
return nil
}
func getConfig(context *cli.Context) (*cgroups.Cgroup, error) {
name := context.String("name")
if name == "" {
log.Fatal(fmt.Errorf("Missing container name"))
}
parent := context.String("parent")
return &cgroups.Cgroup{
Name: name,
Parent: parent,
}, nil
}
func killAll(config *cgroups.Cgroup) {
// We could use freezer here to prevent process spawning while we are trying
// to kill everything. But going with more portable solution of retrying for
// now.
pids := getPids(config)
retry := 10
for len(pids) != 0 || retry > 0 {
killPids(pids)
time.Sleep(100 * time.Millisecond)
retry--
pids = getPids(config)
}
if len(pids) != 0 {
log.Fatal(fmt.Errorf("Could not kill existing processes in the container."))
}
}
func getPids(config *cgroups.Cgroup) []int {
pids, err := fs.GetPids(config)
if err != nil {
log.Fatal(err)
}
return pids
}
func killPids(pids []int) {
for _, pid := range pids {
// pids might go away on their own. Ignore errors.
syscall.Kill(pid, syscall.SIGKILL)
}
}
func setFreezerState(context *cli.Context, state cgroups.FreezerState) {
config, err := getConfig(context)
if err != nil {
log.Fatal(err)
}
if systemd.UseSystemd() {
err = systemd.Freeze(config, state)
} else {
err = fs.Freeze(config, state)
}
if err != nil {
log.Fatal(err)
}
}
func createAction(context *cli.Context) {
config, err := getConfigFromFile(context)
if err != nil {
log.Fatal(err)
}
pid := context.Int("pid")
if pid <= 0 {
log.Fatal(fmt.Errorf("Invalid pid : %d", pid))
}
if systemd.UseSystemd() {
_, err := systemd.Apply(config, pid)
if err != nil {
log.Fatal(err)
}
} else {
_, err := fs.Apply(config, pid)
if err != nil {
log.Fatal(err)
}
}
}
func destroyAction(context *cli.Context) {
config, err := getConfig(context)
if err != nil {
log.Fatal(err)
}
killAll(config)
// Systemd will clean up cgroup state for empty container.
if !systemd.UseSystemd() {
err := fs.Cleanup(config)
if err != nil {
log.Fatal(err)
}
}
}
func statsAction(context *cli.Context) {
config, err := getConfig(context)
if err != nil {
log.Fatal(err)
}
stats, err := fs.GetStats(config)
if err != nil {
log.Fatal(err)
}
out, err := json.MarshalIndent(stats, "", "\t")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Usage stats for '%s':\n %v\n", config.Name, string(out))
}
func pauseAction(context *cli.Context) {
setFreezerState(context, cgroups.Frozen)
}
func resumeAction(context *cli.Context) {
setFreezerState(context, cgroups.Thawed)
}
func psAction(context *cli.Context) {
config, err := getConfig(context)
if err != nil {
log.Fatal(err)
}
pids, err := fs.GetPids(config)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Pids in '%s':\n", config.Name)
fmt.Println(pids)
}
func main() {
logPath := os.Getenv("log")
if logPath != "" {
if err := openLog(logPath); err != nil {
log.Fatal(err)
}
}
app := cli.NewApp()
app.Name = "cgutil"
app.Usage = "Test utility for libcontainer cgroups package"
app.Version = "0.1"
app.Commands = []cli.Command{
createCommand,
destroyCommand,
statsCommand,
pauseCommand,
resumeCommand,
psCommand,
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}

View File

@ -0,0 +1,10 @@
{
"name": "luke",
"parent": "darth",
"allow_all_devices": true,
"memory": 1073741824,
"memory_swap": -1,
"cpu_shares": 2048,
"cpu_quota": 500000,
"cpu_period": 250000
}

View File

@ -12,21 +12,21 @@ import (
var (
subsystems = map[string]subsystem{
"devices": &devicesGroup{},
"memory": &memoryGroup{},
"cpu": &cpuGroup{},
"cpuset": &cpusetGroup{},
"cpuacct": &cpuacctGroup{},
"blkio": &blkioGroup{},
"perf_event": &perfEventGroup{},
"freezer": &freezerGroup{},
"devices": &DevicesGroup{},
"memory": &MemoryGroup{},
"cpu": &CpuGroup{},
"cpuset": &CpusetGroup{},
"cpuacct": &CpuacctGroup{},
"blkio": &BlkioGroup{},
"perf_event": &PerfEventGroup{},
"freezer": &FreezerGroup{},
}
)
type subsystem interface {
Set(*data) error
Remove(*data) error
GetStats(*data, *cgroups.Stats) error
GetStats(string, *cgroups.Stats) error
}
type data struct {
@ -52,6 +52,14 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
return d, nil
}
func Cleanup(c *cgroups.Cgroup) error {
d, err := getCgroupData(c, 0)
if err != nil {
return fmt.Errorf("Could not get Cgroup data %s", err)
}
return d.Cleanup()
}
func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
stats := cgroups.NewStats()
@ -60,10 +68,19 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
return nil, fmt.Errorf("getting CgroupData %s", err)
}
for sysName, sys := range subsystems {
// Don't fail if a cgroup hierarchy was not found.
if err := sys.GetStats(d, stats); err != nil && err != cgroups.ErrNotFound {
return nil, fmt.Errorf("getting stats for system %q %s", sysName, err)
for sysname, sys := range subsystems {
path, err := d.path(sysname)
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if err == cgroups.ErrNotFound {
continue
}
return nil, err
}
if err := sys.GetStats(path, stats); err != nil {
return nil, err
}
}

View File

@ -11,10 +11,10 @@ import (
"github.com/docker/libcontainer/cgroups"
)
type blkioGroup struct {
type BlkioGroup struct {
}
func (s *blkioGroup) Set(d *data) error {
func (s *BlkioGroup) Set(d *data) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("blkio"); err != nil && err != cgroups.ErrNotFound {
return err
@ -22,7 +22,7 @@ func (s *blkioGroup) Set(d *data) error {
return nil
}
func (s *blkioGroup) Remove(d *data) error {
func (s *BlkioGroup) Remove(d *data) error {
return removePath(d.path("blkio"))
}
@ -65,6 +65,9 @@ func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
var blkioStats []cgroups.BlkioStatEntry
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return blkioStats, nil
}
return nil, err
}
defer f.Close()
@ -110,13 +113,9 @@ func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
return blkioStats, nil
}
func (s *blkioGroup) GetStats(d *data, stats *cgroups.Stats) error {
func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
var blkioStats []cgroups.BlkioStatEntry
var err error
path, err := d.path("blkio")
if err != nil {
return err
}
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
return err

View File

@ -44,8 +44,8 @@ func TestBlkioStats(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -84,10 +84,10 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) {
"blkio.io_queued_recursive": queuedRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
}
@ -100,10 +100,10 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
}
@ -116,10 +116,10 @@ func TestBlkioStatsNoServicedFile(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
}
@ -132,10 +132,10 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
}
@ -149,8 +149,8 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
}
@ -166,8 +166,8 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
}

View File

@ -5,15 +5,14 @@ import (
"os"
"path/filepath"
"strconv"
"syscall"
"github.com/docker/libcontainer/cgroups"
)
type cpuGroup struct {
type CpuGroup struct {
}
func (s *cpuGroup) Set(d *data) error {
func (s *CpuGroup) Set(d *data) error {
// We always want to join the cpu group, to allow fair cpu scheduling
// on a container basis
dir, err := d.join("cpu")
@ -38,19 +37,14 @@ func (s *cpuGroup) Set(d *data) error {
return nil
}
func (s *cpuGroup) Remove(d *data) error {
func (s *CpuGroup) Remove(d *data) error {
return removePath(d.path("cpu"))
}
func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error {
path, err := d.path("cpu")
if err != nil {
return err
}
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
f, err := os.Open(filepath.Join(path, "cpu.stat"))
if err != nil {
if pathErr, ok := err.(*os.PathError); ok && pathErr.Err == syscall.ENOENT {
if os.IsNotExist(err) {
return nil
}
return err

View File

@ -23,8 +23,8 @@ func TestCpuStats(t *testing.T) {
"cpu.stat": cpuStatContent,
})
cpu := &cpuGroup{}
err := cpu.GetStats(helper.CgroupData, &actualStats)
cpu := &CpuGroup{}
err := cpu.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -41,8 +41,8 @@ func TestNoCpuStatFile(t *testing.T) {
helper := NewCgroupTestUtil("cpu", t)
defer helper.cleanup()
cpu := &cpuGroup{}
err := cpu.GetStats(helper.CgroupData, &actualStats)
cpu := &CpuGroup{}
err := cpu.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal("Expected not to fail, but did")
}
@ -58,8 +58,8 @@ func TestInvalidCpuStat(t *testing.T) {
"cpu.stat": cpuStatContent,
})
cpu := &cpuGroup{}
err := cpu.GetStats(helper.CgroupData, &actualStats)
cpu := &CpuGroup{}
err := cpu.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failed stat parsing.")
}

View File

@ -22,10 +22,10 @@ var (
const nanosecondsInSecond = 1000000000
type cpuacctGroup struct {
type CpuacctGroup struct {
}
func (s *cpuacctGroup) Set(d *data) error {
func (s *CpuacctGroup) Set(d *data) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("cpuacct"); err != nil && err != cgroups.ErrNotFound {
return err
@ -33,20 +33,20 @@ func (s *cpuacctGroup) Set(d *data) error {
return nil
}
func (s *cpuacctGroup) Remove(d *data) error {
func (s *CpuacctGroup) Remove(d *data) error {
return removePath(d.path("cpuacct"))
}
func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
var (
err error
startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage, kernelModeUsage, userModeUsage, percentage uint64
)
path, err := d.path("cpuacct")
if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil {
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
return err
}
startCpu = kernelModeUsage + userModeUsage
if startSystem, err = s.getSystemCpuUsage(d); err != nil {
if startSystem, err = getSystemCpuUsage(); err != nil {
return err
}
startUsageTime := time.Now()
@ -55,11 +55,11 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
}
// sample for 100ms
time.Sleep(100 * time.Millisecond)
if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil {
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
return err
}
lastCpu = kernelModeUsage + userModeUsage
if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
if lastSystem, err = getSystemCpuUsage(); err != nil {
return err
}
usageSampleDuration := time.Since(startUsageTime)
@ -80,7 +80,7 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
stats.CpuStats.CpuUsage.PercentUsage = percentage
// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds())
percpuUsage, err := s.getPercpuUsage(path)
percpuUsage, err := getPercpuUsage(path)
if err != nil {
return err
}
@ -92,7 +92,7 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
}
// TODO(vmarmol): Use cgroups stats.
func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
func getSystemCpuUsage() (uint64, error) {
f, err := os.Open("/proc/stat")
if err != nil {
@ -125,7 +125,7 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
return 0, fmt.Errorf("invalid stat format")
}
func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, uint64, error) {
func getCpuUsage(path string) (uint64, uint64, error) {
kernelModeUsage := uint64(0)
userModeUsage := uint64(0)
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.stat"))
@ -146,7 +146,7 @@ func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, uint64, error)
return kernelModeUsage, userModeUsage, nil
}
func (s *cpuacctGroup) getPercpuUsage(path string) ([]uint64, error) {
func getPercpuUsage(path string) ([]uint64, error) {
percpuUsage := []uint64{}
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
if err != nil {

View File

@ -10,10 +10,10 @@ import (
"github.com/docker/libcontainer/cgroups"
)
type cpusetGroup struct {
type CpusetGroup struct {
}
func (s *cpusetGroup) Set(d *data) error {
func (s *CpusetGroup) Set(d *data) error {
// we don't want to join this cgroup unless it is specified
if d.c.CpusetCpus != "" {
dir, err := d.path("cpuset")
@ -36,15 +36,15 @@ func (s *cpusetGroup) Set(d *data) error {
return nil
}
func (s *cpusetGroup) Remove(d *data) error {
func (s *CpusetGroup) Remove(d *data) error {
return removePath(d.path("cpuset"))
}
func (s *cpusetGroup) GetStats(d *data, stats *cgroups.Stats) error {
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}
func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
return
}
@ -57,7 +57,7 @@ func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []b
// ensureParent ensures that the parent directory of current is created
// with the proper cpus and mems files copied from it's parent if the values
// are a file with a new line char
func (s *cpusetGroup) ensureParent(current string) error {
func (s *CpusetGroup) ensureParent(current string) error {
parent := filepath.Dir(current)
if _, err := os.Stat(parent); err != nil {
@ -78,7 +78,7 @@ func (s *cpusetGroup) ensureParent(current string) error {
// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
// directory to the current directory if the file's contents are 0
func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
func (s *CpusetGroup) copyIfNeeded(current, parent string) error {
var (
err error
currentCpus, currentMems []byte
@ -105,6 +105,6 @@ func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
return nil
}
func (s *cpusetGroup) isEmpty(b []byte) bool {
func (s *CpusetGroup) isEmpty(b []byte) bool {
return len(bytes.Trim(b, "\n")) == 0
}

View File

@ -2,10 +2,10 @@ package fs
import "github.com/docker/libcontainer/cgroups"
type devicesGroup struct {
type DevicesGroup struct {
}
func (s *devicesGroup) Set(d *data) error {
func (s *DevicesGroup) Set(d *data) error {
dir, err := d.join("devices")
if err != nil {
return err
@ -25,10 +25,10 @@ func (s *devicesGroup) Set(d *data) error {
return nil
}
func (s *devicesGroup) Remove(d *data) error {
func (s *DevicesGroup) Remove(d *data) error {
return removePath(d.path("devices"))
}
func (s *devicesGroup) GetStats(d *data, stats *cgroups.Stats) error {
func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -1,18 +1,16 @@
package fs
import (
"io/ioutil"
"path/filepath"
"strings"
"time"
"github.com/docker/libcontainer/cgroups"
)
type freezerGroup struct {
type FreezerGroup struct {
}
func (s *freezerGroup) Set(d *data) error {
func (s *FreezerGroup) Set(d *data) error {
switch d.c.Freezer {
case cgroups.Frozen, cgroups.Thawed:
dir, err := d.path("freezer")
@ -43,29 +41,10 @@ func (s *freezerGroup) Set(d *data) error {
return nil
}
func (s *freezerGroup) Remove(d *data) error {
func (s *FreezerGroup) Remove(d *data) error {
return removePath(d.path("freezer"))
}
func getFreezerFileData(path string) (string, error) {
data, err := ioutil.ReadFile(path)
return strings.TrimSuffix(string(data), "\n"), err
}
func (s *freezerGroup) GetStats(d *data, stats *cgroups.Stats) error {
path, err := d.path("freezer")
if err != nil {
return err
}
var data string
if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
return err
}
stats.FreezerStats.ParentState = data
if data, err = getFreezerFileData(filepath.Join(path, "freezer.self_freezing")); err != nil {
return err
}
stats.FreezerStats.SelfState = data
func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -9,10 +9,10 @@ import (
"github.com/docker/libcontainer/cgroups"
)
type memoryGroup struct {
type MemoryGroup struct {
}
func (s *memoryGroup) Set(d *data) error {
func (s *MemoryGroup) Set(d *data) error {
dir, err := d.join("memory")
// only return an error for memory if it was not specified
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
@ -47,19 +47,17 @@ func (s *memoryGroup) Set(d *data) error {
return nil
}
func (s *memoryGroup) Remove(d *data) error {
func (s *MemoryGroup) Remove(d *data) error {
return removePath(d.path("memory"))
}
func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error {
path, err := d.path("memory")
if err != nil {
return err
}
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
// Set stats from memory.stat.
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer statsFile.Close()

View File

@ -24,8 +24,8 @@ func TestMemoryStats(t *testing.T) {
"memory.failcnt": memoryFailcnt,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -41,10 +41,10 @@ func TestMemoryStatsNoStatFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil {
t.Fatal("Expected failure")
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
}
@ -56,8 +56,8 @@ func TestMemoryStatsNoUsageFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -71,8 +71,8 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
"memory.usage_in_bytes": memoryUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -87,8 +87,8 @@ func TestMemoryStatsBadStatFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -103,8 +103,8 @@ func TestMemoryStatsBadUsageFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -119,8 +119,8 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
"memory.max_usage_in_bytes": "bad",
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}

View File

@ -4,10 +4,10 @@ import (
"github.com/docker/libcontainer/cgroups"
)
type perfEventGroup struct {
type PerfEventGroup struct {
}
func (s *perfEventGroup) Set(d *data) error {
func (s *PerfEventGroup) Set(d *data) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("perf_event"); err != nil && err != cgroups.ErrNotFound {
return err
@ -15,10 +15,10 @@ func (s *perfEventGroup) Set(d *data) error {
return nil
}
func (s *perfEventGroup) Remove(d *data) error {
func (s *PerfEventGroup) Remove(d *data) error {
return removePath(d.path("perf_event"))
}
func (s *perfEventGroup) GetStats(d *data, stats *cgroups.Stats) error {
func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -55,17 +55,10 @@ type BlkioStats struct {
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"`
}
// TODO(Vishh): Remove freezer from stats since it does not logically belong in stats.
type FreezerStats struct {
ParentState string `json:"parent_state,omitempty"`
SelfState string `json:"self_state,omitempty"`
}
type Stats struct {
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
FreezerStats FreezerStats `json:"freezer_stats,omitempty"`
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
}
func NewStats() *Stats {

View File

@ -23,3 +23,7 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
return fmt.Errorf("Systemd not supported")
}
func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
return nil, fmt.Errorf("Systemd not supported")
}

View File

@ -15,6 +15,7 @@ import (
systemd1 "github.com/coreos/go-systemd/dbus"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/dotcloud/docker/pkg/systemd"
"github.com/godbus/dbus"
)
@ -23,10 +24,24 @@ type systemdCgroup struct {
cleanupDirs []string
}
type subsystem interface {
GetStats(string, *cgroups.Stats) error
}
var (
connLock sync.Mutex
theConn *systemd1.Conn
hasStartTransientUnit bool
subsystems = map[string]subsystem{
"devices": &fs.DevicesGroup{},
"memory": &fs.MemoryGroup{},
"cpu": &fs.CpuGroup{},
"cpuset": &fs.CpusetGroup{},
"cpuacct": &fs.CpuacctGroup{},
"blkio": &fs.BlkioGroup{},
"perf_event": &fs.PerfEventGroup{},
"freezer": &fs.FreezerGroup{},
}
)
func UseSystemd() bool {
@ -316,7 +331,7 @@ func (c *systemdCgroup) Cleanup() error {
}
func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
path, err := getFreezerPath(c)
path, err := getSubsystemPath(c, "freezer")
if err != nil {
return "", err
}
@ -332,23 +347,27 @@ func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
return path, nil
}
func getFreezerPath(c *cgroups.Cgroup) (string, error) {
mountpoint, err := cgroups.FindCgroupMountpoint("freezer")
func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
if err != nil {
return "", err
}
initPath, err := cgroups.GetInitCgroupDir("freezer")
initPath, err := cgroups.GetInitCgroupDir(subsystem)
if err != nil {
return "", err
}
return filepath.Join(mountpoint, initPath, fmt.Sprintf("%s-%s", c.Parent, c.Name)), nil
slice := "system.slice"
if c.Slice != "" {
slice = c.Slice
}
return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
path, err := getFreezerPath(c)
path, err := getSubsystemPath(c, "freezer")
if err != nil {
return err
}
@ -389,3 +408,32 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
func getUnitName(c *cgroups.Cgroup) string {
return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
}
/*
* This would be nicer to get from the systemd API when accounting
* is enabled, but sadly there is no way to do that yet.
* The lack of this functionality in the API & the approach taken
* is guided by
* http://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/#readingaccountinginformation.
*/
func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
stats := cgroups.NewStats()
for sysname, sys := range subsystems {
subsystemPath, err := getSubsystemPath(c, sysname)
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if err == cgroups.ErrNotFound {
continue
}
return nil, err
}
if err := sys.GetStats(subsystemPath, stats); err != nil {
return nil, err
}
}
return stats, nil
}

View File

@ -0,0 +1,86 @@
package libcontainer
import (
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/mount"
"github.com/docker/libcontainer/network"
)
type MountConfig mount.MountConfig
type Network network.Network
// Config defines configuration options for executing a process inside a contained environment.
type Config struct {
// Mount specific options.
MountConfig *MountConfig `json:"mount_config,omitempty"`
// Hostname optionally sets the container's hostname if provided
Hostname string `json:"hostname,omitempty"`
// User will set the uid and gid of the executing process running inside the container
User string `json:"user,omitempty"`
// WorkingDir will change the processes current working directory inside the container's rootfs
WorkingDir string `json:"working_dir,omitempty"`
// Env will populate the processes environment with the provided values
// Any values from the parent processes will be cleared before the values
// provided in Env are provided to the process
Env []string `json:"environment,omitempty"`
// Tty when true will allocate a pty slave on the host for access by the container's process
// and ensure that it is mounted inside the container's rootfs
Tty bool `json:"tty,omitempty"`
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
// If a namespace is not provided that namespace is shared from the container's parent process
Namespaces map[string]bool `json:"namespaces,omitempty"`
// Capabilities specify the capabilities to keep when executing the process inside the container
// All capbilities not specified will be dropped from the processes capability mask
Capabilities []string `json:"capabilities,omitempty"`
// Networks specifies the container's network setup to be created
Networks []*Network `json:"networks,omitempty"`
// Routes can be specified to create entries in the route table as the container is started
Routes []*Route `json:"routes,omitempty"`
// Cgroups specifies specific cgroup settings for the various subsystems that the container is
// placed into to limit the resources the container has available
Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"`
// AppArmorProfile specifies the profile to apply to the process running in the container and is
// change at the time the process is execed
AppArmorProfile string `json:"apparmor_profile,omitempty"`
// ProcessLabel specifies the label to apply to the process running in the container. It is
// commonly used by selinux
ProcessLabel string `json:"process_label,omitempty"`
// RestrictSys will remount /proc/sys, /sys, and mask over sysrq-trigger as well as /proc/irq and
// /proc/bus
RestrictSys bool `json:"restrict_sys,omitempty"`
}
// Routes can be specified to create entries in the route table as the container is started
//
// All of destination, source, and gateway should be either IPv4 or IPv6.
// One of the three options must be present, and ommitted entries will use their
// IP family default for the route table. For IPv4 for example, setting the
// gateway to 1.2.3.4 and the interface to eth0 will set up a standard
// destination of 0.0.0.0(or *) when viewed in the route table.
type Route struct {
// Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6
Destination string `json:"destination,omitempty"`
// Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6
Source string `json:"source,omitempty"`
// Sets the gateway. Accepts IPv4 and IPv6
Gateway string `json:"gateway,omitempty"`
// The device to set this route up for, for example: eth0
InterfaceName string `json:"interface_name,omitempty"`
}

View File

@ -1,86 +1,63 @@
/*
NOTE: The API is in flux and mainly not implemented. Proceed with caution until further notice.
*/
package libcontainer
import (
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/mount"
"github.com/docker/libcontainer/network"
)
type MountConfig mount.MountConfig
type Network network.Network
// Config defines configuration options for executing a process inside a contained environment.
type Config struct {
// Mount specific options.
MountConfig *MountConfig `json:"mount_config,omitempty"`
// Hostname optionally sets the container's hostname if provided
Hostname string `json:"hostname,omitempty"`
// User will set the uid and gid of the executing process running inside the container
User string `json:"user,omitempty"`
// WorkingDir will change the processes current working directory inside the container's rootfs
WorkingDir string `json:"working_dir,omitempty"`
// Env will populate the processes environment with the provided values
// Any values from the parent processes will be cleared before the values
// provided in Env are provided to the process
Env []string `json:"environment,omitempty"`
// Tty when true will allocate a pty slave on the host for access by the container's process
// and ensure that it is mounted inside the container's rootfs
Tty bool `json:"tty,omitempty"`
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
// If a namespace is not provided that namespace is shared from the container's parent process
Namespaces map[string]bool `json:"namespaces,omitempty"`
// Capabilities specify the capabilities to keep when executing the process inside the container
// All capbilities not specified will be dropped from the processes capability mask
Capabilities []string `json:"capabilities,omitempty"`
// Networks specifies the container's network setup to be created
Networks []*Network `json:"networks,omitempty"`
// Routes can be specified to create entries in the route table as the container is started
Routes []*Route `json:"routes,omitempty"`
// Cgroups specifies specific cgroup settings for the various subsystems that the container is
// placed into to limit the resources the container has available
Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"`
// AppArmorProfile specifies the profile to apply to the process running in the container and is
// change at the time the process is execed
AppArmorProfile string `json:"apparmor_profile,omitempty"`
// ProcessLabel specifies the label to apply to the process running in the container. It is
// commonly used by selinux
ProcessLabel string `json:"process_label,omitempty"`
// RestrictSys will remount /proc/sys, /sys, and mask over sysrq-trigger as well as /proc/irq and
// /proc/bus
RestrictSys bool `json:"restrict_sys,omitempty"`
}
// Routes can be specified to create entries in the route table as the container is started
// A libcontainer container object.
//
// All of destination, source, and gateway should be either IPv4 or IPv6.
// One of the three options must be present, and ommitted entries will use their
// IP family default for the route table. For IPv4 for example, setting the
// gateway to 1.2.3.4 and the interface to eth0 will set up a standard
// destination of 0.0.0.0(or *) when viewed in the route table.
type Route struct {
// Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6
Destination string `json:"destination,omitempty"`
// Each container is thread-safe within the same process. Since a container can
// be destroyed by a separate process, any function may return that the container
// was not found.
type Container interface {
// Returns the path to the container which contains the state
Path() string
// Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6
Source string `json:"source,omitempty"`
// Returns the current run state of the container.
//
// Errors: container no longer exists,
// system error.
RunState() (*RunState, error)
// Sets the gateway. Accepts IPv4 and IPv6
Gateway string `json:"gateway,omitempty"`
// Returns the current config of the container.
Config() *Config
// The device to set this route up for, for example: eth0
InterfaceName string `json:"interface_name,omitempty"`
// Destroys the container after killing all running processes.
//
// Any event registrations are removed before the container is destroyed.
// No error is returned if the container is already destroyed.
//
// Errors: system error.
Destroy() error
// Returns the PIDs inside this container. The PIDs are in the namespace of the calling process.
//
// Errors: container no longer exists,
// system error.
//
// Some of the returned PIDs may no longer refer to processes in the Container, unless
// the Container state is PAUSED in which case every PID in the slice is valid.
Processes() ([]int, error)
// Returns statistics for the container.
//
// Errors: container no longer exists,
// system error.
Stats() (*ContainerStats, error)
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses
// the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED.
// If the Container state is PAUSED, do nothing.
//
// Errors: container no longer exists,
// system error.
Pause() error
// If the Container state is PAUSED, resumes the execution of any user processes in the
// Container before setting the Container state to RUNNING.
// If the Container state is RUNNING, do nothing.
//
// Errors: container no longer exists,
// system error.
Resume() error
}

View File

@ -0,0 +1,25 @@
package libcontainer
type Factory interface {
// Creates a new container in the given path. A unique ID is generated for the container and
// starts the initial process inside the container.
//
// Returns the new container with a running process.
//
// Errors:
// Path already exists
// Config or initialConfig is invalid
// System error
//
// On error, any partially created container parts are cleaned up (the operation is atomic).
Create(path string, config *Config) (Container, error)
// Load takes the path for an existing container and reconstructs the container
// from the state.
//
// Errors:
// Path does not exist
// Container is stopped
// System error
Load(path string) (Container, error)
}

View File

@ -18,6 +18,10 @@ func SetFileLabel(path string, fileLabel string) error {
return nil
}
func Relabel(path string, fileLabel string, relabel string) error {
return nil
}
func GetPidCon(pid int) (string, error) {
return "", nil
}

View File

@ -66,6 +66,23 @@ func SetFileLabel(path string, fileLabel string) error {
return nil
}
// Change the label of path to the filelabel string. If the relabel string
// is "z", relabel will change the MCS label to s0. This will allow all
// containers to share the content. If the relabel string is a "Z" then
// the MCS label should continue to be used. SELinux will use this field
// to make sure the content can not be shared by other containes.
func Relabel(path string, fileLabel string, relabel string) error {
if fileLabel == "" {
return nil
}
if relabel == "z" {
c := selinux.NewContext(fileLabel)
c["level"] = "s0"
fileLabel = c.Get()
}
return selinux.Chcon(path, fileLabel, true)
}
func GetPidCon(pid int) (string, error) {
if !selinux.SelinuxEnabled() {
return "", nil

View File

@ -44,7 +44,7 @@ func InitializeMountNamespace(rootfs, console string, mountConfig *MountConfig)
if err := mountSystem(rootfs, mountConfig); err != nil {
return fmt.Errorf("mount system %s", err)
}
if err := setupBindmounts(rootfs, mountConfig.Mounts); err != nil {
if err := setupBindmounts(rootfs, mountConfig); err != nil {
return fmt.Errorf("bind mounts %s", err)
}
if err := nodes.CreateDeviceNodes(rootfs, mountConfig.DeviceNodes); err != nil {
@ -144,7 +144,8 @@ func setupDevSymlinks(rootfs string) error {
return nil
}
func setupBindmounts(rootfs string, bindMounts Mounts) error {
func setupBindmounts(rootfs string, mountConfig *MountConfig) error {
bindMounts := mountConfig.Mounts
for _, m := range bindMounts.OfType("bind") {
var (
flags = syscall.MS_BIND | syscall.MS_REC
@ -176,6 +177,11 @@ func setupBindmounts(rootfs string, bindMounts Mounts) error {
return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err)
}
}
if m.Relabel != "" {
if err := label.Relabel(m.Source, mountConfig.MountLabel, m.Relabel); err != nil {
return fmt.Errorf("relabeling %s to %s %s", m.Source, mountConfig.MountLabel, err)
}
}
if m.Private {
if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil {
return fmt.Errorf("mounting %s private %s", dest, err)

View File

@ -30,6 +30,7 @@ type Mount struct {
Source string `json:"source,omitempty"` // Source path, in the host namespace
Destination string `json:"destination,omitempty"` // Destination path, in the container
Writable bool `json:"writable,omitempty"`
Relabel string `json:"relabel,omitempty"` // Relabel source if set, "z" indicates shared, "Z" indicates unshared
Private bool `json:"private,omitempty"`
}

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/network"
"github.com/docker/libcontainer/syncpipe"
"github.com/dotcloud/docker/pkg/system"
)
@ -28,7 +29,7 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
// create a pipe so that we can syncronize with the namespaced process and
// pass the veth name to the child
syncPipe, err := NewSyncPipe()
syncPipe, err := syncpipe.NewSyncPipe()
if err != nil {
return -1, err
}
@ -42,7 +43,7 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
term.SetMaster(master)
}
command := createCommand(container, console, rootfs, dataPath, os.Args[0], syncPipe.child, args)
command := createCommand(container, console, rootfs, dataPath, os.Args[0], syncPipe.Child(), args)
if err := term.Attach(command); err != nil {
return -1, err
@ -166,7 +167,7 @@ func SetupCgroups(container *libcontainer.Config, nspid int) (cgroups.ActiveCgro
// InitializeNetworking creates the container's network stack outside of the namespace and moves
// interfaces into the container's net namespaces if necessary
func InitializeNetworking(container *libcontainer.Config, nspid int, pipe *SyncPipe, networkState *network.NetworkState) error {
func InitializeNetworking(container *libcontainer.Config, nspid int, pipe *syncpipe.SyncPipe, networkState *network.NetworkState) error {
for _, config := range container.Networks {
strategy, err := network.GetStrategy(config.Type)
if err != nil {

View File

@ -30,8 +30,8 @@ func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []st
panic("unreachable")
}
// NsEnter is run after entering the namespace.
func NsEnter(container *libcontainer.Config, nspid int, args []string) error {
// Run a command in a container after entering the namespace.
func NsEnter(container *libcontainer.Config, args []string) error {
// clear the current processes env and replace it with the environment
// defined on the container
if err := LoadContainerEnvironment(container); err != nil {

View File

@ -18,6 +18,7 @@ import (
"github.com/docker/libcontainer/network"
"github.com/docker/libcontainer/security/capabilities"
"github.com/docker/libcontainer/security/restrict"
"github.com/docker/libcontainer/syncpipe"
"github.com/docker/libcontainer/utils"
"github.com/dotcloud/docker/pkg/system"
"github.com/dotcloud/docker/pkg/user"
@ -27,7 +28,7 @@ import (
// Move this to libcontainer package.
// Init is the init process that first runs inside a new namespace to setup mounts, users, networking,
// and other options required for the new container.
func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) (err error) {
func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *syncpipe.SyncPipe, args []string) (err error) {
defer func() {
if err != nil {
syncPipe.ReportChildError(err)

View File

@ -1,4 +1,4 @@
package main
package nsinit
import (
"log"
@ -19,7 +19,7 @@ func preload(context *cli.Context) error {
return nil
}
func main() {
func NsInit() {
app := cli.NewApp()
app.Name = "nsinit"
app.Version = "0.1"
@ -30,8 +30,10 @@ func main() {
execCommand,
initCommand,
statsCommand,
specCommand,
configCommand,
nsenterCommand,
pauseCommand,
unpauseCommand,
}
if err := app.Run(os.Args); err != nil {

View File

@ -0,0 +1,29 @@
package nsinit
import (
"encoding/json"
"fmt"
"log"
"github.com/codegangsta/cli"
)
var configCommand = cli.Command{
Name: "config",
Usage: "display the container configuration",
Action: configAction,
}
func configAction(context *cli.Context) {
container, err := loadContainer()
if err != nil {
log.Fatal(err)
}
data, err := json.MarshalIndent(container, "", "\t")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", data)
}

View File

@ -1,4 +1,4 @@
package main
package nsinit
import (
"fmt"

View File

@ -1,4 +1,4 @@
package main
package nsinit
import (
"log"
@ -7,6 +7,7 @@ import (
"github.com/codegangsta/cli"
"github.com/docker/libcontainer/namespaces"
"github.com/docker/libcontainer/syncpipe"
)
var (
@ -37,7 +38,7 @@ func initAction(context *cli.Context) {
log.Fatal(err)
}
syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd))
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, uintptr(pipeFd))
if err != nil {
log.Fatalf("unable to create sync pipe: %s", err)
}

View File

@ -1,4 +1,4 @@
package main
package nsinit
import (
"log"
@ -34,7 +34,7 @@ func nsenterAction(context *cli.Context) {
log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid)
}
if err := namespaces.NsEnter(container, nspid, args); err != nil {
if err := namespaces.NsEnter(container, args); err != nil {
log.Fatalf("failed to nsenter: %s", err)
}
}

View File

@ -0,0 +1,7 @@
package main
import "github.com/docker/libcontainer/nsinit"
func main() {
nsinit.NsInit()
}

View File

@ -0,0 +1,49 @@
package nsinit
import (
"log"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
)
var pauseCommand = cli.Command{
Name: "pause",
Usage: "pause the container's processes",
Action: pauseAction,
}
var unpauseCommand = cli.Command{
Name: "unpause",
Usage: "unpause the container's processes",
Action: unpauseAction,
}
func pauseAction(context *cli.Context) {
if err := toggle(cgroups.Frozen); err != nil {
log.Fatal(err)
}
}
func unpauseAction(context *cli.Context) {
if err := toggle(cgroups.Thawed); err != nil {
log.Fatal(err)
}
}
func toggle(state cgroups.FreezerState) error {
container, err := loadContainer()
if err != nil {
return err
}
if systemd.UseSystemd() {
err = systemd.Freeze(container.Cgroups, state)
} else {
err = fs.Freeze(container.Cgroups, state)
}
return err
}

View File

@ -1,40 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer"
)
var specCommand = cli.Command{
Name: "spec",
Usage: "display the container specification",
Action: specAction,
}
func specAction(context *cli.Context) {
container, err := loadContainer()
if err != nil {
log.Fatal(err)
}
spec, err := getContainerSpec(container)
if err != nil {
log.Fatalf("Failed to get spec - %v\n", err)
}
fmt.Printf("Spec:\n%v\n", spec)
}
// returns the container spec in json format.
func getContainerSpec(container *libcontainer.Config) (string, error) {
spec, err := json.MarshalIndent(container, "", "\t")
if err != nil {
return "", err
}
return string(spec), nil
}

View File

@ -1,4 +1,4 @@
package main
package nsinit
import (
"encoding/json"
@ -21,30 +21,19 @@ func statsAction(context *cli.Context) {
log.Fatal(err)
}
runtimeCkpt, err := libcontainer.GetState(dataPath)
state, err := libcontainer.GetState(dataPath)
if err != nil {
log.Fatal(err)
}
stats, err := getStats(container, runtimeCkpt)
if err != nil {
log.Fatalf("Failed to get stats - %v\n", err)
}
fmt.Printf("Stats:\n%v\n", stats)
}
// returns the container stats in json format.
func getStats(container *libcontainer.Config, state *libcontainer.State) (string, error) {
stats, err := libcontainer.GetStats(container, state)
if err != nil {
return "", err
log.Fatal(err)
}
out, err := json.MarshalIndent(stats, "", "\t")
data, err := json.MarshalIndent(stats, "", "\t")
if err != nil {
return "", err
log.Fatal(err)
}
return string(out), nil
fmt.Printf("%s", data)
}

View File

@ -1,4 +1,4 @@
package main
package nsinit
import (
"encoding/json"

View File

@ -0,0 +1,27 @@
package libcontainer
import "io"
// Configuration for a process to be run inside a container.
type ProcessConfig struct {
// The command to be run followed by any arguments.
Args []string
// Map of environment variables to their values.
Env []string
// Stdin is a pointer to a reader which provides the standard input stream.
// Stdout is a pointer to a writer which receives the standard output stream.
// Stderr is a pointer to a writer which receives the standard error stream.
//
// If a reader or writer is nil, the input stream is assumed to be empty and the output is
// discarded.
//
// The readers and writers, if supplied, are closed when the process terminates. Their Close
// methods should be idempotent.
//
// Stdout and Stderr may refer to the same writer in which case the output is interspersed.
Stdin io.ReadCloser
Stdout io.WriteCloser
Stderr io.WriteCloser
}

View File

@ -27,7 +27,7 @@ func DropBoundingSet(capabilities []string) error {
return nil
}
// DropCapabilities drops all capabilities for the current process expect those specified in the container configuration.
// DropCapabilities drops all capabilities for the current process except those specified in the container configuration.
func DropCapabilities(capList []string) error {
c, err := capability.NewPid(os.Getpid())
if err != nil {

View File

@ -64,8 +64,6 @@ var capabilityList = Capabilities{
{Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN},
{Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN},
{Key: "SYSLOG", Value: capability.CAP_SYSLOG},
{Key: "SETUID", Value: capability.CAP_SETUID},
{Key: "SETGID", Value: capability.CAP_SETGID},
{Key: "CHOWN", Value: capability.CAP_CHOWN},
{Key: "NET_RAW", Value: capability.CAP_NET_RAW},
{Key: "DAC_OVERRIDE", Value: capability.CAP_DAC_OVERRIDE},

View File

@ -9,6 +9,7 @@ import (
"github.com/dotcloud/docker/pkg/system"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
@ -76,7 +77,7 @@ func SelinuxEnabled() bool {
}
selinuxEnabledChecked = true
if fs := getSelinuxMountPoint(); fs != "" {
if con, _ := getcon(); con != "kernel" {
if con, _ := Getcon(); con != "kernel" {
selinuxEnabled = true
}
}
@ -145,6 +146,12 @@ func Setfilecon(path string, scon string) error {
return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
}
// Return the SELinux label for this path
func Getfilecon(path string) (string, error) {
con, err := system.Lgetxattr(path, xattrNameSelinux)
return string(con), err
}
func Setfscreatecon(scon string) error {
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", system.Gettid()), scon)
}
@ -153,7 +160,8 @@ func Getfscreatecon() (string, error) {
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", system.Gettid()))
}
func getcon() (string, error) {
// Return the SELinux label of the current process thread.
func Getcon() (string, error) {
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", system.Gettid()))
}
@ -396,3 +404,36 @@ func CopyLevel(src, dest string) (string, error) {
tcon["level"] = scon["level"]
return tcon.Get(), nil
}
// Prevent users from relabing system files
func badPrefix(fpath string) error {
var badprefixes = []string{"/usr"}
for _, prefix := range badprefixes {
if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
return fmt.Errorf("Relabeling content in %s is not allowed.", prefix)
}
}
return nil
}
// Change the fpath file object to the SELinux label scon.
// If the fpath is a directory and recurse is true Chcon will walk the
// directory tree setting the label
func Chcon(fpath string, scon string, recurse bool) error {
if !SelinuxEnabled() {
return nil
}
if err := badPrefix(fpath); err != nil {
return err
}
callback := func(p string, info os.FileInfo, err error) error {
return Setfilecon(p, scon)
}
if recurse {
return filepath.Walk(fpath, callback)
}
return Setfilecon(fpath, scon)
}

View File

@ -12,14 +12,33 @@ import (
type State struct {
// InitPid is the init process id in the parent namespace
InitPid int `json:"init_pid,omitempty"`
// InitStartTime is the init process start time
InitStartTime string `json:"init_start_time,omitempty"`
// Network runtime state.
NetworkState network.NetworkState `json:"network_state,omitempty"`
}
// The name of the runtime state file
const stateFile = "state.json"
// The running state of the container.
type RunState int
const (
// The name of the runtime state file
stateFile = "state.json"
// The container exists and is running.
Running RunState = iota
// The container exists, it is in the process of being paused.
Pausing
// The container exists, but all its processes are paused.
Paused
// The container does not exist.
Destroyed
)
// SaveState writes the container's runtime state to a state.json file
// in the specified path

View File

@ -1,4 +1,4 @@
package namespaces
package syncpipe
import (
"encoding/json"