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:
		| @@ -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)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user