Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Add migrations list support in compute service #3244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions 21 internal/acceptance/openstack/compute/v2/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/gophercloud/gophercloud/v2/internal/acceptance/clients"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/migrations"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers"
th "github.com/gophercloud/gophercloud/v2/testhelper"
)
Expand All @@ -26,6 +27,16 @@ func TestMigrate(t *testing.T) {

err = servers.Migrate(context.TODO(), client, server.ID).ExtractErr()
th.AssertNoErr(t, err)

allPages, err := migrations.List(client, migrations.ListOpts{
InstanceID: &server.ID,
}).AllPages(context.TODO())
th.AssertNoErr(t, err)

allMigrations, err := migrations.ExtractMigrations(allPages)
th.AssertNoErr(t, err)

th.AssertIntGreaterOrEqual(t, len(allMigrations), 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI fails on this line:

migrate_test.go:39: Failure in migrate_test.go, line 39: The first value "0" is lesser than the second value "1"

I can't immediately spot what the issue is, the code looks fine, and this should be running as admin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You found your issue already :)

We'll need to add another compute for the nova job to test migrations. @stephenfin do you have an idea if that's easily feasible?

@Patzifist in case you want to give it a shot, the job is configured in https://github.com/gophercloud/gophercloud/blob/master/.github/workflows/functional-compute.yaml.

Copy link
Author

@Patzifist Patzifist Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest disabling the test and merging the request. what do you think about it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the lag anwering your question.

Rather than deleting a test that is otherwise good, could we instead skip it if there's less than 2 computes?

}

func TestLiveMigrate(t *testing.T) {
Expand All @@ -52,4 +63,14 @@ func TestLiveMigrate(t *testing.T) {

err = servers.LiveMigrate(context.TODO(), client, server.ID, liveMigrateOpts).ExtractErr()
th.AssertNoErr(t, err)

allPages, err := migrations.List(client, migrations.ListOpts{
InstanceID: &server.ID,
}).AllPages(context.TODO())
th.AssertNoErr(t, err)

allMigrations, err := migrations.ExtractMigrations(allPages)
th.AssertNoErr(t, err)

th.AssertIntGreaterOrEqual(t, len(allMigrations), 1)
}
22 changes: 22 additions & 0 deletions 22 openstack/compute/v2/migrations/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package migrations

/*
Package migrations provides the ability to list data on migrations.

Example to List os-migrations:

pages, err := List(client, nil).AllPages(context.TODO())
if err != nil {
panic("fail to get migration pages")
}

migrations, err := ExtractMigrations(pages)
if err != nil {
panic("fail to list migrations")
}

for _, migration := range migrations {
fmt.Println(migration)
}

*/
72 changes: 72 additions & 0 deletions 72 openstack/compute/v2/migrations/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package migrations

import (
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/pagination"
"net/url"
"time"
)

type ListOptsBuilder interface {
ToMigrationsListQuery() (string, error)
}

type ListOpts struct {
// The source/destination compute node of migration to filter
Host *string `q:"host"`
// The uuid of the instance that migration is operated on to filter
InstanceID *string `q:"instance_uuid"`
// The type of migration to filter. Valid values are: evacuation, live-migration, migration, resize
MigrationType *string `q:"migration_type"`
// The source compute node of migration to filter
SourceCompute *string `q:"source_compute"`
// The status of migration to filter
Status *string `q:"status"`
// Requests a page size of items
Limit *int `q:"limit"`
// The UUID of the last-seen migration
Marker *string `q:"marker"`
// Filters the response by a date and time stamp when the migration last changed
ChangesSince *time.Time `q:"changes-since"`
// Filters the response by a date and time stamp when the migration last changed
ChangesBefore *time.Time `q:"changes-before"`
// Filter the migrations by the given user ID
UserID *string `q:"user_id"`
// Filter the migrations by the given project ID
ProjectID *string `q:"project_id"`
}

func (opts ListOpts) ToMigrationsListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}

params := q.Query()

if opts.ChangesSince != nil {
params.Add("changes-since", opts.ChangesSince.Format(time.RFC3339))
}

if opts.ChangesBefore != nil {
params.Add("changes-before", opts.ChangesBefore.Format(time.RFC3339))
}

q = &url.URL{RawQuery: params.Encode()}
return q.String(), nil
}

