Merge pull request #279 from crosbymichael/namespaces-join

Change namespaces config to include path for setns
This commit is contained in:
Mrunal Patel 2014-12-04 11:19:48 -08:00
commit 6ffd59a784
18 changed files with 115 additions and 235 deletions

View File

@ -10,6 +10,13 @@ type MountConfig mount.MountConfig
type Network network.Network
// Namespace defines configuration for each namespace. It specifies an
// alternate path that is able to be joined via setns.
type Namespace struct {
Name string `json:"name"`
Path string `json:"path,omitempty"`
}
// Config defines configuration options for executing a process inside a contained environment.
type Config struct {
// Mount specific options.
@ -38,7 +45,7 @@ type Config struct {
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
// If a namespace is not provided that namespace is shared from the container's parent process
Namespaces map[string]bool `json:"namespaces,omitempty"`
Namespaces []Namespace `json:"namespaces,omitempty"`
// Capabilities specify the capabilities to keep when executing the process inside the container
// All capbilities not specified will be dropped from the processes capability mask
@ -47,9 +54,6 @@ type Config struct {
// Networks specifies the container's network setup to be created
Networks []*Network `json:"networks,omitempty"`
// Ipc specifies the container's ipc setup to be created
IpcNsPath string `json:"ipc,omitempty"`
// Routes can be specified to create entries in the route table as the container is started
Routes []*Route `json:"routes,omitempty"`

View File

@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) {
t.Fail()
}
if !container.Namespaces["NEWNET"] {
if getNamespaceIndex(container, "NEWNET") == -1 {
t.Log("namespaces should contain NEWNET")
t.Fail()
}
if container.Namespaces["NEWUSER"] {
if getNamespaceIndex(container, "NEWUSER") != -1 {
t.Log("namespaces should not contain NEWUSER")
t.Fail()
}
@ -158,3 +158,12 @@ func TestSelinuxLabels(t *testing.T) {
t.Fatalf("expected mount label %q but received %q", label, container.MountConfig.MountLabel)
}
}
func getNamespaceIndex(config *Config, name string) int {
for i, v := range config.Namespaces {
if v.Name == name {
return i
}
}
return -1
}

View File

@ -4,6 +4,8 @@ import (
"os"
"strings"
"testing"
"github.com/docker/libcontainer"
)
func TestExecPS(t *testing.T) {
@ -55,7 +57,6 @@ func TestIPCPrivate(t *testing.T) {
}
config := newTemplateConfig(rootfs)
config.Namespaces["NEWIPC"] = true
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil {
t.Fatal(err)
@ -87,7 +88,8 @@ func TestIPCHost(t *testing.T) {
}
config := newTemplateConfig(rootfs)
config.Namespaces["NEWIPC"] = false
i := getNamespaceIndex(config, "NEWIPC")
config.Namespaces = append(config.Namespaces[:i], config.Namespaces[i+1:]...)
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil {
t.Fatal(err)
@ -119,8 +121,8 @@ func TestIPCJoinPath(t *testing.T) {
}
config := newTemplateConfig(rootfs)
config.Namespaces["NEWIPC"] = false
config.IpcNsPath = "/proc/1/ns/ipc"
i := getNamespaceIndex(config, "NEWIPC")
config.Namespaces[i].Path = "/proc/1/ns/ipc"
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil {
@ -148,8 +150,8 @@ func TestIPCBadPath(t *testing.T) {
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Namespaces["NEWIPC"] = false
config.IpcNsPath = "/proc/1/ns/ipcc"
i := getNamespaceIndex(config, "NEWIPC")
config.Namespaces[i].Path = "/proc/1/ns/ipcc"
_, _, err = runContainer(config, "", "true")
if err == nil {
@ -177,3 +179,12 @@ func TestRlimit(t *testing.T) {
t.Fatalf("expected rlimit to be 1024, got %s", limit)
}
}
func getNamespaceIndex(config *libcontainer.Config, name string) int {
for i, v := range config.Namespaces {
if v.Name == name {
return i
}
}
return -1
}

View File

@ -32,12 +32,12 @@ func newTemplateConfig(rootfs string) *libcontainer.Config {
"KILL",
"AUDIT_WRITE",
},
Namespaces: map[string]bool{
"NEWNS": true,
"NEWUTS": true,
"NEWIPC": true,
"NEWPID": true,
"NEWNET": true,
Namespaces: []libcontainer.Namespace{
{Name: "NEWNS"},
{Name: "NEWUTS"},
{Name: "NEWIPC"},
{Name: "NEWPID"},
{Name: "NEWNET"},
},
Cgroups: &cgroups.Cgroup{
Parent: "integration",

View File

@ -1,29 +0,0 @@
package ipc
import (
"fmt"
"os"
"syscall"
"github.com/docker/libcontainer/system"
)
// Join the IPC Namespace of specified ipc path if it exists.
// If the path does not exist then you are not joining a container.
func Initialize(nsPath string) error {
if nsPath == "" {
return nil
}
f, err := os.OpenFile(nsPath, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get IPC namespace fd: %v", err)
}
err = system.Setns(f.Fd(), syscall.CLONE_NEWIPC)
f.Close()
if err != nil {
return fmt.Errorf("failed to setns current IPC namespace: %v", err)
}
return nil
}

View File

@ -13,7 +13,6 @@ import (
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/apparmor"
"github.com/docker/libcontainer/console"
"github.com/docker/libcontainer/ipc"
"github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/mount"
"github.com/docker/libcontainer/netlink"
@ -65,7 +64,10 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
if err := json.NewDecoder(pipe).Decode(&networkState); err != nil {
return err
}
// join any namespaces via a path to the namespace fd if provided
if err := joinExistingNamespaces(container.Namespaces); err != nil {
return err
}
if consolePath != "" {
if err := console.OpenAndDup(consolePath); err != nil {
return err
@ -79,9 +81,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
return fmt.Errorf("setctty %s", err)
}
}
if err := ipc.Initialize(container.IpcNsPath); err != nil {
return fmt.Errorf("setup IPC %s", err)
}
if err := setupNetwork(container, networkState); err != nil {
return fmt.Errorf("setup networking %s", err)
}
@ -308,3 +308,22 @@ func LoadContainerEnvironment(container *libcontainer.Config) error {
}
return nil
}
// joinExistingNamespaces gets all the namespace paths specified for the container and
// does a setns on the namespace fd so that the current process joins the namespace.
func joinExistingNamespaces(namespaces []libcontainer.Namespace) error {
for _, ns := range namespaces {
if ns.Path != "" {
f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0)
if err != nil {
return err
}
err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Name]))
f.Close()
if err != nil {
return err
}
}
}
return nil
}

View File

@ -1,50 +0,0 @@
package namespaces
import "errors"
type (
Namespace struct {
Key string `json:"key,omitempty"`
Value int `json:"value,omitempty"`
File string `json:"file,omitempty"`
}
Namespaces []*Namespace
)
// namespaceList is used to convert the libcontainer types
// into the names of the files located in /proc/<pid>/ns/* for
// each namespace
var (
namespaceList = Namespaces{}
ErrUnkownNamespace = errors.New("Unknown namespace")
ErrUnsupported = errors.New("Unsupported method")
)
func (ns *Namespace) String() string {
return ns.Key
}
func GetNamespace(key string) *Namespace {
for _, ns := range namespaceList {
if ns.Key == key {
cpy := *ns
return &cpy
}
}
return nil
}
// Contains returns true if the specified Namespace is
// in the slice
func (n Namespaces) Contains(ns string) bool {
return n.Get(ns) != nil
}
func (n Namespaces) Get(ns string) *Namespace {
for _, nsp := range n {
if nsp != nil && nsp.Key == ns {
return nsp
}
}
return nil
}

View File

@ -1,16 +0,0 @@
package namespaces
import (
"syscall"
)
func init() {
namespaceList = Namespaces{
{Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"},
{Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"},
{Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"},
{Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"},
{Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"},
{Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"},
}
}

View File

@ -1,30 +0,0 @@
package namespaces
import (
"testing"
)
func TestNamespacesContains(t *testing.T) {
ns := Namespaces{
GetNamespace("NEWPID"),
GetNamespace("NEWNS"),
GetNamespace("NEWUTS"),
}
if ns.Contains("NEWNET") {
t.Fatal("namespaces should not contain NEWNET")
}
if !ns.Contains("NEWPID") {
t.Fatal("namespaces should contain NEWPID but does not")
}
withNil := Namespaces{
GetNamespace("UNDEFINED"), // this element will be nil
GetNamespace("NEWPID"),
}
if !withNil.Contains("NEWPID") {
t.Fatal("namespaces should contain NEWPID but does not")
}
}

View File

@ -5,6 +5,8 @@ package namespaces
import (
"os"
"syscall"
"github.com/docker/libcontainer"
)
type initError struct {
@ -15,6 +17,15 @@ func (i initError) Error() string {
return i.Message
}
var namespaceInfo = map[string]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,
}
// New returns a newly initialized Pipe for communication between processes
func newInitPipe() (parent *os.File, child *os.File, err error) {
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
@ -26,13 +37,9 @@ func newInitPipe() (parent *os.File, child *os.File, err error) {
// GetNamespaceFlags parses the container's Namespaces options to set the correct
// flags on clone, unshare, and setns
func GetNamespaceFlags(namespaces map[string]bool) (flag int) {
for key, enabled := range namespaces {
if enabled {
if ns := GetNamespace(key); ns != nil {
flag |= ns.Value
}
}
func GetNamespaceFlags(namespaces []libcontainer.Namespace) (flag int) {
for _, v := range namespaces {
flag |= namespaceInfo[v.Name]
}
return flag
}

View File

@ -1,39 +0,0 @@
// +build linux
package network
import (
"fmt"
"os"
"syscall"
"github.com/docker/libcontainer/system"
)
// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace
type NetNS struct {
}
func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error {
networkState.NsPath = n.NsPath
return nil
}
func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error {
if networkState.NsPath == "" {
return fmt.Errorf("nspath does is not specified in NetworkState")
}
f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get network namespace fd: %v", err)
}
if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil {
f.Close()
return fmt.Errorf("failed to setns current network namespace: %v", err)
}
f.Close()
return nil
}

View File

@ -13,7 +13,6 @@ var (
var strategies = map[string]NetworkStrategy{
"veth": &Veth{},
"loopback": &Loopback{},
"netns": &NetNS{},
}
// NetworkStrategy represents a specific network configuration for

View File

@ -8,9 +8,6 @@ type Network struct {
// Type sets the networks type, commonly veth and loopback
Type string `json:"type,omitempty"`
// Path to network namespace
NsPath string `json:"ns_path,omitempty"`
// The bridge to use.
Bridge string `json:"bridge,omitempty"`
@ -50,6 +47,4 @@ type NetworkState struct {
VethHost string `json:"veth_host,omitempty"`
// The name of the veth interface created inside the container for the child.
VethChild string `json:"veth_child,omitempty"`
// Net namespace path.
NsPath string `json:"ns_path,omitempty"`
}

View File

@ -176,13 +176,13 @@
"TERM=xterm"
],
"hostname": "koye",
"namespaces": {
"NEWIPC": true,
"NEWNET": true,
"NEWNS": true,
"NEWPID": true,
"NEWUTS": true
},
"namespaces": [
{"name":"NEWIPC"},
{"name": "NEWNET"},
{"name": "NEWNS"},
{"name": "NEWPID"},
{"name": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",

View File

@ -175,13 +175,13 @@
"TERM=xterm"
],
"hostname": "koye",
"namespaces": {
"NEWIPC": true,
"NEWNET": true,
"NEWNS": true,
"NEWPID": true,
"NEWUTS": true
},
"namespaces": [
{"name": "NEWIPC"},
{"name": "NEWNET"},
{"name": "NEWNS"},
{"name": "NEWPID"},
{"name": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",

View File

@ -181,13 +181,13 @@
"TERM=xterm"
],
"hostname": "koye",
"namespaces": {
"NEWIPC": true,
"NEWNET": true,
"NEWNS": true,
"NEWPID": true,
"NEWUTS": true
},
"namespaces": [
{"name": "NEWIPC"},
{"name": "NEWNET"},
{"name": "NEWNS"},
{"name": "NEWPID"},
{"name": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",

View File

@ -175,13 +175,13 @@
"TERM=xterm"
],
"hostname": "koye",
"namespaces": {
"NEWIPC": true,
"NEWNET": true,
"NEWNS": true,
"NEWPID": true,
"NEWUTS": true
},
"namespaces": [
{"name": "NEWIPC"},
{"name": "NEWNET"},
{"name": "NEWNS"},
{"name": "NEWPID"},
{"name": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",

View File

@ -177,13 +177,13 @@
"TERM=xterm"
],
"hostname": "koye",
"namespaces": {
"NEWIPC": true,
"NEWNET": true,
"NEWNS": true,
"NEWPID": true,
"NEWUTS": true
},
"namespaces": [
{"name": "NEWIPC"},
{"name": "NEWNET"},
{"name": "NEWNS"},
{"name": "NEWPID"},
{"name": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",