From c67ff59a5539097ffaf479bf5db738d18eb0ece9 Mon Sep 17 00:00:00 2001 From: nikParasyr Date: Thu, 25 Aug 2022 14:02:16 +0200 Subject: [PATCH 01/44] Add description to flavor Add description to flavors which has been added as an optional attribute since nova 2.55 (Queens). --- acceptance/openstack/compute/v2/compute.go | 16 ++++-- openstack/compute/v2/flavors/requests.go | 5 ++ openstack/compute/v2/flavors/results.go | 5 ++ .../v2/flavors/testing/requests_test.go | 54 ++++++++++--------- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/acceptance/openstack/compute/v2/compute.go b/acceptance/openstack/compute/v2/compute.go index 21c94192d0..c34a5f5774 100644 --- a/acceptance/openstack/compute/v2/compute.go +++ b/acceptance/openstack/compute/v2/compute.go @@ -208,15 +208,20 @@ func CreateDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.Def // An error will be returned if the flavor could not be created. func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Flavor, error) { flavorName := tools.RandomString("flavor_", 5) + flavorDescription := fmt.Sprintf("I am %s and i am a yummy flavor", flavorName) + + // Microversion 2.55 is required to add description to flavor + client.Microversion = "2.55" t.Logf("Attempting to create flavor %s", flavorName) isPublic := true createOpts := flavors.CreateOpts{ - Name: flavorName, - RAM: 1, - VCPUs: 1, - Disk: gophercloud.IntToPointer(1), - IsPublic: &isPublic, + Name: flavorName, + RAM: 1, + VCPUs: 1, + Disk: gophercloud.IntToPointer(1), + IsPublic: &isPublic, + Description: flavorDescription, } flavor, err := flavors.Create(client, createOpts).Extract() @@ -231,6 +236,7 @@ func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Fla th.AssertEquals(t, flavor.Disk, 1) th.AssertEquals(t, flavor.VCPUs, 1) th.AssertEquals(t, flavor.IsPublic, true) + th.AssertEquals(t, flavor.Description, flavorDescription) return flavor, nil } diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go index 2c527b79fe..61a8b7dc4a 100644 --- a/openstack/compute/v2/flavors/requests.go +++ b/openstack/compute/v2/flavors/requests.go @@ -128,6 +128,11 @@ type CreateOpts struct { // Ephemeral is the amount of ephemeral disk space, measured in GB. Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` + + // Description is a free form description of the flavor. Limited to + // 65535 characters in length. Only printable characters are allowed. + // New in version 2.55 + Description string `json:"description,omitempty"` } // ToFlavorCreateMap constructs a request body from CreateOpts. diff --git a/openstack/compute/v2/flavors/results.go b/openstack/compute/v2/flavors/results.go index 92fe1b1809..3cad7747be 100644 --- a/openstack/compute/v2/flavors/results.go +++ b/openstack/compute/v2/flavors/results.go @@ -69,6 +69,11 @@ type Flavor struct { // Ephemeral is the amount of ephemeral disk space, measured in GB. Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"` + + // Description is a free form description of the flavor. Limited to + // 65535 characters in length. Only printable characters are allowed. + // New in version 2.55 + Description string `json:"description"` } func (r *Flavor) UnmarshalJSON(b []byte) error { diff --git a/openstack/compute/v2/flavors/testing/requests_test.go b/openstack/compute/v2/flavors/testing/requests_test.go index 3dddcd34fe..f045449aea 100644 --- a/openstack/compute/v2/flavors/testing/requests_test.go +++ b/openstack/compute/v2/flavors/testing/requests_test.go @@ -38,7 +38,8 @@ func TestListFlavors(t *testing.T) { "ram": 9216000, "swap":"", "os-flavor-access:is_public": true, - "OS-FLV-EXT-DATA:ephemeral": 10 + "OS-FLV-EXT-DATA:ephemeral": 10, + "description": "foo" }, { "id": "2", @@ -87,7 +88,7 @@ func TestListFlavors(t *testing.T) { } expected := []flavors.Flavor{ - {ID: "1", Name: "m1.tiny", VCPUs: 1, Disk: 1, RAM: 9216000, Swap: 0, IsPublic: true, Ephemeral: 10}, + {ID: "1", Name: "m1.tiny", VCPUs: 1, Disk: 1, RAM: 9216000, Swap: 0, IsPublic: true, Ephemeral: 10, Description: "foo"}, {ID: "2", Name: "m1.small", VCPUs: 1, Disk: 20, RAM: 2048, Swap: 1000, IsPublic: true, Ephemeral: 0}, {ID: "3", Name: "m1.medium", VCPUs: 2, Disk: 40, RAM: 4096, Swap: 1000, IsPublic: false, Ephemeral: 0}, } @@ -124,7 +125,8 @@ func TestGetFlavor(t *testing.T) { "ram": 512, "vcpus": 1, "rxtx_factor": 1, - "swap": "" + "swap": "", + "description": "foo" } } `) @@ -136,13 +138,14 @@ func TestGetFlavor(t *testing.T) { } expected := &flavors.Flavor{ - ID: "1", - Name: "m1.tiny", - Disk: 1, - RAM: 512, - VCPUs: 1, - RxTxFactor: 1, - Swap: 0, + ID: "1", + Name: "m1.tiny", + Disk: 1, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1, + Swap: 0, + Description: "foo", } if !reflect.DeepEqual(expected, actual) { t.Errorf("Expected %#v, but was %#v", expected, actual) @@ -167,7 +170,8 @@ func TestCreateFlavor(t *testing.T) { "ram": 512, "vcpus": 1, "rxtx_factor": 1, - "swap": "" + "swap": "", + "description": "foo" } } `) @@ -175,12 +179,13 @@ func TestCreateFlavor(t *testing.T) { disk := 1 opts := &flavors.CreateOpts{ - ID: "1", - Name: "m1.tiny", - Disk: &disk, - RAM: 512, - VCPUs: 1, - RxTxFactor: 1.0, + ID: "1", + Name: "m1.tiny", + Disk: &disk, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1.0, + Description: "foo", } actual, err := flavors.Create(fake.ServiceClient(), opts).Extract() if err != nil { @@ -188,13 +193,14 @@ func TestCreateFlavor(t *testing.T) { } expected := &flavors.Flavor{ - ID: "1", - Name: "m1.tiny", - Disk: 1, - RAM: 512, - VCPUs: 1, - RxTxFactor: 1, - Swap: 0, + ID: "1", + Name: "m1.tiny", + Disk: 1, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1, + Swap: 0, + Description: "foo", } if !reflect.DeepEqual(expected, actual) { t.Errorf("Expected %#v, but was %#v", expected, actual) From 804107e980e9d10dd1104df1ed42a46b3f9f21c6 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Thu, 1 Sep 2022 08:14:51 +0200 Subject: [PATCH 02/44] Fix typo in blockstorage/v3/attachments docs Fixes #2457 --- openstack/blockstorage/v3/attachments/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack/blockstorage/v3/attachments/doc.go b/openstack/blockstorage/v3/attachments/doc.go index b3962d37f5..838d8962fc 100644 --- a/openstack/blockstorage/v3/attachments/doc.go +++ b/openstack/blockstorage/v3/attachments/doc.go @@ -29,7 +29,7 @@ Example to List Attachments Example to Create Attachment createOpts := &attachments.CreateOpts{ - InstanceiUUID: "uuid", + InstanceUUID: "uuid", VolumeUUID: "uuid" } From 5146c6c951de3c9019f1fa392dbc9f21a1b2fcc7 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Thu, 1 Sep 2022 09:25:03 +0200 Subject: [PATCH 03/44] actions: Add semver tools * Define three new labels: `semver:major`, `semver:minor`, `semver:patch` * Only allow merge if one of the `semver:` labels is present * Remove the `semver:` label upon new push to the pull request --- .github/semver-labels.yaml | 9 +++++++++ .github/workflows/semver-labels.yaml | 15 +++++++++++++++ .github/workflows/semver-require.yaml | 17 +++++++++++++++++ .github/workflows/semver-unlabel.yaml | 19 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 .github/semver-labels.yaml create mode 100644 .github/workflows/semver-labels.yaml create mode 100644 .github/workflows/semver-require.yaml create mode 100644 .github/workflows/semver-unlabel.yaml diff --git a/.github/semver-labels.yaml b/.github/semver-labels.yaml new file mode 100644 index 0000000000..f10205cc95 --- /dev/null +++ b/.github/semver-labels.yaml @@ -0,0 +1,9 @@ +- name: semver:major + description: Breaking change + color: '9E1957' +- name: semver:minor + description: Backwards-compatible change + color: 'D6FC76' +- name: semver:patch + description: No API change + color: 'DBF568' diff --git a/.github/workflows/semver-labels.yaml b/.github/workflows/semver-labels.yaml new file mode 100644 index 0000000000..3901a4458e --- /dev/null +++ b/.github/workflows/semver-labels.yaml @@ -0,0 +1,15 @@ +name: Semver labels +on: + push: + branches: + - master + paths: + - .github/semver-labels.yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: micnncim/action-label-syncer@v1 + with: + manifest: .github/semver-labels.yaml diff --git a/.github/workflows/semver-require.yaml b/.github/workflows/semver-require.yaml new file mode 100644 index 0000000000..beac272b0e --- /dev/null +++ b/.github/workflows/semver-require.yaml @@ -0,0 +1,17 @@ +name: Pull Request Labels +on: + pull_request: + types: + - opened + - labeled + - unlabeled + - synchronize +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: mheap/github-action-required-labels@v2 + with: + mode: exactly + count: 1 + labels: "semver:patch, semver:minor, semver:major" diff --git a/.github/workflows/semver-unlabel.yaml b/.github/workflows/semver-unlabel.yaml new file mode 100644 index 0000000000..e0c835d559 --- /dev/null +++ b/.github/workflows/semver-unlabel.yaml @@ -0,0 +1,19 @@ +name: 'Reset semver labels on PR push' + +# **What it does**: When the content of a PR changes, this workflow removes the semver label +# **Why we have it**: To make sure semver labels are up-to-date. +# **Who does it impact**: Pull requests. + +on: + pull_request: + types: + - synchronize + +jobs: + remove-semver-label: + runs-on: ubuntu-latest + steps: + - name: Remove the semver label + uses: andymckay/labeler@1.0.4 + with: + remove-labels: "semver:patch, semver:minor, semver:major" From d777a1c3f7d4b139c43b1fc1dae87bce5f2ab78b Mon Sep 17 00:00:00 2001 From: nikParasyr Date: Tue, 30 Aug 2022 17:22:16 +0200 Subject: [PATCH 04/44] Add support for Update for flavors --- .../openstack/compute/v2/flavors_test.go | 24 +++++++++ openstack/compute/v2/flavors/doc.go | 13 +++++ openstack/compute/v2/flavors/requests.go | 31 ++++++++++++ openstack/compute/v2/flavors/results.go | 6 +++ .../v2/flavors/testing/requests_test.go | 49 +++++++++++++++++++ openstack/compute/v2/flavors/urls.go | 4 ++ 6 files changed, 127 insertions(+) diff --git a/acceptance/openstack/compute/v2/flavors_test.go b/acceptance/openstack/compute/v2/flavors_test.go index c58f99452e..9e3ec1db45 100644 --- a/acceptance/openstack/compute/v2/flavors_test.go +++ b/acceptance/openstack/compute/v2/flavors_test.go @@ -90,6 +90,30 @@ func TestFlavorsCreateDelete(t *testing.T) { tools.PrintResource(t, flavor) } +func TestFlavorsCreateUpdateDelete(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + flavor, err := CreateFlavor(t, client) + th.AssertNoErr(t, err) + defer DeleteFlavor(t, client, flavor) + + tools.PrintResource(t, flavor) + + newFlavorDescription := "This is the new description" + updateOpts := flavors.UpdateOpts{ + Description: newFlavorDescription, + } + + flavor, err = flavors.Update(client, flavor.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, flavor.Description, newFlavorDescription) + + tools.PrintResource(t, flavor) +} + func TestFlavorsAccessesList(t *testing.T) { clients.RequireAdmin(t) diff --git a/openstack/compute/v2/flavors/doc.go b/openstack/compute/v2/flavors/doc.go index 34d8764fad..747966d8d9 100644 --- a/openstack/compute/v2/flavors/doc.go +++ b/openstack/compute/v2/flavors/doc.go @@ -42,6 +42,19 @@ Example to Create a Flavor panic(err) } +Example to Update a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + updateOpts := flavors.UpdateOpts{ + Description: "This is a good description" + } + + flavor, err := flavors.Update(computeClient, flavorID, updateOpts).Extract() + if err != nil { + panic(err) + } + Example to List Flavor Access flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go index 61a8b7dc4a..3887cdfdca 100644 --- a/openstack/compute/v2/flavors/requests.go +++ b/openstack/compute/v2/flavors/requests.go @@ -154,6 +154,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } +type UpdateOptsBuilder interface { + ToFlavorUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies parameters used for updating a flavor. +type UpdateOpts struct { + // Description is a free form description of the flavor. Limited to + // 65535 characters in length. Only printable characters are allowed. + // New in version 2.55 + Description string `json:"description,omitempty"` +} + +// ToFlavorUpdateMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToFlavorUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "flavor") +} + +// Update requests the update of a new flavor. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToFlavorUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + // Get retrieves details of a single flavor. Use Extract to convert its // result into a Flavor. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { diff --git a/openstack/compute/v2/flavors/results.go b/openstack/compute/v2/flavors/results.go index 3cad7747be..0234402ed2 100644 --- a/openstack/compute/v2/flavors/results.go +++ b/openstack/compute/v2/flavors/results.go @@ -18,6 +18,12 @@ type CreateResult struct { commonResult } +// UpdateResult is the response of a Put operation. Call its Extract method to +// interpret it as a Flavor. +type UpdateResult struct { + commonResult +} + // GetResult is the response of a Get operations. Call its Extract method to // interpret it as a Flavor. type GetResult struct { diff --git a/openstack/compute/v2/flavors/testing/requests_test.go b/openstack/compute/v2/flavors/testing/requests_test.go index f045449aea..05b7fbb57a 100644 --- a/openstack/compute/v2/flavors/testing/requests_test.go +++ b/openstack/compute/v2/flavors/testing/requests_test.go @@ -207,6 +207,55 @@ func TestCreateFlavor(t *testing.T) { } } +func TestUpdateFlavor(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/flavors/12345678", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ` + { + "flavor": { + "id": "1", + "name": "m1.tiny", + "disk": 1, + "ram": 512, + "vcpus": 1, + "rxtx_factor": 1, + "swap": "", + "description": "foo" + } + } + `) + }) + + opts := &flavors.UpdateOpts{ + Description: "foo", + } + actual, err := flavors.Update(fake.ServiceClient(), "12345678", opts).Extract() + if err != nil { + t.Fatalf("Unable to update flavor: %v", err) + } + + expected := &flavors.Flavor{ + ID: "1", + Name: "m1.tiny", + Disk: 1, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1, + Swap: 0, + Description: "foo", + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + func TestDeleteFlavor(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/openstack/compute/v2/flavors/urls.go b/openstack/compute/v2/flavors/urls.go index 8620dd78ad..65bbb65401 100644 --- a/openstack/compute/v2/flavors/urls.go +++ b/openstack/compute/v2/flavors/urls.go @@ -16,6 +16,10 @@ func createURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("flavors") } +func updateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id) +} + func deleteURL(client *gophercloud.ServiceClient, id string) string { return client.ServiceURL("flavors", id) } From 966e902b74b8fe3d7f0c4049ddd92ac568a7536d Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Tue, 6 Sep 2022 11:33:04 +0200 Subject: [PATCH 05/44] Unlabeler: Fix permissions issue Run the action in the context of the base of the pull request, rather than in the context of the merge commit. This change also restricts the permissions to what is strictly needed for unlabeling. --- .github/workflows/semver-unlabel.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/semver-unlabel.yaml b/.github/workflows/semver-unlabel.yaml index e0c835d559..b949512370 100644 --- a/.github/workflows/semver-unlabel.yaml +++ b/.github/workflows/semver-unlabel.yaml @@ -5,12 +5,14 @@ name: 'Reset semver labels on PR push' # **Who does it impact**: Pull requests. on: - pull_request: + pull_request_target: types: - synchronize jobs: remove-semver-label: + permissions: + pull-requests: write runs-on: ubuntu-latest steps: - name: Remove the semver label From 4ae3a080dff820340fff94617b5e2aec6954305b Mon Sep 17 00:00:00 2001 From: emilmaruszczak Date: Tue, 6 Sep 2022 19:56:08 +0200 Subject: [PATCH 06/44] Add GetEnforcementModel operation --- openstack/identity/v3/limits/doc.go | 7 +++++ openstack/identity/v3/limits/requests.go | 7 +++++ openstack/identity/v3/limits/results.go | 25 ++++++++++++++++ .../identity/v3/limits/testing/fixtures.go | 29 +++++++++++++++++++ .../v3/limits/testing/requests_test.go | 10 +++++++ openstack/identity/v3/limits/urls.go | 4 +++ 6 files changed, 82 insertions(+) diff --git a/openstack/identity/v3/limits/doc.go b/openstack/identity/v3/limits/doc.go index db2fc8a2ed..4f97669eff 100644 --- a/openstack/identity/v3/limits/doc.go +++ b/openstack/identity/v3/limits/doc.go @@ -2,6 +2,13 @@ Package limits provides information and interaction with limits for the Openstack Identity service. +Example to Get EnforcementModel + + model, err := limits.GetEnforcementModel(identityClient).Extract() + if err != nil { + panic(err) + } + Example to List Limits listOpts := limits.ListOpts{ diff --git a/openstack/identity/v3/limits/requests.go b/openstack/identity/v3/limits/requests.go index f9e78e7339..1bdac9dc30 100644 --- a/openstack/identity/v3/limits/requests.go +++ b/openstack/identity/v3/limits/requests.go @@ -5,6 +5,13 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +// Get retrieves details on a single limit, by ID. +func GetEnforcementModel(client *gophercloud.ServiceClient) (r EnforcementModelResult) { + resp, err := client.Get(enforcementModelURL(client), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + // ListOptsBuilder allows extensions to add additional parameters to // the List request type ListOptsBuilder interface { diff --git a/openstack/identity/v3/limits/results.go b/openstack/identity/v3/limits/results.go index ba57ad9dae..16ba63bc77 100644 --- a/openstack/identity/v3/limits/results.go +++ b/openstack/identity/v3/limits/results.go @@ -1,9 +1,34 @@ package limits import ( + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) +// A model describing the configured enforcement model used by the deployment. +type EnforcementModel struct { + // The name of the enforcement model. + Name string `json:"name"` + + // A short description of the enforcement model used. + Description string `json:"description"` +} + +// EnforcementModelResult is the response from a GetEnforcementModel operation. Call its Extract method +// to interpret it as a EnforcementModel. +type EnforcementModelResult struct { + gophercloud.Result +} + +// Extract interprets EnforcementModelResult as a EnforcementModel. +func (r EnforcementModelResult) Extract() (*EnforcementModel, error) { + var out struct { + Model *EnforcementModel `json:"model"` + } + err := r.ExtractInto(&out) + return out.Model, err +} + // A limit is the limit that override the registered limit for each project. type Limit struct { // ID is the unique ID of the limit. diff --git a/openstack/identity/v3/limits/testing/fixtures.go b/openstack/identity/v3/limits/testing/fixtures.go index 69ab12ad45..ed502b3d01 100644 --- a/openstack/identity/v3/limits/testing/fixtures.go +++ b/openstack/identity/v3/limits/testing/fixtures.go @@ -10,6 +10,15 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +const GetEnforcementModelOutput = ` +{ + "model": { + "description": "Limit enforcement and validation does not take project hierarchy into consideration.", + "name": "flat" + } +} +` + // ListOutput provides a single page of List results. const ListOutput = ` { @@ -49,6 +58,12 @@ const ListOutput = ` } ` +// Model is the enforcement model in the GetEnforcementModel request. +var Model = limits.EnforcementModel{ + Name: "flat", + Description: "Limit enforcement and validation does not take project hierarchy into consideration.", +} + // FirstLimit is the first limit in the List request. var FirstLimit = limits.Limit{ ResourceName: "volume", @@ -78,6 +93,20 @@ var SecondLimit = limits.Limit{ // ExpectedLimitsSlice is the slice of limits expected to be returned from ListOutput. var ExpectedLimitsSlice = []limits.Limit{FirstLimit, SecondLimit} +// HandleGetEnforcementModelSuccessfully creates an HTTP handler at `/limits/model` on the +// test handler mux that responds with a enforcement model. +func HandleGetEnforcementModelSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/limits/model", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetEnforcementModelOutput) + }) +} + // HandleListLimitsSuccessfully creates an HTTP handler at `/limits` on the // test handler mux that responds with a list of two limits. func HandleListLimitsSuccessfully(t *testing.T) { diff --git a/openstack/identity/v3/limits/testing/requests_test.go b/openstack/identity/v3/limits/testing/requests_test.go index 42e55d691a..ec990357c3 100644 --- a/openstack/identity/v3/limits/testing/requests_test.go +++ b/openstack/identity/v3/limits/testing/requests_test.go @@ -9,6 +9,16 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +func TestGetEnforcementModel(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetEnforcementModelSuccessfully(t) + + actual, err := limits.GetEnforcementModel(client.ServiceClient()).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, Model, *actual) +} + func TestListLimits(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/openstack/identity/v3/limits/urls.go b/openstack/identity/v3/limits/urls.go index 1892614541..022c8d9e52 100644 --- a/openstack/identity/v3/limits/urls.go +++ b/openstack/identity/v3/limits/urls.go @@ -2,6 +2,10 @@ package limits import "github.com/gophercloud/gophercloud" +func enforcementModelURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("limits", "model") +} + func listURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("limits") } From 56631052c71b0304a403e0e6c058de3fc528178a Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Wed, 7 Sep 2022 16:34:41 +0200 Subject: [PATCH 07/44] tooling: Fix semver tooling issues --- .github/semver-labels.yaml | 4 ++-- .github/workflows/semver-labels.yaml | 7 +++++-- .github/workflows/semver-require.yaml | 4 ++-- .github/workflows/semver-unlabel.yaml | 8 ++++---- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/semver-labels.yaml b/.github/semver-labels.yaml index f10205cc95..7e9c8811b5 100644 --- a/.github/semver-labels.yaml +++ b/.github/semver-labels.yaml @@ -3,7 +3,7 @@ color: '9E1957' - name: semver:minor description: Backwards-compatible change - color: 'D6FC76' + color: 'FBCA04' - name: semver:patch description: No API change - color: 'DBF568' + color: '6E7624' diff --git a/.github/workflows/semver-labels.yaml b/.github/workflows/semver-labels.yaml index 3901a4458e..e6644a7bd7 100644 --- a/.github/workflows/semver-labels.yaml +++ b/.github/workflows/semver-labels.yaml @@ -1,15 +1,18 @@ -name: Semver labels +name: Ensure labels on: push: branches: - master paths: - .github/semver-labels.yaml + - .github/workflows/semver-labels.yaml jobs: - build: + semver: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: micnncim/action-label-syncer@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: manifest: .github/semver-labels.yaml diff --git a/.github/workflows/semver-require.yaml b/.github/workflows/semver-require.yaml index beac272b0e..58cc23b54c 100644 --- a/.github/workflows/semver-require.yaml +++ b/.github/workflows/semver-require.yaml @@ -1,4 +1,4 @@ -name: Pull Request Labels +name: Verify PR Labels on: pull_request: types: @@ -7,7 +7,7 @@ on: - unlabeled - synchronize jobs: - label: + semver: runs-on: ubuntu-latest steps: - uses: mheap/github-action-required-labels@v2 diff --git a/.github/workflows/semver-unlabel.yaml b/.github/workflows/semver-unlabel.yaml index b949512370..9fdf5558e4 100644 --- a/.github/workflows/semver-unlabel.yaml +++ b/.github/workflows/semver-unlabel.yaml @@ -1,4 +1,4 @@ -name: 'Reset semver labels on PR push' +name: Reset PR labels on push # **What it does**: When the content of a PR changes, this workflow removes the semver label # **Why we have it**: To make sure semver labels are up-to-date. @@ -10,12 +10,12 @@ on: - synchronize jobs: - remove-semver-label: - permissions: - pull-requests: write + semver: runs-on: ubuntu-latest steps: - name: Remove the semver label uses: andymckay/labeler@1.0.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: remove-labels: "semver:patch, semver:minor, semver:major" From 3ed00e1164f8fbe10eb9e85700afca5994d9ef90 Mon Sep 17 00:00:00 2001 From: shhgs Date: Wed, 1 Jun 2022 10:14:24 -0400 Subject: [PATCH 08/44] Neutron v2: ScheduleBGPSpeakerOpts, RemoveBGPSpeaker, ListDRAgentHostingBGPSpeakers --- .../v2/extensions/agents/agents_test.go | 53 +++++++++++ .../networking/v2/extensions/agents/doc.go | 29 ++++++ .../v2/extensions/agents/requests.go | 47 ++++++++++ .../v2/extensions/agents/results.go | 14 +++ .../v2/extensions/agents/testing/fixtures.go | 90 +++++++++++++++++++ .../agents/testing/requests_test.go | 82 +++++++++++++++++ .../networking/v2/extensions/agents/urls.go | 15 ++++ 7 files changed, 330 insertions(+) diff --git a/acceptance/openstack/networking/v2/extensions/agents/agents_test.go b/acceptance/openstack/networking/v2/extensions/agents/agents_test.go index 3be89554b8..6ffe53b1c1 100644 --- a/acceptance/openstack/networking/v2/extensions/agents/agents_test.go +++ b/acceptance/openstack/networking/v2/extensions/agents/agents_test.go @@ -8,8 +8,10 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + spk "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/bgp/speakers" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/bgp/speakers" th "github.com/gophercloud/gophercloud/testhelper" ) @@ -92,3 +94,54 @@ func TestAgentsRUD(t *testing.T) { err = agents.Delete(client, allAgents[0].ID).ExtractErr() th.AssertNoErr(t, err) } + +func TestBGPAgentRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // List BGP Agents + listOpts := &agents.ListOpts{ + AgentType: "BGP Dynamic Routing Agent", + } + allPages, err := agents.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allAgents, err := agents.ExtractAgents(allPages) + th.AssertNoErr(t, err) + + t.Logf("Retrieved BGP agents") + tools.PrintResource(t, allAgents) + + // Create a BGP Speaker + bgpSpeaker, err := spk.CreateBGPSpeaker(t, client) + th.AssertNoErr(t, err) + + // List the BGP Agent that accommodate the BGP Speaker + pages, err := agents.ListDRAgentHostingBGPSpeakers(client, bgpSpeaker.ID).AllPages() + th.AssertNoErr(t, err) + bgpAgents, err := agents.ExtractAgents(pages) + th.AssertNoErr(t, err) + th.AssertEquals(t, len(bgpAgents), 1) + bgpAgent := bgpAgents[0] + t.Logf("BGP Speaker %s has been scheudled to agent %s", bgpSpeaker.ID, bgpAgent.ID) + + // Remove the BGP speaker from the agent + err = agents.RemoveBGPSpeaker(client, bgpAgent.ID, bgpSpeaker.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Successfully removed speaker %s from agent %s", bgpSpeaker.ID, bgpAgent.ID) + + // Schedule a BGP Speaker to an agent + opts := agents.ScheduleBGPSpeakerOpts{ + SpeakerID: bgpSpeaker.ID, + } + err = agents.ScheduleBGPSpeaker(client, bgpAgent.ID, opts).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Successfully scheduled speaker %s to agent %s", bgpSpeaker.ID, bgpAgent.ID) + + // Delete the BGP Speaker + speakers.Delete(client, bgpSpeaker.ID).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Successfully deleted the BGP Speaker, %s", bgpSpeaker.ID) +} diff --git a/openstack/networking/v2/extensions/agents/doc.go b/openstack/networking/v2/extensions/agents/doc.go index e20b58c797..59eb233bd4 100644 --- a/openstack/networking/v2/extensions/agents/doc.go +++ b/openstack/networking/v2/extensions/agents/doc.go @@ -97,6 +97,35 @@ Example to List BGP speakers by dragent log.Printf("%v", s) } +Example to Schedule bgp speaker to dragent + + var opts agents.ScheduleBGPSpeakerOpts + opts.SpeakerID = speakerID + err := agents.ScheduleBGPSpeaker(c, agentID, opts).ExtractErr() + if err != nil { + log.Panic(err) + } + +Example to Remove bgp speaker from dragent + + err := agents.RemoveBGPSpeaker(c, agentID, speakerID).ExtractErr() + if err != nil { + log.Panic(err) + } + +Example to list dragents hosting specific bgp speaker + + pages, err := agents.ListDRAgentHostingBGPSpeakers(client, speakerID).AllPages() + if err != nil { + log.Panic(err) + } + allAgents, err := agents.ExtractAgents(pages) + if err != nil { + log.Panic(err) + } + for _, a := range allAgents { + log.Printf("%+v", a) + } */ package agents diff --git a/openstack/networking/v2/extensions/agents/requests.go b/openstack/networking/v2/extensions/agents/requests.go index 0aff95af0e..1f0729332f 100644 --- a/openstack/networking/v2/extensions/agents/requests.go +++ b/openstack/networking/v2/extensions/agents/requests.go @@ -158,3 +158,50 @@ func ListBGPSpeakers(c *gophercloud.ServiceClient, agentID string) pagination.Pa return ListBGPSpeakersResult{pagination.SinglePageBase(r)} }) } + +// ScheduleBGPSpeakerOptsBuilder declare a function that build ScheduleBGPSpeakerOpts into a request body +type ScheduleBGPSpeakerOptsBuilder interface { + ToAgentScheduleBGPSpeakerMap() (map[string]interface{}, error) +} + +// ScheduleBGPSpeakerOpts represents the data that would be POST to the endpoint +type ScheduleBGPSpeakerOpts struct { + SpeakerID string `json:"bgp_speaker_id" required:"true"` +} + +// ToAgentScheduleBGPSpeakerMap builds a request body from ScheduleBGPSpeakerOpts +func (opts ScheduleBGPSpeakerOpts) ToAgentScheduleBGPSpeakerMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// ScheduleBGPSpeaker schedule a BGP speaker to a BGP agent +// POST /v2.0/agents/{agent-id}/bgp-drinstances +func ScheduleBGPSpeaker(c *gophercloud.ServiceClient, agentID string, opts ScheduleBGPSpeakerOptsBuilder) (r ScheduleBGPSpeakerResult) { + b, err := opts.ToAgentScheduleBGPSpeakerMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Post(scheduleBGPSpeakersURL(c, agentID), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RemoveBGPSpeaker removoes a BGP speaker from a BGP agent +// DELETE /v2.0/agents/{agent-id}/bgp-drinstances +func RemoveBGPSpeaker(c *gophercloud.ServiceClient, agentID string, speakerID string) (r RemoveBGPSpeakerResult) { + resp, err := c.Delete(removeBGPSpeakersURL(c, agentID, speakerID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ListDRAgentHostingBGPSpeakers the dragents that are hosting a specific bgp speaker +// GET /v2.0/bgp-speakers/{bgp-speaker-id}/bgp-dragents +func ListDRAgentHostingBGPSpeakers(c *gophercloud.ServiceClient, bgpSpeakerID string) pagination.Pager { + url := listDRAgentHostingBGPSpeakersURL(c, bgpSpeakerID) + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AgentPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/openstack/networking/v2/extensions/agents/results.go b/openstack/networking/v2/extensions/agents/results.go index 4e84da2ef6..5f66eb37fe 100644 --- a/openstack/networking/v2/extensions/agents/results.go +++ b/openstack/networking/v2/extensions/agents/results.go @@ -55,6 +55,20 @@ type RemoveDHCPNetworkResult struct { gophercloud.ErrResult } +// ScheduleBGPSpeakerResult represents the result of adding a BGP speaker to a +// BGP DR Agent. ExtractErr method to determine if the request succeeded or +// failed. +type ScheduleBGPSpeakerResult struct { + gophercloud.ErrResult +} + +// RemoveBGPSpeakerResult represents the result of removing a BGP speaker from a +// BGP DR Agent. ExtractErr method to determine if the request succeeded or +// failed. +type RemoveBGPSpeakerResult struct { + gophercloud.ErrResult +} + // Agent represents a Neutron agent. type Agent struct { // ID is the id of the agent. diff --git a/openstack/networking/v2/extensions/agents/testing/fixtures.go b/openstack/networking/v2/extensions/agents/testing/fixtures.go index 9ab0c91a62..35b4ca5232 100644 --- a/openstack/networking/v2/extensions/agents/testing/fixtures.go +++ b/openstack/networking/v2/extensions/agents/testing/fixtures.go @@ -255,3 +255,93 @@ const ListBGPSpeakersResult = ` ] } ` +const ScheduleBGPSpeakerRequest = ` +{ + "bgp_speaker_id": "8edb2c68-0654-49a9-b3fe-030f92e3ddf6" +} +` + +var BGPAgent1 = agents.Agent{ + ID: "60d78b78-b56b-4d91-a174-2c03159f6bb6", + AdminStateUp: true, + AgentType: "BGP dynamic routing agent", + Alive: true, + Binary: "neutron-bgp-dragent", + Configurations: map[string]interface{}{ + "advertise_routes": float64(2), + "bgp_peers": float64(2), + "bgp_speakers": float64(1), + }, + CreatedAt: time.Date(2020, 9, 17, 20, 8, 58, 0, time.UTC), + StartedAt: time.Date(2021, 5, 4, 11, 13, 12, 0, time.UTC), + HeartbeatTimestamp: time.Date(2021, 9, 13, 19, 55, 1, 0, time.UTC), + Host: "agent1.example.com", + Topic: "bgp_dragent", +} + +var BGPAgent2 = agents.Agent{ + ID: "d0bdcea2-1d02-4c1d-9e79-b827e77acc22", + AdminStateUp: true, + AgentType: "BGP dynamic routing agent", + Alive: true, + Binary: "neutron-bgp-dragent", + Configurations: map[string]interface{}{ + "advertise_routes": float64(2), + "bgp_peers": float64(2), + "bgp_speakers": float64(1), + }, + CreatedAt: time.Date(2020, 9, 17, 20, 8, 15, 0, time.UTC), + StartedAt: time.Date(2021, 5, 4, 11, 13, 13, 0, time.UTC), + HeartbeatTimestamp: time.Date(2021, 9, 13, 19, 54, 47, 0, time.UTC), + Host: "agent2.example.com", + Topic: "bgp_dragent", +} + +const ListDRAgentHostingBGPSpeakersResult = ` +{ + "agents": [ + { + "binary": "neutron-bgp-dragent", + "description": null, + "availability_zone": null, + "heartbeat_timestamp": "2021-09-13 19:55:01", + "admin_state_up": true, + "resources_synced": null, + "alive": true, + "topic": "bgp_dragent", + "host": "agent1.example.com", + "agent_type": "BGP dynamic routing agent", + "resource_versions": {}, + "created_at": "2020-09-17 20:08:58", + "started_at": "2021-05-04 11:13:12", + "id": "60d78b78-b56b-4d91-a174-2c03159f6bb6", + "configurations": { + "advertise_routes": 2, + "bgp_peers": 2, + "bgp_speakers": 1 + } + }, + { + "binary": "neutron-bgp-dragent", + "description": null, + "availability_zone": null, + "heartbeat_timestamp": "2021-09-13 19:54:47", + "admin_state_up": true, + "resources_synced": null, + "alive": true, + "topic": "bgp_dragent", + "host": "agent2.example.com", + "agent_type": "BGP dynamic routing agent", + "resource_versions": {}, + "created_at": "2020-09-17 20:08:15", + "started_at": "2021-05-04 11:13:13", + "id": "d0bdcea2-1d02-4c1d-9e79-b827e77acc22", + "configurations": { + "advertise_routes": 2, + "bgp_peers": 2, + "bgp_speakers": 1 + } + } + ] +} +` diff --git a/openstack/networking/v2/extensions/agents/testing/requests_test.go b/openstack/networking/v2/extensions/agents/testing/requests_test.go index 323e750c4a..10411b5910 100644 --- a/openstack/networking/v2/extensions/agents/testing/requests_test.go +++ b/openstack/networking/v2/extensions/agents/testing/requests_test.go @@ -241,3 +241,85 @@ func TestListBGPSpeakers(t *testing.T) { t.Errorf("Expected 1 page, got %d", count) } } + +func TestScheduleBGPSpeaker(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + agentID := "30d76012-46de-4215-aaa1-a1630d01d891" + speakerID := "8edb2c68-0654-49a9-b3fe-030f92e3ddf6" + + th.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ScheduleBGPSpeakerRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + }) + + var opts agents.ScheduleBGPSpeakerOpts + opts.SpeakerID = speakerID + err := agents.ScheduleBGPSpeaker(fake.ServiceClient(), agentID, opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestRemoveBGPSpeaker(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + agentID := "30d76012-46de-4215-aaa1-a1630d01d891" + speakerID := "8edb2c68-0654-49a9-b3fe-030f92e3ddf6" + + th.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances/"+speakerID, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) + + err := agents.RemoveBGPSpeaker(fake.ServiceClient(), agentID, speakerID).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestListDRAgentHostingBGPSpeakers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + speakerID := "3f511b1b-d541-45f1-aa98-2e44e8183d4c" + th.Mux.HandleFunc("/v2.0/bgp-speakers/"+speakerID+"/bgp-dragents", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListDRAgentHostingBGPSpeakersResult) + }) + + count := 0 + agents.ListDRAgentHostingBGPSpeakers(fake.ServiceClient(), speakerID).EachPage( + func(page pagination.Page) (bool, error) { + count++ + actual, err := agents.ExtractAgents(page) + + if err != nil { + t.Errorf("Failed to extract agents: %v", err) + return false, nil + } + + expected := []agents.Agent{BGPAgent1, BGPAgent2} + th.CheckDeepEquals(t, expected, actual) + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} diff --git a/openstack/networking/v2/extensions/agents/urls.go b/openstack/networking/v2/extensions/agents/urls.go index e357d0a08c..237760c510 100644 --- a/openstack/networking/v2/extensions/agents/urls.go +++ b/openstack/networking/v2/extensions/agents/urls.go @@ -50,3 +50,18 @@ func removeDHCPNetworkURL(c *gophercloud.ServiceClient, id string, networkID str func listBGPSpeakersURL(c *gophercloud.ServiceClient, agentID string) string { return c.ServiceURL(resourcePath, agentID, bgpSpeakersResourcePath) } + +// return /v2.0/agents/{agent-id}/bgp-drinstances +func scheduleBGPSpeakersURL(c *gophercloud.ServiceClient, id string) string { + return listBGPSpeakersURL(c, id) +} + +// return /v2.0/agents/{agent-id}/bgp-drinstances/{bgp-speaker-id} +func removeBGPSpeakersURL(c *gophercloud.ServiceClient, agentID string, speakerID string) string { + return c.ServiceURL(resourcePath, agentID, bgpSpeakersResourcePath, speakerID) +} + +// return /v2.0/bgp-speakers/{bgp-speaker-id}/bgp-dragents +func listDRAgentHostingBGPSpeakersURL(c *gophercloud.ServiceClient, speakerID string) string { + return c.ServiceURL("bgp-speakers", speakerID, "bgp-dragents") +} From a90725c57f7077bc29d8d6190fb082a301dcdf9a Mon Sep 17 00:00:00 2001 From: shhgs Date: Wed, 7 Sep 2022 16:30:40 -0400 Subject: [PATCH 09/44] Use agent.Configurations to assert BGP speaker scheduling --- .../v2/extensions/agents/agents_test.go | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/acceptance/openstack/networking/v2/extensions/agents/agents_test.go b/acceptance/openstack/networking/v2/extensions/agents/agents_test.go index 6ffe53b1c1..f0881cedc2 100644 --- a/acceptance/openstack/networking/v2/extensions/agents/agents_test.go +++ b/acceptance/openstack/networking/v2/extensions/agents/agents_test.go @@ -5,6 +5,7 @@ package agents import ( "testing" + "time" "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" @@ -96,6 +97,7 @@ func TestAgentsRUD(t *testing.T) { } func TestBGPAgentRUD(t *testing.T) { + waitTime := 30 * time.Second clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() @@ -117,21 +119,40 @@ func TestBGPAgentRUD(t *testing.T) { // Create a BGP Speaker bgpSpeaker, err := spk.CreateBGPSpeaker(t, client) th.AssertNoErr(t, err) + time.Sleep(waitTime) - // List the BGP Agent that accommodate the BGP Speaker + // List the BGP Agents that accommodate the BGP Speaker pages, err := agents.ListDRAgentHostingBGPSpeakers(client, bgpSpeaker.ID).AllPages() th.AssertNoErr(t, err) bgpAgents, err := agents.ExtractAgents(pages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(bgpAgents), 1) - bgpAgent := bgpAgents[0] - t.Logf("BGP Speaker %s has been scheudled to agent %s", bgpSpeaker.ID, bgpAgent.ID) + th.AssertIntGreaterOrEqual(t, len(bgpAgents), 1) + for _, agt := range bgpAgents { + t.Logf("BGP Speaker %s has been scheduled to agent %s", bgpSpeaker.ID, agt.ID) + bgpAgent, err := agents.Get(client, agt.ID).Extract() + th.AssertNoErr(t, err) + numOfSpeakers := int(bgpAgent.Configurations["bgp_speakers"].(float64)) + th.AssertEquals(t, numOfSpeakers, 1) + } - // Remove the BGP speaker from the agent - err = agents.RemoveBGPSpeaker(client, bgpAgent.ID, bgpSpeaker.ID).ExtractErr() + // Remove the BGP Speaker from the first agent + err = agents.RemoveBGPSpeaker(client, bgpAgents[0].ID, bgpSpeaker.ID).ExtractErr() th.AssertNoErr(t, err) - t.Logf("Successfully removed speaker %s from agent %s", bgpSpeaker.ID, bgpAgent.ID) + t.Logf("BGP Speaker %s has been removed from agent %s", bgpSpeaker.ID, bgpAgents[0].ID) + time.Sleep(waitTime) + bgpAgent, err := agents.Get(client, bgpAgents[0].ID).Extract() + th.AssertNoErr(t, err) + agentConf := bgpAgent.Configurations + th.AssertEquals(t, int(agentConf["bgp_speakers"].(float64)), 0) + // Remove all BGP Speakers from the agent + pages, err = agents.ListBGPSpeakers(client, bgpAgent.ID).AllPages() + th.AssertNoErr(t, err) + allSpeakers, err := agents.ExtractBGPSpeakers(pages) + th.AssertNoErr(t, err) + for _, speaker := range allSpeakers { + th.AssertNoErr(t, agents.RemoveBGPSpeaker(client, bgpAgent.ID, speaker.ID).ExtractErr()) + } // Schedule a BGP Speaker to an agent opts := agents.ScheduleBGPSpeakerOpts{ SpeakerID: bgpSpeaker.ID, @@ -139,9 +160,19 @@ func TestBGPAgentRUD(t *testing.T) { err = agents.ScheduleBGPSpeaker(client, bgpAgent.ID, opts).ExtractErr() th.AssertNoErr(t, err) t.Logf("Successfully scheduled speaker %s to agent %s", bgpSpeaker.ID, bgpAgent.ID) + time.Sleep(waitTime) + bgpAgent, err = agents.Get(client, bgpAgent.ID).Extract() + th.AssertNoErr(t, err) + agentConf = bgpAgent.Configurations + th.AssertEquals(t, 1, int(agentConf["bgp_speakers"].(float64))) // Delete the BGP Speaker speakers.Delete(client, bgpSpeaker.ID).ExtractErr() th.AssertNoErr(t, err) t.Logf("Successfully deleted the BGP Speaker, %s", bgpSpeaker.ID) + time.Sleep(waitTime) + bgpAgent, err = agents.Get(client, bgpAgent.ID).Extract() + th.AssertNoErr(t, err) + agentConf = bgpAgent.Configurations + th.AssertEquals(t, 0, int(agentConf["bgp_speakers"].(float64))) } From b0f867aed22bc2be7836479dc2ba0b82fe095e2b Mon Sep 17 00:00:00 2001 From: shhgs Date: Thu, 8 Sep 2022 09:03:32 -0400 Subject: [PATCH 10/44] Make literals constant var in the URL generation --- openstack/networking/v2/extensions/agents/requests.go | 2 +- openstack/networking/v2/extensions/agents/urls.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openstack/networking/v2/extensions/agents/requests.go b/openstack/networking/v2/extensions/agents/requests.go index 1f0729332f..707f710c4e 100644 --- a/openstack/networking/v2/extensions/agents/requests.go +++ b/openstack/networking/v2/extensions/agents/requests.go @@ -189,7 +189,7 @@ func ScheduleBGPSpeaker(c *gophercloud.ServiceClient, agentID string, opts Sched return } -// RemoveBGPSpeaker removoes a BGP speaker from a BGP agent +// RemoveBGPSpeaker removes a BGP speaker from a BGP agent // DELETE /v2.0/agents/{agent-id}/bgp-drinstances func RemoveBGPSpeaker(c *gophercloud.ServiceClient, agentID string, speakerID string) (r RemoveBGPSpeakerResult) { resp, err := c.Delete(removeBGPSpeakersURL(c, agentID, speakerID), nil) diff --git a/openstack/networking/v2/extensions/agents/urls.go b/openstack/networking/v2/extensions/agents/urls.go index 237760c510..f6021fce2d 100644 --- a/openstack/networking/v2/extensions/agents/urls.go +++ b/openstack/networking/v2/extensions/agents/urls.go @@ -5,6 +5,8 @@ import "github.com/gophercloud/gophercloud" const resourcePath = "agents" const dhcpNetworksResourcePath = "dhcp-networks" const bgpSpeakersResourcePath = "bgp-drinstances" +const bgpDRAgentSpeakersResourcePath = "bgp-speakers" +const bgpDRAgentAgentResourcePath = "bgp-dragents" func resourceURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL(resourcePath, id) @@ -63,5 +65,5 @@ func removeBGPSpeakersURL(c *gophercloud.ServiceClient, agentID string, speakerI // return /v2.0/bgp-speakers/{bgp-speaker-id}/bgp-dragents func listDRAgentHostingBGPSpeakersURL(c *gophercloud.ServiceClient, speakerID string) string { - return c.ServiceURL("bgp-speakers", speakerID, "bgp-dragents") + return c.ServiceURL(bgpDRAgentSpeakersResourcePath, speakerID, bgpDRAgentAgentResourcePath) } From 37211121f156ced1619c5326148412e5f9990bbf Mon Sep 17 00:00:00 2001 From: shhgs Date: Thu, 8 Sep 2022 11:22:01 -0400 Subject: [PATCH 11/44] Use tools.WaitForTimeout to poll the state --- .../v2/extensions/agents/agents_test.go | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/acceptance/openstack/networking/v2/extensions/agents/agents_test.go b/acceptance/openstack/networking/v2/extensions/agents/agents_test.go index f0881cedc2..db34e75a1c 100644 --- a/acceptance/openstack/networking/v2/extensions/agents/agents_test.go +++ b/acceptance/openstack/networking/v2/extensions/agents/agents_test.go @@ -97,7 +97,7 @@ func TestAgentsRUD(t *testing.T) { } func TestBGPAgentRUD(t *testing.T) { - waitTime := 30 * time.Second + timeout := 120 * time.Second clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() @@ -119,60 +119,82 @@ func TestBGPAgentRUD(t *testing.T) { // Create a BGP Speaker bgpSpeaker, err := spk.CreateBGPSpeaker(t, client) th.AssertNoErr(t, err) - time.Sleep(waitTime) - - // List the BGP Agents that accommodate the BGP Speaker pages, err := agents.ListDRAgentHostingBGPSpeakers(client, bgpSpeaker.ID).AllPages() th.AssertNoErr(t, err) bgpAgents, err := agents.ExtractAgents(pages) th.AssertNoErr(t, err) th.AssertIntGreaterOrEqual(t, len(bgpAgents), 1) - for _, agt := range bgpAgents { - t.Logf("BGP Speaker %s has been scheduled to agent %s", bgpSpeaker.ID, agt.ID) - bgpAgent, err := agents.Get(client, agt.ID).Extract() - th.AssertNoErr(t, err) - numOfSpeakers := int(bgpAgent.Configurations["bgp_speakers"].(float64)) - th.AssertEquals(t, numOfSpeakers, 1) - } + + // List the BGP Agents that accommodate the BGP Speaker + err = tools.WaitForTimeout( + func() (bool, error) { + flag := true + for _, agt := range bgpAgents { + t.Logf("BGP Speaker %s has been scheduled to agent %s", bgpSpeaker.ID, agt.ID) + bgpAgent, err := agents.Get(client, agt.ID).Extract() + th.AssertNoErr(t, err) + numOfSpeakers := int(bgpAgent.Configurations["bgp_speakers"].(float64)) + flag = flag && (numOfSpeakers == 1) + } + return flag, nil + }, timeout) + th.AssertNoErr(t, err) // Remove the BGP Speaker from the first agent err = agents.RemoveBGPSpeaker(client, bgpAgents[0].ID, bgpSpeaker.ID).ExtractErr() th.AssertNoErr(t, err) t.Logf("BGP Speaker %s has been removed from agent %s", bgpSpeaker.ID, bgpAgents[0].ID) - time.Sleep(waitTime) - bgpAgent, err := agents.Get(client, bgpAgents[0].ID).Extract() + err = tools.WaitForTimeout( + func() (bool, error) { + bgpAgent, err := agents.Get(client, bgpAgents[0].ID).Extract() + th.AssertNoErr(t, err) + agentConf := bgpAgent.Configurations + numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) + t.Logf("Agent %s has %d speakers", bgpAgent.ID, numOfSpeakers) + return numOfSpeakers == 0, nil + }, timeout) th.AssertNoErr(t, err) - agentConf := bgpAgent.Configurations - th.AssertEquals(t, int(agentConf["bgp_speakers"].(float64)), 0) // Remove all BGP Speakers from the agent - pages, err = agents.ListBGPSpeakers(client, bgpAgent.ID).AllPages() + pages, err = agents.ListBGPSpeakers(client, bgpAgents[0].ID).AllPages() th.AssertNoErr(t, err) allSpeakers, err := agents.ExtractBGPSpeakers(pages) th.AssertNoErr(t, err) for _, speaker := range allSpeakers { - th.AssertNoErr(t, agents.RemoveBGPSpeaker(client, bgpAgent.ID, speaker.ID).ExtractErr()) + th.AssertNoErr(t, agents.RemoveBGPSpeaker(client, bgpAgents[0].ID, speaker.ID).ExtractErr()) } + // Schedule a BGP Speaker to an agent opts := agents.ScheduleBGPSpeakerOpts{ SpeakerID: bgpSpeaker.ID, } - err = agents.ScheduleBGPSpeaker(client, bgpAgent.ID, opts).ExtractErr() + err = agents.ScheduleBGPSpeaker(client, bgpAgents[0].ID, opts).ExtractErr() th.AssertNoErr(t, err) - t.Logf("Successfully scheduled speaker %s to agent %s", bgpSpeaker.ID, bgpAgent.ID) - time.Sleep(waitTime) - bgpAgent, err = agents.Get(client, bgpAgent.ID).Extract() + t.Logf("Successfully scheduled speaker %s to agent %s", bgpSpeaker.ID, bgpAgents[0].ID) + + err = tools.WaitForTimeout( + func() (bool, error) { + bgpAgent, err := agents.Get(client, bgpAgents[0].ID).Extract() + th.AssertNoErr(t, err) + agentConf := bgpAgent.Configurations + numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) + t.Logf("Agent %s has %d speakers", bgpAgent.ID, numOfSpeakers) + return 1 == numOfSpeakers, nil + }, timeout) th.AssertNoErr(t, err) - agentConf = bgpAgent.Configurations - th.AssertEquals(t, 1, int(agentConf["bgp_speakers"].(float64))) // Delete the BGP Speaker speakers.Delete(client, bgpSpeaker.ID).ExtractErr() th.AssertNoErr(t, err) t.Logf("Successfully deleted the BGP Speaker, %s", bgpSpeaker.ID) - time.Sleep(waitTime) - bgpAgent, err = agents.Get(client, bgpAgent.ID).Extract() + err = tools.WaitForTimeout( + func() (bool, error) { + bgpAgent, err := agents.Get(client, bgpAgents[0].ID).Extract() + th.AssertNoErr(t, err) + agentConf := bgpAgent.Configurations + numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) + t.Logf("Agent %s has %d speakers", bgpAgent.ID, numOfSpeakers) + return 0 == numOfSpeakers, nil + }, timeout) th.AssertNoErr(t, err) - agentConf = bgpAgent.Configurations - th.AssertEquals(t, 0, int(agentConf["bgp_speakers"].(float64))) } From 48581dacb7ec726e2ea5ed5094e0bf91db20751b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 12 Sep 2022 09:28:04 +0200 Subject: [PATCH 12/44] Support old time format for port CreatedAt and UpdatedAt Older versions of neutron returned times as RFC 3339 "no Z" format. While gophercloud currently supports OpenStack versions from Train and above, all returning the new RFC 3339 time format, there's no reasons to be hostile against older OpenStack releases. This commit makes the Port.CreatedAt and Port.UpdatedAt fields compatible with older OpenStack releases. Fixes #2469 --- .../extensions/dns/testing/requests_test.go | 2 + .../portsbinding/testing/requests_test.go | 3 ++ openstack/networking/v2/ports/results.go | 43 ++++++++++++++++++- .../networking/v2/ports/testing/fixtures.go | 8 +++- .../v2/ports/testing/requests_test.go | 9 +++- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/openstack/networking/v2/extensions/dns/testing/requests_test.go b/openstack/networking/v2/extensions/dns/testing/requests_test.go index 3a792e97b2..931022c3ec 100644 --- a/openstack/networking/v2/extensions/dns/testing/requests_test.go +++ b/openstack/networking/v2/extensions/dns/testing/requests_test.go @@ -57,6 +57,8 @@ func TestPortList(t *testing.T) { ID: "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", SecurityGroups: []string{}, DeviceID: "9ae135f4-b6e0-4dad-9e91-3c223e385824", + CreatedAt: time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC), + UpdatedAt: time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC), }, PortDNSExt: dns.PortDNSExt{ DNSName: "test-port", diff --git a/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go b/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go index 8f3da66743..666f406ca4 100644 --- a/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go +++ b/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go @@ -2,6 +2,7 @@ package testing import ( "testing" + "time" fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding" @@ -40,6 +41,8 @@ func TestList(t *testing.T) { ID: "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", SecurityGroups: []string{}, DeviceID: "9ae135f4-b6e0-4dad-9e91-3c223e385824", + CreatedAt: time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC), + UpdatedAt: time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC), }, PortsBindingExt: portsbinding.PortsBindingExt{ VNICType: "normal", diff --git a/openstack/networking/v2/ports/results.go b/openstack/networking/v2/ports/results.go index 05ba595619..3bdad55403 100644 --- a/openstack/networking/v2/ports/results.go +++ b/openstack/networking/v2/ports/results.go @@ -1,6 +1,7 @@ package ports import ( + "encoding/json" "time" "github.com/gophercloud/gophercloud" @@ -114,10 +115,48 @@ type Port struct { RevisionNumber int `json:"revision_number"` // Timestamp when the port was created - CreatedAt time.Time `json:"created_at"` + CreatedAt time.Time `json:"-"` // Timestamp when the port was last updated - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt time.Time `json:"-"` +} + +func (r *Port) UnmarshalJSON(b []byte) error { + type tmp Port + + // Support for older neutron time format + var s1 struct { + tmp + CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s1) + if err == nil { + *r = Port(s1.tmp) + r.CreatedAt = time.Time(s1.CreatedAt) + r.UpdatedAt = time.Time(s1.UpdatedAt) + + return nil + } + + // Support for newer neutron time format + var s2 struct { + tmp + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + err = json.Unmarshal(b, &s2) + if err != nil { + return err + } + + *r = Port(s2.tmp) + r.CreatedAt = time.Time(s2.CreatedAt) + r.UpdatedAt = time.Time(s2.UpdatedAt) + + return nil } // PortPage is the page returned by a pager when traversing over a collection diff --git a/openstack/networking/v2/ports/testing/fixtures.go b/openstack/networking/v2/ports/testing/fixtures.go index 97ad08ac2d..83dbbd8f11 100644 --- a/openstack/networking/v2/ports/testing/fixtures.go +++ b/openstack/networking/v2/ports/testing/fixtures.go @@ -30,7 +30,9 @@ const ListResponse = ` } ], "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", - "port_security_enabled": false + "port_security_enabled": false, + "created_at": "2019-06-30T04:15:37", + "updated_at": "2019-06-30T05:18:49" } ] } @@ -73,7 +75,9 @@ const GetResponse = ` "fqdn": "test-port.openstack.local." } ], - "device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e" + "device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e", + "created_at": "2019-06-30T04:15:37Z", + "updated_at": "2019-06-30T05:18:49Z" } } ` diff --git a/openstack/networking/v2/ports/testing/requests_test.go b/openstack/networking/v2/ports/testing/requests_test.go index 7b4dd4a68c..54f2ec0c51 100644 --- a/openstack/networking/v2/ports/testing/requests_test.go +++ b/openstack/networking/v2/ports/testing/requests_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "testing" + "time" fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts" @@ -30,7 +31,7 @@ func TestList(t *testing.T) { count := 0 - ports.List(fake.ServiceClient(), ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + err := ports.List(fake.ServiceClient(), ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { count++ actual, err := ports.ExtractPorts(page) if err != nil { @@ -56,6 +57,8 @@ func TestList(t *testing.T) { ID: "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", SecurityGroups: []string{}, DeviceID: "9ae135f4-b6e0-4dad-9e91-3c223e385824", + CreatedAt: time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC), + UpdatedAt: time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC), }, } @@ -64,6 +67,8 @@ func TestList(t *testing.T) { return true, nil }) + th.AssertNoErr(t, err) + if count != 1 { t.Errorf("Expected 1 page, got %d", count) } @@ -131,6 +136,8 @@ func TestGet(t *testing.T) { th.AssertDeepEquals(t, n.SecurityGroups, []string{}) th.AssertEquals(t, n.Status, "ACTIVE") th.AssertEquals(t, n.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e") + th.AssertEquals(t, n.CreatedAt, time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC)) + th.AssertEquals(t, n.UpdatedAt, time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC)) } func TestGetWithExtensions(t *testing.T) { From bca650ea280d585d3dd953d9f4e4e865b26c9397 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Mon, 12 Sep 2022 14:59:42 +0200 Subject: [PATCH 13/44] Port CreatedAt and UpdatedAt: add back JSON tags Removing them in 48581dacb7ec726e2ea5ed5094e0bf91db20751b was not necessary. Removing them might break client-side serialisation. --- openstack/networking/v2/ports/results.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstack/networking/v2/ports/results.go b/openstack/networking/v2/ports/results.go index 3bdad55403..0883534ac3 100644 --- a/openstack/networking/v2/ports/results.go +++ b/openstack/networking/v2/ports/results.go @@ -115,10 +115,10 @@ type Port struct { RevisionNumber int `json:"revision_number"` // Timestamp when the port was created - CreatedAt time.Time `json:"-"` + CreatedAt time.Time `json:"created_at"` // Timestamp when the port was last updated - UpdatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"updated_at"` } func (r *Port) UnmarshalJSON(b []byte) error { From 7ec75f3c569e51b4ed8fdc8768363f5b2a4d3812 Mon Sep 17 00:00:00 2001 From: Mohammad Fatemipour Date: Sat, 10 Sep 2022 15:35:23 +0430 Subject: [PATCH 14/44] Implementing re-image volumeaction --- .../blockstorage/extensions/extensions.go | 35 +++++++++++++++++++ .../extensions/volumeactions_test.go | 20 +++++++++++ .../extensions/volumeactions/requests.go | 27 ++++++++++++++ .../extensions/volumeactions/results.go | 5 +++ .../volumeactions/testing/fixtures.go | 20 +++++++++++ .../volumeactions/testing/requests_test.go | 15 ++++++++ 6 files changed, 122 insertions(+) diff --git a/acceptance/openstack/blockstorage/extensions/extensions.go b/acceptance/openstack/blockstorage/extensions/extensions.go index d9713bf9c1..d15e4b652d 100644 --- a/acceptance/openstack/blockstorage/extensions/extensions.go +++ b/acceptance/openstack/blockstorage/extensions/extensions.go @@ -313,3 +313,38 @@ func ChangeVolumeType(t *testing.T, client *gophercloud.ServiceClient, volume *v return nil } + +// ReImage will re-image a volume +func ReImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, imageID string) error { + t.Logf("Attempting to re-image volume %s", volume.ID) + + reimageOpts := volumeactions.ReImageOpts{ + ImageID: imageID, + ReImageReserved: false, + } + + err := volumeactions.ReImage(client, volume.ID, reimageOpts).ExtractErr() + if err != nil { + return err + } + + err = volumes.WaitForStatus(client, volume.ID, "available", 60) + if err != nil { + return err + } + + vol, err := v3.Get(client, volume.ID).Extract() + if err != nil { + return err + } + + if vol.VolumeImageMetadata == nil { + return fmt.Errorf("volume does not have VolumeImageMetadata map") + } + + if strings.ToLower(vol.VolumeImageMetadata["image_id"]) != imageID { + return fmt.Errorf("volume image id '%s', expected '%s'", vol.VolumeImageMetadata["image_id"], imageID) + } + + return nil +} diff --git a/acceptance/openstack/blockstorage/extensions/volumeactions_test.go b/acceptance/openstack/blockstorage/extensions/volumeactions_test.go index 264d0efc94..3c69d17a46 100644 --- a/acceptance/openstack/blockstorage/extensions/volumeactions_test.go +++ b/acceptance/openstack/blockstorage/extensions/volumeactions_test.go @@ -145,6 +145,26 @@ func TestVolumeActionsChangeType(t *testing.T) { tools.PrintResource(t, newVolume) } +func TestVolumeActionsReImage(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/yoga") + + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + blockClient.Microversion = "3.68" + + volume, err := blockstorage.CreateVolume(t, blockClient) + th.AssertNoErr(t, err) + defer blockstorage.DeleteVolume(t, blockClient, volume) + + err = ReImage(t, blockClient, volume, choices.ImageID) + th.AssertNoErr(t, err) +} + // Note(jtopjian): I plan to work on this at some point, but it requires // setting up a server with iscsi utils. /* diff --git a/openstack/blockstorage/extensions/volumeactions/requests.go b/openstack/blockstorage/extensions/volumeactions/requests.go index 1c33c1785e..09dfb9ed2f 100644 --- a/openstack/blockstorage/extensions/volumeactions/requests.go +++ b/openstack/blockstorage/extensions/volumeactions/requests.go @@ -391,3 +391,30 @@ func ChangeType(client *gophercloud.ServiceClient, id string, opts ChangeTypeOpt _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// ReImageOpts contains options for Re-image a volume. +type ReImageOpts struct { + // New image id + ImageID string `json:"image_id"` + // set true to re-image volumes in reserved state + ReImageReserved bool `json:"reimage_reserved"` +} + +// ToReImageMap assembles a request body based on the contents of a ReImageOpts. +func (opts ReImageOpts) ToReImageMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-reimage") +} + +// ReImage will re-image a volume based on the values in ReImageOpts +func ReImage(client *gophercloud.ServiceClient, id string, opts ReImageOpts) (r ReImageResult) { + b, err := opts.ToReImageMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/blockstorage/extensions/volumeactions/results.go b/openstack/blockstorage/extensions/volumeactions/results.go index c4bd91a7ff..95b5bac1cb 100644 --- a/openstack/blockstorage/extensions/volumeactions/results.go +++ b/openstack/blockstorage/extensions/volumeactions/results.go @@ -214,3 +214,8 @@ type ForceDeleteResult struct { type ChangeTypeResult struct { gophercloud.ErrResult } + +// ReImageResult contains the response body and error from a ReImage request. +type ReImageResult struct { + gophercloud.ErrResult +} diff --git a/openstack/blockstorage/extensions/volumeactions/testing/fixtures.go b/openstack/blockstorage/extensions/volumeactions/testing/fixtures.go index 0ec9105251..378a120bc6 100644 --- a/openstack/blockstorage/extensions/volumeactions/testing/fixtures.go +++ b/openstack/blockstorage/extensions/volumeactions/testing/fixtures.go @@ -327,6 +327,26 @@ func MockSetBootableResponse(t *testing.T) { }) } +func MockReImageResponse(t *testing.T) { + th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "os-reimage": { + "image_id": "71543ced-a8af-45b6-a5c4-a46282108a90", + "reimage_reserved": false + } +} + `) + w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Length", "0") + w.WriteHeader(http.StatusAccepted) + }) +} + func MockChangeTypeResponse(t *testing.T) { th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { diff --git a/openstack/blockstorage/extensions/volumeactions/testing/requests_test.go b/openstack/blockstorage/extensions/volumeactions/testing/requests_test.go index 2b5bd9ca0a..2191a8a788 100644 --- a/openstack/blockstorage/extensions/volumeactions/testing/requests_test.go +++ b/openstack/blockstorage/extensions/volumeactions/testing/requests_test.go @@ -195,6 +195,21 @@ func TestSetBootable(t *testing.T) { th.AssertNoErr(t, err) } +func TestReImage(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockReImageResponse(t) + + options := volumeactions.ReImageOpts{ + ImageID: "71543ced-a8af-45b6-a5c4-a46282108a90", + ReImageReserved: false, + } + + err := volumeactions.ReImage(client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + th.AssertNoErr(t, err) +} + func TestChangeType(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() From 4f89b30e6f8e40d7a5fb5ab2e96c79262af152be Mon Sep 17 00:00:00 2001 From: Duc Truong Date: Thu, 22 Sep 2022 13:42:57 -0700 Subject: [PATCH 15/44] Return createdAt and updatedAt fields in baremetal node API results --- openstack/baremetal/v1/nodes/results.go | 8 ++++++++ .../baremetal/v1/nodes/testing/fixtures.go | 20 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/openstack/baremetal/v1/nodes/results.go b/openstack/baremetal/v1/nodes/results.go index 2f3d39f3d8..df956fa8d1 100644 --- a/openstack/baremetal/v1/nodes/results.go +++ b/openstack/baremetal/v1/nodes/results.go @@ -1,6 +1,8 @@ package nodes import ( + "time" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -234,6 +236,12 @@ type Node struct { // Static network configuration to use during deployment and cleaning. NetworkData map[string]interface{} `json:"network_data"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. May be “null”. + UpdatedAt time.Time `json:"updated_at"` } // NodePage abstracts the raw results of making a List() request against diff --git a/openstack/baremetal/v1/nodes/testing/fixtures.go b/openstack/baremetal/v1/nodes/testing/fixtures.go index 7dc7c991b2..dac21e1745 100644 --- a/openstack/baremetal/v1/nodes/testing/fixtures.go +++ b/openstack/baremetal/v1/nodes/testing/fixtures.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "testing" + "time" "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" th "github.com/gophercloud/gophercloud/testhelper" @@ -164,7 +165,7 @@ const NodeListDetailBody = ` "target_provision_state": null, "target_raid_config": {}, "traits": [], - "updated_at": null, + "updated_at": "2019-02-15T19:59:29+00:00", "uuid": "d2630783-6ec8-4836-b556-ab427c4b581e", "vendor_interface": "ipmitool", "volume": [ @@ -261,7 +262,7 @@ const NodeListDetailBody = ` "target_provision_state": null, "target_raid_config": {}, "traits": [], - "updated_at": null, + "updated_at": "2019-02-15T19:59:29+00:00", "uuid": "08c84581-58f5-4ea2-a0c6-dd2e5d2b3662", "vendor_interface": "ipmitool", "volume": [ @@ -358,7 +359,7 @@ const NodeListDetailBody = ` "target_provision_state": null, "target_raid_config": {}, "traits": [], - "updated_at": null, + "updated_at": "2019-02-15T19:59:29+00:00", "uuid": "c9afd385-5d89-4ecb-9e1c-68194da6b474", "vendor_interface": "ipmitool", "volume": [ @@ -468,7 +469,7 @@ const SingleNodeBody = ` "target_provision_state": null, "target_raid_config": {}, "traits": [], - "updated_at": null, + "updated_at": "2019-02-15T19:59:29+00:00", "uuid": "d2630783-6ec8-4836-b556-ab427c4b581e", "vendor_interface": "ipmitool", "volume": [ @@ -813,6 +814,11 @@ const NodeSetMaintenanceBody = ` ` var ( + createdAtFoo, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:28+00:00") + createdAtBar, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:29+00:00") + createdAtBaz, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:30+00:00") + updatedAt, _ = time.Parse(time.RFC3339, "2019-02-15T19:59:29+00:00") + NodeFoo = nodes.Node{ UUID: "d2630783-6ec8-4836-b556-ab427c4b581e", Name: "foo", @@ -862,6 +868,8 @@ var ( ConductorGroup: "", Protected: false, ProtectedReason: "", + CreatedAt: createdAtFoo, + UpdatedAt: updatedAt, } NodeFooValidation = nodes.NodeValidation{ @@ -959,6 +967,8 @@ var ( ConductorGroup: "", Protected: false, ProtectedReason: "", + CreatedAt: createdAtBar, + UpdatedAt: updatedAt, } NodeBaz = nodes.Node{ @@ -1003,6 +1013,8 @@ var ( ConductorGroup: "", Protected: false, ProtectedReason: "", + CreatedAt: createdAtBaz, + UpdatedAt: updatedAt, } ConfigDriveMap = nodes.ConfigDrive{ From 0f4183c0fb37edb7c4c5427c4de4d2edfc0fcb57 Mon Sep 17 00:00:00 2001 From: Duc Truong Date: Thu, 22 Sep 2022 15:50:30 -0700 Subject: [PATCH 16/44] Add ProvisionUpdatedAt field to Baremetal nodes --- openstack/baremetal/v1/nodes/results.go | 3 +++ openstack/baremetal/v1/nodes/testing/fixtures.go | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/openstack/baremetal/v1/nodes/results.go b/openstack/baremetal/v1/nodes/results.go index df956fa8d1..b2ec8696b0 100644 --- a/openstack/baremetal/v1/nodes/results.go +++ b/openstack/baremetal/v1/nodes/results.go @@ -242,6 +242,9 @@ type Node struct { // The UTC date and time when the resource was updated, ISO 8601 format. May be “null”. UpdatedAt time.Time `json:"updated_at"` + + // The UTC date and time when the provision state was updated, ISO 8601 format. May be “null”. + ProvisionUpdatedAt time.Time `json:"provision_updated_at"` } // NodePage abstracts the raw results of making a List() request against diff --git a/openstack/baremetal/v1/nodes/testing/fixtures.go b/openstack/baremetal/v1/nodes/testing/fixtures.go index dac21e1745..963a9dc234 100644 --- a/openstack/baremetal/v1/nodes/testing/fixtures.go +++ b/openstack/baremetal/v1/nodes/testing/fixtures.go @@ -144,7 +144,7 @@ const NodeListDetailBody = ` "power_state": null, "properties": {}, "provision_state": "enroll", - "provision_updated_at": null, + "provision_updated_at": "2019-02-15T17:21:29+00:00", "raid_config": {}, "raid_interface": "no-raid", "rescue_interface": "no-rescue", @@ -448,7 +448,7 @@ const SingleNodeBody = ` "power_state": null, "properties": {}, "provision_state": "enroll", - "provision_updated_at": null, + "provision_updated_at": "2019-02-15T17:21:29+00:00", "raid_config": {}, "raid_interface": "no-raid", "rescue_interface": "no-rescue", @@ -814,10 +814,11 @@ const NodeSetMaintenanceBody = ` ` var ( - createdAtFoo, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:28+00:00") - createdAtBar, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:29+00:00") - createdAtBaz, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:30+00:00") - updatedAt, _ = time.Parse(time.RFC3339, "2019-02-15T19:59:29+00:00") + createdAtFoo, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:28+00:00") + createdAtBar, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:29+00:00") + createdAtBaz, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:30+00:00") + updatedAt, _ = time.Parse(time.RFC3339, "2019-02-15T19:59:29+00:00") + provisonUpdatedAt, _ = time.Parse(time.RFC3339, "2019-02-15T17:21:29+00:00") NodeFoo = nodes.Node{ UUID: "d2630783-6ec8-4836-b556-ab427c4b581e", @@ -870,6 +871,7 @@ var ( ProtectedReason: "", CreatedAt: createdAtFoo, UpdatedAt: updatedAt, + ProvisionUpdatedAt: provisonUpdatedAt, } NodeFooValidation = nodes.NodeValidation{ From 2ee8cb657d7b2655add52fdb9f305dfeda980bb1 Mon Sep 17 00:00:00 2001 From: nikParasyr Date: Wed, 21 Sep 2022 10:05:43 +0200 Subject: [PATCH 17/44] Support for service types in neutron subnet --- .../openstack/networking/v2/networking.go | 39 +++++++++++++++++++ .../openstack/networking/v2/subnets_test.go | 27 +++++++++++++ openstack/networking/v2/subnets/doc.go | 3 ++ openstack/networking/v2/subnets/requests.go | 6 +++ openstack/networking/v2/subnets/results.go | 3 ++ .../networking/v2/subnets/testing/fixtures.go | 4 +- .../v2/subnets/testing/requests_test.go | 8 ++-- 7 files changed, 86 insertions(+), 4 deletions(-) diff --git a/acceptance/openstack/networking/v2/networking.go b/acceptance/openstack/networking/v2/networking.go index 9362c9b623..aae456b198 100644 --- a/acceptance/openstack/networking/v2/networking.go +++ b/acceptance/openstack/networking/v2/networking.go @@ -312,6 +312,45 @@ func CreateSubnet(t *testing.T, client *gophercloud.ServiceClient, networkID str return subnet, nil } +// CreateSubnet will create a subnet on the specified Network ID and service types. +// +// An error will be returned if the subnet could not be created. +func CreateSubnetWithServiceTypes(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*subnets.Subnet, error) { + subnetName := tools.RandomString("TESTACC-", 8) + subnetDescription := tools.RandomString("TESTACC-DESC-", 8) + subnetOctet := tools.RandomInt(1, 250) + subnetCIDR := fmt.Sprintf("192.168.%d.0/24", subnetOctet) + subnetGateway := fmt.Sprintf("192.168.%d.1", subnetOctet) + serviceTypes := []string{"network:routed"} + createOpts := subnets.CreateOpts{ + NetworkID: networkID, + CIDR: subnetCIDR, + IPVersion: 4, + Name: subnetName, + Description: subnetDescription, + EnableDHCP: gophercloud.Disabled, + GatewayIP: &subnetGateway, + ServiceTypes: serviceTypes, + } + + t.Logf("Attempting to create subnet: %s", subnetName) + + subnet, err := subnets.Create(client, createOpts).Extract() + if err != nil { + return subnet, err + } + + t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnet.Description, subnetDescription) + th.AssertEquals(t, subnet.GatewayIP, subnetGateway) + th.AssertEquals(t, subnet.CIDR, subnetCIDR) + th.AssertDeepEquals(t, subnet.ServiceTypes, serviceTypes) + + return subnet, nil +} + // CreateSubnetWithDefaultGateway will create a subnet on the specified Network // ID and have Neutron set the gateway by default An error will be returned if // the subnet could not be created. diff --git a/acceptance/openstack/networking/v2/subnets_test.go b/acceptance/openstack/networking/v2/subnets_test.go index 95f5a6833a..6b45d7c6a0 100644 --- a/acceptance/openstack/networking/v2/subnets_test.go +++ b/acceptance/openstack/networking/v2/subnets_test.go @@ -65,6 +65,33 @@ func TestSubnetCRUD(t *testing.T) { th.AssertEquals(t, found, true) } +func TestSubnetsServiceType(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create Network + network, err := CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer DeleteNetwork(t, client, network.ID) + + // Create Subnet + subnet, err := CreateSubnetWithServiceTypes(t, client, network.ID) + th.AssertNoErr(t, err) + defer DeleteSubnet(t, client, subnet.ID) + + tools.PrintResource(t, subnet) + + serviceTypes := []string{"network:floatingip"} + updateOpts := subnets.UpdateOpts{ + ServiceTypes: &serviceTypes, + } + + newSubnet, err := subnets.Update(client, subnet.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, newSubnet.ServiceTypes, serviceTypes) +} + func TestSubnetsDefaultGateway(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) diff --git a/openstack/networking/v2/subnets/doc.go b/openstack/networking/v2/subnets/doc.go index 7d3a1b9b65..8bb4468c4e 100644 --- a/openstack/networking/v2/subnets/doc.go +++ b/openstack/networking/v2/subnets/doc.go @@ -44,6 +44,7 @@ Example to Create a Subnet With Specified Gateway }, }, DNSNameservers: []string{"foo"}, + ServiceTypes: []string{"network:floatingip"}, } subnet, err := subnets.Create(networkClient, createOpts).Extract() @@ -98,11 +99,13 @@ Example to Update a Subnet subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23" dnsNameservers := []string{"8.8.8.8"} + serviceTypes := []string{"network:floatingip", "network:routed"} name := "new_name" updateOpts := subnets.UpdateOpts{ Name: &name, DNSNameservers: &dnsNameservers, + ServiceTypes: &serviceTypes, } subnet, err := subnets.Update(networkClient, subnetID, updateOpts).Extract() diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go index 7d97fb259d..2e87907587 100644 --- a/openstack/networking/v2/subnets/requests.go +++ b/openstack/networking/v2/subnets/requests.go @@ -122,6 +122,9 @@ type CreateOpts struct { // DNSNameservers are the nameservers to be set via DHCP. DNSNameservers []string `json:"dns_nameservers,omitempty"` + // ServiceTypes are the service types associated with the subnet. + ServiceTypes []string `json:"service_types,omitempty"` + // HostRoutes are any static host routes to be set via DHCP. HostRoutes []HostRoute `json:"host_routes,omitempty"` @@ -194,6 +197,9 @@ type UpdateOpts struct { // DNSNameservers are the nameservers to be set via DHCP. DNSNameservers *[]string `json:"dns_nameservers,omitempty"` + // ServiceTypes are the service types associated with the subnet. + ServiceTypes *[]string `json:"service_types,omitempty"` + // HostRoutes are any static host routes to be set via DHCP. HostRoutes *[]HostRoute `json:"host_routes,omitempty"` diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go index e04d486fd4..63b98f7248 100644 --- a/openstack/networking/v2/subnets/results.go +++ b/openstack/networking/v2/subnets/results.go @@ -83,6 +83,9 @@ type Subnet struct { // DNS name servers used by hosts in this subnet. DNSNameservers []string `json:"dns_nameservers"` + // Service types associated with the subnet. + ServiceTypes []string `json:"service_types"` + // Sub-ranges of CIDR available for dynamic allocation to ports. // See AllocationPool. AllocationPools []AllocationPool `json:"allocation_pools"` diff --git a/openstack/networking/v2/subnets/testing/fixtures.go b/openstack/networking/v2/subnets/testing/fixtures.go index 38cdbc8559..af8512e549 100644 --- a/openstack/networking/v2/subnets/testing/fixtures.go +++ b/openstack/networking/v2/subnets/testing/fixtures.go @@ -193,6 +193,7 @@ const SubnetCreateRequest = ` "gateway_ip": "192.168.199.1", "cidr": "192.168.199.0/24", "dns_nameservers": ["foo"], + "service_types": ["network:routed"], "allocation_pools": [ { "start": "192.168.199.2", @@ -212,7 +213,8 @@ const SubnetCreateResult = ` "enable_dhcp": true, "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": [], + "dns_nameservers": ["foo"], + "service_types": ["network:routed"], "allocation_pools": [ { "start": "192.168.199.2", diff --git a/openstack/networking/v2/subnets/testing/requests_test.go b/openstack/networking/v2/subnets/testing/requests_test.go index 34a008599f..7e82d5855d 100644 --- a/openstack/networking/v2/subnets/testing/requests_test.go +++ b/openstack/networking/v2/subnets/testing/requests_test.go @@ -118,6 +118,7 @@ func TestCreate(t *testing.T) { }, }, DNSNameservers: []string{"foo"}, + ServiceTypes: []string{"network:routed"}, HostRoutes: []subnets.HostRoute{ {NextHop: "bar"}, }, @@ -130,7 +131,8 @@ func TestCreate(t *testing.T) { th.AssertEquals(t, s.EnableDHCP, true) th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{}) + th.AssertDeepEquals(t, s.DNSNameservers, []string{"foo"}) + th.AssertDeepEquals(t, s.ServiceTypes, []string{"network:routed"}) th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ { Start: "192.168.199.2", @@ -319,7 +321,7 @@ func TestCreateWithNoCIDR(t *testing.T) { th.AssertEquals(t, s.EnableDHCP, true) th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{}) + th.AssertDeepEquals(t, s.DNSNameservers, []string{"foo"}) th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ { Start: "192.168.199.2", @@ -368,7 +370,7 @@ func TestCreateWithPrefixlen(t *testing.T) { th.AssertEquals(t, s.EnableDHCP, true) th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{}) + th.AssertDeepEquals(t, s.DNSNameservers, []string{"foo"}) th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ { Start: "192.168.199.2", From 4d3ca6eca05a80ce954f224a0c9004221893d831 Mon Sep 17 00:00:00 2001 From: emilmaruszczak Date: Fri, 30 Sep 2022 10:28:52 +0200 Subject: [PATCH 18/44] Add acceptance tests --- acceptance/openstack/identity/v3/limits_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/acceptance/openstack/identity/v3/limits_test.go b/acceptance/openstack/identity/v3/limits_test.go index 1bb7b1e921..2fe5787c58 100644 --- a/acceptance/openstack/identity/v3/limits_test.go +++ b/acceptance/openstack/identity/v3/limits_test.go @@ -7,10 +7,23 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/limits" th "github.com/gophercloud/gophercloud/testhelper" ) +func TestGetEnforcementModel(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + model, err := limits.GetEnforcementModel(client).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, model) +} + func TestLimitsList(t *testing.T) { clients.RequireAdmin(t) From 9f0ca02b742e8b21b5ee07e245ec719f0ec963b6 Mon Sep 17 00:00:00 2001 From: emilmaruszczak Date: Thu, 8 Sep 2022 11:01:51 +0200 Subject: [PATCH 19/44] Add mappings list operation --- .../identity/v3/extensions/federation/doc.go | 16 ++ .../v3/extensions/federation/requests.go | 13 ++ .../v3/extensions/federation/results.go | 167 ++++++++++++++++++ .../extensions/federation/testing/fixtures.go | 109 ++++++++++++ .../federation/testing/requests_test.go | 42 +++++ .../identity/v3/extensions/federation/urls.go | 12 ++ 6 files changed, 359 insertions(+) create mode 100644 openstack/identity/v3/extensions/federation/doc.go create mode 100644 openstack/identity/v3/extensions/federation/requests.go create mode 100644 openstack/identity/v3/extensions/federation/results.go create mode 100644 openstack/identity/v3/extensions/federation/testing/fixtures.go create mode 100644 openstack/identity/v3/extensions/federation/testing/requests_test.go create mode 100644 openstack/identity/v3/extensions/federation/urls.go diff --git a/openstack/identity/v3/extensions/federation/doc.go b/openstack/identity/v3/extensions/federation/doc.go new file mode 100644 index 0000000000..e8b9369aaf --- /dev/null +++ b/openstack/identity/v3/extensions/federation/doc.go @@ -0,0 +1,16 @@ +/* +Package federation provides information and interaction with OS-FEDERATION API for the +Openstack Identity service. + +Example to List Mappings + + allPages, err := federation.ListMappings(identityClient).AllPages() + if err != nil { + panic(err) + } + allMappings, err := federation.ExtractMappings(allPages) + if err != nil { + panic(err) + } +*/ +package federation diff --git a/openstack/identity/v3/extensions/federation/requests.go b/openstack/identity/v3/extensions/federation/requests.go new file mode 100644 index 0000000000..d42caf2e0c --- /dev/null +++ b/openstack/identity/v3/extensions/federation/requests.go @@ -0,0 +1,13 @@ +package federation + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListMappings enumerates the mappings. +func ListMappings(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, mappingsRootURL(client), func(r pagination.PageResult) pagination.Page { + return MappingsPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/openstack/identity/v3/extensions/federation/results.go b/openstack/identity/v3/extensions/federation/results.go new file mode 100644 index 0000000000..745546fd7b --- /dev/null +++ b/openstack/identity/v3/extensions/federation/results.go @@ -0,0 +1,167 @@ +package federation + +import ( + "github.com/gophercloud/gophercloud/pagination" +) + +type UserType string + +const ( + UserTypeEphemeral UserType = "ephemeral" + UserTypeLocal UserType = "local" +) + +// Mapping a set of rules to map federation protocol attributes to +// Identity API objects. +type Mapping struct { + // The Federation Mapping unique ID + ID string `json:"id"` + + // Links contains referencing links to the limit. + Links map[string]interface{} `json:"links"` + + // The list of rules used to map remote users into local users + Rules []MappingRule `json:"rules"` +} + +type MappingRule struct { + // References a local Identity API resource, such as a group or user to which the remote attributes will be mapped. + Local []RuleLocal `json:"local"` + + // Each object contains a rule for mapping remote attributes to Identity API concepts. + Remote []RuleRemote `json:"remote"` +} + +type RuleRemote struct { + // Type represents an assertion type keyword. + Type string `json:"type"` + + // If true, then each string will be evaluated as a regular expression search against the remote attribute type. + Regex *bool `json:"regex,omitempty"` + + // The rule is matched only if any of the specified strings appear in the remote attribute type. + // This is mutually exclusive with NotAnyOf. + AnyOneOf []string `json:"any_one_of,omitempty"` + + // The rule is not matched if any of the specified strings appear in the remote attribute type. + // This is mutually exclusive with AnyOneOf. + NotAnyOf []string `json:"not_any_of,omitempty"` + + // The rule works as a filter, removing any specified strings that are listed there from the remote attribute type. + // This is mutually exclusive with Whitelist. + Blacklist []string `json:"blacklist,omitempty"` + + // The rule works as a filter, allowing only the specified strings in the remote attribute type to be passed ahead. + // This is mutually exclusive with Blacklist. + Whitelist []string `json:"whitelist,omitempty"` +} + +type RuleLocal struct { + // Domain to which the remote attributes will be matched. + Domain *Domain `json:"domain,omitempty"` + + // Group to which the remote attributes will be matched. + Group *Group `json:"group,omitempty"` + + // Group IDs to which the remote attributes will be matched. + GroupIDs string `json:"group_ids,omitempty"` + + // Groups to which the remote attributes will be matched. + Groups string `json:"groups,omitempty"` + + // Projects to which the remote attributes will be matched. + Projects []RuleProject `json:"projects,omitempty"` + + // User to which the remote attributes will be matched. + User *RuleUser `json:"user,omitempty"` +} + +type Domain struct { + // Domain ID + // This is mutually exclusive with Name. + ID string `json:"id,omitempty"` + + // Domain Name + // This is mutually exclusive with ID. + Name string `json:"name,omitempty"` +} + +type Group struct { + // Group ID to which the rule should match. + // This is mutually exclusive with Name and Domain. + ID string `json:"id,omitempty"` + + // Group Name to which the rule should match. + // This is mutually exclusive with ID. + Name string `json:"name,omitempty"` + + // Group Domain to which the rule should match. + // This is mutually exclusive with ID. + Domain *Domain `json:"domain,omitempty"` +} + +type RuleProject struct { + // Project name + Name string `json:"name,omitempty"` + + // Project roles + Roles []RuleProjectRole `json:"roles,omitempty"` +} + +type RuleProjectRole struct { + // Role name + Name string `json:"name,omitempty"` +} + +type RuleUser struct { + // User domain + Domain *Domain `json:"domain,omitempty"` + + // User email + Email string `json:"email,omitempty"` + + // User ID + ID string `json:"id,omitempty"` + + // User name + Name string `json:"name,omitempty"` + + // User type + Type *UserType `json:"type,omitempty"` +} + +// MappingsPage is a single page of Mapping results. +type MappingsPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Mappings contains any results. +func (c MappingsPage) IsEmpty() (bool, error) { + mappings, err := ExtractMappings(c) + return len(mappings) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (c MappingsPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := c.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractMappings returns a slice of Mappings contained in a single page of +// results. +func ExtractMappings(r pagination.Page) ([]Mapping, error) { + var s struct { + Mappings []Mapping `json:"mappings"` + } + err := (r.(MappingsPage)).ExtractInto(&s) + return s.Mappings, err +} diff --git a/openstack/identity/v3/extensions/federation/testing/fixtures.go b/openstack/identity/v3/extensions/federation/testing/fixtures.go new file mode 100644 index 0000000000..59785dfed3 --- /dev/null +++ b/openstack/identity/v3/extensions/federation/testing/fixtures.go @@ -0,0 +1,109 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/federation" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ListOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/OS-FEDERATION/mappings" + }, + "mappings": [ + { + "id": "ACME", + "links": { + "self": "http://example.com/identity/v3/OS-FEDERATION/mappings/ACME" + }, + "rules": [ + { + "local": [ + { + "user": { + "name": "{0}" + } + }, + { + "group": { + "id": "0cd5e9" + } + } + ], + "remote": [ + { + "type": "UserName" + }, + { + "type": "orgPersonType", + "not_any_of": [ + "Contractor", + "Guest" + ] + } + ] + } + ] + } + ] +} +` + +var MappingACME = federation.Mapping{ + ID: "ACME", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/OS-FEDERATION/mappings/ACME", + }, + Rules: []federation.MappingRule{ + { + Local: []federation.RuleLocal{ + { + User: &federation.RuleUser{ + Name: "{0}", + }, + }, + { + Group: &federation.Group{ + ID: "0cd5e9", + }, + }, + }, + Remote: []federation.RuleRemote{ + { + Type: "UserName", + }, + { + Type: "orgPersonType", + NotAnyOf: []string{ + "Contractor", + "Guest", + }, + }, + }, + }, + }, +} + +// ExpectedMappingsSlice is the slice of mappings expected to be returned from ListOutput. +var ExpectedMappingsSlice = []federation.Mapping{MappingACME} + +// HandleListMappingsSuccessfully creates an HTTP handler at `/mappings` on the +// test handler mux that responds with a list of two mappings. +func HandleListMappingsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/OS-FEDERATION/mappings", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} diff --git a/openstack/identity/v3/extensions/federation/testing/requests_test.go b/openstack/identity/v3/extensions/federation/testing/requests_test.go new file mode 100644 index 0000000000..0bf53a4529 --- /dev/null +++ b/openstack/identity/v3/extensions/federation/testing/requests_test.go @@ -0,0 +1,42 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/federation" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListMappings(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListMappingsSuccessfully(t) + + count := 0 + err := federation.ListMappings(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := federation.ExtractMappings(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedMappingsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListMappingsAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListMappingsSuccessfully(t) + + allPages, err := federation.ListMappings(client.ServiceClient()).AllPages() + th.AssertNoErr(t, err) + actual, err := federation.ExtractMappings(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedMappingsSlice, actual) +} diff --git a/openstack/identity/v3/extensions/federation/urls.go b/openstack/identity/v3/extensions/federation/urls.go new file mode 100644 index 0000000000..8841262dca --- /dev/null +++ b/openstack/identity/v3/extensions/federation/urls.go @@ -0,0 +1,12 @@ +package federation + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "OS-FEDERATION" + mappingsPath = "mappings" +) + +func mappingsRootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, mappingsPath) +} From aeccb4544483607010c8f4448a504f2f48a68075 Mon Sep 17 00:00:00 2001 From: emilmaruszczak Date: Tue, 4 Oct 2022 18:14:27 +0200 Subject: [PATCH 20/44] Add acceptance test --- .../openstack/identity/v3/federation_test.go | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 acceptance/openstack/identity/v3/federation_test.go diff --git a/acceptance/openstack/identity/v3/federation_test.go b/acceptance/openstack/identity/v3/federation_test.go new file mode 100644 index 0000000000..21f7447ece --- /dev/null +++ b/acceptance/openstack/identity/v3/federation_test.go @@ -0,0 +1,26 @@ +//go:build acceptance +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/federation" +) + +func TestListMappings(t *testing.T) { + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + allPages, err := federation.ListMappings(client).AllPages() + th.AssertNoErr(t, err) + + mappings, err := federation.ExtractMappings(allPages) + th.AssertNoErr(t, err) + + tools.PrintResource(t, mappings) +} From 4b28068aa97548cc08ccd8ff2f04f24272d4a51b Mon Sep 17 00:00:00 2001 From: emilmaruszczak Date: Wed, 5 Oct 2022 10:16:33 +0200 Subject: [PATCH 21/44] Run go fmt --- acceptance/openstack/identity/v3/federation_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/openstack/identity/v3/federation_test.go b/acceptance/openstack/identity/v3/federation_test.go index 21f7447ece..8afc7f9ad2 100644 --- a/acceptance/openstack/identity/v3/federation_test.go +++ b/acceptance/openstack/identity/v3/federation_test.go @@ -8,18 +8,18 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" - th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/federation" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestListMappings(t *testing.T) { client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) - allPages, err := federation.ListMappings(client).AllPages() + allPages, err := federation.ListMappings(client).AllPages() th.AssertNoErr(t, err) - mappings, err := federation.ExtractMappings(allPages) + mappings, err := federation.ExtractMappings(allPages) th.AssertNoErr(t, err) tools.PrintResource(t, mappings) From 01d57b79670f9ba26c96a5ba0e57699bdc5b8e3e Mon Sep 17 00:00:00 2001 From: nikParasyr Date: Wed, 12 Oct 2022 17:46:48 +0200 Subject: [PATCH 22/44] Add Prometheus protocol for octavia listeners Prometheus protocol was added for octavia listeners on 2.25 --- openstack/loadbalancer/v2/listeners/requests.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstack/loadbalancer/v2/listeners/requests.go b/openstack/loadbalancer/v2/listeners/requests.go index 54e968ce51..a0a06f6448 100644 --- a/openstack/loadbalancer/v2/listeners/requests.go +++ b/openstack/loadbalancer/v2/listeners/requests.go @@ -18,7 +18,9 @@ const ( ProtocolHTTP Protocol = "HTTP" ProtocolHTTPS Protocol = "HTTPS" // Protocol SCTP requires octavia microversion 2.23 - ProtocolSCTP Protocol = "SCTP" + ProtocolSCTP Protocol = "SCTP" + // Protocol Prometheus requires octavia microversion 2.25 + ProtocolPrometheus Protocol = "PROMETHEUS" ProtocolTerminatedHTTPS Protocol = "TERMINATED_HTTPS" ) From c741b60c1d310b4f717d0f6c6e1c6495cfae2cd3 Mon Sep 17 00:00:00 2001 From: nikParasyr Date: Wed, 12 Oct 2022 17:51:25 +0200 Subject: [PATCH 23/44] Add Persistance for octavia pools.UpdateOpts Session persistance can be updated on Octavia. Add it to UpdateOpts --- openstack/loadbalancer/v2/pools/requests.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openstack/loadbalancer/v2/pools/requests.go b/openstack/loadbalancer/v2/pools/requests.go index 41d7dd9a41..69e6a2a763 100644 --- a/openstack/loadbalancer/v2/pools/requests.go +++ b/openstack/loadbalancer/v2/pools/requests.go @@ -190,6 +190,9 @@ type UpdateOpts struct { // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` + // Persistence is the session persistence of the pool. + Persistence *SessionPersistence `json:"session_persistence,omitempty"` + // Tags is a set of resource tags. New in version 2.5 Tags *[]string `json:"tags,omitempty"` } From 1470f5c989d72a044fd9f3374df067f399c03b14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 09:45:41 +0000 Subject: [PATCH 24/44] Bump EmilienM/devstack-action from 0.7 to 0.8 Bumps [EmilienM/devstack-action](https://github.com/EmilienM/devstack-action) from 0.7 to 0.8. - [Release notes](https://github.com/EmilienM/devstack-action/releases) - [Commits](https://github.com/EmilienM/devstack-action/compare/v0.7...v0.8) --- updated-dependencies: - dependency-name: EmilienM/devstack-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/functional-baremetal.yaml | 2 +- .github/workflows/functional-basic.yaml | 2 +- .github/workflows/functional-blockstorage.yaml | 2 +- .github/workflows/functional-clustering.yaml | 2 +- .github/workflows/functional-compute.yaml | 2 +- .github/workflows/functional-containerinfra.yaml | 2 +- .github/workflows/functional-dns.yaml | 2 +- .github/workflows/functional-identity.yaml | 2 +- .github/workflows/functional-imageservice.yaml | 2 +- .github/workflows/functional-keymanager.yaml | 2 +- .github/workflows/functional-loadbalancer.yaml | 2 +- .github/workflows/functional-messaging.yaml | 2 +- .github/workflows/functional-networking.yaml | 2 +- .github/workflows/functional-objectstorage.yaml | 2 +- .github/workflows/functional-orchestration.yaml | 2 +- .github/workflows/functional-placement.yaml | 2 +- .github/workflows/functional-sharedfilesystems.yaml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/functional-baremetal.yaml b/.github/workflows/functional-baremetal.yaml index a4a4038017..065c0a5147 100644 --- a/.github/workflows/functional-baremetal.yaml +++ b/.github/workflows/functional-baremetal.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-basic.yaml b/.github/workflows/functional-basic.yaml index 78b0f9760b..97e3fe0feb 100644 --- a/.github/workflows/functional-basic.yaml +++ b/.github/workflows/functional-basic.yaml @@ -41,7 +41,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} enabled_services: 's-account,s-container,s-object,s-proxy' diff --git a/.github/workflows/functional-blockstorage.yaml b/.github/workflows/functional-blockstorage.yaml index 6878da6d94..557e737c31 100644 --- a/.github/workflows/functional-blockstorage.yaml +++ b/.github/workflows/functional-blockstorage.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-clustering.yaml b/.github/workflows/functional-clustering.yaml index 0c6247887b..128c4c14ca 100644 --- a/.github/workflows/functional-clustering.yaml +++ b/.github/workflows/functional-clustering.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-compute.yaml b/.github/workflows/functional-compute.yaml index 8e9ad33b49..3f788fa53b 100644 --- a/.github/workflows/functional-compute.yaml +++ b/.github/workflows/functional-compute.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-containerinfra.yaml b/.github/workflows/functional-containerinfra.yaml index 6cccd1cb3b..2f6b58f76e 100644 --- a/.github/workflows/functional-containerinfra.yaml +++ b/.github/workflows/functional-containerinfra.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-dns.yaml b/.github/workflows/functional-dns.yaml index 6e9f6ea389..572fdc9129 100644 --- a/.github/workflows/functional-dns.yaml +++ b/.github/workflows/functional-dns.yaml @@ -39,7 +39,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-identity.yaml b/.github/workflows/functional-identity.yaml index fcddd1db5a..97eca1b270 100644 --- a/.github/workflows/functional-identity.yaml +++ b/.github/workflows/functional-identity.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} - name: Checkout go diff --git a/.github/workflows/functional-imageservice.yaml b/.github/workflows/functional-imageservice.yaml index 83a17356e1..ba57071d84 100644 --- a/.github/workflows/functional-imageservice.yaml +++ b/.github/workflows/functional-imageservice.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} - name: Checkout go diff --git a/.github/workflows/functional-keymanager.yaml b/.github/workflows/functional-keymanager.yaml index 994aa3fe9b..25b9ed4568 100644 --- a/.github/workflows/functional-keymanager.yaml +++ b/.github/workflows/functional-keymanager.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-loadbalancer.yaml b/.github/workflows/functional-loadbalancer.yaml index 827be255b1..690857d2be 100644 --- a/.github/workflows/functional-loadbalancer.yaml +++ b/.github/workflows/functional-loadbalancer.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-messaging.yaml b/.github/workflows/functional-messaging.yaml index 6a8adf9fac..5e74b98038 100644 --- a/.github/workflows/functional-messaging.yaml +++ b/.github/workflows/functional-messaging.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-networking.yaml b/.github/workflows/functional-networking.yaml index 315ed5a2ae..9de7888106 100644 --- a/.github/workflows/functional-networking.yaml +++ b/.github/workflows/functional-networking.yaml @@ -61,7 +61,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-objectstorage.yaml b/.github/workflows/functional-objectstorage.yaml index 85e6107828..5c79372ac8 100644 --- a/.github/workflows/functional-objectstorage.yaml +++ b/.github/workflows/functional-objectstorage.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-orchestration.yaml b/.github/workflows/functional-orchestration.yaml index 28d3b291a6..2605e170fe 100644 --- a/.github/workflows/functional-orchestration.yaml +++ b/.github/workflows/functional-orchestration.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-placement.yaml b/.github/workflows/functional-placement.yaml index ea514716ff..784f2de6ce 100644 --- a/.github/workflows/functional-placement.yaml +++ b/.github/workflows/functional-placement.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} - name: Checkout go diff --git a/.github/workflows/functional-sharedfilesystems.yaml b/.github/workflows/functional-sharedfilesystems.yaml index 7884bda683..47435f99d5 100644 --- a/.github/workflows/functional-sharedfilesystems.yaml +++ b/.github/workflows/functional-sharedfilesystems.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.7 + uses: EmilienM/devstack-action@v0.8 with: branch: ${{ matrix.openstack_version }} conf_overrides: | From 9e21d13fd164cfdaa9a866b8ea570da56dafb784 Mon Sep 17 00:00:00 2001 From: nikParasyr Date: Thu, 13 Oct 2022 15:25:27 +0200 Subject: [PATCH 25/44] Add VipQosPolicyID to loadbalancer Create and Update VipQosPolicyID can be defined for loadbalancers during creation and update. Moreover add the `neutron-qos` service to loadbalancer workflow. [Docs](https://docs.openstack.org/api-ref/load-balancer/v2/?expanded=create-a-load-balancer-detail,update-a-load-balancer-detail#create-a-load-balancer) --- .../workflows/functional-loadbalancer.yaml | 2 +- .../openstack/loadbalancer/v2/loadbalancer.go | 10 +++++++- .../loadbalancer/v2/loadbalancers_test.go | 24 +++++++++++++++---- .../loadbalancer/v2/loadbalancers/requests.go | 6 +++++ .../loadbalancer/v2/loadbalancers/results.go | 3 +++ 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/functional-loadbalancer.yaml b/.github/workflows/functional-loadbalancer.yaml index 827be255b1..4fe92ea876 100644 --- a/.github/workflows/functional-loadbalancer.yaml +++ b/.github/workflows/functional-loadbalancer.yaml @@ -44,7 +44,7 @@ jobs: conf_overrides: | enable_plugin octavia https://opendev.org/openstack/octavia ${{ matrix.openstack_version }} enable_plugin neutron https://opendev.org/openstack/neutron ${{ matrix.openstack_version }} - enabled_services: 'octavia,o-api,o-cw,o-hk,o-hm,o-da' + enabled_services: 'octavia,o-api,o-cw,o-hk,o-hm,o-da,neutron-qos' - name: Checkout go uses: actions/setup-go@v3 with: diff --git a/acceptance/openstack/loadbalancer/v2/loadbalancer.go b/acceptance/openstack/loadbalancer/v2/loadbalancer.go index f8d44a8b17..8d5efb011d 100644 --- a/acceptance/openstack/loadbalancer/v2/loadbalancer.go +++ b/acceptance/openstack/loadbalancer/v2/loadbalancer.go @@ -110,7 +110,7 @@ func CreateListenerHTTP(t *testing.T, client *gophercloud.ServiceClient, lb *loa // CreateLoadBalancer will create a load balancer with a random name on a given // subnet. An error will be returned if the loadbalancer could not be created. -func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetID string, tags []string) (*loadbalancers.LoadBalancer, error) { +func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetID string, tags []string, policyID string) (*loadbalancers.LoadBalancer, error) { lbName := tools.RandomString("TESTACCT-", 8) lbDescription := tools.RandomString("TESTACCT-DESC-", 8) @@ -126,6 +126,10 @@ func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetI createOpts.Tags = tags } + if len(policyID) > 0 { + createOpts.VipQosPolicyID = policyID + } + lb, err := loadbalancers.Create(client, createOpts).Extract() if err != nil { return lb, err @@ -149,6 +153,10 @@ func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetI th.AssertDeepEquals(t, lb.Tags, tags) } + if len(policyID) > 0 { + th.AssertEquals(t, lb.VipQosPolicyID, policyID) + } + return lb, nil } diff --git a/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go b/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go index c21663ef35..2a987bd9b9 100644 --- a/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go +++ b/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/qos/policies" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" @@ -50,7 +51,7 @@ func TestLoadbalancersListByTags(t *testing.T) { // Add "test" tag intentionally to test the "not-tags" parameter. Because "test" tag is also used in other test // cases, we use "test" tag to exclude load balancers created by other test case. tags := []string{"tag1", "tag2", "test"} - lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags) + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags, "") th.AssertNoErr(t, err) defer DeleteLoadBalancer(t, lbClient, lb.ID) @@ -110,7 +111,7 @@ func TestLoadbalancerHTTPCRUD(t *testing.T) { th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, netClient, subnet.ID) - lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, nil) + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, nil, "") th.AssertNoErr(t, err) defer DeleteLoadBalancer(t, lbClient, lb.ID) @@ -239,6 +240,12 @@ func TestLoadbalancersCRUD(t *testing.T) { netClient, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Create QoS policy first as the loadbalancer and its port + //needs to be deleted before the QoS policy can be deleted + policy2, err := policies.CreateQoSPolicy(t, netClient) + th.AssertNoErr(t, err) + defer policies.DeleteQoSPolicy(t, netClient, policy2.ID) + lbClient, err := clients.NewLoadBalancerV2Client() th.AssertNoErr(t, err) @@ -250,14 +257,20 @@ func TestLoadbalancersCRUD(t *testing.T) { th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, netClient, subnet.ID) + policy1, err := policies.CreateQoSPolicy(t, netClient) + th.AssertNoErr(t, err) + defer policies.DeleteQoSPolicy(t, netClient, policy1.ID) + tags := []string{"test"} - lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags) + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags, policy1.ID) th.AssertNoErr(t, err) + th.AssertEquals(t, lb.VipQosPolicyID, policy1.ID) defer DeleteLoadBalancer(t, lbClient, lb.ID) lbDescription := "" updateLoadBalancerOpts := loadbalancers.UpdateOpts{ - Description: &lbDescription, + Description: &lbDescription, + VipQosPolicyID: &policy2.ID, } _, err = loadbalancers.Update(lbClient, lb.ID, updateLoadBalancerOpts).Extract() th.AssertNoErr(t, err) @@ -272,6 +285,7 @@ func TestLoadbalancersCRUD(t *testing.T) { tools.PrintResource(t, newLB) th.AssertEquals(t, newLB.Description, lbDescription) + th.AssertEquals(t, newLB.VipQosPolicyID, policy2.ID) lbStats, err := loadbalancers.GetStats(lbClient, lb.ID).Extract() th.AssertNoErr(t, err) @@ -449,7 +463,7 @@ func TestLoadbalancersCascadeCRUD(t *testing.T) { defer networking.DeleteSubnet(t, netClient, subnet.ID) tags := []string{"test"} - lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags) + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags, "") th.AssertNoErr(t, err) defer CascadeDeleteLoadBalancer(t, lbClient, lb.ID) diff --git a/openstack/loadbalancer/v2/loadbalancers/requests.go b/openstack/loadbalancer/v2/loadbalancers/requests.go index 42179ce7e3..099113c418 100644 --- a/openstack/loadbalancer/v2/loadbalancers/requests.go +++ b/openstack/loadbalancer/v2/loadbalancers/requests.go @@ -107,6 +107,9 @@ type CreateOpts struct { // The IP address of the Loadbalancer. VipAddress string `json:"vip_address,omitempty"` + // The ID of the QoS Policy which will apply to the Virtual IP + VipQosPolicyID string `json:"vip_qos_policy_id,omitempty"` + // The administrative state of the Loadbalancer. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` @@ -185,6 +188,9 @@ type UpdateOpts struct { // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` + // The ID of the QoS Policy which will apply to the Virtual IP + VipQosPolicyID *string `json:"vip_qos_policy_id,omitempty"` + // Tags is a set of resource tags. Tags *[]string `json:"tags,omitempty"` } diff --git a/openstack/loadbalancer/v2/loadbalancers/results.go b/openstack/loadbalancer/v2/loadbalancers/results.go index 9a385363f2..739337c4d6 100644 --- a/openstack/loadbalancer/v2/loadbalancers/results.go +++ b/openstack/loadbalancer/v2/loadbalancers/results.go @@ -47,6 +47,9 @@ type LoadBalancer struct { // Loadbalancer address. VipNetworkID string `json:"vip_network_id"` + // The ID of the QoS Policy which will apply to the Virtual IP + VipQosPolicyID string `json:"vip_qos_policy_id"` + // The unique ID for the LoadBalancer. ID string `json:"id"` From 2169d6bf20b3e7965d3dc3e2f9a0e5e1979f3b4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Oct 2022 09:29:32 +0000 Subject: [PATCH 26/44] Bump EmilienM/devstack-action from 0.8 to 0.9 Bumps [EmilienM/devstack-action](https://github.com/EmilienM/devstack-action) from 0.8 to 0.9. - [Release notes](https://github.com/EmilienM/devstack-action/releases) - [Commits](https://github.com/EmilienM/devstack-action/compare/v0.8...v0.9) --- updated-dependencies: - dependency-name: EmilienM/devstack-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/functional-baremetal.yaml | 2 +- .github/workflows/functional-basic.yaml | 2 +- .github/workflows/functional-blockstorage.yaml | 2 +- .github/workflows/functional-clustering.yaml | 2 +- .github/workflows/functional-compute.yaml | 2 +- .github/workflows/functional-containerinfra.yaml | 2 +- .github/workflows/functional-dns.yaml | 2 +- .github/workflows/functional-identity.yaml | 2 +- .github/workflows/functional-imageservice.yaml | 2 +- .github/workflows/functional-keymanager.yaml | 2 +- .github/workflows/functional-loadbalancer.yaml | 2 +- .github/workflows/functional-messaging.yaml | 2 +- .github/workflows/functional-networking.yaml | 2 +- .github/workflows/functional-objectstorage.yaml | 2 +- .github/workflows/functional-orchestration.yaml | 2 +- .github/workflows/functional-placement.yaml | 2 +- .github/workflows/functional-sharedfilesystems.yaml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/functional-baremetal.yaml b/.github/workflows/functional-baremetal.yaml index 065c0a5147..f9f0925d75 100644 --- a/.github/workflows/functional-baremetal.yaml +++ b/.github/workflows/functional-baremetal.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-basic.yaml b/.github/workflows/functional-basic.yaml index 97e3fe0feb..778f7bcad2 100644 --- a/.github/workflows/functional-basic.yaml +++ b/.github/workflows/functional-basic.yaml @@ -41,7 +41,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} enabled_services: 's-account,s-container,s-object,s-proxy' diff --git a/.github/workflows/functional-blockstorage.yaml b/.github/workflows/functional-blockstorage.yaml index 557e737c31..8baed7dc45 100644 --- a/.github/workflows/functional-blockstorage.yaml +++ b/.github/workflows/functional-blockstorage.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-clustering.yaml b/.github/workflows/functional-clustering.yaml index 128c4c14ca..9bd52ee967 100644 --- a/.github/workflows/functional-clustering.yaml +++ b/.github/workflows/functional-clustering.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-compute.yaml b/.github/workflows/functional-compute.yaml index 3f788fa53b..f15786a2b0 100644 --- a/.github/workflows/functional-compute.yaml +++ b/.github/workflows/functional-compute.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-containerinfra.yaml b/.github/workflows/functional-containerinfra.yaml index 2f6b58f76e..8e320a1ff5 100644 --- a/.github/workflows/functional-containerinfra.yaml +++ b/.github/workflows/functional-containerinfra.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-dns.yaml b/.github/workflows/functional-dns.yaml index 572fdc9129..782c9e05d4 100644 --- a/.github/workflows/functional-dns.yaml +++ b/.github/workflows/functional-dns.yaml @@ -39,7 +39,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-identity.yaml b/.github/workflows/functional-identity.yaml index 97eca1b270..58b7c08434 100644 --- a/.github/workflows/functional-identity.yaml +++ b/.github/workflows/functional-identity.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} - name: Checkout go diff --git a/.github/workflows/functional-imageservice.yaml b/.github/workflows/functional-imageservice.yaml index ba57071d84..5059267da8 100644 --- a/.github/workflows/functional-imageservice.yaml +++ b/.github/workflows/functional-imageservice.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} - name: Checkout go diff --git a/.github/workflows/functional-keymanager.yaml b/.github/workflows/functional-keymanager.yaml index 25b9ed4568..53f0f0e15f 100644 --- a/.github/workflows/functional-keymanager.yaml +++ b/.github/workflows/functional-keymanager.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-loadbalancer.yaml b/.github/workflows/functional-loadbalancer.yaml index 15dafad29d..e73fb303b7 100644 --- a/.github/workflows/functional-loadbalancer.yaml +++ b/.github/workflows/functional-loadbalancer.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-messaging.yaml b/.github/workflows/functional-messaging.yaml index 5e74b98038..8215fa4c38 100644 --- a/.github/workflows/functional-messaging.yaml +++ b/.github/workflows/functional-messaging.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-networking.yaml b/.github/workflows/functional-networking.yaml index 9de7888106..dd1ccd7ff5 100644 --- a/.github/workflows/functional-networking.yaml +++ b/.github/workflows/functional-networking.yaml @@ -61,7 +61,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-objectstorage.yaml b/.github/workflows/functional-objectstorage.yaml index 5c79372ac8..f87be4b502 100644 --- a/.github/workflows/functional-objectstorage.yaml +++ b/.github/workflows/functional-objectstorage.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-orchestration.yaml b/.github/workflows/functional-orchestration.yaml index 2605e170fe..f3030bf2db 100644 --- a/.github/workflows/functional-orchestration.yaml +++ b/.github/workflows/functional-orchestration.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | diff --git a/.github/workflows/functional-placement.yaml b/.github/workflows/functional-placement.yaml index 784f2de6ce..5f6ba1fcb7 100644 --- a/.github/workflows/functional-placement.yaml +++ b/.github/workflows/functional-placement.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} - name: Checkout go diff --git a/.github/workflows/functional-sharedfilesystems.yaml b/.github/workflows/functional-sharedfilesystems.yaml index 47435f99d5..83c9d97519 100644 --- a/.github/workflows/functional-sharedfilesystems.yaml +++ b/.github/workflows/functional-sharedfilesystems.yaml @@ -38,7 +38,7 @@ jobs: - name: Checkout Gophercloud uses: actions/checkout@v3 - name: Deploy devstack - uses: EmilienM/devstack-action@v0.8 + uses: EmilienM/devstack-action@v0.9 with: branch: ${{ matrix.openstack_version }} conf_overrides: | From 32266607e09a4ae3f5da2a7d5806f630fc9fee2d Mon Sep 17 00:00:00 2001 From: Stas Kraev Date: Mon, 31 Oct 2022 20:55:52 +1300 Subject: [PATCH 27/44] Add support for l3-agent-scheduler extensions --- .../v2/extensions/agents/requests.go | 46 +++++++++ .../v2/extensions/agents/results.go | 31 ++++++ .../v2/extensions/agents/testing/fixtures.go | 86 ++++++++++++++++ .../agents/testing/requests_test.go | 98 +++++++++++++++++++ .../networking/v2/extensions/agents/urls.go | 19 ++++ 5 files changed, 280 insertions(+) diff --git a/openstack/networking/v2/extensions/agents/requests.go b/openstack/networking/v2/extensions/agents/requests.go index 707f710c4e..5a3c4c35c3 100644 --- a/openstack/networking/v2/extensions/agents/requests.go +++ b/openstack/networking/v2/extensions/agents/requests.go @@ -205,3 +205,49 @@ func ListDRAgentHostingBGPSpeakers(c *gophercloud.ServiceClient, bgpSpeakerID st return AgentPage{pagination.LinkedPageBase{PageResult: r}} }) } + +// ListL3Routers returns a list of routers scheduled to a specific +// L3 agent. +func ListL3Routers(c *gophercloud.ServiceClient, id string) (r ListL3RoutersResult) { + resp, err := c.Get(listL3RoutersURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ScheduleL3RouterOptsBuilder allows extensions to add additional parameters +// to the ScheduleL3Router request. +type ScheduleL3RouterOptsBuilder interface { + ToAgentScheduleL3RouterMap() (map[string]interface{}, error) +} + +// ScheduleL3RouterOpts represents the attributes used when scheduling a +// router to a L3 agent. +type ScheduleL3RouterOpts struct { + RouterID string `json:"router_id" required:"true"` +} + +// ToAgentScheduleL3RouterMap builds a request body from ScheduleL3RouterOpts. +func (opts ScheduleL3RouterOpts) ToAgentScheduleL3RouterMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// ScheduleL3Router schedule a router to a L3 agent. +func ScheduleL3Router(c *gophercloud.ServiceClient, id string, opts ScheduleL3RouterOptsBuilder) (r ScheduleL3RouterResult) { + b, err := opts.ToAgentScheduleL3RouterMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Post(scheduleL3RouterURL(c, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RemoveL3Router removes a router from a L3 agent. +func RemoveL3Router(c *gophercloud.ServiceClient, id string, routerID string) (r RemoveL3RouterResult) { + resp, err := c.Delete(removeL3RouterURL(c, id, routerID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/networking/v2/extensions/agents/results.go b/openstack/networking/v2/extensions/agents/results.go index 5f66eb37fe..0af9e0fdd0 100644 --- a/openstack/networking/v2/extensions/agents/results.go +++ b/openstack/networking/v2/extensions/agents/results.go @@ -6,6 +6,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/bgp/speakers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/pagination" ) @@ -208,3 +209,33 @@ func ExtractBGPSpeakers(r pagination.Page) ([]speakers.BGPSpeaker, error) { err := (r.(ListBGPSpeakersResult)).ExtractInto(&s) return s.Speakers, err } + +// ListL3RoutersResult is the response from a List operation. +// Call its Extract method to interpret it as routers. +type ListL3RoutersResult struct { + gophercloud.Result +} + +// ScheduleL3RouterResult represents the result of a schedule a router to +// a L3 agent operation. ExtractErr method to determine if the request +// succeeded or failed. +type ScheduleL3RouterResult struct { + gophercloud.ErrResult +} + +// RemoveL3RouterResult represents the result of a remove a router from a +// L3 agent operation. ExtractErr method to determine if the request succeeded +// or failed. +type RemoveL3RouterResult struct { + gophercloud.ErrResult +} + +// Extract interprets any ListL3RoutesResult as an array of routers. +func (r ListL3RoutersResult) Extract() ([]routers.Router, error) { + var s struct { + Routers []routers.Router `json:"routers"` + } + + err := r.ExtractInto(&s) + return s.Routers, err +} diff --git a/openstack/networking/v2/extensions/agents/testing/fixtures.go b/openstack/networking/v2/extensions/agents/testing/fixtures.go index 35b4ca5232..d40bc6ecdb 100644 --- a/openstack/networking/v2/extensions/agents/testing/fixtures.go +++ b/openstack/networking/v2/extensions/agents/testing/fixtures.go @@ -345,3 +345,89 @@ const ListDRAgentHostingBGPSpeakersResult = ` ] } ` + +// AgentL3ListListResult represents raw response for the ListL3Routers request. +const AgentL3RoutersListResult = ` +{ + "routers": [ + { + "admin_state_up": true, + "availability_zone_hints": [], + "availability_zones": [ + "nova" + ], + "description": "", + "distributed": false, + "external_gateway_info": { + "enable_snat": true, + "external_fixed_ips": [ + { + "ip_address": "172.24.4.3", + "subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf" + }, + { + "ip_address": "2001:db8::c", + "subnet_id": "0c56df5d-ace5-46c8-8f4c-45fa4e334d18" + } + ], + "network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3" + }, + "flavor_id": "f7b14d9a-b0dc-4fbe-bb14-a0f4970a69e0", + "ha": false, + "id": "915a14a6-867b-4af7-83d1-70efceb146f9", + "name": "router2", + "revision_number": 1, + "routes": [ + { + "destination": "179.24.1.0/24", + "nexthop": "172.24.3.99" + } + ], + "status": "ACTIVE", + "project_id": "0bd18306d801447bb457a46252d82d13", + "tenant_id": "0bd18306d801447bb457a46252d82d13", + "service_type_id": null + }, + { + "admin_state_up": true, + "availability_zone_hints": [], + "availability_zones": [ + "nova" + ], + "description": "", + "distributed": false, + "external_gateway_info": { + "enable_snat": true, + "external_fixed_ips": [ + { + "ip_address": "172.24.4.6", + "subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf" + }, + { + "ip_address": "2001:db8::9", + "subnet_id": "0c56df5d-ace5-46c8-8f4c-45fa4e334d18" + } + ], + "network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3" + }, + "flavor_id": "f7b14d9a-b0dc-4fbe-bb14-a0f4970a69e0", + "ha": false, + "id": "f8a44de0-fc8e-45df-93c7-f79bf3b01c95", + "name": "router1", + "revision_number": 1, + "routes": [], + "status": "ACTIVE", + "project_id": "0bd18306d801447bb457a46252d82d13", + "tenant_id": "0bd18306d801447bb457a46252d82d13", + "service_type_id": null + } + ] +} +` + +// ScheduleL3RouterRequest represents raw request for the ScheduleL3Router request. +const ScheduleL3RouterRequest = ` +{ + "router_id": "43e66290-79a4-415d-9eb9-7ff7919839e1" +} +` diff --git a/openstack/networking/v2/extensions/agents/testing/requests_test.go b/openstack/networking/v2/extensions/agents/testing/requests_test.go index 10411b5910..296e8a2cdf 100644 --- a/openstack/networking/v2/extensions/agents/testing/requests_test.go +++ b/openstack/networking/v2/extensions/agents/testing/requests_test.go @@ -8,6 +8,7 @@ import ( fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" ) @@ -323,3 +324,100 @@ func TestListDRAgentHostingBGPSpeakers(t *testing.T) { t.Errorf("Expected 1 page, got %d", count) } } + +func TestListL3Routers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AgentL3RoutersListResult) + }) + + s, err := agents.ListL3Routers(fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() + th.AssertNoErr(t, err) + + routes := []routers.Route{ + { + "172.24.3.99", + "179.24.1.0/24", + }, + } + + var snat bool = true + gw := routers.GatewayInfo{ + EnableSNAT: &snat, + NetworkID: "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3", + ExternalFixedIPs: []routers.ExternalFixedIP{ + { + IPAddress: "172.24.4.3", + SubnetID: "b930d7f6-ceb7-40a0-8b81-a425dd994ccf", + }, + + { + IPAddress: "2001:db8::c", + SubnetID: "0c56df5d-ace5-46c8-8f4c-45fa4e334d18", + }, + }, + } + + var nilSlice []string + th.AssertEquals(t, len(s), 2) + th.AssertEquals(t, s[0].ID, "915a14a6-867b-4af7-83d1-70efceb146f9") + th.AssertEquals(t, s[0].AdminStateUp, true) + th.AssertEquals(t, s[0].ProjectID, "0bd18306d801447bb457a46252d82d13") + th.AssertEquals(t, s[0].Name, "router2") + th.AssertEquals(t, s[0].Status, "ACTIVE") + th.AssertEquals(t, s[0].TenantID, "0bd18306d801447bb457a46252d82d13") + th.AssertDeepEquals(t, s[0].AvailabilityZoneHints, []string{}) + th.AssertDeepEquals(t, s[0].Routes, routes) + th.AssertDeepEquals(t, s[0].GatewayInfo, gw) + th.AssertDeepEquals(t, s[0].Tags, nilSlice) + th.AssertEquals(t, s[1].ID, "f8a44de0-fc8e-45df-93c7-f79bf3b01c95") + th.AssertEquals(t, s[1].Name, "router1") + +} + +func TestScheduleL3Router(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ScheduleL3RouterRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + }) + + opts := &agents.ScheduleL3RouterOpts{ + RouterID: "43e66290-79a4-415d-9eb9-7ff7919839e1", + } + err := agents.ScheduleL3Router(fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestRemoveL3Router(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers/43e66290-79a4-415d-9eb9-7ff7919839e1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) + + err := agents.RemoveL3Router(fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", "43e66290-79a4-415d-9eb9-7ff7919839e1").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/openstack/networking/v2/extensions/agents/urls.go b/openstack/networking/v2/extensions/agents/urls.go index f6021fce2d..d4581ea3e7 100644 --- a/openstack/networking/v2/extensions/agents/urls.go +++ b/openstack/networking/v2/extensions/agents/urls.go @@ -4,6 +4,7 @@ import "github.com/gophercloud/gophercloud" const resourcePath = "agents" const dhcpNetworksResourcePath = "dhcp-networks" +const l3RoutersResourcePath = "l3-routers" const bgpSpeakersResourcePath = "bgp-drinstances" const bgpDRAgentSpeakersResourcePath = "bgp-speakers" const bgpDRAgentAgentResourcePath = "bgp-dragents" @@ -36,18 +37,36 @@ func dhcpNetworksURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL(resourcePath, id, dhcpNetworksResourcePath) } +func l3RoutersURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, l3RoutersResourcePath) +} + func listDHCPNetworksURL(c *gophercloud.ServiceClient, id string) string { return dhcpNetworksURL(c, id) } +func listL3RoutersURL(c *gophercloud.ServiceClient, id string) string { + // TODO + // hmm list should be the plain l3RoutersURL but dhcp example tell otherwise + return l3RoutersURL(c, id) +} + func scheduleDHCPNetworkURL(c *gophercloud.ServiceClient, id string) string { return dhcpNetworksURL(c, id) } +func scheduleL3RouterURL(c *gophercloud.ServiceClient, id string) string { + return l3RoutersURL(c, id) +} + func removeDHCPNetworkURL(c *gophercloud.ServiceClient, id string, networkID string) string { return c.ServiceURL(resourcePath, id, dhcpNetworksResourcePath, networkID) } +func removeL3RouterURL(c *gophercloud.ServiceClient, id string, routerID string) string { + return c.ServiceURL(resourcePath, id, l3RoutersResourcePath, routerID) +} + // return /v2.0/agents/{agent-id}/bgp-drinstances func listBGPSpeakersURL(c *gophercloud.ServiceClient, agentID string) string { return c.ServiceURL(resourcePath, agentID, bgpSpeakersResourcePath) From effefce7d06135d7c4b80cbc9948ef81adc615a6 Mon Sep 17 00:00:00 2001 From: Stas Kraev Date: Tue, 1 Nov 2022 12:20:27 +1300 Subject: [PATCH 28/44] Add documentation for l3-agent-scheduler --- .../networking/v2/extensions/agents/doc.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openstack/networking/v2/extensions/agents/doc.go b/openstack/networking/v2/extensions/agents/doc.go index 59eb233bd4..a52c709781 100644 --- a/openstack/networking/v2/extensions/agents/doc.go +++ b/openstack/networking/v2/extensions/agents/doc.go @@ -126,6 +126,37 @@ Example to list dragents hosting specific bgp speaker for _, a := range allAgents { log.Printf("%+v", a) } + +Example to list routers scheduled to L3 agent + + routers, err := agents.ListL3Routers(neutron, "655967f5-d6f3-4732-88f5-617b0ff5c356").Extract() + if err != nil { + log.Panic(err) + } + + for _, r := range routers { + log.Printf("%+v", r) + } + +Example to remove router from L3 agent + + agentID := "0e1095ae-6f36-40f3-8322-8e1c9a5e68ca" + routerID := "e6fa0457-efc2-491d-ac12-17ab60417efd" + err = agents.RemoveL3Router(neutron, "0e1095ae-6f36-40f3-8322-8e1c9a5e68ca", "e6fa0457-efc2-491d-ac12-17ab60417efd").ExtractErr() + if err != nil { + log.Panic(err) + } + +Example to schedule router to L3 agent + + agentID := "0e1095ae-6f36-40f3-8322-8e1c9a5e68ca" + routerID := "e6fa0457-efc2-491d-ac12-17ab60417efd" + err = agents.ScheduleL3Router(neutron, agentID, agents.ScheduleL3RouterOpts{routerID}).ExtractErr() + if err != nil { + log.Panic(err) + } + + */ package agents From a795059f3b5d7cd9a0c388840682355d00858259 Mon Sep 17 00:00:00 2001 From: Stas Kraev Date: Tue, 1 Nov 2022 20:59:35 +1300 Subject: [PATCH 29/44] Add acceptance test for l3-agent-scheduler --- .../extensions/layer3/l3_scheduling_test.go | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go diff --git a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go new file mode 100644 index 0000000000..7cc679bc83 --- /dev/null +++ b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go @@ -0,0 +1,76 @@ +//go:build acceptance || networking || layer3 || router +// +build acceptance networking layer3 router + +package layer3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestLayer3RouterScheduling(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, client, subnet.ID) + + router, err := CreateRouter(t, client, network.ID) + th.AssertNoErr(t, err) + defer DeleteRouter(t, client, router.ID) + tools.PrintResource(t, router) + + routerInterface, err := CreateRouterInterfaceOnSubnet(t, client, subnet.ID, router.ID) + tools.PrintResource(t, routerInterface) + th.AssertNoErr(t, err) + defer DeleteRouterInterface(t, client, routerInterface.PortID, router.ID) + + // List hosting agent + allPages, err := routers.ListL3Agents(client, router.ID).AllPages() + th.AssertNoErr(t, err) + hostingAgents, err := routers.ExtractL3Agents(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, len(hostingAgents) > 0, true) + hostingAgent := hostingAgents[0] + t.Logf("Router %s is scheduled on %s", router.ID, hostingAgent.ID) + + // remove from hosting agent + err = agents.RemoveL3Router(client, hostingAgent.ID, router.ID).ExtractErr() + th.AssertNoErr(t, err) + + containsRouterFunc := func(rs []routers.Router, routerID string) bool { + for _, r := range rs { + if r.ID == router.ID { + return true + } + } + return false + } + + // List routers on hosting agent + routersOnHostingAgent, err := agents.ListL3Routers(client, hostingAgent.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, containsRouterFunc(routersOnHostingAgent, router.ID), false) + t.Logf("Router %s is not scheduled on %s", router.ID, hostingAgent.ID) + + // schedule back + err = agents.ScheduleL3Router(client, hostingAgents[0].ID, agents.ScheduleL3RouterOpts{RouterID: router.ID}).ExtractErr() + th.AssertNoErr(t, err) + + // List hosting agent after readding + routersOnHostingAgent, err = agents.ListL3Routers(client, hostingAgent.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, containsRouterFunc(routersOnHostingAgent, router.ID), true) + t.Logf("Router %s is scheduled on %s", router.ID, hostingAgent.ID) +} From c5d5056080ec302e2a996f771c0a51195ff517b3 Mon Sep 17 00:00:00 2001 From: Stas Kraev Date: Tue, 1 Nov 2022 23:23:16 +1300 Subject: [PATCH 30/44] Fix go vet in neutron tests --- .../networking/v2/extensions/agents/testing/requests_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstack/networking/v2/extensions/agents/testing/requests_test.go b/openstack/networking/v2/extensions/agents/testing/requests_test.go index 296e8a2cdf..d1afbc5c98 100644 --- a/openstack/networking/v2/extensions/agents/testing/requests_test.go +++ b/openstack/networking/v2/extensions/agents/testing/requests_test.go @@ -344,8 +344,8 @@ func TestListL3Routers(t *testing.T) { routes := []routers.Route{ { - "172.24.3.99", - "179.24.1.0/24", + NextHop: "172.24.3.99", + DestinationCIDR: "179.24.1.0/24", }, } From 1a3982feca40f4c5313fe46f8310a7a8c06d6aa3 Mon Sep 17 00:00:00 2001 From: Stas Kraev Date: Tue, 1 Nov 2022 23:35:54 +1300 Subject: [PATCH 31/44] Skip l3_scheduling acceptance test when extension not enabled --- .../networking/v2/extensions/layer3/l3_scheduling_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go index 7cc679bc83..905246af18 100644 --- a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go +++ b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go @@ -9,6 +9,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/common/extensions" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" th "github.com/gophercloud/gophercloud/testhelper" @@ -18,6 +19,11 @@ func TestLayer3RouterScheduling(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + _, err = extensions.Get(client, "l3_agent_scheduler").Extract() + if err != nil { + t.Skip("Extension l3_agent_scheduler not present") + } + network, err := networking.CreateNetwork(t, client) th.AssertNoErr(t, err) defer networking.DeleteNetwork(t, client, network.ID) From 9b25f1c6cc05f1da1f33f97dbbc9379abee62f17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Nov 2022 09:06:30 +0000 Subject: [PATCH 32/44] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/semver-labels.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semver-labels.yaml b/.github/workflows/semver-labels.yaml index e6644a7bd7..ccaf44522b 100644 --- a/.github/workflows/semver-labels.yaml +++ b/.github/workflows/semver-labels.yaml @@ -10,7 +10,7 @@ jobs: semver: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: micnncim/action-label-syncer@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 93fcf241ce3405b530ce561082b29bd6d3afcea7 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Mon, 7 Nov 2022 11:17:43 +0100 Subject: [PATCH 33/44] Bump golang.org/x/crypto Update the depedency to the last commit that is compatible with Go v1.14. The [next commit][1] replaces calls to `io/ioutil` to its replacements in the `io` and `os` packages, which are not available in Go v1.14. [1]: https://go-review.googlesource.com/c/crypto/+/430797 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c51d7daaaf..0c7f0517e6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/gophercloud/gophercloud go 1.14 require ( - golang.org/x/crypto v0.0.0-20211202192323-5770296d904e + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index dec4af3cdc..0d5a1cde5a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8= -golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 90b1b76f03b2d14c7aa502b6356464d7fbc8da92 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Mon, 7 Nov 2022 15:55:53 +0100 Subject: [PATCH 34/44] tests: Fix Go v1.14 "go vet" We use "go get" to install test dependencies in Go v1.14. As a side effect, `go.sum` was polluted and `golang.org/x/crypto` was bumped to a version that is incompatible with Go v1.14. With this change, installing test dependencies no longer changes the state of the checked-out repository. --- .github/workflows/unit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index bca05da058..dd6832b2af 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -29,6 +29,10 @@ jobs: - name: Setup environment run: | + # Changing into a different directory to avoid polluting go.sum with "go get" + cd "$(mktemp -d)" + + # we use "go get" for Go v1.14 go install github.com/wadey/gocovmerge@master || go get github.com/wadey/gocovmerge go install golang.org/x/tools/cmd/goimports@latest || go get golang.org/x/tools/cmd/goimports From 904e42c24a38470a489405762fa519bf564d0d3c Mon Sep 17 00:00:00 2001 From: Stas Kraev Date: Fri, 11 Nov 2022 15:18:53 +1300 Subject: [PATCH 35/44] Addressing review for l3-agent-scheduling --- .../networking/v2/extensions/layer3/l3_scheduling_test.go | 2 +- openstack/networking/v2/extensions/agents/doc.go | 4 ++-- openstack/networking/v2/extensions/agents/urls.go | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go index 905246af18..f5a5ba27dc 100644 --- a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go +++ b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go @@ -47,7 +47,7 @@ func TestLayer3RouterScheduling(t *testing.T) { th.AssertNoErr(t, err) hostingAgents, err := routers.ExtractL3Agents(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(hostingAgents) > 0, true) + th.AssertIntGreaterOrEqual(t, len(hostingAgents), 0) hostingAgent := hostingAgents[0] t.Logf("Router %s is scheduled on %s", router.ID, hostingAgent.ID) diff --git a/openstack/networking/v2/extensions/agents/doc.go b/openstack/networking/v2/extensions/agents/doc.go index a52c709781..83bc09cfdb 100644 --- a/openstack/networking/v2/extensions/agents/doc.go +++ b/openstack/networking/v2/extensions/agents/doc.go @@ -142,7 +142,7 @@ Example to remove router from L3 agent agentID := "0e1095ae-6f36-40f3-8322-8e1c9a5e68ca" routerID := "e6fa0457-efc2-491d-ac12-17ab60417efd" - err = agents.RemoveL3Router(neutron, "0e1095ae-6f36-40f3-8322-8e1c9a5e68ca", "e6fa0457-efc2-491d-ac12-17ab60417efd").ExtractErr() + err = agents.RemoveL3Router(neutron, agentID, routerID).ExtractErr() if err != nil { log.Panic(err) } @@ -151,7 +151,7 @@ Example to schedule router to L3 agent agentID := "0e1095ae-6f36-40f3-8322-8e1c9a5e68ca" routerID := "e6fa0457-efc2-491d-ac12-17ab60417efd" - err = agents.ScheduleL3Router(neutron, agentID, agents.ScheduleL3RouterOpts{routerID}).ExtractErr() + err = agents.ScheduleL3Router(neutron, agentID, agents.ScheduleL3RouterOpts{RouterID: routerID}).ExtractErr() if err != nil { log.Panic(err) } diff --git a/openstack/networking/v2/extensions/agents/urls.go b/openstack/networking/v2/extensions/agents/urls.go index d4581ea3e7..3ee3e02dcd 100644 --- a/openstack/networking/v2/extensions/agents/urls.go +++ b/openstack/networking/v2/extensions/agents/urls.go @@ -46,8 +46,6 @@ func listDHCPNetworksURL(c *gophercloud.ServiceClient, id string) string { } func listL3RoutersURL(c *gophercloud.ServiceClient, id string) string { - // TODO - // hmm list should be the plain l3RoutersURL but dhcp example tell otherwise return l3RoutersURL(c, id) } From 918cd8378747db5360fbc5810eb6f3cf229a8457 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Sat, 12 Nov 2022 09:14:55 +1300 Subject: [PATCH 36/44] Fixes misspel in l3-scheduling acceptance test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martin André --- .../networking/v2/extensions/layer3/l3_scheduling_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go index f5a5ba27dc..ec32dfda11 100644 --- a/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go +++ b/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go @@ -47,7 +47,7 @@ func TestLayer3RouterScheduling(t *testing.T) { th.AssertNoErr(t, err) hostingAgents, err := routers.ExtractL3Agents(allPages) th.AssertNoErr(t, err) - th.AssertIntGreaterOrEqual(t, len(hostingAgents), 0) + th.AssertIntGreaterOrEqual(t, len(hostingAgents), 1) hostingAgent := hostingAgents[0] t.Logf("Router %s is scheduled on %s", router.ID, hostingAgent.ID) From 022c50f253ebd8ebcd5fedeec7141307411cd74c Mon Sep 17 00:00:00 2001 From: artem_lifshits Date: Wed, 16 Nov 2022 11:11:07 +0300 Subject: [PATCH 37/44] Add available domain listing --- .../openstack/identity/v3/domains_test.go | 17 +++++ openstack/identity/v3/domains/requests.go | 8 +++ .../identity/v3/domains/testing/fixtures.go | 71 +++++++++++++++++++ .../v3/domains/testing/requests_test.go | 20 ++++++ openstack/identity/v3/domains/urls.go | 4 ++ 5 files changed, 120 insertions(+) diff --git a/acceptance/openstack/identity/v3/domains_test.go b/acceptance/openstack/identity/v3/domains_test.go index 48ad8247c1..5e9d06b266 100644 --- a/acceptance/openstack/identity/v3/domains_test.go +++ b/acceptance/openstack/identity/v3/domains_test.go @@ -12,6 +12,23 @@ import ( th "github.com/gophercloud/gophercloud/testhelper" ) +func TestDomainsListAvailable(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + allPages, err := domains.ListAvailable(client).AllPages() + th.AssertNoErr(t, err) + + allDomains, err := domains.ExtractDomains(allPages) + th.AssertNoErr(t, err) + + for _, domain := range allDomains { + tools.PrintResource(t, domain) + } +} + func TestDomainsList(t *testing.T) { clients.RequireAdmin(t) diff --git a/openstack/identity/v3/domains/requests.go b/openstack/identity/v3/domains/requests.go index 78847c8794..bf911d05c7 100644 --- a/openstack/identity/v3/domains/requests.go +++ b/openstack/identity/v3/domains/requests.go @@ -41,6 +41,14 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa }) } +// ListAvailable enumerates the domains which are available to a specific user. +func ListAvailable(client *gophercloud.ServiceClient) pagination.Pager { + url := listAvailableURL(client) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return DomainPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + // Get retrieves details on a single domain, by ID. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { resp, err := client.Get(getURL(client, id), &r.Body, nil) diff --git a/openstack/identity/v3/domains/testing/fixtures.go b/openstack/identity/v3/domains/testing/fixtures.go index 87ac561b5a..db328831b2 100644 --- a/openstack/identity/v3/domains/testing/fixtures.go +++ b/openstack/identity/v3/domains/testing/fixtures.go @@ -10,6 +10,37 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +// ListAvailableOutput provides a single page of available domain results. +const ListAvailableOutput = ` +{ + "domains": [ + { + "id": "52af04aec5f84182b06959d2775d2000", + "name": "TestDomain", + "description": "Testing domain", + "enabled": false, + "links": { + "self": "https://example.com/v3/domains/52af04aec5f84182b06959d2775d2000" + } + }, + { + "id": "a720688fb87f4575a4c000d818061eae", + "name": "ProdDomain", + "description": "Production domain", + "enabled": true, + "links": { + "self": "https://example.com/v3/domains/a720688fb87f4575a4c000d818061eae" + } + } + ], + "links": { + "next": null, + "self": "https://example.com/v3/auth/domains", + "previous": null + } +} +` + // ListOutput provides a single page of Domain results. const ListOutput = ` { @@ -87,6 +118,28 @@ const UpdateOutput = ` } ` +// ProdDomain is a domain fixture. +var ProdDomain = domains.Domain{ + Enabled: true, + ID: "a720688fb87f4575a4c000d818061eae", + Links: map[string]interface{}{ + "self": "https://example.com/v3/domains/a720688fb87f4575a4c000d818061eae", + }, + Name: "ProdDomain", + Description: "Production domain", +} + +// TestDomain is a domain fixture. +var TestDomain = domains.Domain{ + Enabled: false, + ID: "52af04aec5f84182b06959d2775d2000", + Links: map[string]interface{}{ + "self": "https://example.com/v3/domains/52af04aec5f84182b06959d2775d2000", + }, + Name: "TestDomain", + Description: "Testing domain", +} + // FirstDomain is the first domain in the List request. var FirstDomain = domains.Domain{ Enabled: true, @@ -119,9 +172,27 @@ var SecondDomainUpdated = domains.Domain{ Description: "Staging Domain", } +// ExpectedAvailableDomainsSlice is the slice of domains expected to be returned +// from ListAvailableOutput. +var ExpectedAvailableDomainsSlice = []domains.Domain{TestDomain, ProdDomain} + // ExpectedDomainsSlice is the slice of domains expected to be returned from ListOutput. var ExpectedDomainsSlice = []domains.Domain{FirstDomain, SecondDomain} +// HandleListAvailableDomainsSuccessfully creates an HTTP handler at `/auth/domains` +// on the test handler mux that responds with a list of two domains. +func HandleListAvailableDomainsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/auth/domains", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListAvailableOutput) + }) +} + // HandleListDomainsSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that responds with a list of two domains. func HandleListDomainsSuccessfully(t *testing.T) { diff --git a/openstack/identity/v3/domains/testing/requests_test.go b/openstack/identity/v3/domains/testing/requests_test.go index 07eeb06ca0..0f8d6fc7cf 100644 --- a/openstack/identity/v3/domains/testing/requests_test.go +++ b/openstack/identity/v3/domains/testing/requests_test.go @@ -9,6 +9,26 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +func TestListAvailableDomains(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListAvailableDomainsSuccessfully(t) + + count := 0 + err := domains.ListAvailable(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := domains.ExtractDomains(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedAvailableDomainsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + func TestListDomains(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/openstack/identity/v3/domains/urls.go b/openstack/identity/v3/domains/urls.go index b0c21b80be..902532cc39 100644 --- a/openstack/identity/v3/domains/urls.go +++ b/openstack/identity/v3/domains/urls.go @@ -2,6 +2,10 @@ package domains import "github.com/gophercloud/gophercloud" +func listAvailableURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("auth", "domains") +} + func listURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("domains") } From 41a6bc5d3dd6bf0e19fd2937f80bfb5f7e6cf14a Mon Sep 17 00:00:00 2001 From: Robert Vasek Date: Thu, 20 Oct 2022 14:57:07 +0200 Subject: [PATCH 38/44] Manila: add Get for share-access-rules API --- .../sharedfilesystems/v2/shareaccessrules.go | 18 +++++ .../v2/shareaccessrules_test.go | 48 ++++++++++++++ .../v2/shareaccessrules/requests.go | 12 ++++ .../v2/shareaccessrules/results.go | 65 +++++++++++++++++++ .../v2/shareaccessrules/testing/fixtures.go | 45 +++++++++++++ .../shareaccessrules/testing/requests_test.go | 39 +++++++++++ .../v2/shareaccessrules/urls.go | 11 ++++ 7 files changed, 238 insertions(+) create mode 100644 acceptance/openstack/sharedfilesystems/v2/shareaccessrules.go create mode 100644 acceptance/openstack/sharedfilesystems/v2/shareaccessrules_test.go create mode 100644 openstack/sharedfilesystems/v2/shareaccessrules/requests.go create mode 100644 openstack/sharedfilesystems/v2/shareaccessrules/results.go create mode 100644 openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures.go create mode 100644 openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go create mode 100644 openstack/sharedfilesystems/v2/shareaccessrules/urls.go diff --git a/acceptance/openstack/sharedfilesystems/v2/shareaccessrules.go b/acceptance/openstack/sharedfilesystems/v2/shareaccessrules.go new file mode 100644 index 0000000000..8a8c746503 --- /dev/null +++ b/acceptance/openstack/sharedfilesystems/v2/shareaccessrules.go @@ -0,0 +1,18 @@ +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shareaccessrules" +) + +func ShareAccessRuleGet(t *testing.T, client *gophercloud.ServiceClient, accessID string) (*shareaccessrules.ShareAccess, error) { + accessRule, err := shareaccessrules.Get(client, accessID).Extract() + if err != nil { + t.Logf("Failed to get share access rule %s: %v", accessID, err) + return nil, err + } + + return accessRule, nil +} diff --git a/acceptance/openstack/sharedfilesystems/v2/shareaccessrules_test.go b/acceptance/openstack/sharedfilesystems/v2/shareaccessrules_test.go new file mode 100644 index 0000000000..5da64a7252 --- /dev/null +++ b/acceptance/openstack/sharedfilesystems/v2/shareaccessrules_test.go @@ -0,0 +1,48 @@ +//go:build acceptance +// +build acceptance + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestShareAccessRulesGet(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + + client.Microversion = "2.49" + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + shareAccessRight, err := GrantAccess(t, client, share) + if err != nil { + t.Fatalf("Unable to grant access to share %s: %v", share.ID, err) + } + + accessRule, err := ShareAccessRuleGet(t, client, shareAccessRight.ID) + if err != nil { + t.Logf("Unable to get share access rule for share %s: %v", share.ID, err) + } + + tools.PrintResource(t, accessRule) + + th.AssertEquals(t, shareAccessRight.ID, accessRule.ID) + th.AssertEquals(t, shareAccessRight.ShareID, accessRule.ShareID) + th.AssertEquals(t, shareAccessRight.AccessType, accessRule.AccessType) + th.AssertEquals(t, shareAccessRight.AccessLevel, accessRule.AccessLevel) + th.AssertEquals(t, shareAccessRight.AccessTo, accessRule.AccessTo) + th.AssertEquals(t, shareAccessRight.AccessKey, accessRule.AccessKey) + th.AssertEquals(t, shareAccessRight.State, accessRule.State) +} diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/requests.go b/openstack/sharedfilesystems/v2/shareaccessrules/requests.go new file mode 100644 index 0000000000..491085d5c1 --- /dev/null +++ b/openstack/sharedfilesystems/v2/shareaccessrules/requests.go @@ -0,0 +1,12 @@ +package shareaccessrules + +import ( + "github.com/gophercloud/gophercloud" +) + +// Get retrieves details about a share access rule. +func Get(client *gophercloud.ServiceClient, accessID string) (r GetResult) { + resp, err := client.Get(getURL(client, accessID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/results.go b/openstack/sharedfilesystems/v2/shareaccessrules/results.go new file mode 100644 index 0000000000..2e54f5d410 --- /dev/null +++ b/openstack/sharedfilesystems/v2/shareaccessrules/results.go @@ -0,0 +1,65 @@ +package shareaccessrules + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" +) + +// ShareAccess contains information associated with an OpenStack share access rule. +type ShareAccess struct { + // The UUID of the share to which you are granted or denied access. + ShareID string `json:"share_id"` + // The date and time stamp when the resource was created within the service’s database. + CreatedAt time.Time `json:"-"` + // The date and time stamp when the resource was last updated within the service’s database. + UpdatedAt time.Time `json:"-"` + // The access rule type. + AccessType string `json:"access_type"` + // The value that defines the access. The back end grants or denies the access to it. + AccessTo string `json:"access_to"` + // The access credential of the entity granted share access. + AccessKey string `json:"access_key"` + // The state of the access rule. + State string `json:"state"` + // The access level to the share. + AccessLevel string `json:"access_level"` + // The access rule ID. + ID string `json:"id"` + // Access rule metadata. + Metadata map[string]interface{} `json:"metadata"` +} + +func (r *ShareAccess) UnmarshalJSON(b []byte) error { + type tmp ShareAccess + 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 + } + *r = ShareAccess(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} + +// GetResult contains the response body and error from a Get request. +type GetResult struct { + gophercloud.Result +} + +// Extract will get the ShareAccess object from the GetResult. +func (r GetResult) Extract() (*ShareAccess, error) { + var s struct { + ShareAccess *ShareAccess `json:"access"` + } + err := r.ExtractInto(&s) + return s.ShareAccess, err +} diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures.go b/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures.go new file mode 100644 index 0000000000..2f053961f9 --- /dev/null +++ b/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures.go @@ -0,0 +1,45 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ( + shareAccessRulesEndpoint = "/share-access-rules" + shareAccessRuleID = "507bf114-36f2-4f56-8cf4-857985ca87c1" + shareID = "fb213952-2352-41b4-ad7b-2c4c69d13eef" +) + +var getResponse = `{ + "access": { + "access_level": "rw", + "state": "error", + "id": "507bf114-36f2-4f56-8cf4-857985ca87c1", + "share_id": "fb213952-2352-41b4-ad7b-2c4c69d13eef", + "access_type": "cert", + "access_to": "example.com", + "access_key": null, + "created_at": "2018-07-17T02:01:04.000000", + "updated_at": "2018-07-17T02:01:04.000000", + "metadata": { + "key1": "value1", + "key2": "value2" + } + } +}` + +func MockGetResponse(t *testing.T) { + th.Mux.HandleFunc(shareAccessRulesEndpoint+"/"+shareAccessRuleID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, getResponse) + }) +} diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go b/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go new file mode 100644 index 0000000000..a04b5f877b --- /dev/null +++ b/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go @@ -0,0 +1,39 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shareaccessrules" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockGetResponse(t) + + resp := shareaccessrules.Get(client.ServiceClient(), "507bf114-36f2-4f56-8cf4-857985ca87c1") + th.AssertNoErr(t, resp.Err) + + accessRule, err := resp.Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, &shareaccessrules.ShareAccess{ + ShareID: "fb213952-2352-41b4-ad7b-2c4c69d13eef", + CreatedAt: time.Date(2018, 7, 17, 2, 1, 4, 0, time.UTC), + UpdatedAt: time.Date(2018, 7, 17, 2, 1, 4, 0, time.UTC), + AccessType: "cert", + AccessTo: "example.com", + AccessKey: "", + State: "error", + AccessLevel: "rw", + ID: "507bf114-36f2-4f56-8cf4-857985ca87c1", + Metadata: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + }, accessRule) +} diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/urls.go b/openstack/sharedfilesystems/v2/shareaccessrules/urls.go new file mode 100644 index 0000000000..02766301e4 --- /dev/null +++ b/openstack/sharedfilesystems/v2/shareaccessrules/urls.go @@ -0,0 +1,11 @@ +package shareaccessrules + +import ( + "github.com/gophercloud/gophercloud" +) + +const shareAccessRulesEndpoint = "share-access-rules" + +func getURL(c *gophercloud.ServiceClient, accessID string) string { + return c.ServiceURL(shareAccessRulesEndpoint, accessID) +} From 64ed1bc1ee4b8e3a063c11bf7fdcd8c8328fe18a Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Wed, 23 Nov 2022 12:50:29 +0100 Subject: [PATCH 39/44] objectstorage: Do not parse NoContent responses Some Swift instances respond with HTTP status code 204 when asked to list containers and there are no containers, or when asked to list objects on an empty container. Before this patch, Gophercloud would error because the header `content-type` is absent on the response. With this patch, objectstorage responses with HTTP status code 204 are immediately recognised as empty and their body is not read. --- .../objectstorage/v1/containers/results.go | 4 ++++ .../v1/containers/testing/fixtures.go | 13 +++++++++++ .../v1/containers/testing/requests_test.go | 12 ++++++++++ openstack/objectstorage/v1/objects/results.go | 4 ++++ .../v1/objects/testing/fixtures.go | 13 +++++++++++ .../v1/objects/testing/requests_test.go | 23 +++++++++++++++++++ pagination/http.go | 5 ++-- results.go | 5 ++++ 8 files changed, 77 insertions(+), 2 deletions(-) diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go index e6e6a0487b..c6dc61fa9f 100644 --- a/openstack/objectstorage/v1/containers/results.go +++ b/openstack/objectstorage/v1/containers/results.go @@ -30,6 +30,10 @@ type ContainerPage struct { // IsEmpty returns true if a ListResult contains no container names. func (r ContainerPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + names, err := ExtractNames(r) return len(names) == 0, err } diff --git a/openstack/objectstorage/v1/containers/testing/fixtures.go b/openstack/objectstorage/v1/containers/testing/fixtures.go index 042e39d2fa..bc623b3149 100644 --- a/openstack/objectstorage/v1/containers/testing/fixtures.go +++ b/openstack/objectstorage/v1/containers/testing/fixtures.go @@ -94,6 +94,19 @@ func HandleListContainerNamesSuccessfully(t *testing.T) { }) } +// HandleListZeroContainerNames204 creates an HTTP handler at `/` on the test handler mux that +// responds with "204 No Content" when container names are requested. This happens on some, but not all, +// objectstorage instances. This case is peculiar in that the server sends no `content-type` header. +func HandleListZeroContainerNames204(t *testing.T) { + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "text/plain") + + w.WriteHeader(http.StatusNoContent) + }) +} + // HandleCreateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `Create` response. func HandleCreateContainerSuccessfully(t *testing.T) { diff --git a/openstack/objectstorage/v1/containers/testing/requests_test.go b/openstack/objectstorage/v1/containers/testing/requests_test.go index dcfd1de753..e684f5d395 100644 --- a/openstack/objectstorage/v1/containers/testing/requests_test.go +++ b/openstack/objectstorage/v1/containers/testing/requests_test.go @@ -79,6 +79,18 @@ func TestListAllContainerNames(t *testing.T) { th.CheckDeepEquals(t, ExpectedListNames, actual) } +func TestListZeroContainerNames(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListZeroContainerNames204(t) + + allPages, err := containers.List(fake.ServiceClient(), &containers.ListOpts{Full: false}).AllPages() + th.AssertNoErr(t, err) + actual, err := containers.ExtractNames(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, []string{}, actual) +} + func TestCreateContainer(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/openstack/objectstorage/v1/objects/results.go b/openstack/objectstorage/v1/objects/results.go index 75367d8349..0afc7e7bf9 100644 --- a/openstack/objectstorage/v1/objects/results.go +++ b/openstack/objectstorage/v1/objects/results.go @@ -70,6 +70,10 @@ type ObjectPage struct { // IsEmpty returns true if a ListResult contains no object names. func (r ObjectPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + names, err := ExtractNames(r) return len(names) == 0, err } diff --git a/openstack/objectstorage/v1/objects/testing/fixtures.go b/openstack/objectstorage/v1/objects/testing/fixtures.go index 21da11450c..0149d40e1e 100644 --- a/openstack/objectstorage/v1/objects/testing/fixtures.go +++ b/openstack/objectstorage/v1/objects/testing/fixtures.go @@ -162,6 +162,19 @@ func HandleListObjectNamesSuccessfully(t *testing.T) { }) } +// HandleListZeroObjectNames204 creates an HTTP handler at `/testContainer` on the test handler mux that +// responds with "204 No Content" when object names are requested. This happens on some, but not all, objectstorage +// instances. This case is peculiar in that the server sends no `content-type` header. +func HandleListZeroObjectNames204(t *testing.T) { + th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "text/plain") + + w.WriteHeader(http.StatusNoContent) + }) +} + // HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux // that responds with a `Create` response. A Content-Type of "text/plain" is expected. func HandleCreateTextObjectSuccessfully(t *testing.T, content string) { diff --git a/openstack/objectstorage/v1/objects/testing/requests_test.go b/openstack/objectstorage/v1/objects/testing/requests_test.go index 3b7156d0be..ff94ad0677 100644 --- a/openstack/objectstorage/v1/objects/testing/requests_test.go +++ b/openstack/objectstorage/v1/objects/testing/requests_test.go @@ -160,6 +160,29 @@ func TestListObjectNames(t *testing.T) { th.CheckEquals(t, count, 1) } +func TestListZeroObjectNames204(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListZeroObjectNames204(t) + + count := 0 + options := &objects.ListOpts{Full: false} + err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := objects.ExtractNames(page) + if err != nil { + t.Errorf("Failed to extract container names: %v", err) + return false, err + } + + th.CheckDeepEquals(t, []string{}, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 0, count) +} + func TestCreateObject(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/pagination/http.go b/pagination/http.go index df3503159a..7845cda13b 100644 --- a/pagination/http.go +++ b/pagination/http.go @@ -44,8 +44,9 @@ func PageResultFrom(resp *http.Response) (PageResult, error) { func PageResultFromParsed(resp *http.Response, body interface{}) PageResult { return PageResult{ Result: gophercloud.Result{ - Body: body, - Header: resp.Header, + Body: body, + StatusCode: resp.StatusCode, + Header: resp.Header, }, URL: *resp.Request.URL, } diff --git a/results.go b/results.go index 1b608103b7..b3ee9d5682 100644 --- a/results.go +++ b/results.go @@ -30,6 +30,11 @@ type Result struct { // this will be the deserialized JSON structure. Body interface{} + // StatusCode is the HTTP status code of the original response. Will be + // one of the OkCodes defined on the gophercloud.RequestOpts that was + // used in the request. + StatusCode int + // Header contains the HTTP header structure from the original response. Header http.Header From 184da063ffe86fd509411dd4c737dd9a292bc199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kubica?= Date: Wed, 21 Sep 2022 10:18:24 +0200 Subject: [PATCH 40/44] Add support for volume type for db/v1/instance --- openstack/db/v1/instances/requests.go | 13 ++++++++++++- openstack/db/v1/instances/results.go | 2 ++ openstack/db/v1/instances/testing/fixtures.go | 16 ++++++++++------ .../db/v1/instances/testing/requests_test.go | 6 ++++-- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/openstack/db/v1/instances/requests.go b/openstack/db/v1/instances/requests.go index f5243e70b1..73c7acc74e 100644 --- a/openstack/db/v1/instances/requests.go +++ b/openstack/db/v1/instances/requests.go @@ -53,6 +53,8 @@ type CreateOpts struct { // Specifies the volume size in gigabytes (GB). The value must be between 1 // and 300. Required. Size int + // Specifies the volume type. + VolumeType string // Name of the instance to create. The length of the name is limited to // 255 characters and any characters are permitted. Optional. Name string @@ -82,7 +84,6 @@ func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) { } instance := map[string]interface{}{ - "volume": map[string]int{"size": opts.Size}, "flavorRef": opts.FlavorRef, } @@ -123,6 +124,16 @@ func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) { instance["nics"] = networks } + volume := map[string]interface{}{ + "size": opts.Size, + } + + if opts.VolumeType != "" { + volume["type"] = opts.VolumeType + } + + instance["volume"] = volume + return map[string]interface{}{"instance": instance}, nil } diff --git a/openstack/db/v1/instances/results.go b/openstack/db/v1/instances/results.go index 3ac7b02480..5ec8db939d 100644 --- a/openstack/db/v1/instances/results.go +++ b/openstack/db/v1/instances/results.go @@ -14,6 +14,8 @@ import ( type Volume struct { // The size in GB of the volume Size int + // The type of the volume + Type string Used float64 } diff --git a/openstack/db/v1/instances/testing/fixtures.go b/openstack/db/v1/instances/testing/fixtures.go index c7e019e271..7a7bacc38b 100644 --- a/openstack/db/v1/instances/testing/fixtures.go +++ b/openstack/db/v1/instances/testing/fixtures.go @@ -48,7 +48,8 @@ var instance = ` "status": "BUILD", "updated": "` + timestamp + `", "volume": { - "size": 2 + "size": 2, + "type": "ssd" } } ` @@ -86,6 +87,7 @@ var instanceGet = ` "updated": "` + timestamp + `", "volume": { "size": 1, + "type": "ssd", "used": 0.12 }, "addresses": [ @@ -128,7 +130,8 @@ var createReq = ` } ], "volume": { - "size": 2 + "size": 2, + "type": "ssd" } } } @@ -166,7 +169,8 @@ var instanceWithFault = ` "status": "BUILD", "updated": "` + timestamp + `", "volume": { - "size": 2 + "size": 2, + "type": "ssd" }, "fault": { "message": "some error message", @@ -219,7 +223,7 @@ var expectedInstance = instances.Instance{ }, Name: "json_rack_instance", Status: "BUILD", - Volume: instances.Volume{Size: 2}, + Volume: instances.Volume{Size: 2, Type: "ssd"}, Datastore: datastores.DatastorePartial{ Type: "mysql", Version: "5.6", @@ -242,7 +246,7 @@ var expectedGetInstance = instances.Instance{ }, Name: "test", Status: "ACTIVE", - Volume: instances.Volume{Size: 1, Used: 0.12}, + Volume: instances.Volume{Size: 1, Type: "ssd", Used: 0.12}, Datastore: datastores.DatastorePartial{ Type: "mysql", Version: "5.6", @@ -270,7 +274,7 @@ var expectedInstanceWithFault = instances.Instance{ }, Name: "json_rack_instance", Status: "BUILD", - Volume: instances.Volume{Size: 2}, + Volume: instances.Volume{Size: 2, Type: "ssd"}, Datastore: datastores.DatastorePartial{ Type: "mysql", Version: "5.6", diff --git a/openstack/db/v1/instances/testing/requests_test.go b/openstack/db/v1/instances/testing/requests_test.go index 815793cbb6..a1575a4bb1 100644 --- a/openstack/db/v1/instances/testing/requests_test.go +++ b/openstack/db/v1/instances/testing/requests_test.go @@ -32,7 +32,8 @@ func TestCreate(t *testing.T) { }, }, }, - Size: 2, + Size: 2, + VolumeType: "ssd", } instance, err := instances.Create(fake.ServiceClient(), opts).Extract() @@ -62,7 +63,8 @@ func TestCreateWithFault(t *testing.T) { }, }, }, - Size: 2, + Size: 2, + VolumeType: "ssd", } instance, err := instances.Create(fake.ServiceClient(), opts).Extract() From 1a96f25b641fe73d0362d0e457123711b713696c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kubica?= Date: Wed, 21 Sep 2022 22:35:34 +0200 Subject: [PATCH 41/44] Remove the non-existent Type field from the response --- openstack/db/v1/instances/results.go | 2 -- openstack/db/v1/instances/testing/fixtures.go | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/openstack/db/v1/instances/results.go b/openstack/db/v1/instances/results.go index 5ec8db939d..3ac7b02480 100644 --- a/openstack/db/v1/instances/results.go +++ b/openstack/db/v1/instances/results.go @@ -14,8 +14,6 @@ import ( type Volume struct { // The size in GB of the volume Size int - // The type of the volume - Type string Used float64 } diff --git a/openstack/db/v1/instances/testing/fixtures.go b/openstack/db/v1/instances/testing/fixtures.go index 7a7bacc38b..233143e3d8 100644 --- a/openstack/db/v1/instances/testing/fixtures.go +++ b/openstack/db/v1/instances/testing/fixtures.go @@ -87,7 +87,6 @@ var instanceGet = ` "updated": "` + timestamp + `", "volume": { "size": 1, - "type": "ssd", "used": 0.12 }, "addresses": [ @@ -223,7 +222,7 @@ var expectedInstance = instances.Instance{ }, Name: "json_rack_instance", Status: "BUILD", - Volume: instances.Volume{Size: 2, Type: "ssd"}, + Volume: instances.Volume{Size: 2}, Datastore: datastores.DatastorePartial{ Type: "mysql", Version: "5.6", @@ -246,7 +245,7 @@ var expectedGetInstance = instances.Instance{ }, Name: "test", Status: "ACTIVE", - Volume: instances.Volume{Size: 1, Type: "ssd", Used: 0.12}, + Volume: instances.Volume{Size: 1, Used: 0.12}, Datastore: datastores.DatastorePartial{ Type: "mysql", Version: "5.6", @@ -274,7 +273,7 @@ var expectedInstanceWithFault = instances.Instance{ }, Name: "json_rack_instance", Status: "BUILD", - Volume: instances.Volume{Size: 2, Type: "ssd"}, + Volume: instances.Volume{Size: 2}, Datastore: datastores.DatastorePartial{ Type: "mysql", Version: "5.6", From 13195b74322da44b558c5af0fa769ae5b6f3750b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kubica?= Date: Wed, 21 Sep 2022 23:01:08 +0200 Subject: [PATCH 42/44] Remove the non-existent Type field from the response - fixtures --- openstack/db/v1/instances/testing/fixtures.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openstack/db/v1/instances/testing/fixtures.go b/openstack/db/v1/instances/testing/fixtures.go index 233143e3d8..782c048dc3 100644 --- a/openstack/db/v1/instances/testing/fixtures.go +++ b/openstack/db/v1/instances/testing/fixtures.go @@ -48,8 +48,7 @@ var instance = ` "status": "BUILD", "updated": "` + timestamp + `", "volume": { - "size": 2, - "type": "ssd" + "size": 2 } } ` @@ -168,8 +167,7 @@ var instanceWithFault = ` "status": "BUILD", "updated": "` + timestamp + `", "volume": { - "size": 2, - "type": "ssd" + "size": 2 }, "fault": { "message": "some error message", From b0961d48fa26a34aa512510824eacf4f7c24147e Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Wed, 23 Nov 2022 16:36:38 +0100 Subject: [PATCH 43/44] objectstorage testing: Fix order of assertions Fix confusing error messages on failing tests. The assertions all required "expected" before "actual" in their arguments. --- .../v1/containers/testing/requests_test.go | 4 ++-- .../v1/objects/testing/requests_test.go | 18 +++++++++--------- .../v1/swauth/testing/requests_test.go | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openstack/objectstorage/v1/containers/testing/requests_test.go b/openstack/objectstorage/v1/containers/testing/requests_test.go index dcfd1de753..213ab6a273 100644 --- a/openstack/objectstorage/v1/containers/testing/requests_test.go +++ b/openstack/objectstorage/v1/containers/testing/requests_test.go @@ -30,7 +30,7 @@ func TestListContainerInfo(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAllContainerInfo(t *testing.T) { @@ -64,7 +64,7 @@ func TestListContainerNames(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAllContainerNames(t *testing.T) { diff --git a/openstack/objectstorage/v1/objects/testing/requests_test.go b/openstack/objectstorage/v1/objects/testing/requests_test.go index 3b7156d0be..3a58f7833f 100644 --- a/openstack/objectstorage/v1/objects/testing/requests_test.go +++ b/openstack/objectstorage/v1/objects/testing/requests_test.go @@ -75,7 +75,7 @@ func TestDownloadWithLastModified(t *testing.T) { response2 := objects.Download(fake.ServiceClient(), "testContainer", "testObject", options2) content, err2 := response2.ExtractContent() th.AssertNoErr(t, err2) - th.AssertEquals(t, len(content), 0) + th.AssertEquals(t, 0, len(content)) } func TestListObjectInfo(t *testing.T) { @@ -95,7 +95,7 @@ func TestListObjectInfo(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListObjectSubdir(t *testing.T) { @@ -115,7 +115,7 @@ func TestListObjectSubdir(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListObjectNames(t *testing.T) { @@ -139,7 +139,7 @@ func TestListObjectNames(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) // Check with delimiter. count = 0 @@ -157,7 +157,7 @@ func TestListObjectNames(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestCreateObject(t *testing.T) { @@ -290,7 +290,7 @@ func TestGetObject(t *testing.T) { } actualHeaders, err := objects.Get(fake.ServiceClient(), "testContainer", "testObject", getOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, actualHeaders.StaticLargeObject, true) + th.AssertEquals(t, true, actualHeaders.StaticLargeObject) } func TestETag(t *testing.T) { @@ -303,7 +303,7 @@ func TestETag(t *testing.T) { _, headers, _, err := createOpts.ToObjectCreateParams() th.AssertNoErr(t, err) _, ok := headers["ETag"] - th.AssertEquals(t, ok, false) + th.AssertEquals(t, false, ok) hash := md5.New() io.WriteString(hash, content) @@ -316,7 +316,7 @@ func TestETag(t *testing.T) { _, headers, _, err = createOpts.ToObjectCreateParams() th.AssertNoErr(t, err) - th.AssertEquals(t, headers["ETag"], localChecksum) + th.AssertEquals(t, localChecksum, headers["ETag"]) } func TestObjectCreateParamsWithoutSeek(t *testing.T) { @@ -329,7 +329,7 @@ func TestObjectCreateParamsWithoutSeek(t *testing.T) { th.AssertNoErr(t, err) _, ok := reader.(io.ReadSeeker) - th.AssertEquals(t, ok, true) + th.AssertEquals(t, true, ok) c, err := ioutil.ReadAll(reader) th.AssertNoErr(t, err) diff --git a/openstack/objectstorage/v1/swauth/testing/requests_test.go b/openstack/objectstorage/v1/swauth/testing/requests_test.go index 46571f6117..0850aeff45 100644 --- a/openstack/objectstorage/v1/swauth/testing/requests_test.go +++ b/openstack/objectstorage/v1/swauth/testing/requests_test.go @@ -23,7 +23,7 @@ func TestAuth(t *testing.T) { swiftClient, err := swauth.NewObjectStorageV1(providerClient, authOpts) th.AssertNoErr(t, err) - th.AssertEquals(t, swiftClient.TokenID, AuthResult.Token) + th.AssertEquals(t, AuthResult.Token, swiftClient.TokenID) } func TestBadAuth(t *testing.T) { From 6266ecf266002a63a07b2469041a841acfeac8eb Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Mon, 12 Sep 2022 10:50:17 +0200 Subject: [PATCH 44/44] Update changelog preparing v1.1.0 The changelog for v1.1.0 is the curated output of: ``` gh pr list \ --state merged \ --search 'milestone:v1.1.0' \ --json number,title \ --template \ '{{range .}}* {{ printf "[GH-%v](https://github.com/gophercloud/gophercloud/pull/%v)" .number .number }} {{ .title }} {{end}}' ``` --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8137c10d..2cef7d88e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## v1.1.0 (2022-11-24) + +* [GH-2513](https://github.com/gophercloud/gophercloud/pull/2513) objectstorage: Do not parse NoContent responses +* [GH-2503](https://github.com/gophercloud/gophercloud/pull/2503) Bump golang.org/x/crypto +* [GH-2501](https://github.com/gophercloud/gophercloud/pull/2501) Staskraev/l3 agent scheduler +* [GH-2496](https://github.com/gophercloud/gophercloud/pull/2496) Manila: add Get for share-access-rules API +* [GH-2491](https://github.com/gophercloud/gophercloud/pull/2491) Add VipQosPolicyID to loadbalancer Create and Update +* [GH-2488](https://github.com/gophercloud/gophercloud/pull/2488) Add Persistance for octavia pools.UpdateOpts +* [GH-2487](https://github.com/gophercloud/gophercloud/pull/2487) Add Prometheus protocol for octavia listeners +* [GH-2482](https://github.com/gophercloud/gophercloud/pull/2482) Add createdAt, updatedAt and provisionUpdatedAt fields in Baremetal V1 nodes +* [GH-2479](https://github.com/gophercloud/gophercloud/pull/2479) Add service_types support for neutron subnet +* [GH-2477](https://github.com/gophercloud/gophercloud/pull/2477) Port CreatedAt and UpdatedAt: add back JSON tags +* [GH-2475](https://github.com/gophercloud/gophercloud/pull/2475) Support old time format for port CreatedAt and UpdatedAt +* [GH-2474](https://github.com/gophercloud/gophercloud/pull/2474) Implementing re-image volumeaction +* [GH-2470](https://github.com/gophercloud/gophercloud/pull/2470) keystone: add v3 limits GetEnforcementModel operation +* [GH-2468](https://github.com/gophercloud/gophercloud/pull/2468) keystone: add v3 OS-FEDERATION extension List Mappings +* [GH-2458](https://github.com/gophercloud/gophercloud/pull/2458) Fix typo in blockstorage/v3/attachments docs +* [GH-2456](https://github.com/gophercloud/gophercloud/pull/2456) Add support for Update for flavors +* [GH-2453](https://github.com/gophercloud/gophercloud/pull/2453) Add description to flavor +* [GH-2417](https://github.com/gophercloud/gophercloud/pull/2417) Neutron v2: ScheduleBGPSpeakerOpts, RemoveBGPSpeaker, Lis… + ## 1.0.0 (2022-08-29) UPGRADE NOTES + PROMISE OF COMPATIBILITY