container/view.go
4f0d95fa
 package container // import "github.com/docker/docker/container"
054728b1
 
edad5270
 import (
1128fc1a
 	"errors"
edad5270
 	"fmt"
 	"strings"
 	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/go-connections/nat"
 	"github.com/hashicorp/go-memdb"
1009e6a4
 	"github.com/sirupsen/logrus"
edad5270
 )
054728b1
 
 const (
b4a63139
 	memdbContainersTable  = "containers"
 	memdbNamesTable       = "names"
1128fc1a
 	memdbIDIndex          = "id"
 	memdbContainerIDIndex = "containerid"
 )
 
 var (
 	// ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved
 	ErrNameReserved = errors.New("name is reserved")
 	// ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved
 	ErrNameNotReserved = errors.New("name is not reserved")
054728b1
 )
 
edad5270
 // Snapshot is a read only view for Containers. It holds all information necessary to serve container queries in a
 // versioned ACID in-memory store.
 type Snapshot struct {
 	types.Container
 
 	// additional info queries need to filter on
 	// preserve nanosec resolution for queries
 	CreatedAt    time.Time
 	StartedAt    time.Time
 	Name         string
 	Pid          int
 	ExitCode     int
 	Running      bool
 	Paused       bool
 	Managed      bool
 	ExposedPorts nat.PortSet
 	PortBindings nat.PortSet
 	Health       string
 	HostConfig   struct {
 		Isolation string
 	}
 }
 
1128fc1a
 // nameAssociation associates a container id with a name.
 type nameAssociation struct {
 	// name is the name to associate. Note that name is the primary key
 	// ("id" in memdb).
 	name        string
 	containerID string
 }
 
aacddda8
 // ViewDB provides an in-memory transactional (ACID) container Store
 type ViewDB interface {
1128fc1a
 	Snapshot() View
edad5270
 	Save(*Container) error
 	Delete(*Container) error
1128fc1a
 
 	ReserveName(name, containerID string) error
bc3209bc
 	ReleaseName(name string) error
aacddda8
 }
 
 // View can be used by readers to avoid locking
 type View interface {
 	All() ([]Snapshot, error)
 	Get(id string) (*Snapshot, error)
1128fc1a
 
 	GetID(name string) (string, error)
 	GetAllNames() map[string][]string
aacddda8
 }
 
054728b1
 var schema = &memdb.DBSchema{
 	Tables: map[string]*memdb.TableSchema{
1128fc1a
 		memdbContainersTable: {
 			Name: memdbContainersTable,
054728b1
 			Indexes: map[string]*memdb.IndexSchema{
 				memdbIDIndex: {
 					Name:    memdbIDIndex,
 					Unique:  true,
edad5270
 					Indexer: &containerByIDIndexer{},
054728b1
 				},
 			},
 		},
1128fc1a
 		memdbNamesTable: {
 			Name: memdbNamesTable,
 			Indexes: map[string]*memdb.IndexSchema{
 				// Used for names, because "id" is the primary key in memdb.
 				memdbIDIndex: {
 					Name:    memdbIDIndex,
 					Unique:  true,
 					Indexer: &namesByNameIndexer{},
 				},
 				memdbContainerIDIndex: {
 					Name:    memdbContainerIDIndex,
 					Indexer: &namesByContainerIDIndexer{},
 				},
 			},
 		},
054728b1
 	},
 }
 
aacddda8
 type memDB struct {
054728b1
 	store *memdb.MemDB
 }
 
c26b0cdf
 // NoSuchContainerError indicates that the container wasn't found in the
 // database.
 type NoSuchContainerError struct {
 	id string
 }
 
 // Error satisfies the error interface.
 func (e NoSuchContainerError) Error() string {
 	return "no such container " + e.id
 }
 
aacddda8
 // NewViewDB provides the default implementation, with the default schema
 func NewViewDB() (ViewDB, error) {
054728b1
 	store, err := memdb.NewMemDB(schema)
 	if err != nil {
 		return nil, err
 	}
aacddda8
 	return &memDB{store: store}, nil
054728b1
 }
 
 // Snapshot provides a consistent read-only View of the database
1128fc1a
 func (db *memDB) Snapshot() View {
edad5270
 	return &memdbView{
1128fc1a
 		txn: db.store.Txn(false),
edad5270
 	}
054728b1
 }
 
