The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/2384
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From f36ac9dfad74f98b94f8588f1911eaa0cf8afecd Mon Sep 17 00:00:00 2001 From: Tycho Andersen <tycho.ander...@canonical.com> Date: Thu, 15 Sep 2016 13:13:30 +0000 Subject: [PATCH 1/2] actually support copying across different CoW based backend types Closes #2359 Signed-off-by: Tycho Andersen <tycho.ander...@canonical.com> --- lxd/migrate.go | 35 +--------------- lxd/storage.go | 112 +++++++++++++++++++++++++++++++++++++++++---------- lxd/storage_btrfs.go | 27 +++++++++++-- lxd/storage_dir.go | 4 +- lxd/storage_lvm.go | 4 +- lxd/storage_zfs.go | 66 +++++++++++++++++++++++++----- 6 files changed, 176 insertions(+), 72 deletions(-) diff --git a/lxd/migrate.go b/lxd/migrate.go index 5377a61..a5ad1ad 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -582,32 +582,6 @@ func (c *migrationSink) do() error { imagesDir := "" srcIdmap := new(shared.IdmapSet) - snapshots := []container{} - for _, snap := range header.Snapshots { - // TODO: we need to propagate snapshot configurations - // as well. Right now the container configuration is - // done through the initial migration post. Should we - // post the snapshots and their configs as well, or do - // it some other way? - name := c.container.Name() + shared.SnapshotDelimiter + snap - args := containerArgs{ - Ctype: cTypeSnapshot, - Config: c.container.LocalConfig(), - Profiles: c.container.Profiles(), - Ephemeral: c.container.IsEphemeral(), - Architecture: c.container.Architecture(), - Devices: c.container.LocalDevices(), - Name: name, - } - - ct, err := containerCreateEmptySnapshot(c.container.Daemon(), args) - if err != nil { - restore <- err - return - } - snapshots = append(snapshots, ct) - } - for _, idmap := range header.Idmap { e := shared.IdmapEntry{ Isuid: *idmap.Isuid, @@ -626,7 +600,7 @@ func (c *migrationSink) do() error { */ fsTransfer := make(chan error) go func() { - if err := mySink(c.live, c.container, snapshots, c.fsConn); err != nil { + if err := mySink(c.live, c.container, header.Snapshots, c.fsConn, srcIdmap); err != nil { fsTransfer <- err return } @@ -670,13 +644,6 @@ func (c *migrationSink) do() error { } - for _, snap := range snapshots { - if err := ShiftIfNecessary(snap, srcIdmap); err != nil { - restore <- err - return - } - } - restore <- nil }(c) diff --git a/lxd/storage.go b/lxd/storage.go index 7d92d16..4af2b28 100644 --- a/lxd/storage.go +++ b/lxd/storage.go @@ -192,7 +192,7 @@ type storage interface { // already present on the target instance as an exercise for the // enterprising developer. MigrationSource(container container) (MigrationStorageSourceDriver, error) - MigrationSink(live bool, container container, objects []container, conn *websocket.Conn) error + MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error } func newStorage(d *Daemon, sType storageType) (storage, error) { @@ -556,19 +556,15 @@ func (lw *storageLogWrapper) MigrationSource(container container) (MigrationStor return lw.w.MigrationSource(container) } -func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []container, conn *websocket.Conn) error { - objNames := []string{} - for _, obj := range objects { - objNames = append(objNames, obj.Name()) - } - +func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { lw.log.Debug("MigrationSink", log.Ctx{ "live": live, "container": container.Name(), - "objects": objNames, + "objects": objects, + "srcIdmap": *srcIdmap, }) - return lw.w.MigrationSink(live, container, objects, conn) + return lw.w.MigrationSink(live, container, objects, conn, srcIdmap) } func ShiftIfNecessary(container container, srcIdmap *shared.IdmapSet) error { @@ -608,9 +604,17 @@ func (s rsyncStorageSourceDriver) Snapshots() []container { } func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn) error { - toSend := append([]container{s.container}, s.snapshots...) + toSend := []container{} + toSend = append(toSend, s.snapshots...) + toSend = append(toSend, s.container) for _, send := range toSend { + if send.IsSnapshot() { + if err := send.StorageStart(); err != nil { + return err + } + defer send.StorageStop() + } path := send.Path() if err := RsyncSend(shared.AddSlash(path), conn); err != nil { return err @@ -638,21 +642,83 @@ func rsyncMigrationSource(container container) (MigrationStorageSourceDriver, er return rsyncStorageSourceDriver{container, snapshots}, nil } -func rsyncMigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error { - /* the first object is the actual container */ - if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil { - return err - } +func rsyncMigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { + isDirBackend := container.Storage().GetStorageType() == storageTypeDir - if len(snapshots) > 0 { - err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700) - if err != nil { + if isDirBackend { + if len(snapshots) > 0 { + err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700) + if err != nil { + return err + } + } + for _, snap := range snapshots { + // TODO: we need to propagate snapshot configurations + // as well. Right now the container configuration is + // done through the initial migration post. Should we + // post the snapshots and their configs as well, or do + // it some other way? + name := container.Name() + shared.SnapshotDelimiter + snap + args := containerArgs{ + Ctype: cTypeSnapshot, + Config: container.LocalConfig(), + Profiles: container.Profiles(), + Ephemeral: container.IsEphemeral(), + Architecture: container.Architecture(), + Devices: container.LocalDevices(), + Name: name, + } + + s, err := containerCreateEmptySnapshot(container.Daemon(), args) + if err != nil { + return err + } + + if err := RsyncRecv(shared.AddSlash(s.Path()), conn); err != nil { + return err + } + + if err := ShiftIfNecessary(container, srcIdmap); err != nil { + return err + } + } + + if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil { return err } - } + } else { + for _, snap := range snapshots { + if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil { + return err + } - for _, snap := range snapshots { - if err := RsyncRecv(shared.AddSlash(snap.Path()), conn); err != nil { + if err := ShiftIfNecessary(container, srcIdmap); err != nil { + return err + } + + // TODO: we need to propagate snapshot configurations + // as well. Right now the container configuration is + // done through the initial migration post. Should we + // post the snapshots and their configs as well, or do + // it some other way? + name := container.Name() + shared.SnapshotDelimiter + snap + args := containerArgs{ + Ctype: cTypeSnapshot, + Config: container.LocalConfig(), + Profiles: container.Profiles(), + Ephemeral: container.IsEphemeral(), + Architecture: container.Architecture(), + Devices: container.LocalDevices(), + Name: name, + } + + _, err := containerCreateAsSnapshot(container.Daemon(), args, container) + if err != nil { + return err + } + } + + if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil { return err } } @@ -664,6 +730,10 @@ func rsyncMigrationSink(live bool, container container, snapshots []container, c } } + if err := ShiftIfNecessary(container, srcIdmap); err != nil { + return err + } + return nil } diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go index 79b1aa0..29a92fd 100644 --- a/lxd/storage_btrfs.go +++ b/lxd/storage_btrfs.go @@ -985,9 +985,9 @@ func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDrive return driver, nil } -func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error { +func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { if runningInUserns { - return rsyncMigrationSink(live, container, snapshots, conn) + return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap) } cName := container.Name() @@ -1057,7 +1057,28 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [ } for _, snap := range snapshots { - if err := btrfsRecv(containerPath(cName, true), snap.Path(), true); err != nil { + // TODO: we need to propagate snapshot configurations + // as well. Right now the container configuration is + // done through the initial migration post. Should we + // post the snapshots and their configs as well, or do + // it some other way? + name := container.Name() + shared.SnapshotDelimiter + snap + args := containerArgs{ + Ctype: cTypeSnapshot, + Config: container.LocalConfig(), + Profiles: container.Profiles(), + Ephemeral: container.IsEphemeral(), + Architecture: container.Architecture(), + Devices: container.LocalDevices(), + Name: name, + } + + s, err := containerCreateEmptySnapshot(container.Daemon(), args) + if err != nil { + return err + } + + if err := btrfsRecv(containerPath(cName, true), s.Path(), true); err != nil { return err } } diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go index 7cfe0a8..8c3d33d 100644 --- a/lxd/storage_dir.go +++ b/lxd/storage_dir.go @@ -282,6 +282,6 @@ func (s *storageDir) MigrationSource(container container) (MigrationStorageSourc return rsyncMigrationSource(container) } -func (s *storageDir) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error { - return rsyncMigrationSink(live, container, snapshots, conn) +func (s *storageDir) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { + return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap) } diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go index 2799032..de49050 100644 --- a/lxd/storage_lvm.go +++ b/lxd/storage_lvm.go @@ -972,6 +972,6 @@ func (s *storageLvm) MigrationSource(container container) (MigrationStorageSourc return rsyncMigrationSource(container) } -func (s *storageLvm) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error { - return rsyncMigrationSink(live, container, snapshots, conn) +func (s *storageLvm) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { + return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap) } diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go index 27ac0e1..c6fd3e6 100644 --- a/lxd/storage_zfs.go +++ b/lxd/storage_zfs.go @@ -79,18 +79,44 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) { // Things we don't need to care about func (s *storageZfs) ContainerStart(container container) error { - fs := fmt.Sprintf("containers/%s", container.Name()) - // Just in case the container filesystem got unmounted - if !shared.IsMountPoint(shared.VarPath(fs)) { - s.zfsMount(fs) + if !container.IsSnapshot() { + fs := fmt.Sprintf("containers/%s", container.Name()) + + // Just in case the container filesystem got unmounted + if !shared.IsMountPoint(shared.VarPath(fs)) { + s.zfsMount(fs) + } + } else { + /* the zfs CLI tool doesn't support mounting snapshots, but we + * can mount them with the syscall directly... + */ + fields := strings.SplitN(container.Name(), shared.SnapshotDelimiter, 2) + if len(fields) != 2 { + return fmt.Errorf("invalid snapshot name %s", container.Name()) + } + + src := fmt.Sprintf("containers/%s@%s", fields[0], fields[1]) + dest := shared.VarPath("snapshots", fields[0], fields[1]) + + return tryMount(src, dest, "zfs", 0, "") } return nil } func (s *storageZfs) ContainerStop(container container) error { - return nil + if !container.IsSnapshot() { + return nil + } + + fields := strings.SplitN(container.Name(), shared.SnapshotDelimiter, 2) + if len(fields) != 2 { + return fmt.Errorf("invalid snapshot name %s", container.Name()) + } + + p := shared.VarPath("snapshots", fields[0], fields[1]) + return tryUnmount(p, 0) } // Things we do have to care about @@ -1373,7 +1399,7 @@ func (s *storageZfs) MigrationSource(ct container) (MigrationStorageSourceDriver return &driver, nil } -func (s *storageZfs) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error { +func (s *storageZfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { zfsRecv := func(zfsName string) error { zfsFsName := fmt.Sprintf("%s/%s", s.zfsPool, zfsName) args := []string{"receive", "-F", "-u", zfsFsName} @@ -1420,18 +1446,38 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []c } for _, snap := range snapshots { - fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2) - name := fmt.Sprintf("containers/%s@snapshot-%s", fields[0], fields[1]) + // TODO: we need to propagate snapshot configurations + // as well. Right now the container configuration is + // done through the initial migration post. Should we + // post the snapshots and their configs as well, or do + // it some other way? + snapName := container.Name() + shared.SnapshotDelimiter + snap + args := containerArgs{ + Ctype: cTypeSnapshot, + Config: container.LocalConfig(), + Profiles: container.Profiles(), + Ephemeral: container.IsEphemeral(), + Architecture: container.Architecture(), + Devices: container.LocalDevices(), + Name: snapName, + } + + _, err := containerCreateEmptySnapshot(container.Daemon(), args) + if err != nil { + return err + } + + name := fmt.Sprintf("containers/%s@snapshot-%s", container.Name(), snap) if err := zfsRecv(name); err != nil { return err } - err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", fields[0])), 0700) + err = os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700) if err != nil { return err } - err = os.Symlink("on-zfs", shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", fields[0], fields[1]))) + err = os.Symlink("on-zfs", shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", container.Name(), snap))) if err != nil { return err } From 7e9cabb0dcf8d37790db723eb18be8c30f74451e Mon Sep 17 00:00:00 2001 From: Tycho Andersen <tycho.ander...@canonical.com> Date: Thu, 15 Sep 2016 20:51:31 +0000 Subject: [PATCH 2/2] container copy: preserve snapshot configuration Previously, we weren't preserving snapshot configuration (as noted in the TODO). Let's do that. Signed-off-by: Tycho Andersen <tycho.ander...@canonical.com> --- lxd/migrate.go | 51 +++++++++++++++++++++- lxd/migrate.pb.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++- lxd/migrate.proto | 54 +++++++++++++++-------- lxd/storage.go | 75 +++++++++++++++++--------------- lxd/storage_btrfs.go | 19 +------- lxd/storage_dir.go | 2 +- lxd/storage_lvm.go | 2 +- lxd/storage_zfs.go | 19 +------- test/suites/migration.sh | 4 ++ 9 files changed, 243 insertions(+), 94 deletions(-) diff --git a/lxd/migrate.go b/lxd/migrate.go index a5ad1ad..416438a 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -250,6 +250,37 @@ fi return err } +func snapshotToProtobuf(c container) *Snapshot { + config := []*Config{} + for k, v := range c.LocalConfig() { + kCopy := string(k) + vCopy := string(v) + config = append(config, &Config{Key: &kCopy, Value: &vCopy}) + } + + devices := []*Device{} + for name, d := range c.LocalDevices() { + props := []*Config{} + for k, v := range d { + kCopy := string(k) + vCopy := string(v) + props = append(props, &Config{Key: &kCopy, Value: &vCopy}) + } + + devices = append(devices, &Device{Name: &name, Config: props}) + } + + parts := strings.SplitN(c.Name(), shared.SnapshotDelimiter, 2) + isEphemeral := c.IsEphemeral() + return &Snapshot{ + Name: &parts[len(parts)-1], + Config: config, + Profiles: c.Profiles(), + Ephemeral: &isEphemeral, + Devices: devices, + } +} + func (s *migrationSourceWs) Do(migrateOp *operation) error { <-s.allConnected @@ -286,11 +317,11 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { /* the protocol says we have to send a header no matter what, so let's * do that, but then immediately send an error. */ - snapshots := []string{} + snapshots := []*Snapshot{} if fsErr == nil { fullSnaps := driver.Snapshots() for _, snap := range fullSnaps { - snapshots = append(snapshots, shared.ExtractSnapshotName(snap.Name())) + snapshots = append(snapshots, snapshotToProtobuf(snap)) } } @@ -600,6 +631,22 @@ func (c *migrationSink) do() error { */ fsTransfer := make(chan error) go func() { + snapshots := []*Snapshot{} + + /* Legacy: we only sent the snapshot names, so we just + * copy the container's config over, same as we used to + * do. + */ + if len(header.SnapshotNames) > 0 { + for _, name := range header.SnapshotNames { + base := snapshotToProtobuf(c.container) + base.Name = &name + snapshots = append(snapshots, base) + } + } else { + snapshots = header.Snapshots + } + if err := mySink(c.live, c.container, header.Snapshots, c.fsConn, srcIdmap); err != nil { fsTransfer <- err return diff --git a/lxd/migrate.pb.go b/lxd/migrate.pb.go index 751c0db..ae11974 100644 --- a/lxd/migrate.pb.go +++ b/lxd/migrate.pb.go @@ -10,6 +10,9 @@ It is generated from these files: It has these top-level messages: IDMapType + Config + Device + Snapshot MigrationHeader MigrationControl */ @@ -139,11 +142,108 @@ func (m *IDMapType) GetMaprange() int32 { return 0 } +type Config struct { + Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` + Value *string `protobuf:"bytes,2,req,name=value" json:"value,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} + +func (m *Config) GetKey() string { + if m != nil && m.Key != nil { + return *m.Key + } + return "" +} + +func (m *Config) GetValue() string { + if m != nil && m.Value != nil { + return *m.Value + } + return "" +} + +type Device struct { + Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` + Config []*Config `protobuf:"bytes,2,rep,name=config" json:"config,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} + +func (m *Device) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *Device) GetConfig() []*Config { + if m != nil { + return m.Config + } + return nil +} + +type Snapshot struct { + Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` + Config []*Config `protobuf:"bytes,2,rep,name=config" json:"config,omitempty"` + Profiles []string `protobuf:"bytes,3,rep,name=profiles" json:"profiles,omitempty"` + Ephemeral *bool `protobuf:"varint,4,req,name=ephemeral" json:"ephemeral,omitempty"` + Devices []*Device `protobuf:"bytes,5,rep,name=devices" json:"devices,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Snapshot) Reset() { *m = Snapshot{} } +func (m *Snapshot) String() string { return proto.CompactTextString(m) } +func (*Snapshot) ProtoMessage() {} + +func (m *Snapshot) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *Snapshot) GetConfig() []*Config { + if m != nil { + return m.Config + } + return nil +} + +func (m *Snapshot) GetProfiles() []string { + if m != nil { + return m.Profiles + } + return nil +} + +func (m *Snapshot) GetEphemeral() bool { + if m != nil && m.Ephemeral != nil { + return *m.Ephemeral + } + return false +} + +func (m *Snapshot) GetDevices() []*Device { + if m != nil { + return m.Devices + } + return nil +} + type MigrationHeader struct { Fs *MigrationFSType `protobuf:"varint,1,req,name=fs,enum=main.MigrationFSType" json:"fs,omitempty"` Criu *CRIUType `protobuf:"varint,2,opt,name=criu,enum=main.CRIUType" json:"criu,omitempty"` Idmap []*IDMapType `protobuf:"bytes,3,rep,name=idmap" json:"idmap,omitempty"` - Snapshots []string `protobuf:"bytes,4,rep,name=snapshots" json:"snapshots,omitempty"` + SnapshotNames []string `protobuf:"bytes,4,rep,name=snapshotNames" json:"snapshotNames,omitempty"` + Snapshots []*Snapshot `protobuf:"bytes,5,rep,name=snapshots" json:"snapshots,omitempty"` XXX_unrecognized []byte `json:"-"` } @@ -172,7 +272,14 @@ func (m *MigrationHeader) GetIdmap() []*IDMapType { return nil } -func (m *MigrationHeader) GetSnapshots() []string { +func (m *MigrationHeader) GetSnapshotNames() []string { + if m != nil { + return m.SnapshotNames + } + return nil +} + +func (m *MigrationHeader) GetSnapshots() []*Snapshot { if m != nil { return m.Snapshots } diff --git a/lxd/migrate.proto b/lxd/migrate.proto index bdf5608..6140fb4 100644 --- a/lxd/migrate.proto +++ b/lxd/migrate.proto @@ -1,35 +1,53 @@ package main; enum MigrationFSType { - RSYNC = 0; - BTRFS = 1; - ZFS = 2; + RSYNC = 0; + BTRFS = 1; + ZFS = 2; } enum CRIUType { - CRIU_RSYNC = 0; - PHAUL = 1; + CRIU_RSYNC = 0; + PHAUL = 1; } message IDMapType { - required bool isuid = 1; - required bool isgid = 2; - required int32 hostid = 3; - required int32 nsid = 4; - required int32 maprange = 5; + required bool isuid = 1; + required bool isgid = 2; + required int32 hostid = 3; + required int32 nsid = 4; + required int32 maprange = 5; } -message MigrationHeader { - required MigrationFSType fs = 1; - optional CRIUType criu = 2; - repeated IDMapType idmap = 3; +message Config { + required string key = 1; + required string value = 2; +} + +message Device { + required string name = 1; + repeated Config config = 2; +} - repeated string snapshots = 4; +message Snapshot { + required string name = 1; + repeated Config config = 2; + repeated string profiles = 3; + required bool ephemeral = 4; + repeated Device devices = 5; +} + +message MigrationHeader { + required MigrationFSType fs = 1; + optional CRIUType criu = 2; + repeated IDMapType idmap = 3; + repeated string snapshotNames = 4; + repeated Snapshot snapshots = 5; } message MigrationControl { - required bool success = 1; + required bool success = 1; - /* optional failure message if sending a failure */ - optional string message = 2; + /* optional failure message if sending a failure */ + optional string message = 2; } diff --git a/lxd/storage.go b/lxd/storage.go index 4af2b28..e58ee72 100644 --- a/lxd/storage.go +++ b/lxd/storage.go @@ -192,7 +192,7 @@ type storage interface { // already present on the target instance as an exercise for the // enterprising developer. MigrationSource(container container) (MigrationStorageSourceDriver, error) - MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error + MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error } func newStorage(d *Daemon, sType storageType) (storage, error) { @@ -556,11 +556,16 @@ func (lw *storageLogWrapper) MigrationSource(container container) (MigrationStor return lw.w.MigrationSource(container) } -func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { +func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { + objNames := []string{} + for _, obj := range objects { + objNames = append(objNames, obj.GetName()) + } + lw.log.Debug("MigrationSink", log.Ctx{ "live": live, "container": container.Name(), - "objects": objects, + "objects": objNames, "srcIdmap": *srcIdmap, }) @@ -642,7 +647,35 @@ func rsyncMigrationSource(container container) (MigrationStorageSourceDriver, er return rsyncStorageSourceDriver{container, snapshots}, nil } -func rsyncMigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { +func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) containerArgs { + config := map[string]string{} + + for _, ent := range snap.Config { + config[ent.GetKey()] = ent.GetValue() + } + + devices := shared.Devices{} + for _, ent := range snap.Devices { + props := map[string]string{} + for _, prop := range ent.Config { + props[prop.GetKey()] = prop.GetValue() + } + + devices[ent.GetName()] = props + } + + name := containerName + shared.SnapshotDelimiter + snap.GetName() + return containerArgs{ + Name: name, + Ctype: cTypeSnapshot, + Config: config, + Profiles: snap.Profiles, + Ephemeral: snap.GetEphemeral(), + Devices: devices, + } +} + +func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { isDirBackend := container.Storage().GetStorageType() == storageTypeDir if isDirBackend { @@ -653,22 +686,7 @@ func rsyncMigrationSink(live bool, container container, snapshots []string, conn } } for _, snap := range snapshots { - // TODO: we need to propagate snapshot configurations - // as well. Right now the container configuration is - // done through the initial migration post. Should we - // post the snapshots and their configs as well, or do - // it some other way? - name := container.Name() + shared.SnapshotDelimiter + snap - args := containerArgs{ - Ctype: cTypeSnapshot, - Config: container.LocalConfig(), - Profiles: container.Profiles(), - Ephemeral: container.IsEphemeral(), - Architecture: container.Architecture(), - Devices: container.LocalDevices(), - Name: name, - } - + args := snapshotProtobufToContainerArgs(container.Name(), snap) s, err := containerCreateEmptySnapshot(container.Daemon(), args) if err != nil { return err @@ -696,22 +714,7 @@ func rsyncMigrationSink(live bool, container container, snapshots []string, conn return err } - // TODO: we need to propagate snapshot configurations - // as well. Right now the container configuration is - // done through the initial migration post. Should we - // post the snapshots and their configs as well, or do - // it some other way? - name := container.Name() + shared.SnapshotDelimiter + snap - args := containerArgs{ - Ctype: cTypeSnapshot, - Config: container.LocalConfig(), - Profiles: container.Profiles(), - Ephemeral: container.IsEphemeral(), - Architecture: container.Architecture(), - Devices: container.LocalDevices(), - Name: name, - } - + args := snapshotProtobufToContainerArgs(container.Name(), snap) _, err := containerCreateAsSnapshot(container.Daemon(), args, container) if err != nil { return err diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go index 29a92fd..3f627de 100644 --- a/lxd/storage_btrfs.go +++ b/lxd/storage_btrfs.go @@ -985,7 +985,7 @@ func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDrive return driver, nil } -func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { +func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { if runningInUserns { return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap) } @@ -1057,22 +1057,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [ } for _, snap := range snapshots { - // TODO: we need to propagate snapshot configurations - // as well. Right now the container configuration is - // done through the initial migration post. Should we - // post the snapshots and their configs as well, or do - // it some other way? - name := container.Name() + shared.SnapshotDelimiter + snap - args := containerArgs{ - Ctype: cTypeSnapshot, - Config: container.LocalConfig(), - Profiles: container.Profiles(), - Ephemeral: container.IsEphemeral(), - Architecture: container.Architecture(), - Devices: container.LocalDevices(), - Name: name, - } - + args := snapshotProtobufToContainerArgs(container.Name(), snap) s, err := containerCreateEmptySnapshot(container.Daemon(), args) if err != nil { return err diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go index 8c3d33d..94eec94 100644 --- a/lxd/storage_dir.go +++ b/lxd/storage_dir.go @@ -282,6 +282,6 @@ func (s *storageDir) MigrationSource(container container) (MigrationStorageSourc return rsyncMigrationSource(container) } -func (s *storageDir) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { +func (s *storageDir) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap) } diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go index de49050..e348370 100644 --- a/lxd/storage_lvm.go +++ b/lxd/storage_lvm.go @@ -972,6 +972,6 @@ func (s *storageLvm) MigrationSource(container container) (MigrationStorageSourc return rsyncMigrationSource(container) } -func (s *storageLvm) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { +func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap) } diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go index c6fd3e6..4b9fa0f 100644 --- a/lxd/storage_zfs.go +++ b/lxd/storage_zfs.go @@ -1399,7 +1399,7 @@ func (s *storageZfs) MigrationSource(ct container) (MigrationStorageSourceDriver return &driver, nil } -func (s *storageZfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { +func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error { zfsRecv := func(zfsName string) error { zfsFsName := fmt.Sprintf("%s/%s", s.zfsPool, zfsName) args := []string{"receive", "-F", "-u", zfsFsName} @@ -1446,22 +1446,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []s } for _, snap := range snapshots { - // TODO: we need to propagate snapshot configurations - // as well. Right now the container configuration is - // done through the initial migration post. Should we - // post the snapshots and their configs as well, or do - // it some other way? - snapName := container.Name() + shared.SnapshotDelimiter + snap - args := containerArgs{ - Ctype: cTypeSnapshot, - Config: container.LocalConfig(), - Profiles: container.Profiles(), - Ephemeral: container.IsEphemeral(), - Architecture: container.Architecture(), - Devices: container.LocalDevices(), - Name: snapName, - } - + args := snapshotProtobufToContainerArgs(container.Name(), snap) _, err := containerCreateEmptySnapshot(container.Daemon(), args) if err != nil { return err diff --git a/test/suites/migration.sh b/test/suites/migration.sh index da0c928..0c865f6 100644 --- a/test/suites/migration.sh +++ b/test/suites/migration.sh @@ -12,8 +12,12 @@ test_migration() { lxc_remote init testimage nonlive # test moving snapshots + lxc_remote config set l1:nonlive user.tester foo lxc_remote snapshot l1:nonlive + lxc_remote config unset l1:nonlive user.tester lxc_remote move l1:nonlive l2: + lxc_remote config show l2:nonlive/snap0 | grep user.tester | grep foo + # FIXME: make this backend agnostic if [ "${LXD_BACKEND}" != "lvm" ]; then [ -d "${LXD2_DIR}/containers/nonlive/rootfs" ]
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel