Add support for domain migrations

This adds basic support for domain migrations from one hypervisor to
another. Migration options, e.g., live, tunneled, compressed, etc..,
are specified by the constants described `constants.Migrate*`.

Two unknowns remain, 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 used by `virsh` will be Good Enough.
This commit is contained in:
Ben LeMasurier 2016-08-19 12:31:59 -06:00
parent 0b294fc010
commit 128bc7d448
No known key found for this signature in database
GPG Key ID: 248D430AE8E74189
4 changed files with 157 additions and 1 deletions

View File

@ -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

View File

@ -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.

View File

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

View File

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