Plugged 4 more commands into the real backend: 'diff', 'reset', 'run', 'stop'. 'run' actually runs the process, but doesn't capture outptu properly, and encounters mount issues

This commit is contained in:
Solomon Hykes 2013-01-26 15:56:42 -08:00
parent dbc7fb7575
commit 5d6dd22fb2
5 changed files with 143 additions and 117 deletions

View File

@ -11,6 +11,8 @@ import (
"path"
"syscall"
"time"
"strings"
"bytes"
)
type Container struct {
@ -30,6 +32,9 @@ type Container struct {
cmd *exec.Cmd
stdout *writeBroadcaster
stderr *writeBroadcaster
stdoutLog *bytes.Buffer
stderrLog *bytes.Buffer
}
type Config struct {
@ -51,8 +56,13 @@ func createContainer(id string, root string, command string, args []string, laye
lxcConfigPath: path.Join(root, "config.lxc"),
stdout: newWriteBroadcaster(),
stderr: newWriteBroadcaster(),
stdoutLog: new(bytes.Buffer),
stderrLog: new(bytes.Buffer),
}
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
if err := os.Mkdir(root, 0700); err != nil {
return nil, err
}
@ -73,6 +83,8 @@ func loadContainer(containerPath string) (*Container, error) {
container := &Container{
stdout: newWriteBroadcaster(),
stderr: newWriteBroadcaster(),
stdoutLog: new(bytes.Buffer),
stderrLog: new(bytes.Buffer),
}
if err := json.Unmarshal(data, container); err != nil {
return nil, err
@ -81,6 +93,11 @@ func loadContainer(containerPath string) (*Container, error) {
return container, nil
}
func (container *Container) Cmd() *exec.Cmd {
return container.cmd
}
func (container *Container) loadUserData() (map[string]string, error) {
jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json"))
if err != nil {
@ -200,12 +217,21 @@ func (container *Container) StdoutPipe() (io.ReadCloser, error) {
return newBufReader(reader), nil
}
func (container *Container) StdoutLog() io.Reader {
return strings.NewReader(container.stdoutLog.String())
}
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 {
return strings.NewReader(container.stderrLog.String())
}
func (container *Container) monitor() {
// Wait for the program to exit
container.cmd.Wait()

View File

@ -9,11 +9,9 @@ import (
"errors"
"log"
"io"
"io/ioutil"
"flag"
"fmt"
"strings"
"bytes"
"text/tabwriter"
"sort"
"os"
@ -49,6 +47,29 @@ func (srv *Server) Help() string {
}
func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
if err := cmd.Parse(args); err != nil {
cmd.Usage()
return nil
}
if cmd.NArg() < 1 {
cmd.Usage()
return nil
}
for _, name := range cmd.Args() {
if container := srv.docker.Get(name); container != nil {
if err := container.Stop(); err != nil {
return err
}
fmt.Fprintln(stdout, container.Id)
} else {
return errors.New("No such container: " + container.Id)
}
}
return nil
}
func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
flags := rcli.Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
limit := flags.Int("l", 0, "Only show the N most recent versions of each name")
@ -87,7 +108,7 @@ func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string
/* SOURCE */ container.GetUserData("source"),
/* SIZE */ fmt.Sprintf("%.1fM", float32(fake.RandomContainerSize()) / 1024 / 1024),
/* CHANGES */ fmt.Sprintf("%.1fM", float32(fake.RandomBytesChanged() / 1024 / 1024)),
/* RUNNING */ fmt.Sprintf("%v", fake.ContainerRunning()),
/* RUNNING */ fmt.Sprintf("%v", container.State.Running),
/* COMMAND */ fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
} {
if idx == 0 {
@ -193,11 +214,14 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
if dstName == "" {
dstName = srcName
}
if _, exists := srv.findContainer(srcName); exists {
/*
if src, exists := srv.findContainer(srcName); exists {
baseLayer := src.Filesystem.Layers[0]
//dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
//fmt.Fprintln(stdout, dst.Id)
return nil
}
*/
return errors.New("No such container: " + srcName)
}
@ -220,9 +244,6 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
flags := rcli.Subcmd(stdout,
"diff", "CONTAINER [OPTIONS]",
"Inspect changes on a container's filesystem")
fl_diff := flags.Bool("d", true, "Show changes in diff format")
fl_bytes := flags.Bool("b", false, "Show how many bytes have been changed")
fl_list := flags.Bool("l", false, "Show a list of changed files")
if err := flags.Parse(args); err != nil {
return nil
}
@ -231,44 +252,37 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
}
if container, exists := srv.findContainer(flags.Arg(0)); !exists {
return errors.New("No such container")
} else if *fl_bytes {
fmt.Fprintf(stdout, "%d\n", container.BytesChanged)
} else if *fl_list {
// FAKE
fmt.Fprintf(stdout, strings.Join([]string{
"/etc/postgres/pg.conf",
"/etc/passwd",
"/var/lib/postgres",
"/usr/bin/postgres",
"/usr/bin/psql",
"/var/log/postgres",
"/var/log/postgres/postgres.log",
"/var/log/postgres/postgres.log.0",
"/var/log/postgres/postgres.log.1.gz"}, "\n"))
} else if *fl_diff {
// Achievement unlocked: embed a diff of your code as a string in your code
fmt.Fprintf(stdout, `
diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go
index 2dae694..e43caca 100644
--- a/dockerd/dockerd.go
+++ b/dockerd/dockerd.go
@@ -158,6 +158,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...str
flags := rcli.Subcmd(stdout,
"diff", "CONTAINER [OPTIONS]",
"Inspect changes on a container's filesystem")
+ fl_diff := flags.Bool("d", true, "Show changes in diff format")
fl_bytes := flags.Bool("b", false, "Show how many bytes have been changes")
fl_list := flags.Bool("l", false, "Show a list of changed files")
fl_download := flags.Bool("d", false, "Download the changes as gzipped tar stream")
`)
return nil
} else {
flags.Usage()
return nil
changes, err := container.Filesystem.Changes()
if err != nil {
return err
}
for _, change := range changes {
fmt.Fprintln(stdout, change.String())
}
}
return nil
}
func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
flags := rcli.Subcmd(stdout,
"reset", "CONTAINER [OPTIONS]",
"Reset changes to a container's filesystem")
if err := flags.Parse(args); err != nil {
return nil
}
if flags.NArg() < 1 {
return errors.New("Not enough arguments")
}
for _, name := range flags.Args() {
if container, exists := srv.findContainer(name); exists {
if err := container.Filesystem.Reset(); err != nil {
return errors.New("Reset " + container.Id + ": " + err.Error())
}
}
}
return nil
}
// ByDate wraps an array of layers so they can be sorted by date (most recent first)
@ -349,15 +363,19 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
return err
}
if _, err := io.Copy(stdout, container.StderrLog()); err != nil {
return err
}
return nil
}
return errors.New("No such container: " + flags.Arg(0))
}
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
//fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
if err := flags.Parse(args); err != nil {
return nil
}
@ -367,16 +385,37 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
}
name, cmd := flags.Arg(0), flags.Args()[1:]
if container, exists := srv.findContainer(name); exists {
if container.Running {
return errors.New("Already running: " + name)
}
log.Printf("Running container %#v\n", container)
container.Path = cmd[0]
container.Args = cmd[1:]
if *fl_attach {
return container.Run(cmd[0], cmd[1:], stdin, stdout, *fl_tty)
cmd_stdout, err := container.StdoutPipe()
if err != nil {
return err
}
cmd_stderr, err := container.StderrPipe()
if err != nil {
return err
}
if err := container.Start(); err != nil {
return err
}
sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
err_sending_stdout := <-sending_stdout
err_sending_stderr := <-sending_stderr
if err_sending_stdout != nil {
return err_sending_stdout
}
return err_sending_stderr
} else {
go container.Run(cmd[0], cmd[1:], ioutil.NopCloser(new(bytes.Buffer)), ioutil.Discard, *fl_tty)
fmt.Fprintln(stdout, container.Id)
return nil
if output, err := container.Output(); err != nil {
return err
} else {
fmt.Printf("-->|%s|\n", output)
}
}
return nil
}
return errors.New("No such container: " + name)
}
@ -415,10 +454,7 @@ func New() (*Server, error) {
layers: store,
docker: d,
}
// Update name index
log.Printf("Building name index from %s...\n")
for _, container := range srv.docker.List() {
log.Printf("Indexing %s to %s\n", container.Id, container.GetUserData("name"))
name := container.GetUserData("name")
if _, exists := srv.containersByName[name]; !exists {
srv.containersByName[name] = new(ByDate)

View File

@ -6,11 +6,8 @@ import (
"io"
"archive/tar"
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/future"
"errors"
"os/exec"
"strings"
"fmt"
"github.com/kr/pty"
)
@ -66,77 +63,14 @@ type Container struct {
Size uint
FilesChanged uint
BytesChanged uint
Running bool
stdoutLog *bytes.Buffer
stdinLog *bytes.Buffer
}
func NewContainer(c *docker.Container) *Container {
return &Container{
Container: c,
Name: c.GetUserData("name"),
stdoutLog: new(bytes.Buffer),
stdinLog: new(bytes.Buffer),
}
}
func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdout io.Writer, tty bool) error {
// Not thread-safe
if c.Running {
return errors.New("Already running")
}
c.Path = command
c.Args = args
// Reset logs
c.stdoutLog.Reset()
c.stdinLog.Reset()
cmd := exec.Command(c.Path, c.Args...)
cmd_stdin, cmd_stdout, err := startCommand(cmd, tty)
if err != nil {
return err
}
c.Running = true
// ADD FAKE RANDOM CHANGES
c.FilesChanged = RandomFilesChanged()
c.BytesChanged = RandomBytesChanged()
copy_out := future.Go(func() error {
_, err := io.Copy(io.MultiWriter(stdout, c.stdoutLog), cmd_stdout)
return err
})
future.Go(func() error {
_, err := io.Copy(io.MultiWriter(cmd_stdin, c.stdinLog), stdin)
cmd_stdin.Close()
stdin.Close()
return err
})
wait := future.Go(func() error {
err := cmd.Wait()
c.Running = false
return err
})
if err := <-copy_out; err != nil {
if c.Running {
return err
}
}
if err := <-wait; err != nil {
if status, ok := err.(*exec.ExitError); ok {
fmt.Fprintln(stdout, status)
return nil
}
return err
}
return nil
}
func (c *Container) StdoutLog() io.Reader {
return strings.NewReader(c.stdoutLog.String())
}
func (c *Container) StdinLog() io.Reader {
return strings.NewReader(c.stdinLog.String())
}
func (c *Container) CmdString() string {
return strings.Join(append([]string{c.Path}, c.Args...), " ")
}

View File

@ -80,6 +80,16 @@ type Change struct {
Kind ChangeType
}
func (change *Change) String() string {
var kind string
switch change.Kind {
case ChangeModify: kind = "C"
case ChangeAdd: kind = "A"
case ChangeDelete: kind = "D"
}
return fmt.Sprintf("%s %s", kind, change.Path)
}
func (fs *Filesystem) Changes() ([]Change, error) {
var changes []Change
err := filepath.Walk(fs.RWPath, func(path string, f os.FileInfo, err error) error {
@ -152,6 +162,16 @@ func (fs *Filesystem) Changes() ([]Change, error) {
return changes, nil
}
func (fs *Filesystem) Reset() error {
if err := os.RemoveAll(fs.RWPath); err != nil {
return err
}
if err := fs.createMountPoints(); err != nil {
return err
}
return nil
}
func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {
return &Filesystem{
RootFS: rootfs,

View File

@ -7,6 +7,16 @@ import (
"sync"
)
type nopWriteCloser struct {
io.Writer
}
func (w *nopWriteCloser) Close() error { return nil }
func NopWriteCloser(w io.Writer) io.WriteCloser {
return &nopWriteCloser{w}
}
type bufReader struct {
buf *bytes.Buffer
reader io.Reader