From 6490524874143e88ceb227ebf8d3d4243c4b7d6a Mon Sep 17 00:00:00 2001 From: Daniil Rutskiy Date: Wed, 17 Jan 2018 18:17:37 +0300 Subject: [PATCH 1/5] Add support for server live-migration --- .../compute/v2/extensions/migrate/doc.go | 18 ++++++++- .../compute/v2/extensions/migrate/requests.go | 40 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/openstack/compute/v2/extensions/migrate/doc.go b/openstack/compute/v2/extensions/migrate/doc.go index 86750d6c6f..c32328a759 100644 --- a/openstack/compute/v2/extensions/migrate/doc.go +++ b/openstack/compute/v2/extensions/migrate/doc.go @@ -2,12 +2,28 @@ Package migrate provides functionality to migrate servers that have been provisioned by the OpenStack Compute service. -Example to Migrate a Server +Example of Migrate Server (migrate Action) serverID := "b16ba811-199d-4ffd-8839-ba96c1185a67" err := migrate.Migrate(computeClient, serverID).ExtractErr() if err != nil { panic(err) } + +Example of Live-Migrate Server (os-migrateLive Action) + + serverID := "b16ba811-199d-4ffd-8839-ba96c1185a67" + blockMigration := false + + migrationOpts := migrate.LiveMigrateOpts{ + Host: "01c0cadef72d47e28a672a76060d492c", + BlockMigration: &blockMigration, + } + + err := migrate.LiveMigrate(computeClient, serverID, migrationOpts).ExtractErr() + if err != nil { + panic(err) + } + */ package migrate diff --git a/openstack/compute/v2/extensions/migrate/requests.go b/openstack/compute/v2/extensions/migrate/requests.go index 9f263fa3ba..79ffd751b6 100644 --- a/openstack/compute/v2/extensions/migrate/requests.go +++ b/openstack/compute/v2/extensions/migrate/requests.go @@ -9,3 +9,43 @@ func Migrate(client *gophercloud.ServiceClient, id string) (r MigrateResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"migrate": nil}, nil, nil) return } + +// LiveMigrateOptsBuilder allows extensions to add additional parameters to the +// LiveMigrate request. +type LiveMigrateOptsBuilder interface { + ToLiveMigrateMap() (map[string]interface{}, error) +} + +// LiveMigrateOpts specifies parameters of live migrate action. +type LiveMigrateOpts struct { + + // The host to which to migrate the server. + // If this parameter is None, the scheduler chooses a host. + Host string `json:"host,omitempty"` + + // Set to True to migrate local disks by using block migration. + // If the source or destination host uses shared storage and you set + // this value to True, the live migration fails. + BlockMigration *bool `json:"block_migration,omitempty"` + + // Set to True to enable over commit when the destination host is checked + // for available disk space. Set to False to disable over commit. This setting + // affects only the libvirt virt driver. + DiskOverCommit *bool `json:"disk_over_commit,omitempty"` +} + +// ToLiveMigrateMap constructs a request body from LiveMigrateOpts. +func (opts LiveMigrateOpts) ToLiveMigrateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-migrateLive") +} + +// LiveMigrate will initiate a live-migration (without rebooting) of the instance to another host. +func LiveMigrate(client *gophercloud.ServiceClient, id string, opts LiveMigrateOptsBuilder) (r MigrateResult) { + b, err := opts.ToLiveMigrateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} From 8e53312033e9ead5a67bf1d66e6cfef484b76a7a Mon Sep 17 00:00:00 2001 From: Daniil Rutskiy Date: Wed, 17 Jan 2018 18:18:11 +0300 Subject: [PATCH 2/5] Add unit test --- .../v2/extensions/migrate/testing/fixtures.go | 15 +++++++++++++++ .../migrate/testing/requests_test.go | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/openstack/compute/v2/extensions/migrate/testing/fixtures.go b/openstack/compute/v2/extensions/migrate/testing/fixtures.go index 8a59aa3478..1d2f5902c2 100644 --- a/openstack/compute/v2/extensions/migrate/testing/fixtures.go +++ b/openstack/compute/v2/extensions/migrate/testing/fixtures.go @@ -16,3 +16,18 @@ func mockMigrateResponse(t *testing.T, id string) { w.WriteHeader(http.StatusAccepted) }) } + +func mockLiveMigrateResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "os-migrateLive": { + "host": "01c0cadef72d47e28a672a76060d492c", + "block_migration": false, + "disk_over_commit": true + } + }`) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/openstack/compute/v2/extensions/migrate/testing/requests_test.go b/openstack/compute/v2/extensions/migrate/testing/requests_test.go index 7d14365d6a..adb76461fb 100644 --- a/openstack/compute/v2/extensions/migrate/testing/requests_test.go +++ b/openstack/compute/v2/extensions/migrate/testing/requests_test.go @@ -19,3 +19,22 @@ func TestMigrate(t *testing.T) { err := migrate.Migrate(client.ServiceClient(), serverID).ExtractErr() th.AssertNoErr(t, err) } + +func TestLiveMigrate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + mockLiveMigrateResponse(t, serverID) + + blockMigration := false + diskOverCommit := true + + migrationOpts := migrate.LiveMigrateOpts{ + Host: "01c0cadef72d47e28a672a76060d492c", + BlockMigration: &blockMigration, + DiskOverCommit: &diskOverCommit, + } + + err := migrate.LiveMigrate(client.ServiceClient(), serverID, migrationOpts).ExtractErr() + th.AssertNoErr(t, err) +} From 36c6c90fbde4b52ab05bcc4c40a35f8598bc9ce5 Mon Sep 17 00:00:00 2001 From: Daniil Rutskiy Date: Fri, 19 Jan 2018 17:10:29 +0300 Subject: [PATCH 3/5] Fix 'Host' type `string` -> `*string` to be able to set it to nil --- openstack/compute/v2/extensions/migrate/doc.go | 3 ++- openstack/compute/v2/extensions/migrate/requests.go | 2 +- .../compute/v2/extensions/migrate/testing/requests_test.go | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openstack/compute/v2/extensions/migrate/doc.go b/openstack/compute/v2/extensions/migrate/doc.go index c32328a759..cf3067716d 100644 --- a/openstack/compute/v2/extensions/migrate/doc.go +++ b/openstack/compute/v2/extensions/migrate/doc.go @@ -13,10 +13,11 @@ Example of Migrate Server (migrate Action) Example of Live-Migrate Server (os-migrateLive Action) serverID := "b16ba811-199d-4ffd-8839-ba96c1185a67" + host := "01c0cadef72d47e28a672a76060d492c" blockMigration := false migrationOpts := migrate.LiveMigrateOpts{ - Host: "01c0cadef72d47e28a672a76060d492c", + Host: &host, BlockMigration: &blockMigration, } diff --git a/openstack/compute/v2/extensions/migrate/requests.go b/openstack/compute/v2/extensions/migrate/requests.go index 79ffd751b6..0cb6f8100b 100644 --- a/openstack/compute/v2/extensions/migrate/requests.go +++ b/openstack/compute/v2/extensions/migrate/requests.go @@ -21,7 +21,7 @@ type LiveMigrateOpts struct { // The host to which to migrate the server. // If this parameter is None, the scheduler chooses a host. - Host string `json:"host,omitempty"` + Host *string `json:"host"` // Set to True to migrate local disks by using block migration. // If the source or destination host uses shared storage and you set diff --git a/openstack/compute/v2/extensions/migrate/testing/requests_test.go b/openstack/compute/v2/extensions/migrate/testing/requests_test.go index adb76461fb..b6906b7839 100644 --- a/openstack/compute/v2/extensions/migrate/testing/requests_test.go +++ b/openstack/compute/v2/extensions/migrate/testing/requests_test.go @@ -26,11 +26,12 @@ func TestLiveMigrate(t *testing.T) { mockLiveMigrateResponse(t, serverID) + host := "01c0cadef72d47e28a672a76060d492c" blockMigration := false diskOverCommit := true migrationOpts := migrate.LiveMigrateOpts{ - Host: "01c0cadef72d47e28a672a76060d492c", + Host: &host, BlockMigration: &blockMigration, DiskOverCommit: &diskOverCommit, } From 4f5656e2e79dfd2e604f3b0d2a63b1dc2af38a35 Mon Sep 17 00:00:00 2001 From: Daniil Rutskiy Date: Fri, 19 Jan 2018 17:36:33 +0300 Subject: [PATCH 4/5] Add acceptance test --- .../openstack/compute/v2/migrate_test.go | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/acceptance/openstack/compute/v2/migrate_test.go b/acceptance/openstack/compute/v2/migrate_test.go index 4d03350100..7fdf405e61 100644 --- a/acceptance/openstack/compute/v2/migrate_test.go +++ b/acceptance/openstack/compute/v2/migrate_test.go @@ -28,3 +28,31 @@ func TestMigrate(t *testing.T) { t.Fatalf("Error during migration: %v", err) } } + +func TestLiveMigrate(t *testing.T) { + client, err := clients.NewComputeV2Client() + if err != nil { + t.Fatalf("Unable to create a compute client: %v", err) + } + + server, err := CreateServer(t, client) + if err != nil { + t.Fatalf("Unable to create server: %v", err) + } + defer DeleteServer(t, client, server) + + t.Logf("Attempting to migrate server %s", server.ID) + + blockMigration := false + diskOverCommit := false + + liveMigrateOpts := migrate.LiveMigrateOpts{ + BlockMigration: &blockMigration, + DiskOverCommit: &diskOverCommit, + } + + err = migrate.LiveMigrate(client, server.ID, liveMigrateOpts).ExtractErr() + if err != nil { + t.Fatalf("Error during live migration: %v", err) + } +} From 1d12f0dab2963ec79346351ce68bd6605a1c3cc2 Mon Sep 17 00:00:00 2001 From: Daniil Rutskiy Date: Sat, 20 Jan 2018 12:03:00 +0300 Subject: [PATCH 5/5] Add ability to turn-on/off acceptance test for server live-migration --- acceptance/clients/clients.go | 8 ++++++++ acceptance/openstack/compute/v2/migrate_test.go | 9 +++++++++ openstack/compute/v2/extensions/migrate/requests.go | 1 - 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/acceptance/clients/clients.go b/acceptance/clients/clients.go index fcf22a7e8d..d0cfe1ef64 100644 --- a/acceptance/clients/clients.go +++ b/acceptance/clients/clients.go @@ -6,6 +6,7 @@ package clients import ( "fmt" "os" + "strconv" "strings" "github.com/gophercloud/gophercloud" @@ -42,6 +43,9 @@ type AcceptanceTestChoices struct { // DBDatastoreTypeID is the datastore type version for DB tests. DBDatastoreVersion string + + // LiveMigrate indicates ability to run multi-node migration tests + LiveMigrate bool } // AcceptanceTestChoicesFromEnv populates a ComputeChoices struct from environment variables. @@ -56,6 +60,7 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { shareNetworkID := os.Getenv("OS_SHARE_NETWORK_ID") dbDatastoreType := os.Getenv("OS_DB_DATASTORE_TYPE") dbDatastoreVersion := os.Getenv("OS_DB_DATASTORE_VERSION") + liveMigrate := os.Getenv("OS_LIVE_MIGRATE") missing := make([]string, 0, 3) if imageID == "" { @@ -84,6 +89,8 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { notDistinct = "OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE must be distinct." } + LiveMigrate, _ := strconv.ParseBool(liveMigrate) + if len(missing) > 0 || notDistinct != "" { text := "You're missing some important setup:\n" if len(missing) > 0 { @@ -106,6 +113,7 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { ShareNetworkID: shareNetworkID, DBDatastoreType: dbDatastoreType, DBDatastoreVersion: dbDatastoreVersion, + LiveMigrate: LiveMigrate, }, nil } diff --git a/acceptance/openstack/compute/v2/migrate_test.go b/acceptance/openstack/compute/v2/migrate_test.go index 7fdf405e61..0a4d3140e4 100644 --- a/acceptance/openstack/compute/v2/migrate_test.go +++ b/acceptance/openstack/compute/v2/migrate_test.go @@ -30,6 +30,15 @@ func TestMigrate(t *testing.T) { } func TestLiveMigrate(t *testing.T) { + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + if !choices.LiveMigrate { + t.Skip() + } + client, err := clients.NewComputeV2Client() if err != nil { t.Fatalf("Unable to create a compute client: %v", err) diff --git a/openstack/compute/v2/extensions/migrate/requests.go b/openstack/compute/v2/extensions/migrate/requests.go index 0cb6f8100b..90ae62e381 100644 --- a/openstack/compute/v2/extensions/migrate/requests.go +++ b/openstack/compute/v2/extensions/migrate/requests.go @@ -18,7 +18,6 @@ type LiveMigrateOptsBuilder interface { // LiveMigrateOpts specifies parameters of live migrate action. type LiveMigrateOpts struct { - // The host to which to migrate the server. // If this parameter is None, the scheduler chooses a host. Host *string `json:"host"`