Merged upstream changes in fs branch

This commit is contained in:
shin- 2013-03-11 05:50:09 -07:00
commit 2441edf1a3
12 changed files with 129 additions and 493 deletions

View File

@ -2,6 +2,7 @@ package docker
import ( import (
"./fs" "./fs"
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/kr/pty" "github.com/kr/pty"
@ -118,17 +119,25 @@ func createContainer(id string, root string, command string, args []string, imag
return container, nil return container, nil
} }
func loadContainer(containerPath string, netManager *NetworkManager) (*Container, error) { func loadContainer(store *fs.Store, containerPath string, netManager *NetworkManager) (*Container, error) {
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json")) data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
mountpoint, err := store.FetchMountpoint(
path.Join(containerPath, "rootfs"),
path.Join(containerPath, "rw"),
)
if err != nil {
return nil, err
}
container := &Container{ container := &Container{
stdout: newWriteBroadcaster(), stdout: newWriteBroadcaster(),
stderr: newWriteBroadcaster(), stderr: newWriteBroadcaster(),
lxcConfigPath: path.Join(containerPath, "config.lxc"), lxcConfigPath: path.Join(containerPath, "config.lxc"),
networkManager: netManager, networkManager: netManager,
NetworkSettings: &NetworkSettings{}, NetworkSettings: &NetworkSettings{},
Mountpoint: mountpoint,
} }
// Load container settings // Load container settings
if err := json.Unmarshal(data, container); err != nil { if err := json.Unmarshal(data, container); err != nil {

View File

@ -94,12 +94,11 @@ func (docker *Docker) restore() error {
return err return err
} }
for _, v := range dir { for _, v := range dir {
container, err := loadContainer(path.Join(docker.repository, v.Name()), docker.networkManager) container, err := loadContainer(docker.Store, path.Join(docker.repository, v.Name()), docker.networkManager)
if err != nil { if err != nil {
log.Printf("Failed to load container %v: %v", v.Name(), err) log.Printf("Failed to load container %v: %v", v.Name(), err)
continue continue
} }
container.Mountpoint.Store = docker.Store
docker.containers.PushBack(container) docker.containers.PushBack(container)
} }
return nil return nil

View File

@ -1,9 +1,9 @@
package main package main
import ( import (
"flag"
".." ".."
"../server" "../server"
"flag"
"log" "log"
) )

View File

@ -13,7 +13,7 @@ func FakeTar() (io.Reader, error) {
content := []byte("Hello world!\n") content := []byte("Hello world!\n")
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
tw := tar.NewWriter(buf) tw := tar.NewWriter(buf)
for _, name := range []string{"hello", "etc/postgres/postgres.conf", "etc/passwd", "var/log/postgres/postgres.conf"} { for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
hdr := new(tar.Header) hdr := new(tar.Header)
hdr.Size = int64(len(content)) hdr.Size = int64(len(content))
hdr.Name = name hdr.Name = name

View File

@ -1,129 +1,129 @@
package fs package fs
import ( import (
"fmt" "fmt"
"path/filepath" "os"
"os" "path/filepath"
"strings" "strings"
) )
type ChangeType int type ChangeType int
const ( const (
ChangeModify = iota ChangeModify = iota
ChangeAdd ChangeAdd
ChangeDelete ChangeDelete
) )
type Change struct { type Change struct {
Path string Path string
Kind ChangeType Kind ChangeType
} }
func (change *Change) String() string { func (change *Change) String() string {
var kind string var kind string
switch change.Kind { switch change.Kind {
case ChangeModify: case ChangeModify:
kind = "C" kind = "C"
case ChangeAdd: case ChangeAdd:
kind = "A" kind = "A"
case ChangeDelete: case ChangeDelete:
kind = "D" kind = "D"
} }
return fmt.Sprintf("%s %s", kind, change.Path) return fmt.Sprintf("%s %s", kind, change.Path)
} }
func (store *Store) Changes(mp *Mountpoint) ([]Change, error) { func (store *Store) Changes(mp *Mountpoint) ([]Change, error) {
var changes []Change var changes []Change
image, err := store.Get(mp.Image) image, err := store.Get(mp.Image)
if err != nil { if err != nil {
return nil, err return nil, err
} }
layers, err := image.layers() layers, err := image.layers()
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = filepath.Walk(mp.Rw, func(path string, f os.FileInfo, err error) error { err = filepath.Walk(mp.Rw, func(path string, f os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
// Rebase path // Rebase path
path, err = filepath.Rel(mp.Rw, path) path, err = filepath.Rel(mp.Rw, path)
if err != nil { if err != nil {
return err return err
} }
path = filepath.Join("/", path) path = filepath.Join("/", path)
// Skip root // Skip root
if path == "/" { if path == "/" {
return nil return nil
} }
// Skip AUFS metadata // Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched { if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
return err return err
} }
change := Change{ change := Change{
Path: path, Path: path,
} }
// Find out what kind of modification happened // Find out what kind of modification happened
file := filepath.Base(path) file := filepath.Base(path)
// If there is a whiteout, then the file was removed // If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") { if strings.HasPrefix(file, ".wh.") {
originalFile := strings.TrimLeft(file, ".wh.") originalFile := strings.TrimLeft(file, ".wh.")
change.Path = filepath.Join(filepath.Dir(path), originalFile) change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete change.Kind = ChangeDelete
} else { } else {
// Otherwise, the file was added // Otherwise, the file was added
change.Kind = ChangeAdd change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification // ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range layers { for _, layer := range layers {
stat, err := os.Stat(filepath.Join(layer, path)) stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
} }
if err == nil { if err == nil {
// The file existed in the top layer, so that's a modification // The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified. // However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() { if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change // Both directories are the same, don't record the change
return nil return nil
} }
} }
change.Kind = ChangeModify change.Kind = ChangeModify
break break
} }
} }
} }
// Record change // Record change
changes = append(changes, change) changes = append(changes, change)
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
return changes, nil return changes, nil
} }
// Reset removes all changes to the filesystem, reverting it to its initial state. // Reset removes all changes to the filesystem, reverting it to its initial state.
func (mp *Mountpoint) Reset() error { func (mp *Mountpoint) Reset() error {
if err := os.RemoveAll(mp.Rw); err != nil { if err := os.RemoveAll(mp.Rw); err != nil {
return err return err
} }
// We removed the RW directory itself along with its content: let's re-create an empty one. // We removed the RW directory itself along with its content: let's re-create an empty one.
if err := mp.createFolders(); err != nil { if err := mp.createFolders(); err != nil {
return err return err
} }
return nil return nil
} }
// Open opens the named file for reading. // Open opens the named file for reading.

