Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
| ... | ... |
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution |
| 75 | 75 |
mkdir -p src/github.com/docker/distribution |
| 76 | 76 |
mv tmp-digest src/github.com/docker/distribution/digest |
| 77 | 77 |
|
| 78 |
-clone git github.com/docker/libcontainer a6044b701c166fe538fc760f9e2dcea3d737cd2a |
|
| 78 |
+clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197 |
|
| 79 | 79 |
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) |
| 80 | 80 |
rm -rf src/github.com/docker/libcontainer/vendor |
| 81 | 81 |
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" |
| ... | ... |
@@ -220,16 +220,16 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
|
| 220 | 220 |
}, nil |
| 221 | 221 |
} |
| 222 | 222 |
|
| 223 |
-func (raw *data) parent(subsystem string) (string, error) {
|
|
| 223 |
+func (raw *data) parent(subsystem, mountpoint string) (string, error) {
|
|
| 224 | 224 |
initPath, err := cgroups.GetInitCgroupDir(subsystem) |
| 225 | 225 |
if err != nil {
|
| 226 | 226 |
return "", err |
| 227 | 227 |
} |
| 228 |
- return filepath.Join(raw.root, subsystem, initPath), nil |
|
| 228 |
+ return filepath.Join(mountpoint, initPath), nil |
|
| 229 | 229 |
} |
| 230 | 230 |
|
| 231 | 231 |
func (raw *data) path(subsystem string) (string, error) {
|
| 232 |
- _, err := cgroups.FindCgroupMountpoint(subsystem) |
|
| 232 |
+ mnt, err := cgroups.FindCgroupMountpoint(subsystem) |
|
| 233 | 233 |
// If we didn't mount the subsystem, there is no point we make the path. |
| 234 | 234 |
if err != nil {
|
| 235 | 235 |
return "", err |
| ... | ... |
@@ -240,7 +240,7 @@ func (raw *data) path(subsystem string) (string, error) {
|
| 240 | 240 |
return filepath.Join(raw.root, subsystem, raw.cgroup), nil |
| 241 | 241 |
} |
| 242 | 242 |
|
| 243 |
- parent, err := raw.parent(subsystem) |
|
| 243 |
+ parent, err := raw.parent(subsystem, mnt) |
|
| 244 | 244 |
if err != nil {
|
| 245 | 245 |
return "", err |
| 246 | 246 |
} |
| ... | ... |
@@ -43,6 +43,10 @@ var subsystems = map[string]subsystem{
|
| 43 | 43 |
"freezer": &fs.FreezerGroup{},
|
| 44 | 44 |
} |
| 45 | 45 |
|
| 46 |
+const ( |
|
| 47 |
+ testScopeWait = 4 |
|
| 48 |
+) |
|
| 49 |
+ |
|
| 46 | 50 |
var ( |
| 47 | 51 |
connLock sync.Mutex |
| 48 | 52 |
theConn *systemd.Conn |
| ... | ... |
@@ -86,16 +90,41 @@ func UseSystemd() bool {
|
| 86 | 86 |
} |
| 87 | 87 |
} |
| 88 | 88 |
|
| 89 |
+ // Ensure the scope name we use doesn't exist. Use the Pid to |
|
| 90 |
+ // avoid collisions between multiple libcontainer users on a |
|
| 91 |
+ // single host. |
|
| 92 |
+ scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
|
|
| 93 |
+ testScopeExists := true |
|
| 94 |
+ for i := 0; i <= testScopeWait; i++ {
|
|
| 95 |
+ if _, err := theConn.StopUnit(scope, "replace"); err != nil {
|
|
| 96 |
+ if dbusError, ok := err.(dbus.Error); ok {
|
|
| 97 |
+ if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
|
|
| 98 |
+ testScopeExists = false |
|
| 99 |
+ break |
|
| 100 |
+ } |
|
| 101 |
+ } |
|
| 102 |
+ } |
|
| 103 |
+ time.Sleep(time.Millisecond) |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ // Bail out if we can't kill this scope without testing for DefaultDependencies |
|
| 107 |
+ if testScopeExists {
|
|
| 108 |
+ return hasStartTransientUnit |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 89 | 111 |
// Assume StartTransientUnit on a scope allows DefaultDependencies |
| 90 | 112 |
hasTransientDefaultDependencies = true |
| 91 | 113 |
ddf := newProp("DefaultDependencies", false)
|
| 92 |
- if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil {
|
|
| 114 |
+ if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil {
|
|
| 93 | 115 |
if dbusError, ok := err.(dbus.Error); ok {
|
| 94 | 116 |
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
|
| 95 | 117 |
hasTransientDefaultDependencies = false |
| 96 | 118 |
} |
| 97 | 119 |
} |
| 98 | 120 |
} |
| 121 |
+ |
|
| 122 |
+ // Not critical because of the stop unit logic above. |
|
| 123 |
+ theConn.StopUnit(scope, "replace") |
|
| 99 | 124 |
} |
| 100 | 125 |
return hasStartTransientUnit |
| 101 | 126 |
} |
| ... | ... |
@@ -193,12 +193,13 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, |
| 193 | 193 |
|
| 194 | 194 |
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
| 195 | 195 |
return &initConfig{
|
| 196 |
- Config: c.config, |
|
| 197 |
- Args: process.Args, |
|
| 198 |
- Env: process.Env, |
|
| 199 |
- User: process.User, |
|
| 200 |
- Cwd: process.Cwd, |
|
| 201 |
- Console: process.consolePath, |
|
| 196 |
+ Config: c.config, |
|
| 197 |
+ Args: process.Args, |
|
| 198 |
+ Env: process.Env, |
|
| 199 |
+ User: process.User, |
|
| 200 |
+ Cwd: process.Cwd, |
|
| 201 |
+ Console: process.consolePath, |
|
| 202 |
+ Capabilities: process.Capabilities, |
|
| 202 | 203 |
} |
| 203 | 204 |
} |
| 204 | 205 |
|
| ... | ... |
@@ -40,13 +40,14 @@ type network struct {
|
| 40 | 40 |
|
| 41 | 41 |
// initConfig is used for transferring parameters from Exec() to Init() |
| 42 | 42 |
type initConfig struct {
|
| 43 |
- Args []string `json:"args"` |
|
| 44 |
- Env []string `json:"env"` |
|
| 45 |
- Cwd string `json:"cwd"` |
|
| 46 |
- User string `json:"user"` |
|
| 47 |
- Config *configs.Config `json:"config"` |
|
| 48 |
- Console string `json:"console"` |
|
| 49 |
- Networks []*network `json:"network"` |
|
| 43 |
+ Args []string `json:"args"` |
|
| 44 |
+ Env []string `json:"env"` |
|
| 45 |
+ Cwd string `json:"cwd"` |
|
| 46 |
+ Capabilities []string `json:"capabilities"` |
|
| 47 |
+ User string `json:"user"` |
|
| 48 |
+ Config *configs.Config `json:"config"` |
|
| 49 |
+ Console string `json:"console"` |
|
| 50 |
+ Networks []*network `json:"network"` |
|
| 50 | 51 |
} |
| 51 | 52 |
|
| 52 | 53 |
type initer interface {
|
| ... | ... |
@@ -99,7 +100,12 @@ func finalizeNamespace(config *initConfig) error {
|
| 99 | 99 |
if err := utils.CloseExecFrom(3); err != nil {
|
| 100 | 100 |
return err |
| 101 | 101 |
} |
| 102 |
- w, err := newCapWhitelist(config.Config.Capabilities) |
|
| 102 |
+ |
|
| 103 |
+ capabilities := config.Config.Capabilities |
|
| 104 |
+ if config.Capabilities != nil {
|
|
| 105 |
+ capabilities = config.Capabilities |
|
| 106 |
+ } |
|
| 107 |
+ w, err := newCapWhitelist(capabilities) |
|
| 103 | 108 |
if err != nil {
|
| 104 | 109 |
return err |
| 105 | 110 |
} |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"io/ioutil" |
| 6 | 6 |
"os" |
| 7 |
+ "strconv" |
|
| 7 | 8 |
"strings" |
| 8 | 9 |
"testing" |
| 9 | 10 |
|
| ... | ... |
@@ -395,6 +396,90 @@ func TestProcessEnv(t *testing.T) {
|
| 395 | 395 |
} |
| 396 | 396 |
} |
| 397 | 397 |
|
| 398 |
+func TestProcessCaps(t *testing.T) {
|
|
| 399 |
+ if testing.Short() {
|
|
| 400 |
+ return |
|
| 401 |
+ } |
|
| 402 |
+ root, err := newTestRoot() |
|
| 403 |
+ if err != nil {
|
|
| 404 |
+ t.Fatal(err) |
|
| 405 |
+ } |
|
| 406 |
+ defer os.RemoveAll(root) |
|
| 407 |
+ |
|
| 408 |
+ rootfs, err := newRootfs() |
|
| 409 |
+ if err != nil {
|
|
| 410 |
+ t.Fatal(err) |
|
| 411 |
+ } |
|
| 412 |
+ defer remove(rootfs) |
|
| 413 |
+ |
|
| 414 |
+ config := newTemplateConfig(rootfs) |
|
| 415 |
+ |
|
| 416 |
+ factory, err := libcontainer.New(root, libcontainer.Cgroupfs) |
|
| 417 |
+ if err != nil {
|
|
| 418 |
+ t.Fatal(err) |
|
| 419 |
+ } |
|
| 420 |
+ |
|
| 421 |
+ container, err := factory.Create("test", config)
|
|
| 422 |
+ if err != nil {
|
|
| 423 |
+ t.Fatal(err) |
|
| 424 |
+ } |
|
| 425 |
+ defer container.Destroy() |
|
| 426 |
+ |
|
| 427 |
+ processCaps := append(config.Capabilities, "NET_ADMIN") |
|
| 428 |
+ |
|
| 429 |
+ var stdout bytes.Buffer |
|
| 430 |
+ pconfig := libcontainer.Process{
|
|
| 431 |
+ Args: []string{"sh", "-c", "cat /proc/self/status"},
|
|
| 432 |
+ Env: standardEnvironment, |
|
| 433 |
+ Capabilities: processCaps, |
|
| 434 |
+ Stdin: nil, |
|
| 435 |
+ Stdout: &stdout, |
|
| 436 |
+ } |
|
| 437 |
+ err = container.Start(&pconfig) |
|
| 438 |
+ if err != nil {
|
|
| 439 |
+ t.Fatal(err) |
|
| 440 |
+ } |
|
| 441 |
+ |
|
| 442 |
+ // Wait for process |
|
| 443 |
+ waitProcess(&pconfig, t) |
|
| 444 |
+ |
|
| 445 |
+ outputStatus := string(stdout.Bytes()) |
|
| 446 |
+ if err != nil {
|
|
| 447 |
+ t.Fatal(err) |
|
| 448 |
+ } |
|
| 449 |
+ |
|
| 450 |
+ lines := strings.Split(outputStatus, "\n") |
|
| 451 |
+ |
|
| 452 |
+ effectiveCapsLine := "" |
|
| 453 |
+ for _, l := range lines {
|
|
| 454 |
+ line := strings.TrimSpace(l) |
|
| 455 |
+ if strings.Contains(line, "CapEff:") {
|
|
| 456 |
+ effectiveCapsLine = line |
|
| 457 |
+ break |
|
| 458 |
+ } |
|
| 459 |
+ } |
|
| 460 |
+ |
|
| 461 |
+ if effectiveCapsLine == "" {
|
|
| 462 |
+ t.Fatal("Couldn't find effective caps: ", outputStatus)
|
|
| 463 |
+ } |
|
| 464 |
+ |
|
| 465 |
+ parts := strings.Split(effectiveCapsLine, ":") |
|
| 466 |
+ effectiveCapsStr := strings.TrimSpace(parts[1]) |
|
| 467 |
+ |
|
| 468 |
+ effectiveCaps, err := strconv.ParseUint(effectiveCapsStr, 16, 64) |
|
| 469 |
+ if err != nil {
|
|
| 470 |
+ t.Fatal("Could not parse effective caps", err)
|
|
| 471 |
+ } |
|
| 472 |
+ |
|
| 473 |
+ var netAdminMask uint64 |
|
| 474 |
+ var netAdminBit uint |
|
| 475 |
+ netAdminBit = 12 // from capability.h |
|
| 476 |
+ netAdminMask = 1 << netAdminBit |
|
| 477 |
+ if effectiveCaps&netAdminMask != netAdminMask {
|
|
| 478 |
+ t.Fatal("CAP_NET_ADMIN is not set as expected")
|
|
| 479 |
+ } |
|
| 480 |
+} |
|
| 481 |
+ |
|
| 398 | 482 |
func TestFreeze(t *testing.T) {
|
| 399 | 483 |
if testing.Short() {
|
| 400 | 484 |
return |
| 401 | 485 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,57 @@ |
| 0 |
+## nsinit |
|
| 1 |
+ |
|
| 2 |
+`nsinit` is a cli application which demonstrates the use of libcontainer. |
|
| 3 |
+It is able to spawn new containers or join existing containers. |
|
| 4 |
+ |
|
| 5 |
+### How to build? |
|
| 6 |
+ |
|
| 7 |
+First to add the `libcontainer/vendor` into your GOPATH. It's because something related with this [issue](https://github.com/docker/libcontainer/issues/210). |
|
| 8 |
+ |
|
| 9 |
+``` |
|
| 10 |
+export GOPATH=$GOPATH:/your/path/to/libcontainer/vendor |
|
| 11 |
+``` |
|
| 12 |
+ |
|
| 13 |
+Then get into the nsinit folder and get the imported file. Use `make` command to make the nsinit binary. |
|
| 14 |
+ |
|
| 15 |
+``` |
|
| 16 |
+cd libcontainer/nsinit |
|
| 17 |
+go get |
|
| 18 |
+make |
|
| 19 |
+``` |
|
| 20 |
+ |
|
| 21 |
+We have finished compiling the nsinit package, but a root filesystem must be provided for use along with a container configuration file. |
|
| 22 |
+ |
|
| 23 |
+Choose a proper place to run your container. For example we use `/busybox`. |
|
| 24 |
+ |
|
| 25 |
+``` |
|
| 26 |
+mkdir /busybox |
|
| 27 |
+curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox |
|
| 28 |
+``` |
|
| 29 |
+ |
|
| 30 |
+Then you may need to write a configure file named `container.json` in the `/busybox` folder. |
|
| 31 |
+Environment, networking, and different capabilities for the container are specified in this file. |
|
| 32 |
+The configuration is used for each process executed inside the container |
|
| 33 |
+See the `sample_configs` folder for examples of what the container configuration should look like. |
|
| 34 |
+ |
|
| 35 |
+``` |
|
| 36 |
+cp libcontainer/sample_configs/minimal.json /busybox/container.json |
|
| 37 |
+cd /busybox |
|
| 38 |
+``` |
|
| 39 |
+ |
|
| 40 |
+Now the nsinit is ready to work. |
|
| 41 |
+To execute `/bin/bash` in the current directory as a container just run the following **as root**: |
|
| 42 |
+```bash |
|
| 43 |
+nsinit exec --tty /bin/bash |
|
| 44 |
+``` |
|
| 45 |
+ |
|
| 46 |
+If you wish to spawn another process inside the container while your |
|
| 47 |
+current bash session is running, run the same command again to |
|
| 48 |
+get another bash shell (or change the command). If the original |
|
| 49 |
+process (PID 1) dies, all other processes spawned inside the container |
|
| 50 |
+will be killed and the namespace will be removed. |
|
| 51 |
+ |
|
| 52 |
+You can identify if a process is running in a container by |
|
| 53 |
+looking to see if `state.json` is in the root of the directory. |
|
| 54 |
+ |
|
| 55 |
+You may also specify an alternate root place where |
|
| 56 |
+the `container.json` file is read and where the `state.json` file will be saved. |
| ... | ... |
@@ -41,6 +41,10 @@ type Process struct {
|
| 41 | 41 |
// consolePath is the path to the console allocated to the container. |
| 42 | 42 |
consolePath string |
| 43 | 43 |
|
| 44 |
+ // Capabilities specify the capabilities to keep when executing the process inside the container |
|
| 45 |
+ // All capbilities not specified will be dropped from the processes capability mask |
|
| 46 |
+ Capabilities []string |
|
| 47 |
+ |
|
| 44 | 48 |
ops processOperations |
| 45 | 49 |
} |
| 46 | 50 |
|
| ... | ... |
@@ -4,6 +4,7 @@ package libcontainer |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 | 6 |
"encoding/json" |
| 7 |
+ "errors" |
|
| 7 | 8 |
"io" |
| 8 | 9 |
"os" |
| 9 | 10 |
"os/exec" |
| ... | ... |
@@ -44,8 +45,12 @@ func (p *setnsProcess) startTime() (string, error) {
|
| 44 | 44 |
return system.GetProcessStartTime(p.pid()) |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
-func (p *setnsProcess) signal(s os.Signal) error {
|
|
| 48 |
- return p.cmd.Process.Signal(s) |
|
| 47 |
+func (p *setnsProcess) signal(sig os.Signal) error {
|
|
| 48 |
+ s, ok := sig.(syscall.Signal) |
|
| 49 |
+ if !ok {
|
|
| 50 |
+ return errors.New("os: unsupported signal type")
|
|
| 51 |
+ } |
|
| 52 |
+ return syscall.Kill(p.cmd.Process.Pid, s) |
|
| 49 | 53 |
} |
| 50 | 54 |
|
| 51 | 55 |
func (p *setnsProcess) start() (err error) {
|
| ... | ... |
@@ -235,6 +240,10 @@ func (p *initProcess) createNetworkInterfaces() error {
|
| 235 | 235 |
return nil |
| 236 | 236 |
} |
| 237 | 237 |
|
| 238 |
-func (p *initProcess) signal(s os.Signal) error {
|
|
| 239 |
- return p.cmd.Process.Signal(s) |
|
| 238 |
+func (p *initProcess) signal(sig os.Signal) error {
|
|
| 239 |
+ s, ok := sig.(syscall.Signal) |
|
| 240 |
+ if !ok {
|
|
| 241 |
+ return errors.New("os: unsupported signal type")
|
|
| 242 |
+ } |
|
| 243 |
+ return syscall.Kill(p.cmd.Process.Pid, s) |
|
| 240 | 244 |
} |