Improve execin to support registering funcs

This also changes the functionality of the default exec in to just be an
existing func that is called than handles the implementation to exec a
user user's process inside the container.  This implements this
functionallity in nsinit but is a base for how we will be handling these
types of features inside docker.

Signed-off-by: Michael Crosby <michael@docker.com>
This commit is contained in:
Michael Crosby 2014-08-12 11:43:12 -07:00
parent 51e6049226
commit 70367b2cf3
6 changed files with 162 additions and 39 deletions

View File

@ -3,6 +3,7 @@
package namespaces
import (
"fmt"
"io"
"os"
"os/exec"
@ -18,10 +19,10 @@ import (
// ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the
// setns code in a single threaded environment joining the existing containers' namespaces.
func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath string,
func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath, action string,
stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
args := []string{"nsenter", "--nspid", strconv.Itoa(state.InitPid)}
args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)}
if console != "" {
args = append(args, "--console", console)

View File

@ -36,7 +36,7 @@ func execAction(context *cli.Context) {
}
if state != nil {
exitCode, err = startInExistingContainer(container, state, context)
exitCode, err = startInExistingContainer(container, state, "exec", context)
} else {
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
}
@ -52,7 +52,7 @@ func execAction(context *cli.Context) {
// with the nsenter argument so that the C code can setns an the namespaces that we require. Then that
// code path will drop us into the path that we can do the final setup of the namespace and exec the users
// application.
func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, context *cli.Context) (int, error) {
func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) {
var (
master *os.File
console string
@ -102,7 +102,7 @@ func startInExistingContainer(config *libcontainer.Config, state *libcontainer.S
}()
}
return namespaces.ExecIn(config, state, context.Args(), os.Args[0], stdin, stdout, stderr, console, startCallback)
return namespaces.ExecIn(config, state, context.Args(), os.Args[0], action, stdin, stdout, stderr, console, startCallback)
}
// startContainer starts the container. Returns the exit status or -1 and an

58
nsinit/execfunc.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"log"
"os"
"text/tabwriter"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer"
)
var execFuncCommand = cli.Command{
Name: "func",
Usage: "execute a registered function inside an existing container",
Action: execFuncAction,
Flags: []cli.Flag{
cli.BoolFlag{Name: "list", Usage: "list all registered functions"},
cli.StringFlag{Name: "func", Usage: "function name to exec inside a container"},
},
}
func execFuncAction(context *cli.Context) {
if context.Bool("list") {
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprint(w, "NAME\tUSAGE\n")
for k, f := range argvs {
fmt.Fprintf(w, "%s\t%s\n", k, f.Usage)
}
w.Flush()
return
}
var exitCode int
config, err := loadContainer()
if err != nil {
log.Fatal(err)
}
// FIXME: remove tty from container config, this should be per process
config.Tty = false
state, err := libcontainer.GetState(dataPath)
if err != nil {
log.Fatalf("unable to read state.json: %s", err)
}
exitCode, err = startInExistingContainer(config, state, context.String("func"), context)
if err != nil {
log.Fatal(err)
}
os.Exit(exitCode)
}

View File

@ -9,11 +9,19 @@ import (
var (
logPath = os.Getenv("log")
argvs = make(map[string]func())
argvs = make(map[string]*rFunc)
)
func init() {
argvs["nsenter"] = nsenter
argvs["nsenter-exec"] = &rFunc{
Usage: "execute a process inside an existing container",
Action: nsenterExec,
}
argvs["nsenter-mknod"] = &rFunc{
Usage: "mknod a device inside an existing container",
Action: nsenterMknod,
}
}
func preload(context *cli.Context) error {
@ -26,13 +34,23 @@ func preload(context *cli.Context) error {
return nil
}
func runFunc(f *rFunc) {
userArgs := findUserArgs()
config, err := loadConfigFromFd()
if err != nil {
log.Fatalf("unable to receive config from sync pipe: %s", err)
}
f.Action(config, userArgs)
}
func main() {
// we need to check our argv 0 for any registred functions to run instead of the
// normal cli code path
action, exists := argvs[os.Args[0]]
f, exists := argvs[os.Args[0]]
if exists {
action()
runFunc(f)
return
}
@ -56,6 +74,7 @@ func main() {
configCommand,
pauseCommand,
unpauseCommand,
execFuncCommand,
}
if err := app.Run(os.Args); err != nil {

View File

@ -2,41 +2,50 @@ package main
import (
"log"
"os"
"strconv"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/devices"
"github.com/docker/libcontainer/mount/nodes"
"github.com/docker/libcontainer/namespaces"
_ "github.com/docker/libcontainer/namespaces/nsenter"
"github.com/docker/libcontainer/syncpipe"
)
func findUserArgs() []string {
i := 0
for _, a := range os.Args {
i++
if a == "--" {
break
}
}
return os.Args[i:]
}
// this expects that we already have our namespaces setup by the C initializer
// we are expected to finalize the namespace and exec the user's application
func nsenter() {
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
if err != nil {
log.Fatalf("unable to create sync pipe: %s", err)
}
var config *libcontainer.Config
if err := syncPipe.ReadFromParent(&config); err != nil {
log.Fatalf("reading container config from parent: %s", err)
}
if err := namespaces.FinalizeSetns(config, findUserArgs()); err != nil {
// nsenterExec exec's a process inside an existing container
func nsenterExec(config *libcontainer.Config, args []string) {
if err := namespaces.FinalizeSetns(config, args); err != nil {
log.Fatalf("failed to nsenter: %s", err)
}
}
// nsenterMknod runs mknod inside an existing container
//
// mknod <path> <type> <major> <minor>
func nsenterMknod(config *libcontainer.Config, args []string) {
if len(args) != 4 {
log.Fatalf("expected mknod to have 4 arguments not %d", len(args))
}
t := rune(args[1][0])
major, err := strconv.Atoi(args[2])
if err != nil {
log.Fatal(err)
}
minor, err := strconv.Atoi(args[3])
if err != nil {
log.Fatal(err)
}
n := &devices.Device{
Path: args[0],
Type: t,
MajorNumber: int64(major),
MinorNumber: int64(minor),
}
if err := nodes.CreateDeviceNode("/", n); err != nil {
log.Fatal(err)
}
}

View File

@ -7,8 +7,15 @@ import (
"path/filepath"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/syncpipe"
)
// rFunc is a function registration for calling after an execin
type rFunc struct {
Usage string
Action func(*libcontainer.Config, []string)
}
func loadContainer() (*libcontainer.Config, error) {
f, err := os.Open(filepath.Join(dataPath, "container.json"))
if err != nil {
@ -44,3 +51,32 @@ func loadContainerFromJson(rawData string) (*libcontainer.Config, error) {
return container, nil
}
func findUserArgs() []string {
i := 0
for _, a := range os.Args {
i++
if a == "--" {
break
}
}
return os.Args[i:]
}
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
// fd 3 when running a process
func loadConfigFromFd() (*libcontainer.Config, error) {
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
if err != nil {
return nil, err
}
var config *libcontainer.Config
if err := syncPipe.ReadFromParent(&config); err != nil {
return nil, err
}
return config, nil
}