Przeglądaj źródła

in-memory ACID store for containers

This can be used by readers/queries so they don't need locks.

Signed-off-by: Fabio Kung <fabio.kung@gmail.com>
Fabio Kung 8 lat temu
rodzic
commit
054728b1f5
3 zmienionych plików z 300 dodań i 0 usunięć
  1. 152 0
      container/snapshot.go
  2. 90 0
      container/view.go
  3. 58 0
      container/view_test.go

+ 152 - 0
container/snapshot.go

@@ -0,0 +1,152 @@
+package container
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/network"
+	"github.com/docker/go-connections/nat"
+)
+
+// Snapshot is a read only view for Containers
+type Snapshot struct {
+	ID           string `json:"Id"`
+	Name         string
+	Pid          int
+	Managed      bool
+	Image        string
+	ImageID      string
+	Command      string
+	Ports        []types.Port
+	ExposedPorts nat.PortSet
+	PublishPorts nat.PortSet
+	Labels       map[string]string
+	State        string
+	Status       string
+	Health       string
+	HostConfig   struct {
+		NetworkMode string
+		Isolation   string
+	}
+	NetworkSettings types.SummaryNetworkSettings
+	Mounts          []types.MountPoint
+	Created         time.Time
+	StartedAt       time.Time
+	Running         bool
+	Paused          bool
+	ExitCode        int
+}
+
+// Snapshot provides a read only view of a Container. Callers must hold a Lock on the container object.
+func (container *Container) Snapshot() *Snapshot {
+	snapshot := &Snapshot{
+		ID:           container.ID,
+		Name:         container.Name,
+		Pid:          container.Pid,
+		Managed:      container.Managed,
+		ImageID:      container.ImageID.String(),
+		Ports:        []types.Port{},
+		ExposedPorts: make(nat.PortSet),
+		PublishPorts: make(nat.PortSet),
+		State:        container.State.StateString(),
+		Status:       container.State.String(),
+		Health:       container.State.HealthString(),
+		Mounts:       container.GetMountPoints(),
+		Created:      container.Created,
+		StartedAt:    container.StartedAt,
+		Running:      container.Running,
+		Paused:       container.Paused,
+		ExitCode:     container.ExitCode(),
+	}
+
+	if container.HostConfig != nil {
+		snapshot.HostConfig.Isolation = string(container.HostConfig.Isolation)
+		snapshot.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode)
+		for publish := range container.HostConfig.PortBindings {
+			snapshot.PublishPorts[publish] = 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 {
+		args := []string{}
+		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
+	}
+
+	if container.NetworkSettings != nil {
+		networks := make(map[string]*network.EndpointSettings)
+		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,
+				}
+			}
+		}
+		snapshot.NetworkSettings = types.SummaryNetworkSettings{Networks: networks}
+		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,
+				})
+			}
+		}
+
+	}
+
+	return snapshot
+}

+ 90 - 0
container/view.go

@@ -0,0 +1,90 @@
+package container
+
+import "github.com/hashicorp/go-memdb"
+
+const (
+	memdbTable   = "containers"
+	memdbIDField = "ID"
+	memdbIDIndex = "id"
+)
+
+var schema = &memdb.DBSchema{
+	Tables: map[string]*memdb.TableSchema{
+		memdbTable: {
+			Name: memdbTable,
+			Indexes: map[string]*memdb.IndexSchema{
+				memdbIDIndex: {
+					Name:    memdbIDIndex,
+					Unique:  true,
+					Indexer: &memdb.StringFieldIndex{Field: memdbIDField},
+				},
+			},
+		},
+	},
+}
+
+// MemDB provides an in-memory transactional (ACID) container Store
+type MemDB struct {
+	store *memdb.MemDB
+}
+
+// NewMemDB provides the default implementation, with the default schema
+func NewMemDB() (*MemDB, error) {
+	store, err := memdb.NewMemDB(schema)
+	if err != nil {
+		return nil, err
+	}
+	return &MemDB{store: store}, nil
+}
+
+// Snapshot provides a consistent read-only View of the database
+func (db *MemDB) Snapshot() *View {
+	return &View{db.store.Txn(false)}
+}
+
+// Save atomically updates the in-memory store
+func (db *MemDB) Save(snapshot *Snapshot) error {
+	txn := db.store.Txn(true)
+	defer txn.Commit()
+	return txn.Insert(memdbTable, snapshot)
+}
+
+// Delete removes an item by ID
+func (db *MemDB) Delete(id string) error {
+	txn := db.store.Txn(true)
+	defer txn.Commit()
+	return txn.Delete(memdbTable, &Snapshot{ID: id})
+}
+
+// View can be used by readers to avoid locking
+type View struct {
+	txn *memdb.Txn
+}
+
+// All returns a all items in this snapshot
+func (v *View) All() ([]Snapshot, error) {
+	var all []Snapshot
+	iter, err := v.txn.Get(memdbTable, memdbIDIndex)
+	if err != nil {
+		return nil, err
+	}
+	for {
+		item := iter.Next()
+		if item == nil {
+			break
+		}
+		snapshot := *(item.(*Snapshot)) // force a copy
+		all = append(all, snapshot)
+	}
+	return all, nil
+}
+
+//Get returns an item by id
+func (v *View) Get(id string) (*Snapshot, error) {
+	s, err := v.txn.First(memdbTable, memdbIDIndex, id)
+	if err != nil {
+		return nil, err
+	}
+	snapshot := *(s.(*Snapshot)) // force a copy
+	return &snapshot, nil
+}

+ 58 - 0
container/view_test.go

@@ -0,0 +1,58 @@
+package container
+
+import "testing"
+
+func TestViewSave(t *testing.T) {
+	db, err := NewMemDB()
+	if err != nil {
+		t.Fatal(err)
+	}
+	snapshot := NewBaseContainer("id", "root").Snapshot()
+	if err := db.Save(snapshot); err != nil {
+		t.Fatal(err)
+
+	}
+}
+
+func TestViewAll(t *testing.T) {
+	var (
+		db, _ = NewMemDB()
+		one   = NewBaseContainer("id1", "root1").Snapshot()
+		two   = NewBaseContainer("id2", "root2").Snapshot()
+	)
+	one.Pid = 10
+	two.Pid = 20
+	db.Save(one)
+	db.Save(two)
+	all, err := db.Snapshot().All()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if l := len(all); l != 2 {
+		t.Fatalf("expected 2 items, got %d", l)
+	}
+	byID := make(map[string]Snapshot)
+	for i := range all {
+		byID[all[i].ID] = all[i]
+	}
+	if s, ok := byID["id1"]; !ok || s.Pid != 10 {
+		t.Fatalf("expected something different with for id1: %v", s)
+	}
+	if s, ok := byID["id2"]; !ok || s.Pid != 20 {
+		t.Fatalf("expected something different with for id1: %v", s)
+	}
+}
+
+func TestViewGet(t *testing.T) {
+	db, _ := NewMemDB()
+	one := NewBaseContainer("id", "root")
+	one.ImageID = "some-image-123"
+	db.Save(one.Snapshot())
+	s, err := db.Snapshot().Get("id")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if s == nil || s.ImageID != "some-image-123" {
+		t.Fatalf("expected something different. Got: %v", s)
+	}
+}