0e57eb95
 func (db *memDB) withTxn(cb func(*memdb.Txn) error) error {
 	txn := db.store.Txn(true)
 	err := cb(txn)
 	if err != nil {
 		txn.Abort()
 		return err
 	}
 	txn.Commit()
 	return nil
 }
 
a43be343
 // Save atomically updates the in-memory store state for a Container.
 // Only read only (deep) copies of containers may be passed in.
edad5270
 func (db *memDB) Save(c *Container) error {
0e57eb95
 	return db.withTxn(func(txn *memdb.Txn) error {
 		return txn.Insert(memdbContainersTable, c)
 	})
054728b1
 }
 
 // Delete removes an item by ID
edad5270
 func (db *memDB) Delete(c *Container) error {
0e57eb95
 	return db.withTxn(func(txn *memdb.Txn) error {
 		view := &memdbView{txn: txn}
 		names := view.getNames(c.ID)
1128fc1a
 
0e57eb95
 		for _, name := range names {
 			txn.Delete(memdbNamesTable, nameAssociation{name: name})
 		}
1128fc1a
 
1d9546fc
 		// Ignore error - the container may not actually exist in the
 		// db, but we still need to clean up associated names.
 		txn.Delete(memdbContainersTable, NewBaseContainer(c.ID, c.Root))
0e57eb95
 		return nil
 	})
1128fc1a
 }
 
 // ReserveName registers a container ID to a name
 // ReserveName is idempotent
 // Attempting to reserve a container ID to a name that already exists results in an `ErrNameReserved`
 // A name reservation is globally unique
 func (db *memDB) ReserveName(name, containerID string) error {
0e57eb95
 	return db.withTxn(func(txn *memdb.Txn) error {
 		s, err := txn.First(memdbNamesTable, memdbIDIndex, name)
 		if err != nil {
 			return err
 		}
 		if s != nil {
 			if s.(nameAssociation).containerID != containerID {
 				return ErrNameReserved
 			}
 			return nil
 		}
b4a63139
 		return txn.Insert(memdbNamesTable, nameAssociation{name: name, containerID: containerID})
0e57eb95
 	})
1128fc1a
 }
 
 // ReleaseName releases the reserved name
 // Once released, a name can be reserved again
bc3209bc
 func (db *memDB) ReleaseName(name string) error {
0e57eb95
 	return db.withTxn(func(txn *memdb.Txn) error {
f7f101d5
 		return txn.Delete(memdbNamesTable, nameAssociation{name: name})
0e57eb95
 	})
054728b1
 }
 
aacddda8
 type memdbView struct {
1128fc1a
 	txn *memdb.Txn
054728b1
 }
 
edad5270
 // All returns a all items in this snapshot. Returned objects must never be modified.
aacddda8
 func (v *memdbView) All() ([]Snapshot, error) {
054728b1
 	var all []Snapshot
1128fc1a
 	iter, err := v.txn.Get(memdbContainersTable, memdbIDIndex)
054728b1
 	if err != nil {
 		return nil, err
 	}
 	for {
 		item := iter.Next()
 		if item == nil {
 			break
 		}
edad5270
 		snapshot := v.transform(item.(*Container))
 		all = append(all, *snapshot)
054728b1
 	}
 	return all, nil
 }
 
edad5270
 // Get returns an item by id. Returned objects must never be modified.
aacddda8
 func (v *memdbView) Get(id string) (*Snapshot, error) {
1128fc1a
 	s, err := v.txn.First(memdbContainersTable, memdbIDIndex, id)
054728b1
 	if err != nil {
 		return nil, err
 	}
c26b0cdf
 	if s == nil {
 		return nil, NoSuchContainerError{id: id}
 	}
edad5270
 	return v.transform(s.(*Container)), nil
 }
 
