diff --git a/commands.go b/commands.go index e558c5f6b8..79619ca44d 100644 --- a/commands.go +++ b/commands.go @@ -1,20 +1,16 @@ package docker import ( - "bufio" "bytes" "encoding/json" "errors" "fmt" "github.com/dotcloud/docker/auth" - "github.com/dotcloud/docker/fs" "github.com/dotcloud/docker/future" "github.com/dotcloud/docker/rcli" "io" - "io/ioutil" "net/http" "net/url" - "os" "path" "strconv" "strings" @@ -137,7 +133,7 @@ func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...str // 'docker info': display system-wide information. func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - images, _ := srv.images.Images() + images, _ := srv.containers.graph.All() var imgcount int if images == nil { imgcount = 0 @@ -236,7 +232,7 @@ func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...strin } for _, name := range cmd.Args() { if container := srv.containers.Get(name); container != nil { - if err := container.Mountpoint.EnsureMounted(); err != nil { + if err := container.EnsureMounted(); err != nil { return err } fmt.Fprintln(stdout, container.Id) @@ -260,7 +256,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str var obj interface{} if container := srv.containers.Get(name); container != nil { obj = container - } else if image, err := srv.images.Find(name); err != nil { + } else if image, err := srv.containers.graph.Get(name); err != nil { return err } else if image != nil { obj = image @@ -310,27 +306,12 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string // 'docker rmi NAME' removes all images with the name NAME func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image") - fl_all := cmd.Bool("a", false, "Use IMAGE as a path and remove ALL images in this path") - fl_regexp := cmd.Bool("r", false, "Use IMAGE as a regular expression instead of an exact name") if cmd.Parse(args) != nil || cmd.NArg() < 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { - if *fl_regexp { - err = srv.images.RemoveRegexp(name) - } else if *fl_all { - err = srv.images.RemoveInPath(name) - } else { - if image, err1 := srv.images.Find(name); err1 != nil { - err = err1 - } else if err1 == nil && image == nil { - err = fmt.Errorf("No such image: %s", name) - } else { - err = srv.images.Remove(image) - } - } - if err != nil { + if err := srv.containers.graph.Delete(name); err != nil { return err } } @@ -409,7 +390,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri archive = future.ProgressReader(resp.Body, int(resp.ContentLength), stdout) } fmt.Fprintf(stdout, "Unpacking to %s\n", name) - img, err := srv.images.Create(archive, nil, name, "") + img, err := srv.containers.graph.Create(archive, "", "") if err != nil { return err } @@ -419,7 +400,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images") - limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") + //limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") quiet := cmd.Bool("q", false, "only show numeric IDs") if err := cmd.Parse(args); err != nil { return nil @@ -428,51 +409,65 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - var nameFilter string - if cmd.NArg() == 1 { - nameFilter = cmd.Arg(0) - } + /* + var nameFilter string + if cmd.NArg() == 1 { + nameFilter = cmd.Arg(0) + } + */ w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) if !*quiet { fmt.Fprintf(w, "NAME\tID\tCREATED\tPARENT\n") } - paths, err := srv.images.Paths() - if err != nil { - return err - } - for _, name := range paths { - if nameFilter != "" && nameFilter != name { - continue - } - ids, err := srv.images.List(name) + if *quiet { + images, err := srv.containers.graph.All() if err != nil { return err } - for idx, img := range ids { - if *limit > 0 && idx >= *limit { - break - } - if !*quiet { - for idx, field := range []string{ - /* NAME */ name, - /* ID */ img.Id, - /* CREATED */ future.HumanDuration(time.Now().Sub(time.Unix(img.Created, 0))) + " ago", - /* PARENT */ img.Parent, - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } - } - w.Write([]byte{'\n'}) - } else { - stdout.Write([]byte(img.Id + "\n")) - } + for _, image := range images { + fmt.Fprintln(stdout, image.Id) } - } - if !*quiet { - w.Flush() + } else { + // FIXME: + // paths, err := srv.images.Paths() + // if err != nil { + // return err + // } + // for _, name := range paths { + // if nameFilter != "" && nameFilter != name { + // continue + // } + // ids, err := srv.images.List(name) + // if err != nil { + // return err + // } + // for idx, img := range ids { + // if *limit > 0 && idx >= *limit { + // break + // } + // if !*quiet { + // for idx, field := range []string{ + // /* NAME */ name, + // /* ID */ img.Id, + // /* CREATED */ future.HumanDuration(time.Now().Sub(time.Unix(img.Created, 0))) + " ago", + // /* PARENT */ img.Parent, + // } { + // if idx == 0 { + // w.Write([]byte(field)) + // } else { + // w.Write([]byte("\t" + field)) + // } + // } + // w.Write([]byte{'\n'}) + // } else { + // stdout.Write([]byte(img.Id + "\n")) + // } + // } + // } + // if !*quiet { + // w.Flush() + // } + // } return nil @@ -492,7 +487,6 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n") } for _, container := range srv.containers.List() { - comment := container.GetUserData("comment") if !container.State.Running && !*fl_all { continue } @@ -503,11 +497,11 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) } for idx, field := range []string{ /* ID */ container.Id, - /* IMAGE */ container.GetUserData("image"), + /* IMAGE */ container.Image, /* COMMAND */ command, /* CREATED */ future.HumanDuration(time.Now().Sub(container.Created)) + " ago", /* STATUS */ container.State.String(), - /* COMMENT */ comment, + /* COMMENT */ "", } { if idx == 0 { w.Write([]byte(field)) @@ -540,17 +534,13 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri } if container := srv.containers.Get(containerName); container != nil { // FIXME: freeze the container before copying it to avoid data corruption? - rwTar, err := fs.Tar(container.Mountpoint.Rw, fs.Uncompressed) + // FIXME: this shouldn't be in commands. + rwTar, err := container.ExportRw() if err != nil { return err } // Create a new image from the container's base layers + a new layer from container changes - parentImg, err := srv.images.Get(container.Image) - if err != nil { - return err - } - - img, err := srv.images.Create(rwTar, parentImg, imgName, "") + img, err := srv.containers.graph.Create(rwTar, container.Image, "") if err != nil { return err } @@ -574,10 +564,10 @@ func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) } name := cmd.Arg(0) if container := srv.containers.Get(name); container != nil { - if err := container.Mountpoint.EnsureMounted(); err != nil { + if err := container.EnsureMounted(); err != nil { return err } - data, err := fs.Tar(container.Mountpoint.Root, fs.Uncompressed) + data, err := container.Export() if err != nil { return err } @@ -603,7 +593,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string if container := srv.containers.Get(cmd.Arg(0)); container == nil { return errors.New("No such container") } else { - changes, err := srv.images.Changes(container.Mountpoint) + changes, err := container.Changes() if err != nil { return err } @@ -625,10 +615,20 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string } name := cmd.Arg(0) if container := srv.containers.Get(name); container != nil { - if _, err := io.Copy(stdout, container.StdoutLog()); err != nil { + log_stdout, err := container.ReadLog("stdout") + if err != nil { return err } - if _, err := io.Copy(stdout, container.StderrLog()); err != nil { + log_stderr, err := container.ReadLog("stderr") + if err != nil { + return err + } + // FIXME: Interpolate stdout and stderr instead of concatenating them + // FIXME: Differentiate stdout and stderr in the remote protocol + if _, err := io.Copy(stdout, log_stdout); err != nil { + return err + } + if _, err := io.Copy(stdout, log_stderr); err != nil { return err } return nil @@ -636,31 +636,6 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string return errors.New("No such container: " + cmd.Arg(0)) } -func (srv *Server) CreateContainer(img *fs.Image, ports []int, user string, tty bool, openStdin bool, memory int64, comment string, cmd string, args ...string) (*Container, error) { - id := future.RandomId()[:8] - container, err := srv.containers.Create(id, cmd, args, img, - &Config{ - Hostname: id, - Ports: ports, - User: user, - Tty: tty, - OpenStdin: openStdin, - Memory: memory, - }) - if err != nil { - return nil, err - } - if err := container.SetUserData("image", img.Id); err != nil { - srv.containers.Destroy(container) - return nil, errors.New("Error setting container userdata: " + err.Error()) - } - if err := container.SetUserData("comment", comment); err != nil { - srv.containers.Destroy(container) - return nil, errors.New("Error setting container userdata: " + err.Error()) - } - return container, nil -} - func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "attach", "[OPTIONS]", "Attach to a running container") fl_i := cmd.Bool("i", false, "Attach to stdin") @@ -729,7 +704,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) fl_attach := cmd.Bool("a", false, "Attach stdin and stdout") fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached") fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty") - fl_comment := cmd.String("c", "", "Comment") fl_memory := cmd.Int64("m", 0, "Memory limit (in bytes)") var fl_ports ports @@ -738,8 +712,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) return nil } name := cmd.Arg(0) - var img_name string - //var img_version string // Only here for reference var cmdline []string if len(cmd.Args()) >= 2 { @@ -758,33 +730,15 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) cmdline = []string{"/bin/bash", "-i"} } - // Find the image - img, err := srv.images.Find(name) - if err != nil { - return err - } else if img == nil { - // Separate the name:version tag - if strings.Contains(name, ":") { - parts := strings.SplitN(name, ":", 2) - img_name = parts[0] - //img_version = parts[1] // Only here for reference - } else { - img_name = name - } - - stdin_noclose := ioutil.NopCloser(stdin) - if err := srv.CmdImport(stdin_noclose, stdout, img_name); err != nil { - return err - } - img, err = srv.images.Find(name) - if err != nil || img == nil { - return errors.New("Could not find image after downloading: " + name) - } - } - // Create new container - container, err := srv.CreateContainer(img, fl_ports, *fl_user, *fl_tty, - *fl_stdin, *fl_memory, *fl_comment, cmdline[0], cmdline[1:]...) + container, err := srv.containers.Create(cmdline[0], cmdline[1:], name, + &Config{ + Ports: fl_ports, + User: *fl_user, + Tty: *fl_tty, + OpenStdin: *fl_stdin, + Memory: *fl_memory, + }) if err != nil { return errors.New("Error creating container: " + err.Error()) } @@ -850,7 +804,6 @@ func NewServer() (*Server, error) { return nil, err } srv := &Server{ - images: containers.Store, containers: containers, } return srv, nil @@ -858,5 +811,4 @@ func NewServer() (*Server, error) { type Server struct { containers *Docker - images *fs.Store } diff --git a/container.go b/container.go index c80129e2a8..26e18cc1de 100644 --- a/container.go +++ b/container.go @@ -3,7 +3,9 @@ package docker import ( "encoding/json" "errors" - "github.com/dotcloud/docker/fs" + "fmt" + "github.com/dotcloud/docker/future" + "github.com/dotcloud/docker/graph" "github.com/kr/pty" "io" "io/ioutil" @@ -16,40 +18,34 @@ import ( "time" ) -var sysInitPath string - -func init() { - sysInitPath = SelfPath() -} - type Container struct { - Id string - Root string + root string + + Id string Created time.Time Path string Args []string - Config *Config - Mountpoint *fs.Mountpoint - State *State - Image string + Config *Config + State State + Image string network *NetworkInterface networkManager *NetworkManager NetworkSettings *NetworkSettings - SysInitPath string - lxcConfigPath string - cmd *exec.Cmd - stdout *writeBroadcaster - stderr *writeBroadcaster - stdin io.ReadCloser - stdinPipe io.WriteCloser + SysInitPath string + cmd *exec.Cmd + stdout *writeBroadcaster + stderr *writeBroadcaster + stdin io.ReadCloser + stdinPipe io.WriteCloser stdoutLog *os.File stderrLog *os.File + runtime *Docker // FIXME: rename Docker to Runtime for clarity } type Config struct { @@ -69,104 +65,9 @@ type NetworkSettings struct { PortMapping map[string]string } -func createContainer(id string, root string, command string, args []string, image *fs.Image, config *Config, netManager *NetworkManager) (*Container, error) { - mountpoint, err := image.Mountpoint(path.Join(root, "rootfs"), path.Join(root, "rw")) - if err != nil { - return nil, err - } - container := &Container{ - Id: id, - Root: root, - Created: time.Now(), - Path: command, - Args: args, - Config: config, - Image: image.Id, - Mountpoint: mountpoint, - State: newState(), - networkManager: netManager, - NetworkSettings: &NetworkSettings{}, - SysInitPath: sysInitPath, - lxcConfigPath: path.Join(root, "config.lxc"), - stdout: newWriteBroadcaster(), - stderr: newWriteBroadcaster(), - } - if err := os.Mkdir(root, 0700); err != nil { - return nil, err - } - // Setup logging of stdout and stderr to disk - if stdoutLog, err := os.OpenFile(path.Join(container.Root, id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil { - return nil, err - } else { - container.stdoutLog = stdoutLog - } - if stderrLog, err := os.OpenFile(path.Join(container.Root, id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil { - return nil, err - } else { - container.stderrLog = stderrLog - } - if container.Config.OpenStdin { - container.stdin, container.stdinPipe = io.Pipe() - } else { - container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin - } - container.stdout.AddWriter(NopWriteCloser(container.stdoutLog)) - container.stderr.AddWriter(NopWriteCloser(container.stderrLog)) - - if err := container.save(); err != nil { - return nil, err - } - return container, nil -} - -func loadContainer(store *fs.Store, containerPath string, netManager *NetworkManager) (*Container, error) { - data, err := ioutil.ReadFile(path.Join(containerPath, "config.json")) - if err != nil { - return nil, err - } - mountpoint, err := store.FetchMountpoint( - path.Join(containerPath, "rootfs"), - path.Join(containerPath, "rw"), - ) - if err != nil { - return nil, err - } else if mountpoint == nil { - return nil, errors.New("Couldn't load container: unregistered mountpoint.") - } - container := &Container{ - stdout: newWriteBroadcaster(), - stderr: newWriteBroadcaster(), - lxcConfigPath: path.Join(containerPath, "config.lxc"), - networkManager: netManager, - NetworkSettings: &NetworkSettings{}, - Mountpoint: mountpoint, - } - // Load container settings - if err := json.Unmarshal(data, container); err != nil { - return nil, err - } - - // Setup logging of stdout and stderr to disk - if stdoutLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil { - return nil, err - } else { - container.stdoutLog = stdoutLog - } - if stderrLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil { - return nil, err - } else { - container.stderrLog = stderrLog - } - container.stdout.AddWriter(NopWriteCloser(container.stdoutLog)) - container.stderr.AddWriter(NopWriteCloser(container.stderrLog)) - - if container.Config.OpenStdin { - container.stdin, container.stdinPipe = io.Pipe() - } else { - container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin - } - container.State = newState() - return container, nil +func GenerateId() string { + future.Seed() + return future.RandomId() } func (container *Container) Cmd() *exec.Cmd { @@ -177,64 +78,32 @@ func (container *Container) When() time.Time { return container.Created } -func (container *Container) loadUserData() (map[string]string, error) { - jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json")) - if err != nil { - if os.IsNotExist(err) { - return make(map[string]string), nil - } - return nil, err - } - data := make(map[string]string) - if err := json.Unmarshal(jsonData, &data); err != nil { - return nil, err - } - return data, nil -} - -func (container *Container) saveUserData(data map[string]string) error { - jsonData, err := json.Marshal(data) +func (container *Container) FromDisk() error { + data, err := ioutil.ReadFile(container.jsonPath()) if err != nil { return err } - return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700) -} - -func (container *Container) SetUserData(key, value string) error { - data, err := container.loadUserData() - if err != nil { + // Load container settings + if err := json.Unmarshal(data, container); err != nil { return err } - data[key] = value - return container.saveUserData(data) + return nil } -func (container *Container) GetUserData(key string) string { - data, err := container.loadUserData() - if err != nil { - return "" - } - if value, exists := data[key]; exists { - return value - } - return "" -} - -func (container *Container) save() (err error) { +func (container *Container) ToDisk() (err error) { data, err := json.Marshal(container) if err != nil { return } - return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0666) + return ioutil.WriteFile(container.jsonPath(), data, 0666) } func (container *Container) generateLXCConfig() error { - fo, err := os.Create(container.lxcConfigPath) + fo, err := os.Create(container.lxcConfigPath()) if err != nil { return err } defer fo.Close() - if err := LxcTemplateCompiled.Execute(fo, container); err != nil { return err } @@ -309,7 +178,7 @@ func (container *Container) start() error { } func (container *Container) Start() error { - if err := container.Mountpoint.EnsureMounted(); err != nil { + if err := container.EnsureMounted(); err != nil { return err } if err := container.allocateNetwork(); err != nil { @@ -320,7 +189,7 @@ func (container *Container) Start() error { } params := []string{ "-n", container.Id, - "-f", container.lxcConfigPath, + "-f", container.lxcConfigPath(), "--", "/sbin/init", } @@ -348,8 +217,10 @@ func (container *Container) Start() error { if err != nil { return err } + // FIXME: save state on disk *first*, then converge + // this way disk state is used as a journal, eg. we can restore after crash etc. container.State.setRunning(container.cmd.Process.Pid) - container.save() + container.ToDisk() go container.monitor() return nil } @@ -389,28 +260,12 @@ func (container *Container) StdoutPipe() (io.ReadCloser, error) { return newBufReader(reader), nil } -func (container *Container) StdoutLog() io.Reader { - r, err := os.Open(container.stdoutLog.Name()) - if err != nil { - return nil - } - return r -} - func (container *Container) StderrPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() container.stderr.AddWriter(writer) return newBufReader(reader), nil } -func (container *Container) StderrLog() io.Reader { - r, err := os.Open(container.stderrLog.Name()) - if err != nil { - return nil - } - return r -} - func (container *Container) allocateNetwork() error { iface, err := container.networkManager.Allocate() if err != nil { @@ -450,7 +305,7 @@ func (container *Container) monitor() { } container.stdout.Close() container.stderr.Close() - if err := container.Mountpoint.Umount(); err != nil { + if err := container.Unmount(); err != nil { log.Printf("%v: Failed to umount filesystem: %v", container.Id, err) } @@ -461,7 +316,7 @@ func (container *Container) monitor() { // Report status back container.State.setStopped(exitCode) - container.save() + container.ToDisk() } func (container *Container) kill() error { @@ -523,6 +378,17 @@ func (container *Container) Wait() int { return container.State.ExitCode } +func (container *Container) ExportRw() (graph.Archive, error) { + return graph.Tar(container.rwPath(), graph.Uncompressed) +} + +func (container *Container) Export() (graph.Archive, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } + return graph.Tar(container.RootfsPath(), graph.Uncompressed) +} + func (container *Container) WaitTimeout(timeout time.Duration) error { done := make(chan bool) go func() { @@ -538,3 +404,75 @@ func (container *Container) WaitTimeout(timeout time.Duration) error { } return nil } + +func (container *Container) EnsureMounted() error { + if mounted, err := container.Mounted(); err != nil { + return err + } else if mounted { + return nil + } + return container.Mount() +} + +func (container *Container) Mount() error { + image, err := container.GetImage() + if err != nil { + return err + } + return image.Mount(container.RootfsPath(), container.rwPath()) +} + +func (container *Container) Changes() ([]graph.Change, error) { + image, err := container.GetImage() + if err != nil { + return nil, err + } + return image.Changes(container.rwPath()) +} + +func (container *Container) GetImage() (*graph.Image, error) { + if container.runtime == nil { + return nil, fmt.Errorf("Can't get image of unregistered container") + } + return container.runtime.graph.Get(container.Image) +} + +func (container *Container) Mounted() (bool, error) { + return graph.Mounted(container.RootfsPath()) +} + +func (container *Container) Unmount() error { + return graph.Unmount(container.RootfsPath()) +} + +func (container *Container) logPath(name string) string { + return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name)) +} + +func (container *Container) ReadLog(name string) (io.Reader, error) { + return os.Open(container.logPath(name)) +} + +func (container *Container) jsonPath() string { + return path.Join(container.root, "config.json") +} + +func (container *Container) lxcConfigPath() string { + return path.Join(container.root, "config.lxc") +} + +// This method must be exported to be used from the lxc template +func (container *Container) RootfsPath() string { + return path.Join(container.root, "rootfs") +} + +func (container *Container) rwPath() string { + return path.Join(container.root, "rw") +} + +func validateId(id string) error { + if id == "" { + return fmt.Errorf("Invalid empty id") + } + return nil +} diff --git a/container_test.go b/container_test.go index 18beeba253..1658e5832c 100644 --- a/container_test.go +++ b/container_test.go @@ -3,7 +3,6 @@ package docker import ( "bufio" "fmt" - "github.com/dotcloud/docker/fs" "io" "io/ioutil" "math/rand" @@ -21,10 +20,9 @@ func TestCommitRun(t *testing.T) { } defer nuke(docker) container1, err := docker.Create( - "precommit_test", "/bin/sh", []string{"-c", "echo hello > /world"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ Memory: 33554432, }, @@ -44,18 +42,11 @@ func TestCommitRun(t *testing.T) { t.Errorf("Container shouldn't be running") } - // FIXME: freeze the container before copying it to avoid data corruption? - rwTar, err := fs.Tar(container1.Mountpoint.Rw, fs.Uncompressed) + rwTar, err := container1.ExportRw() if err != nil { t.Error(err) } - // Create a new image from the container's base layers + a new layer from container changes - parentImg, err := docker.Store.Get(container1.Image) - if err != nil { - t.Error(err) - } - - img, err := docker.Store.Create(rwTar, parentImg, "test_commitrun", "unit test commited image") + img, err := docker.graph.Create(rwTar, container1.Image, "unit test commited image") if err != nil { t.Error(err) } @@ -63,10 +54,9 @@ func TestCommitRun(t *testing.T) { // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world container2, err := docker.Create( - "postcommit_test", "cat", []string{"/world"}, - img, + img.Id, &Config{ Memory: 33554432, }, @@ -98,10 +88,9 @@ func TestRun(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "run_test", "ls", []string{"-al"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ Memory: 33554432, }, @@ -129,10 +118,9 @@ func TestOutput(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "output_test", "echo", []string{"-n", "foobar"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -155,10 +143,9 @@ func TestKill(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "stop_test", "cat", []string{"/dev/zero"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -199,10 +186,9 @@ func TestExitCode(t *testing.T) { defer nuke(docker) trueContainer, err := docker.Create( - "exit_test_1", "/bin/true", []string{""}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -214,10 +200,9 @@ func TestExitCode(t *testing.T) { } falseContainer, err := docker.Create( - "exit_test_2", "/bin/false", []string{""}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -244,10 +229,9 @@ func TestRestart(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "restart_test", "echo", []string{"-n", "foobar"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -279,10 +263,9 @@ func TestRestartStdin(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "restart_stdin_test", "cat", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ OpenStdin: true, }, @@ -331,10 +314,9 @@ func TestUser(t *testing.T) { // Default user must be root container, err := docker.Create( - "user_default", "id", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -351,10 +333,9 @@ func TestUser(t *testing.T) { // Set a username container, err = docker.Create( - "user_root", "id", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ User: "root", }, @@ -373,10 +354,9 @@ func TestUser(t *testing.T) { // Set a UID container, err = docker.Create( - "user_uid0", "id", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ User: "0", }, @@ -395,10 +375,9 @@ func TestUser(t *testing.T) { // Set a different user by uid container, err = docker.Create( - "user_uid1", "id", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ User: "1", }, @@ -419,10 +398,9 @@ func TestUser(t *testing.T) { // Set a different user by username container, err = docker.Create( - "user_daemon", "id", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ User: "daemon", }, @@ -448,10 +426,9 @@ func TestMultipleContainers(t *testing.T) { defer nuke(docker) container1, err := docker.Create( - "container1", "cat", []string{"/dev/zero"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -460,10 +437,9 @@ func TestMultipleContainers(t *testing.T) { defer docker.Destroy(container1) container2, err := docker.Create( - "container2", "cat", []string{"/dev/zero"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -504,10 +480,9 @@ func TestStdin(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "stdin_test", "cat", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ OpenStdin: true, }, @@ -540,10 +515,9 @@ func TestTty(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "tty_test", "cat", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ OpenStdin: true, }, @@ -576,10 +550,9 @@ func TestEnv(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "env_test", "/usr/bin/env", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -651,10 +624,9 @@ func TestLXCConfig(t *testing.T) { memMax := 536870912 mem := memMin + rand.Intn(memMax-memMin) container, err := docker.Create( - "config_test", "/bin/true", []string{}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{ Hostname: "foobar", Memory: int64(mem), @@ -665,10 +637,10 @@ func TestLXCConfig(t *testing.T) { } defer docker.Destroy(container) container.generateLXCConfig() - grepFile(t, container.lxcConfigPath, "lxc.utsname = foobar") - grepFile(t, container.lxcConfigPath, + grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar") + grepFile(t, container.lxcConfigPath(), fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem)) - grepFile(t, container.lxcConfigPath, + grepFile(t, container.lxcConfigPath(), fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2)) } @@ -680,10 +652,9 @@ func BenchmarkRunSequencial(b *testing.B) { defer nuke(docker) for i := 0; i < b.N; i++ { container, err := docker.Create( - fmt.Sprintf("bench_%v", i), "echo", []string{"-n", "foo"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -717,10 +688,9 @@ func BenchmarkRunParallel(b *testing.B) { tasks = append(tasks, complete) go func(i int, complete chan error) { container, err := docker.Create( - fmt.Sprintf("bench_%v", i), "echo", []string{"-n", "foo"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { diff --git a/docker.go b/docker.go index 626c7452ea..65654ba752 100644 --- a/docker.go +++ b/docker.go @@ -3,12 +3,15 @@ package docker import ( "container/list" "fmt" - "github.com/dotcloud/docker/fs" + "github.com/dotcloud/docker/graph" + "io" "io/ioutil" "log" "os" "path" "sort" + "sync" + "time" ) type Docker struct { @@ -16,7 +19,13 @@ type Docker struct { repository string containers *list.List networkManager *NetworkManager - Store *fs.Store + graph *graph.Graph +} + +var sysInitPath string + +func init() { + sysInitPath = SelfPath() } func (docker *Docker) List() []*Container { @@ -49,20 +58,98 @@ func (docker *Docker) Exists(id string) bool { return docker.Get(id) != nil } -func (docker *Docker) Create(id string, command string, args []string, image *fs.Image, config *Config) (*Container, error) { - if docker.Exists(id) { - return nil, fmt.Errorf("Container %v already exists", id) - } - root := path.Join(docker.repository, id) +func (docker *Docker) containerRoot(id string) string { + return path.Join(docker.repository, id) +} - container, err := createContainer(id, root, command, args, image, config, docker.networkManager) - if err != nil { +func (docker *Docker) Create(command string, args []string, image string, config *Config) (*Container, error) { + container := &Container{ + // FIXME: we should generate the ID here instead of receiving it as an argument + Id: GenerateId(), + Created: time.Now(), + Path: command, + Args: args, + Config: config, + Image: image, + NetworkSettings: &NetworkSettings{}, + // FIXME: do we need to store this in the container? + SysInitPath: sysInitPath, + } + container.root = docker.containerRoot(container.Id) + // 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 + } + // Step 2: save the container json + if err := container.ToDisk(); err != nil { + return nil, err + } + // Step 3: register the container + if err := docker.Register(container); err != nil { return nil, err } - docker.containers.PushBack(container) return container, nil } +func (docker *Docker) Load(id string) (*Container, error) { + container := &Container{root: docker.containerRoot(id)} + 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) + } + if err := docker.Register(container); err != nil { + return nil, err + } + return container, nil +} + +// Register makes a container object usable by the runtime as +func (docker *Docker) Register(container *Container) error { + if container.runtime != nil || docker.Exists(container.Id) { + return fmt.Errorf("Container is already loaded") + } + if err := validateId(container.Id); err != nil { + return err + } + container.runtime = docker + container.networkManager = docker.networkManager // FIXME: infer from docker.runtime + // Setup state lock (formerly in newState() + lock := new(sync.Mutex) + container.State.stateChangeLock = lock + container.State.stateChangeCond = sync.NewCond(lock) + // 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 + } + // Setup logging of stdout and stderr to disk + if err := docker.LogToDisk(container.stdout, container.logPath("stdout")); err != nil { + return err + } + if err := docker.LogToDisk(container.stderr, container.logPath("stderr")); err != nil { + return err + } + // done + docker.containers.PushBack(container) + return nil +} + +func (docker *Docker) LogToDisk(src *writeBroadcaster, dst string) error { + log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) + if err != nil { + return err + } + src.AddWriter(NopWriteCloser(log)) + return nil +} + func (docker *Docker) Destroy(container *Container) error { element := docker.getContainerElement(container.Id) if element == nil { @@ -72,18 +159,18 @@ func (docker *Docker) Destroy(container *Container) error { if err := container.Stop(); err != nil { return err } - if container.Mountpoint.Mounted() { - if err := container.Mountpoint.Umount(); err != nil { - return fmt.Errorf("Unable to umount container %v: %v", container.Id, err) + 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) } } - if err := container.Mountpoint.Deregister(); err != nil { - return fmt.Errorf("Unable to deregiser -- ? mountpoint %v: %v", container.Mountpoint.Root, err) - } - if err := os.RemoveAll(container.Root); err != nil { + // Deregister the container before removing its directory, to avoid race conditions + docker.containers.Remove(element) + if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err) } - docker.containers.Remove(element) return nil } @@ -93,12 +180,13 @@ func (docker *Docker) restore() error { return err } for _, v := range dir { - container, err := loadContainer(docker.Store, path.Join(docker.repository, v.Name()), docker.networkManager) + id := v.Name() + container, err := docker.Load(id) if err != nil { - log.Printf("Failed to load container %v: %v", v.Name(), err) + log.Printf("Failed to load container %v: %v", id, err) continue } - docker.containers.PushBack(container) + log.Printf("Loaded container %v", container.Id) } return nil } @@ -114,7 +202,7 @@ func NewFromDirectory(root string) (*Docker, error) { return nil, err } - store, err := fs.New(path.Join(root, "images")) + graph, err := graph.New(path.Join(root, "graph")) if err != nil { return nil, err } @@ -127,8 +215,8 @@ func NewFromDirectory(root string) (*Docker, error) { root: root, repository: docker_repo, containers: list.New(), - Store: store, networkManager: netManager, + graph: graph, } if err := docker.restore(); err != nil { diff --git a/docker_test.go b/docker_test.go index 735e15baf7..a8329e10dd 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1,7 +1,7 @@ package docker import ( - "github.com/dotcloud/docker/fs" + "github.com/dotcloud/docker/graph" "io" "io/ioutil" "os" @@ -63,7 +63,6 @@ func init() { } // Create the "Server" srv := &Server{ - images: docker.Store, containers: docker, } // Retrieve the Image @@ -93,8 +92,8 @@ func newTestDocker() (*Docker, error) { return docker, nil } -func GetTestImage(docker *Docker) *fs.Image { - imgs, err := docker.Store.Images() +func GetTestImage(docker *Docker) *graph.Image { + imgs, err := docker.graph.All() if err != nil { panic(err) } else if len(imgs) < 1 { @@ -115,10 +114,9 @@ func TestCreate(t *testing.T) { t.Errorf("Expected 0 containers, %v found", len(docker.List())) } container, err := docker.Create( - "test_create", "ls", []string{"-al"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -137,22 +135,22 @@ func TestCreate(t *testing.T) { } // Make sure the container List() returns is the right one - if docker.List()[0].Id != "test_create" { + if docker.List()[0].Id != container.Id { t.Errorf("Unexpected container %v returned by List", docker.List()[0]) } // Make sure we can get the container with Get() - if docker.Get("test_create") == nil { + if docker.Get(container.Id) == nil { t.Errorf("Unable to get newly created container") } // Make sure it is the right container - if docker.Get("test_create") != container { + if docker.Get(container.Id) != container { t.Errorf("Get() returned the wrong container") } // Make sure Exists returns it as existing - if !docker.Exists("test_create") { + if !docker.Exists(container.Id) { t.Errorf("Exists() returned false for a newly created container") } } @@ -164,10 +162,9 @@ func TestDestroy(t *testing.T) { } defer nuke(docker) container, err := docker.Create( - "test_destroy", "ls", []string{"-al"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -189,12 +186,12 @@ func TestDestroy(t *testing.T) { } // Make sure docker.Get() refuses to return the unexisting container - if docker.Get("test_destroy") != nil { + if docker.Get(container.Id) != nil { t.Errorf("Unable to get newly created container") } // Make sure the container root directory does not exist anymore - _, err = os.Stat(container.Root) + _, err = os.Stat(container.root) if err == nil || !os.IsNotExist(err) { t.Errorf("Container root directory still exists after destroy") } @@ -213,10 +210,9 @@ func TestGet(t *testing.T) { } defer nuke(docker) container1, err := docker.Create( - "test1", "ls", []string{"-al"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -225,10 +221,9 @@ func TestGet(t *testing.T) { defer docker.Destroy(container1) container2, err := docker.Create( - "test2", "ls", []string{"-al"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -237,10 +232,9 @@ func TestGet(t *testing.T) { defer docker.Destroy(container2) container3, err := docker.Create( - "test3", "ls", []string{"-al"}, - GetTestImage(docker), + GetTestImage(docker).Id, &Config{}, ) if err != nil { @@ -248,16 +242,16 @@ func TestGet(t *testing.T) { } defer docker.Destroy(container3) - if docker.Get("test1") != container1 { - t.Errorf("Get(test1) returned %v while expecting %v", docker.Get("test1"), container1) + if docker.Get(container1.Id) != container1 { + t.Errorf("Get(test1) returned %v while expecting %v", docker.Get(container1.Id), container1) } - if docker.Get("test2") != container2 { - t.Errorf("Get(test2) returned %v while expecting %v", docker.Get("test2"), container2) + if docker.Get(container2.Id) != container2 { + t.Errorf("Get(test2) returned %v while expecting %v", docker.Get(container2.Id), container2) } - if docker.Get("test3") != container3 { - t.Errorf("Get(test3) returned %v while expecting %v", docker.Get("test3"), container3) + if docker.Get(container3.Id) != container3 { + t.Errorf("Get(test3) returned %v while expecting %v", docker.Get(container3.Id), container3) } } @@ -282,10 +276,9 @@ func TestRestore(t *testing.T) { // Create a container with one instance of docker container1, err := docker1.Create( - "restore_test", "ls", []string{"-al"}, - GetTestImage(docker1), + GetTestImage(docker1).Id, &Config{}, ) if err != nil { @@ -309,7 +302,7 @@ func TestRestore(t *testing.T) { if len(docker2.List()) != 1 { t.Errorf("Expected 1 container, %v found", len(docker2.List())) } - container2 := docker2.Get("restore_test") + container2 := docker2.Get(container1.Id) if container2 == nil { t.Fatal("Unable to Get container") } diff --git a/lxc_template.go b/lxc_template.go index b86beb6bbc..e3beb037f9 100755 --- a/lxc_template.go +++ b/lxc_template.go @@ -22,7 +22,7 @@ lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}} # root filesystem -{{$ROOTFS := .Mountpoint.Root}} +{{$ROOTFS := .RootfsPath}} lxc.rootfs = {{$ROOTFS}} # use a dedicated pts for the container (and limit the number of pseudo terminal diff --git a/state.go b/state.go index e864f304f9..d2c53b83ce 100644 --- a/state.go +++ b/state.go @@ -17,14 +17,6 @@ type State struct { stateChangeCond *sync.Cond } -func newState() *State { - lock := new(sync.Mutex) - return &State{ - stateChangeLock: lock, - stateChangeCond: sync.NewCond(lock), - } -} - // String returns a human-readable description of the state func (s *State) String() string { if s.Running {