The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4272
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 1551e180ab6d1808ab3cff15b4ac56088a4e9bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Feb 2018 18:21:09 -0500 Subject: [PATCH 1/7] shared/eagain: Make our EAGAIN code a new package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- shared/eagain/file.go | 52 ++++++++++++++++++++++++++++++++++++++++++ test/suites/static_analysis.sh | 1 + 2 files changed, 53 insertions(+) create mode 100644 shared/eagain/file.go diff --git a/shared/eagain/file.go b/shared/eagain/file.go new file mode 100644 index 000000000..9e3eac9c0 --- /dev/null +++ b/shared/eagain/file.go @@ -0,0 +1,52 @@ +package eagain + +import ( + "io" + "syscall" + + "github.com/lxc/lxd/shared" +) + +// Reader represents an io.Reader that handles EAGAIN +type Reader struct { + Reader io.Reader +} + +// Read behaves like io.Reader.Read but will retry on EAGAIN +func (er Reader) Read(p []byte) (int, error) { +again: + n, err := er.Reader.Read(p) + if err == nil { + return n, nil + } + + // keep retrying on EAGAIN + errno, ok := shared.GetErrno(err) + if ok && errno == syscall.EAGAIN { + goto again + } + + return n, err +} + +// Writer represents an io.Writer that handles EAGAIN +type Writer struct { + Writer io.Writer +} + +// Write behaves like io.Writer.Write but will retry on EAGAIN +func (ew Writer) Write(p []byte) (int, error) { +again: + n, err := ew.Writer.Write(p) + if err == nil { + return n, nil + } + + // keep retrying on EAGAIN + errno, ok := shared.GetErrno(err) + if ok && errno == syscall.EAGAIN { + goto again + } + + return n, err +} diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh index ce311c401..598d77113 100644 --- a/test/suites/static_analysis.sh +++ b/test/suites/static_analysis.sh @@ -83,6 +83,7 @@ test_static_analysis() { golint -set_exit_status shared/api/ golint -set_exit_status shared/cancel/ golint -set_exit_status shared/cmd/ + golint -set_exit_status shared/eagain/ golint -set_exit_status shared/gnuflag/ golint -set_exit_status shared/i18n/ golint -set_exit_status shared/ioprogress/ From f4dfb99569dd6ca434c66732e00a4a593c8e2abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Feb 2018 18:22:30 -0500 Subject: [PATCH 2/7] lxd/netcat: Port to using shared/eagain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/main_netcat.go | 46 +++------------------------------------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go index 2fac5b338..26e2e8b0c 100644 --- a/lxd/main_netcat.go +++ b/lxd/main_netcat.go @@ -6,9 +6,9 @@ import ( "net" "os" "sync" - "syscall" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/eagain" ) // Netcat is called with: @@ -50,7 +50,7 @@ func cmdNetcat(args *Args) error { wg.Add(1) go func() { - _, err := io.Copy(eagainWriter{os.Stdout}, eagainReader{conn}) + _, err := io.Copy(eagain.Writer{Writer: os.Stdout}, eagain.Reader{Reader: conn}) if err != nil { logFile.WriteString(fmt.Sprintf("Error while copying from stdout to unix domain socket \"%s\": %s.\n", args.Params[0], err)) } @@ -59,7 +59,7 @@ func cmdNetcat(args *Args) error { }() go func() { - _, err := io.Copy(eagainWriter{conn}, eagainReader{os.Stdin}) + _, err := io.Copy(eagain.Writer{Writer: conn}, eagain.Reader{Reader: os.Stdin}) if err != nil { logFile.WriteString(fmt.Sprintf("Error while copying from unix domain socket \"%s\" to stdin: %s.\n", args.Params[0], err)) } @@ -69,43 +69,3 @@ func cmdNetcat(args *Args) error { return nil } - -type eagainReader struct { - r io.Reader -} - -func (er eagainReader) Read(p []byte) (int, error) { -again: - n, err := er.r.Read(p) - if err == nil { - return n, nil - } - - // keep retrying on EAGAIN - errno, ok := shared.GetErrno(err) - if ok && errno == syscall.EAGAIN { - goto again - } - - return n, err -} - -type eagainWriter struct { - w io.Writer -} - -func (ew eagainWriter) Write(p []byte) (int, error) { -again: - n, err := ew.w.Write(p) - if err == nil { - return n, nil - } - - // keep retrying on EAGAIN - errno, ok := shared.GetErrno(err) - if ok && errno == syscall.EAGAIN { - goto again - } - - return n, err -} From 4de1243fe80fc9421d60c60c6ba176169195d68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Feb 2018 18:50:35 -0500 Subject: [PATCH 3/7] lxd: Move migration code to own package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- Makefile | 2 +- lxd/containers_post.go | 3 +- lxd/migrate.go | 104 ++++++++++--------------- lxd/{ => migration}/migrate.pb.go | 159 +++++++++++++++++++------------------- lxd/{ => migration}/migrate.proto | 2 +- lxd/migration/wsproto.go | 71 +++++++++++++++++ lxd/storage.go | 5 +- lxd/storage_btrfs.go | 9 ++- lxd/storage_ceph_migration.go | 7 +- lxd/storage_dir.go | 7 +- lxd/storage_lvm.go | 7 +- lxd/storage_migration.go | 5 +- lxd/storage_mock.go | 8 +- lxd/storage_zfs.go | 7 +- test/suites/static_analysis.sh | 1 + 15 files changed, 227 insertions(+), 170 deletions(-) rename lxd/{ => migration}/migrate.pb.go (70%) rename lxd/{ => migration}/migrate.proto (99%) create mode 100644 lxd/migration/wsproto.go diff --git a/Makefile b/Makefile index 891088074..9d7a37226 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ debug: # it's not a default build step. .PHONY: protobuf protobuf: - protoc --go_out=. ./lxd/migrate.proto + protoc --go_out=. ./lxd/migration/migrate.proto .PHONY: check check: default diff --git a/lxd/containers_post.go b/lxd/containers_post.go index 76d2a615a..55875277f 100644 --- a/lxd/containers_post.go +++ b/lxd/containers_post.go @@ -12,6 +12,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -316,7 +317,7 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response { return InternalError(err) } - if ps.MigrationType() == MigrationFSType_RSYNC { + if ps.MigrationType() == migration.MigrationFSType_RSYNC { c, err = containerCreateFromImage(d.State(), args, req.Source.BaseImage) if err != nil { return InternalError(err) diff --git a/lxd/migrate.go b/lxd/migrate.go index 6bedadcb1..62078766e 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -24,6 +24,7 @@ import ( "github.com/gorilla/websocket" "gopkg.in/lxc/go-lxc.v2" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -63,36 +64,17 @@ func (c *migrationFields) send(m proto.Message) error { */ c.controlLock.Lock() defer c.controlLock.Unlock() - w, err := c.controlConn.NextWriter(websocket.BinaryMessage) - if err != nil { - return err - } - defer w.Close() - data, err := proto.Marshal(m) + err := migration.ProtoSend(c.controlConn, m) if err != nil { return err } - return shared.WriteAll(w, data) + return nil } func (c *migrationFields) recv(m proto.Message) error { - mt, r, err := c.controlConn.NextReader() - if err != nil { - return err - } - - if mt != websocket.BinaryMessage { - return fmt.Errorf("Only binary messages allowed") - } - - buf, err := ioutil.ReadAll(r) - if err != nil { - return err - } - - return proto.Unmarshal(buf, m) + return migration.ProtoRecv(c.controlConn, m) } func (c *migrationFields) disconnect() { @@ -123,26 +105,20 @@ func (c *migrationFields) disconnect() { } func (c *migrationFields) sendControl(err error) { - message := "" - if err != nil { - message = err.Error() - } + c.controlLock.Lock() + defer c.controlLock.Unlock() - msg := MigrationControl{ - Success: proto.Bool(err == nil), - Message: proto.String(message), - } - c.send(&msg) + migration.ProtoSendControl(c.controlConn, err) if err != nil { c.disconnect() } } -func (c *migrationFields) controlChannel() <-chan MigrationControl { - ch := make(chan MigrationControl) +func (c *migrationFields) controlChannel() <-chan migration.MigrationControl { + ch := make(chan migration.MigrationControl) go func() { - msg := MigrationControl{} + msg := migration.MigrationControl{} err := c.recv(&msg) if err != nil { logger.Debugf("Got error reading migration control socket %s", err) @@ -321,24 +297,24 @@ fi return err } -func snapshotToProtobuf(c container) *Snapshot { - config := []*Config{} +func snapshotToProtobuf(c container) *migration.Snapshot { + config := []*migration.Config{} for k, v := range c.LocalConfig() { kCopy := string(k) vCopy := string(v) - config = append(config, &Config{Key: &kCopy, Value: &vCopy}) + config = append(config, &migration.Config{Key: &kCopy, Value: &vCopy}) } - devices := []*Device{} + devices := []*migration.Device{} for name, d := range c.LocalDevices() { - props := []*Config{} + props := []*migration.Config{} for k, v := range d { kCopy := string(k) vCopy := string(v) - props = append(props, &Config{Key: &kCopy, Value: &vCopy}) + props = append(props, &migration.Config{Key: &kCopy, Value: &vCopy}) } - devices = append(devices, &Device{Name: &name, Config: props}) + devices = append(devices, &migration.Device{Name: &name, Config: props}) } parts := strings.SplitN(c.Name(), shared.SnapshotDelimiter, 2) @@ -346,7 +322,7 @@ func snapshotToProtobuf(c container) *Snapshot { arch := int32(c.Architecture()) stateful := c.IsStateful() - return &Snapshot{ + return &migration.Snapshot{ Name: &parts[len(parts)-1], LocalConfig: config, Profiles: c.Profiles(), @@ -444,7 +420,7 @@ func readCriuStatsDump(path string) (uint64, uint64, error) { size := binary.LittleEndian.Uint32(in[8:12]) logger.Debugf("stats-dump payload size %d", size) - statsEntry := &StatsEntry{} + statsEntry := &migration.StatsEntry{} if err = proto.Unmarshal(in[12:12+size], statsEntry); err != nil { logger.Errorf("Failed to parse CRIU's 'stats-dump' file: %s", err.Error()) return 0, 0, err @@ -534,7 +510,7 @@ func (s *migrationSourceWs) preDumpLoop(args *preDumpLoopArgs) (bool, error) { // expects a message to know if this was the // last pre-dump logger.Debugf("Sending another header") - sync := MigrationSync{ + sync := migration.MigrationSync{ FinalPreDump: proto.Bool(final), } @@ -557,11 +533,11 @@ func (s *migrationSourceWs) preDumpLoop(args *preDumpLoopArgs) (bool, error) { func (s *migrationSourceWs) Do(migrateOp *operation) error { <-s.allConnected - criuType := CRIUType_CRIU_RSYNC.Enum() + criuType := migration.CRIUType_CRIU_RSYNC.Enum() if !s.live { criuType = nil if s.container.IsRunning() { - criuType = CRIUType_NONE.Enum() + criuType = migration.CRIUType_NONE.Enum() } } @@ -575,7 +551,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { defer s.container.StorageStop() } - idmaps := make([]*IDMapType, 0) + idmaps := make([]*migration.IDMapType, 0) idmapset, err := s.container.IdmapSet() if err != nil { @@ -584,7 +560,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { if idmapset != nil { for _, ctnIdmap := range idmapset.Idmap { - idmap := IDMapType{ + idmap := migration.IDMapType{ Isuid: proto.Bool(ctnIdmap.Isuid), Isgid: proto.Bool(ctnIdmap.Isgid), Hostid: proto.Int32(int32(ctnIdmap.Hostid)), @@ -598,7 +574,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { driver, fsErr := s.container.Storage().MigrationSource(s.container, s.containerOnly) - snapshots := []*Snapshot{} + snapshots := []*migration.Snapshot{} snapshotNames := []string{} // Only send snapshots when requested. if !s.containerOnly { @@ -620,7 +596,7 @@ 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. myType := s.container.Storage().MigrationType() - header := MigrationHeader{ + header := migration.MigrationHeader{ Fs: &myType, Criu: criuType, Idmap: idmaps, @@ -648,7 +624,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { bwlimit := "" if *header.Fs != myType { - myType = MigrationFSType_RSYNC + myType = migration.MigrationFSType_RSYNC header.Fs = &myType driver, _ = rsyncMigrationSource(s.container, s.containerOnly) @@ -693,7 +669,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { if s.live { if header.Criu == nil { return abort(fmt.Errorf("Got no CRIU socket type for live migration")) - } else if *header.Criu != CRIUType_CRIU_RSYNC { + } else if *header.Criu != migration.CRIUType_CRIU_RSYNC { return abort(fmt.Errorf("Formats other than criu rsync not understood")) } @@ -865,7 +841,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { } } - if s.live || (header.Criu != nil && *header.Criu == CRIUType_NONE) { + if s.live || (header.Criu != nil && *header.Criu == migration.CRIUType_NONE) { err = driver.SendAfterCheckpoint(s.fsConn, bwlimit) if err != nil { return abort(err) @@ -874,7 +850,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error { driver.Cleanup() - msg := MigrationControl{} + msg := migration.MigrationControl{} err = s.recv(&msg) if err != nil { s.disconnect() @@ -1091,7 +1067,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { controller = c.dest.sendControl } - header := MigrationHeader{} + header := migration.MigrationHeader{} if err := receiver(&header); err != nil { controller(err) return err @@ -1102,9 +1078,9 @@ func (c *migrationSink) Do(migrateOp *operation) error { live = c.dest.live } - criuType := CRIUType_CRIU_RSYNC.Enum() - if header.Criu != nil && *header.Criu == CRIUType_NONE { - criuType = CRIUType_NONE.Enum() + criuType := migration.CRIUType_CRIU_RSYNC.Enum() + if header.Criu != nil && *header.Criu == migration.CRIUType_NONE { + criuType = migration.CRIUType_NONE.Enum() } else { if !live { criuType = nil @@ -1113,7 +1089,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { mySink := c.src.container.Storage().MigrationSink myType := c.src.container.Storage().MigrationType() - resp := MigrationHeader{ + resp := migration.MigrationHeader{ Fs: &myType, Criu: criuType, } @@ -1122,7 +1098,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { // we have to use rsync. if *header.Fs != *resp.Fs { mySink = rsyncMigrationSink - myType = MigrationFSType_RSYNC + myType = migration.MigrationFSType_RSYNC resp.Fs = &myType } @@ -1163,7 +1139,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { */ fsTransfer := make(chan error) go func() { - snapshots := []*Snapshot{} + snapshots := []*migration.Snapshot{} /* Legacy: we only sent the snapshot names, so we just * copy the container's config over, same as we used to @@ -1191,7 +1167,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { sendFinalFsDelta = true } - if criuType != nil && *criuType == CRIUType_NONE { + if criuType != nil && *criuType == migration.CRIUType_NONE { sendFinalFsDelta = true } @@ -1229,7 +1205,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { criuConn = c.src.criuConn } - sync := &MigrationSync{ + sync := &migration.MigrationSync{ FinalPreDump: proto.Bool(false), } @@ -1307,7 +1283,7 @@ func (c *migrationSink) Do(migrateOp *operation) error { restore <- nil }(c) - var source <-chan MigrationControl + var source <-chan migration.MigrationControl if c.push { source = c.dest.controlChannel() } else { diff --git a/lxd/migrate.pb.go b/lxd/migration/migrate.pb.go similarity index 70% rename from lxd/migrate.pb.go rename to lxd/migration/migrate.pb.go index 85368b02d..9e9f4a465 100644 --- a/lxd/migrate.pb.go +++ b/lxd/migration/migrate.pb.go @@ -1,12 +1,11 @@ -// Code generated by protoc-gen-go. -// source: lxd/migrate.proto -// DO NOT EDIT! +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: lxd/migration/migrate.proto /* -Package main is a generated protocol buffer package. +Package migration is a generated protocol buffer package. It is generated from these files: - lxd/migrate.proto + lxd/migration/migrate.proto It has these top-level messages: IDMapType @@ -20,7 +19,7 @@ It has these top-level messages: RestoreStatsEntry StatsEntry */ -package main +package migration import proto "github.com/golang/protobuf/proto" import fmt "fmt" @@ -279,8 +278,8 @@ func (m *Snapshot) GetStateful() bool { } 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"` + Fs *MigrationFSType `protobuf:"varint,1,req,name=fs,enum=migration.MigrationFSType" json:"fs,omitempty"` + Criu *CRIUType `protobuf:"varint,2,opt,name=criu,enum=migration.CRIUType" json:"criu,omitempty"` Idmap []*IDMapType `protobuf:"bytes,3,rep,name=idmap" json:"idmap,omitempty"` SnapshotNames []string `protobuf:"bytes,4,rep,name=snapshotNames" json:"snapshotNames,omitempty"` Snapshots []*Snapshot `protobuf:"bytes,5,rep,name=snapshots" json:"snapshots,omitempty"` @@ -551,78 +550,78 @@ func (m *StatsEntry) GetRestore() *RestoreStatsEntry { } func init() { - proto.RegisterType((*IDMapType)(nil), "main.IDMapType") - proto.RegisterType((*Config)(nil), "main.Config") - proto.RegisterType((*Device)(nil), "main.Device") - proto.RegisterType((*Snapshot)(nil), "main.Snapshot") - proto.RegisterType((*MigrationHeader)(nil), "main.MigrationHeader") - proto.RegisterType((*MigrationControl)(nil), "main.MigrationControl") - proto.RegisterType((*MigrationSync)(nil), "main.MigrationSync") - proto.RegisterType((*DumpStatsEntry)(nil), "main.dump_stats_entry") - proto.RegisterType((*RestoreStatsEntry)(nil), "main.restore_stats_entry") - proto.RegisterType((*StatsEntry)(nil), "main.stats_entry") - proto.RegisterEnum("main.MigrationFSType", MigrationFSType_name, MigrationFSType_value) - proto.RegisterEnum("main.CRIUType", CRIUType_name, CRIUType_value) -} - -func init() { proto.RegisterFile("lxd/migrate.proto", fileDescriptor0) } + proto.RegisterType((*IDMapType)(nil), "migration.IDMapType") + proto.RegisterType((*Config)(nil), "migration.Config") + proto.RegisterType((*Device)(nil), "migration.Device") + proto.RegisterType((*Snapshot)(nil), "migration.Snapshot") + proto.RegisterType((*MigrationHeader)(nil), "migration.MigrationHeader") + proto.RegisterType((*MigrationControl)(nil), "migration.MigrationControl") + proto.RegisterType((*MigrationSync)(nil), "migration.MigrationSync") + proto.RegisterType((*DumpStatsEntry)(nil), "migration.dump_stats_entry") + proto.RegisterType((*RestoreStatsEntry)(nil), "migration.restore_stats_entry") + proto.RegisterType((*StatsEntry)(nil), "migration.stats_entry") + proto.RegisterEnum("migration.MigrationFSType", MigrationFSType_name, MigrationFSType_value) + proto.RegisterEnum("migration.CRIUType", CRIUType_name, CRIUType_value) +} + +func init() { proto.RegisterFile("lxd/migration/migrate.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 888 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x94, 0xcd, 0x6e, 0xdb, 0x46, - 0x10, 0xc7, 0x4b, 0x89, 0xb2, 0xc5, 0xa1, 0x2c, 0x2b, 0xdb, 0x34, 0x60, 0x8b, 0x16, 0x55, 0x19, - 0x1b, 0x10, 0x8c, 0xc2, 0x31, 0x94, 0x53, 0x8f, 0xb5, 0x5c, 0x23, 0x01, 0x12, 0xd7, 0x58, 0x39, - 0x28, 0xda, 0x0b, 0xb1, 0x25, 0x87, 0xf2, 0xc2, 0xfc, 0xc2, 0x2e, 0x65, 0x57, 0xbe, 0xf4, 0x69, - 0xfa, 0x48, 0x7d, 0x90, 0x5e, 0x7b, 0x2a, 0x76, 0x96, 0xa4, 0xa5, 0x22, 0xb9, 0xed, 0xfc, 0xe6, - 0xcf, 0x99, 0x9d, 0x0f, 0x2e, 0x3c, 0xcb, 0xfe, 0x48, 0x5e, 0xe5, 0x72, 0xa5, 0x44, 0x8d, 0xa7, - 0x95, 0x2a, 0xeb, 0x92, 0xb9, 0xb9, 0x90, 0x45, 0xf8, 0x27, 0x78, 0x6f, 0x2f, 0xde, 0x8b, 0xea, - 0x66, 0x53, 0x21, 0x7b, 0x0e, 0x03, 0xa9, 0xd7, 0x32, 0x09, 0x9c, 0x69, 0x6f, 0x36, 0xe4, 0xd6, - 0xb0, 0x74, 0x25, 0x93, 0xa0, 0xd7, 0xd2, 0x95, 0x4c, 0xd8, 0x0b, 0xd8, 0xbb, 0x2d, 0x75, 0x2d, - 0x93, 0xa0, 0x3f, 0xed, 0xcd, 0x06, 0xbc, 0xb1, 0x18, 0x03, 0xb7, 0xd0, 0x32, 0x09, 0x5c, 0xa2, - 0x74, 0x66, 0x5f, 0xc1, 0x30, 0x17, 0x95, 0x12, 0xc5, 0x0a, 0x83, 0x01, 0xf1, 0xce, 0x0e, 0xcf, - 0x60, 0x6f, 0x51, 0x16, 0xa9, 0x5c, 0xb1, 0x09, 0xf4, 0xef, 0x70, 0x43, 0xb9, 0x3d, 0x6e, 0x8e, - 0x26, 0xf3, 0xbd, 0xc8, 0xd6, 0x48, 0x99, 0x3d, 0x6e, 0x8d, 0xf0, 0x1c, 0xf6, 0x2e, 0xf0, 0x5e, - 0xc6, 0x48, 0xb9, 0x44, 0x8e, 0xcd, 0x27, 0x74, 0x66, 0x47, 0xb0, 0x17, 0x53, 0xbc, 0xa0, 0x37, - 0xed, 0xcf, 0xfc, 0xf9, 0xe8, 0xd4, 0xd4, 0x79, 0x6a, 0x73, 0xf0, 0xc6, 0x17, 0xfe, 0xeb, 0xc0, - 0x70, 0x59, 0x88, 0x4a, 0xdf, 0x96, 0xf5, 0x47, 0xc3, 0x9c, 0x82, 0x9f, 0x95, 0xb1, 0xc8, 0x16, - 0x9f, 0x8e, 0xb5, 0x2d, 0x30, 0x25, 0x56, 0xaa, 0x4c, 0x65, 0x86, 0x3a, 0xe8, 0x4f, 0xfb, 0x33, - 0x8f, 0x77, 0x36, 0xfb, 0x1a, 0x3c, 0xac, 0x6e, 0x31, 0x47, 0x25, 0x32, 0xea, 0xcb, 0x90, 0x3f, - 0x01, 0x76, 0x06, 0x23, 0x0a, 0x64, 0x6b, 0xd2, 0xc1, 0x60, 0x3b, 0x95, 0x85, 0x7c, 0x47, 0xc1, - 0x42, 0x18, 0x09, 0x15, 0xdf, 0xca, 0x1a, 0xe3, 0x7a, 0xad, 0x30, 0xd8, 0xa3, 0x96, 0xee, 0x30, - 0x73, 0x1f, 0x5d, 0x8b, 0x1a, 0xd3, 0x75, 0x16, 0xec, 0x53, 0xca, 0xce, 0x0e, 0xff, 0x71, 0xe0, - 0xf0, 0x3d, 0xed, 0x82, 0x2c, 0x8b, 0x37, 0x28, 0x12, 0x54, 0xec, 0x18, 0x7a, 0xa9, 0xa6, 0x0e, - 0x8c, 0xe7, 0x5f, 0xd8, 0xdc, 0x9d, 0xe4, 0x72, 0x69, 0xb6, 0x83, 0xf7, 0x52, 0x93, 0xda, 0x8d, - 0x95, 0x5c, 0x07, 0xbd, 0xa9, 0x33, 0x1b, 0xcf, 0xc7, 0x4d, 0x3f, 0xf8, 0xdb, 0x0f, 0xa4, 0x20, - 0x1f, 0x3b, 0x86, 0x81, 0x4c, 0x72, 0x51, 0x51, 0x1f, 0xfc, 0xf9, 0xa1, 0x15, 0x75, 0x5b, 0xc6, - 0xad, 0x97, 0x1d, 0xc1, 0x81, 0x6e, 0x26, 0x70, 0x25, 0x72, 0xd4, 0x81, 0x4b, 0x6d, 0xdb, 0x85, - 0xec, 0x7b, 0xf0, 0x5a, 0xd0, 0xb6, 0xa6, 0xc9, 0xda, 0x8e, 0x8f, 0x3f, 0x09, 0x58, 0x00, 0xfb, - 0x95, 0xc2, 0x64, 0x9d, 0x57, 0xc1, 0xfe, 0xd4, 0x99, 0x0d, 0x79, 0x6b, 0x86, 0x97, 0x30, 0xe9, - 0xea, 0x59, 0x94, 0x45, 0xad, 0xca, 0xcc, 0xa8, 0xf5, 0x3a, 0x8e, 0x51, 0xeb, 0x66, 0xe1, 0x5b, - 0xd3, 0x78, 0x72, 0xd4, 0x5a, 0xac, 0x90, 0x2a, 0xf5, 0x78, 0x6b, 0x86, 0xaf, 0xe1, 0xa0, 0x8b, - 0xb3, 0xdc, 0x14, 0xb1, 0x19, 0x46, 0x2a, 0x0b, 0x91, 0x5d, 0x2b, 0xbc, 0x30, 0x79, 0x6d, 0xa4, - 0x1d, 0x16, 0xfe, 0xd5, 0x87, 0x89, 0xb9, 0x45, 0x64, 0x46, 0xa0, 0x23, 0x2c, 0x6a, 0xb5, 0x61, - 0x2f, 0xe1, 0x20, 0x55, 0x88, 0x8f, 0xb2, 0x58, 0x45, 0xb5, 0x6c, 0xd6, 0xef, 0x80, 0x8f, 0x5a, - 0x78, 0x23, 0x73, 0x64, 0xdf, 0x82, 0x9f, 0xaa, 0xf2, 0x11, 0x0b, 0x2b, 0xe9, 0x91, 0x04, 0x2c, - 0x22, 0xc1, 0x77, 0x30, 0xca, 0x31, 0xa7, 0xe0, 0xa4, 0xe8, 0x93, 0xc2, 0x6f, 0x18, 0x49, 0x5e, - 0xc2, 0x41, 0x8e, 0xf9, 0x83, 0x92, 0x35, 0x5a, 0x8d, 0x6b, 0x13, 0xb5, 0xb0, 0x15, 0x55, 0x62, - 0x85, 0x3a, 0xd2, 0xb1, 0x28, 0x0a, 0x4c, 0xe8, 0x3f, 0x75, 0xf9, 0x88, 0xe0, 0xd2, 0x32, 0x76, - 0x06, 0xcf, 0x1b, 0xd1, 0x9d, 0xac, 0x2a, 0x4c, 0xa2, 0x4a, 0x28, 0x2c, 0x6a, 0x5a, 0x40, 0x97, - 0x33, 0xab, 0xb5, 0xae, 0x6b, 0xf2, 0x3c, 0x85, 0x35, 0x99, 0x6a, 0x2c, 0x68, 0x17, 0xdb, 0xb0, - 0xbf, 0x58, 0x66, 0x44, 0x52, 0xe5, 0xa2, 0x8a, 0x14, 0xea, 0x32, 0xbb, 0xc7, 0x60, 0x38, 0x75, - 0xcc, 0x05, 0x09, 0x72, 0xcb, 0xd8, 0x37, 0x00, 0x36, 0x52, 0x26, 0x1e, 0x37, 0x81, 0x47, 0x61, - 0x3c, 0x22, 0xef, 0xc4, 0xe3, 0xa6, 0x75, 0x47, 0x95, 0xac, 0x50, 0x07, 0x30, 0x75, 0x5a, 0xf7, - 0xb5, 0x01, 0xec, 0x08, 0xc6, 0x9d, 0x3b, 0xfa, 0x7d, 0x9d, 0xea, 0xc0, 0x27, 0xc9, 0xa8, 0x95, - 0x9c, 0xaf, 0x53, 0x1d, 0xfe, 0xed, 0xc0, 0xe7, 0x0a, 0x75, 0x5d, 0x2a, 0xdc, 0x19, 0xd5, 0xb1, - 0xfd, 0x5a, 0x47, 0x71, 0x99, 0x9b, 0x92, 0xed, 0x03, 0xe9, 0x72, 0x5b, 0xdb, 0xa2, 0x81, 0xec, - 0x04, 0x9e, 0xed, 0xb6, 0x27, 0x2e, 0x1f, 0x68, 0x64, 0x2e, 0x3f, 0xdc, 0xee, 0xcd, 0xa2, 0x7c, - 0x30, 0x73, 0x4b, 0x4b, 0x75, 0xd7, 0x0d, 0xbf, 0x99, 0x5b, 0xc3, 0xda, 0xd1, 0xb6, 0x97, 0xd9, - 0x1a, 0x9b, 0xdf, 0x30, 0x92, 0x74, 0x17, 0x6b, 0xa0, 0x19, 0x9b, 0xd3, 0x5d, 0x8c, 0x37, 0x30, - 0x2c, 0xc0, 0xdf, 0x2e, 0xe7, 0x04, 0xdc, 0xc4, 0xae, 0xaa, 0x33, 0xf3, 0xe7, 0x2f, 0xec, 0xef, - 0xf4, 0xff, 0xfd, 0xe4, 0xa4, 0x61, 0xaf, 0x61, 0xbf, 0x89, 0x4d, 0x7f, 0x82, 0x3f, 0xff, 0xd2, - 0xca, 0x3f, 0xd2, 0x26, 0xde, 0x2a, 0x4f, 0x7e, 0xd8, 0x7a, 0x5f, 0xec, 0xe3, 0xc1, 0x3c, 0x18, - 0xf0, 0xe5, 0xaf, 0x57, 0x8b, 0xc9, 0x67, 0xe6, 0x78, 0x7e, 0xc3, 0x2f, 0x97, 0x13, 0x87, 0xed, - 0x43, 0xff, 0xb7, 0xcb, 0xe5, 0xa4, 0x67, 0x0e, 0xfc, 0xfc, 0x62, 0xd2, 0x3f, 0x79, 0x05, 0xc3, - 0xf6, 0x39, 0x61, 0x63, 0x00, 0x73, 0x8e, 0xb6, 0x3e, 0xbc, 0x7e, 0xf3, 0xe3, 0x87, 0x77, 0x13, - 0x87, 0x0d, 0xc1, 0xbd, 0xfa, 0xf9, 0xea, 0xa7, 0x49, 0xef, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x0d, 0x9a, 0xe8, 0xf0, 0xda, 0x06, 0x00, 0x00, + // 894 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0x5b, 0x6f, 0xdb, 0x36, + 0x14, 0x9e, 0x64, 0xf9, 0xa2, 0x23, 0x3b, 0x75, 0xd9, 0x60, 0x10, 0xda, 0x5d, 0x3c, 0xb5, 0xc3, + 0x3c, 0x3f, 0x24, 0x9d, 0x8b, 0x01, 0xdb, 0xe3, 0xe2, 0x2c, 0x6b, 0x81, 0x36, 0x0b, 0xe8, 0x14, + 0xc3, 0xf6, 0x22, 0x70, 0xd2, 0x91, 0x43, 0x44, 0x37, 0x90, 0x72, 0x52, 0xe7, 0x65, 0x2f, 0xdb, + 0x4f, 0xd9, 0x4f, 0xda, 0xff, 0x19, 0x48, 0x4a, 0x8a, 0xbc, 0x15, 0xe8, 0x1b, 0xcf, 0x77, 0x3e, + 0x9e, 0xef, 0xdc, 0x48, 0x78, 0x92, 0xbe, 0x8b, 0x8f, 0x33, 0xbe, 0x11, 0xac, 0xe2, 0x45, 0x5e, + 0x9f, 0xf0, 0xa8, 0x14, 0x45, 0x55, 0x10, 0xb7, 0x75, 0x04, 0x7f, 0x80, 0xfb, 0xea, 0xf4, 0x0d, + 0x2b, 0x2f, 0x77, 0x25, 0x92, 0x43, 0xe8, 0x73, 0xb9, 0xe5, 0xb1, 0x6f, 0xcd, 0xec, 0xf9, 0x88, + 0x1a, 0xc3, 0xa0, 0x1b, 0x1e, 0xfb, 0x76, 0x83, 0x6e, 0x78, 0x4c, 0x3e, 0x86, 0xc1, 0x55, 0x21, + 0x2b, 0x1e, 0xfb, 0xbd, 0x99, 0x3d, 0xef, 0xd3, 0xda, 0x22, 0x04, 0x9c, 0x5c, 0xf2, 0xd8, 0x77, + 0x34, 0xaa, 0xcf, 0xe4, 0x31, 0x8c, 0x32, 0x56, 0x0a, 0x96, 0x6f, 0xd0, 0xef, 0x6b, 0xbc, 0xb5, + 0x83, 0xe7, 0x30, 0x58, 0x15, 0x79, 0xc2, 0x37, 0x64, 0x0a, 0xbd, 0x6b, 0xdc, 0x69, 0x6d, 0x97, + 0xaa, 0xa3, 0x52, 0xbe, 0x61, 0xe9, 0x16, 0xb5, 0xb2, 0x4b, 0x8d, 0x11, 0xfc, 0x04, 0x83, 0x53, + 0xbc, 0xe1, 0x11, 0x6a, 0x2d, 0x96, 0x61, 0x7d, 0x45, 0x9f, 0xc9, 0xd7, 0x30, 0x88, 0x74, 0x3c, + 0xdf, 0x9e, 0xf5, 0xe6, 0xde, 0xf2, 0xe1, 0x51, 0x5b, 0xec, 0x91, 0x11, 0xa2, 0x35, 0x21, 0xf8, + 0xd3, 0x86, 0xd1, 0x3a, 0x67, 0xa5, 0xbc, 0x2a, 0xaa, 0xf7, 0xc6, 0x7a, 0x01, 0x5e, 0x5a, 0x44, + 0x2c, 0x5d, 0x7d, 0x20, 0x60, 0x97, 0xa5, 0x8a, 0x2d, 0x45, 0x91, 0xf0, 0x14, 0xa5, 0xdf, 0x9b, + 0xf5, 0xe6, 0x2e, 0x6d, 0x6d, 0xf2, 0x09, 0xb8, 0x58, 0x5e, 0x61, 0x86, 0x82, 0xa5, 0xba, 0x43, + 0x23, 0x7a, 0x0f, 0x90, 0x6f, 0x61, 0xac, 0x03, 0x99, 0xea, 0xa4, 0xdf, 0xff, 0x9f, 0x9e, 0xf1, + 0xd0, 0x3d, 0x1a, 0x09, 0x60, 0xcc, 0x44, 0x74, 0xc5, 0x2b, 0x8c, 0xaa, 0xad, 0x40, 0x7f, 0xa0, + 0x3b, 0xbc, 0x87, 0xa9, 0xa4, 0x64, 0xc5, 0x2a, 0x4c, 0xb6, 0xa9, 0x3f, 0xd4, 0xba, 0xad, 0x1d, + 0xfc, 0x65, 0xc3, 0x83, 0x37, 0x8d, 0xc4, 0x4b, 0x64, 0x31, 0x0a, 0xb2, 0x00, 0x3b, 0x91, 0xba, + 0x17, 0x07, 0xcb, 0xc7, 0x9d, 0x04, 0x5a, 0xde, 0xd9, 0x5a, 0x6d, 0x0c, 0xb5, 0x13, 0x49, 0xbe, + 0x02, 0x27, 0x12, 0x7c, 0xeb, 0xdb, 0x33, 0x6b, 0x7e, 0xb0, 0x7c, 0xd4, 0x6d, 0x0f, 0x7d, 0xf5, + 0x56, 0xd3, 0x34, 0x81, 0x2c, 0xa0, 0xcf, 0xe3, 0x8c, 0x95, 0xba, 0x2d, 0xde, 0xf2, 0xb0, 0xc3, + 0x6c, 0x77, 0x90, 0x1a, 0x0a, 0x79, 0x06, 0x13, 0x59, 0x8f, 0xe6, 0x9c, 0x65, 0x28, 0x7d, 0x47, + 0xb7, 0x72, 0x1f, 0x24, 0xdf, 0x80, 0xdb, 0x00, 0x4d, 0xbb, 0xba, 0xfa, 0xcd, 0x70, 0xe9, 0x3d, + 0x8b, 0xf8, 0x30, 0x2c, 0x05, 0xc6, 0xdb, 0xac, 0xf4, 0x87, 0x33, 0x6b, 0x3e, 0xa2, 0x8d, 0x19, + 0x9c, 0xc1, 0xb4, 0x2d, 0x6f, 0x55, 0xe4, 0x95, 0x28, 0x52, 0xc5, 0x96, 0xdb, 0x28, 0x42, 0x29, + 0xeb, 0x37, 0xd1, 0x98, 0xca, 0x93, 0xa1, 0x94, 0x6c, 0x83, 0xba, 0x70, 0x97, 0x36, 0x66, 0xf0, + 0x02, 0x26, 0x6d, 0x9c, 0xf5, 0x2e, 0x8f, 0xd4, 0x80, 0x12, 0x9e, 0xb3, 0xf4, 0x42, 0xe0, 0xa9, + 0xd2, 0x35, 0x91, 0xf6, 0xb0, 0xe0, 0xef, 0x1e, 0x4c, 0x55, 0x16, 0xa1, 0x1a, 0x8b, 0x0c, 0x31, + 0xaf, 0xc4, 0x8e, 0x3c, 0x85, 0x49, 0x22, 0x10, 0xef, 0x78, 0xbe, 0x09, 0x2b, 0x5e, 0x2f, 0xe7, + 0x84, 0x8e, 0x1b, 0xf0, 0x92, 0x67, 0x48, 0x3e, 0x07, 0x2f, 0x11, 0xc5, 0x1d, 0xe6, 0x86, 0x62, + 0x6b, 0x0a, 0x18, 0x48, 0x13, 0xbe, 0x80, 0x71, 0x86, 0x99, 0x0e, 0xae, 0x19, 0x3d, 0xcd, 0xf0, + 0x6a, 0x4c, 0x53, 0x9e, 0xc2, 0x24, 0xc3, 0xec, 0x56, 0xf0, 0x0a, 0x0d, 0xc7, 0x31, 0x42, 0x0d, + 0xd8, 0x90, 0x4a, 0xb6, 0x41, 0x19, 0xca, 0x88, 0xe5, 0x39, 0xc6, 0xfa, 0x29, 0x3b, 0x74, 0xac, + 0xc1, 0xb5, 0xc1, 0xc8, 0x73, 0x38, 0xac, 0x49, 0xd7, 0xbc, 0x2c, 0x31, 0x0e, 0x4b, 0x26, 0x30, + 0xaf, 0xf4, 0x52, 0x3a, 0x94, 0x18, 0xae, 0x71, 0x5d, 0x68, 0xcf, 0x7d, 0x58, 0xa5, 0x54, 0x61, + 0xae, 0xf7, 0xb3, 0x09, 0xfb, 0x8b, 0xc1, 0x14, 0x89, 0x8b, 0x8c, 0x95, 0xa1, 0x40, 0x59, 0xa4, + 0x37, 0xe8, 0x8f, 0x66, 0x96, 0x4a, 0x50, 0x83, 0xd4, 0x60, 0xe4, 0x53, 0x00, 0x13, 0x29, 0x65, + 0x77, 0x3b, 0xdf, 0xd5, 0x61, 0x5c, 0x8d, 0xbc, 0x66, 0x77, 0xbb, 0xc6, 0x1d, 0x96, 0xbc, 0x44, + 0xe9, 0xc3, 0xcc, 0x6a, 0xdc, 0x17, 0x0a, 0x20, 0xcf, 0xe0, 0xa0, 0x75, 0x87, 0xbf, 0x6f, 0x13, + 0xe9, 0x7b, 0x9a, 0x32, 0x6e, 0x28, 0x27, 0xdb, 0x44, 0x06, 0xff, 0x58, 0xf0, 0x48, 0xa0, 0xac, + 0x0a, 0x81, 0x7b, 0xa3, 0xfa, 0xd2, 0xdc, 0x96, 0x61, 0x54, 0x64, 0xaa, 0x64, 0xf3, 0x87, 0x3a, + 0xd4, 0xd4, 0xb6, 0xaa, 0x41, 0xb2, 0x80, 0x87, 0xfb, 0xed, 0x89, 0x8a, 0x5b, 0x3d, 0x32, 0x87, + 0x3e, 0xe8, 0xf6, 0x66, 0x55, 0xdc, 0xaa, 0xb9, 0x25, 0x85, 0xb8, 0x6e, 0x87, 0x5f, 0xcf, 0xad, + 0xc6, 0x9a, 0xd1, 0x36, 0xc9, 0x74, 0xc6, 0xe6, 0xd5, 0x98, 0xa6, 0xb4, 0x89, 0xd5, 0xa0, 0x1a, + 0x9b, 0xd5, 0x26, 0x46, 0x6b, 0x30, 0x78, 0x07, 0x5e, 0xb7, 0x9c, 0x63, 0x70, 0x62, 0xb3, 0xaa, + 0xd6, 0xdc, 0x5b, 0x3e, 0xe9, 0xbc, 0xa9, 0xff, 0x2e, 0x29, 0xd5, 0x44, 0xf2, 0x1d, 0x0c, 0x6b, + 0x01, 0xfd, 0x1c, 0xbc, 0xe5, 0x67, 0x9d, 0x3b, 0xef, 0x69, 0x18, 0x6d, 0xe8, 0x8b, 0xef, 0x3b, + 0xbf, 0x8f, 0xf9, 0x55, 0x88, 0x0b, 0x7d, 0xba, 0xfe, 0xf5, 0x7c, 0x35, 0xfd, 0x48, 0x1d, 0x4f, + 0x2e, 0xe9, 0xd9, 0x7a, 0x6a, 0x91, 0x21, 0xf4, 0x7e, 0x3b, 0x5b, 0x4f, 0x6d, 0x75, 0xa0, 0x27, + 0xa7, 0xd3, 0xde, 0xe2, 0x18, 0x46, 0xcd, 0x17, 0x43, 0x0e, 0x00, 0xd4, 0x39, 0xec, 0x5c, 0xbc, + 0x78, 0xf9, 0xc3, 0xdb, 0xd7, 0x53, 0x8b, 0x8c, 0xc0, 0x39, 0xff, 0xf9, 0xfc, 0xc7, 0xa9, 0xfd, + 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa0, 0x86, 0x78, 0x5b, 0x16, 0x07, 0x00, 0x00, } diff --git a/lxd/migrate.proto b/lxd/migration/migrate.proto similarity index 99% rename from lxd/migrate.proto rename to lxd/migration/migrate.proto index 8bcfdc295..bfb35eca2 100644 --- a/lxd/migrate.proto +++ b/lxd/migration/migrate.proto @@ -1,7 +1,7 @@ // silence the protobuf compiler warning by setting the default syntax = "proto2"; -package main; +package migration; enum MigrationFSType { RSYNC = 0; diff --git a/lxd/migration/wsproto.go b/lxd/migration/wsproto.go new file mode 100644 index 000000000..7ba37b29f --- /dev/null +++ b/lxd/migration/wsproto.go @@ -0,0 +1,71 @@ +package migration + +import ( + "fmt" + "io/ioutil" + + "github.com/golang/protobuf/proto" + "github.com/gorilla/websocket" + + "github.com/lxc/lxd/shared" +) + +// ProtoRecv gets a protobuf message from a websocket +func ProtoRecv(ws *websocket.Conn, msg proto.Message) error { + mt, r, err := ws.NextReader() + if err != nil { + return err + } + + if mt != websocket.BinaryMessage { + return fmt.Errorf("Only binary messages allowed") + } + + buf, err := ioutil.ReadAll(r) + if err != nil { + return err + } + + err = proto.Unmarshal(buf, msg) + if err != nil { + return err + } + + return nil +} + +// ProtoSend sends a protobuf message over a websocket +func ProtoSend(ws *websocket.Conn, msg proto.Message) error { + w, err := ws.NextWriter(websocket.BinaryMessage) + if err != nil { + return err + } + defer w.Close() + + data, err := proto.Marshal(msg) + if err != nil { + return err + } + + err = shared.WriteAll(w, data) + if err != nil { + return err + } + + return nil +} + +// ProtoSendControl sends a migration control message over a websocket +func ProtoSendControl(ws *websocket.Conn, err error) { + message := "" + if err != nil { + message = err.Error() + } + + msg := MigrationControl{ + Success: proto.Bool(err == nil), + Message: proto.String(message), + } + + ProtoSend(ws, &msg) +} diff --git a/lxd/storage.go b/lxd/storage.go index 5df78f7c6..f7b04ebf0 100644 --- a/lxd/storage.go +++ b/lxd/storage.go @@ -12,6 +12,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/state" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -196,7 +197,7 @@ type storage interface { StorageEntitySetQuota(volumeType int, size int64, data interface{}) error // Functions dealing with migration. - MigrationType() MigrationFSType + MigrationType() migration.MigrationFSType // Does this storage backend preserve inodes when it is moved across LXD // hosts? PreservesInodes() bool @@ -222,7 +223,7 @@ type storage interface { MigrationSink( live bool, c container, - objects []*Snapshot, + objects []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go index 2eca2c6a8..996ca3a85 100644 --- a/lxd/storage_btrfs.go +++ b/lxd/storage_btrfs.go @@ -16,6 +16,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -1972,12 +1973,12 @@ func (s *btrfsMigrationSourceDriver) Cleanup() { } } -func (s *storageBtrfs) MigrationType() MigrationFSType { +func (s *storageBtrfs) MigrationType() migration.MigrationFSType { if s.s.OS.RunningInUserNS { - return MigrationFSType_RSYNC + return migration.MigrationFSType_RSYNC } - return MigrationFSType_BTRFS + return migration.MigrationFSType_BTRFS } func (s *storageBtrfs) PreservesInodes() bool { @@ -2023,7 +2024,7 @@ func (s *storageBtrfs) MigrationSource(c container, containerOnly bool) (Migrati return driver, nil } -func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { +func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { if s.s.OS.RunningInUserNS { return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly) } diff --git a/lxd/storage_ceph_migration.go b/lxd/storage_ceph_migration.go index 000a91a9c..4e4016203 100644 --- a/lxd/storage_ceph_migration.go +++ b/lxd/storage_ceph_migration.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/idmap" "github.com/lxc/lxd/shared/logger" @@ -169,8 +170,8 @@ func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, return nil } -func (s *storageCeph) MigrationType() MigrationFSType { - return MigrationFSType_RBD +func (s *storageCeph) MigrationType() migration.MigrationFSType { + return migration.MigrationFSType_RBD } func (s *storageCeph) PreservesInodes() bool { @@ -240,7 +241,7 @@ func (s *storageCeph) MigrationSource(c container, containerOnly bool) (Migratio } func (s *storageCeph) MigrationSink(live bool, c container, - snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, + snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { // Check that we received a valid root disk device with a pool property // set. diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go index 771153d70..6e4ddd42e 100644 --- a/lxd/storage_dir.go +++ b/lxd/storage_dir.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/websocket" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/idmap" @@ -1030,8 +1031,8 @@ func (s *storageDir) ImageUmount(fingerprint string) (bool, error) { return true, nil } -func (s *storageDir) MigrationType() MigrationFSType { - return MigrationFSType_RSYNC +func (s *storageDir) MigrationType() migration.MigrationFSType { + return migration.MigrationFSType_RSYNC } func (s *storageDir) PreservesInodes() bool { @@ -1042,7 +1043,7 @@ func (s *storageDir) MigrationSource(container container, containerOnly bool) (M return rsyncMigrationSource(container, containerOnly) } -func (s *storageDir) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { +func (s *storageDir) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly) } diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go index d74b24d2f..3eedf4fa0 100644 --- a/lxd/storage_lvm.go +++ b/lxd/storage_lvm.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/websocket" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/idmap" @@ -1724,8 +1725,8 @@ func (s *storageLvm) ImageUmount(fingerprint string) (bool, error) { return true, nil } -func (s *storageLvm) MigrationType() MigrationFSType { - return MigrationFSType_RSYNC +func (s *storageLvm) MigrationType() migration.MigrationFSType { + return migration.MigrationFSType_RSYNC } func (s *storageLvm) PreservesInodes() bool { @@ -1736,7 +1737,7 @@ func (s *storageLvm) MigrationSource(container container, containerOnly bool) (M return rsyncMigrationSource(container, containerOnly) } -func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { +func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly) } diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go index 76308e96d..e8a966319 100644 --- a/lxd/storage_migration.go +++ b/lxd/storage_migration.go @@ -6,6 +6,7 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/idmap" @@ -96,7 +97,7 @@ func rsyncMigrationSource(c container, containerOnly bool) (MigrationStorageSour return rsyncStorageSourceDriver{c, snapshots}, nil } -func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) db.ContainerArgs { +func snapshotProtobufToContainerArgs(containerName string, snap *migration.Snapshot) db.ContainerArgs { config := map[string]string{} for _, ent := range snap.LocalConfig { @@ -126,7 +127,7 @@ func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) db.Co } } -func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { +func rsyncMigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { ourStart, err := container.StorageStart() if err != nil { return err diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go index 5019237f7..1fa402f87 100644 --- a/lxd/storage_mock.go +++ b/lxd/storage_mock.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/websocket" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/idmap" "github.com/lxc/lxd/shared/logger" @@ -204,8 +205,8 @@ func (s *storageMock) ImageUmount(fingerprint string) (bool, error) { return true, nil } -func (s *storageMock) MigrationType() MigrationFSType { - return MigrationFSType_RSYNC +func (s *storageMock) MigrationType() migration.MigrationFSType { + return migration.MigrationFSType_RSYNC } func (s *storageMock) PreservesInodes() bool { @@ -215,7 +216,8 @@ func (s *storageMock) PreservesInodes() bool { func (s *storageMock) MigrationSource(container container, containerOnly bool) (MigrationStorageSourceDriver, error) { return nil, fmt.Errorf("not implemented") } -func (s *storageMock) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { + +func (s *storageMock) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { return nil } diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go index 46d9b6c50..9e4dec3eb 100644 --- a/lxd/storage_zfs.go +++ b/lxd/storage_zfs.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/websocket" + "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -2160,8 +2161,8 @@ func (s *zfsMigrationSourceDriver) Cleanup() { } } -func (s *storageZfs) MigrationType() MigrationFSType { - return MigrationFSType_ZFS +func (s *storageZfs) MigrationType() migration.MigrationFSType { + return migration.MigrationFSType_ZFS } func (s *storageZfs) PreservesInodes() bool { @@ -2219,7 +2220,7 @@ func (s *storageZfs) MigrationSource(ct container, containerOnly bool) (Migratio return &driver, nil } -func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { +func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error { poolName := s.getOnDiskPoolName() zfsRecv := func(zfsName string, writeWrapper func(io.WriteCloser) io.WriteCloser) error { zfsFsName := fmt.Sprintf("%s/%s", poolName, zfsName) diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh index 598d77113..4f9d0c892 100644 --- a/test/suites/static_analysis.sh +++ b/test/suites/static_analysis.sh @@ -72,6 +72,7 @@ test_static_analysis() { golint -set_exit_status lxd/debug golint -set_exit_status lxd/endpoints golint -set_exit_status lxd/maas + golint -set_exit_status lxd/migration golint -set_exit_status lxd/node golint -set_exit_status lxd/state golint -set_exit_status lxd/sys From 51eb1f60363f524daa87fcbe4fdce39ffc0c3d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 22 Feb 2018 00:26:15 -0500 Subject: [PATCH 4/7] lxd/daemon: Cleanup startup code a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/main_daemon.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go index 4b0948544..eec8d9e4e 100644 --- a/lxd/main_daemon.go +++ b/lxd/main_daemon.go @@ -24,23 +24,24 @@ func cmdDaemon(args *Args) error { dbg.Memory(args.MemProfile), dbg.Goroutines(args.PrintGoroutinesEvery), ) - defer stop() if err != nil { - fmt.Printf("%v\n", err) - return nil + return err } + defer stop() + neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"} for _, p := range neededPrograms { _, err := exec.LookPath(p) if err != nil { return err } - } + c := &DaemonConfig{ Group: args.Group, } + d := NewDaemon(c, sys.DefaultOS()) err = d.Init() if err != nil { @@ -56,7 +57,6 @@ func cmdDaemon(args *Args) error { s := d.State() select { case sig := <-ch: - if sig == syscall.SIGPWR { logger.Infof("Received '%s signal', shutting down containers.", sig) containersShutdown(s) From d3b81aab6c4abfd6540ad3a5a4434501727061d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 22 Feb 2018 01:11:45 -0500 Subject: [PATCH 5/7] lxc: Introduce a new utils package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxc/copy.go | 3 +- lxc/image.go | 11 +-- lxc/init.go | 5 +- lxc/storage.go | 3 +- lxc/utils.go | 153 ----------------------------------------- lxc/utils/cancel.go | 48 +++++++++++++ lxc/utils/progress.go | 128 ++++++++++++++++++++++++++++++++++ test/suites/static_analysis.sh | 1 + 8 files changed, 190 insertions(+), 162 deletions(-) create mode 100644 lxc/utils/cancel.go create mode 100644 lxc/utils/progress.go diff --git a/lxc/copy.go b/lxc/copy.go index 85acaeb63..8b68143ee 100644 --- a/lxc/copy.go +++ b/lxc/copy.go @@ -6,6 +6,7 @@ import ( "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxc/config" + "github.com/lxc/lxd/lxc/utils" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" @@ -195,7 +196,7 @@ func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, } // Watch the background operation - progress := progressRenderer{Format: i18n.G("Transferring container: %s")} + progress := utils.ProgressRenderer{Format: i18n.G("Transferring container: %s")} _, err = op.AddHandler(progress.UpdateOp) if err != nil { progress.Done("") diff --git a/lxc/image.go b/lxc/image.go index eaccbb231..eb104ec02 100644 --- a/lxc/image.go +++ b/lxc/image.go @@ -18,6 +18,7 @@ import ( "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxc/config" + "github.com/lxc/lxd/lxc/utils" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" @@ -445,7 +446,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error { } // Register progress handler - progress := progressRenderer{Format: i18n.G("Copying the image: %s")} + progress := utils.ProgressRenderer{Format: i18n.G("Copying the image: %s")} _, err = op.AddHandler(progress.UpdateOp) if err != nil { progress.Done("") @@ -453,7 +454,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error { } // Wait for operation to finish - err = cancelableWait(op, &progress) + err = utils.CancelableWait(op, &progress) if err != nil { progress.Done("") return err @@ -525,7 +526,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error { } image := c.dereferenceAlias(d, inName) - progress := progressRenderer{Format: i18n.G("Refreshing the image: %s")} + progress := utils.ProgressRenderer{Format: i18n.G("Refreshing the image: %s")} op, err := d.RefreshImage(image) if err != nil { return err @@ -713,7 +714,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error { image.Properties[strings.TrimSpace(fields[0])] = strings.TrimSpace(fields[1]) } - progress := progressRenderer{Format: i18n.G("Transferring image: %s")} + progress := utils.ProgressRenderer{Format: i18n.G("Transferring image: %s")} if strings.HasPrefix(imageFile, "https://") { image.Source = &api.ImagesPostSource{} image.Source.Type = "url" @@ -918,7 +919,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error { defer destRootfs.Close() // Prepare the download request - progress := progressRenderer{Format: i18n.G("Exporting the image: %s")} + progress := utils.ProgressRenderer{Format: i18n.G("Exporting the image: %s")} req := lxd.ImageFileRequest{ MetaFile: io.WriteSeeker(dest), RootfsFile: io.WriteSeeker(destRootfs), diff --git a/lxc/init.go b/lxc/init.go index 4b8fcf3fc..611aeb2d9 100644 --- a/lxc/init.go +++ b/lxc/init.go @@ -7,6 +7,7 @@ import ( "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxc/config" + "github.com/lxc/lxd/lxc/utils" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" @@ -286,14 +287,14 @@ func (c *initCmd) create(conf *config.Config, args []string) (lxd.ContainerServe } // Watch the background operation - progress := progressRenderer{Format: i18n.G("Retrieving image: %s")} + progress := utils.ProgressRenderer{Format: i18n.G("Retrieving image: %s")} _, err = op.AddHandler(progress.UpdateOp) if err != nil { progress.Done("") return nil, "", err } - err = cancelableWait(op, &progress) + err = utils.CancelableWait(op, &progress) if err != nil { progress.Done("") return nil, "", err diff --git a/lxc/storage.go b/lxc/storage.go index 735160743..c59598eb9 100644 --- a/lxc/storage.go +++ b/lxc/storage.go @@ -14,6 +14,7 @@ import ( "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxc/config" + "github.com/lxc/lxd/lxc/utils" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" @@ -1071,7 +1072,7 @@ func (c *storageCmd) doStoragePoolVolumeCopy(client lxd.ContainerServer, src str return err } // Register progress handler - progress := progressRenderer{Format: opMsg} + progress := utils.ProgressRenderer{Format: opMsg} _, err = op.AddHandler(progress.UpdateOp) if err != nil { progress.Done("") diff --git a/lxc/utils.go b/lxc/utils.go index 97d367249..47d0573ae 100644 --- a/lxc/utils.go +++ b/lxc/utils.go @@ -5,17 +5,13 @@ import ( "net" "net/url" "os" - "os/signal" "sort" "strings" - "sync" "syscall" - "time" "github.com/lxc/lxd/client" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/i18n" - "github.com/lxc/lxd/shared/ioprogress" ) // Lists @@ -26,118 +22,6 @@ const ( listFormatYAML = "yaml" ) -// Progress tracking -type progressRenderer struct { - Format string - - maxLength int - wait time.Time - done bool - lock sync.Mutex -} - -func (p *progressRenderer) Done(msg string) { - // Acquire rendering lock - p.lock.Lock() - defer p.lock.Unlock() - - // Check if we're already done - if p.done { - return - } - - // Mark this renderer as done - p.done = true - - // Print the new message - if msg != "" { - msg += "\n" - } - - if len(msg) > p.maxLength { - p.maxLength = len(msg) - } else { - fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength)) - } - - fmt.Print("\r") - fmt.Print(msg) -} - -func (p *progressRenderer) Update(status string) { - // Wait if needed - timeout := p.wait.Sub(time.Now()) - if timeout.Seconds() > 0 { - time.Sleep(timeout) - } - - // Acquire rendering lock - p.lock.Lock() - defer p.lock.Unlock() - - // Check if we're already done - if p.done { - return - } - - // Print the new message - msg := "%s" - if p.Format != "" { - msg = p.Format - } - - msg = fmt.Sprintf("\r"+msg, status) - - if len(msg) > p.maxLength { - p.maxLength = len(msg) - } else { - fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength)) - } - - fmt.Print(msg) -} - -func (p *progressRenderer) Warn(status string, timeout time.Duration) { - // Acquire rendering lock - p.lock.Lock() - defer p.lock.Unlock() - - // Check if we're already done - if p.done { - return - } - - // Render the new message - p.wait = time.Now().Add(timeout) - msg := fmt.Sprintf("\r%s", status) - - if len(msg) > p.maxLength { - p.maxLength = len(msg) - } else { - fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength)) - } - - fmt.Print(msg) -} - -func (p *progressRenderer) UpdateProgress(progress ioprogress.ProgressData) { - p.Update(progress.Text) -} - -func (p *progressRenderer) UpdateOp(op api.Operation) { - if op.Metadata == nil { - return - } - - for _, key := range []string{"fs_progress", "download_progress"} { - value, ok := op.Metadata[key] - if ok { - p.Update(value.(string)) - break - } - } -} - type stringList [][]string func (a stringList) Len() int { @@ -343,43 +227,6 @@ func profileDeviceAdd(client lxd.ContainerServer, name string, devName string, d return nil } -// Wait for an operation and cancel it on SIGINT/SIGTERM -func cancelableWait(op *lxd.RemoteOperation, progress *progressRenderer) error { - // Signal handling - chSignal := make(chan os.Signal) - signal.Notify(chSignal, os.Interrupt) - - // Operation handling - chOperation := make(chan error) - go func() { - chOperation <- op.Wait() - close(chOperation) - }() - - count := 0 - for { - select { - case err := <-chOperation: - return err - case <-chSignal: - err := op.CancelTarget() - if err == nil { - return fmt.Errorf(i18n.G("Remote operation canceled by user")) - } - - count++ - - if count == 3 { - return fmt.Errorf(i18n.G("User signaled us three times, exiting. The remote operation will keep running.")) - } - - if progress != nil { - progress.Warn(fmt.Sprintf(i18n.G("%v (interrupt two more times to force)"), err), time.Second*5) - } - } - } -} - // Create the specified image alises, updating those that already exist func ensureImageAliases(client lxd.ContainerServer, aliases []api.ImageAlias, fingerprint string) error { if len(aliases) == 0 { diff --git a/lxc/utils/cancel.go b/lxc/utils/cancel.go new file mode 100644 index 000000000..f1b065cbd --- /dev/null +++ b/lxc/utils/cancel.go @@ -0,0 +1,48 @@ +package utils + +import ( + "fmt" + "os" + "os/signal" + "time" + + "github.com/lxc/lxd/client" + "github.com/lxc/lxd/shared/i18n" +) + +// CancelableWait waits for an operation and cancel it on SIGINT/SIGTERM +func CancelableWait(op *lxd.RemoteOperation, progress *ProgressRenderer) error { + // Signal handling + chSignal := make(chan os.Signal) + signal.Notify(chSignal, os.Interrupt) + + // Operation handling + chOperation := make(chan error) + go func() { + chOperation <- op.Wait() + close(chOperation) + }() + + count := 0 + for { + select { + case err := <-chOperation: + return err + case <-chSignal: + err := op.CancelTarget() + if err == nil { + return fmt.Errorf(i18n.G("Remote operation canceled by user")) + } + + count++ + + if count == 3 { + return fmt.Errorf(i18n.G("User signaled us three times, exiting. The remote operation will keep running.")) + } + + if progress != nil { + progress.Warn(fmt.Sprintf(i18n.G("%v (interrupt two more times to force)"), err), time.Second*5) + } + } + } +} diff --git a/lxc/utils/progress.go b/lxc/utils/progress.go new file mode 100644 index 000000000..1a781895a --- /dev/null +++ b/lxc/utils/progress.go @@ -0,0 +1,128 @@ +package utils + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/lxc/lxd/shared/api" + "github.com/lxc/lxd/shared/ioprogress" +) + +// ProgressRenderer tracks the progress information +type ProgressRenderer struct { + Format string + + maxLength int + wait time.Time + done bool + lock sync.Mutex +} + +// Done prints the final status and prevents any update +func (p *ProgressRenderer) Done(msg string) { + // Acquire rendering lock + p.lock.Lock() + defer p.lock.Unlock() + + // Check if we're already done + if p.done { + return + } + + // Mark this renderer as done + p.done = true + + // Print the new message + if msg != "" { + msg += "\n" + } + + if len(msg) > p.maxLength { + p.maxLength = len(msg) + } else { + fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength)) + } + + fmt.Print("\r") + fmt.Print(msg) +} + +// Update changes the status message to the provided string +func (p *ProgressRenderer) Update(status string) { + // Wait if needed + timeout := p.wait.Sub(time.Now()) + if timeout.Seconds() > 0 { + time.Sleep(timeout) + } + + // Acquire rendering lock + p.lock.Lock() + defer p.lock.Unlock() + + // Check if we're already done + if p.done { + return + } + + // Print the new message + msg := "%s" + if p.Format != "" { + msg = p.Format + } + + msg = fmt.Sprintf("\r"+msg, status) + + if len(msg) > p.maxLength { + p.maxLength = len(msg) + } else { + fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength)) + } + + fmt.Print(msg) +} + +// Warn shows a temporary message instead of the status +func (p *ProgressRenderer) Warn(status string, timeout time.Duration) { + // Acquire rendering lock + p.lock.Lock() + defer p.lock.Unlock() + + // Check if we're already done + if p.done { + return + } + + // Render the new message + p.wait = time.Now().Add(timeout) + msg := fmt.Sprintf("\r%s", status) + + if len(msg) > p.maxLength { + p.maxLength = len(msg) + } else { + fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength)) + } + + fmt.Print(msg) +} + +// UpdateProgress is a helper to update the status using an iopgress instance +func (p *ProgressRenderer) UpdateProgress(progress ioprogress.ProgressData) { + p.Update(progress.Text) +} + +// UpdateOp is a helper to update the status using a LXD API operation +func (p *ProgressRenderer) UpdateOp(op api.Operation) { + if op.Metadata == nil { + return + } + + for _, key := range []string{"fs_progress", "download_progress"} { + value, ok := op.Metadata[key] + if ok { + p.Update(value.(string)) + break + } + } +} diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh index 4f9d0c892..a3cbb96ff 100644 --- a/test/suites/static_analysis.sh +++ b/test/suites/static_analysis.sh @@ -61,6 +61,7 @@ test_static_analysis() { golint -set_exit_status lxc/ golint -set_exit_status lxc/config/ + golint -set_exit_status lxc/utils/ golint -set_exit_status lxd-benchmark golint -set_exit_status lxd-benchmark/benchmark From 6a3c2cc0e262149c8747a086804ac8191688a940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Fri, 9 Feb 2018 21:27:00 -0500 Subject: [PATCH 6/7] lxd-p2c: Add new tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd-p2c/main.go | 69 ++++++++++++++++ lxd-p2c/main_migrate.go | 172 +++++++++++++++++++++++++++++++++++++++ lxd-p2c/main_netcat.go | 61 ++++++++++++++ lxd-p2c/setns.go | 33 ++++++++ lxd-p2c/transfer.go | 107 ++++++++++++++++++++++++ lxd-p2c/utils.go | 180 +++++++++++++++++++++++++++++++++++++++++ test/suites/static_analysis.sh | 2 + 7 files changed, 624 insertions(+) create mode 100644 lxd-p2c/main.go create mode 100644 lxd-p2c/main_migrate.go create mode 100644 lxd-p2c/main_netcat.go create mode 100644 lxd-p2c/setns.go create mode 100644 lxd-p2c/transfer.go create mode 100644 lxd-p2c/utils.go diff --git a/lxd-p2c/main.go b/lxd-p2c/main.go new file mode 100644 index 000000000..5a9d71340 --- /dev/null +++ b/lxd-p2c/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/lxc/lxd/shared/version" +) + +type cmdGlobal struct { + flagVersion bool + flagHelp bool +} + +func main() { + app := &cobra.Command{} + app.Use = "lxd-p2c" + app.Short = "Physical to container migration tool" + app.Long = `Description: + Physical to container migration tool + + This tool lets you turn any Linux filesystem (including your current one) + into a LXD container on a remote LXD host. + + It will setup a clean mount tree made of the root filesystem and any + additional mount you list, then transfer this through LXD's migration + API to create a new container from it. + + The same set of options as ` + "`lxc launch`" + ` are also supported. +` + app.SilenceUsage = true + + // Global flags + globalCmd := cmdGlobal{} + app.PersistentFlags().BoolVar(&globalCmd.flagVersion, "version", false, "Print version number") + app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help") + + // Version handling + app.SetVersionTemplate("{{.Version}}\n") + app.Version = version.Version + + // migrate command (main) + migrateCmd := cmdMigrate{global: &globalCmd} + app.Flags().StringArrayVarP(&migrateCmd.flagConfig, "config", "c", nil, "Configuration key and value to set on the container"+"``") + app.Flags().StringVarP(&migrateCmd.flagNetwork, "network", "n", "", "Network to use for the container"+"``") + app.Flags().StringArrayVarP(&migrateCmd.flagProfile, "profile", "p", nil, "Profile to apply to the container"+"``") + app.Flags().StringVarP(&migrateCmd.flagStorage, "storage", "s", "", "Storage pool to use for the container"+"``") + app.Flags().StringVarP(&migrateCmd.flagType, "type", "t", "", "Instance type to use for the container"+"``") + app.Flags().BoolVar(&migrateCmd.flagNoProfiles, "no-profiles", false, "Create the container with no profiles applied") + app.Use = "lxd-p2c <target URL> <container name> <filesystem root> [<filesystem mounts>...]" + app.RunE = migrateCmd.Run + app.Args = cobra.ArbitraryArgs + + // netcat sub-command + netcatCmd := cmdNetcat{global: &globalCmd} + appNetcat := &cobra.Command{} + appNetcat.Use = "netcat <address>" + appNetcat.Hidden = true + appNetcat.Short = "Sends stdin data to a unix socket" + appNetcat.RunE = netcatCmd.Run + app.AddCommand(appNetcat) + + // Run the main command and handle errors + err := app.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/lxd-p2c/main_migrate.go b/lxd-p2c/main_migrate.go new file mode 100644 index 000000000..660ac2437 --- /dev/null +++ b/lxd-p2c/main_migrate.go @@ -0,0 +1,172 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "sort" + "strings" + "syscall" + + "github.com/spf13/cobra" + + "github.com/lxc/lxd/lxc/utils" + "github.com/lxc/lxd/shared/api" + "github.com/lxc/lxd/shared/osarch" +) + +type cmdMigrate struct { + global *cmdGlobal + + flagConfig []string + flagNetwork string + flagProfile []string + flagStorage string + flagType string + flagNoProfiles bool +} + +func (c *cmdMigrate) Run(cmd *cobra.Command, args []string) error { + // Help and usage + if len(args) == 0 { + return cmd.Help() + } + + // Sanity checks + if os.Geteuid() != 0 { + return fmt.Errorf("This tool must be run as root") + } + + _, err := exec.LookPath("rsync") + if err != nil { + return err + } + + if c.flagNoProfiles && len(c.flagProfile) != 0 { + return fmt.Errorf("no-profiles can't be specified alongside profiles") + } + + // Handle mandatory arguments + if len(args) < 3 { + cmd.Help() + return fmt.Errorf("Missing required arguments") + } + + // Get and sort the mounts + mounts := args[2:] + sort.Strings(mounts) + + // Create the temporary directory to be used for the mounts + path, err := ioutil.TempDir("", "lxd-p2c_mount_") + if err != nil { + return err + } + + // Automatically clean-up the temporary path on exit + defer func(path string) { + syscall.Unmount(path, syscall.MNT_DETACH) + os.Remove(path) + }(path) + + // Create the rootfs directory + fullPath := fmt.Sprintf("%s/rootfs", path) + err = os.Mkdir(fullPath, 0755) + if err != nil { + return err + } + + // Setup the source (mounts) + err = setupSource(fullPath, mounts) + if err != nil { + return fmt.Errorf("Failed to setup the source: %v", err) + } + + // Connect to the target + dst, err := connectTarget(args[0]) + if err != nil { + return err + } + + // Container creation request + apiArgs := api.ContainersPost{} + apiArgs.Name = args[1] + apiArgs.Source = api.ContainerSource{ + Type: "migration", + Mode: "push", + } + + // System architecture + architectureName, err := osarch.ArchitectureGetLocal() + if err != nil { + return err + } + apiArgs.Architecture = architectureName + + // Instance type + apiArgs.InstanceType = c.flagType + + // Config overrides + apiArgs.Config = map[string]string{} + for _, entry := range c.flagConfig { + if !strings.Contains(entry, "=") { + return fmt.Errorf("Bad key=value configuration: %v", entry) + } + + fields := strings.SplitN(entry, "=", 2) + apiArgs.Config[fields[0]] = fields[1] + } + + // Profiles + if len(c.flagProfile) != 0 { + apiArgs.Profiles = c.flagProfile + } + + if c.flagNoProfiles { + apiArgs.Profiles = []string{} + } + + // Devices + apiArgs.Devices = map[string]map[string]string{} + + network := c.flagNetwork + if network != "" { + apiArgs.Devices["eth0"] = map[string]string{ + "type": "nic", + "nictype": "bridged", + "parent": network, + "name": "eth0", + } + } + + storage := c.flagStorage + if network != "" { + apiArgs.Devices["root"] = map[string]string{ + "type": "disk", + "pool": storage, + "path": "/", + } + } + + // Create the container + op, err := dst.CreateContainer(apiArgs) + if err != nil { + return err + } + + progress := utils.ProgressRenderer{Format: "Transferring container: %s"} + _, err = op.AddHandler(progress.UpdateOp) + if err != nil { + progress.Done("") + return err + } + + err = transferRootfs(dst, op, fullPath) + if err != nil { + return err + } + + progress.Done(fmt.Sprintf("Container %s successfully created", apiArgs.Name)) + + return nil +} diff --git a/lxd-p2c/main_netcat.go b/lxd-p2c/main_netcat.go new file mode 100644 index 000000000..20350a27d --- /dev/null +++ b/lxd-p2c/main_netcat.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "io" + "net" + "os" + "sync" + + "github.com/spf13/cobra" + + "github.com/lxc/lxd/shared/eagain" +) + +type cmdNetcat struct { + global *cmdGlobal +} + +func (c *cmdNetcat) Run(cmd *cobra.Command, args []string) error { + // Help and usage + if len(args) == 0 { + cmd.Help() + return nil + } + + // Handle mandatory arguments + if len(args) != 1 { + cmd.Help() + return fmt.Errorf("Missing required argument") + } + + // Connect to the provided address + uAddr, err := net.ResolveUnixAddr("unix", args[0]) + if err != nil { + return err + } + + conn, err := net.DialUnix("unix", nil, uAddr) + if err != nil { + return err + } + + // We'll wait until we're done reading from the socket + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + io.Copy(eagain.Writer{Writer: os.Stdout}, eagain.Reader{Reader: conn}) + conn.Close() + wg.Done() + }() + + go func() { + io.Copy(eagain.Writer{Writer: conn}, eagain.Reader{Reader: os.Stdin}) + }() + + // Wait + wg.Wait() + + return nil +} diff --git a/lxd-p2c/setns.go b/lxd-p2c/setns.go new file mode 100644 index 000000000..cd9ef1dc0 --- /dev/null +++ b/lxd-p2c/setns.go @@ -0,0 +1,33 @@ +package main + +/* +#define _GNU_SOURCE +#include <errno.h> +#include <sched.h> +#include <stdio.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <unistd.h> + +__attribute__((constructor)) void init(void) { + if (geteuid() != 0) { + return; + } + + // Unshare a new mntns so our mounts don't leak + if (unshare(CLONE_NEWNS) < 0) { + fprintf(stderr, "Failed to unshare a new mount namespace: %s\n", strerror(errno)); + _exit(1); + } + + // Prevent mount propagation back to initial namespace + if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) { + fprintf(stderr, "Failed to mark / private: %s\n", strerror(errno)); + _exit(1); + } + + // We're done, jump back to Go +} +*/ +import "C" diff --git a/lxd-p2c/transfer.go b/lxd-p2c/transfer.go new file mode 100644 index 000000000..45a4eadcb --- /dev/null +++ b/lxd-p2c/transfer.go @@ -0,0 +1,107 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + + "github.com/gorilla/websocket" + "github.com/pborman/uuid" + + "github.com/lxc/lxd/lxd/migration" + "github.com/lxc/lxd/shared" +) + +// Send an rsync stream of a path over a websocket +func rsyncSend(conn *websocket.Conn, path string) error { + cmd, dataSocket, stderr, err := rsyncSendSetup(path) + if err != nil { + return err + } + + if dataSocket != nil { + defer dataSocket.Close() + } + + readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, io.ReadCloser(dataSocket), nil, nil) + + output, err := ioutil.ReadAll(stderr) + if err != nil { + cmd.Process.Kill() + cmd.Wait() + return fmt.Errorf("Failed to rsync: %v\n%s", err, output) + } + + err = cmd.Wait() + <-readDone + <-writeDone + + if err != nil { + return err + } + + return nil +} + +// Spawn the rsync process +func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) { + auds := fmt.Sprintf("@lxd-p2c/%s", uuid.NewRandom().String()) + if len(auds) > shared.ABSTRACT_UNIX_SOCK_LEN-1 { + auds = auds[:shared.ABSTRACT_UNIX_SOCK_LEN-1] + } + + l, err := net.Listen("unix", auds) + if err != nil { + return nil, nil, nil, err + } + + execPath, err := os.Readlink("/proc/self/exe") + if err != nil { + return nil, nil, nil, err + } + + rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, auds) + + cmd := exec.Command("rsync", + "-arvP", + "--devices", + "--numeric-ids", + "--partial", + "--sparse", + path, + "localhost:/tmp/foo", + "-e", + rsyncCmd) + + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, nil, nil, err + } + + if err := cmd.Start(); err != nil { + return nil, nil, nil, err + } + + conn, err := l.Accept() + if err != nil { + cmd.Process.Kill() + cmd.Wait() + return nil, nil, nil, err + } + l.Close() + + return cmd, conn, stderr, nil +} + +func protoSendError(ws *websocket.Conn, err error) { + migration.ProtoSendControl(ws, err) + + if err != nil { + closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") + ws.WriteMessage(websocket.CloseMessage, closeMsg) + ws.Close() + } +} diff --git a/lxd-p2c/utils.go b/lxd-p2c/utils.go new file mode 100644 index 000000000..dda9f2954 --- /dev/null +++ b/lxd-p2c/utils.go @@ -0,0 +1,180 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "strings" + "syscall" + + "golang.org/x/crypto/ssh/terminal" + + "github.com/lxc/lxd/client" + "github.com/lxc/lxd/lxd/migration" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" +) + +func transferRootfs(dst lxd.ContainerServer, op *lxd.Operation, rootfs string) error { + // Connect to the websockets + wsControl, err := dst.GetOperationWebsocket(op.ID, op.Metadata["control"].(string)) + if err != nil { + return err + } + + wsFs, err := dst.GetOperationWebsocket(op.ID, op.Metadata["fs"].(string)) + if err != nil { + return err + } + + // Setup control struct + fs := migration.MigrationFSType_RSYNC + header := migration.MigrationHeader{ + Fs: &fs, + } + + err = migration.ProtoSend(wsControl, &header) + if err != nil { + protoSendError(wsControl, err) + return err + } + + err = migration.ProtoRecv(wsControl, &header) + if err != nil { + protoSendError(wsControl, err) + return err + } + + // Send the filesystem + abort := func(err error) error { + protoSendError(wsControl, err) + return err + } + + err = rsyncSend(wsFs, rootfs) + if err != nil { + return abort(err) + } + + // Check the result + msg := migration.MigrationControl{} + err = migration.ProtoRecv(wsControl, &msg) + if err != nil { + wsControl.Close() + return err + } + + if !*msg.Success { + return fmt.Errorf(*msg.Message) + } + + return nil +} + +func connectTarget(url string) (lxd.ContainerServer, error) { + // Generate a new client certificate for this + fmt.Println("Generating a temporary client certificate. This may take a minute...") + clientCrt, clientKey, err := shared.GenerateMemCert(true) + if err != nil { + return nil, err + } + + // Attempt to connect using the system CA + args := lxd.ConnectionArgs{} + args.TLSClientCert = string(clientCrt) + args.TLSClientKey = string(clientKey) + args.UserAgent = "LXD-P2C" + c, err := lxd.ConnectLXD(url, &args) + + var certificate *x509.Certificate + if err != nil { + // Failed to connect using the system CA, so retrieve the remote certificate + certificate, err = shared.GetRemoteCertificate(url) + if err != nil { + return nil, err + } + } + + // Handle certificate prompt + if certificate != nil { + digest := shared.CertFingerprint(certificate) + + fmt.Printf("Certificate fingerprint: %s\n", digest) + fmt.Printf("ok (y/n)? ") + line, err := shared.ReadStdin() + if err != nil { + return nil, err + } + + if len(line) < 1 || line[0] != 'y' && line[0] != 'Y' { + return nil, fmt.Errorf("Server certificate rejected by user") + } + + serverCrt := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw}) + args.TLSServerCert = string(serverCrt) + + // Setup a new connection, this time with the remote certificate + c, err = lxd.ConnectLXD(url, &args) + if err != nil { + return nil, err + } + } + + // Get server information + srv, _, err := c.GetServer() + if err != nil { + return nil, err + } + + // Check if our cert is already trusted + if srv.Auth == "trusted" { + return c, nil + } + + // Prompt for trust password + fmt.Printf("Admin password for %s: ", url) + pwd, err := terminal.ReadPassword(0) + if err != nil { + return nil, err + } + fmt.Println("") + + // Add client certificate to trust store + req := api.CertificatesPost{ + Password: string(pwd), + } + req.Type = "client" + + err = c.CreateCertificate(req) + if err != nil { + return nil, err + } + + return c, nil +} + +func setupSource(path string, mounts []string) error { + prefix := "/" + if len(mounts) > 0 { + prefix = mounts[0] + } + + // Mount everything + for _, mount := range mounts { + target := fmt.Sprintf("%s/%s", path, strings.TrimPrefix(mount, prefix)) + + // Mount the path + err := syscall.Mount(mount, target, "none", syscall.MS_BIND, "") + if err != nil { + return fmt.Errorf("Failed to mount %s: %v", mount, err) + } + + // Make it read-only + err = syscall.Mount("", target, "none", syscall.MS_BIND|syscall.MS_RDONLY|syscall.MS_REMOUNT, "") + if err != nil { + return fmt.Errorf("Failed to make %s read-only: %v", mount, err) + } + } + + return nil +} diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh index a3cbb96ff..aff7eeafa 100644 --- a/test/suites/static_analysis.sh +++ b/test/suites/static_analysis.sh @@ -66,6 +66,8 @@ test_static_analysis() { golint -set_exit_status lxd-benchmark golint -set_exit_status lxd-benchmark/benchmark + golint -set_exit_status lxd-p2c + golint -set_exit_status lxd/config golint -set_exit_status lxd/db/node golint -set_exit_status lxd/db/query From b986027e8d3a89381eae07ae22c900fcd2b0763c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Thu, 22 Feb 2018 03:13:28 -0500 Subject: [PATCH 7/7] i18n: Look at all lxc files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9d7a37226..297f6bfd1 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ update-po: update-pot: go get -v -x github.com/snapcore/snapd/i18n/xgettext-go/ - xgettext-go -o po/$(DOMAIN).pot --add-comments-tag=TRANSLATORS: --sort-output --package-name=$(DOMAIN) --msgid-bugs-address=lxc-devel@lists.linuxcontainers.org --keyword=i18n.G --keyword-plural=i18n.NG shared/*.go lxc/*.go lxd/*.go + xgettext-go -o po/$(DOMAIN).pot --add-comments-tag=TRANSLATORS: --sort-output --package-name=$(DOMAIN) --msgid-bugs-address=lxc-devel@lists.linuxcontainers.org --keyword=i18n.G --keyword-plural=i18n.NG lxc/*.go lxc/*/*.go build-mo: $(MOFILES)
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel