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>
This commit is contained in:
Brian Goff 2015-11-21 13:45:34 -05:00
parent 0f9f99500c
commit 2600777469
3 changed files with 171 additions and 0 deletions

View File

@ -48,6 +48,7 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/discovery"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"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{})
for _, c := range containers {
if err := daemon.registerName(c); err != nil {
@ -346,10 +348,31 @@ func (daemon *Daemon) restore() error {
if daemon.configStore.AutoRestart && c.ShouldRestart() {
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
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 {
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
}
// 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
return container.ToDisk()
}

View File

@ -1,9 +1,12 @@
package daemon
import (
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/docker/pkg/graphdb"
)
// 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)
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
daemon/links_test.go Normal file
View File

@ -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])
}
}