open-nomad/client/executor/exec_linux.go

181 lines
4.7 KiB
Go
Raw Normal View History

package executor
import (
"fmt"
"os"
"os/user"
"syscall"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/nomad/structs"
2015-09-15 21:58:15 +00:00
cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
cgroupConfig "github.com/opencontainers/runc/libcontainer/configs"
)
func NewExecutor() Executor {
return &LinuxExecutor{}
}
// Linux executor is designed to run on linux kernel 2.8+.
2015-09-15 02:38:21 +00:00
type LinuxExecutor struct {
cmd
2015-09-15 21:58:15 +00:00
user *user.User
manager *cgroupFs.Manager
2015-09-15 02:38:21 +00:00
}
func (e *LinuxExecutor) Limit(resources *structs.Resources) error {
2015-09-15 21:58:15 +00:00
if resources == nil {
return nil
}
pid, err := e.Pid()
if err != nil {
return fmt.Errorf("Error getting pid: %s", err)
}
2015-09-15 02:38:21 +00:00
// TODO limit some things
2015-09-15 21:58:15 +00:00
if e.manager == nil {
// accept default paths, cgroups
e.manager = &cgroupFs.Manager{}
}
groups := cgroupConfig.Cgroup{}
// TODO: verify this is needed for things like network access
groups.AllowAllDevices = true
if resources.MemoryMB > 0 {
// Total amount of memory allowed to consume
groups.Memory = int64(resources.MemoryMB * 1024 * 1024)
// Disable swap to avoid issues on the machine
groups.MemorySwap = int64(-1)
}
if resources.CPU > 0.0 {
// Set the relative CPU shares for this cgroup.
// The simplest scale is 1 share to 1 MHz so 1024 = 1GHz. This means any
// given process will have at least that amount of resources, but likely
// more since it is (probably) rare that the machine will run at 100%
// CPU. This scale will cease to work if a node is overprovisioned.
groups.CpuShares = int64(resources.CPU)
}
if resources.IOPS > 0 {
groups.BlkioThrottleReadIOpsDevice = strconv.FormatInt(int64(resources.IOPS), 10)
groups.BlkioThrottleWriteIOpsDevice = strconv.FormatInt(int64(resources.IOPS), 10)
}
e.manager.Cgroups = &groups
e.manager.Apply(pid)
return nil
}
func (e *LinuxExecutor) RunAs(userid string) error {
errs := new(multierror.Error)
// First, try to lookup the user by uid
u, err := user.LookupId(userid)
if err == nil {
e.user = u
return nil
} else {
errs = multierror.Append(errs, err)
}
// Lookup failed, so try by username instead
u, err = user.Lookup(userid)
if err == nil {
e.user = u
return nil
} else {
errs = multierror.Append(errs, err)
}
2015-09-15 02:38:21 +00:00
// If we got here we failed to lookup based on id and username, so we'll
// return those errors.
return fmt.Errorf("Failed to identify user to run as: %s", errs)
}
func (e *LinuxExecutor) Start() error {
// If no user has been specified, try to run as "nobody" user so we don't
// leak root privilege to the spawned process. Note that we will only do
// this if we can call SetUID. Otherwise we'll just run the other process
// as our current (non-root) user. This makes testing easier and also means
// we aren't forced to run nomad as root.
if e.user == nil && canSetUID() {
2015-09-15 02:04:29 +00:00
e.RunAs("nobody")
}
// Set the user and group this process should run as. If RunAs was called
// but we are not root this will cause Start to fail. This is intentional.
if e.user != nil {
e.cmd.SetUID(e.user.Uid)
e.cmd.SetGID(e.user.Gid)
}
// We don't want to call ourself. We want to call Start on our embedded Cmd.
return e.cmd.Start()
}
func (e *LinuxExecutor) Open(pid int) error {
process, err := os.FindProcess(pid)
// FindProcess doesn't do any checking against the process table so it's
// unlikely we'll ever see this error.
if err != nil {
return fmt.Errorf("Failed to reopen pid %d: %s", pid, err)
}
// On linux FindProcess() will return a pid but doesn't actually check to
// see whether that process is running. We'll send signal 0 to see if the
// process is alive.
2015-09-15 02:38:21 +00:00
err = process.Signal(syscall.Signal(0))
if err != nil {
return fmt.Errorf("Unable to signal pid %d: %s", err)
}
e.Process = process
return nil
}
func (e *LinuxExecutor) Wait() error {
// We don't want to call ourself. We want to call Start on our embedded Cmd
return e.cmd.Wait()
}
func (e *LinuxExecutor) Pid() (int, error) {
if e.cmd.Process != nil {
return e.cmd.Process.Pid, nil
} else {
return 0, fmt.Errorf("Process has finished or was never started")
}
}
func (e *LinuxExecutor) Shutdown() error {
return e.ForceStop()
}
func (e *LinuxExecutor) ForceStop() error {
return e.Process.Kill()
}
func (e *LinuxExecutor) Command() *cmd {
return &e.cmd
}
// canSetUID will tell us whether we're capable of using SetUID. If we are not
// rootish this command will fail. In that case we'll just run the forked
// process under our own user.
func canSetUID() bool {
checkroot := Command("true")
u, err := user.Current()
if err != nil {
return false
}
// Make sure RunAs is explicitly set so we don't cause infinite recursion.
checkroot.RunAs(u.Uid)
err = checkroot.Start()
if err != nil {
return false
}
return true
}