github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/container/view.go (about)

     1  package container
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/network"
    11  	"github.com/docker/go-connections/nat"
    12  	"github.com/hashicorp/go-memdb"
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  const (
    17  	memdbContainersTable = "containers"
    18  	memdbNamesTable      = "names"
    19  
    20  	memdbIDIndex          = "id"
    21  	memdbContainerIDIndex = "containerid"
    22  )
    23  
    24  var (
    25  	// ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved
    26  	ErrNameReserved = errors.New("name is reserved")
    27  	// ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved
    28  	ErrNameNotReserved = errors.New("name is not reserved")
    29  )
    30  
    31  // Snapshot is a read only view for Containers. It holds all information necessary to serve container queries in a
    32  // versioned ACID in-memory store.
    33  type Snapshot struct {
    34  	types.Container
    35  
    36  	// additional info queries need to filter on
    37  	// preserve nanosec resolution for queries
    38  	CreatedAt    time.Time
    39  	StartedAt    time.Time
    40  	Name         string
    41  	Pid          int
    42  	ExitCode     int
    43  	Running      bool
    44  	Paused       bool
    45  	Managed      bool
    46  	ExposedPorts nat.PortSet
    47  	PortBindings nat.PortSet
    48  	Health       string
    49  	HostConfig   struct {
    50  		Isolation string
    51  	}
    52  }
    53  
    54  // nameAssociation associates a container id with a name.
    55  type nameAssociation struct {
    56  	// name is the name to associate. Note that name is the primary key
    57  	// ("id" in memdb).
    58  	name        string
    59  	containerID string
    60  }
    61  
    62  // ViewDB provides an in-memory transactional (ACID) container Store
    63  type ViewDB interface {
    64  	Snapshot() View
    65  	Save(*Container) error
    66  	Delete(*Container) error
    67  
    68  	ReserveName(name, containerID string) error
    69  	ReleaseName(name string) error
    70  }
    71  
    72  // View can be used by readers to avoid locking
    73  type View interface {
    74  	All() ([]Snapshot, error)
    75  	Get(id string) (*Snapshot, error)
    76  
    77  	GetID(name string) (string, error)
    78  	GetAllNames() map[string][]string
    79  }
    80  
    81  var schema = &memdb.DBSchema{
    82  	Tables: map[string]*memdb.TableSchema{
    83  		memdbContainersTable: {
    84  			Name: memdbContainersTable,
    85  			Indexes: map[string]*memdb.IndexSchema{
    86  				memdbIDIndex: {
    87  					Name:    memdbIDIndex,
    88  					Unique:  true,
    89  					Indexer: &containerByIDIndexer{},
    90  				},
    91  			},
    92  		},
    93  		memdbNamesTable: {
    94  			Name: memdbNamesTable,
    95  			Indexes: map[string]*memdb.IndexSchema{
    96  				// Used for names, because "id" is the primary key in memdb.
    97  				memdbIDIndex: {
    98  					Name:    memdbIDIndex,
    99  					Unique:  true,
   100  					Indexer: &namesByNameIndexer{},
   101  				},
   102  				memdbContainerIDIndex: {
   103  					Name:    memdbContainerIDIndex,
   104  					Indexer: &namesByContainerIDIndexer{},
   105  				},
   106  			},
   107  		},
   108  	},
   109  }
   110  
   111  type memDB struct {
   112  	store *memdb.MemDB
   113  }
   114  
   115  // NoSuchContainerError indicates that the container wasn't found in the
   116  // database.
   117  type NoSuchContainerError struct {
   118  	id string
   119  }
   120  
   121  // Error satisfies the error interface.
   122  func (e NoSuchContainerError) Error() string {
   123  	return "no such container " + e.id
   124  }
   125  
   126  // NewViewDB provides the default implementation, with the default schema
   127  func NewViewDB() (ViewDB, error) {
   128  	store, err := memdb.NewMemDB(schema)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	return &memDB{store: store}, nil
   133  }
   134  
   135  // Snapshot provides a consistent read-only View of the database
   136  func (db *memDB) Snapshot() View {
   137  	return &memdbView{
   138  		txn: db.store.Txn(false),
   139  	}
   140  }
   141  
   142  func (db *memDB) withTxn(cb func(*memdb.Txn) error) error {
   143  	txn := db.store.Txn(true)
   144  	err := cb(txn)
   145  	if err != nil {
   146  		txn.Abort()
   147  		return err
   148  	}
   149  	txn.Commit()
   150  	return nil
   151  }
   152  
   153  // Save atomically updates the in-memory store state for a Container.
   154  // Only read only (deep) copies of containers may be passed in.
   155  func (db *memDB) Save(c *Container) error {
   156  	return db.withTxn(func(txn *memdb.Txn) error {
   157  		return txn.Insert(memdbContainersTable, c)
   158  	})
   159  }
   160  
   161  // Delete removes an item by ID
   162  func (db *memDB) Delete(c *Container) error {
   163  	return db.withTxn(func(txn *memdb.Txn) error {
   164  		view := &memdbView{txn: txn}
   165  		names := view.getNames(c.ID)
   166  
   167  		for _, name := range names {
   168  			txn.Delete(memdbNamesTable, nameAssociation{name: name})
   169  		}
   170  
   171  		// Ignore error - the container may not actually exist in the
   172  		// db, but we still need to clean up associated names.
   173  		txn.Delete(memdbContainersTable, NewBaseContainer(c.ID, c.Root))
   174  		return nil
   175  	})
   176  }
   177  
   178  // ReserveName registers a container ID to a name
   179  // ReserveName is idempotent
   180  // Attempting to reserve a container ID to a name that already exists results in an `ErrNameReserved`
   181  // A name reservation is globally unique
   182  func (db *memDB) ReserveName(name, containerID string) error {
   183  	return db.withTxn(func(txn *memdb.Txn) error {
   184  		s, err := txn.First(memdbNamesTable, memdbIDIndex, name)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		if s != nil {
   189  			if s.(nameAssociation).containerID != containerID {
   190  				return ErrNameReserved
   191  			}
   192  			return nil
   193  		}
   194  
   195  		if err := txn.Insert(memdbNamesTable, nameAssociation{name: name, containerID: containerID}); err != nil {
   196  			return err
   197  		}
   198  		return nil
   199  	})
   200  }
   201  
   202  // ReleaseName releases the reserved name
   203  // Once released, a name can be reserved again
   204  func (db *memDB) ReleaseName(name string) error {
   205  	return db.withTxn(func(txn *memdb.Txn) error {
   206  		return txn.Delete(memdbNamesTable, nameAssociation{name: name})
   207  	})
   208  }
   209  
   210  type memdbView struct {
   211  	txn *memdb.Txn
   212  }
   213  
   214  // All returns a all items in this snapshot. Returned objects must never be modified.
   215  func (v *memdbView) All() ([]Snapshot, error) {
   216  	var all []Snapshot
   217  	iter, err := v.txn.Get(memdbContainersTable, memdbIDIndex)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	for {
   222  		item := iter.Next()
   223  		if item == nil {
   224  			break
   225  		}
   226  		snapshot := v.transform(item.(*Container))
   227  		all = append(all, *snapshot)
   228  	}
   229  	return all, nil
   230  }
   231  
   232  // Get returns an item by id. Returned objects must never be modified.
   233  func (v *memdbView) Get(id string) (*Snapshot, error) {
   234  	s, err := v.txn.First(memdbContainersTable, memdbIDIndex, id)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	if s == nil {
   239  		return nil, NoSuchContainerError{id: id}
   240  	}
   241  	return v.transform(s.(*Container)), nil
   242  }
   243  
   244  // getNames lists all the reserved names for the given container ID.
   245  func (v *memdbView) getNames(containerID string) []string {
   246  	iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex, containerID)
   247  	if err != nil {
   248  		return nil
   249  	}
   250  
   251  	var names []string
   252  	for {
   253  		item := iter.Next()
   254  		if item == nil {
   255  			break
   256  		}
   257  		names = append(names, item.(nameAssociation).name)
   258  	}
   259  
   260  	return names
   261  }
   262  
   263  // GetID returns the container ID that the passed in name is reserved to.
   264  func (v *memdbView) GetID(name string) (string, error) {
   265  	s, err := v.txn.First(memdbNamesTable, memdbIDIndex, name)
   266  	if err != nil {
   267  		return "", err
   268  	}
   269  	if s == nil {
   270  		return "", ErrNameNotReserved
   271  	}
   272  	return s.(nameAssociation).containerID, nil
   273  }
   274  
   275  // GetAllNames returns all registered names.
   276  func (v *memdbView) GetAllNames() map[string][]string {
   277  	iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex)
   278  	if err != nil {
   279  		return nil
   280  	}
   281  
   282  	out := make(map[string][]string)
   283  	for {
   284  		item := iter.Next()
   285  		if item == nil {
   286  			break
   287  		}
   288  		assoc := item.(nameAssociation)
   289  		out[assoc.containerID] = append(out[assoc.containerID], assoc.name)
   290  	}
   291  
   292  	return out
   293  }
   294  
   295  // transform maps a (deep) copied Container object to what queries need.
   296  // A lock on the Container is not held because these are immutable deep copies.
   297  func (v *memdbView) transform(container *Container) *Snapshot {
   298  	snapshot := &Snapshot{
   299  		Container: types.Container{
   300  			ID:      container.ID,
   301  			Names:   v.getNames(container.ID),
   302  			ImageID: container.ImageID.String(),
   303  			Ports:   []types.Port{},
   304  			Mounts:  container.GetMountPoints(),
   305  			State:   container.State.StateString(),
   306  			Status:  container.State.String(),
   307  			Created: container.Created.Unix(),
   308  		},
   309  		CreatedAt:    container.Created,
   310  		StartedAt:    container.StartedAt,
   311  		Name:         container.Name,
   312  		Pid:          container.Pid,
   313  		Managed:      container.Managed,
   314  		ExposedPorts: make(nat.PortSet),
   315  		PortBindings: make(nat.PortSet),
   316  		Health:       container.HealthString(),
   317  		Running:      container.Running,
   318  		Paused:       container.Paused,
   319  		ExitCode:     container.ExitCode(),
   320  	}
   321  
   322  	if snapshot.Names == nil {
   323  		// Dead containers will often have no name, so make sure the response isn't null
   324  		snapshot.Names = []string{}
   325  	}
   326  
   327  	if container.HostConfig != nil {
   328  		snapshot.Container.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode)
   329  		snapshot.HostConfig.Isolation = string(container.HostConfig.Isolation)
   330  		for binding := range container.HostConfig.PortBindings {
   331  			snapshot.PortBindings[binding] = struct{}{}
   332  		}
   333  	}
   334  
   335  	if container.Config != nil {
   336  		snapshot.Image = container.Config.Image
   337  		snapshot.Labels = container.Config.Labels
   338  		for exposed := range container.Config.ExposedPorts {
   339  			snapshot.ExposedPorts[exposed] = struct{}{}
   340  		}
   341  	}
   342  
   343  	if len(container.Args) > 0 {
   344  		args := []string{}
   345  		for _, arg := range container.Args {
   346  			if strings.Contains(arg, " ") {
   347  				args = append(args, fmt.Sprintf("'%s'", arg))
   348  			} else {
   349  				args = append(args, arg)
   350  			}
   351  		}
   352  		argsAsString := strings.Join(args, " ")
   353  		snapshot.Command = fmt.Sprintf("%s %s", container.Path, argsAsString)
   354  	} else {
   355  		snapshot.Command = container.Path
   356  	}
   357  
   358  	snapshot.Ports = []types.Port{}
   359  	networks := make(map[string]*network.EndpointSettings)
   360  	if container.NetworkSettings != nil {
   361  		for name, netw := range container.NetworkSettings.Networks {
   362  			if netw == nil || netw.EndpointSettings == nil {
   363  				continue
   364  			}
   365  			networks[name] = &network.EndpointSettings{
   366  				EndpointID:          netw.EndpointID,
   367  				Gateway:             netw.Gateway,
   368  				IPAddress:           netw.IPAddress,
   369  				IPPrefixLen:         netw.IPPrefixLen,
   370  				IPv6Gateway:         netw.IPv6Gateway,
   371  				GlobalIPv6Address:   netw.GlobalIPv6Address,
   372  				GlobalIPv6PrefixLen: netw.GlobalIPv6PrefixLen,
   373  				MacAddress:          netw.MacAddress,
   374  				NetworkID:           netw.NetworkID,
   375  			}
   376  			if netw.IPAMConfig != nil {
   377  				networks[name].IPAMConfig = &network.EndpointIPAMConfig{
   378  					IPv4Address: netw.IPAMConfig.IPv4Address,
   379  					IPv6Address: netw.IPAMConfig.IPv6Address,
   380  				}
   381  			}
   382  		}
   383  		for port, bindings := range container.NetworkSettings.Ports {
   384  			p, err := nat.ParsePort(port.Port())
   385  			if err != nil {
   386  				logrus.Warnf("invalid port map %+v", err)
   387  				continue
   388  			}
   389  			if len(bindings) == 0 {
   390  				snapshot.Ports = append(snapshot.Ports, types.Port{
   391  					PrivatePort: uint16(p),
   392  					Type:        port.Proto(),
   393  				})
   394  				continue
   395  			}
   396  			for _, binding := range bindings {
   397  				h, err := nat.ParsePort(binding.HostPort)
   398  				if err != nil {
   399  					logrus.Warnf("invalid host port map %+v", err)
   400  					continue
   401  				}
   402  				snapshot.Ports = append(snapshot.Ports, types.Port{
   403  					PrivatePort: uint16(p),
   404  					PublicPort:  uint16(h),
   405  					Type:        port.Proto(),
   406  					IP:          binding.HostIP,
   407  				})
   408  			}
   409  		}
   410  	}
   411  	snapshot.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks}
   412  
   413  	return snapshot
   414  }
   415  
   416  // containerByIDIndexer is used to extract the ID field from Container types.
   417  // memdb.StringFieldIndex can not be used since ID is a field from an embedded struct.
   418  type containerByIDIndexer struct{}
   419  
   420  // FromObject implements the memdb.SingleIndexer interface for Container objects
   421  func (e *containerByIDIndexer) FromObject(obj interface{}) (bool, []byte, error) {
   422  	c, ok := obj.(*Container)
   423  	if !ok {
   424  		return false, nil, fmt.Errorf("%T is not a Container", obj)
   425  	}
   426  	// Add the null character as a terminator
   427  	v := c.ID + "\x00"
   428  	return true, []byte(v), nil
   429  }
   430  
   431  // FromArgs implements the memdb.Indexer interface
   432  func (e *containerByIDIndexer) FromArgs(args ...interface{}) ([]byte, error) {
   433  	if len(args) != 1 {
   434  		return nil, fmt.Errorf("must provide only a single argument")
   435  	}
   436  	arg, ok := args[0].(string)
   437  	if !ok {
   438  		return nil, fmt.Errorf("argument must be a string: %#v", args[0])
   439  	}
   440  	// Add the null character as a terminator
   441  	arg += "\x00"
   442  	return []byte(arg), nil
   443  }
   444  
   445  // namesByNameIndexer is used to index container name associations by name.
   446  type namesByNameIndexer struct{}
   447  
   448  func (e *namesByNameIndexer) FromObject(obj interface{}) (bool, []byte, error) {
   449  	n, ok := obj.(nameAssociation)
   450  	if !ok {
   451  		return false, nil, fmt.Errorf(`%T does not have type "nameAssociation"`, obj)
   452  	}
   453  
   454  	// Add the null character as a terminator
   455  	return true, []byte(n.name + "\x00"), nil
   456  }
   457  
   458  func (e *namesByNameIndexer) FromArgs(args ...interface{}) ([]byte, error) {
   459  	if len(args) != 1 {
   460  		return nil, fmt.Errorf("must provide only a single argument")
   461  	}
   462  	arg, ok := args[0].(string)
   463  	if !ok {
   464  		return nil, fmt.Errorf("argument must be a string: %#v", args[0])
   465  	}
   466  	// Add the null character as a terminator
   467  	arg += "\x00"
   468  	return []byte(arg), nil
   469  }
   470  
   471  // namesByContainerIDIndexer is used to index container names by container ID.
   472  type namesByContainerIDIndexer struct{}
   473  
   474  func (e *namesByContainerIDIndexer) FromObject(obj interface{}) (bool, []byte, error) {
   475  	n, ok := obj.(nameAssociation)
   476  	if !ok {
   477  		return false, nil, fmt.Errorf(`%T does not have type "nameAssocation"`, obj)
   478  	}
   479  
   480  	// Add the null character as a terminator
   481  	return true, []byte(n.containerID + "\x00"), nil
   482  }
   483  
   484  func (e *namesByContainerIDIndexer) FromArgs(args ...interface{}) ([]byte, error) {
   485  	if len(args) != 1 {
   486  		return nil, fmt.Errorf("must provide only a single argument")
   487  	}
   488  	arg, ok := args[0].(string)
   489  	if !ok {
   490  		return nil, fmt.Errorf("argument must be a string: %#v", args[0])
   491  	}
   492  	// Add the null character as a terminator
   493  	arg += "\x00"
   494  	return []byte(arg), nil
   495  }