mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #12965 from tianon/libcontainer-logrus
Update libcontainer and make it the source of truth on logrus version
This commit is contained in:
commit
db26564864
58 changed files with 1213 additions and 460 deletions
|
@ -32,7 +32,7 @@ func initializer() {
|
|||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if err := factory.StartInitialization(3); err != nil {
|
||||
if err := factory.StartInitialization(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,8 +53,6 @@ clone hg code.google.com/p/gosqlite 74691fb6f837
|
|||
|
||||
clone git github.com/docker/libtrust 230dfd18c232
|
||||
|
||||
clone git github.com/Sirupsen/logrus v0.7.2
|
||||
|
||||
clone git github.com/go-fsnotify/fsnotify v1.2.0
|
||||
|
||||
clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
|
||||
|
@ -69,8 +67,8 @@ mv tmp-digest src/github.com/docker/distribution/digest
|
|||
mkdir -p src/github.com/docker/distribution/registry
|
||||
mv tmp-api src/github.com/docker/distribution/registry/api
|
||||
|
||||
clone git github.com/docker/libcontainer bd8ec36106086f72b66e1be85a81202b93503e44
|
||||
clone git github.com/docker/libcontainer 6607689b1d06743003a45a722d9fe0bef36b274e
|
||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
||||
rm -rf src/github.com/docker/libcontainer/vendor
|
||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
|
||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
|
||||
# we exclude "github.com/codegangsta/cli" here because it's only needed for "nsinit", which Docker doesn't include
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
# 0.7.3
|
||||
|
||||
formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
formatter/text: Add configuration option for time format (#158)
|
||||
|
|
45
vendor/src/github.com/Sirupsen/logrus/README.md
vendored
45
vendor/src/github.com/Sirupsen/logrus/README.md
vendored
|
@ -108,6 +108,16 @@ func main() {
|
|||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -189,31 +199,18 @@ func init() {
|
|||
}
|
||||
```
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
|
||||
Send errors to an exception tracking service compatible with the Airbrake API.
|
||||
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
|
||||
Send errors to the Papertrail hosted logging service via UDP.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
||||
Send errors to remote syslog server.
|
||||
Uses standard library `log/syslog` behind the scenes.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/bugsnag`](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go)
|
||||
Send errors to the Bugsnag exception tracking service.
|
||||
|
||||
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
||||
Send errors to a channel in hipchat.
|
||||
|
||||
* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
|
||||
Send logs to Loggly (https://www.loggly.com/)
|
||||
|
||||
* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
|
||||
Hook for Slack chat.
|
||||
|
||||
* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook).
|
||||
Hook for logging to `systemd-journald`.
|
||||
| Hook | Description |
|
||||
| ----- | ----------- |
|
||||
| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||
| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
|
||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||
| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||
| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||
|
||||
#### Level logging
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
const DefaultTimestampFormat = time.RFC3339
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
|
|
|
@ -3,19 +3,27 @@ package logstash
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Formatter generates json in logstash format.
|
||||
// Logstash site: http://logstash.net/
|
||||
type LogstashFormatter struct {
|
||||
Type string // if not empty use for logstash type field.
|
||||
|
||||
// TimestampFormat sets the format used for timestamps.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
entry.Data["@version"] = 1
|
||||
entry.Data["@timestamp"] = entry.Time.Format(time.RFC3339)
|
||||
|
||||
if f.TimestampFormat == "" {
|
||||
f.TimestampFormat = logrus.DefaultTimestampFormat
|
||||
}
|
||||
|
||||
entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
|
||||
|
||||
// set message field
|
||||
v, ok := entry.Data["message"]
|
||||
|
|
|
@ -3,10 +3,12 @@ package logrus
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSONFormatter struct{}
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
|
@ -21,7 +23,12 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
data["time"] = entry.Time.Format(time.RFC3339)
|
||||
|
||||
if f.TimestampFormat == "" {
|
||||
f.TimestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
|
||||
data["time"] = entry.Time.Format(f.TimestampFormat)
|
||||
data["msg"] = entry.Message
|
||||
data["level"] = entry.Level.String()
|
||||
|
||||
|
|
|
@ -18,9 +18,8 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
isTerminal bool
|
||||
defaultTimestampFormat = time.RFC3339
|
||||
baseTimestamp time.Time
|
||||
isTerminal bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -47,7 +46,7 @@ type TextFormatter struct {
|
|||
// the time passed since beginning of execution.
|
||||
FullTimestamp bool
|
||||
|
||||
// Timestamp format to use for display, if a full timestamp is printed
|
||||
// TimestampFormat to use for display when a full timestamp is printed
|
||||
TimestampFormat string
|
||||
|
||||
// The fields are sorted by default for a consistent output. For applications
|
||||
|
@ -73,7 +72,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|||
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||
|
||||
if f.TimestampFormat == "" {
|
||||
f.TimestampFormat = defaultTimestampFormat
|
||||
f.TimestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
if isColored {
|
||||
f.printColored(b, entry, keys)
|
||||
|
|
|
@ -14,8 +14,10 @@ import (
|
|||
|
||||
func IsEnabled() bool {
|
||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
|
||||
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
||||
return err == nil && len(buf) > 1 && buf[0] == 'Y'
|
||||
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
|
||||
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
||||
return err == nil && len(buf) > 1 && buf[0] == 'Y'
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -19,6 +21,7 @@ var (
|
|||
"cpuset": &CpusetGroup{},
|
||||
"cpuacct": &CpuacctGroup{},
|
||||
"blkio": &BlkioGroup{},
|
||||
"hugetlb": &HugetlbGroup{},
|
||||
"perf_event": &PerfEventGroup{},
|
||||
"freezer": &FreezerGroup{},
|
||||
}
|
||||
|
@ -75,10 +78,13 @@ type data struct {
|
|||
}
|
||||
|
||||
func (m *Manager) Apply(pid int) error {
|
||||
|
||||
if m.Cgroups == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var c = m.Cgroups
|
||||
|
||||
d, err := getCgroupData(m.Cgroups, pid)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -108,6 +114,12 @@ func (m *Manager) Apply(pid int) error {
|
|||
}
|
||||
m.Paths = paths
|
||||
|
||||
if paths["cpu"] != "" {
|
||||
if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -119,19 +131,6 @@ func (m *Manager) GetPaths() map[string]string {
|
|||
return m.Paths
|
||||
}
|
||||
|
||||
// Symmetrical public function to update device based cgroups. Also available
|
||||
// in the systemd implementation.
|
||||
func ApplyDevices(c *configs.Cgroup, pid int) error {
|
||||
d, err := getCgroupData(c, pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
devices := subsystems["devices"]
|
||||
|
||||
return devices.Apply(d)
|
||||
}
|
||||
|
||||
func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||
stats := cgroups.NewStats()
|
||||
for name, path := range m.Paths {
|
||||
|
@ -280,3 +279,27 @@ func removePath(p string, err error) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckCpushares(path string, c int64) error {
|
||||
var cpuShares int64
|
||||
|
||||
fd, err := os.Open(filepath.Join(path, "cpu.shares"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
_, err = fmt.Fscanf(fd, "%d", &cpuShares)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if c != 0 {
|
||||
if c > cpuShares {
|
||||
return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares)
|
||||
} else if c < cpuShares {
|
||||
return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -35,6 +35,32 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
|
|||
}
|
||||
}
|
||||
|
||||
if cgroup.BlkioWeightDevice != "" {
|
||||
if err := writeFile(path, "blkio.weight_device", cgroup.BlkioWeightDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cgroup.BlkioThrottleReadBpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.read_bps_device", cgroup.BlkioThrottleReadBpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cgroup.BlkioThrottleWriteBpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.write_bps_device", cgroup.BlkioThrottleWriteBpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cgroup.BlkioThrottleReadIOpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.read_iops_device", cgroup.BlkioThrottleReadIOpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cgroup.BlkioThrottleWriteIOpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.write_iops_device", cgroup.BlkioThrottleWriteIOpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,8 @@ Total 22061056`
|
|||
252:0 Async 164
|
||||
252:0 Total 164
|
||||
Total 328`
|
||||
throttleBefore = `8:0 1024`
|
||||
throttleAfter = `8:0 2048`
|
||||
)
|
||||
|
||||
func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) {
|
||||
|
@ -102,6 +104,35 @@ func TestBlkioSetWeight(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBlkioSetWeightDevice(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("blkio", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
const (
|
||||
weightDeviceBefore = "8:0 400"
|
||||
weightDeviceAfter = "8:0 500"
|
||||
)
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"blkio.weight_device": weightDeviceBefore,
|
||||
})
|
||||
|
||||
helper.CgroupData.c.BlkioWeightDevice = weightDeviceAfter
|
||||
blkio := &BlkioGroup{}
|
||||
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse blkio.weight_device - %s", err)
|
||||
}
|
||||
|
||||
if value != weightDeviceAfter {
|
||||
t.Fatal("Got the wrong value, set blkio.weight_device failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlkioStats(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("blkio", t)
|
||||
defer helper.cleanup()
|
||||
|
@ -442,3 +473,96 @@ func TestNonCFQBlkioStats(t *testing.T) {
|
|||
|
||||
expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
|
||||
}
|
||||
|
||||
func TestBlkioSetThrottleReadBpsDevice(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("blkio", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"blkio.throttle.read_bps_device": throttleBefore,
|
||||
})
|
||||
|
||||
helper.CgroupData.c.BlkioThrottleReadBpsDevice = throttleAfter
|
||||
blkio := &BlkioGroup{}
|
||||
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err)
|
||||
}
|
||||
|
||||
if value != throttleAfter {
|
||||
t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.")
|
||||
}
|
||||
}
|
||||
func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("blkio", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"blkio.throttle.write_bps_device": throttleBefore,
|
||||
})
|
||||
|
||||
helper.CgroupData.c.BlkioThrottleWriteBpsDevice = throttleAfter
|
||||
blkio := &BlkioGroup{}
|
||||
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err)
|
||||
}
|
||||
|
||||
if value != throttleAfter {
|
||||
t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.")
|
||||
}
|
||||
}
|
||||
func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("blkio", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"blkio.throttle.read_iops_device": throttleBefore,
|
||||
})
|
||||
|
||||
helper.CgroupData.c.BlkioThrottleReadIOpsDevice = throttleAfter
|
||||
blkio := &BlkioGroup{}
|
||||
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err)
|
||||
}
|
||||
|
||||
if value != throttleAfter {
|
||||
t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.")
|
||||
}
|
||||
}
|
||||
func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("blkio", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"blkio.throttle.write_iops_device": throttleBefore,
|
||||
})
|
||||
|
||||
helper.CgroupData.c.BlkioThrottleWriteIOpsDevice = throttleAfter
|
||||
blkio := &BlkioGroup{}
|
||||
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err)
|
||||
}
|
||||
|
||||
if value != throttleAfter {
|
||||
t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,17 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeFile(path, "devices.allow", "a"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dev := range cgroup.DeniedDevices {
|
||||
if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -17,7 +17,18 @@ var (
|
|||
FileMode: 0666,
|
||||
},
|
||||
}
|
||||
allowedList = "c 1:5 rwm"
|
||||
allowedList = "c 1:5 rwm"
|
||||
deniedDevices = []*configs.Device{
|
||||
{
|
||||
Path: "/dev/null",
|
||||
Type: 'c',
|
||||
Major: 1,
|
||||
Minor: 3,
|
||||
Permissions: "rwm",
|
||||
FileMode: 0666,
|
||||
},
|
||||
}
|
||||
deniedList = "c 1:3 rwm"
|
||||
)
|
||||
|
||||
func TestDevicesSetAllow(t *testing.T) {
|
||||
|
@ -44,3 +55,28 @@ func TestDevicesSetAllow(t *testing.T) {
|
|||
t.Fatal("Got the wrong value, set devices.allow failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevicesSetDeny(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("devices", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"devices.allow": "a",
|
||||
})
|
||||
|
||||
helper.CgroupData.c.AllowAllDevices = true
|
||||
helper.CgroupData.c.DeniedDevices = deniedDevices
|
||||
devices := &DevicesGroup{}
|
||||
if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "devices.deny")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse devices.deny - %s", err)
|
||||
}
|
||||
|
||||
if value != deniedList {
|
||||
t.Fatal("Got the wrong value, set devices.deny failed.")
|
||||
}
|
||||
}
|
||||
|
|
29
vendor/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go
vendored
Normal file
29
vendor/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
"github.com/docker/libcontainer/configs"
|
||||
)
|
||||
|
||||
type HugetlbGroup struct {
|
||||
}
|
||||
|
||||
func (s *HugetlbGroup) Apply(d *data) error {
|
||||
// we just want to join this group even though we don't set anything
|
||||
if _, err := d.join("hugetlb"); err != nil && !cgroups.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HugetlbGroup) Remove(d *data) error {
|
||||
return removePath(d.path("hugetlb"))
|
||||
}
|
||||
|
||||
func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
return nil
|
||||
}
|
|
@ -95,6 +95,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|||
return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err)
|
||||
}
|
||||
stats.MemoryStats.Usage = value
|
||||
stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
|
||||
value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err)
|
||||
|
|
|
@ -128,7 +128,7 @@ func TestMemoryStats(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
|
||||
expectedStats := cgroups.MemoryStats{Usage: 2048, Cache: 512, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
|
||||
expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ package fs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
)
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ type CpuStats struct {
|
|||
type MemoryStats struct {
|
||||
// current res_counter usage for memory
|
||||
Usage uint64 `json:"usage,omitempty"`
|
||||
// memory used for cache
|
||||
Cache uint64 `json:"cache,omitempty"`
|
||||
// maximum usage ever recorded.
|
||||
MaxUsage uint64 `json:"max_usage,omitempty"`
|
||||
// TODO(vishh): Export these as stronger types.
|
||||
|
|
|
@ -46,10 +46,6 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
|
|||
return fmt.Errorf("Systemd not supported")
|
||||
}
|
||||
|
||||
func ApplyDevices(c *configs.Cgroup, pid int) error {
|
||||
return fmt.Errorf("Systemd not supported")
|
||||
}
|
||||
|
||||
func Freeze(c *configs.Cgroup, state configs.FreezerState) error {
|
||||
return fmt.Errorf("Systemd not supported")
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ var subsystems = map[string]subsystem{
|
|||
"cpuset": &fs.CpusetGroup{},
|
||||
"cpuacct": &fs.CpuacctGroup{},
|
||||
"blkio": &fs.BlkioGroup{},
|
||||
"hugetlb": &fs.HugetlbGroup{},
|
||||
"perf_event": &fs.PerfEventGroup{},
|
||||
"freezer": &fs.FreezerGroup{},
|
||||
}
|
||||
|
@ -216,6 +217,13 @@ func (m *Manager) Apply(pid int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// FIXME: Systemd does have `BlockIODeviceWeight` property, but we got problem
|
||||
// using that (at least on systemd 208, see https://github.com/docker/libcontainer/pull/354),
|
||||
// so use fs work around for now.
|
||||
if err := joinBlkio(c, pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paths := make(map[string]string)
|
||||
for sysname := range subsystems {
|
||||
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
|
||||
|
@ -228,9 +236,14 @@ func (m *Manager) Apply(pid int) error {
|
|||
}
|
||||
paths[sysname] = subsystemPath
|
||||
}
|
||||
|
||||
m.Paths = paths
|
||||
|
||||
if paths["cpu"] != "" {
|
||||
if err := fs.CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -350,7 +363,17 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
|||
}
|
||||
|
||||
func (m *Manager) Set(container *configs.Config) error {
|
||||
panic("not implemented")
|
||||
for name, path := range m.Paths {
|
||||
sys, ok := subsystems[name]
|
||||
if !ok || !cgroups.PathExists(path) {
|
||||
continue
|
||||
}
|
||||
if err := sys.Set(path, container.Cgroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUnitName(c *configs.Cgroup) string {
|
||||
|
@ -362,7 +385,7 @@ func getUnitName(c *configs.Cgroup) string {
|
|||
// * Support for wildcards to allow /dev/pts support
|
||||
//
|
||||
// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
|
||||
// in wide use. When both these are availalable we will be able to switch, but need to keep the old
|
||||
// in wide use. When both these are available we will be able to switch, but need to keep the old
|
||||
// implementation for backwards compat.
|
||||
//
|
||||
// Note: we can't use systemd to set up the initial limits, and then change the cgroup
|
||||
|
@ -375,17 +398,7 @@ func joinDevices(c *configs.Cgroup, pid int) error {
|
|||
}
|
||||
|
||||
devices := subsystems["devices"]
|
||||
if err := devices.Set(path, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Symmetrical public function to update device based cgroups. Also available
|
||||
// in the fs implementation.
|
||||
func ApplyDevices(c *configs.Cgroup, pid int) error {
|
||||
return joinDevices(c, pid)
|
||||
return devices.Set(path, c)
|
||||
}
|
||||
|
||||
func joinMemory(c *configs.Cgroup, pid int) error {
|
||||
|
@ -417,3 +430,40 @@ func joinCpuset(c *configs.Cgroup, pid int) error {
|
|||
|
||||
return s.ApplyDir(path, c, pid)
|
||||
}
|
||||
|
||||
// `BlockIODeviceWeight` property of systemd does not work properly, and systemd
|
||||
// expects device path instead of major minor numbers, which is also confusing
|
||||
// for users. So we use fs work around for now.
|
||||
func joinBlkio(c *configs.Cgroup, pid int) error {
|
||||
path, err := getSubsystemPath(c, "blkio")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.BlkioWeightDevice != "" {
|
||||
if err := writeFile(path, "blkio.weight_device", c.BlkioWeightDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.BlkioThrottleReadBpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.read_bps_device", c.BlkioThrottleReadBpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.BlkioThrottleWriteBpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.write_bps_device", c.BlkioThrottleWriteBpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.BlkioThrottleReadIOpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.read_iops_device", c.BlkioThrottleReadIOpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.BlkioThrottleWriteIOpsDevice != "" {
|
||||
if err := writeFile(path, "blkio.throttle.write_iops_device", c.BlkioThrottleWriteIOpsDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ type Cgroup struct {
|
|||
|
||||
AllowedDevices []*Device `json:"allowed_devices"`
|
||||
|
||||
DeniedDevices []*Device `json:"denied_devices"`
|
||||
|
||||
// Memory limit (in bytes)
|
||||
Memory int64 `json:"memory"`
|
||||
|
||||
|
@ -43,9 +45,24 @@ type Cgroup struct {
|
|||
// MEM to use
|
||||
CpusetMems string `json:"cpuset_mems"`
|
||||
|
||||
// IO read rate limit per cgroup per device, bytes per second.
|
||||
BlkioThrottleReadBpsDevice string `json:"blkio_throttle_read_bps_device"`
|
||||
|
||||
// IO write rate limit per cgroup per divice, bytes per second.
|
||||
BlkioThrottleWriteBpsDevice string `json:"blkio_throttle_write_bps_device"`
|
||||
|
||||
// IO read rate limit per cgroup per device, IO per second.
|
||||
BlkioThrottleReadIOpsDevice string `json:"blkio_throttle_read_iops_device"`
|
||||
|
||||
// IO write rate limit per cgroup per device, IO per second.
|
||||
BlkioThrottleWriteIOpsDevice string `json:"blkio_throttle_write_iops_device"`
|
||||
|
||||
// Specifies per cgroup weight, range is from 10 to 1000.
|
||||
BlkioWeight int64 `json:"blkio_weight"`
|
||||
|
||||
// Weight per cgroup per device, can override BlkioWeight.
|
||||
BlkioWeightDevice string `json:"blkio_weight_device"`
|
||||
|
||||
// set the freeze value for the process
|
||||
Freezer FreezerState `json:"freezer"`
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ type Config struct {
|
|||
// bind mounts are writtable.
|
||||
Readonlyfs bool `json:"readonlyfs"`
|
||||
|
||||
// Privatefs will mount the container's rootfs as private where mount points from the parent will not propogate
|
||||
Privatefs bool `json:"privatefs"`
|
||||
|
||||
// Mounts specify additional source and destination paths that will be mounted inside the container's
|
||||
// rootfs and mount namespace if specified
|
||||
Mounts []*Mount `json:"mounts"`
|
||||
|
@ -96,6 +99,10 @@ type Config struct {
|
|||
// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
|
||||
// so that these files prevent any writes.
|
||||
ReadonlyPaths []string `json:"readonly_paths"`
|
||||
|
||||
// SystemProperties is a map of properties and their values. It is the equivalent of using
|
||||
// sysctl -w my.property.name value in Linux.
|
||||
SystemProperties map[string]string `json:"system_properties"`
|
||||
}
|
||||
|
||||
// Gets the root uid for the process on host which could be non-zero
|
||||
|
|
|
@ -18,4 +18,17 @@ type Mount struct {
|
|||
|
||||
// Relabel source if set, "z" indicates shared, "Z" indicates unshared.
|
||||
Relabel string `json:"relabel"`
|
||||
|
||||
// Optional Command to be run before Source is mounted.
|
||||
PremountCmds []Command `json:"premount_cmds"`
|
||||
|
||||
// Optional Command to be run after Source is mounted.
|
||||
PostmountCmds []Command `json:"postmount_cmds"`
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Path string `json:"path"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
Dir string `json:"dir"`
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package configs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
type NamespaceType string
|
||||
|
||||
|
@ -34,10 +31,6 @@ type Namespace struct {
|
|||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func (n *Namespace) Syscall() int {
|
||||
return namespaceInfo[n.Type]
|
||||
}
|
||||
|
||||
func (n *Namespace) GetPath(pid int) string {
|
||||
if n.Path != "" {
|
||||
return n.Path
|
||||
|
@ -96,25 +89,3 @@ func (n *Namespaces) index(t NamespaceType) int {
|
|||
func (n *Namespaces) Contains(t NamespaceType) bool {
|
||||
return n.index(t) != -1
|
||||
}
|
||||
|
||||
var namespaceInfo = map[NamespaceType]int{
|
||||
NEWNET: syscall.CLONE_NEWNET,
|
||||
NEWNS: syscall.CLONE_NEWNS,
|
||||
NEWUSER: syscall.CLONE_NEWUSER,
|
||||
NEWIPC: syscall.CLONE_NEWIPC,
|
||||
NEWUTS: syscall.CLONE_NEWUTS,
|
||||
NEWPID: syscall.CLONE_NEWPID,
|
||||
}
|
||||
|
||||
// CloneFlags parses the container's Namespaces options to set the correct
|
||||
// flags on clone, unshare. This functions returns flags only for new namespaces.
|
||||
func (n *Namespaces) CloneFlags() uintptr {
|
||||
var flag int
|
||||
for _, v := range *n {
|
||||
if v.Path != "" {
|
||||
continue
|
||||
}
|
||||
flag |= namespaceInfo[v.Type]
|
||||
}
|
||||
return uintptr(flag)
|
||||
}
|
||||
|
|
31
vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall.go
vendored
Normal file
31
vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall.go
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
// +build linux
|
||||
|
||||
package configs
|
||||
|
||||
import "syscall"
|
||||
|
||||
func (n *Namespace) Syscall() int {
|
||||
return namespaceInfo[n.Type]
|
||||
}
|
||||
|
||||
var namespaceInfo = map[NamespaceType]int{
|
||||
NEWNET: syscall.CLONE_NEWNET,
|
||||
NEWNS: syscall.CLONE_NEWNS,
|
||||
NEWUSER: syscall.CLONE_NEWUSER,
|
||||
NEWIPC: syscall.CLONE_NEWIPC,
|
||||
NEWUTS: syscall.CLONE_NEWUTS,
|
||||
NEWPID: syscall.CLONE_NEWPID,
|
||||
}
|
||||
|
||||
// CloneFlags parses the container's Namespaces options to set the correct
|
||||
// flags on clone, unshare. This functions returns flags only for new namespaces.
|
||||
func (n *Namespaces) CloneFlags() uintptr {
|
||||
var flag int
|
||||
for _, v := range *n {
|
||||
if v.Path != "" {
|
||||
continue
|
||||
}
|
||||
flag |= namespaceInfo[v.Type]
|
||||
}
|
||||
return uintptr(flag)
|
||||
}
|
15
vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go
vendored
Normal file
15
vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// +build !linux
|
||||
|
||||
package configs
|
||||
|
||||
func (n *Namespace) Syscall() int {
|
||||
panic("No namespace syscall support")
|
||||
return 0
|
||||
}
|
||||
|
||||
// CloneFlags parses the container's Namespaces options to set the correct
|
||||
// flags on clone, unshare. This functions returns flags only for new namespaces.
|
||||
func (n *Namespaces) CloneFlags() uintptr {
|
||||
panic("No namespace syscall support")
|
||||
return uintptr(0)
|
||||
}
|
|
@ -2,7 +2,7 @@ package configs
|
|||
|
||||
// Network defines configuration for a container's networking stack
|
||||
//
|
||||
// The network configuration can be omited from a container causing the
|
||||
// The network configuration can be omitted from a container causing the
|
||||
// container to be setup with the host's networking stack
|
||||
type Network struct {
|
||||
// Type sets the networks type, commonly veth and loopback
|
||||
|
@ -53,7 +53,7 @@ type Network struct {
|
|||
// 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
|
||||
// One of the three options must be present, and omitted 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.
|
||||
|
|
|
@ -38,7 +38,7 @@ func newConsole(uid, gid int) (Console, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// newConsoleFromPath is an internal fucntion returning an initialzied console for use inside
|
||||
// newConsoleFromPath is an internal function returning an initialized console for use inside
|
||||
// a container's MNT namespace.
|
||||
func newConsoleFromPath(slavePath string) *linuxConsole {
|
||||
return &linuxConsole{
|
||||
|
|
|
@ -67,7 +67,7 @@ type Container interface {
|
|||
// State returns the current container's state information.
|
||||
//
|
||||
// errors:
|
||||
// Systemerror - System erroor.
|
||||
// Systemerror - System error.
|
||||
State() (*State, error)
|
||||
|
||||
// Returns the current config of the container.
|
||||
|
|
|
@ -16,6 +16,8 @@ import (
|
|||
"github.com/docker/libcontainer/configs"
|
||||
)
|
||||
|
||||
const stdioFdCount = 3
|
||||
|
||||
type linuxContainer struct {
|
||||
id string
|
||||
root string
|
||||
|
@ -139,7 +141,8 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
|||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.ExtraFiles = []*os.File{childPipe}
|
||||
cmd.ExtraFiles = append(p.ExtraFiles, childPipe)
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
|
||||
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
|
||||
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
|
||||
// even with the parent still running.
|
||||
|
@ -178,11 +181,9 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
|||
fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()),
|
||||
"_LIBCONTAINER_INITTYPE=setns",
|
||||
)
|
||||
|
||||
if p.consolePath != "" {
|
||||
cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath)
|
||||
}
|
||||
|
||||
// TODO: set on container for process management
|
||||
return &setnsProcess{
|
||||
cmd: cmd,
|
||||
|
@ -195,13 +196,14 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
|||
|
||||
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||
return &initConfig{
|
||||
Config: c.config,
|
||||
Args: process.Args,
|
||||
Env: process.Env,
|
||||
User: process.User,
|
||||
Cwd: process.Cwd,
|
||||
Console: process.consolePath,
|
||||
Capabilities: process.Capabilities,
|
||||
Config: c.config,
|
||||
Args: process.Args,
|
||||
Env: process.Env,
|
||||
User: process.User,
|
||||
Cwd: process.Cwd,
|
||||
Console: process.consolePath,
|
||||
Capabilities: process.Capabilities,
|
||||
PassedFilesCount: len(process.ExtraFiles),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ var (
|
|||
ioutilReadDir = ioutil.ReadDir
|
||||
)
|
||||
|
||||
// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct.
|
||||
// Given the path to a device and it's cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct.
|
||||
func DeviceFromPath(path, permissions string) (*configs.Device, error) {
|
||||
fileInfo, err := osLstat(path)
|
||||
if err != nil {
|
||||
|
|
|
@ -32,15 +32,13 @@ type Factory interface {
|
|||
// System error
|
||||
Load(id string) (Container, error)
|
||||
|
||||
// StartInitialization is an internal API to libcontainer used during the rexec of the
|
||||
// container. pipefd is the fd to the child end of the pipe used to syncronize the
|
||||
// parent and child process providing state and configuration to the child process and
|
||||
// returning any errors during the init of the container
|
||||
// StartInitialization is an internal API to libcontainer used during the reexec of the
|
||||
// container.
|
||||
//
|
||||
// Errors:
|
||||
// pipe connection error
|
||||
// system error
|
||||
StartInitialization(pipefd uintptr) error
|
||||
// Pipe connection error
|
||||
// System error
|
||||
StartInitialization() error
|
||||
|
||||
// Type returns info string about factory type (e.g. lxc, libcontainer...)
|
||||
Type() string
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
|
@ -194,7 +195,11 @@ func (l *LinuxFactory) Type() string {
|
|||
|
||||
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
|
||||
// This is a low level implementation detail of the reexec and should not be consumed externally
|
||||
func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) {
|
||||
func (l *LinuxFactory) StartInitialization() (err error) {
|
||||
pipefd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_INITPIPE"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
pipe = os.NewFile(uintptr(pipefd), "pipe")
|
||||
it = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
|
||||
|
|
|
@ -40,14 +40,15 @@ type network struct {
|
|||
|
||||
// initConfig is used for transferring parameters from Exec() to Init()
|
||||
type initConfig struct {
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
Cwd string `json:"cwd"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
User string `json:"user"`
|
||||
Config *configs.Config `json:"config"`
|
||||
Console string `json:"console"`
|
||||
Networks []*network `json:"network"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
Cwd string `json:"cwd"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
User string `json:"user"`
|
||||
Config *configs.Config `json:"config"`
|
||||
Console string `json:"console"`
|
||||
Networks []*network `json:"network"`
|
||||
PassedFilesCount int `json:"passed_files_count"`
|
||||
}
|
||||
|
||||
type initer interface {
|
||||
|
@ -95,10 +96,10 @@ func populateProcessEnvironment(env []string) error {
|
|||
// and working dir, and closes any leaked file descriptors
|
||||
// before executing the command inside the namespace
|
||||
func finalizeNamespace(config *initConfig) error {
|
||||
// Ensure that all non-standard fds we may have accidentally
|
||||
// Ensure that all unwanted fds we may have accidentally
|
||||
// inherited are marked close-on-exec so they stay out of the
|
||||
// container
|
||||
if err := utils.CloseExecFrom(3); err != nil {
|
||||
if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ import (
|
|||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
|
@ -29,9 +31,7 @@ func testExecPS(t *testing.T, userns bool) {
|
|||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
if userns {
|
||||
|
@ -64,21 +64,15 @@ func TestIPCPrivate(t *testing.T) {
|
|||
}
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
||||
|
@ -95,22 +89,16 @@ func TestIPCHost(t *testing.T) {
|
|||
}
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces.Remove(configs.NEWIPC)
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
||||
|
@ -127,23 +115,17 @@ func TestIPCJoinPath(t *testing.T) {
|
|||
}
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc")
|
||||
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
||||
|
@ -160,9 +142,7 @@ func TestIPCBadPath(t *testing.T) {
|
|||
}
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
|
@ -180,16 +160,12 @@ func TestRlimit(t *testing.T) {
|
|||
}
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" {
|
||||
t.Fatalf("expected rlimit to be 1025, got %s", limit)
|
||||
}
|
||||
|
@ -208,9 +184,7 @@ func newTestRoot() (string, error) {
|
|||
|
||||
func waitProcess(p *libcontainer.Process, t *testing.T) {
|
||||
status, err := p.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
if !status.Success() {
|
||||
t.Fatal(status)
|
||||
}
|
||||
|
@ -221,35 +195,25 @@ func TestEnter(t *testing.T) {
|
|||
return
|
||||
}
|
||||
root, err := newTestRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
// Execute a first process in the container
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
var stdout, stdout2 bytes.Buffer
|
||||
|
||||
|
@ -262,19 +226,13 @@ func TestEnter(t *testing.T) {
|
|||
err = container.Start(&pconfig)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
pid, err := pconfig.Pid()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
// Execute another process in the container
|
||||
stdinR2, stdinW2, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
pconfig2 := libcontainer.Process{
|
||||
Env: standardEnvironment,
|
||||
}
|
||||
|
@ -285,19 +243,13 @@ func TestEnter(t *testing.T) {
|
|||
err = container.Start(&pconfig2)
|
||||
stdinR2.Close()
|
||||
defer stdinW2.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
pid2, err := pconfig2.Pid()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
processes, err := container.Processes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
n := 0
|
||||
for i := range processes {
|
||||
|
@ -318,14 +270,10 @@ func TestEnter(t *testing.T) {
|
|||
|
||||
// Check that both processes live in the same pidns
|
||||
pidns := string(stdout.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
pidns2 := string(stdout2.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
if pidns != pidns2 {
|
||||
t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2)
|
||||
|
@ -337,28 +285,20 @@ func TestProcessEnv(t *testing.T) {
|
|||
return
|
||||
}
|
||||
root, err := newTestRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
var stdout bytes.Buffer
|
||||
|
@ -374,17 +314,12 @@ func TestProcessEnv(t *testing.T) {
|
|||
Stdout: &stdout,
|
||||
}
|
||||
err = container.Start(&pconfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
// Wait for process
|
||||
waitProcess(&pconfig, t)
|
||||
|
||||
outputEnv := string(stdout.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check that the environment has the key/value pair we added
|
||||
if !strings.Contains(outputEnv, "FOO=BAR") {
|
||||
|
@ -402,28 +337,20 @@ func TestProcessCaps(t *testing.T) {
|
|||
return
|
||||
}
|
||||
root, err := newTestRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
processCaps := append(config.Capabilities, "NET_ADMIN")
|
||||
|
@ -437,17 +364,12 @@ func TestProcessCaps(t *testing.T) {
|
|||
Stdout: &stdout,
|
||||
}
|
||||
err = container.Start(&pconfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
// Wait for process
|
||||
waitProcess(&pconfig, t)
|
||||
|
||||
outputStatus := string(stdout.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lines := strings.Split(outputStatus, "\n")
|
||||
|
||||
|
@ -497,37 +419,28 @@ func testFreeze(t *testing.T, systemd bool) {
|
|||
return
|
||||
}
|
||||
root, err := newTestRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
cgm := libcontainer.Cgroupfs
|
||||
if systemd {
|
||||
config.Cgroups.Slice = "system.slice"
|
||||
cgm = libcontainer.SystemdCgroups
|
||||
}
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
factory, err := libcontainer.New(root, cgm)
|
||||
ok(t, err)
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
pconfig := libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
|
@ -537,44 +450,64 @@ func testFreeze(t *testing.T, systemd bool) {
|
|||
err = container.Start(&pconfig)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
pid, err := pconfig.Pid()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
if err := container.Pause(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = container.Pause()
|
||||
ok(t, err)
|
||||
state, err := container.Status()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Resume(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
err = container.Resume()
|
||||
ok(t, err)
|
||||
if state != libcontainer.Paused {
|
||||
t.Fatal("Unexpected state: ", state)
|
||||
}
|
||||
|
||||
stdinW.Close()
|
||||
s, err := process.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
if !s.Success() {
|
||||
t.Fatal(s.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCpuShares(t *testing.T) {
|
||||
testCpuShares(t, false)
|
||||
}
|
||||
|
||||
func TestSystemdCpuShares(t *testing.T) {
|
||||
if !systemd.UseSystemd() {
|
||||
t.Skip("Systemd is unsupported")
|
||||
}
|
||||
testCpuShares(t, true)
|
||||
}
|
||||
|
||||
func testCpuShares(t *testing.T, systemd bool) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
if systemd {
|
||||
config.Cgroups.Slice = "system.slice"
|
||||
}
|
||||
config.Cgroups.CpuShares = 1
|
||||
|
||||
_, _, err = runContainer(config, "", "ps")
|
||||
if err == nil {
|
||||
t.Fatalf("runContainer should failed with invalid CpuShares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerState(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
|
@ -648,3 +581,185 @@ func TestContainerState(t *testing.T) {
|
|||
stdinW.Close()
|
||||
p.Wait()
|
||||
}
|
||||
|
||||
func TestPassExtraFiles(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
|
||||
factory, err := libcontainer.New(rootfs, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer container.Destroy()
|
||||
|
||||
var stdout bytes.Buffer
|
||||
pipeout1, pipein1, err := os.Pipe()
|
||||
pipeout2, pipein2, err := os.Pipe()
|
||||
process := libcontainer.Process{
|
||||
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
|
||||
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
||||
ExtraFiles: []*os.File{pipein1, pipein2},
|
||||
Stdin: nil,
|
||||
Stdout: &stdout,
|
||||
}
|
||||
err = container.Start(&process)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
waitProcess(&process, t)
|
||||
|
||||
out := string(stdout.Bytes())
|
||||
// fd 5 is the directory handle for /proc/$$/fd
|
||||
if out != "0 1 2 3 4 5" {
|
||||
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out)
|
||||
}
|
||||
var buf = []byte{0}
|
||||
_, err = pipeout1.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out1 := string(buf)
|
||||
if out1 != "1" {
|
||||
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
|
||||
}
|
||||
|
||||
_, err = pipeout2.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out2 := string(buf)
|
||||
if out2 != "2" {
|
||||
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountCmds(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
root, err := newTestRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer remove(rootfs)
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "tmpdir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Mounts = append(config.Mounts, &configs.Mount{
|
||||
Source: tmpDir,
|
||||
Destination: filepath.Join(rootfs, "tmp"),
|
||||
Device: "bind",
|
||||
Flags: syscall.MS_BIND | syscall.MS_REC,
|
||||
PremountCmds: []configs.Command{
|
||||
{Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}},
|
||||
{Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}},
|
||||
},
|
||||
PostmountCmds: []configs.Command{
|
||||
{Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}},
|
||||
{Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}},
|
||||
},
|
||||
})
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer container.Destroy()
|
||||
|
||||
pconfig := libcontainer.Process{
|
||||
Args: []string{"sh", "-c", "env"},
|
||||
Env: standardEnvironment,
|
||||
}
|
||||
err = container.Start(&pconfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for process
|
||||
waitProcess(&pconfig, t)
|
||||
|
||||
entries, err := ioutil.ReadDir(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"hello", "hello-backup", "world", "world-backup"}
|
||||
for i, e := range entries {
|
||||
if e.Name() != expected[i] {
|
||||
t.Errorf("Got(%s), expect %s", e.Name(), expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemProperties(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
root, err := newTestRoot()
|
||||
ok(t, err)
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
rootfs, err := newRootfs()
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.SystemProperties = map[string]string{
|
||||
"kernel.shmmni": "8192",
|
||||
}
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
ok(t, err)
|
||||
|
||||
container, err := factory.Create("test", config)
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
var stdout bytes.Buffer
|
||||
pconfig := libcontainer.Process{
|
||||
Args: []string{"sh", "-c", "cat /proc/sys/kernel/shmmni"},
|
||||
Env: standardEnvironment,
|
||||
Stdin: nil,
|
||||
Stdout: &stdout,
|
||||
}
|
||||
err = container.Start(&pconfig)
|
||||
ok(t, err)
|
||||
|
||||
// Wait for process
|
||||
waitProcess(&pconfig, t)
|
||||
|
||||
shmmniOutput := strings.TrimSpace(string(stdout.Bytes()))
|
||||
if shmmniOutput != "8192" {
|
||||
t.Fatalf("kernel.shmmni property expected to be 8192, but is %s", shmmniOutput)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,22 +16,16 @@ func TestExecIn(t *testing.T) {
|
|||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
container, err := newContainer(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
// Execute a first process in the container
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
process := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
|
@ -40,9 +34,7 @@ func TestExecIn(t *testing.T) {
|
|||
err = container.Start(process)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
buffers := newStdBuffers()
|
||||
ps := &libcontainer.Process{
|
||||
|
@ -53,12 +45,9 @@ func TestExecIn(t *testing.T) {
|
|||
Stderr: buffers.Stderr,
|
||||
}
|
||||
err = container.Start(ps)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ps.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
_, err = ps.Wait()
|
||||
ok(t, err)
|
||||
stdinW.Close()
|
||||
if _, err := process.Wait(); err != nil {
|
||||
t.Log(err)
|
||||
|
@ -74,21 +63,15 @@ func TestExecInRlimit(t *testing.T) {
|
|||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
container, err := newContainer(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
process := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
|
@ -97,9 +80,7 @@ func TestExecInRlimit(t *testing.T) {
|
|||
err = container.Start(process)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
buffers := newStdBuffers()
|
||||
ps := &libcontainer.Process{
|
||||
|
@ -110,12 +91,9 @@ func TestExecInRlimit(t *testing.T) {
|
|||
Stderr: buffers.Stderr,
|
||||
}
|
||||
err = container.Start(ps)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ps.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
_, err = ps.Wait()
|
||||
ok(t, err)
|
||||
stdinW.Close()
|
||||
if _, err := process.Wait(); err != nil {
|
||||
t.Log(err)
|
||||
|
@ -131,22 +109,16 @@ func TestExecInError(t *testing.T) {
|
|||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
container, err := newContainer(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
// Execute a first process in the container
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
process := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
|
@ -160,9 +132,7 @@ func TestExecInError(t *testing.T) {
|
|||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok(t, err)
|
||||
|
||||
unexistent := &libcontainer.Process{
|
||||
Args: []string{"unexistent"},
|
||||
|
@ -178,6 +148,121 @@ func TestExecInError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestExecInTTY(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
container, err := newContainer(config)
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
// Execute a first process in the container
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
ok(t, err)
|
||||
process := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
Stdin: stdinR,
|
||||
}
|
||||
err = container.Start(process)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
ok(t, err)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
ps := &libcontainer.Process{
|
||||
Args: []string{"ps"},
|
||||
Env: standardEnvironment,
|
||||
}
|
||||
console, err := ps.NewConsole(0)
|
||||
copy := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(&stdout, console)
|
||||
close(copy)
|
||||
}()
|
||||
ok(t, err)
|
||||
err = container.Start(ps)
|
||||
ok(t, err)
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Waiting for copy timed out")
|
||||
case <-copy:
|
||||
}
|
||||
_, err = ps.Wait()
|
||||
ok(t, err)
|
||||
stdinW.Close()
|
||||
if _, err := process.Wait(); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
out := stdout.String()
|
||||
if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") {
|
||||
t.Fatalf("unexpected running process, output %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecInEnvironment(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
ok(t, err)
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
container, err := newContainer(config)
|
||||
ok(t, err)
|
||||
defer container.Destroy()
|
||||
|
||||
// Execute a first process in the container
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
ok(t, err)
|
||||
process := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
Stdin: stdinR,
|
||||
}
|
||||
err = container.Start(process)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
ok(t, err)
|
||||
|
||||
buffers := newStdBuffers()
|
||||
process2 := &libcontainer.Process{
|
||||
Args: []string{"env"},
|
||||
Env: []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"DEBUG=true",
|
||||
"DEBUG=false",
|
||||
"ENV=test",
|
||||
},
|
||||
Stdin: buffers.Stdin,
|
||||
Stdout: buffers.Stdout,
|
||||
Stderr: buffers.Stderr,
|
||||
}
|
||||
err = container.Start(process2)
|
||||
ok(t, err)
|
||||
if _, err := process2.Wait(); err != nil {
|
||||
out := buffers.Stdout.String()
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
stdinW.Close()
|
||||
if _, err := process.Wait(); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
out := buffers.Stdout.String()
|
||||
// check execin's process environment
|
||||
if !strings.Contains(out, "DEBUG=false") ||
|
||||
!strings.Contains(out, "ENV=test") ||
|
||||
!strings.Contains(out, "HOME=/root") ||
|
||||
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
|
||||
strings.Contains(out, "DEBUG=true") {
|
||||
t.Fatalf("unexpected running process, output %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecinPassExtraFiles(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
@ -211,106 +296,45 @@ func TestExecInTTY(t *testing.T) {
|
|||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
ps := &libcontainer.Process{
|
||||
Args: []string{"ps"},
|
||||
Env: standardEnvironment,
|
||||
pipeout1, pipein1, err := os.Pipe()
|
||||
pipeout2, pipein2, err := os.Pipe()
|
||||
inprocess := &libcontainer.Process{
|
||||
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
|
||||
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
||||
ExtraFiles: []*os.File{pipein1, pipein2},
|
||||
Stdin: nil,
|
||||
Stdout: &stdout,
|
||||
}
|
||||
console, err := ps.NewConsole(0)
|
||||
copy := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(&stdout, console)
|
||||
close(copy)
|
||||
}()
|
||||
err = container.Start(inprocess)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = container.Start(ps)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Waiting for copy timed out")
|
||||
case <-copy:
|
||||
}
|
||||
if _, err := ps.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
waitProcess(inprocess, t)
|
||||
stdinW.Close()
|
||||
if _, err := process.Wait(); err != nil {
|
||||
t.Log(err)
|
||||
waitProcess(process, t)
|
||||
|
||||
out := string(stdout.Bytes())
|
||||
// fd 5 is the directory handle for /proc/$$/fd
|
||||
if out != "0 1 2 3 4 5" {
|
||||
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
|
||||
}
|
||||
out := stdout.String()
|
||||
if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") {
|
||||
t.Fatalf("unexpected running process, output %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecInEnvironment(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
rootfs, err := newRootfs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer remove(rootfs)
|
||||
config := newTemplateConfig(rootfs)
|
||||
container, err := newContainer(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer container.Destroy()
|
||||
|
||||
// Execute a first process in the container
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
process := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
Stdin: stdinR,
|
||||
}
|
||||
err = container.Start(process)
|
||||
stdinR.Close()
|
||||
defer stdinW.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buffers := newStdBuffers()
|
||||
process2 := &libcontainer.Process{
|
||||
Args: []string{"env"},
|
||||
Env: []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"DEBUG=true",
|
||||
"DEBUG=false",
|
||||
"ENV=test",
|
||||
},
|
||||
Stdin: buffers.Stdin,
|
||||
Stdout: buffers.Stdout,
|
||||
Stderr: buffers.Stderr,
|
||||
}
|
||||
err = container.Start(process2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := process2.Wait(); err != nil {
|
||||
out := buffers.Stdout.String()
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
stdinW.Close()
|
||||
if _, err := process.Wait(); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
out := buffers.Stdout.String()
|
||||
// check execin's process environment
|
||||
if !strings.Contains(out, "DEBUG=false") ||
|
||||
!strings.Contains(out, "ENV=test") ||
|
||||
!strings.Contains(out, "HOME=/root") ||
|
||||
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
|
||||
strings.Contains(out, "DEBUG=true") {
|
||||
t.Fatalf("unexpected running process, output %q", out)
|
||||
var buf = []byte{0}
|
||||
_, err = pipeout1.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out1 := string(buf)
|
||||
if out1 != "1" {
|
||||
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
|
||||
}
|
||||
|
||||
_, err = pipeout2.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out2 := string(buf)
|
||||
if out2 != "2" {
|
||||
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func init() {
|
|||
if err != nil {
|
||||
log.Fatalf("unable to initialize for container: %s", err)
|
||||
}
|
||||
if err := factory.StartInitialization(3); err != nil {
|
||||
if err := factory.StartInitialization(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,11 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/configs"
|
||||
|
@ -38,6 +41,14 @@ func (b *stdBuffers) String() string {
|
|||
return strings.Join(s, "|")
|
||||
}
|
||||
|
||||
// ok fails the test if an err is not nil.
|
||||
func ok(t testing.TB, err error) {
|
||||
if err != nil {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// newRootfs creates a new tmp directory and copies the busybox root filesystem
|
||||
func newRootfs() (string, error) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
|
|
|
@ -101,10 +101,22 @@ func SetFileCreateLabel(fileLabel string) error {
|
|||
// 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 {
|
||||
exclude_path := []string{"/", "/usr", "/etc"}
|
||||
if fileLabel == "" {
|
||||
return nil
|
||||
}
|
||||
if relabel == "z" {
|
||||
for _, p := range exclude_path {
|
||||
if path == p {
|
||||
return fmt.Errorf("Relabeling of %s is not allowed", path)
|
||||
}
|
||||
}
|
||||
if !strings.ContainsAny(relabel, "zZ") {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(relabel, "z") && strings.Contains(relabel, "Z") {
|
||||
return fmt.Errorf("Bad SELinux option z and Z can not be used together")
|
||||
}
|
||||
if strings.Contains(relabel, "z") {
|
||||
c := selinux.NewContext(fileLabel)
|
||||
c["level"] = "s0"
|
||||
fileLabel = c.Get()
|
||||
|
|
|
@ -87,3 +87,31 @@ func TestDuplicateLabel(t *testing.T) {
|
|||
t.Errorf("DisableSecOpt Failed level incorrect")
|
||||
}
|
||||
}
|
||||
func TestRelabel(t *testing.T) {
|
||||
testdir := "/tmp/test"
|
||||
label := "system_u:system_r:svirt_sandbox_file_t:s0:c1,c2"
|
||||
if err := Relabel(testdir, "", "z"); err != nil {
|
||||
t.Fatal("Relabel with no label failed: %v", err)
|
||||
}
|
||||
if err := Relabel(testdir, label, ""); err != nil {
|
||||
t.Fatal("Relabel with no relabel field failed: %v", err)
|
||||
}
|
||||
if err := Relabel(testdir, label, "z"); err != nil {
|
||||
t.Fatal("Relabel shared failed: %v", err)
|
||||
}
|
||||
if err := Relabel(testdir, label, "Z"); err != nil {
|
||||
t.Fatal("Relabel unshared failed: %v", err)
|
||||
}
|
||||
if err := Relabel(testdir, label, "zZ"); err == nil {
|
||||
t.Fatal("Relabel with shared and unshared succeeded")
|
||||
}
|
||||
if err := Relabel("/etc", label, "zZ"); err == nil {
|
||||
t.Fatal("Relabel /etc succeeded")
|
||||
}
|
||||
if err := Relabel("/", label, ""); err == nil {
|
||||
t.Fatal("Relabel / succeeded")
|
||||
}
|
||||
if err := Relabel("/usr", label, "Z"); err == nil {
|
||||
t.Fatal("Relabel /usr succeeded")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
## nsenter
|
||||
|
||||
The `nsenter` package registers a special init constructor that is called before the Go runtime has
|
||||
a chance to boot. This provides us the ability to `setns` on existing namespaces and avoid the issues
|
||||
that the Go runtime has with multiple threads. This constructor is only called if this package is
|
||||
registered, imported, in your go application and the argv 0 is `nsenter`.
|
||||
The `nsenter` package registers a special init constructor that is called before
|
||||
the Go runtime has a chance to boot. This provides us the ability to `setns` on
|
||||
existing namespaces and avoid the issues that the Go runtime has with multiple
|
||||
threads. This constructor will be called if this package is registered,
|
||||
imported, in your go application.
|
||||
|
||||
The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/)
|
||||
package. In cgo, if the import of "C" is immediately preceded by a comment, that comment,
|
||||
called the preamble, is used as a header when compiling the C parts of the package.
|
||||
So every time we import package `nsenter`, the C code function `nsexec()` would be
|
||||
called. And package `nsenter` is now only imported in Docker execdriver, so every time
|
||||
before we call `execdriver.Exec()`, that C code would run.
|
||||
|
||||
`nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID`
|
||||
which will give the process of the container that should be joined. Namespaces fd will
|
||||
be found from `/proc/[pid]/ns` and set by `setns` syscall.
|
||||
|
||||
And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could
|
||||
be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will
|
||||
have value and start a console for output.
|
||||
|
||||
Finally, `nsexec()` will clone a child process , exit the parent process and let
|
||||
the Go runtime take over.
|
||||
|
|
|
@ -24,7 +24,7 @@ func TestNsenterAlivePid(t *testing.T) {
|
|||
Path: os.Args[0],
|
||||
Args: args,
|
||||
ExtraFiles: []*os.File{w},
|
||||
Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid())},
|
||||
Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"},
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
|
|
|
@ -66,7 +66,7 @@ void nsexec()
|
|||
const int num = sizeof(namespaces) / sizeof(char *);
|
||||
jmp_buf env;
|
||||
char buf[PATH_MAX], *val;
|
||||
int i, tfd, child, len, consolefd = -1;
|
||||
int i, tfd, child, len, pipenum, consolefd = -1;
|
||||
pid_t pid;
|
||||
char *console;
|
||||
|
||||
|
@ -81,6 +81,19 @@ void nsexec()
|
|||
exit(1);
|
||||
}
|
||||
|
||||
val = getenv("_LIBCONTAINER_INITPIPE");
|
||||
if (val == NULL) {
|
||||
pr_perror("Child pipe not found");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pipenum = atoi(val);
|
||||
snprintf(buf, sizeof(buf), "%d", pipenum);
|
||||
if (strcmp(val, buf)) {
|
||||
pr_perror("Unable to parse _LIBCONTAINER_INITPIPE");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
console = getenv("_LIBCONTAINER_CONSOLE_PATH");
|
||||
if (console != NULL) {
|
||||
consolefd = open(console, O_RDWR);
|
||||
|
@ -124,6 +137,8 @@ void nsexec()
|
|||
}
|
||||
|
||||
if (setjmp(env) == 1) {
|
||||
// Child
|
||||
|
||||
if (setsid() == -1) {
|
||||
pr_perror("setsid failed");
|
||||
exit(1);
|
||||
|
@ -149,7 +164,11 @@ void nsexec()
|
|||
// Finish executing, let the Go runtime take over.
|
||||
return;
|
||||
}
|
||||
// Parent
|
||||
|
||||
// We must fork to actually enter the PID namespace, use CLONE_PARENT
|
||||
// so the child can have the right parent, and we don't need to forward
|
||||
// the child's exit code or resend its death signal.
|
||||
child = clone_parent(&env);
|
||||
if (child < 0) {
|
||||
pr_perror("Unable to fork");
|
||||
|
@ -158,7 +177,7 @@ void nsexec()
|
|||
|
||||
len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child);
|
||||
|
||||
if (write(3, buf, len) != len) {
|
||||
if (write(pipenum, buf, len) != len) {
|
||||
pr_perror("Unable to send a child pid");
|
||||
kill(child, SIGKILL);
|
||||
exit(1);
|
||||
|
|
|
@ -65,3 +65,48 @@ You can identify if a process is running in a container by looking to see if
|
|||
|
||||
You may also specify an alternate root directory from where the `container.json`
|
||||
file is read and where the `state.json` file will be saved.
|
||||
|
||||
### How to use?
|
||||
|
||||
Currently nsinit has 9 commands. Type `nsinit -h` to list all of them.
|
||||
And for every alternative command, you can also use `--help` to get more
|
||||
detailed help documents. For example, `nsinit config --help`.
|
||||
|
||||
`nsinit` cli application is implemented using [cli.go](https://github.com/codegangsta/cli).
|
||||
Lots of details are handled in cli.go, so the implementation of `nsinit` itself
|
||||
is very clean and clear.
|
||||
|
||||
* **config**
|
||||
It will generate a standard configuration file for a container. By default, it
|
||||
will generate as the template file in [config.go](https://github.com/docker/libcontainer/blob/master/nsinit/config.go#L192).
|
||||
It will modify the template if you have specified some configuration by options.
|
||||
* **exec**
|
||||
Starts a container and execute a new command inside it. Besides common options, it
|
||||
has some special options as below.
|
||||
- `--tty,-t`: allocate a TTY to the container.
|
||||
- `--config`: you can specify a configuration file. By default, it will use
|
||||
template configuration.
|
||||
- `--id`: specify the ID for a container. By default, the id is "nsinit".
|
||||
- `--user,-u`: set the user, uid, and/or gid for the process. By default the
|
||||
value is "root".
|
||||
- `--cwd`: set the current working dir.
|
||||
- `--env`: set environment variables for the process.
|
||||
* **init**
|
||||
It's an internal command that is called inside the container's namespaces to
|
||||
initialize the namespace and exec the user's process. It should not be called
|
||||
externally.
|
||||
* **oom**
|
||||
Display oom notifications for a container, you should specify container id.
|
||||
* **pause**
|
||||
Pause the container's processes, you should specify container id. It will use
|
||||
cgroup freeze subsystem to help.
|
||||
* **unpause**
|
||||
Unpause the container's processes. Same with `pause`.
|
||||
* **stats**
|
||||
Display statistics for the container, it will mainly show cgroup and network
|
||||
statistics.
|
||||
* **state**
|
||||
Get the container's current state. You can also read the state from `state.json`
|
||||
in your container_id folder.
|
||||
* **help, h**
|
||||
Shows a list of commands or help for one command.
|
||||
|
|
|
@ -43,6 +43,7 @@ var createFlags = []cli.Flag{
|
|||
cli.StringFlag{Name: "veth-address", Usage: "veth ip address"},
|
||||
cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"},
|
||||
cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"},
|
||||
cli.BoolFlag{Name: "cgroup", Usage: "mount the cgroup data for the container"},
|
||||
}
|
||||
|
||||
var configCommand = cli.Command{
|
||||
|
@ -187,6 +188,12 @@ func modify(config *configs.Config, context *cli.Context) {
|
|||
}
|
||||
config.Networks = append(config.Networks, network)
|
||||
}
|
||||
if context.Bool("cgroup") {
|
||||
config.Mounts = append(config.Mounts, &configs.Mount{
|
||||
Destination: "/sys/fs/cgroup",
|
||||
Device: "cgroup",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getTemplate() *configs.Config {
|
||||
|
|
|
@ -23,6 +23,7 @@ var execCommand = cli.Command{
|
|||
Action: execAction,
|
||||
Flags: append([]cli.Flag{
|
||||
cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"},
|
||||
cli.BoolFlag{Name: "systemd", Usage: "Use systemd for managing cgroups, if available"},
|
||||
cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"},
|
||||
cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"},
|
||||
cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"},
|
||||
|
|
|
@ -20,7 +20,7 @@ var initCommand = cli.Command{
|
|||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if err := factory.StartInitialization(3); err != nil {
|
||||
if err := factory.StartInitialization(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
panic("This line should never been executed")
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups/systemd"
|
||||
"github.com/docker/libcontainer/configs"
|
||||
)
|
||||
|
||||
|
@ -29,7 +31,15 @@ func loadConfig(context *cli.Context) (*configs.Config, error) {
|
|||
}
|
||||
|
||||
func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
|
||||
return libcontainer.New(context.GlobalString("root"), libcontainer.Cgroupfs)
|
||||
cgm := libcontainer.Cgroupfs
|
||||
if context.Bool("systemd") {
|
||||
if systemd.UseSystemd() {
|
||||
cgm = libcontainer.SystemdCgroups
|
||||
} else {
|
||||
log.Warn("systemd cgroup flag passed, but systemd support for managing cgroups is not available.")
|
||||
}
|
||||
}
|
||||
return libcontainer.New(context.GlobalString("root"), cgm)
|
||||
}
|
||||
|
||||
func getContainer(context *cli.Context) (libcontainer.Container, error) {
|
||||
|
|
|
@ -23,7 +23,7 @@ type Process struct {
|
|||
Env []string
|
||||
|
||||
// User will set the uid and gid of the executing process running inside the container
|
||||
// local to the contaienr's user and group configuration.
|
||||
// local to the container's user and group configuration.
|
||||
User string
|
||||
|
||||
// Cwd will change the processes current working directory inside the container's rootfs.
|
||||
|
@ -38,11 +38,14 @@ type Process struct {
|
|||
// Stderr is a pointer to a writer which receives the standard error stream.
|
||||
Stderr io.Writer
|
||||
|
||||
// ExtraFiles specifies additional open files to be inherited by the container
|
||||
ExtraFiles []*os.File
|
||||
|
||||
// consolePath is the path to the console allocated to the container.
|
||||
consolePath string
|
||||
|
||||
// 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
|
||||
// All capabilities not specified will be dropped from the processes capability mask
|
||||
Capabilities []string
|
||||
|
||||
ops processOperations
|
||||
|
|
|
@ -119,6 +119,9 @@ func (p *setnsProcess) execSetns() error {
|
|||
// terminate sends a SIGKILL to the forked process for the setns routine then waits to
|
||||
// avoid the process becomming a zombie.
|
||||
func (p *setnsProcess) terminate() error {
|
||||
if p.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
err := p.cmd.Process.Kill()
|
||||
if _, werr := p.wait(); err == nil {
|
||||
err = werr
|
||||
|
|
|
@ -6,11 +6,14 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
"github.com/docker/libcontainer/configs"
|
||||
"github.com/docker/libcontainer/label"
|
||||
)
|
||||
|
@ -24,9 +27,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
|
|||
return newSystemError(err)
|
||||
}
|
||||
for _, m := range config.Mounts {
|
||||
for _, precmd := range m.PremountCmds {
|
||||
if err := mountCmd(precmd); err != nil {
|
||||
return newSystemError(err)
|
||||
}
|
||||
}
|
||||
if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
|
||||
return newSystemError(err)
|
||||
}
|
||||
|
||||
for _, postcmd := range m.PostmountCmds {
|
||||
if err := mountCmd(postcmd); err != nil {
|
||||
return newSystemError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := createDevices(config); err != nil {
|
||||
return newSystemError(err)
|
||||
|
@ -62,6 +76,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func mountCmd(cmd configs.Command) error {
|
||||
|
||||
command := exec.Command(cmd.Path, cmd.Args[:]...)
|
||||
command.Env = cmd.Env
|
||||
command.Dir = cmd.Dir
|
||||
if out, err := command.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
|
||||
var (
|
||||
dest = m.Destination
|
||||
|
@ -72,11 +98,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
|
|||
}
|
||||
|
||||
switch m.Device {
|
||||
case "proc", "mqueue", "sysfs":
|
||||
case "proc", "sysfs":
|
||||
if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "")
|
||||
case "mqueue":
|
||||
if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return label.SetFileLabel(dest, mountLabel)
|
||||
case "tmpfs":
|
||||
stat, err := os.Stat(dest)
|
||||
if err != nil {
|
||||
|
@ -126,6 +160,37 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
case "cgroup":
|
||||
mounts, err := cgroups.GetCgroupMounts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var binds []*configs.Mount
|
||||
for _, mm := range mounts {
|
||||
dir, err := mm.GetThisCgroupDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binds = append(binds, &configs.Mount{
|
||||
Device: "bind",
|
||||
Source: filepath.Join(mm.Mountpoint, dir),
|
||||
Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")),
|
||||
Flags: syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY,
|
||||
})
|
||||
}
|
||||
tmpfs := &configs.Mount{
|
||||
Device: "tmpfs",
|
||||
Destination: m.Destination,
|
||||
Flags: syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||
}
|
||||
if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, b := range binds {
|
||||
if err := mountToRootfs(b, rootfs, mountLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination)
|
||||
}
|
||||
|
@ -240,9 +305,9 @@ func mknodDevice(dest string, node *configs.Device) error {
|
|||
}
|
||||
|
||||
func prepareRoot(config *configs.Config) error {
|
||||
flag := syscall.MS_PRIVATE | syscall.MS_REC
|
||||
if config.NoPivotRoot {
|
||||
flag = syscall.MS_SLAVE | syscall.MS_REC
|
||||
flag := syscall.MS_SLAVE | syscall.MS_REC
|
||||
if config.Privatefs {
|
||||
flag = syscall.MS_PRIVATE | syscall.MS_REC
|
||||
}
|
||||
if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
|
||||
return err
|
||||
|
@ -355,3 +420,10 @@ func maskFile(path string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeSystemProperty writes the value to a path under /proc/sys as determined from the key.
|
||||
// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward.
|
||||
func writeSystemProperty(key, value string) error {
|
||||
keyPath := strings.Replace(key, ".", "/", -1)
|
||||
return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644)
|
||||
}
|
||||
|
|
|
@ -64,6 +64,13 @@ func (l *linuxStandardInit) Init() error {
|
|||
if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, value := range l.config.Config.SystemProperties {
|
||||
if err := writeSystemProperty(key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range l.config.Config.ReadonlyPaths {
|
||||
if err := remountReadonly(path); err != nil {
|
||||
return err
|
||||
|
|
|
@ -12,8 +12,10 @@ import (
|
|||
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
|
||||
var setNsMap = map[string]uintptr{
|
||||
"linux/386": 346,
|
||||
"linux/arm64": 268,
|
||||
"linux/amd64": 308,
|
||||
"linux/arm": 374,
|
||||
"linux/arm": 375,
|
||||
"linux/ppc": 350,
|
||||
"linux/ppc64": 350,
|
||||
"linux/ppc64le": 350,
|
||||
"linux/s390x": 339,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build linux,amd64 linux,ppc64 linux,ppc64le linux,s390x
|
||||
// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x
|
||||
|
||||
package system
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ clone() {
|
|||
clone git github.com/codegangsta/cli 1.1.0
|
||||
clone git github.com/coreos/go-systemd v2
|
||||
clone git github.com/godbus/dbus v2
|
||||
clone git github.com/Sirupsen/logrus v0.6.6
|
||||
clone git github.com/Sirupsen/logrus v0.7.3
|
||||
clone git github.com/syndtr/gocapability 8e4cdcb
|
||||
|
||||
# intentionally not vendoring Docker itself... that'd be a circle :)
|
||||
|
|
Loading…
Reference in a new issue