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:
parent
0b294fc010
commit
128bc7d448
@ -29,10 +29,11 @@ const (
|
|||||||
ProcConnectOpen = 1
|
ProcConnectOpen = 1
|
||||||
ProcConnectClose = 2
|
ProcConnectClose = 2
|
||||||
ProcDomainLookupByName = 23
|
ProcDomainLookupByName = 23
|
||||||
ProcDomainMigrateSetMaxSpeed = 207
|
|
||||||
ProcAuthList = 66
|
ProcAuthList = 66
|
||||||
ProcConnectGetLibVersion = 157
|
ProcConnectGetLibVersion = 157
|
||||||
|
ProcDomainMigrateSetMaxSpeed = 207
|
||||||
ProcConnectListAllDomains = 273
|
ProcConnectListAllDomains = 273
|
||||||
|
ProcMigratePerformParams = 305
|
||||||
)
|
)
|
||||||
|
|
||||||
// qemu procedure identifiers
|
// qemu procedure identifiers
|
||||||
|
103
libvirt.go
103
libvirt.go
@ -23,6 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/davecgh/go-xdr/xdr2"
|
"github.com/davecgh/go-xdr/xdr2"
|
||||||
@ -77,6 +78,56 @@ type qemuError struct {
|
|||||||
} `json:"error"`
|
} `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.
|
// Connect establishes communication with the libvirt server.
|
||||||
// The underlying libvirt socket connection must be previously established.
|
// The underlying libvirt socket connection must be previously established.
|
||||||
func (l *Libvirt) Connect() error {
|
func (l *Libvirt) Connect() error {
|
||||||
@ -204,6 +255,58 @@ func (l *Libvirt) Events(dom string) (<-chan DomainEvent, error) {
|
|||||||
return c, nil
|
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
|
// MigrateSetMaxSpeed set the maximum migration bandwidth (in MiB/s) for a
|
||||||
// domain which is being migrated to another host. Specifying a negative value
|
// domain which is being migrated to another host. Specifying a negative value
|
||||||
// results in an essentially unlimited value being provided to the hypervisor.
|
// results in an essentially unlimited value being provided to the hypervisor.
|
||||||
|
@ -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) {
|
func TestMigrateSetMaxSpeed(t *testing.T) {
|
||||||
conn := libvirttest.New()
|
conn := libvirttest.New()
|
||||||
l := New(conn)
|
l := New(conn)
|
||||||
|
@ -94,6 +94,19 @@ var testDisconnectReply = []byte{
|
|||||||
0x00, 0x00, 0x00, 0x00, // status
|
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{
|
var testRunReply = []byte{
|
||||||
0x00, 0x00, 0x00, 0x74, // length
|
0x00, 0x00, 0x00, 0x74, // length
|
||||||
0x20, 0x00, 0x80, 0x87, // program
|
0x20, 0x00, 0x80, 0x87, // program
|
||||||
@ -262,6 +275,8 @@ func (m *MockLibvirt) handleRemote(procedure uint32, conn net.Conn) {
|
|||||||
conn.Write(m.reply(testDomainsReply))
|
conn.Write(m.reply(testDomainsReply))
|
||||||
case constants.ProcDomainMigrateSetMaxSpeed:
|
case constants.ProcDomainMigrateSetMaxSpeed:
|
||||||
conn.Write(m.reply(testSetSpeedReply))
|
conn.Write(m.reply(testSetSpeedReply))
|
||||||
|
case constants.ProcMigratePerformParams:
|
||||||
|
conn.Write(m.reply(testMigrateReply))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user