1128fc1a
 // getNames lists all the reserved names for the given container ID.
 func (v *memdbView) getNames(containerID string) []string {
 	iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex, containerID)
 	if err != nil {
 		return nil
 	}
 
 	var names []string
 	for {
 		item := iter.Next()
 		if item == nil {
 			break
 		}
 		names = append(names, item.(nameAssociation).name)
 	}
 
 	return names
 }
 
 // GetID returns the container ID that the passed in name is reserved to.
 func (v *memdbView) GetID(name string) (string, error) {
 	s, err := v.txn.First(memdbNamesTable, memdbIDIndex, name)
 	if err != nil {
 		return "", err
 	}
 	if s == nil {
 		return "", ErrNameNotReserved
 	}
 	return s.(nameAssociation).containerID, nil
 }
 
 // GetAllNames returns all registered names.
 func (v *memdbView) GetAllNames() map[string][]string {
 	iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex)
 	if err != nil {
 		return nil
 	}
 
 	out := make(map[string][]string)
 	for {
 		item := iter.Next()
 		if item == nil {
 			break
 		}
 		assoc := item.(nameAssociation)
 		out[assoc.containerID] = append(out[assoc.containerID], assoc.name)
 	}
 
 	return out
 }
 
edad5270
 // transform maps a (deep) copied Container object to what queries need.
 // A lock on the Container is not held because these are immutable deep copies.
 func (v *memdbView) transform(container *Container) *Snapshot {
97b16aec
 	health := types.NoHealthcheck
 	if container.Health != nil {
 		health = container.Health.Status()
 	}
edad5270
 	snapshot := &Snapshot{
 		Container: types.Container{
 			ID:      container.ID,
1128fc1a
 			Names:   v.getNames(container.ID),
edad5270
 			ImageID: container.ImageID.String(),
 			Ports:   []types.Port{},
 			Mounts:  container.GetMountPoints(),
 			State:   container.State.StateString(),
 			Status:  container.State.String(),
 			Created: container.Created.Unix(),
 		},
 		CreatedAt:    container.Created,
 		StartedAt:    container.StartedAt,
 		Name:         container.Name,
 		Pid:          container.Pid,
 		Managed:      container.Managed,
 		ExposedPorts: make(nat.PortSet),
 		PortBindings: make(nat.PortSet),
97b16aec
 		Health:       health,
edad5270
 		Running:      container.Running,
 		Paused:       container.Paused,
 		ExitCode:     container.ExitCode(),
 	}
 
 	if snapshot.Names == nil {
 		// Dead containers will often have no name, so make sure the response isn't null
 		snapshot.Names = []string{}
 	}
 
 	if container.HostConfig != nil {
 		snapshot.Container.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode)
 		snapshot.HostConfig.Isolation = string(container.HostConfig.Isolation)
 		for binding := range container.HostConfig.PortBindings {
 			snapshot.PortBindings[binding] = struct{}{}
 		}
 	}
 
 	if container.Config != nil {
 		snapshot.Image = container.Config.Image
 		snapshot.Labels = container.Config.Labels
 		for exposed := range container.Config.ExposedPorts {
 			snapshot.ExposedPorts[exposed] = struct{}{}
 		}
 	}
 
 	if len(container.Args) > 0 {
f23c00d8
 		var args []string
edad5270
 		for _, arg := range container.Args {
 			if strings.Contains(arg, " ") {
 				args = append(args, fmt.Sprintf("'%s'", arg))
 			} else {
 				args = append(args, arg)
 			}
 		}
 		argsAsString := strings.Join(args, " ")
 		snapshot.Command = fmt.Sprintf("%s %s", container.Path, argsAsString)
 	} else {
 		snapshot.Command = container.Path
 	}
 
 	snapshot.Ports = []types.Port{}
 	networks := make(map[string]*network.EndpointSettings)
 	if container.NetworkSettings != nil {
 		for name, netw := range container.NetworkSettings.Networks {
 			if netw == nil || netw.EndpointSettings == nil {
 				continue
 			}
 			networks[name] = &network.EndpointSettings{
 				EndpointID:          netw.EndpointID,
 				Gateway:             netw.Gateway,
 				IPAddress:           netw.IPAddress,
 				IPPrefixLen:         netw.IPPrefixLen,
 				IPv6Gateway:         netw.IPv6Gateway,
 				GlobalIPv6Address:   netw.GlobalIPv6Address,
 				GlobalIPv6PrefixLen: netw.GlobalIPv6PrefixLen,
 				MacAddress:          netw.MacAddress,
 				NetworkID:           netw.NetworkID,
 			}
 			if netw.IPAMConfig != nil {
 				networks[name].IPAMConfig = &network.EndpointIPAMConfig{
 					IPv4Address: netw.IPAMConfig.IPv4Address,
 					IPv6Address: netw.IPAMConfig.IPv6Address,
 				}
 			}
 		}
 		for port, bindings := range container.NetworkSettings.Ports {
 			p, err := nat.ParsePort(port.Port())
 			if err != nil {
 				logrus.Warnf("invalid port map %+v", err)
 				continue
 			}
 			if len(bindings) == 0 {
 				snapshot.Ports = append(snapshot.Ports, types.Port{
 					PrivatePort: uint16(p),
 					Type:        port.Proto(),
 				})
 				continue
 			}
 			for _, binding := range bindings {
 				h, err := nat.ParsePort(binding.HostPort)
 				if err != nil {
 					logrus.Warnf("invalid host port map %+v", err)
 					continue
 				}
 				snapshot.Ports = append(snapshot.Ports, types.Port{
 					PrivatePort: uint16(p),
 					PublicPort:  uint16(h),
 					Type:        port.Proto(),
 					IP:          binding.HostIP,
 				})
 			}
 		}
 	}
 	snapshot.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks}
 
 	return snapshot
 }
 
 // containerByIDIndexer is used to extract the ID field from Container types.
 // memdb.StringFieldIndex can not be used since ID is a field from an embedded struct.
 type containerByIDIndexer struct{}
 
 // FromObject implements the memdb.SingleIndexer interface for Container objects
 func (e *containerByIDIndexer) FromObject(obj interface{}) (bool, []byte, error) {
 	c, ok := obj.(*Container)
 	if !ok {
 		return false, nil, fmt.Errorf("%T is not a Container", obj)
 	}
 	// Add the null character as a terminator
 	v := c.ID + "\x00"
 	return true, []byte(v), nil
 }
 
 // FromArgs implements the memdb.Indexer interface
 func (e *containerByIDIndexer) FromArgs(args ...interface{}) ([]byte, error) {
 	if len(args) != 1 {
 		return nil, fmt.Errorf("must provide only a single argument")
 	}
 	arg, ok := args[0].(string)
 	if !ok {
 		return nil, fmt.Errorf("argument must be a string: %#v", args[0])
 	}
 	// Add the null character as a terminator
 	arg += "\x00"
 	return []byte(arg), nil
054728b1
 }
