diff --git a/internal/constants/constants.go b/internal/constants/constants.go index bd71bda..de7e1af 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -29,10 +29,11 @@ const ( ProcConnectOpen = 1 ProcConnectClose = 2 ProcDomainLookupByName = 23 - ProcDomainMigrateSetMaxSpeed = 207 ProcAuthList = 66 ProcConnectGetLibVersion = 157 + ProcDomainMigrateSetMaxSpeed = 207 ProcConnectListAllDomains = 273 + ProcMigratePerformParams = 305 ) // qemu procedure identifiers diff --git a/libvirt.go b/libvirt.go index c00bf0b..1074e61 100644 --- a/libvirt.go +++ b/libvirt.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "net" + "net/url" "sync" "github.com/davecgh/go-xdr/xdr2" @@ -77,6 +78,56 @@ type qemuError struct { } `json:"error"` } +// MigrateFlags specifies options when performing a migration. +type MigrateFlags uint32 + +const ( + // MigrateFlagLive performs a zero-downtime live migration. + MigrateFlagLive MigrateFlags = 1 << iota + + // MigrateFlagPeerToPeer creates a direct source to destination control channel. + MigrateFlagPeerToPeer + + // MigrateFlagTunneled tunnels migration data over the libvirtd connection. + MigrateFlagTunneled + + // MigrateFlagPersistDestination will persist the VM on the destination host. + MigrateFlagPersistDestination + + // MigrateFlagUndefineSource undefines the VM on the source host. + MigrateFlagUndefineSource + + // MigrateFlagPaused will pause the remote side VM. + MigrateFlagPaused + + // MigrateFlagNonSharedDisk migrate non-shared storage with full disk copy. + MigrateFlagNonSharedDisk + + // MigrateFlagNonSharedIncremental migrate non-shared storage with incremental copy. + MigrateFlagNonSharedIncremental + + // MigrateFlagChangeProtection prevents any changes to the domain configuration through the whole migration process. + MigrateFlagChangeProtection + + // MigrateFlagUnsafe will force a migration even when it is considered unsafe. + MigrateFlagUnsafe + + // MigrateFlagOffline is used to perform an offline migration. + MigrateFlagOffline + + // MigrateFlagCompressed compresses data during migration. + MigrateFlagCompressed + + // MigrateFlagAbortOnError will abort a migration on I/O errors encountered during migration. + MigrateFlagAbortOnError + + // MigrateFlagAutoConverge forces convergence. + MigrateFlagAutoConverge + + // MigrateFlagRDMAPinAll enables RDMA memory pinning. + MigrateFlagRDMAPinAll +) + // Connect establishes communication with the libvirt server. // The underlying libvirt socket connection must be previously established. func (l *Libvirt) Connect() error { @@ -204,6 +255,58 @@ func (l *Libvirt) Events(dom string) (<-chan DomainEvent, error) { return c, nil } +// Migrate synchronously migrates the domain specified by dom, e.g., +// 'prod-lb-01', to the destination hypervisor specified by dest, e.g., +// 'qemu+tcp://example.com/system'. The flags argument determines the +// type of migration and how it will be performed. For more information +// on available migration flags and their meaning, see MigrateFlag*. +func (l *Libvirt) Migrate(dom string, dest string, flags MigrateFlags) error { + _, err := url.Parse(dest) + if err != nil { + return err + } + + d, err := l.lookup(dom) + if err != nil { + return err + } + + // Two unknowns remain here , Libvirt specifies RemoteParameters + // and CookieIn. In testing both values are always set to 0 by virsh + // and the source does not provide clear definitions of their purpose. + // For now, using the same zero'd values as done by virsh will be Good Enough. + payload := struct { + Domain Domain + DestinationURI string + RemoteParameters uint32 + CookieIn uint32 + Flags MigrateFlags + }{ + Domain: *d, + DestinationURI: dest, + RemoteParameters: 0, + CookieIn: 0, + Flags: flags, + } + + buf, err := encode(&payload) + if err != nil { + return err + } + + resp, err := l.request(constants.ProcMigratePerformParams, constants.ProgramRemote, &buf) + if err != nil { + return err + } + + r := <-resp + if r.Status != StatusOK { + return decodeError(r.Payload) + } + + return nil +} + // MigrateSetMaxSpeed set the maximum migration bandwidth (in MiB/s) for a // domain which is being migrated to another host. Specifying a negative value // results in an essentially unlimited value being provided to the hypervisor. diff --git a/libvirt_test.go b/libvirt_test.go index 74ecb21..1edba40 100644 --- a/libvirt_test.go +++ b/libvirt_test.go @@ -43,6 +43,43 @@ func TestDisconnect(t *testing.T) { } } +func TestMigrate(t *testing.T) { + conn := libvirttest.New() + l := New(conn) + + var flags MigrateFlags + flags = MigrateFlagLive | + MigrateFlagPeerToPeer | + MigrateFlagPersistDestination | + MigrateFlagChangeProtection | + MigrateFlagAbortOnError | + MigrateFlagAutoConverge | + MigrateFlagNonSharedDisk + + if err := l.Migrate("test", "qemu+tcp://foo/system", flags); err != nil { + t.Fatalf("unexpected live migration error: %v", err) + } +} + +func TestMigrateInvalidDest(t *testing.T) { + conn := libvirttest.New() + l := New(conn) + + var flags MigrateFlags + flags = MigrateFlagLive | + MigrateFlagPeerToPeer | + MigrateFlagPersistDestination | + MigrateFlagChangeProtection | + MigrateFlagAbortOnError | + MigrateFlagAutoConverge | + MigrateFlagNonSharedDisk + + dest := ":$'" + if err := l.Migrate("test", dest, flags); err == nil { + t.Fatalf("expected invalid dest uri %q to fail", dest) + } +} + func TestMigrateSetMaxSpeed(t *testing.T) { conn := libvirttest.New() l := New(conn) diff --git a/libvirttest/libvirt.go b/libvirttest/libvirt.go index 4eee906..91461ef 100644 --- a/libvirttest/libvirt.go +++ b/libvirttest/libvirt.go @@ -94,6 +94,19 @@ var testDisconnectReply = []byte{ 0x00, 0x00, 0x00, 0x00, // status } +var testMigrateReply = []byte{ + 0x00, 0x00, 0x00, 0x20, // length + 0x20, 0x00, 0x80, 0x86, // program + 0x00, 0x00, 0x00, 0x01, // version + 0x00, 0x00, 0x01, 0x31, // procedure + 0x00, 0x00, 0x00, 0x01, // type + 0x00, 0x00, 0x00, 0x00, // serial + 0x00, 0x00, 0x00, 0x00, // status + + // cookie out: 0 + 0x00, 0x00, 0x00, 0x00, +} + var testRunReply = []byte{ 0x00, 0x00, 0x00, 0x74, // length 0x20, 0x00, 0x80, 0x87, // program @@ -262,6 +275,8 @@ func (m *MockLibvirt) handleRemote(procedure uint32, conn net.Conn) { conn.Write(m.reply(testDomainsReply)) case constants.ProcDomainMigrateSetMaxSpeed: conn.Write(m.reply(testSetSpeedReply)) + case constants.ProcMigratePerformParams: + conn.Write(m.reply(testMigrateReply)) } }