Add gograph database to handle linking

This commit is contained in:
Michael Crosby
2013-09-26 14:20:43 -07:00
parent 99f5fc3116
commit 784e7647e1
11 changed files with 936 additions and 35 deletions
+24
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"github.com/gorilla/mux"
"io"
@@ -992,6 +993,28 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
}
}
func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
runtime := srv.runtime
out := []APILink{}
err := runtime.containerGraph.Walk(func(p string, e *gograph.Entity) error {
container := runtime.Get(e.ID())
if container != nil {
out = append(out, APILink{
Path: p,
ContainerID: container.ID,
Image: runtime.repositories.ImageName(container.Image),
})
}
return nil
})
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, out)
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter()
@@ -1012,6 +1035,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
"/containers/links": getContainersLinks,
},
"POST": {
"/auth": postAuth,
+7 -1
View File
@@ -42,7 +42,6 @@ type APIRmi struct {
}
type APIContainers struct {
Name string
ID string `json:"Id"`
Image string
Command string
@@ -51,6 +50,7 @@ type APIContainers struct {
Ports []APIPort
SizeRw int64
SizeRootFs int64
Names []string
}
func (self *APIContainers) ToLegacy() APIContainersOld {
@@ -121,3 +121,9 @@ type APICopy struct {
Resource string
HostPath string
}
type APILink struct {
Path string
ContainerID string
Image string
}
+33 -3
View File
@@ -98,6 +98,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"kill", "Kill a running container"},
{"login", "Register or Login to the docker registry server"},
{"logs", "Fetch the logs of a container"},
{"ls", "List links for containers"},
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
{"top", "Lookup the running processes of a container"},
{"ps", "List containers"},
@@ -1073,7 +1074,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprint(w, "NAME\tID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
if *size {
fmt.Fprintln(w, "\tSIZE")
} else {
@@ -1082,11 +1083,12 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
for _, out := range outs {
names := strings.Join(out.Names, ",")
if !*quiet {
if *noTrunc {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.Name, out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.Name, utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names)
}
if *size {
if out.SizeRootFs > 0 {
@@ -1112,6 +1114,34 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return nil
}
func (cli *DockerCli) CmdLs(args ...string) error {
cmd := Subcmd("ls", "", "List links for containers")
if err := cmd.Parse(args); err != nil {
return nil
}
body, _, err := cli.call("GET", "/containers/links", nil)
if err != nil {
return err
}
var links []APILink
if err := json.Unmarshal(body, &links); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tID\tIMAGE")
fmt.Fprintf(w, "\n")
for _, link := range links {
fmt.Fprintf(w, "%s\t%s\t%s", link.Path, link.ContainerID, link.Image)
fmt.Fprintf(w, "\n")
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
flComment := cmd.String("m", "", "Commit message")
+335
View File
@@ -0,0 +1,335 @@
package gograph
import (
"fmt"
"path"
"sync"
)
// Entity with a unique id and user defined value
type Entity struct {
id string
Value interface{}
}
// An Edge connects two entities together
type Edge struct {
EntityID string
Name string
ParentID string
}
type Entities map[string]*Entity
type Edges []*Edge
type WalkFunc func(fullPath string, entity *Entity) error
// Graph database for storing entities and their relationships
type Database struct {
entities Entities
edges Edges
mux sync.Mutex
rootID string
}
// Create a new graph database initialized with a root entity
func NewDatabase(rootPath, rootId string) (*Database, error) {
db := &Database{Entities{}, Edges{}, sync.Mutex{}, rootId}
e := &Entity{
id: rootId,
}
db.entities[rootId] = e
edge := &Edge{
EntityID: rootId,
Name: "/",
}
db.edges = append(db.edges, edge)
return db, nil
}
// Set the entity id for a given path
func (db *Database) Set(fullPath, id string) (*Entity, error) {
db.mux.Lock()
defer db.mux.Unlock()
e, exists := db.entities[id]
if !exists {
e = &Entity{
id: id,
}
db.entities[id] = e
}
parentPath, name := splitPath(fullPath)
if err := db.setEdge(parentPath, name, e); err != nil {
return nil, err
}
return e, nil
}
func (db *Database) setEdge(parentPath, name string, e *Entity) error {
parent := db.Get(parentPath)
if parent == nil {
return fmt.Errorf("Parent does not exist for path: %s", parentPath)
}
if parent.id == e.id {
return fmt.Errorf("Cannot set self as child")
}
edge := &Edge{
ParentID: parent.id,
EntityID: e.id,
Name: name,
}
if db.edges.Exists(parent.id, name) {
return fmt.Errorf("Relationship already exists for %s/%s", parentPath, name)
}
db.edges = append(db.edges, edge)
return nil
}
// Return the root "/" entity for the database
func (db *Database) RootEntity() *Entity {
return db.entities[db.rootID]
}
// Return the entity for a given path
func (db *Database) Get(name string) *Entity {
e := db.RootEntity()
// We always know the root name so return it if
// it is requested
if name == "/" {
return e
}
parts := split(name)
for i := 1; i < len(parts); i++ {
p := parts[i]
next := db.child(e, p)
if next == nil {
return nil
}
e = next
}
return e
}
// List all entities by from the name
// The key will be the full path of the entity
func (db *Database) List(name string, depth int) Entities {
out := Entities{}
for c := range db.children(name, depth) {
out[c.FullPath] = c.Entity
}
return out
}
func (db *Database) Walk(walkFunc WalkFunc) error {
parent := db.RootEntity()
if err := walkFunc("/", parent); err != nil {
return err
}
for c := range db.children("/", -1) {
if err := walkFunc(c.FullPath, c.Entity); err != nil {
return err
}
}
return nil
}
// Return the refrence count for a specified id
func (db *Database) Refs(id string) int {
return len(db.RefPaths(id))
}
// Return all the id's path references
func (db *Database) RefPaths(id string) Edges {
refs := db.edges.Search(func(e *Edge) bool {
return e.EntityID == id
})
return refs
}
// Delete the reference to an entity at a given path
func (db *Database) Delete(name string) error {
if name == "/" {
return fmt.Errorf("Cannot delete root entity")
}
db.mux.Lock()
defer db.mux.Unlock()
parentPath, n := splitPath(name)
parent := db.Get(parentPath)
if parent == nil {
return fmt.Errorf("Cannot find parent for %s", parentPath)
}
edge, i := db.edges.Get(parent.id, n)
if edge == nil {
return fmt.Errorf("Edge does not exist at %s", name)
}
db.deleteEdgeAtIndex(i)
return nil
}
func (db *Database) deleteEdgeAtIndex(i int) {
db.edges[len(db.edges)-1], db.edges[i], db.edges = nil, db.edges[len(db.edges)-1], db.edges[:len(db.edges)-1]
}
// Remove the entity with the specified id
// Walk the graph to make sure all references to the entity
// are removed and return the number of references removed
func (db *Database) Purge(id string) (int, error) {
db.mux.Lock()
defer db.mux.Unlock()
getIndex := func(e *Edge) int {
for i, edge := range db.edges {
if edge.EntityID == e.EntityID &&
edge.Name == e.Name &&
edge.ParentID == e.ParentID {
return i
}
}
return -1
}
refsToDelete := db.RefPaths(id)
for i, e := range refsToDelete {
index := getIndex(e)
if index == -1 {
return i + 1, fmt.Errorf("Cannot find index for %s %s", e.ParentID, e.Name)
}
db.deleteEdgeAtIndex(index)
}
return len(refsToDelete), nil
}
// Rename an edge for a given path
func (db *Database) Rename(currentName, newName string) error {
parentPath, name := splitPath(currentName)
newParentPath, newEdgeName := splitPath(newName)
if parentPath != newParentPath {
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
}
db.mux.Lock()
defer db.mux.Unlock()
parent := db.Get(parentPath)
if parent == nil {
return fmt.Errorf("Cannot locate parent for %s", currentName)
}
edge, _ := db.edges.Get(parent.id, name)
if edge == nil {
return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
}
edge.Name = newEdgeName
return nil
}
type WalkMeta struct {
Parent *Entity
Entity *Entity
FullPath string
Edge *Edge
}
func (db *Database) children(name string, depth int) <-chan WalkMeta {
out := make(chan WalkMeta)
e := db.Get(name)
if e == nil {
close(out)
return out
}
go func() {
for _, edge := range db.edges {
if edge.ParentID == e.id {
child := db.entities[edge.EntityID]
meta := WalkMeta{
Parent: e,
Entity: child,
FullPath: path.Join(name, edge.Name),
Edge: edge,
}
out <- meta
if depth == 0 {
continue
}
nDepth := depth
if depth != -1 {
nDepth -= 1
}
sc := db.children(meta.FullPath, nDepth)
for c := range sc {
out <- c
}
}
}
close(out)
}()
return out
}
// Return the entity based on the parent path and name
func (db *Database) child(parent *Entity, name string) *Entity {
edge, _ := db.edges.Get(parent.id, name)
if edge == nil {
return nil
}
return db.entities[edge.EntityID]
}
// Return the id used to reference this entity
func (e *Entity) ID() string {
return e.id
}
// Return the paths sorted by depth
func (e Entities) Paths() []string {
out := make([]string, len(e))
var i int
for k := range e {
out[i] = k
i++
}
sortByDepth(out)
return out
}
// Checks if an edge with the specified parent id and name exist in the slice
func (e Edges) Exists(parendId, name string) bool {
edge, _ := e.Get(parendId, name)
return edge != nil
}
// Returns the edge and index in the slice with the specified parent id and name
func (e Edges) Get(parentId, name string) (*Edge, int) {
for i, edge := range e {
if edge.ParentID == parentId && edge.Name == name {
return edge, i
}
}
return nil, -1
}
func (e Edges) Search(predicate func(edge *Edge) bool) Edges {
out := Edges{}
for _, edge := range e {
if predicate(edge) {
out = append(out, edge)
}
}
return out
}
+413
View File
@@ -0,0 +1,413 @@
package gograph
import (
"os"
"strconv"
"testing"
)
func newTestDb(t *testing.T) *Database {
db, err := NewDatabase(os.TempDir(), "0")
if err != nil {
t.Fatal(err)
}
return db
}
func TestNewDatabase(t *testing.T) {
db := newTestDb(t)
if db == nil {
t.Fatal("Datbase should not be nil")
}
}
func TestCreateRootEnity(t *testing.T) {
db := newTestDb(t)
root := db.RootEntity()
if root == nil {
t.Fatal("Root entity should not be nil")
}
}
func TestGetRootEntity(t *testing.T) {
db := newTestDb(t)
e := db.Get("/")
if e == nil {
t.Fatal("Entity should not be nil")
}
if e.ID() != "0" {
t.Fatalf("Enity id should be 0, got %s", e.ID())
}
}
func TestSetEntityWithDifferentName(t *testing.T) {
db := newTestDb(t)
db.Set("/test", "1")
if _, err := db.Set("/other", "1"); err != nil {
t.Fatal(err)
}
}
func TestCreateChild(t *testing.T) {
db := newTestDb(t)
child, err := db.Set("/db", "1")
if err != nil {
t.Fatal(err)
}
if child == nil {
t.Fatal("Child should not be nil")
}
if child.ID() != "1" {
t.Fail()
}
}
func TestListAllRootChildren(t *testing.T) {
db := newTestDb(t)
for i := 1; i < 6; i++ {
a := strconv.Itoa(i)
if _, err := db.Set("/"+a, a); err != nil {
t.Fatal(err)
}
}
entries := db.List("/", -1)
if len(entries) != 5 {
t.Fatalf("Expect 5 entries for / got %d", len(entries))
}
}
func TestListAllSubChildren(t *testing.T) {
db := newTestDb(t)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
entries := db.List("/webapp", 1)
if len(entries) != 3 {
t.Fatalf("Expect 3 entries for / got %d", len(entries))
}
entries = db.List("/webapp", 0)
if len(entries) != 2 {
t.Fatalf("Expect 2 entries for / got %d", len(entries))
}
}
func TestAddSelfAsChild(t *testing.T) {
db := newTestDb(t)
child, err := db.Set("/test", "1")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/test/other", child.ID()); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestAddChildToNonExistantRoot(t *testing.T) {
db := newTestDb(t)
if _, err := db.Set("/myapp", "1"); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestWalkAll(t *testing.T) {
db := newTestDb(t)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/db/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
if err := db.Walk(func(p string, e *Entity) error {
t.Logf("Path: %s Entity: %s", p, e.ID())
return nil
}); err != nil {
t.Fatal(err)
}
}
func TestGetEntityByPath(t *testing.T) {
db := newTestDb(t)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
entity := db.Get("/webapp/db/logs")
if entity == nil {
t.Fatal("Entity should not be nil")
}
if entity.ID() != "4" {
t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
}
}
func TestEnitiesPaths(t *testing.T) {
db := newTestDb(t)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
out := db.List("/", -1)
for _, p := range out.Paths() {
t.Log(p)
}
}
func TestDeleteRootEntity(t *testing.T) {
db := newTestDb(t)
if err := db.Delete("/"); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestDeleteEntity(t *testing.T) {
db := newTestDb(t)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
if err := db.Delete("/webapp/sentry"); err != nil {
t.Fatal(err)
}
entity := db.Get("/webapp/sentry")
if entity != nil {
t.Fatal("Entity /webapp/sentry should be nil")
}
}
func TestCountRefs(t *testing.T) {
db := newTestDb(t)
db.Set("/webapp", "1")
if db.Refs("1") != 1 {
t.Fatal("Expect reference count to be 1")
}
db.Set("/db", "2")
db.Set("/webapp/db", "2")
if db.Refs("2") != 2 {
t.Fatal("Expect reference count to be 2")
}
}
func TestPurgeId(t *testing.T) {
db := newTestDb(t)
db.Set("/webapp", "1")
if db.Refs("1") != 1 {
t.Fatal("Expect reference count to be 1")
}
db.Set("/db", "2")
db.Set("/webapp/db", "2")
count, err := db.Purge("2")
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Fatal("Expected 2 references to be removed")
}
}
func TestRename(t *testing.T) {
db := newTestDb(t)
db.Set("/webapp", "1")
if db.Refs("1") != 1 {
t.Fatal("Expect reference count to be 1")
}
db.Set("/db", "2")
db.Set("/webapp/db", "2")
if db.Get("/webapp/db") == nil {
t.Fatal("Cannot find entity at path /webapp/db")
}
if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
t.Fatal(err)
}
if db.Get("/webapp/db") != nil {
t.Fatal("Entity should not exist at /webapp/db")
}
if db.Get("/webapp/newdb") == nil {
t.Fatal("Cannot find entity at path /webapp/newdb")
}
}
func TestCreateMultipleNames(t *testing.T) {
db := newTestDb(t)
db.Set("/db", "1")
if _, err := db.Set("/myapp", "1"); err != nil {
t.Fatal(err)
}
db.Walk(func(p string, e *Entity) error {
t.Logf("%s\n", p)
return nil
})
}
+27
View File
@@ -0,0 +1,27 @@
package gograph
import "sort"
type pathSorter struct {
paths []string
by func(i, j string) bool
}
func sortByDepth(paths []string) {
s := &pathSorter{paths, func(i, j string) bool {
return pathDepth(i) > pathDepth(j)
}}
sort.Sort(s)
}
func (s *pathSorter) Len() int {
return len(s.paths)
}
func (s *pathSorter) Swap(i, j int) {
s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
}
func (s *pathSorter) Less(i, j int) bool {
return s.by(s.paths[i], s.paths[j])
}
+29
View File
@@ -0,0 +1,29 @@
package gograph
import (
"testing"
)
func TestSort(t *testing.T) {
paths := []string{
"/",
"/myreallylongname",
"/app/db",
}
sortByDepth(paths)
if len(paths) != 3 {
t.Fatalf("Expected 3 parts got %d", len(paths))
}
if paths[0] != "/app/db" {
t.Fatalf("Expected /app/db got %s", paths[0])
}
if paths[1] != "/myreallylongname" {
t.Fatalf("Expected /myreallylongname got %s", paths[1])
}
if paths[2] != "/" {
t.Fatalf("Expected / got %s", paths[2])
}
}
+29
View File
@@ -0,0 +1,29 @@
package gograph
import (
"path"
"strings"
)
// Split p on /
func split(p string) []string {
return strings.Split(p, "/")
}
// Returns the depth or number of / in a given path
func pathDepth(p string) int {
parts := split(p)
if len(parts) == 2 && parts[1] == "" {
return 1
}
return len(parts)
}
func splitPath(p string) (parent, name string) {
parent, name = path.Split(p)
l := len(parent)
if parent[l-1] == '/' {
parent = parent[:l-1]
}
return
}
-2
View File
@@ -7,8 +7,6 @@ import (
"strings"
)
// A Link represents a connection between two containers
// for a specific port on a specific bridge interface
type Link struct {
FromID string
ToID string
+13
View File
@@ -3,6 +3,7 @@ package docker
import (
"container/list"
"fmt"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -36,6 +37,7 @@ type Runtime struct {
srv *Server
config *DaemonConfig
links *LinkRepository
containerGraph *gograph.Database
}
var sysInitPath string
@@ -321,6 +323,12 @@ func (runtime *Runtime) Create(config *Config) (*Container, []string, error) {
// Generate id
id := GenerateID()
// Set the default enitity in the graph
if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil {
return nil, nil, err
}
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
@@ -503,6 +511,10 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
if err != nil {
return nil, err
}
graph, err := gograph.NewDatabase("", "engine")
if err != nil {
return nil, err
}
runtime := &Runtime{
repository: runtimeRepo,
@@ -515,6 +527,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
volumes: volumes,
config: config,
links: links,
containerGraph: graph,
}
if err := runtime.restore(); err != nil {
+26 -29
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
"io"
@@ -357,7 +358,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers {
var foundBefore bool
var displayed int
retContainers := make(map[string]APIContainers)
out := []APIContainers{}
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
@@ -379,39 +380,35 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
break
}
displayed++
c := APIContainers{
ID: container.ID,
}
c.Name = utils.TruncateID(container.ID)
c.Image = srv.runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingAPI()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
retContainers[utils.TruncateID(c.ID)] = c
}
out := make([]APIContainers, len(retContainers))
var i int
for _, v := range retContainers {
out[i] = v
i++
}
// Add links to result
for _, link := range srv.runtime.links.GetAll() {
cp := retContainers[link.ToID]
c := cp
c.Name = fmt.Sprintf("%s/%s", link.FromID, link.Alias)
c := createAPIContainer(container, size, srv.runtime)
out = append(out, c)
}
return out
}
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
c := APIContainers{
ID: container.ID,
}
names := []string{}
runtime.containerGraph.Walk(func(p string, e *gograph.Entity) error {
if e.ID() == container.ID {
names = append(names, p)
}
return nil
})
c.Names = names
c.Image = runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingAPI()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
return c
}
func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) {
container := srv.runtime.Get(name)
if container == nil {