1128fc1a
 
 // namesByNameIndexer is used to index container name associations by name.
 type namesByNameIndexer struct{}
 
 func (e *namesByNameIndexer) FromObject(obj interface{}) (bool, []byte, error) {
 	n, ok := obj.(nameAssociation)
 	if !ok {
 		return false, nil, fmt.Errorf(`%T does not have type "nameAssociation"`, obj)
 	}
 
 	// Add the null character as a terminator
 	return true, []byte(n.name + "\x00"), nil
 }
 
 func (e *namesByNameIndexer) FromArgs(args ...interface{}) ([]byte, error) {
 	if len(args) != 1 {
 		return nil, fmt.Errorf("must provide only a single argument")
 	}
 	arg, ok := args[0].(string)
 	if !ok {
 		return nil, fmt.Errorf("argument must be a string: %#v", args[0])
 	}
 	// Add the null character as a terminator
 	arg += "\x00"
 	return []byte(arg), nil
 }
 
 // namesByContainerIDIndexer is used to index container names by container ID.
 type namesByContainerIDIndexer struct{}
 
 func (e *namesByContainerIDIndexer) FromObject(obj interface{}) (bool, []byte, error) {
 	n, ok := obj.(nameAssociation)
 	if !ok {
89e3a4af
 		return false, nil, fmt.Errorf(`%T does not have type "nameAssociation"`, obj)
1128fc1a
 	}
 
 	// Add the null character as a terminator
 	return true, []byte(n.containerID + "\x00"), nil
 }
 
 func (e *namesByContainerIDIndexer) FromArgs(args ...interface{}) ([]byte, error) {
 	if len(args) != 1 {
 		return nil, fmt.Errorf("must provide only a single argument")
 	}
 	arg, ok := args[0].(string)
 	if !ok {
 		return nil, fmt.Errorf("argument must be a string: %#v", args[0])
 	}
 	// Add the null character as a terminator
 	arg += "\x00"
 	return []byte(arg), nil
 }