func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
reqUrl := listURL(client)
if opts != nil {
query, err := opts.ToMigrationsListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
reqUrl += query
}

return pagination.NewPager(client, reqUrl, func(r pagination.PageResult) pagination.Page {
return MigrationPage{pagination.SinglePageBase(r)}
})
}
82 changes: 82 additions & 0 deletions 82 openstack/compute/v2/migrations/results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package migrations

import (
"encoding/json"
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/pagination"
"time"
)

// Migration represents the details of a migration.
type Migration struct {
// The date and time when the resource was created
CreatedAt time.Time `json:"-"`
// The target compute for a migration
DestCompute string `json:"dest_compute"`
// The target host for a migration
DestHost string `json:"dest_host"`
// The target node for a migration.
DestNode string `json:"dest_node"`
// The ID of the server migration
Id int64 `json:"id"`
// The UUID of the server
InstanceID string `json:"instance_uuid"`
// In resize case, the flavor ID for resizing the server
NewInstanceTypeId int64 `json:"new_instance_type_id"`
// The flavor ID of the server when the migration was started
OldInstanceTypeId int64 `json:"old_instance_type_id"`
// The source compute for a migration
SourceCompute string `json:"source_compute"`
// The source node for a migration
SourceNode string `json:"source_node"`
// The current status of the migration
Status string `json:"status"`
// The date and time when the resource was updated
UpdatedAt time.Time `json:"-"`
// The type of the server migration. This is one of live-migration, migration, resize and evacuation
MigrationType string `json:"migration_type"`
// The UUID of the migration
Uuid string `json:"uuid"`
// The ID of the user which initiated the server migration
UserId string `json:"user_id"`
// The ID of the user which initiated the server migration
ProjectId string `json:"project_id"`
}

// UnmarshalJSON converts our JSON API response into our migration struct
func (i *Migration) UnmarshalJSON(b []byte) error {
type tmp Migration
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*i = Migration(s.tmp)

i.UpdatedAt = time.Time(s.UpdatedAt)
i.CreatedAt = time.Time(s.CreatedAt)
return err
}

type MigrationPage struct {
pagination.SinglePageBase
}

func (r MigrationPage) IsEmpty() (bool, error) {
migrations, err := ExtractMigrations(r)
return len(migrations) == 0, err
}

func ExtractMigrations(r pagination.Page) ([]Migration, error) {
var resp []Migration
err := ExtractMigrationsInto(r, &resp)
return resp, err
}

func ExtractMigrationsInto(r pagination.Page, v any) error {
return r.(MigrationPage).Result.ExtractIntoSlicePtr(v, "migrations")
}
2 changes: 2 additions & 0 deletions 2 openstack/compute/v2/migrations/testing/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// migrations unit tests
package testing
107 changes: 107 additions & 0 deletions 107 openstack/compute/v2/migrations/testing/fixtures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package testing

import (
"fmt"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/migrations"
th "github.com/gophercloud/gophercloud/v2/testhelper"
"github.com/gophercloud/gophercloud/v2/testhelper/client"
"net/http"
"testing"
"time"
)

