From 1a3d43c23ec7bbb1aa206581acd0497c47e29a2f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 4 Jun 2014 16:46:30 -0700 Subject: [PATCH] Update nsinit to be nicer to work with and test Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 74 ++++++++++ pkg/libcontainer/nsinit/init.go | 43 ++++++ pkg/libcontainer/nsinit/main.go | 231 ++++--------------------------- pkg/libcontainer/nsinit/spec.go | 38 +++++ pkg/libcontainer/nsinit/stats.go | 42 ++++++ pkg/libcontainer/nsinit/utils.go | 52 +++++++ 6 files changed, 277 insertions(+), 203 deletions(-) create mode 100644 pkg/libcontainer/nsinit/exec.go create mode 100644 pkg/libcontainer/nsinit/init.go create mode 100644 pkg/libcontainer/nsinit/spec.go create mode 100644 pkg/libcontainer/nsinit/stats.go create mode 100644 pkg/libcontainer/nsinit/utils.go diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go new file mode 100644 index 0000000000..ef33d3a5a3 --- /dev/null +++ b/pkg/libcontainer/nsinit/exec.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "os/signal" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var execCommand = cli.Command{ + Name: "exec", + Usage: "execute a new command inside a container", + Action: execAction, +} + +func execAction(context *cli.Context) { + var ( + err error + nspid, exitCode int + ) + + if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { + log.Fatalf("unable to read pid: %s", err) + } + + if nspid > 0 { + exitCode, err = namespaces.ExecIn(container, nspid, []string(context.Args())) + } else { + term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) + exitCode, err = startContainer(container, term, dataPath, []string(context.Args())) + } + + if err != nil { + log.Fatalf("failed to exec: %s", err) + } + + os.Exit(exitCode) +} + +// startContainer starts the container. Returns the exit status or -1 and an +// error. +// +// Signals sent to the current process will be forwarded to container. +func startContainer(container *libcontainer.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { + var ( + cmd *exec.Cmd + sigc = make(chan os.Signal, 10) + ) + + signal.Notify(sigc) + + createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { + cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) + if logPath != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("log=%s", logPath)) + } + return cmd + } + + startCallback := func() { + go func() { + for sig := range sigc { + cmd.Process.Signal(sig) + } + }() + } + + return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go new file mode 100644 index 0000000000..561b93bb71 --- /dev/null +++ b/pkg/libcontainer/nsinit/init.go @@ -0,0 +1,43 @@ +package main + +import ( + "log" + "os" + "strconv" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var ( + dataPath = os.Getenv("data_path") + console = os.Getenv("console") + rawPipeFd = os.Getenv("pipe") + + initCommand = cli.Command{ + Name: "init", + Usage: "runs the init process inside the namespace", + Action: initAction, + } +) + +func initAction(context *cli.Context) { + rootfs, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + pipeFd, err := strconv.Atoi(rawPipeFd) + if err != nil { + log.Fatal(err) + } + + syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) + if err != nil { + log.Fatalf("unable to create sync pipe: %s", err) + } + + if err := namespaces.Init(container, rootfs, console, syncPipe, []string(context.Args())); err != nil { + log.Fatalf("unable to initialize for container: %s", err) + } +} diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index bddc1992fd..29b80bad49 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -1,220 +1,45 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" "log" "os" - "os/exec" - "os/signal" - "path/filepath" - "strconv" + "github.com/codegangsta/cli" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces" ) var ( - dataPath = os.Getenv("data_path") - console = os.Getenv("console") - rawPipeFd = os.Getenv("pipe") + container *libcontainer.Container + logPath = os.Getenv("log") ) +func preload(context *cli.Context) (err error) { + container, err = loadContainer() + if err != nil { + return err + } + + if logPath != "" { + } + + return nil +} + func main() { - if len(os.Args) < 2 { - log.Fatalf("invalid number of arguments %d", len(os.Args)) + app := cli.NewApp() + app.Name = "nsinit" + app.Version = "0.1" + app.Author = "libcontainer maintainers" + + app.Before = preload + app.Commands = []cli.Command{ + execCommand, + initCommand, + statsCommand, + specCommand, } - switch os.Args[1] { - case "exec": // this is executed outside of the namespace in the cwd - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - var nspid, exitCode int - if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { - log.Fatalf("unable to read pid: %s", err) - } - - if nspid > 0 { - err = namespaces.ExecIn(container, nspid, os.Args[2:]) - } else { - term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) - exitCode, err = startContainer(container, term, dataPath, os.Args[2:]) - } - - if err != nil { - log.Fatalf("failed to exec: %s", err) - } - os.Exit(exitCode) - case "nsenter": // this is executed inside the namespace. - // nsinit nsenter ... - if len(os.Args) < 6 { - log.Fatalf("incorrect usage: nsinit nsenter ...") - } - - container, err := loadContainerFromJson(os.Args[4]) - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - nspid, err := strconv.Atoi(os.Args[2]) - if err != nil { - log.Fatalf("unable to read pid: %s from %q", err, os.Args[2]) - } - - if nspid <= 0 { - log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) - } - - err = namespaces.NsEnter(container, os.Args[3], nspid, os.Args[5:]) - if err != nil { - log.Fatalf("failed to nsenter: %s", err) - } - case "init": // this is executed inside of the namespace to setup the container - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // by default our current dir is always our rootfs - rootfs, err := os.Getwd() - if err != nil { - log.Fatal(err) - } - - pipeFd, err := strconv.Atoi(rawPipeFd) - if err != nil { - log.Fatal(err) - } - syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) - if err != nil { - log.Fatalf("unable to create sync pipe: %s", err) - } - - if err := namespaces.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { - log.Fatalf("unable to initialize for container: %s", err) - } - case "stats": - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // returns the stats of the current container. - stats, err := getContainerStats(container) - if err != nil { - log.Printf("Failed to get stats - %v\n", err) - os.Exit(1) - } - fmt.Printf("Stats:\n%v\n", stats) - os.Exit(0) - - case "spec": - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // returns the spec of the current container. - spec, err := getContainerSpec(container) - if err != nil { - log.Printf("Failed to get spec - %v\n", err) - os.Exit(1) - } - fmt.Printf("Spec:\n%v\n", spec) - os.Exit(0) - - default: - log.Fatalf("command not supported for nsinit %s", os.Args[1]) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) } } - -func loadContainer() (*libcontainer.Container, error) { - f, err := os.Open(filepath.Join(dataPath, "container.json")) - if err != nil { - log.Printf("Path: %q", filepath.Join(dataPath, "container.json")) - return nil, err - } - defer f.Close() - - var container *libcontainer.Container - if err := json.NewDecoder(f).Decode(&container); err != nil { - return nil, err - } - return container, nil -} - -func loadContainerFromJson(rawData string) (*libcontainer.Container, error) { - container := &libcontainer.Container{} - err := json.Unmarshal([]byte(rawData), container) - if err != nil { - return nil, err - } - return container, nil -} - -func readPid() (int, error) { - data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) - if err != nil { - return -1, err - } - pid, err := strconv.Atoi(string(data)) - if err != nil { - return -1, err - } - return pid, nil -} - -// startContainer starts the container. Returns the exit status or -1 and an -// error. -// -// Signals sent to the current process will be forwarded to container. -func startContainer(container *libcontainer.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { - var ( - cmd *exec.Cmd - sigc = make(chan os.Signal, 10) - ) - - signal.Notify(sigc) - - createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { - cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) - return cmd - } - - startCallback := func() { - go func() { - for sig := range sigc { - cmd.Process.Signal(sig) - } - }() - } - - return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) -} - -// returns the container stats in json format. -func getContainerStats(container *libcontainer.Container) (string, error) { - stats, err := fs.GetStats(container.Cgroups) - if err != nil { - return "", err - } - out, err := json.MarshalIndent(stats, "", "\t") - if err != nil { - return "", err - } - return string(out), nil -} - -// returns the container spec in json format. -func getContainerSpec(container *libcontainer.Container) (string, error) { - spec, err := json.MarshalIndent(container, "", "\t") - if err != nil { - return "", err - } - return string(spec), nil -} diff --git a/pkg/libcontainer/nsinit/spec.go b/pkg/libcontainer/nsinit/spec.go new file mode 100644 index 0000000000..92a2f64cdc --- /dev/null +++ b/pkg/libcontainer/nsinit/spec.go @@ -0,0 +1,38 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" +) + +var specCommand = cli.Command{ + Name: "spec", + Usage: "display the container specification", + Action: specAction, +} + +func specAction(context *cli.Context) { + // returns the spec of the current container. + spec, err := getContainerSpec(container) + if err != nil { + log.Printf("Failed to get spec - %v\n", err) + os.Exit(1) + } + fmt.Printf("Spec:\n%v\n", spec) + os.Exit(0) + +} + +// returns the container spec in json format. +func getContainerSpec(container *libcontainer.Container) (string, error) { + spec, err := json.MarshalIndent(container, "", "\t") + if err != nil { + return "", err + } + return string(spec), nil +} diff --git a/pkg/libcontainer/nsinit/stats.go b/pkg/libcontainer/nsinit/stats.go new file mode 100644 index 0000000000..0e930823b1 --- /dev/null +++ b/pkg/libcontainer/nsinit/stats.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" +) + +var statsCommand = cli.Command{ + Name: "stats", + Usage: "display statistics for the container", + Action: statsAction, +} + +func statsAction(context *cli.Context) { + // returns the stats of the current container. + stats, err := getContainerStats(container) + if err != nil { + log.Printf("Failed to get stats - %v\n", err) + os.Exit(1) + } + fmt.Printf("Stats:\n%v\n", stats) + os.Exit(0) +} + +// returns the container stats in json format. +func getContainerStats(container *libcontainer.Container) (string, error) { + stats, err := fs.GetStats(container.Cgroups) + if err != nil { + return "", err + } + out, err := json.MarshalIndent(stats, "", "\t") + if err != nil { + return "", err + } + return string(out), nil +} diff --git a/pkg/libcontainer/nsinit/utils.go b/pkg/libcontainer/nsinit/utils.go new file mode 100644 index 0000000000..fc311a351d --- /dev/null +++ b/pkg/libcontainer/nsinit/utils.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer" +) + +func loadContainer() (*libcontainer.Container, error) { + f, err := os.Open(filepath.Join(dataPath, "container.json")) + if err != nil { + return nil, err + } + defer f.Close() + + var container *libcontainer.Container + if err := json.NewDecoder(f).Decode(&container); err != nil { + return nil, err + } + + return container, nil +} + +func readPid() (int, error) { + data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) + if err != nil { + return -1, err + } + + pid, err := strconv.Atoi(string(data)) + if err != nil { + return -1, err + } + + return pid, nil +} + +func openLog(name string) error { + f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + return err + } + + log.SetOutput(f) + + return nil +}