runtime.go
a27b4b8c
 package docker
 
 import (
 	"container/list"
 	"fmt"
c72ff318
 	"github.com/dotcloud/docker/auth"
7c57a4cf
 	"io"
a27b4b8c
 	"io/ioutil"
003622c8
 	"log"
a27b4b8c
 	"os"
3dcaf20d
 	"os/exec"
a27b4b8c
 	"path"
f8f9285c
 	"sort"
3dcaf20d
 	"strings"
7c57a4cf
 	"time"
a27b4b8c
 )
 
640efc2e
 type Capabilities struct {
 	MemoryLimit bool
 	SwapLimit   bool
 }
 
b8547f31
 type Runtime struct {
bd2f5129
 	root           string
 	repository     string
 	containers     *list.List
 	networkManager *NetworkManager
ef711962
 	graph          *Graph
 	repositories   *TagStore
c72ff318
 	authConfig     *auth.AuthConfig
0b9a3c86
 	idIndex        *TruncIndex
640efc2e
 	capabilities   *Capabilities
003622c8
 	kernelVersion  *KernelVersionInfo
50144aeb
 	autoRestart    bool
1df5f409
 	volumes        *Graph
7c57a4cf
 }
 
 var sysInitPath string
 
 func init() {
 	sysInitPath = SelfPath()
a27b4b8c
 }
 
b8547f31
 func (runtime *Runtime) List() []*Container {
f8f9285c
 	containers := new(History)
b8547f31
 	for e := runtime.containers.Front(); e != nil; e = e.Next() {
f8f9285c
 		containers.Add(e.Value.(*Container))
c7a944ca
 	}
f8f9285c
 	return *containers
a27b4b8c
 }
 
b8547f31
 func (runtime *Runtime) getContainerElement(id string) *list.Element {
 	for e := runtime.containers.Front(); e != nil; e = e.Next() {
a27b4b8c
 		container := e.Value.(*Container)
78c02daf
 		if container.Id == id {
a27b4b8c
 			return e
 		}
 	}
 	return nil
 }
 
0b9a3c86
 func (runtime *Runtime) Get(name string) *Container {
 	id, err := runtime.idIndex.Get(name)
 	if err != nil {
 		return nil
 	}
b8547f31
 	e := runtime.getContainerElement(id)
a27b4b8c
 	if e == nil {
 		return nil
 	}
 	return e.Value.(*Container)
 }
 
b8547f31
 func (runtime *Runtime) Exists(id string) bool {
 	return runtime.Get(id) != nil
a27b4b8c
 }
 
b8547f31
 func (runtime *Runtime) containerRoot(id string) string {
 	return path.Join(runtime.repository, id)
7c57a4cf
 }
 
51d62282
 func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) {
 	if userConf.Hostname != "" {
 		userConf.Hostname = imageConf.Hostname
 	}
 	if userConf.User != "" {
 		userConf.User = imageConf.User
 	}
 	if userConf.Memory == 0 {
 		userConf.Memory = imageConf.Memory
 	}
 	if userConf.MemorySwap == 0 {
 		userConf.MemorySwap = imageConf.MemorySwap
 	}
 	if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
 		userConf.PortSpecs = imageConf.PortSpecs
 	}
 	if !userConf.Tty {
 		userConf.Tty = userConf.Tty
 	}
 	if !userConf.OpenStdin {
 		userConf.OpenStdin = imageConf.OpenStdin
 	}
 	if !userConf.StdinOnce {
 		userConf.StdinOnce = imageConf.StdinOnce
 	}
 	if userConf.Env == nil || len(userConf.Env) == 0 {
 		userConf.Env = imageConf.Env
 	}
 	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
 		userConf.Cmd = imageConf.Cmd
 	}
 	if userConf.Dns == nil || len(userConf.Dns) == 0 {
 		userConf.Dns = imageConf.Dns
 	}
 }
 
