Update libcontainer dep to fb67bb80b4205bece36ff70
Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@docker.com> (github: crosbymichael)
This commit is contained in:
parent
7ebd49c49a
commit
3b4a1c54d8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
func NewStats() *Stats {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package nsinit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "github.com/docker/libcontainer/nsinit"
|
||||
|
||||
func main() {
|
||||
nsinit.NsInit()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package nsinit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 running state of the container.
|
||||
type RunState int
|
||||
|
||||
const (
|
||||
// The name of the runtime state file
|
||||
const stateFile = "state.json"
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package namespaces
|
||||
package syncpipe
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
|||
package namespaces
|
||||
package syncpipe
|
||||
|
||||
import (
|
||||
"os"
|
|
@ -1,4 +1,4 @@
|
|||
package namespaces
|
||||
package syncpipe
|
||||
|
||||
import (
|
||||
"fmt"
|
Loading…
Reference in New Issue