// ListExpected represents an expected response from a List request.
var ListExpected = []migrations.Migration{
{
Id: 2,
CreatedAt: time.Date(2024, 11, 28, 8, 15, 6, 000000, time.UTC),
DestCompute: "hci0",
DestHost: "192.168.1.41",
DestNode: "hci0",
InstanceID: "6ba1f91a-50cc-48cd-9ce6-310991acb08d",
NewInstanceTypeId: 1,
OldInstanceTypeId: 1,
SourceCompute: "compute0",
SourceNode: "compute0",
Status: "completed",
UpdatedAt: time.Date(2024, 11, 28, 8, 15, 28, 000000, time.UTC),
MigrationType: "live-migration",
Uuid: "dde90a82-3059-4369-8bda-e0ba92713c54",
UserId: "admin",
ProjectId: "admin",
},
{
Id: 1,
CreatedAt: time.Date(2024, 11, 28, 8, 10, 02, 000000, time.UTC),
DestCompute: "compute0",
DestHost: "192.168.1.42",
DestNode: "compute0",
InstanceID: "6ba1f91a-50cc-48cd-9ce6-310991acb08d",
NewInstanceTypeId: 1,
OldInstanceTypeId: 1,
SourceCompute: "hci0",
SourceNode: "hci0",
Status: "completed",
UpdatedAt: time.Date(2024, 11, 28, 8, 10, 34, 000000, time.UTC),
MigrationType: "live-migration",
Uuid: "dde90a82-3059-4369-8bda-e0ba92713c54",
UserId: "admin",
ProjectId: "admin",
},
}

// HandleMigrationListSuccessfully sets up the test server to respond to a List request.
func HandleMigrationListSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/os-migrations", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)

w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, `{
"migrations": [
{
"id": 2,
"uuid": "dde90a82-3059-4369-8bda-e0ba92713c54",
"source_compute": "compute0",
"dest_compute": "hci0",
"source_node": "compute0",
"dest_node": "hci0",
"dest_host": "192.168.1.41",
"old_instance_type_id": 1,
"new_instance_type_id": 1,
"instance_uuid": "6ba1f91a-50cc-48cd-9ce6-310991acb08d",
"status": "completed",
"migration_type": "live-migration",
"user_id": "admin",
"project_id": "admin",
"created_at": "2024-11-28T08:15:06.000000",
"updated_at": "2024-11-28T08:15:28.000000"
},
{
"id": 1,
"uuid": "dde90a82-3059-4369-8bda-e0ba92713c54",
"source_compute": "hci0",
"dest_compute": "compute0",
"source_node": "hci0",
"dest_node": "compute0",
"dest_host": "192.168.1.42",
"old_instance_type_id": 1,
"new_instance_type_id": 1,
"instance_uuid": "6ba1f91a-50cc-48cd-9ce6-310991acb08d",
"status": "completed",
"migration_type": "live-migration",
"user_id": "admin",
"project_id": "admin",
"created_at": "2024-11-28T08:10:02.000000",
"updated_at": "2024-11-28T08:10:34.000000"
}
],
"migrations_links": [
{
"rel": "next",
"href": "http://192.168.122.161:8774/v2.1/os-migrations?instance_uuid=6ba1f91a-50cc-48cd-9ce6-310991acb08d&limit=1&marker=dde90a82-3059-4369-8bda-e0ba92713c54"
}
]
}`)
})
}
34 changes: 34 additions & 0 deletions 34 openstack/compute/v2/migrations/testing/request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package testing

import (
"context"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/migrations"
"github.com/gophercloud/gophercloud/v2/pagination"
th "github.com/gophercloud/gophercloud/v2/testhelper"
"github.com/gophercloud/gophercloud/v2/testhelper/client"
"testing"
)

func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleMigrationListSuccessfully(t)

expected := ListExpected
pages := 0
err := migrations.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
pages++

actual, err := migrations.ExtractMigrations(page)
th.AssertNoErr(t, err)

if len(actual) != 2 {
t.Fatalf("Expected 2 migrations, got %d", len(actual))
}
th.CheckDeepEquals(t, expected, actual)

return true, nil
})
th.AssertNoErr(t, err)
th.CheckEquals(t, 1, pages)
}
7 changes: 7 additions & 0 deletions 7 openstack/compute/v2/migrations/urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package migrations

import "github.com/gophercloud/gophercloud/v2"

func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-migrations")
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.