View File

@ -2,7 +2,6 @@ package fs
import "syscall" import "syscall"
func mount(source string, target string, fstype string, flags uintptr, data string) (err error) { func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
return syscall.Mount(source, target, fstype, flags, data) return syscall.Mount(source, target, fstype, flags, data)
} }

View File

@ -4,9 +4,9 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"github.com/coopernurse/gorp"
"github.com/dotcloud/docker/future" "github.com/dotcloud/docker/future"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/shykes/gorp" //Forked to implement CreateTablesOpts
"io" "io"
"os" "os"
"path" "path"
@ -29,8 +29,6 @@ func New(root string) (*Store, error) {
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) { if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
return nil, err return nil, err
} else if os.IsExist(err) {
isNewStore = false
} }
db, err := sql.Open("sqlite3", path.Join(root, "db")) db, err := sql.Open("sqlite3", path.Join(root, "db"))
if err != nil { if err != nil {
@ -42,7 +40,7 @@ func New(root string) (*Store, error) {
orm.AddTableWithName(Mountpoint{}, "mountpoints").SetKeys(false, "Root") orm.AddTableWithName(Mountpoint{}, "mountpoints").SetKeys(false, "Root")
orm.AddTableWithName(Tag{}, "tags").SetKeys(false, "TagName") orm.AddTableWithName(Tag{}, "tags").SetKeys(false, "TagName")
if isNewStore { if isNewStore {
if err := orm.CreateTables(); err != nil { if err := orm.CreateTablesOpts(true); err != nil {
return nil, err return nil, err
} }
} }
@ -227,7 +225,7 @@ func (image *Image) Mountpoints() ([]*Mountpoint, error) {
func (image *Image) Mount(root, rw string) (*Mountpoint, error) { func (image *Image) Mount(root, rw string) (*Mountpoint, error) {
var mountpoint *Mountpoint var mountpoint *Mountpoint
if mp, err := image.fetchMountpoint(root, rw); err != nil { if mp, err := image.store.FetchMountpoint(root, rw); err != nil {
return nil, err return nil, err
} else if mp == nil { } else if mp == nil {
mountpoint, err = image.Mountpoint(root, rw) mountpoint, err = image.Mountpoint(root, rw)
@ -345,8 +343,8 @@ func (mp *Mountpoint) Deregister() error {
return err return err
} }
func (image *Image) fetchMountpoint(root, rw string) (*Mountpoint, error) { func (store *Store) FetchMountpoint(root, rw string) (*Mountpoint, error) {
res, err := image.store.orm.Select(Mountpoint{}, "select * from mountpoints where Image=? and Root=? and Rw=?", image.Id, root, rw) res, err := store.orm.Select(Mountpoint{}, "select * from mountpoints where Root=? and Rw=?", root, rw)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(res) < 1 || res[0] == nil { } else if len(res) < 1 || res[0] == nil {
@ -354,7 +352,7 @@ func (image *Image) fetchMountpoint(root, rw string) (*Mountpoint, error) {
} }
mp := res[0].(*Mountpoint) mp := res[0].(*Mountpoint)
mp.Store = image.store mp.Store = store
return mp, nil return mp, nil
} }

View File

@ -1,9 +1,9 @@
package fs package fs
import ( import (
"../fake"
"errors" "errors"
"fmt" "fmt"
"../fake"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"

View File

@ -1,362 +0,0 @@
package image
import (
"encoding/json"
"errors"
"github.com/dotcloud/docker/future"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
type Store struct {
*Index
Root string
Layers *LayerStore
}
func New(root string) (*Store, error) {
abspath, err := filepath.Abs(root)
if err != nil {
return nil, err
}
if err := os.MkdirAll(abspath, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
layers, err := NewLayerStore(path.Join(root, "layers"))
if err != nil {
return nil, err
}
if err := layers.Init(); err != nil {
return nil, err
}
return &Store{
Root: abspath,
Index: NewIndex(path.Join(root, "index.json")),
Layers: layers,
}, nil
}
// Import creates a new image from the contents of `archive` and registers it in the store as `name`.
// If `parent` is not nil, it will registered as the parent of the new image.
func (store *Store) Import(name string, archive io.Reader, parent *Image) (*Image, error) {
layer, err := store.Layers.AddLayer(archive)
if err != nil {
return nil, err
}
layers := []string{layer}
if parent != nil {
layers = append(layers, parent.Layers...)
}
var parentId string
if parent != nil {
parentId = parent.Id
}
return store.Create(name, parentId, layers...)
}
func (store *Store) Create(name string, source string, layers ...string) (*Image, error) {
image, err := NewImage(name, layers, source)
if err != nil {
return nil, err
}
if err := store.Index.Add(name, image); err != nil {
return nil, err
}
return image, nil
}
// Index
type Index struct {
Path string
ByName map[string]*History
ById map[string]*Image
}
func NewIndex(path string) *Index {
return &Index{
Path: path,
ByName: make(map[string]*History),
ById: make(map[string]*Image),
}
}
func (index *Index) Exists(id string) bool {
_, exists := index.ById[id]
return exists
}
func (index *Index) Find(idOrName string) *Image {
// Load
if err := index.load(); err != nil {
return nil
}
// Lookup by ID
if image, exists := index.ById[idOrName]; exists {
return image
}
// Lookup by name
if history, exists := index.ByName[idOrName]; exists && history.Len() > 0 {
return (*history)[0]
}
return nil
}
func (index *Index) Add(name string, image *Image) error {
// Load
if err := index.load(); err != nil {
return err
}
if _, exists := index.ByName[name]; !exists {
index.ByName[name] = new(History)
} else {
// If this image is already the latest version, don't add it.
if (*index.ByName[name])[0].Id == image.Id {
return nil
}
}
index.ByName[name].Add(image)
index.ById[image.Id] = image
// Save
if err := index.save(); err != nil {
return err
}
return nil
}
func (index *Index) Copy(srcNameOrId, dstName string) (*Image, error) {
if srcNameOrId == "" || dstName == "" {
return nil, errors.New("Illegal image name")
}
// Load
if err := index.load(); err != nil {
return nil, err
}
src := index.Find(srcNameOrId)
if src == nil {
return nil, errors.New("No such image: " + srcNameOrId)
}
dst, err := NewImage(dstName, src.Layers, src.Id)
if err != nil {
return nil, err
}
if err := index.Add(dstName, dst); err != nil {
return nil, err
}
// Save
if err := index.save(); err != nil {
return nil, err
}
return dst, nil
}
func (index *Index) Rename(oldName, newName string) error {
// Load
if err := index.load(); err != nil {
return err
}
if _, exists := index.ByName[oldName]; !exists {
return errors.New("Can't rename " + oldName + ": no such image.")
}
if _, exists := index.ByName[newName]; exists {
return errors.New("Can't rename to " + newName + ": name is already in use.")
}
index.ByName[newName] = index.ByName[oldName]
delete(index.ByName, oldName)
// Change the ID of all images, since they include the name
for _, image := range *index.ByName[newName] {
if id, err := generateImageId(newName, image.Layers); err != nil {
return err
} else {
oldId := image.Id
image.Id = id
index.ById[id] = image
delete(index.ById, oldId)
}
}
// Save
if err := index.save(); err != nil {
return err
}
return nil
}
// Delete deletes all images with the name `name`
func (index *Index) Delete(name string) error {
// Load
if err := index.load(); err != nil {
return err
}
if _, exists := index.ByName[name]; !exists {
return errors.New("No such image: " + name)
}
// Remove from index lookup
for _, image := range *index.ByName[name] {
delete(index.ById, image.Id)
}
// Remove from name lookup
delete(index.ByName, name)
// Save
if err := index.save(); err != nil {
return err
}
return nil
}
// DeleteMatch deletes all images whose name matches `pattern`
func (index *Index) DeleteMatch(pattern string) error {
// Load
if err := index.load(); err != nil {
return err
}
for name, history := range index.ByName {
if match, err := regexp.MatchString(pattern, name); err != nil {
return err
} else if match {
// Remove from index lookup
for _, image := range *history {
delete(index.ById, image.Id)
}
// Remove from name lookup
delete(index.ByName, name)
}
}
// Save
if err := index.save(); err != nil {
return err
}
return nil
}
func (index *Index) Names() []string {
if err := index.load(); err != nil {
return []string{}
}
var names []string
for name := range index.ByName {
names = append(names, name)
}
sort.Strings(names)
return names
}
func (index *Index) load() error {
jsonData, err := ioutil.ReadFile(index.Path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
path := index.Path
if err := json.Unmarshal(jsonData, index); err != nil {
return err
}
index.Path = path
return nil
}
func (index *Index) save() error {
jsonData, err := json.Marshal(index)
if err != nil {
return err
}
if err := ioutil.WriteFile(index.Path, jsonData, 0600); err != nil {
return err
}
return nil
}
// History wraps an array of images so they can be sorted by date (most recent first)
type History []*Image
func (history *History) Len() int {
return len(*history)
}
func (history *History) Less(i, j int) bool {
images := *history
return images[j].Created.Before(images[i].Created)
}
func (history *History) Swap(i, j int) {
images := *history
tmp := images[i]
images[i] = images[j]
images[j] = tmp
}
func (history *History) Add(image *Image) {
*history = append(*history, image)
sort.Sort(history)
}
func (history *History) Del(id string) {
for idx, image := range *history {
if image.Id == id {
*history = append((*history)[:idx], (*history)[idx+1:]...)
}
}
}
type Image struct {
Id string // Globally unique identifier
Layers []string // Absolute paths
Created time.Time
Parent string
}
func (image *Image) IdParts() (string, string) {
if len(image.Id) < 8 {
return "", image.Id
}
hash := image.Id[len(image.Id)-8 : len(image.Id)]
name := image.Id[:len(image.Id)-9]
return name, hash
}
func (image *Image) IdIsFinal() bool {
return len(image.Layers) == 1
}
func generateImageId(name string, layers []string) (string, error) {
if len(layers) == 0 {
return "", errors.New("No layers provided.")
}
var hash string
if len(layers) == 1 {
hash = path.Base(layers[0])
} else {
var ids string
for _, layer := range layers {
ids += path.Base(layer)
}
if h, err := future.ComputeId(strings.NewReader(ids)); err != nil {
return "", err
} else {
hash = h
}
}
return name + ":" + hash, nil
}
func NewImage(name string, layers []string, parent string) (*Image, error) {
id, err := generateImageId(name, layers)
if err != nil {
return nil, err
}
return &Image{
Id: id,
Layers: layers,
Created: time.Now(),
Parent: parent,
}, nil
}

View File

@ -1,13 +1,12 @@
package rcli package rcli
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"fmt"
) )
// Use this key to encode an RPC call into an URL, // Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon // eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q" const ARG_URL_KEY = "q"
@ -16,18 +15,16 @@ func URLToCall(u *url.URL) (method string, args []string) {
return path.Base(u.Path), u.Query()[ARG_URL_KEY] return path.Base(u.Path), u.Query()[ARG_URL_KEY]
} }
func ListenAndServeHTTP(addr string, service Service) error { func ListenAndServeHTTP(addr string, service Service) error {
return http.ListenAndServe(addr, http.HandlerFunc( return http.ListenAndServe(addr, http.HandlerFunc(
func (w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
cmd, args := URLToCall(r.URL) cmd, args := URLToCall(r.URL)
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil { if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
fmt.Fprintf(w, "Error: " + err.Error() + "\n") fmt.Fprintf(w, "Error: "+err.Error()+"\n")
} }
})) }))
} }
type AutoFlush struct { type AutoFlush struct {
http.ResponseWriter http.ResponseWriter
} }

View File

@ -1,13 +1,13 @@
package rcli package rcli
import ( import (
"bufio"
"encoding/json"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"log" "log"
"fmt" "net"
"encoding/json"
"bufio"
) )
// Connect to a remote endpoint using protocol `proto` and address `addr`, // Connect to a remote endpoint using protocol `proto` and address `addr`,
@ -44,7 +44,7 @@ func ListenAndServe(proto, addr string, service Service) error {
go func() { go func() {
if err := Serve(conn, service); err != nil { if err := Serve(conn, service); err != nil {
log.Printf("Error: " + err.Error() + "\n") log.Printf("Error: " + err.Error() + "\n")
fmt.Fprintf(conn, "Error: " + err.Error() + "\n") fmt.Fprintf(conn, "Error: "+err.Error()+"\n")
} }
conn.Close() conn.Close()
}() }()
@ -53,7 +53,6 @@ func ListenAndServe(proto, addr string, service Service) error {
return nil return nil
} }
// Parse an rcli call on a new connection, and pass it to `service` if it // Parse an rcli call on a new connection, and pass it to `service` if it
// is valid. // is valid.
func Serve(conn io.ReadWriter, service Service) error { func Serve(conn io.ReadWriter, service Service) error {
@ -68,4 +67,3 @@ func Serve(conn io.ReadWriter, service Service) error {
} }
return nil return nil
} }

View File

@ -8,13 +8,13 @@ package rcli
// are the usual suspects. // are the usual suspects.
import ( import (
"errors"
"flag"
"fmt" "fmt"
"io" "io"
"reflect"
"flag"
"log" "log"
"reflect"
"strings" "strings"
"errors"
) )
type Service interface { type Service interface {
@ -25,7 +25,6 @@ type Service interface {
type Cmd func(io.ReadCloser, io.Writer, ...string) error type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) == 0 { if len(args) == 0 {
args = []string{"help"} args = []string{"help"}
@ -63,7 +62,7 @@ func getMethod(service Service, name string) Cmd {
return nil return nil
} }
} }
methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:]) methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
method, exists := reflect.TypeOf(service).MethodByName(methodName) method, exists := reflect.TypeOf(service).MethodByName(methodName)
if !exists { if !exists {
return nil return nil
@ -91,4 +90,3 @@ func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet
} }
return flags return flags
} }