mirror of https://github.com/docker/docs.git
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
|
@ -32,7 +32,7 @@ func initializer() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
if err := factory.StartInitialization(3); err != nil {
|
if err := factory.StartInitialization(); err != nil {
|
||||||
fatal(err)
|
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/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-fsnotify/fsnotify v1.2.0
|
||||||
|
|
||||||
clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
|
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
|
mkdir -p src/github.com/docker/distribution/registry
|
||||||
mv tmp-api src/github.com/docker/distribution/registry/api
|
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)
|
# 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
|
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
|
# 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
|
# 0.7.2
|
||||||
|
|
||||||
formatter/text: Add configuration option for time format (#158)
|
formatter/text: Add configuration option for time format (#158)
|
||||||
|
|
|
@ -108,6 +108,16 @@ func main() {
|
||||||
"omg": true,
|
"omg": true,
|
||||||
"number": 100,
|
"number": 100,
|
||||||
}).Fatal("The ice breaks!")
|
}).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)
|
| Hook | Description |
|
||||||
Send errors to the Papertrail hosted logging service via UDP.
|
| ----- | ----------- |
|
||||||
|
| [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/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
|
||||||
Send errors to remote syslog server.
|
| [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. |
|
||||||
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. |
|
||||||
* [`github.com/Sirupsen/logrus/hooks/bugsnag`](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go)
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
Send errors to the Bugsnag exception tracking service.
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
|
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||||
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||||
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`.
|
|
||||||
|
|
||||||
#### Level logging
|
#### Level logging
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const DefaultTimestampFormat = time.RFC3339
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
//
|
//
|
||||||
|
|
|
@ -3,19 +3,27 @@ package logstash
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Formatter generates json in logstash format.
|
// Formatter generates json in logstash format.
|
||||||
// Logstash site: http://logstash.net/
|
// Logstash site: http://logstash.net/
|
||||||
type LogstashFormatter struct {
|
type LogstashFormatter struct {
|
||||||
Type string // if not empty use for logstash type field.
|
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) {
|
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||||
entry.Data["@version"] = 1
|
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
|
// set message field
|
||||||
v, ok := entry.Data["message"]
|
v, ok := entry.Data["message"]
|
||||||
|
|
|
@ -3,10 +3,12 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"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) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields, len(entry.Data)+3)
|
data := make(Fields, len(entry.Data)+3)
|
||||||
|
@ -21,7 +23,12 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefixFieldClashes(data)
|
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["msg"] = entry.Message
|
||||||
data["level"] = entry.Level.String()
|
data["level"] = entry.Level.String()
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
baseTimestamp time.Time
|
baseTimestamp time.Time
|
||||||
isTerminal bool
|
isTerminal bool
|
||||||
defaultTimestampFormat = time.RFC3339
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -47,7 +46,7 @@ type TextFormatter struct {
|
||||||
// the time passed since beginning of execution.
|
// the time passed since beginning of execution.
|
||||||
FullTimestamp bool
|
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
|
TimestampFormat string
|
||||||
|
|
||||||
// The fields are sorted by default for a consistent output. For applications
|
// 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
|
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||||
|
|
||||||
if f.TimestampFormat == "" {
|
if f.TimestampFormat == "" {
|
||||||
f.TimestampFormat = defaultTimestampFormat
|
f.TimestampFormat = DefaultTimestampFormat
|
||||||
}
|
}
|
||||||
if isColored {
|
if isColored {
|
||||||
f.printColored(b, entry, keys)
|
f.printColored(b, entry, keys)
|
||||||
|
|
|
@ -14,8 +14,10 @@ import (
|
||||||
|
|
||||||
func IsEnabled() bool {
|
func IsEnabled() bool {
|
||||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
|
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
|
||||||
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
|
||||||
return err == nil && len(buf) > 1 && buf[0] == 'Y'
|
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
||||||
|
return err == nil && len(buf) > 1 && buf[0] == 'Y'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -19,6 +21,7 @@ var (
|
||||||
"cpuset": &CpusetGroup{},
|
"cpuset": &CpusetGroup{},
|
||||||
"cpuacct": &CpuacctGroup{},
|
"cpuacct": &CpuacctGroup{},
|
||||||
"blkio": &BlkioGroup{},
|
"blkio": &BlkioGroup{},
|
||||||
|
"hugetlb": &HugetlbGroup{},
|
||||||
"perf_event": &PerfEventGroup{},
|
"perf_event": &PerfEventGroup{},
|
||||||
"freezer": &FreezerGroup{},
|
"freezer": &FreezerGroup{},
|
||||||
}
|
}
|
||||||
|
@ -75,10 +78,13 @@ type data struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Apply(pid int) error {
|
func (m *Manager) Apply(pid int) error {
|
||||||
|
|
||||||
if m.Cgroups == nil {
|
if m.Cgroups == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var c = m.Cgroups
|
||||||
|
|
||||||
d, err := getCgroupData(m.Cgroups, pid)
|
d, err := getCgroupData(m.Cgroups, pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -108,6 +114,12 @@ func (m *Manager) Apply(pid int) error {
|
||||||
}
|
}
|
||||||
m.Paths = paths
|
m.Paths = paths
|
||||||
|
|
||||||
|
if paths["cpu"] != "" {
|
||||||
|
if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,19 +131,6 @@ func (m *Manager) GetPaths() map[string]string {
|
||||||
return m.Paths
|
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) {
|
func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||||
stats := cgroups.NewStats()
|
stats := cgroups.NewStats()
|
||||||
for name, path := range m.Paths {
|
for name, path := range m.Paths {
|
||||||
|
@ -280,3 +279,27 @@ func removePath(p string, err error) error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,8 @@ Total 22061056`
|
||||||
252:0 Async 164
|
252:0 Async 164
|
||||||
252:0 Total 164
|
252:0 Total 164
|
||||||
Total 328`
|
Total 328`
|
||||||
|
throttleBefore = `8:0 1024`
|
||||||
|
throttleAfter = `8:0 2048`
|
||||||
)
|
)
|
||||||
|
|
||||||
func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) {
|
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) {
|
func TestBlkioStats(t *testing.T) {
|
||||||
helper := NewCgroupTestUtil("blkio", t)
|
helper := NewCgroupTestUtil("blkio", t)
|
||||||
defer helper.cleanup()
|
defer helper.cleanup()
|
||||||
|
@ -442,3 +473,96 @@ func TestNonCFQBlkioStats(t *testing.T) {
|
||||||
|
|
||||||
expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
|
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 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
|
return nil
|
||||||
|
|
|
@ -17,7 +17,18 @@ var (
|
||||||
FileMode: 0666,
|
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) {
|
func TestDevicesSetAllow(t *testing.T) {
|
||||||
|
@ -44,3 +55,28 @@ func TestDevicesSetAllow(t *testing.T) {
|
||||||
t.Fatal("Got the wrong value, set devices.allow failed.")
|
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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err)
|
||||||
}
|
}
|
||||||
stats.MemoryStats.Usage = value
|
stats.MemoryStats.Usage = value
|
||||||
|
stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
|
||||||
value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes")
|
value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ type CpuStats struct {
|
||||||
type MemoryStats struct {
|
type MemoryStats struct {
|
||||||
// current res_counter usage for memory
|
// current res_counter usage for memory
|
||||||
Usage uint64 `json:"usage,omitempty"`
|
Usage uint64 `json:"usage,omitempty"`
|
||||||
|
// memory used for cache
|
||||||
|
Cache uint64 `json:"cache,omitempty"`
|
||||||
// maximum usage ever recorded.
|
// maximum usage ever recorded.
|
||||||
MaxUsage uint64 `json:"max_usage,omitempty"`
|
MaxUsage uint64 `json:"max_usage,omitempty"`
|
||||||
// TODO(vishh): Export these as stronger types.
|
// 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")
|
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 {
|
func Freeze(c *configs.Cgroup, state configs.FreezerState) error {
|
||||||
return fmt.Errorf("Systemd not supported")
|
return fmt.Errorf("Systemd not supported")
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ var subsystems = map[string]subsystem{
|
||||||
"cpuset": &fs.CpusetGroup{},
|
"cpuset": &fs.CpusetGroup{},
|
||||||
"cpuacct": &fs.CpuacctGroup{},
|
"cpuacct": &fs.CpuacctGroup{},
|
||||||
"blkio": &fs.BlkioGroup{},
|
"blkio": &fs.BlkioGroup{},
|
||||||
|
"hugetlb": &fs.HugetlbGroup{},
|
||||||
"perf_event": &fs.PerfEventGroup{},
|
"perf_event": &fs.PerfEventGroup{},
|
||||||
"freezer": &fs.FreezerGroup{},
|
"freezer": &fs.FreezerGroup{},
|
||||||
}
|
}
|
||||||
|
@ -216,6 +217,13 @@ func (m *Manager) Apply(pid int) error {
|
||||||
return err
|
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)
|
paths := make(map[string]string)
|
||||||
for sysname := range subsystems {
|
for sysname := range subsystems {
|
||||||
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
|
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
|
||||||
|
@ -228,9 +236,14 @@ func (m *Manager) Apply(pid int) error {
|
||||||
}
|
}
|
||||||
paths[sysname] = subsystemPath
|
paths[sysname] = subsystemPath
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Paths = paths
|
m.Paths = paths
|
||||||
|
|
||||||
|
if paths["cpu"] != "" {
|
||||||
|
if err := fs.CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +363,17 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Set(container *configs.Config) 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 {
|
func getUnitName(c *configs.Cgroup) string {
|
||||||
|
@ -362,7 +385,7 @@ func getUnitName(c *configs.Cgroup) string {
|
||||||
// * Support for wildcards to allow /dev/pts support
|
// * 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
|
// 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.
|
// implementation for backwards compat.
|
||||||
//
|
//
|
||||||
// Note: we can't use systemd to set up the initial limits, and then change the cgroup
|
// 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"]
|
devices := subsystems["devices"]
|
||||||
if err := devices.Set(path, c); err != nil {
|
return devices.Set(path, c)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinMemory(c *configs.Cgroup, pid int) error {
|
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)
|
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"`
|
AllowedDevices []*Device `json:"allowed_devices"`
|
||||||
|
|
||||||
|
DeniedDevices []*Device `json:"denied_devices"`
|
||||||
|
|
||||||
// Memory limit (in bytes)
|
// Memory limit (in bytes)
|
||||||
Memory int64 `json:"memory"`
|
Memory int64 `json:"memory"`
|
||||||
|
|
||||||
|
@ -43,9 +45,24 @@ type Cgroup struct {
|
||||||
// MEM to use
|
// MEM to use
|
||||||
CpusetMems string `json:"cpuset_mems"`
|
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.
|
// Specifies per cgroup weight, range is from 10 to 1000.
|
||||||
BlkioWeight int64 `json:"blkio_weight"`
|
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
|
// set the freeze value for the process
|
||||||
Freezer FreezerState `json:"freezer"`
|
Freezer FreezerState `json:"freezer"`
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,9 @@ type Config struct {
|
||||||
// bind mounts are writtable.
|
// bind mounts are writtable.
|
||||||
Readonlyfs bool `json:"readonlyfs"`
|
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
|
// Mounts specify additional source and destination paths that will be mounted inside the container's
|
||||||
// rootfs and mount namespace if specified
|
// rootfs and mount namespace if specified
|
||||||
Mounts []*Mount `json:"mounts"`
|
Mounts []*Mount `json:"mounts"`
|
||||||
|
@ -96,6 +99,10 @@ type Config struct {
|
||||||
// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
|
// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
|
||||||
// so that these files prevent any writes.
|
// so that these files prevent any writes.
|
||||||
ReadonlyPaths []string `json:"readonly_paths"`
|
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
|
// 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 source if set, "z" indicates shared, "Z" indicates unshared.
|
||||||
Relabel string `json:"relabel"`
|
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
|
package configs
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NamespaceType string
|
type NamespaceType string
|
||||||
|
|
||||||
|
@ -34,10 +31,6 @@ type Namespace struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Namespace) Syscall() int {
|
|
||||||
return namespaceInfo[n.Type]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespace) GetPath(pid int) string {
|
func (n *Namespace) GetPath(pid int) string {
|
||||||
if n.Path != "" {
|
if n.Path != "" {
|
||||||
return n.Path
|
return n.Path
|
||||||
|
@ -96,25 +89,3 @@ func (n *Namespaces) index(t NamespaceType) int {
|
||||||
func (n *Namespaces) Contains(t NamespaceType) bool {
|
func (n *Namespaces) Contains(t NamespaceType) bool {
|
||||||
return n.index(t) != -1
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
// 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
|
// container to be setup with the host's networking stack
|
||||||
type Network struct {
|
type Network struct {
|
||||||
// Type sets the networks type, commonly veth and loopback
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// 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
|
}, 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.
|
// a container's MNT namespace.
|
||||||
func newConsoleFromPath(slavePath string) *linuxConsole {
|
func newConsoleFromPath(slavePath string) *linuxConsole {
|
||||||
return &linuxConsole{
|
return &linuxConsole{
|
||||||
|
|
|
@ -67,7 +67,7 @@ type Container interface {
|
||||||
// State returns the current container's state information.
|
// State returns the current container's state information.
|
||||||
//
|
//
|
||||||
// errors:
|
// errors:
|
||||||
// Systemerror - System erroor.
|
// Systemerror - System error.
|
||||||
State() (*State, error)
|
State() (*State, error)
|
||||||
|
|
||||||
// Returns the current config of the container.
|
// Returns the current config of the container.
|
||||||
|
|
|
@ -16,6 +16,8 @@ import (
|
||||||
"github.com/docker/libcontainer/configs"
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const stdioFdCount = 3
|
||||||
|
|
||||||
type linuxContainer struct {
|
type linuxContainer struct {
|
||||||
id string
|
id string
|
||||||
root string
|
root string
|
||||||
|
@ -139,7 +141,8 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
||||||
if cmd.SysProcAttr == nil {
|
if cmd.SysProcAttr == nil {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
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
|
// 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
|
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
|
||||||
// even with the parent still running.
|
// 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()),
|
fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()),
|
||||||
"_LIBCONTAINER_INITTYPE=setns",
|
"_LIBCONTAINER_INITTYPE=setns",
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.consolePath != "" {
|
if p.consolePath != "" {
|
||||||
cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath)
|
cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: set on container for process management
|
// TODO: set on container for process management
|
||||||
return &setnsProcess{
|
return &setnsProcess{
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
|
@ -195,13 +196,14 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
||||||
|
|
||||||
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||||
return &initConfig{
|
return &initConfig{
|
||||||
Config: c.config,
|
Config: c.config,
|
||||||
Args: process.Args,
|
Args: process.Args,
|
||||||
Env: process.Env,
|
Env: process.Env,
|
||||||
User: process.User,
|
User: process.User,
|
||||||
Cwd: process.Cwd,
|
Cwd: process.Cwd,
|
||||||
Console: process.consolePath,
|
Console: process.consolePath,
|
||||||
Capabilities: process.Capabilities,
|
Capabilities: process.Capabilities,
|
||||||
|
PassedFilesCount: len(process.ExtraFiles),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ var (
|
||||||
ioutilReadDir = ioutil.ReadDir
|
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) {
|
func DeviceFromPath(path, permissions string) (*configs.Device, error) {
|
||||||
fileInfo, err := osLstat(path)
|
fileInfo, err := osLstat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,15 +32,13 @@ type Factory interface {
|
||||||
// System error
|
// System error
|
||||||
Load(id string) (Container, error)
|
Load(id string) (Container, error)
|
||||||
|
|
||||||
// StartInitialization is an internal API to libcontainer used during the rexec of the
|
// StartInitialization is an internal API to libcontainer used during the reexec of the
|
||||||
// container. pipefd is the fd to the child end of the pipe used to syncronize the
|
// container.
|
||||||
// parent and child process providing state and configuration to the child process and
|
|
||||||
// returning any errors during the init of the container
|
|
||||||
//
|
//
|
||||||
// Errors:
|
// Errors:
|
||||||
// pipe connection error
|
// Pipe connection error
|
||||||
// system error
|
// System error
|
||||||
StartInitialization(pipefd uintptr) error
|
StartInitialization() error
|
||||||
|
|
||||||
// Type returns info string about factory type (e.g. lxc, libcontainer...)
|
// Type returns info string about factory type (e.g. lxc, libcontainer...)
|
||||||
Type() string
|
Type() string
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/mount"
|
"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
|
// 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
|
// 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 (
|
var (
|
||||||
pipe = os.NewFile(uintptr(pipefd), "pipe")
|
pipe = os.NewFile(uintptr(pipefd), "pipe")
|
||||||
it = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
|
it = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
|
||||||
|
|
|
@ -40,14 +40,15 @@ type network struct {
|
||||||
|
|
||||||
// initConfig is used for transferring parameters from Exec() to Init()
|
// initConfig is used for transferring parameters from Exec() to Init()
|
||||||
type initConfig struct {
|
type initConfig struct {
|
||||||
Args []string `json:"args"`
|
Args []string `json:"args"`
|
||||||
Env []string `json:"env"`
|
Env []string `json:"env"`
|
||||||
Cwd string `json:"cwd"`
|
Cwd string `json:"cwd"`
|
||||||
Capabilities []string `json:"capabilities"`
|
Capabilities []string `json:"capabilities"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Config *configs.Config `json:"config"`
|
Config *configs.Config `json:"config"`
|
||||||
Console string `json:"console"`
|
Console string `json:"console"`
|
||||||
Networks []*network `json:"network"`
|
Networks []*network `json:"network"`
|
||||||
|
PassedFilesCount int `json:"passed_files_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type initer interface {
|
type initer interface {
|
||||||
|
@ -95,10 +96,10 @@ func populateProcessEnvironment(env []string) error {
|
||||||
// and working dir, and closes any leaked file descriptors
|
// and working dir, and closes any leaked file descriptors
|
||||||
// before executing the command inside the namespace
|
// before executing the command inside the namespace
|
||||||
func finalizeNamespace(config *initConfig) error {
|
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
|
// inherited are marked close-on-exec so they stay out of the
|
||||||
// container
|
// container
|
||||||
if err := utils.CloseExecFrom(3); err != nil {
|
if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
|
@ -29,9 +31,7 @@ func testExecPS(t *testing.T, userns bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
if userns {
|
if userns {
|
||||||
|
@ -64,21 +64,15 @@ func TestIPCPrivate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
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()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
config.Namespaces.Remove(configs.NEWIPC)
|
config.Namespaces.Remove(configs.NEWIPC)
|
||||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
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()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc")
|
config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc")
|
||||||
|
|
||||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
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()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
|
@ -180,16 +160,12 @@ func TestRlimit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n")
|
out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n")
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" {
|
if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" {
|
||||||
t.Fatalf("expected rlimit to be 1025, got %s", limit)
|
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) {
|
func waitProcess(p *libcontainer.Process, t *testing.T) {
|
||||||
status, err := p.Wait()
|
status, err := p.Wait()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !status.Success() {
|
if !status.Success() {
|
||||||
t.Fatal(status)
|
t.Fatal(status)
|
||||||
}
|
}
|
||||||
|
@ -221,35 +195,25 @@ func TestEnter(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
root, err := newTestRoot()
|
root, err := newTestRoot()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
|
|
||||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err := factory.Create("test", config)
|
container, err := factory.Create("test", config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
// Execute a first process in the container
|
// Execute a first process in the container
|
||||||
stdinR, stdinW, err := os.Pipe()
|
stdinR, stdinW, err := os.Pipe()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout, stdout2 bytes.Buffer
|
var stdout, stdout2 bytes.Buffer
|
||||||
|
|
||||||
|
@ -262,19 +226,13 @@ func TestEnter(t *testing.T) {
|
||||||
err = container.Start(&pconfig)
|
err = container.Start(&pconfig)
|
||||||
stdinR.Close()
|
stdinR.Close()
|
||||||
defer stdinW.Close()
|
defer stdinW.Close()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pid, err := pconfig.Pid()
|
pid, err := pconfig.Pid()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute another process in the container
|
// Execute another process in the container
|
||||||
stdinR2, stdinW2, err := os.Pipe()
|
stdinR2, stdinW2, err := os.Pipe()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pconfig2 := libcontainer.Process{
|
pconfig2 := libcontainer.Process{
|
||||||
Env: standardEnvironment,
|
Env: standardEnvironment,
|
||||||
}
|
}
|
||||||
|
@ -285,19 +243,13 @@ func TestEnter(t *testing.T) {
|
||||||
err = container.Start(&pconfig2)
|
err = container.Start(&pconfig2)
|
||||||
stdinR2.Close()
|
stdinR2.Close()
|
||||||
defer stdinW2.Close()
|
defer stdinW2.Close()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pid2, err := pconfig2.Pid()
|
pid2, err := pconfig2.Pid()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
processes, err := container.Processes()
|
processes, err := container.Processes()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
for i := range processes {
|
for i := range processes {
|
||||||
|
@ -318,14 +270,10 @@ func TestEnter(t *testing.T) {
|
||||||
|
|
||||||
// Check that both processes live in the same pidns
|
// Check that both processes live in the same pidns
|
||||||
pidns := string(stdout.Bytes())
|
pidns := string(stdout.Bytes())
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pidns2 := string(stdout2.Bytes())
|
pidns2 := string(stdout2.Bytes())
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pidns != pidns2 {
|
if pidns != pidns2 {
|
||||||
t.Fatal("The second process isn't in the required pid namespace", 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
|
return
|
||||||
}
|
}
|
||||||
root, err := newTestRoot()
|
root, err := newTestRoot()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
|
|
||||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err := factory.Create("test", config)
|
container, err := factory.Create("test", config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
|
@ -374,17 +314,12 @@ func TestProcessEnv(t *testing.T) {
|
||||||
Stdout: &stdout,
|
Stdout: &stdout,
|
||||||
}
|
}
|
||||||
err = container.Start(&pconfig)
|
err = container.Start(&pconfig)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for process
|
// Wait for process
|
||||||
waitProcess(&pconfig, t)
|
waitProcess(&pconfig, t)
|
||||||
|
|
||||||
outputEnv := string(stdout.Bytes())
|
outputEnv := string(stdout.Bytes())
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the environment has the key/value pair we added
|
// Check that the environment has the key/value pair we added
|
||||||
if !strings.Contains(outputEnv, "FOO=BAR") {
|
if !strings.Contains(outputEnv, "FOO=BAR") {
|
||||||
|
@ -402,28 +337,20 @@ func TestProcessCaps(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
root, err := newTestRoot()
|
root, err := newTestRoot()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
|
|
||||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err := factory.Create("test", config)
|
container, err := factory.Create("test", config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
processCaps := append(config.Capabilities, "NET_ADMIN")
|
processCaps := append(config.Capabilities, "NET_ADMIN")
|
||||||
|
@ -437,17 +364,12 @@ func TestProcessCaps(t *testing.T) {
|
||||||
Stdout: &stdout,
|
Stdout: &stdout,
|
||||||
}
|
}
|
||||||
err = container.Start(&pconfig)
|
err = container.Start(&pconfig)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for process
|
// Wait for process
|
||||||
waitProcess(&pconfig, t)
|
waitProcess(&pconfig, t)
|
||||||
|
|
||||||
outputStatus := string(stdout.Bytes())
|
outputStatus := string(stdout.Bytes())
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(outputStatus, "\n")
|
lines := strings.Split(outputStatus, "\n")
|
||||||
|
|
||||||
|
@ -497,37 +419,28 @@ func testFreeze(t *testing.T, systemd bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
root, err := newTestRoot()
|
root, err := newTestRoot()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
|
cgm := libcontainer.Cgroupfs
|
||||||
if systemd {
|
if systemd {
|
||||||
config.Cgroups.Slice = "system.slice"
|
cgm = libcontainer.SystemdCgroups
|
||||||
}
|
}
|
||||||
|
|
||||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
factory, err := libcontainer.New(root, cgm)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err := factory.Create("test", config)
|
container, err := factory.Create("test", config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
stdinR, stdinW, err := os.Pipe()
|
stdinR, stdinW, err := os.Pipe()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pconfig := libcontainer.Process{
|
pconfig := libcontainer.Process{
|
||||||
Args: []string{"cat"},
|
Args: []string{"cat"},
|
||||||
|
@ -537,44 +450,64 @@ func testFreeze(t *testing.T, systemd bool) {
|
||||||
err = container.Start(&pconfig)
|
err = container.Start(&pconfig)
|
||||||
stdinR.Close()
|
stdinR.Close()
|
||||||
defer stdinW.Close()
|
defer stdinW.Close()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pid, err := pconfig.Pid()
|
pid, err := pconfig.Pid()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
process, err := os.FindProcess(pid)
|
process, err := os.FindProcess(pid)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := container.Pause(); err != nil {
|
err = container.Pause()
|
||||||
t.Fatal(err)
|
ok(t, err)
|
||||||
}
|
|
||||||
state, err := container.Status()
|
state, err := container.Status()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
err = container.Resume()
|
||||||
}
|
ok(t, err)
|
||||||
if err := container.Resume(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if state != libcontainer.Paused {
|
if state != libcontainer.Paused {
|
||||||
t.Fatal("Unexpected state: ", state)
|
t.Fatal("Unexpected state: ", state)
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinW.Close()
|
stdinW.Close()
|
||||||
s, err := process.Wait()
|
s, err := process.Wait()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !s.Success() {
|
if !s.Success() {
|
||||||
t.Fatal(s.String())
|
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) {
|
func TestContainerState(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
return
|
return
|
||||||
|
@ -648,3 +581,185 @@ func TestContainerState(t *testing.T) {
|
||||||
stdinW.Close()
|
stdinW.Close()
|
||||||
p.Wait()
|
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
|
return
|
||||||
}
|
}
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
container, err := newContainer(config)
|
container, err := newContainer(config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
// Execute a first process in the container
|
// Execute a first process in the container
|
||||||
stdinR, stdinW, err := os.Pipe()
|
stdinR, stdinW, err := os.Pipe()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
process := &libcontainer.Process{
|
process := &libcontainer.Process{
|
||||||
Args: []string{"cat"},
|
Args: []string{"cat"},
|
||||||
Env: standardEnvironment,
|
Env: standardEnvironment,
|
||||||
|
@ -40,9 +34,7 @@ func TestExecIn(t *testing.T) {
|
||||||
err = container.Start(process)
|
err = container.Start(process)
|
||||||
stdinR.Close()
|
stdinR.Close()
|
||||||
defer stdinW.Close()
|
defer stdinW.Close()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers := newStdBuffers()
|
buffers := newStdBuffers()
|
||||||
ps := &libcontainer.Process{
|
ps := &libcontainer.Process{
|
||||||
|
@ -53,12 +45,9 @@ func TestExecIn(t *testing.T) {
|
||||||
Stderr: buffers.Stderr,
|
Stderr: buffers.Stderr,
|
||||||
}
|
}
|
||||||
err = container.Start(ps)
|
err = container.Start(ps)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
_, err = ps.Wait()
|
||||||
}
|
ok(t, err)
|
||||||
if _, err := ps.Wait(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
stdinW.Close()
|
stdinW.Close()
|
||||||
if _, err := process.Wait(); err != nil {
|
if _, err := process.Wait(); err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
|
@ -74,21 +63,15 @@ func TestExecInRlimit(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
container, err := newContainer(config)
|
container, err := newContainer(config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
stdinR, stdinW, err := os.Pipe()
|
stdinR, stdinW, err := os.Pipe()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
process := &libcontainer.Process{
|
process := &libcontainer.Process{
|
||||||
Args: []string{"cat"},
|
Args: []string{"cat"},
|
||||||
Env: standardEnvironment,
|
Env: standardEnvironment,
|
||||||
|
@ -97,9 +80,7 @@ func TestExecInRlimit(t *testing.T) {
|
||||||
err = container.Start(process)
|
err = container.Start(process)
|
||||||
stdinR.Close()
|
stdinR.Close()
|
||||||
defer stdinW.Close()
|
defer stdinW.Close()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers := newStdBuffers()
|
buffers := newStdBuffers()
|
||||||
ps := &libcontainer.Process{
|
ps := &libcontainer.Process{
|
||||||
|
@ -110,12 +91,9 @@ func TestExecInRlimit(t *testing.T) {
|
||||||
Stderr: buffers.Stderr,
|
Stderr: buffers.Stderr,
|
||||||
}
|
}
|
||||||
err = container.Start(ps)
|
err = container.Start(ps)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
_, err = ps.Wait()
|
||||||
}
|
ok(t, err)
|
||||||
if _, err := ps.Wait(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
stdinW.Close()
|
stdinW.Close()
|
||||||
if _, err := process.Wait(); err != nil {
|
if _, err := process.Wait(); err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
|
@ -131,22 +109,16 @@ func TestExecInError(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rootfs, err := newRootfs()
|
rootfs, err := newRootfs()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
container, err := newContainer(config)
|
container, err := newContainer(config)
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer container.Destroy()
|
defer container.Destroy()
|
||||||
|
|
||||||
// Execute a first process in the container
|
// Execute a first process in the container
|
||||||
stdinR, stdinW, err := os.Pipe()
|
stdinR, stdinW, err := os.Pipe()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
process := &libcontainer.Process{
|
process := &libcontainer.Process{
|
||||||
Args: []string{"cat"},
|
Args: []string{"cat"},
|
||||||
Env: standardEnvironment,
|
Env: standardEnvironment,
|
||||||
|
@ -160,9 +132,7 @@ func TestExecInError(t *testing.T) {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
ok(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
unexistent := &libcontainer.Process{
|
unexistent := &libcontainer.Process{
|
||||||
Args: []string{"unexistent"},
|
Args: []string{"unexistent"},
|
||||||
|
@ -178,6 +148,121 @@ func TestExecInError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecInTTY(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() {
|
if testing.Short() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -211,106 +296,45 @@ func TestExecInTTY(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
ps := &libcontainer.Process{
|
pipeout1, pipein1, err := os.Pipe()
|
||||||
Args: []string{"ps"},
|
pipeout2, pipein2, err := os.Pipe()
|
||||||
Env: standardEnvironment,
|
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)
|
err = container.Start(inprocess)
|
||||||
copy := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
io.Copy(&stdout, console)
|
|
||||||
close(copy)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = container.Start(ps)
|
|
||||||
if err != nil {
|
waitProcess(inprocess, t)
|
||||||
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)
|
|
||||||
}
|
|
||||||
stdinW.Close()
|
stdinW.Close()
|
||||||
if _, err := process.Wait(); err != nil {
|
waitProcess(process, t)
|
||||||
t.Log(err)
|
|
||||||
|
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()
|
var buf = []byte{0}
|
||||||
if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") {
|
_, err = pipeout1.Read(buf)
|
||||||
t.Fatalf("unexpected running process, output %q", out)
|
if err != nil {
|
||||||
}
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
out1 := string(buf)
|
||||||
func TestExecInEnvironment(t *testing.T) {
|
if out1 != "1" {
|
||||||
if testing.Short() {
|
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
rootfs, err := newRootfs()
|
_, err = pipeout2.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer remove(rootfs)
|
out2 := string(buf)
|
||||||
config := newTemplateConfig(rootfs)
|
if out2 != "2" {
|
||||||
container, err := newContainer(config)
|
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ func init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("unable to initialize for container: %s", err)
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/configs"
|
"github.com/docker/libcontainer/configs"
|
||||||
|
@ -38,6 +41,14 @@ func (b *stdBuffers) String() string {
|
||||||
return strings.Join(s, "|")
|
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
|
// newRootfs creates a new tmp directory and copies the busybox root filesystem
|
||||||
func newRootfs() (string, error) {
|
func newRootfs() (string, error) {
|
||||||
dir, err := ioutil.TempDir("", "")
|
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
|
// 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.
|
// to make sure the content can not be shared by other containes.
|
||||||
func Relabel(path string, fileLabel string, relabel string) error {
|
func Relabel(path string, fileLabel string, relabel string) error {
|
||||||
|
exclude_path := []string{"/", "/usr", "/etc"}
|
||||||
if fileLabel == "" {
|
if fileLabel == "" {
|
||||||
return nil
|
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 := selinux.NewContext(fileLabel)
|
||||||
c["level"] = "s0"
|
c["level"] = "s0"
|
||||||
fileLabel = c.Get()
|
fileLabel = c.Get()
|
||||||
|
|
|
@ -87,3 +87,31 @@ func TestDuplicateLabel(t *testing.T) {
|
||||||
t.Errorf("DisableSecOpt Failed level incorrect")
|
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
|
## nsenter
|
||||||
|
|
||||||
The `nsenter` package registers a special init constructor that is called before the Go runtime has
|
The `nsenter` package registers a special init constructor that is called before
|
||||||
a chance to boot. This provides us the ability to `setns` on existing namespaces and avoid the issues
|
the Go runtime has a chance to boot. This provides us the ability to `setns` on
|
||||||
that the Go runtime has with multiple threads. This constructor is only called if this package is
|
existing namespaces and avoid the issues that the Go runtime has with multiple
|
||||||
registered, imported, in your go application and the argv 0 is `nsenter`.
|
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],
|
Path: os.Args[0],
|
||||||
Args: args,
|
Args: args,
|
||||||
ExtraFiles: []*os.File{w},
|
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 {
|
if err := cmd.Start(); err != nil {
|
||||||
|
|
|
@ -66,7 +66,7 @@ void nsexec()
|
||||||
const int num = sizeof(namespaces) / sizeof(char *);
|
const int num = sizeof(namespaces) / sizeof(char *);
|
||||||
jmp_buf env;
|
jmp_buf env;
|
||||||
char buf[PATH_MAX], *val;
|
char buf[PATH_MAX], *val;
|
||||||
int i, tfd, child, len, consolefd = -1;
|
int i, tfd, child, len, pipenum, consolefd = -1;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
char *console;
|
char *console;
|
||||||
|
|
||||||
|
@ -81,6 +81,19 @@ void nsexec()
|
||||||
exit(1);
|
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");
|
console = getenv("_LIBCONTAINER_CONSOLE_PATH");
|
||||||
if (console != NULL) {
|
if (console != NULL) {
|
||||||
consolefd = open(console, O_RDWR);
|
consolefd = open(console, O_RDWR);
|
||||||
|
@ -124,6 +137,8 @@ void nsexec()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setjmp(env) == 1) {
|
if (setjmp(env) == 1) {
|
||||||
|
// Child
|
||||||
|
|
||||||
if (setsid() == -1) {
|
if (setsid() == -1) {
|
||||||
pr_perror("setsid failed");
|
pr_perror("setsid failed");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@ -149,7 +164,11 @@ void nsexec()
|
||||||
// Finish executing, let the Go runtime take over.
|
// Finish executing, let the Go runtime take over.
|
||||||
return;
|
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);
|
child = clone_parent(&env);
|
||||||
if (child < 0) {
|
if (child < 0) {
|
||||||
pr_perror("Unable to fork");
|
pr_perror("Unable to fork");
|
||||||
|
@ -158,7 +177,7 @@ void nsexec()
|
||||||
|
|
||||||
len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child);
|
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");
|
pr_perror("Unable to send a child pid");
|
||||||
kill(child, SIGKILL);
|
kill(child, SIGKILL);
|
||||||
exit(1);
|
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`
|
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.
|
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-address", Usage: "veth ip address"},
|
||||||
cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"},
|
cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"},
|
||||||
cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"},
|
cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"},
|
||||||
|
cli.BoolFlag{Name: "cgroup", Usage: "mount the cgroup data for the container"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var configCommand = cli.Command{
|
var configCommand = cli.Command{
|
||||||
|
@ -187,6 +188,12 @@ func modify(config *configs.Config, context *cli.Context) {
|
||||||
}
|
}
|
||||||
config.Networks = append(config.Networks, network)
|
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 {
|
func getTemplate() *configs.Config {
|
||||||
|
|
|
@ -23,6 +23,7 @@ var execCommand = cli.Command{
|
||||||
Action: execAction,
|
Action: execAction,
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"},
|
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: "id", Value: "nsinit", Usage: "specify the ID for a container"},
|
||||||
cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"},
|
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"},
|
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 {
|
if err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
if err := factory.StartInitialization(3); err != nil {
|
if err := factory.StartInitialization(); err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
panic("This line should never been executed")
|
panic("This line should never been executed")
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
|
"github.com/docker/libcontainer/cgroups/systemd"
|
||||||
"github.com/docker/libcontainer/configs"
|
"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) {
|
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) {
|
func getContainer(context *cli.Context) (libcontainer.Container, error) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Process struct {
|
||||||
Env []string
|
Env []string
|
||||||
|
|
||||||
// User will set the uid and gid of the executing process running inside the container
|
// 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
|
User string
|
||||||
|
|
||||||
// Cwd will change the processes current working directory inside the container's rootfs.
|
// 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 is a pointer to a writer which receives the standard error stream.
|
||||||
Stderr io.Writer
|
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 is the path to the console allocated to the container.
|
||||||
consolePath string
|
consolePath string
|
||||||
|
|
||||||
// Capabilities specify the capabilities to keep when executing the process inside the container
|
// 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
|
Capabilities []string
|
||||||
|
|
||||||
ops processOperations
|
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
|
// terminate sends a SIGKILL to the forked process for the setns routine then waits to
|
||||||
// avoid the process becomming a zombie.
|
// avoid the process becomming a zombie.
|
||||||
func (p *setnsProcess) terminate() error {
|
func (p *setnsProcess) terminate() error {
|
||||||
|
if p.cmd.Process == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err := p.cmd.Process.Kill()
|
err := p.cmd.Process.Kill()
|
||||||
if _, werr := p.wait(); err == nil {
|
if _, werr := p.wait(); err == nil {
|
||||||
err = werr
|
err = werr
|
||||||
|
|
|
@ -6,11 +6,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/cgroups"
|
||||||
"github.com/docker/libcontainer/configs"
|
"github.com/docker/libcontainer/configs"
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
)
|
)
|
||||||
|
@ -24,9 +27,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
|
||||||
return newSystemError(err)
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
for _, m := range config.Mounts {
|
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 {
|
if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
|
||||||
return newSystemError(err)
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, postcmd := range m.PostmountCmds {
|
||||||
|
if err := mountCmd(postcmd); err != nil {
|
||||||
|
return newSystemError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := createDevices(config); err != nil {
|
if err := createDevices(config); err != nil {
|
||||||
return newSystemError(err)
|
return newSystemError(err)
|
||||||
|
@ -62,6 +76,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
|
||||||
return nil
|
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 {
|
func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
|
||||||
var (
|
var (
|
||||||
dest = m.Destination
|
dest = m.Destination
|
||||||
|
@ -72,11 +98,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch m.Device {
|
switch m.Device {
|
||||||
case "proc", "mqueue", "sysfs":
|
case "proc", "sysfs":
|
||||||
if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
|
if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "")
|
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":
|
case "tmpfs":
|
||||||
stat, err := os.Stat(dest)
|
stat, err := os.Stat(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -126,6 +160,37 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
|
||||||
return err
|
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:
|
default:
|
||||||
return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination)
|
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 {
|
func prepareRoot(config *configs.Config) error {
|
||||||
flag := syscall.MS_PRIVATE | syscall.MS_REC
|
flag := syscall.MS_SLAVE | syscall.MS_REC
|
||||||
if config.NoPivotRoot {
|
if config.Privatefs {
|
||||||
flag = syscall.MS_SLAVE | syscall.MS_REC
|
flag = syscall.MS_PRIVATE | syscall.MS_REC
|
||||||
}
|
}
|
||||||
if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
|
if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -355,3 +420,10 @@ func maskFile(path string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil {
|
||||||
return err
|
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 {
|
for _, path := range l.config.Config.ReadonlyPaths {
|
||||||
if err := remountReadonly(path); err != nil {
|
if err := remountReadonly(path); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -12,8 +12,10 @@ import (
|
||||||
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
|
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
|
||||||
var setNsMap = map[string]uintptr{
|
var setNsMap = map[string]uintptr{
|
||||||
"linux/386": 346,
|
"linux/386": 346,
|
||||||
|
"linux/arm64": 268,
|
||||||
"linux/amd64": 308,
|
"linux/amd64": 308,
|
||||||
"linux/arm": 374,
|
"linux/arm": 375,
|
||||||
|
"linux/ppc": 350,
|
||||||
"linux/ppc64": 350,
|
"linux/ppc64": 350,
|
||||||
"linux/ppc64le": 350,
|
"linux/ppc64le": 350,
|
||||||
"linux/s390x": 339,
|
"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
|
package system
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ clone() {
|
||||||
clone git github.com/codegangsta/cli 1.1.0
|
clone git github.com/codegangsta/cli 1.1.0
|
||||||
clone git github.com/coreos/go-systemd v2
|
clone git github.com/coreos/go-systemd v2
|
||||||
clone git github.com/godbus/dbus 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
|
clone git github.com/syndtr/gocapability 8e4cdcb
|
||||||
|
|
||||||
# intentionally not vendoring Docker itself... that'd be a circle :)
|
# intentionally not vendoring Docker itself... that'd be a circle :)
|
||||||
|
|
Loading…
Reference in New Issue