| package lxc |
| |
| import ( |
| "fmt" |
| "github.com/dotcloud/docker/execdriver" |
| "github.com/dotcloud/docker/pkg/cgroups" |
| "github.com/dotcloud/docker/utils" |
| "io/ioutil" |
| "log" |
| "os" |
| "os/exec" |
| "path" |
| "path/filepath" |
| "strconv" |
| "strings" |
| "syscall" |
| "time" |
| ) |
| |
| const DriverName = "lxc" |
| |
| func init() { |
| execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { |
| if err := setupHostname(args); err != nil { |
| return err |
| } |
| |
| if err := setupNetworking(args); err != nil { |
| return err |
| } |
| |
| if err := setupCapabilities(args); err != nil { |
| return err |
| } |
| |
| if err := setupWorkingDirectory(args); err != nil { |
| return err |
| } |
| |
| if err := changeUser(args); err != nil { |
| return err |
| } |
| |
| path, err := exec.LookPath(args.Args[0]) |
| if err != nil { |
| log.Printf("Unable to locate %v", args.Args[0]) |
| os.Exit(127) |
| } |
| if err := syscall.Exec(path, args.Args, os.Environ()); err != nil { |
| return fmt.Errorf("dockerinit unable to execute %s - %s", path, err) |
| } |
| panic("Unreachable") |
| }) |
| } |
| |
| type driver struct { |
| root string // root path for the driver to use |
| apparmor bool |
| sharedRoot bool |
| } |
| |
| func NewDriver(root string, apparmor bool) (*driver, error) { |
| // setup unconfined symlink |
| if err := linkLxcStart(root); err != nil { |
| return nil, err |
| } |
| return &driver{ |
| apparmor: apparmor, |
| root: root, |
| sharedRoot: rootIsShared(), |
| }, nil |
| } |
| |
| func (d *driver) Name() string { |
| version := d.version() |
| return fmt.Sprintf("%s-%s", DriverName, version) |
| } |
| |
| func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { |
| configPath, err := d.generateLXCConfig(c) |
| if err != nil { |
| return -1, err |
| } |
| params := []string{ |
| "lxc-start", |
| "-n", c.ID, |
| "-f", configPath, |
| "--", |
| c.InitPath, |
| "-driver", |
| DriverName, |
| } |
| |
| if c.Network != nil { |
| params = append(params, |
| "-g", c.Network.Gateway, |
| "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), |
| "-mtu", strconv.Itoa(c.Network.Mtu), |
| ) |
| } |
| |
| if c.User != "" { |
| params = append(params, "-u", c.User) |
| } |
| |
| if c.Privileged { |
| if d.apparmor { |
| params[0] = path.Join(d.root, "lxc-start-unconfined") |
| |
| } |
| params = append(params, "-privileged") |
| } |
| |
| if c.WorkingDir != "" { |
| params = append(params, "-w", c.WorkingDir) |
| } |
| |
| params = append(params, "--", c.Entrypoint) |
| params = append(params, c.Arguments...) |
| |
| if d.sharedRoot { |
| // lxc-start really needs / to be non-shared, or all kinds of stuff break |
| // when lxc-start unmount things and those unmounts propagate to the main |
| // mount namespace. |
| // What we really want is to clone into a new namespace and then |
| // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork |
| // without exec in go we have to do this horrible shell hack... |
| shellString := |
| "mount --make-rslave /; exec " + |
| utils.ShellQuoteArguments(params) |
| |
| params = []string{ |
| "unshare", "-m", "--", "/bin/sh", "-c", shellString, |
| } |
| } |
| |
| var ( |
| name = params[0] |
| arg = params[1:] |
| ) |
| aname, err := exec.LookPath(name) |
| if err != nil { |
| aname = name |
| } |
| c.Path = aname |
| c.Args = append([]string{name}, arg...) |
| |
| if err := c.Start(); err != nil { |
| return -1, err |
| } |
| |
| var ( |
| waitErr error |
| waitLock = make(chan struct{}) |
| ) |
| go func() { |
| if err := c.Wait(); err != nil { |
| if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0 |
| waitErr = err |
| } |
| } |
| close(waitLock) |
| }() |
| |
| // Poll lxc for RUNNING status |
| if err := d.waitForStart(c, waitLock); err != nil { |
| return -1, err |
| } |
| |
| if startCallback != nil { |
| startCallback(c) |
| } |
| |
| <-waitLock |
| |
| return getExitCode(c), waitErr |
| } |
| |
| /// Return the exit code of the process |
| // if the process has not exited -1 will be returned |
| func getExitCode(c *execdriver.Command) int { |
| if c.ProcessState == nil { |
| return -1 |
| } |
| return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() |
| } |
| |
| func (d *driver) Kill(c *execdriver.Command, sig int) error { |
| return d.kill(c, sig) |
| } |
| |
| func (d *driver) Restore(c *execdriver.Command) error { |
| for { |
| output, err := exec.Command("lxc-info", "-n", c.ID).CombinedOutput() |
| if err != nil { |
| return err |
| } |
| if !strings.Contains(string(output), "RUNNING") { |
| return nil |
| } |
| time.Sleep(500 * time.Millisecond) |
| } |
| } |
| |
| func (d *driver) version() string { |
| version := "" |
| if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil { |
| outputStr := string(output) |
| if len(strings.SplitN(outputStr, ":", 2)) == 2 { |
| version = strings.TrimSpace(strings.SplitN(outputStr, ":", 2)[1]) |
| } |
| } |
| return version |
| } |
| |
| func (d *driver) kill(c *execdriver.Command, sig int) error { |
| var ( |
| err error |
| output []byte |
| ) |
| _, err = exec.LookPath("lxc-kill") |
| if err == nil { |
| output, err = exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() |
| } else { |
| output, err = exec.Command("lxc-stop", "-k", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() |
| } |
| if err != nil { |
| return fmt.Errorf("Err: %s Output: %s", err, output) |
| } |
| return nil |
| } |
| |
| func (d *driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) error { |
| var ( |
| err error |
| output []byte |
| ) |
| // We wait for the container to be fully running. |
| // Timeout after 5 seconds. In case of broken pipe, just retry. |
| // Note: The container can run and finish correctly before |
| // the end of this loop |
| for now := time.Now(); time.Since(now) < 5*time.Second; { |
| select { |
| case <-waitLock: |
| // If the process dies while waiting for it, just return |
| return nil |
| if c.ProcessState != nil && c.ProcessState.Exited() { |
| return nil |
| } |
| default: |
| } |
| |
| output, err = d.getInfo(c.ID) |
| if err != nil { |
| output, err = d.getInfo(c.ID) |
| if err != nil { |
| return err |
| } |
| } |
| if strings.Contains(string(output), "RUNNING") { |
| return nil |
| } |
| time.Sleep(50 * time.Millisecond) |
| } |
| return execdriver.ErrNotRunning |
| } |
| |
| func (d *driver) getInfo(id string) ([]byte, error) { |
| return exec.Command("lxc-info", "-s", "-n", id).CombinedOutput() |
| } |
| |
| type info struct { |
| ID string |
| driver *driver |
| } |
| |
| func (i *info) IsRunning() bool { |
| var running bool |
| |
| output, err := i.driver.getInfo(i.ID) |
| if err != nil { |
| panic(err) |
| } |
| if strings.Contains(string(output), "RUNNING") { |
| running = true |
| } |
| return running |
| } |
| |
| func (d *driver) Info(id string) execdriver.Info { |
| return &info{ |
| ID: id, |
| driver: d, |
| } |
| } |
| |
| func (d *driver) GetPidsForContainer(id string) ([]int, error) { |
| pids := []int{} |
| |
| // memory is chosen randomly, any cgroup used by docker works |
| subsystem := "memory" |
| |
| cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem) |
| if err != nil { |
| return pids, err |
| } |
| |
| cgroupDir, err := cgroups.GetThisCgroupDir(subsystem) |
| if err != nil { |
| return pids, err |
| } |
| |
| filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks") |
| if _, err := os.Stat(filename); os.IsNotExist(err) { |
| // With more recent lxc versions use, cgroup will be in lxc/ |
| filename = filepath.Join(cgroupRoot, cgroupDir, "lxc", id, "tasks") |
| } |
| |
| output, err := ioutil.ReadFile(filename) |
| if err != nil { |
| return pids, err |
| } |
| for _, p := range strings.Split(string(output), "\n") { |
| if len(p) == 0 { |
| continue |
| } |
| pid, err := strconv.Atoi(p) |
| if err != nil { |
| return pids, fmt.Errorf("Invalid pid '%s': %s", p, err) |
| } |
| pids = append(pids, pid) |
| } |
| return pids, nil |
| } |
| |
| func linkLxcStart(root string) error { |
| sourcePath, err := exec.LookPath("lxc-start") |
| if err != nil { |
| return err |
| } |
| targetPath := path.Join(root, "lxc-start-unconfined") |
| |
| if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) { |
| return err |
| } else if err == nil { |
| if err := os.Remove(targetPath); err != nil { |
| return err |
| } |
| } |
| return os.Symlink(sourcePath, targetPath) |
| } |
| |
| // TODO: This can be moved to the mountinfo reader in the mount pkg |
| func rootIsShared() bool { |
| if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { |
| for _, line := range strings.Split(string(data), "\n") { |
| cols := strings.Split(line, " ") |
| if len(cols) >= 6 && cols[4] == "/" { |
| return strings.HasPrefix(cols[6], "shared") |
| } |
| } |
| } |
| |
| // No idea, probably safe to assume so |
| return true |
| } |
| |
| func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { |
| root := path.Join(d.root, "containers", c.ID, "config.lxc") |
| fo, err := os.Create(root) |
| if err != nil { |
| return "", err |
| } |
| defer fo.Close() |
| |
| if err := LxcTemplateCompiled.Execute(fo, struct { |
| *execdriver.Command |
| AppArmor bool |
| }{ |
| Command: c, |
| AppArmor: d.apparmor, |
| }); err != nil { |
| return "", err |
| } |
| return root, nil |
| } |