6ce64e84
 func (runtime *Runtime) Create(config *Config) (*Container, error) {
51d62282
 
379d449c
 	// Lookup image
6ce64e84
 	img, err := runtime.repositories.LookupImage(config.Image)
379d449c
 	if err != nil {
 		return nil, err
 	}
51d62282
 
 	if img.Config != nil {
7ff65d40
 		runtime.mergeConfig(config, img.Config)
51d62282
 	}
 
 	if config.Cmd == nil {
 		return nil, fmt.Errorf("No command specified")
 	}
 
54fa59c8
 	// Generate id
 	id := GenerateId()
 	// Generate default hostname
0b9a3c86
 	// FIXME: the lxc template no longer needs to set a default hostname
54fa59c8
 	if config.Hostname == "" {
 		config.Hostname = id[:12]
 	}
1f9f5eed
 
7c57a4cf
 	container := &Container{
 		// FIXME: we should generate the ID here instead of receiving it as an argument
54fa59c8
 		Id:              id,
031f91df
 		Created:         time.Now(),
 		Path:            config.Cmd[0],
 		Args:            config.Cmd[1:], //FIXME: de-duplicate from config
 		Config:          config,
 		Image:           img.Id, // Always use the resolved image id
7c57a4cf
 		NetworkSettings: &NetworkSettings{},
 		// FIXME: do we need to store this in the container?
 		SysInitPath: sysInitPath,
 	}
51d62282
 
b8547f31
 	container.root = runtime.containerRoot(container.Id)
7c57a4cf
 	// Step 1: create the container directory.
 	// This doubles as a barrier to avoid race conditions.
 	if err := os.Mkdir(container.root, 0700); err != nil {
 		return nil, err
 	}
7673afc8
 
 	// If custom dns exists, then create a resolv.conf for the container
 	if len(config.Dns) > 0 {
 		container.ResolvConfPath = path.Join(container.root, "resolv.conf")
 		f, err := os.Create(container.ResolvConfPath)
 		if err != nil {
 			return nil, err
 		}
 		defer f.Close()
 		for _, dns := range config.Dns {
 			if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
 				return nil, err
 			}
 		}
 	} else {
 		container.ResolvConfPath = "/etc/resolv.conf"
 	}
 
7c57a4cf
 	// Step 2: save the container json
 	if err := container.ToDisk(); err != nil {
 		return nil, err
 	}
 	// Step 3: register the container
b8547f31
 	if err := runtime.Register(container); err != nil {
7c57a4cf
 		return nil, err
a27b4b8c
 	}
7c57a4cf
 	return container, nil
 }
97a82094
 
b8547f31
 func (runtime *Runtime) Load(id string) (*Container, error) {
 	container := &Container{root: runtime.containerRoot(id)}
7c57a4cf
 	if err := container.FromDisk(); err != nil {
 		return nil, err
 	}
 	if container.Id != id {
 		return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
 	}
313d13ea
 	if container.State.Running {
 		container.State.Ghost = true
 	}
b8547f31
 	if err := runtime.Register(container); err != nil {
a27b4b8c
 		return nil, err
 	}
 	return container, nil
 }
 
7c57a4cf
 // Register makes a container object usable by the runtime as <container.Id>
