فهرست منبع

Add migration from sqlite links back to hostConfig

Before #16032, once links were setup
in the sqlite db, hostConfig.Links was cleared out.
This means that we need to migrate data back out of the sqlite db and
put it back into hostConfig.Links so that links specified on older
daemons can be used.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff 9 سال پیش
والد
کامیت
2600777469
3فایلهای تغییر یافته به همراه171 افزوده شده و 0 حذف شده
  1. 29 0
      daemon/daemon.go
  2. 41 0
      daemon/links.go
  3. 101 0
      daemon/links_test.go

+ 29 - 0
daemon/daemon.go

@@ -48,6 +48,7 @@ import (
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/discovery"
 	"github.com/docker/docker/pkg/discovery"
 	"github.com/docker/docker/pkg/fileutils"
 	"github.com/docker/docker/pkg/fileutils"
+	"github.com/docker/docker/pkg/graphdb"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/namesgenerator"
 	"github.com/docker/docker/pkg/namesgenerator"
@@ -331,6 +332,7 @@ func (daemon *Daemon) restore() error {
 		}
 		}
 	}
 	}
 
 
+	var migrateLegacyLinks bool
 	restartContainers := make(map[*container.Container]chan struct{})
 	restartContainers := make(map[*container.Container]chan struct{})
 	for _, c := range containers {
 	for _, c := range containers {
 		if err := daemon.registerName(c); err != nil {
 		if err := daemon.registerName(c); err != nil {
@@ -346,10 +348,31 @@ func (daemon *Daemon) restore() error {
 		if daemon.configStore.AutoRestart && c.ShouldRestart() {
 		if daemon.configStore.AutoRestart && c.ShouldRestart() {
 			restartContainers[c] = make(chan struct{})
 			restartContainers[c] = make(chan struct{})
 		}
 		}
+
+		// if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated
+		if c.HostConfig != nil && c.HostConfig.Links == nil {
+			migrateLegacyLinks = true
+		}
+	}
+
+	// migrate any legacy links from sqlite
+	linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
+	var legacyLinkDB *graphdb.Database
+	if migrateLegacyLinks {
+		legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile)
+		if err != nil {
+			return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err)
+		}
+		defer legacyLinkDB.Close()
 	}
 	}
 
 
 	// Now that all the containers are registered, register the links
 	// Now that all the containers are registered, register the links
 	for _, c := range containers {
 	for _, c := range containers {
+		if migrateLegacyLinks {
+			if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil {
+				return err
+			}
+		}
 		if err := daemon.registerLinks(c, c.HostConfig); err != nil {
 		if err := daemon.registerLinks(c, c.HostConfig); err != nil {
 			logrus.Errorf("failed to register link for container %s: %v", c.ID, err)
 			logrus.Errorf("failed to register link for container %s: %v", c.ID, err)
 		}
 		}
@@ -1359,6 +1382,12 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *
 		return err
 		return err
 	}
 	}
 
 
+	// make sure links is not nil
+	// this ensures that on the next daemon restart we don't try to migrate from legacy sqlite links
+	if hostConfig.Links == nil {
+		hostConfig.Links = []string{}
+	}
+
 	container.HostConfig = hostConfig
 	container.HostConfig = hostConfig
 	return container.ToDisk()
 	return container.ToDisk()
 }
 }

+ 41 - 0
daemon/links.go

@@ -1,9 +1,12 @@
 package daemon
 package daemon
 
 
 import (
 import (
+	"strings"
 	"sync"
 	"sync"
 
 
+	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/pkg/graphdb"
 )
 )
 
 
 // linkIndex stores link relationships between containers, including their specified alias
 // linkIndex stores link relationships between containers, including their specified alias
@@ -85,3 +88,41 @@ func (l *linkIndex) delete(container *container.Container) {
 	delete(l.childIdx, container)
 	delete(l.childIdx, container)
 	l.mu.Unlock()
 	l.mu.Unlock()
 }
 }
+
+// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig
+// when sqlite links were used, hostConfig.Links was set to nil
+func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error {
+	// if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped
+	if container.HostConfig == nil || container.HostConfig.Links != nil {
+		return nil
+	}
+
+	logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID)
+
+	fullName := container.Name
+	if fullName[0] != '/' {
+		fullName = "/" + fullName
+	}
+
+	// don't use a nil slice, this ensures that the check above will skip once the migration has completed
+	links := []string{}
+	children, err := db.Children(fullName, 0)
+	if err != nil {
+		if !strings.Contains(err.Error(), "Cannot find child for") {
+			return err
+		}
+		// else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration
+	}
+
+	for _, child := range children {
+		c, err := daemon.GetContainer(child.Entity.ID())
+		if err != nil {
+			return err
+		}
+
+		links = append(links, c.Name+":"+child.Edge.Name)
+	}
+
+	container.HostConfig.Links = links
+	return container.WriteHostConfig()
+}

+ 101 - 0
daemon/links_test.go

@@ -0,0 +1,101 @@
+package daemon
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/docker/container"
+	"github.com/docker/docker/pkg/graphdb"
+	"github.com/docker/docker/pkg/stringid"
+	containertypes "github.com/docker/engine-api/types/container"
+)
+
+func TestMigrateLegacySqliteLinks(t *testing.T) {
+	tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpDir)
+
+	name1 := "test1"
+	c1 := &container.Container{
+		CommonContainer: container.CommonContainer{
+			ID:         stringid.GenerateNonCryptoID(),
+			Name:       name1,
+			HostConfig: &containertypes.HostConfig{},
+		},
+	}
+	c1.Root = tmpDir
+
+	name2 := "test2"
+	c2 := &container.Container{
+		CommonContainer: container.CommonContainer{
+			ID:   stringid.GenerateNonCryptoID(),
+			Name: name2,
+		},
+	}
+
+	store := &contStore{
+		s: map[string]*container.Container{
+			c1.ID: c1,
+			c2.ID: c2,
+		},
+	}
+
+	d := &Daemon{root: tmpDir, containers: store}
+	db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := db.Set("/"+name1, c1.ID); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := db.Set("/"+name2, c2.ID); err != nil {
+		t.Fatal(err)
+	}
+
+	alias := "hello"
+	if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := d.migrateLegacySqliteLinks(db, c1); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(c1.HostConfig.Links) != 1 {
+		t.Fatal("expected links to be populated but is empty")
+	}
+
+	expected := name2 + ":" + alias
+	actual := c1.HostConfig.Links[0]
+	if actual != expected {
+		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual)
+	}
+
+	// ensure this is persisted
+	b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	type hc struct {
+		Links []string
+	}
+	var cfg hc
+	if err := json.Unmarshal(b, &cfg); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(cfg.Links) != 1 {
+		t.Fatalf("expected one entry in links, got: %d", len(cfg.Links))
+	}
+	if cfg.Links[0] != expected { // same expected as above
+		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0])
+	}
+}