Merge pull request #7 from digitalocean/migrations
Add support for domain migrations
This commit is contained in:
commit
0417eacbef
@ -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
|
||||
|
103
libvirt.go
103
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.
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user