b8547f31
 func (runtime *Runtime) Register(container *Container) error {
 	if container.runtime != nil || runtime.Exists(container.Id) {
7c57a4cf
 		return fmt.Errorf("Container is already loaded")
 	}
 	if err := validateId(container.Id); err != nil {
 		return err
 	}
3dcaf20d
 
9f83b9df
 	// init the wait lock
 	container.waitLock = make(chan struct{})
 
 	// Even if not running, we init the lock (prevents races in start/stop/kill)
7c2b085d
 	container.State.initLock()
3dcaf20d
 
b8547f31
 	container.runtime = runtime
329f4449
 
7c57a4cf
 	// Attach to stdout and stderr
 	container.stderr = newWriteBroadcaster()
 	container.stdout = newWriteBroadcaster()
 	// Attach to stdin
 	if container.Config.OpenStdin {
 		container.stdin, container.stdinPipe = io.Pipe()
 	} else {
 		container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
 	}
 	// done
b8547f31
 	runtime.containers.PushBack(container)
0b9a3c86
 	runtime.idIndex.Add(container.Id)
82848d41
 
50144aeb
 	// When we actually restart, Start() do the monitoring.
 	// However, when we simply 'reattach', we have to restart a monitor
 	nomonitor := false
 
 	// FIXME: if the container is supposed to be running but is not, auto restart it?
 	//        if so, then we need to restart monitor and init a new lock
 	// If the container is supposed to be running, make sure of it
 	if container.State.Running {
 		if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
 			return err
 		} else {
 			if !strings.Contains(string(output), "RUNNING") {
 				Debugf("Container %s was supposed to be running be is not.", container.Id)
 				if runtime.autoRestart {
 					Debugf("Restarting")
 					container.State.Ghost = false
 					container.State.setStopped(0)
 					if err := container.Start(); err != nil {
 						return err
 					}
 					nomonitor = true
 				} else {
 					Debugf("Marking as stopped")
 					container.State.setStopped(-127)
 					if err := container.ToDisk(); err != nil {
 						return err
 					}
 				}
 			}
 		}
 	}
 
82848d41
 	// If the container is not running or just has been flagged not running
 	// then close the wait lock chan (will be reset upon start)
 	if !container.State.Running {
 		close(container.waitLock)
50144aeb
 	} else if !nomonitor {
82848d41
 		container.allocateNetwork()
 		go container.monitor()
 	}
7c57a4cf
 	return nil
 }
 
b8547f31
 func (runtime *Runtime) LogToDisk(src *writeBroadcaster, dst string) error {
7c57a4cf
 	log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
 	if err != nil {
 		return err
 	}
0f7a4534
 	src.AddWriter(log)
7c57a4cf
 	return nil
 }
 
b8547f31
 func (runtime *Runtime) Destroy(container *Container) error {
 	element := runtime.getContainerElement(container.Id)
a27b4b8c
 	if element == nil {
78c02daf
 		return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
a27b4b8c
 	}
 
1615bb08
 	if err := container.Stop(10); err != nil {
a27b4b8c
 		return err
 	}
7c57a4cf
 	if mounted, err := container.Mounted(); err != nil {
 		return err
 	} else if mounted {
 		if err := container.Unmount(); err != nil {
 			return fmt.Errorf("Unable to unmount container %v: %v", container.Id, err)
fb40a788
 		}
fcc0af9f
 	}
7c57a4cf
 	// Deregister the container before removing its directory, to avoid race conditions
0b9a3c86
 	runtime.idIndex.Delete(container.Id)
b8547f31
 	runtime.containers.Remove(element)
7c57a4cf
 	if err := os.RemoveAll(container.root); err != nil {
54b44e37
 		return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
a27b4b8c
 	}
 	return nil
 }
 
8396798e
 // Commit creates a new filesystem image from the current state of a container.
 // The image can optionally be tagged into a repository
51d62282
 func (runtime *Runtime) Commit(id, repository, tag, comment, author string, config *Config) (*Image, error) {
8396798e
 	container := runtime.Get(id)
 	if container == nil {
 		return nil, fmt.Errorf("No such container: %s", id)
 	}
 	// FIXME: freeze the container before copying it to avoid data corruption?
 	// FIXME: this shouldn't be in commands.
 	rwTar, err := container.ExportRw()
 	if err != nil {
 		return nil, err
 	}
 	// Create a new image from the container's base layers + a new layer from container changes
51d62282
 	img, err := runtime.graph.Create(rwTar, container, comment, author, config)
8396798e
 	if err != nil {
 		return nil, err
 	}
 	// Register the image if needed
 	if repository != "" {
bf7602bc
 		if err := runtime.repositories.Set(repository, tag, img.Id, true); err != nil {
8396798e
 			return img, err
 		}
 	}
 	return img, nil
 }
 
b8547f31
 func (runtime *Runtime) restore() error {
 	dir, err := ioutil.ReadDir(runtime.repository)
a27b4b8c
 	if err != nil {
 		return err
 	}
 	for _, v := range dir {
7c57a4cf
 		id := v.Name()
b8547f31
 		container, err := runtime.Load(id)
a27b4b8c
 		if err != nil {
6e507b94
 			Debugf("Failed to load container %v: %v", id, err)
a27b4b8c
 			continue
 		}
6e507b94
 		Debugf("Loaded container %v", container.Id)
a27b4b8c
 	}
 	return nil
 }
 
9042535f
 func (runtime *Runtime) UpdateCapabilities(quiet bool) {
 	if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil {
 		if !quiet {
 			log.Printf("WARNING: %s\n", err)
 		}
 	} else {
 		_, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes"))
 		_, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes"))
 		runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil
 		if !runtime.capabilities.MemoryLimit && !quiet {
 			log.Printf("WARNING: Your kernel does not support cgroup memory limit.")
 		}
 
 		_, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes"))
 		runtime.capabilities.SwapLimit = err == nil
 		if !runtime.capabilities.SwapLimit && !quiet {
 			log.Printf("WARNING: Your kernel does not support cgroup swap limit.")
 		}
 	}
 }
 
0b9a3c86
 // FIXME: harmonize with NewGraph()
50144aeb
 func NewRuntime(autoRestart bool) (*Runtime, error) {
 	runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
003622c8
 	if err != nil {
 		return nil, err
 	}
 
3514e47e
 	if k, err := GetKernelVersion(); err != nil {
 		log.Printf("WARNING: %s\n", err)
 	} else {
 		runtime.kernelVersion = k
 		if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
 			log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
 		}
003622c8
 	}
9042535f
 	runtime.UpdateCapabilities(false)
003622c8
 	return runtime, nil
a27b4b8c
 }
 
50144aeb
 func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
a6da7f13
 	runtimeRepo := path.Join(root, "containers")
06553a75
 
a6da7f13
 	if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
06553a75
 		return nil, err
 	}
 
ef711962
 	g, err := NewGraph(path.Join(root, "graph"))
2ebf3464
 	if err != nil {
 		return nil, err
 	}
1df5f409
 	volumes, err := NewGraph(path.Join(root, "volumes"))
 	if err != nil {
 		return nil, err
 	}
ef711962
 	repositories, err := NewTagStore(path.Join(root, "repositories"), g)
44faa07b
 	if err != nil {
 		return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
 	}
1b370f9d
 	if NetworkBridgeIface == "" {
 		NetworkBridgeIface = DefaultNetworkBridge
 	}
f39af7e0
 	netManager, err := newNetworkManager(NetworkBridgeIface)
c08f5b2b
 	if err != nil {
 		return nil, err
 	}
c72ff318
 	authConfig, err := auth.LoadConfig(root)
 	if err != nil && authConfig == nil {
 		// If the auth file does not exist, keep going
 		return nil, err
 	}
b8547f31
 	runtime := &Runtime{
bd2f5129
 		root:           root,
a6da7f13
 		repository:     runtimeRepo,
bd2f5129
 		containers:     list.New(),
 		networkManager: netManager,
44faa07b
 		graph:          g,
 		repositories:   repositories,
c72ff318
 		authConfig:     authConfig,
0b9a3c86
 		idIndex:        NewTruncIndex(),
640efc2e
 		capabilities:   &Capabilities{},
50144aeb
 		autoRestart:    autoRestart,
1df5f409
 		volumes:        volumes,
a27b4b8c
 	}
 
b8547f31
 	if err := runtime.restore(); err != nil {
a27b4b8c
 		return nil, err
 	}
b8547f31
 	return runtime, nil
a27b4b8c
 }
f8f9285c
 
 type History []*Container
 
 func (history *History) Len() int {
 	return len(*history)
 }
 
 func (history *History) Less(i, j int) bool {
 	containers := *history
 	return containers[j].When().Before(containers[i].When())
 }
 
 func (history *History) Swap(i, j int) {
 	containers := *history
 	tmp := containers[i]
 	containers[i] = containers[j]
 	containers[j] = tmp
 }
 
 func (history *History) Add(container *Container) {
 	*history = append(*history, container)
 	sort.Sort(history)
 }