diff --git a/.zuul.yaml b/.zuul.yaml index 5da8982915..8cba23cb0b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -13,7 +13,7 @@ Run gophercloud acceptance test on master branch run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml timeout: 18000 # 5 hours - nodeset: ubuntu-bionic + nodeset: ubuntu-focal - job: name: gophercloud-acceptance-test-ironic @@ -21,7 +21,7 @@ description: | Run gophercloud ironic acceptance test on master branch run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml - nodeset: ubuntu-bionic + nodeset: ubuntu-focal - job: name: gophercloud-acceptance-test-ussuri diff --git a/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml b/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml index b6fcfc32c6..901c29fc95 100644 --- a/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml +++ b/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml @@ -9,7 +9,7 @@ - 'ironic' - role: install-devstack environment: - OVERRIDE_ENABLED_SERVICES: 'g-api,g-reg,q-agt,q-dhcp,q-l3,q-svc,key,mysql,rabbit,ir-api,ir-cond,s-account,s-container,s-object,s-proxy,tempest' + OVERRIDE_ENABLED_SERVICES: 'g-api,g-reg,q-agt,q-dhcp,q-l3,q-svc,key,mysql,rabbit,ir-api,ir-cond,s-account,s-container,s-object,s-proxy' PROJECTS: 'openstack/ironic-python-agent-builder openstack/ironic' tasks: - name: Run ironic acceptance tests with gophercloud diff --git a/CHANGELOG.md b/CHANGELOG.md index ea4ca7539e..e5c9c4e169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,44 @@ -## 0.18.0 (Unreleased) +## 0.19.0 (Unreleased) + +## 0.18.0 (June 11, 2021) + +NOTES / BREAKING CHANGES + +* As of [GH-2160](https://github.com/gophercloud/gophercloud/pull/2160), Gophercloud no longer URL encodes Object Storage containers and object names. You can still encode them yourself before passing the names to the Object Storage functions. + +* `baremetal/v1/nodes.ListBIOSSettings` now takes three parameters. The third, new, parameter is `ListBIOSSettingsOptsBuilder` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) + +BUG FIXES + +* Fixed expected OK codes to use default codes [GH-2173](https://github.com/gophercloud/gophercloud/pull/2173) +* Fixed inablity to create sub-containers (objects with `/` in their name) [GH-2160](https://github.com/gophercloud/gophercloud/pull/2160) + +IMPROVEMENTS + +* Added `orchestration/v1/stacks.ListOpts.ShowHidden` [GH-2104](https://github.com/gophercloud/gophercloud/pull/2104) +* Added `loadbalancer/v2/listeners.ProtocolSCTP` [GH-2149](https://github.com/gophercloud/gophercloud/pull/2149) +* Added `loadbalancer/v2/listeners.CreateOpts.TLSVersions` [GH-2150](https://github.com/gophercloud/gophercloud/pull/2150) +* Added `loadbalancer/v2/listeners.UpdateOpts.TLSVersions` [GH-2150](https://github.com/gophercloud/gophercloud/pull/2150) +* Added `baremetal/v1/nodes.CreateOpts.NetworkData` [GH-2154](https://github.com/gophercloud/gophercloud/pull/2154) +* Added `baremetal/v1/nodes.Node.NetworkData` [GH-2154](https://github.com/gophercloud/gophercloud/pull/2154) +* Added `loadbalancer/v2/pools.ProtocolPROXYV2` [GH-2158](https://github.com/gophercloud/gophercloud/pull/2158) +* Added `loadbalancer/v2/pools.ProtocolSCTP` [GH-2158](https://github.com/gophercloud/gophercloud/pull/2158) +* Added `placement/v1/resourceproviders.GetAllocations` [GH-2162](https://github.com/gophercloud/gophercloud/pull/2162) +* Added `baremetal/v1/nodes.CreateOpts.BIOSInterface` [GH-2164](https://github.com/gophercloud/gophercloud/pull/2164) +* Added `baremetal/v1/nodes.Node.BIOSInterface` [GH-2164](https://github.com/gophercloud/gophercloud/pull/2164) +* Added `baremetal/v1/nodes.NodeValidation.BIOS` [GH-2164](https://github.com/gophercloud/gophercloud/pull/2164) +* Added `baremetal/v1/nodes.ListBIOSSettings` [GH-2171](https://github.com/gophercloud/gophercloud/pull/2171) +* Added `baremetal/v1/nodes.GetBIOSSetting` [GH-2171](https://github.com/gophercloud/gophercloud/pull/2171) +* Added `baremetal/v1/nodes.ListBIOSSettingsOpts` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.AttributeType` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.AllowableValues` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.LowerBound` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.UpperBound` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.MinLength` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.MaxLength` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.ReadOnly` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.ResetRequired` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.Unique` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) ## 0.17.0 (April 9, 2021) diff --git a/acceptance/openstack/blockstorage/apiversions_test.go b/acceptance/openstack/blockstorage/apiversions_test.go index 1adf5c3e8b..5c94d55928 100644 --- a/acceptance/openstack/blockstorage/apiversions_test.go +++ b/acceptance/openstack/blockstorage/apiversions_test.go @@ -11,7 +11,7 @@ import ( ) func TestAPIVersionsList(t *testing.T) { - client, err := clients.NewBlockStorageV2Client() + client, err := clients.NewBlockStorageV3Client() if err != nil { t.Fatalf("Unable to create a blockstorage client: %v", err) } @@ -32,7 +32,7 @@ func TestAPIVersionsList(t *testing.T) { } func TestAPIVersionsGet(t *testing.T) { - client, err := clients.NewBlockStorageV2Client() + client, err := clients.NewBlockStorageV3Client() if err != nil { t.Fatalf("Unable to create a blockstorage client: %v", err) } diff --git a/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go b/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go index b13cb0ec6b..5b4c35e2d3 100644 --- a/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go +++ b/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go @@ -18,7 +18,7 @@ func TestSchedulerStatsList(t *testing.T) { clients.SkipRelease(t, "stable/ocata") clients.SkipRelease(t, "stable/pike") - blockClient, err := clients.NewBlockStorageV2Client() + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) listOpts := schedulerstats.ListOpts{ diff --git a/acceptance/openstack/blockstorage/extensions/volumeactions_test.go b/acceptance/openstack/blockstorage/extensions/volumeactions_test.go index 9844410f2f..e5cc069ebc 100644 --- a/acceptance/openstack/blockstorage/extensions/volumeactions_test.go +++ b/acceptance/openstack/blockstorage/extensions/volumeactions_test.go @@ -15,7 +15,9 @@ import ( ) func TestVolumeActionsUploadImageDestroy(t *testing.T) { - blockClient, err := clients.NewBlockStorageV2Client() + t.Skip("Currently failing in OpenLab") + + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) computeClient, err := clients.NewComputeV2Client() @@ -35,7 +37,7 @@ func TestVolumeActionsUploadImageDestroy(t *testing.T) { } func TestVolumeActionsAttachCreateDestroy(t *testing.T) { - blockClient, err := clients.NewBlockStorageV2Client() + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) computeClient, err := clients.NewComputeV2Client() @@ -59,7 +61,7 @@ func TestVolumeActionsAttachCreateDestroy(t *testing.T) { } func TestVolumeActionsReserveUnreserve(t *testing.T) { - client, err := clients.NewBlockStorageV2Client() + client, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, client) @@ -72,7 +74,7 @@ func TestVolumeActionsReserveUnreserve(t *testing.T) { } func TestVolumeActionsExtendSize(t *testing.T) { - blockClient, err := clients.NewBlockStorageV2Client() + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, blockClient) @@ -91,7 +93,7 @@ func TestVolumeActionsExtendSize(t *testing.T) { } func TestVolumeActionsImageMetadata(t *testing.T) { - blockClient, err := clients.NewBlockStorageV2Client() + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, blockClient) @@ -103,7 +105,7 @@ func TestVolumeActionsImageMetadata(t *testing.T) { } func TestVolumeActionsSetBootable(t *testing.T) { - blockClient, err := clients.NewBlockStorageV2Client() + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, blockClient) diff --git a/acceptance/openstack/blockstorage/v3/volumeattachments_test.go b/acceptance/openstack/blockstorage/v3/volumeattachments_test.go index 0ec40ca09c..26921a0525 100644 --- a/acceptance/openstack/blockstorage/v3/volumeattachments_test.go +++ b/acceptance/openstack/blockstorage/v3/volumeattachments_test.go @@ -12,6 +12,8 @@ import ( ) func TestVolumeAttachments(t *testing.T) { + t.Skip("Currently failing in OpenLab") + clients.SkipRelease(t, "stable/mitaka") clients.SkipRelease(t, "stable/newton") clients.SkipRelease(t, "stable/ocata") diff --git a/acceptance/openstack/compute/v2/bootfromvolume_test.go b/acceptance/openstack/compute/v2/bootfromvolume_test.go index 9128747810..f98c561425 100644 --- a/acceptance/openstack/compute/v2/bootfromvolume_test.go +++ b/acceptance/openstack/compute/v2/bootfromvolume_test.go @@ -88,7 +88,7 @@ func TestBootFromExistingVolume(t *testing.T) { computeClient, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - blockStorageClient, err := clients.NewBlockStorageV2Client() + blockStorageClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolumeFromImage(t, blockStorageClient) @@ -221,7 +221,7 @@ func TestAttachExistingVolume(t *testing.T) { computeClient, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - blockStorageClient, err := clients.NewBlockStorageV2Client() + blockStorageClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) choices, err := clients.AcceptanceTestChoicesFromEnv() diff --git a/acceptance/openstack/compute/v2/volumeattach_test.go b/acceptance/openstack/compute/v2/volumeattach_test.go index 022df830ca..e8d15786d1 100644 --- a/acceptance/openstack/compute/v2/volumeattach_test.go +++ b/acceptance/openstack/compute/v2/volumeattach_test.go @@ -17,7 +17,7 @@ func TestVolumeAttachAttachment(t *testing.T) { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - blockClient, err := clients.NewBlockStorageV2Client() + blockClient, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) server, err := CreateServer(t, client) diff --git a/acceptance/openstack/loadbalancer/v2/loadbalancer.go b/acceptance/openstack/loadbalancer/v2/loadbalancer.go index 944a2cdb9e..244d8d90b9 100644 --- a/acceptance/openstack/loadbalancer/v2/loadbalancer.go +++ b/acceptance/openstack/loadbalancer/v2/loadbalancer.go @@ -67,6 +67,9 @@ func CreateListenerHTTP(t *testing.T, client *gophercloud.ServiceClient, lb *loa "X-Forwarded-For": "true", } + tlsVersions := []listeners.TLSVersion{"TLSv1.2", "TLSv1.3"} + tlsVersionsExp := []string{"TLSv1.2", "TLSv1.3"} + createOpts := listeners.CreateOpts{ Name: listenerName, Description: listenerDescription, @@ -74,6 +77,7 @@ func CreateListenerHTTP(t *testing.T, client *gophercloud.ServiceClient, lb *loa InsertHeaders: headers, Protocol: listeners.ProtocolHTTP, ProtocolPort: listenerPort, + TLSVersions: tlsVersions, } listener, err := listeners.Create(client, createOpts).Extract() @@ -93,6 +97,7 @@ func CreateListenerHTTP(t *testing.T, client *gophercloud.ServiceClient, lb *loa th.AssertEquals(t, listener.Protocol, string(listeners.ProtocolHTTP)) th.AssertEquals(t, listener.ProtocolPort, listenerPort) th.AssertDeepEquals(t, listener.InsertHeaders, headers) + th.AssertDeepEquals(t, listener.TLSVersions, tlsVersionsExp) return listener, nil } diff --git a/acceptance/openstack/objectstorage/v1/containers_test.go b/acceptance/openstack/objectstorage/v1/containers_test.go index c6f1b6a67c..1152fd3dc8 100644 --- a/acceptance/openstack/objectstorage/v1/containers_test.go +++ b/acceptance/openstack/objectstorage/v1/containers_test.go @@ -173,7 +173,7 @@ func TestBulkDeleteContainers(t *testing.T) { // Create a slice of random container names. cNames := make([]string, numContainers) for i := 0; i < numContainers; i++ { - cNames[i] = tools.RandomString("test&happy?-", 8) + cNames[i] = tools.RandomString("gophercloud-test-container-", 8) } // Create numContainers containers. diff --git a/acceptance/openstack/objectstorage/v1/objects_test.go b/acceptance/openstack/objectstorage/v1/objects_test.go index f3f77d36c1..1b61324665 100644 --- a/acceptance/openstack/objectstorage/v1/objects_test.go +++ b/acceptance/openstack/objectstorage/v1/objects_test.go @@ -290,22 +290,22 @@ func TestObjectsBulkDelete(t *testing.T) { } // Create a random subdirectory name. - cSubdir1 := tools.RandomString("don't worry & be happy?-", 8) - cSubdir2 := tools.RandomString("don't worry & be happy?-", 8) + cSubdir1 := tools.RandomString("test-subdir-", 8) + cSubdir2 := tools.RandomString("test-subdir-", 8) // Make a slice of length numObjects to hold the random object names. oNames1 := make([]string, numObjects) for i := 0; i < len(oNames1); i++ { - oNames1[i] = cSubdir1 + "/" + tools.RandomString("stranger?things-", 8) + oNames1[i] = cSubdir1 + "/" + tools.RandomString("test-object-", 8) } oNames2 := make([]string, numObjects) for i := 0; i < len(oNames2); i++ { - oNames2[i] = cSubdir2 + "/" + tools.RandomString("freddy's coming for you?-", 8) + oNames2[i] = cSubdir2 + "/" + tools.RandomString("test-object-", 8) } // Create a container to hold the test objects. - cName := tools.RandomString("test&happy?-", 8) + cName := tools.RandomString("test-container-", 8) _, err = containers.Create(client, cName, nil).Extract() th.AssertNoErr(t, err) @@ -327,12 +327,6 @@ func TestObjectsBulkDelete(t *testing.T) { th.AssertNoErr(t, res.Err) } - expectedResp := objects.BulkDeleteResponse{ - ResponseStatus: "200 OK", - Errors: [][]string{}, - NumberDeleted: numObjects * 2, - } - oContents2 := make([]*bytes.Buffer, numObjects) for i := 0; i < numObjects; i++ { oContents2[i] = bytes.NewBuffer([]byte(tools.RandomString("", 10))) @@ -344,6 +338,12 @@ func TestObjectsBulkDelete(t *testing.T) { } // Delete the objects after testing. + expectedResp := objects.BulkDeleteResponse{ + ResponseStatus: "200 OK", + Errors: [][]string{}, + NumberDeleted: numObjects * 2, + } + resp, err := objects.BulkDelete(client, cName, append(oNames1, oNames2...)).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, *resp, expectedResp) diff --git a/acceptance/openstack/placement/v1/resourceproviders_test.go b/acceptance/openstack/placement/v1/resourceproviders_test.go index 52d2137800..0092b10ef1 100644 --- a/acceptance/openstack/placement/v1/resourceproviders_test.go +++ b/acceptance/openstack/placement/v1/resourceproviders_test.go @@ -142,3 +142,33 @@ func TestResourceProviderTraits(t *testing.T) { tools.PrintResource(t, usage) } + +func TestResourceProviderAllocations(t *testing.T) { + clients.SkipRelease(t, "stable/mitaka") + clients.SkipRelease(t, "stable/newton") + clients.SkipRelease(t, "stable/ocata") + clients.SkipRelease(t, "stable/pike") + clients.SkipRelease(t, "stable/queens") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + // first create new resource provider + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create resource provider: %s", name) + + createOpts := resourceproviders.CreateOpts{ + Name: name, + } + + client.Microversion = "1.20" + resourceProvider, err := resourceproviders.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + + // now get the allocations for the newly created resource provider + usage, err := resourceproviders.GetAllocations(client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, usage) +} diff --git a/openstack/baremetal/v1/nodes/requests.go b/openstack/baremetal/v1/nodes/requests.go index 9648abe5c5..7c9befb161 100644 --- a/openstack/baremetal/v1/nodes/requests.go +++ b/openstack/baremetal/v1/nodes/requests.go @@ -185,6 +185,9 @@ type CreateOpts struct { // Requires microversion 1.47 or later. AutomatedClean *bool `json:"automated_clean,omitempty"` + // The BIOS interface for a Node, e.g. “redfish”. + BIOSInterface string `json:"bios_interface,omitempty"` + // The boot interface for a Node, e.g. “pxe”. BootInterface string `json:"boot_interface,omitempty"` @@ -247,6 +250,9 @@ type CreateOpts struct { // A string or UUID of the tenant who owns the baremetal node. Owner string `json:"owner,omitempty"` + + // Static network configuration to use during deployment and cleaning. + NetworkData map[string]interface{} `json:"network_data,omitempty"` } // ToNodeCreateMap assembles a request body based on the contents of a CreateOpts. @@ -633,3 +639,58 @@ func SetRAIDConfig(client *gophercloud.ServiceClient, id string, raidConfigOptsB _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// ListBIOSSettingsOptsBuilder allows extensions to add additional parameters to the +// ListBIOSSettings request. +type ListBIOSSettingsOptsBuilder interface { + ToListBIOSSettingsOptsQuery() (string, error) +} + +// ListBIOSSettingsOpts defines query options that can be passed to ListBIOSettings +type ListBIOSSettingsOpts struct { + // Provide additional information for the BIOS Settings + Detail bool `q:"detail"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` +} + +// ToListBIOSSettingsOptsQuery formats a ListBIOSSettingsOpts into a query string +func (opts ListBIOSSettingsOpts) ToListBIOSSettingsOptsQuery() (string, error) { + if opts.Detail == true && len(opts.Fields) > 0 { + return "", fmt.Errorf("cannot have both fields and detail options for BIOS settings") + } + + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Get the current BIOS Settings for the given Node. +// To use the opts requires microversion 1.74. +func ListBIOSSettings(client *gophercloud.ServiceClient, id string, opts ListBIOSSettingsOptsBuilder) (r ListBIOSSettingsResult) { + url := biosListSettingsURL(client, id) + if opts != nil { + + query, err := opts.ToListBIOSSettingsOptsQuery() + if err != nil { + r.Err = err + return + } + url += query + } + + resp, err := client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get one BIOS Setting for the given Node. +func GetBIOSSetting(client *gophercloud.ServiceClient, id string, setting string) (r GetBIOSSettingResult) { + resp, err := client.Get(biosGetSettingURL(client, id, setting), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/baremetal/v1/nodes/results.go b/openstack/baremetal/v1/nodes/results.go index 70dc9a0447..1d1e50ffc1 100644 --- a/openstack/baremetal/v1/nodes/results.go +++ b/openstack/baremetal/v1/nodes/results.go @@ -48,6 +48,23 @@ func ExtractNodesInto(r pagination.Page, v interface{}) error { return r.(NodePage).Result.ExtractIntoSlicePtr(v, "nodes") } +// Extract interprets a BIOSSettingsResult as an array of BIOSSetting structs, if possible. +func (r ListBIOSSettingsResult) Extract() ([]BIOSSetting, error) { + var s struct { + Settings []BIOSSetting `json:"bios"` + } + + err := r.ExtractInto(&s) + return s.Settings, err +} + +// Extract interprets a SingleBIOSSettingResult as a BIOSSetting struct, if possible. +func (r GetBIOSSettingResult) Extract() (*BIOSSetting, error) { + var s SingleBIOSSetting + err := r.ExtractInto(&s) + return &s.Setting, err +} + // Node represents a node in the OpenStack Bare Metal API. type Node struct { // Whether automated cleaning is enabled or disabled on this node. @@ -145,6 +162,9 @@ type Node struct { // For more details, see: https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html ResourceClass string `json:"resource_class"` + // BIOS interface for a Node, e.g. “redfish”. + BIOSInterface string `json:"bios_interface"` + // Boot interface for a Node, e.g. “pxe”. BootInterface string `json:"boot_interface"` @@ -192,6 +212,9 @@ type Node struct { // A string or UUID of the tenant who owns the baremetal node. Owner string `json:"owner"` + + // Static network configuration to use during deployment and cleaning. + NetworkData map[string]interface{} `json:"network_data"` } // NodePage abstracts the raw results of making a List() request against @@ -270,7 +293,7 @@ type BootDeviceResult struct { gophercloud.Result } -// BootDeviceResult is the response from a GetBootDevice operation. Call its Extract +// SetBootDeviceResult is the response from a SetBootDevice operation. Call its Extract // method to interpret it as a BootDeviceOpts struct. type SetBootDeviceResult struct { gophercloud.ErrResult @@ -288,6 +311,18 @@ type ChangePowerStateResult struct { gophercloud.ErrResult } +// ListBIOSSettingsResult is the response from a ListBIOSSettings operation. Call its Extract +// method to interpret it as an array of BIOSSetting structs. +type ListBIOSSettingsResult struct { + gophercloud.Result +} + +// GetBIOSSettingResult is the response from a GetBIOSSetting operation. Call its Extract +// method to interpret it as a BIOSSetting struct. +type GetBIOSSettingResult struct { + gophercloud.Result +} + // Each element in the response will contain a “result” variable, which will have a value of “true” or “false”, and // also potentially a reason. A value of nil indicates that the Node’s driver does not support that interface. type DriverValidation struct { @@ -298,6 +333,7 @@ type DriverValidation struct { // Ironic validates whether the Node’s driver has enough information to manage the Node. This polls each interface on // the driver, and returns the status of that interface as an DriverValidation struct. type NodeValidation struct { + BIOS DriverValidation `json:"bios"` Boot DriverValidation `json:"boot"` Console DriverValidation `json:"console"` Deploy DriverValidation `json:"deploy"` @@ -310,6 +346,51 @@ type NodeValidation struct { Storage DriverValidation `json:"storage"` } +// A particular BIOS setting for a node in the OpenStack Bare Metal API. +type BIOSSetting struct { + + // Identifier for the BIOS setting. + Name string `json:"name"` + + // Value of the BIOS setting. + Value string `json:"value"` + + // The following fields are returned in microversion 1.74 or later + // when using the `details` option + + // The type of setting - Enumeration, String, Integer, or Boolean. + AttributeType string `json:"attribute_type"` + + // The allowable value for an Enumeration type setting. + AllowableValues []string `json:"allowable_values"` + + // The lowest value for an Integer type setting. + LowerBound *int `json:"lower_bound"` + + // The highest value for an Integer type setting. + UpperBound *int `json:"upper_bound"` + + // Minimum length for a String type setting. + MinLength *int `json:"min_length"` + + // Maximum length for a String type setting. + MaxLength *int `json:"max_length"` + + // Whether or not this setting is read only. + ReadOnly *bool `json:"read_only"` + + // Whether or not a reset is required after changing this setting. + ResetRequired *bool `json:"reset_required"` + + // Whether or not this setting's value is unique to this node, e.g. + // a serial number. + Unique *bool `json:"unique"` +} + +type SingleBIOSSetting struct { + Setting BIOSSetting +} + // ChangeStateResult is the response from any state change operation. Call its ExtractErr // method to determine if the call succeeded or failed. type ChangeStateResult struct { diff --git a/openstack/baremetal/v1/nodes/testing/fixtures.go b/openstack/baremetal/v1/nodes/testing/fixtures.go index a849d05ee1..39499e7bcd 100644 --- a/openstack/baremetal/v1/nodes/testing/fixtures.go +++ b/openstack/baremetal/v1/nodes/testing/fixtures.go @@ -604,6 +604,119 @@ const NodeProvisionStateConfigDriveBody = ` } ` +const NodeBIOSSettingsBody = ` +{ + "bios": [ + { + "name": "Proc1L2Cache", + "value": "10x256 KB" + }, + { + "name": "Proc1NumCores", + "value": "10" + }, + { + "name": "ProcVirtualization", + "value": "Enabled" + } + ] +} +` + +const NodeDetailBIOSSettingsBody = ` +{ + "bios": [ + { + "created_at": "2021-05-11T21:33:44+00:00", + "updated_at": null, + "name": "Proc1L2Cache", + "value": "10x256 KB", + "attribute_type": "String", + "allowable_values": [], + "lower_bound": null, + "max_length": 16, + "min_length": 0, + "read_only": true, + "reset_required": null, + "unique": null, + "upper_bound": null, + "links": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d26115bf-1296-4ca8-8c86-6f310d8ec375/bios/Proc1L2Cache", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d26115bf-1296-4ca8-8c86-6f310d8ec375/bios/Proc1L2Cache", + "rel": "bookmark" + } + ] + }, + { + "created_at": "2021-05-11T21:33:44+00:00", + "updated_at": null, + "name": "Proc1NumCores", + "value": "10", + "attribute_type": "Integer", + "allowable_values": [], + "lower_bound": 0, + "max_length": null, + "min_length": null, + "read_only": true, + "reset_required": null, + "unique": null, + "upper_bound": 20, + "links": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d26115bf-1296-4ca8-8c86-6f310d8ec375/bios/Proc1NumCores", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d26115bf-1296-4ca8-8c86-6f310d8ec375/bios/Proc1NumCores", + "rel": "bookmark" + } + ] + }, + { + "created_at": "2021-05-11T21:33:44+00:00", + "updated_at": null, + "name": "ProcVirtualization", + "value": "Enabled", + "attribute_type": "Enumeration", + "allowable_values": [ + "Enabled", + "Disabled" + ], + "lower_bound": null, + "max_length": null, + "min_length": null, + "read_only": false, + "reset_required": null, + "unique": null, + "upper_bound": null, + "links": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d26115bf-1296-4ca8-8c86-6f310d8ec375/bios/ProcVirtualization", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d26115bf-1296-4ca8-8c86-6f310d8ec375/bios/ProcVirtualization", + "rel": "bookmark" + } + ] + } + ] +} +` + +const NodeSingleBIOSSettingBody = ` +{ + "Setting": { + "name": "ProcVirtualization", + "value": "Enabled" + } +} +` + var ( NodeFoo = nodes.Node{ UUID: "d2630783-6ec8-4836-b556-ab427c4b581e", @@ -638,6 +751,7 @@ var ( CleanStep: map[string]interface{}{}, DeployStep: map[string]interface{}{}, ResourceClass: "", + BIOSInterface: "no-bios", BootInterface: "pxe", ConsoleInterface: "no-console", DeployInterface: "iscsi", @@ -656,6 +770,10 @@ var ( } NodeFooValidation = nodes.NodeValidation{ + BIOS: nodes.DriverValidation{ + Result: false, + Reason: "Driver ipmi does not support bios (disabled or not implemented).", + }, Boot: nodes.DriverValidation{ Result: false, Reason: "Cannot validate image information for node a62b8495-52e2-407b-b3cb-62775d04c2b8 because one or more parameters are missing from its instance_info and insufficent information is present to boot from a remote volume. Missing are: ['ramdisk', 'kernel', 'image_source']", @@ -730,6 +848,7 @@ var ( CleanStep: map[string]interface{}{}, DeployStep: map[string]interface{}{}, ResourceClass: "", + BIOSInterface: "no-bios", BootInterface: "pxe", ConsoleInterface: "no-console", DeployInterface: "iscsi", @@ -773,6 +892,7 @@ var ( CleanStep: map[string]interface{}{}, DeployStep: map[string]interface{}{}, ResourceClass: "", + BIOSInterface: "no-bios", BootInterface: "pxe", ConsoleInterface: "no-console", DeployInterface: "iscsi", @@ -804,6 +924,75 @@ var ( }, }, } + + NodeBIOSSettings = []nodes.BIOSSetting{ + { + Name: "Proc1L2Cache", + Value: "10x256 KB", + }, + { + Name: "Proc1NumCores", + Value: "10", + }, + { + Name: "ProcVirtualization", + Value: "Enabled", + }, + } + + iTrue = true + iFalse = false + minLength = 0 + maxLength = 16 + lowerBound = 0 + upperBound = 20 + + NodeDetailBIOSSettings = []nodes.BIOSSetting{ + { + Name: "Proc1L2Cache", + Value: "10x256 KB", + AttributeType: "String", + AllowableValues: []string{}, + LowerBound: nil, + UpperBound: nil, + MinLength: &minLength, + MaxLength: &maxLength, + ReadOnly: &iTrue, + ResetRequired: nil, + Unique: nil, + }, + { + Name: "Proc1NumCores", + Value: "10", + AttributeType: "Integer", + AllowableValues: []string{}, + LowerBound: &lowerBound, + UpperBound: &upperBound, + MinLength: nil, + MaxLength: nil, + ReadOnly: &iTrue, + ResetRequired: nil, + Unique: nil, + }, + { + Name: "ProcVirtualization", + Value: "Enabled", + AttributeType: "Enumeration", + AllowableValues: []string{"Enabled", "Disabled"}, + LowerBound: nil, + UpperBound: nil, + MinLength: nil, + MaxLength: nil, + ReadOnly: &iFalse, + ResetRequired: nil, + Unique: nil, + }, + } + + NodeSingleBIOSSetting = nodes.BIOSSetting{ + Name: "ProcVirtualization", + Value: "Enabled", + } ) // HandleNodeListSuccessfully sets up the test server to respond to a server List request. @@ -1061,3 +1250,33 @@ func HandleSetRAIDConfigMaxSize(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) } + +func HandleListBIOSSettingsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/bios", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, NodeBIOSSettingsBody) + }) +} + +func HandleListDetailBIOSSettingsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/bios", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, NodeDetailBIOSSettingsBody) + }) +} + +func HandleGetBIOSSettingSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/bios/ProcVirtualization", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, NodeSingleBIOSSettingBody) + }) +} diff --git a/openstack/baremetal/v1/nodes/testing/requests_test.go b/openstack/baremetal/v1/nodes/testing/requests_test.go index 33f5817284..46e02eb496 100644 --- a/openstack/baremetal/v1/nodes/testing/requests_test.go +++ b/openstack/baremetal/v1/nodes/testing/requests_test.go @@ -545,3 +545,51 @@ func TestToRAIDConfigMap(t *testing.T) { }) } } + +func TestListBIOSSettings(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListBIOSSettingsSuccessfully(t) + + c := client.ServiceClient() + actual, err := nodes.ListBIOSSettings(c, "1234asdf", nil).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, NodeBIOSSettings, actual) +} + +func TestListDetailBIOSSettings(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListDetailBIOSSettingsSuccessfully(t) + + opts := nodes.ListBIOSSettingsOpts{ + Detail: true, + } + + c := client.ServiceClient() + actual, err := nodes.ListBIOSSettings(c, "1234asdf", opts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, NodeDetailBIOSSettings, actual) +} + +func TestGetBIOSSetting(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetBIOSSettingSuccessfully(t) + + c := client.ServiceClient() + actual, err := nodes.GetBIOSSetting(c, "1234asdf", "ProcVirtualization").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, NodeSingleBIOSSetting, *actual) +} + +func TestListBIOSSettingsOpts(t *testing.T) { + // Detail cannot take Fields + opts := nodes.ListBIOSSettingsOpts{ + Detail: true, + Fields: []string{"name", "value"}, + } + + _, err := opts.ToListBIOSSettingsOptsQuery() + th.AssertEquals(t, err.Error(), "cannot have both fields and detail options for BIOS settings") +} diff --git a/openstack/baremetal/v1/nodes/urls.go b/openstack/baremetal/v1/nodes/urls.go index c7ef550365..44ce58f336 100644 --- a/openstack/baremetal/v1/nodes/urls.go +++ b/openstack/baremetal/v1/nodes/urls.go @@ -57,3 +57,11 @@ func provisionStateURL(client *gophercloud.ServiceClient, id string) string { func raidConfigURL(client *gophercloud.ServiceClient, id string) string { return statesResourceURL(client, id, "raid") } + +func biosListSettingsURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "bios") +} + +func biosGetSettingURL(client *gophercloud.ServiceClient, id string, setting string) string { + return client.ServiceURL("nodes", id, "bios", setting) +} diff --git a/openstack/loadbalancer/v2/listeners/requests.go b/openstack/loadbalancer/v2/listeners/requests.go index 07fa26848d..c15d65dab0 100644 --- a/openstack/loadbalancer/v2/listeners/requests.go +++ b/openstack/loadbalancer/v2/listeners/requests.go @@ -12,14 +12,27 @@ type Protocol string // Supported attributes for create/update operations. const ( - ProtocolTCP Protocol = "TCP" - ProtocolUDP Protocol = "UDP" - ProtocolPROXY Protocol = "PROXY" - ProtocolHTTP Protocol = "HTTP" - ProtocolHTTPS Protocol = "HTTPS" + ProtocolTCP Protocol = "TCP" + ProtocolUDP Protocol = "UDP" + ProtocolPROXY Protocol = "PROXY" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" + // Protocol SCTP requires octavia microversion 2.23 + ProtocolSCTP Protocol = "SCTP" ProtocolTerminatedHTTPS Protocol = "TERMINATED_HTTPS" ) +// Type TLSVersion represents a tls version +type TLSVersion string + +const ( + TLSVersionSSLv3 TLSVersion = "SSLv3" + TLSVersionTLSv1 TLSVersion = "TLSv1" + TLSVersionTLSv1_1 TLSVersion = "TLSv1.1" + TLSVersionTLSv1_2 TLSVersion = "TLSv1.2" + TLSVersionTLSv1_3 TLSVersion = "TLSv1.3" +) + // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { @@ -88,7 +101,7 @@ type CreateOpts struct { // The load balancer on which to provision this listener. LoadbalancerID string `json:"loadbalancer_id,omitempty"` - // The protocol - can either be TCP, HTTP, HTTPS or TERMINATED_HTTPS. + // The protocol - can either be TCP, SCTP, HTTP, HTTPS or TERMINATED_HTTPS. Protocol Protocol `json:"protocol" required:"true"` // The port on which to listen for client traffic. @@ -151,6 +164,9 @@ type CreateOpts struct { // A list of IPv4, IPv6 or mix of both CIDRs AllowedCIDRs []string `json:"allowed_cidrs,omitempty"` + + // A list of TLS protocol versions. Available from microversion 2.17 + TLSVersions []TLSVersion `json:"tls_versions,omitempty"` } // ToListenerCreateMap builds a request body from CreateOpts. @@ -230,6 +246,9 @@ type UpdateOpts struct { // A list of IPv4, IPv6 or mix of both CIDRs AllowedCIDRs *[]string `json:"allowed_cidrs,omitempty"` + + // A list of TLS protocol versions. Available from microversion 2.17 + TLSVersions *[]TLSVersion `json:"tls_versions,omitempty"` } // ToListenerUpdateMap builds a request body from UpdateOpts. diff --git a/openstack/loadbalancer/v2/listeners/results.go b/openstack/loadbalancer/v2/listeners/results.go index 3271c6ae05..be2a6dcfed 100644 --- a/openstack/loadbalancer/v2/listeners/results.go +++ b/openstack/loadbalancer/v2/listeners/results.go @@ -27,7 +27,7 @@ type Listener struct { // Human-readable description for the Listener. Description string `json:"description"` - // The protocol to loadbalance. A valid value is TCP, HTTP, or HTTPS. + // The protocol to loadbalance. A valid value is TCP, SCTP, HTTP, HTTPS or TERMINATED_HTTPS. Protocol string `json:"protocol"` // The port on which to listen to client traffic that is associated with the @@ -83,6 +83,9 @@ type Listener struct { // A list of IPv4, IPv6 or mix of both CIDRs AllowedCIDRs []string `json:"allowed_cidrs"` + + // A list of TLS protocol versions. Available from microversion 2.17 + TLSVersions []string `json:"tls_versions"` } type Stats struct { diff --git a/openstack/loadbalancer/v2/listeners/testing/fixtures.go b/openstack/loadbalancer/v2/listeners/testing/fixtures.go index b74f2638b9..3efdf0af31 100644 --- a/openstack/loadbalancer/v2/listeners/testing/fixtures.go +++ b/openstack/loadbalancer/v2/listeners/testing/fixtures.go @@ -29,7 +29,8 @@ const ListenersListBody = ` "allowed_cidrs": [ "192.0.2.0/24", "198.51.100.0/24" - ] + ], + "tls_versions": ["TLSv1.2", "TLSv1.3"] }, { "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", @@ -54,7 +55,8 @@ const ListenersListBody = ` "allowed_cidrs": [ "192.0.2.0/24", "198.51.100.0/24" - ] + ], + "tls_versions": ["TLSv1.2"] } ] } @@ -86,7 +88,8 @@ const SingleListenerBody = ` "allowed_cidrs": [ "192.0.2.0/24", "198.51.100.0/24" - ] + ], + "tls_versions": ["TLSv1.2"] } } ` @@ -114,7 +117,8 @@ const PostUpdateListenerBody = ` "insert_headers": { "X-Forwarded-For": "true", "X-Forwarded-Port": "false" - } + }, + "tls_versions": ["TLSv1.2", "TLSv1.3"] } } ` @@ -146,6 +150,7 @@ var ( DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, AllowedCIDRs: []string{"192.0.2.0/24", "198.51.100.0/24"}, + TLSVersions: []string{"TLSv1.2", "TLSv1.3"}, } ListenerDb = listeners.Listener{ ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", @@ -166,6 +171,7 @@ var ( TimeoutTCPInspect: 0, InsertHeaders: map[string]string{"X-Forwarded-For": "true"}, AllowedCIDRs: []string{"192.0.2.0/24", "198.51.100.0/24"}, + TLSVersions: []string{"TLSv1.2"}, } ListenerUpdated = listeners.Listener{ ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", @@ -188,6 +194,7 @@ var ( "X-Forwarded-For": "true", "X-Forwarded-Port": "false", }, + TLSVersions: []string{"TLSv1.2", "TLSv1.3"}, } ListenerStatsTree = listeners.Stats{ ActiveConnections: 0, @@ -239,7 +246,8 @@ func HandleListenerCreationSuccessfully(t *testing.T, response string) { "allowed_cidrs": [ "192.0.2.0/24", "198.51.100.0/24" - ] + ], + "tls_versions": ["TLSv1.2"] } }`) @@ -289,7 +297,8 @@ func HandleListenerUpdateSuccessfully(t *testing.T) { "insert_headers": { "X-Forwarded-For": "true", "X-Forwarded-Port": "false" - } + }, + "tls_versions": ["TLSv1.2", "TLSv1.3"] } }`) diff --git a/openstack/loadbalancer/v2/listeners/testing/requests_test.go b/openstack/loadbalancer/v2/listeners/testing/requests_test.go index 1cdb553ab1..21fc0a4bea 100644 --- a/openstack/loadbalancer/v2/listeners/testing/requests_test.go +++ b/openstack/loadbalancer/v2/listeners/testing/requests_test.go @@ -68,6 +68,7 @@ func TestCreateListener(t *testing.T) { ProtocolPort: 3306, InsertHeaders: map[string]string{"X-Forwarded-For": "true"}, AllowedCIDRs: []string{"192.0.2.0/24", "198.51.100.0/24"}, + TLSVersions: []listeners.TLSVersion{"TLSv1.2"}, }).Extract() th.AssertNoErr(t, err) @@ -134,6 +135,7 @@ func TestUpdateListener(t *testing.T) { "X-Forwarded-For": "true", "X-Forwarded-Port": "false", } + tlsVersions := []listeners.TLSVersion{"TLSv1.2", "TLSv1.3"} actual, err := listeners.Update(client, "4ec89087-d057-4e2c-911f-60a3b47ee304", listeners.UpdateOpts{ Name: &name, ConnLimit: &i1001, @@ -143,6 +145,7 @@ func TestUpdateListener(t *testing.T) { TimeoutMemberConnect: &i181000, TimeoutTCPInspect: &i181000, InsertHeaders: &insertHeaders, + TLSVersions: &tlsVersions, }).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) diff --git a/openstack/loadbalancer/v2/pools/requests.go b/openstack/loadbalancer/v2/pools/requests.go index bf93656b02..0379d0385c 100644 --- a/openstack/loadbalancer/v2/pools/requests.go +++ b/openstack/loadbalancer/v2/pools/requests.go @@ -71,6 +71,10 @@ const ( ProtocolPROXY Protocol = "PROXY" ProtocolHTTP Protocol = "HTTP" ProtocolHTTPS Protocol = "HTTPS" + // Protocol PROXYV2 requires octavia microversion 2.22 + ProtocolPROXYV2 Protocol = "PROXYV2" + // Protocol SCTP requires octavia microversion 2.23 + ProtocolSCTP Protocol = "SCTP" ) // CreateOptsBuilder allows extensions to add additional parameters to the @@ -88,7 +92,8 @@ type CreateOpts struct { LBMethod LBMethod `json:"lb_algorithm" required:"true"` // The protocol used by the pool members, you can use either - // ProtocolTCP, ProtocolUDP, ProtocolPROXY, ProtocolHTTP, or ProtocolHTTPS. + // ProtocolTCP, ProtocolUDP, ProtocolPROXY, ProtocolHTTP, ProtocolHTTPS, + // ProtocolSCTP or ProtocolPROXYV2. Protocol Protocol `json:"protocol" required:"true"` // The Loadbalancer on which the members of the pool will be associated with. diff --git a/openstack/networking/v2/extensions/networkipavailabilities/doc.go b/openstack/networking/v2/extensions/networkipavailabilities/doc.go index 9109473693..faadaa2227 100644 --- a/openstack/networking/v2/extensions/networkipavailabilities/doc.go +++ b/openstack/networking/v2/extensions/networkipavailabilities/doc.go @@ -9,7 +9,7 @@ Example of Listing NetworkIPAvailabilities panic(err) } - allAvailabilities, err := subnetpools.ExtractSubnetPools(allPages) + allAvailabilities, err := networkipavailabilities.ExtractNetworkIPAvailabilities(allPages) if err != nil { panic(err) } diff --git a/openstack/networking/v2/extensions/security/rules/results.go b/openstack/networking/v2/extensions/security/rules/results.go index 3bf5501d92..52ac3f7a75 100644 --- a/openstack/networking/v2/extensions/security/rules/results.go +++ b/openstack/networking/v2/extensions/security/rules/results.go @@ -17,7 +17,7 @@ type SecGroupRule struct { // instance. An egress rule is applied to traffic leaving the instance. Direction string - // Descripton of the rule + // Description of the rule Description string `json:"description"` // Must be IPv4 or IPv6, and addresses represented in CIDR must match the diff --git a/openstack/objectstorage/v1/containers/requests.go b/openstack/objectstorage/v1/containers/requests.go index 119e06997f..bea0d9575c 100644 --- a/openstack/objectstorage/v1/containers/requests.go +++ b/openstack/objectstorage/v1/containers/requests.go @@ -1,7 +1,6 @@ package containers import ( - "net/url" "strings" "github.com/gophercloud/gophercloud" @@ -108,7 +107,7 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB h[k] = v } } - resp, err := c.Request("PUT", createURL(c, url.QueryEscape(containerName)), &gophercloud.RequestOpts{ + resp, err := c.Request("PUT", createURL(c, containerName), &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{201, 202, 204}, }) @@ -123,7 +122,7 @@ func BulkDelete(c *gophercloud.ServiceClient, containers []string) (r BulkDelete // https://github.com/openstack/swift/blob/stable/train/swift/common/swob.py#L302 encodedContainers := make([]string, len(containers)) for i, v := range containers { - encodedContainers[i] = url.QueryEscape(v) + encodedContainers[i] = v } b := strings.NewReader(strings.Join(encodedContainers, "\n") + "\n") resp, err := c.Post(bulkDeleteURL(c), b, &r.Body, &gophercloud.RequestOpts{ @@ -139,7 +138,7 @@ func BulkDelete(c *gophercloud.ServiceClient, containers []string) (r BulkDelete // Delete is a function that deletes a container. func Delete(c *gophercloud.ServiceClient, containerName string) (r DeleteResult) { - resp, err := c.Delete(deleteURL(c, url.QueryEscape(containerName)), nil) + resp, err := c.Delete(deleteURL(c, containerName), nil) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -202,7 +201,7 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB h[k] = v } } - resp, err := c.Request("POST", updateURL(c, url.QueryEscape(containerName)), &gophercloud.RequestOpts{ + resp, err := c.Request("POST", updateURL(c, containerName), &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{201, 202, 204}, }) @@ -242,7 +241,7 @@ func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder h[k] = v } } - resp, err := c.Head(getURL(c, url.QueryEscape(containerName)), &gophercloud.RequestOpts{ + resp, err := c.Head(getURL(c, containerName), &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{200, 204}, }) diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go index 14e390541f..e032640d6f 100644 --- a/openstack/objectstorage/v1/containers/results.go +++ b/openstack/objectstorage/v1/containers/results.go @@ -72,7 +72,7 @@ func ExtractNames(page pagination.Page) ([]string, error) { names = append(names, container.Name) } return names, nil - case strings.HasPrefix(ct, "text/plain"): + case strings.HasPrefix(ct, "text/plain") || ct == "": names := make([]string, 0, 50) body := string(page.(ContainerPage).Body.([]uint8)) diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go index 7d6eb4123a..e820026a2e 100644 --- a/openstack/objectstorage/v1/objects/requests.go +++ b/openstack/objectstorage/v1/objects/requests.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "io/ioutil" - "net/url" "strings" "time" @@ -54,7 +53,7 @@ func (opts ListOpts) ToObjectListParams() (bool, string, error) { func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager { headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"} - url := listURL(c, url.QueryEscape(containerName)) + url := listURL(c, containerName) if opts != nil { full, query, err := opts.ToObjectListParams() if err != nil { @@ -119,7 +118,7 @@ func (opts DownloadOpts) ToObjectDownloadParams() (map[string]string, string, er // To extract just the content, pass the DownloadResult response to the // ExtractContent function. func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) (r DownloadResult) { - url := downloadURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName)) + url := downloadURL(c, containerName, objectName) h := make(map[string]string) if opts != nil { headers, query, err := opts.ToObjectDownloadParams() @@ -225,7 +224,7 @@ func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, str // checksum, the failed request will automatically be retried up to a maximum // of 3 times. func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) { - url := createURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName)) + url := createURL(c, containerName, objectName) h := make(map[string]string) var b io.Reader if opts != nil { @@ -289,7 +288,7 @@ func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts C h[k] = v } - url := copyURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName)) + url := copyURL(c, containerName, objectName) resp, err := c.Request("COPY", url, &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{201}, @@ -317,7 +316,7 @@ func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) { // Delete is a function that deletes an object. func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) { - url := deleteURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName)) + url := deleteURL(c, containerName, objectName) if opts != nil { query, err := opts.ToObjectDeleteQuery() if err != nil { @@ -362,7 +361,7 @@ func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) { // the custom metadata, pass the GetResult response to the ExtractMetadata // function. func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { - url := getURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName)) + url := getURL(c, containerName, objectName) h := make(map[string]string) if opts != nil { headers, query, err := opts.ToObjectGetParams() @@ -434,7 +433,7 @@ func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts h[k] = v } } - url := updateURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName)) + url := updateURL(c, containerName, objectName) resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{ MoreHeaders: h, }) @@ -489,7 +488,7 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin duration := time.Duration(opts.TTL) * time.Second expiry := date.Add(duration).Unix() - getHeader, err := containers.Get(c, url.QueryEscape(containerName), nil).Extract() + getHeader, err := containers.Get(c, containerName, nil).Extract() if err != nil { return "", err } @@ -521,10 +520,7 @@ func BulkDelete(c *gophercloud.ServiceClient, container string, objects []string // https://github.com/openstack/swift/blob/stable/train/swift/common/swob.py#L302 encodedObjects := make([]string, len(objects)) for i, v := range objects { - encodedObjects[i] = strings.Join([]string{ - url.QueryEscape(container), - url.QueryEscape(v)}, - "/") + encodedObjects[i] = strings.Join([]string{container, v}, "/") } b := strings.NewReader(strings.Join(encodedObjects, "\n") + "\n") resp, err := c.Post(bulkDeleteURL(c), b, &r.Body, &gophercloud.RequestOpts{ diff --git a/openstack/orchestration/v1/stacks/requests.go b/openstack/orchestration/v1/stacks/requests.go index bf480f06d8..a17cbd5f98 100644 --- a/openstack/orchestration/v1/stacks/requests.go +++ b/openstack/orchestration/v1/stacks/requests.go @@ -253,6 +253,9 @@ type ListOpts struct { // ShowNested set to `true` to include nested stacks in the list. ShowNested bool `q:"show_nested"` + // ShowHidden set to `true` to include hiddened stacks in the list. + ShowHidden bool `q:"show_hidden"` + // Tags lists stacks that contain one or more simple string tags. Tags string `q:"tags"` diff --git a/openstack/placement/v1/resourceproviders/doc.go b/openstack/placement/v1/resourceproviders/doc.go index 051d404dbd..1945958438 100644 --- a/openstack/placement/v1/resourceproviders/doc.go +++ b/openstack/placement/v1/resourceproviders/doc.go @@ -50,5 +50,12 @@ Example to get resource providers traits panic(err) } +Example to get resource providers allocations + + rp, err := resourceproviders.GetAllocations(placementClient, resourceProviderID).Extract() + if err != nil { + panic(err) + } + */ package resourceproviders diff --git a/openstack/placement/v1/resourceproviders/requests.go b/openstack/placement/v1/resourceproviders/requests.go index f0da23f989..c2c9980838 100644 --- a/openstack/placement/v1/resourceproviders/requests.go +++ b/openstack/placement/v1/resourceproviders/requests.go @@ -108,6 +108,12 @@ func GetInventories(client *gophercloud.ServiceClient, resourceProviderID string return } +func GetAllocations(client *gophercloud.ServiceClient, resourceProviderID string) (r GetAllocationsResult) { + resp, err := client.Get(getResourceProviderAllocationsURL(client, resourceProviderID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + func GetTraits(client *gophercloud.ServiceClient, resourceProviderID string) (r GetTraitsResult) { resp, err := client.Get(getResourceProviderTraitsURL(client, resourceProviderID), &r.Body, nil) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) diff --git a/openstack/placement/v1/resourceproviders/results.go b/openstack/placement/v1/resourceproviders/results.go index 0562451777..582cb86af7 100644 --- a/openstack/placement/v1/resourceproviders/results.go +++ b/openstack/placement/v1/resourceproviders/results.go @@ -47,11 +47,20 @@ type Inventory struct { Total int `json:"total"` } +type Allocation struct { + Resources map[string]int `json:"resources"` +} + type ResourceProviderInventories struct { ResourceProviderGeneration int `json:"resource_provider_generation"` Inventories map[string]Inventory `json:"inventories"` } +type ResourceProviderAllocations struct { + ResourceProviderGeneration int `json:"resource_provider_generation"` + Allocations map[string]Allocation `json:"allocations"` +} + type ResourceProviderTraits struct { ResourceProviderGeneration int `json:"resource_provider_generation"` Traits []string `json:"traits"` @@ -122,6 +131,19 @@ func (r GetInventoriesResult) Extract() (*ResourceProviderInventories, error) { return &s, err } +// GetAllocationsResult is the response of a Get allocations operations. Call its Extract method +// to interpret it as a ResourceProviderAllocations. +type GetAllocationsResult struct { + gophercloud.Result +} + +// Extract interprets a GetAllocationsResult as a ResourceProviderAllocations. +func (r GetAllocationsResult) Extract() (*ResourceProviderAllocations, error) { + var s ResourceProviderAllocations + err := r.ExtractInto(&s) + return &s, err +} + // GetTraitsResult is the response of a Get traits operations. Call its Extract method // to interpret it as a ResourceProviderTraits. type GetTraitsResult struct { diff --git a/openstack/placement/v1/resourceproviders/testing/fixtures.go b/openstack/placement/v1/resourceproviders/testing/fixtures.go index 70d31ec145..29bf4c68fe 100644 --- a/openstack/placement/v1/resourceproviders/testing/fixtures.go +++ b/openstack/placement/v1/resourceproviders/testing/fixtures.go @@ -105,6 +105,32 @@ const InventoriesBody = ` } ` +const AllocationsBody = ` +{ + "allocations": { + "56785a3f-6f1c-4fec-af0b-0faf075b1fcb": { + "resources": { + "MEMORY_MB": 256, + "VCPU": 1 + } + }, + "9afd5aeb-d6b9-4dea-a588-1e6327a91834": { + "resources": { + "MEMORY_MB": 512, + "VCPU": 2 + } + }, + "9d16a611-e7f9-4ef3-be26-c61ed01ecefb": { + "resources": { + "MEMORY_MB": 1024, + "VCPU": 1 + } + } + }, + "resource_provider_generation": 12 +} +` + const TraitsBody = ` { "resource_provider_generation": 1, @@ -187,6 +213,30 @@ var ExpectedInventories = resourceproviders.ResourceProviderInventories{ }, } +var ExpectedAllocations = resourceproviders.ResourceProviderAllocations{ + ResourceProviderGeneration: 12, + Allocations: map[string]resourceproviders.Allocation{ + "56785a3f-6f1c-4fec-af0b-0faf075b1fcb": { + Resources: map[string]int{ + "MEMORY_MB": 256, + "VCPU": 1, + }, + }, + "9afd5aeb-d6b9-4dea-a588-1e6327a91834": { + Resources: map[string]int{ + "MEMORY_MB": 512, + "VCPU": 2, + }, + }, + "9d16a611-e7f9-4ef3-be26-c61ed01ecefb": { + Resources: map[string]int{ + "MEMORY_MB": 1024, + "VCPU": 1, + }, + }, + }, +} + var ExpectedTraits = resourceproviders.ResourceProviderTraits{ ResourceProviderGeneration: 1, Traits: []string{ @@ -250,6 +300,21 @@ func HandleResourceProviderGetInventories(t *testing.T) { }) } +func HandleResourceProviderGetAllocations(t *testing.T) { + allocationsTestUrl := fmt.Sprintf("/resource_providers/%s/allocations", ResourceProviderTestID) + + th.Mux.HandleFunc(allocationsTestUrl, + 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, AllocationsBody) + }) +} + func HandleResourceProviderGetTraits(t *testing.T) { traitsTestUrl := fmt.Sprintf("/resource_providers/%s/traits", ResourceProviderTestID) diff --git a/openstack/placement/v1/resourceproviders/testing/requests_test.go b/openstack/placement/v1/resourceproviders/testing/requests_test.go index bf3837b8b0..896df74812 100644 --- a/openstack/placement/v1/resourceproviders/testing/requests_test.go +++ b/openstack/placement/v1/resourceproviders/testing/requests_test.go @@ -78,6 +78,17 @@ func TestGetResourceProvidersInventories(t *testing.T) { th.AssertDeepEquals(t, ExpectedInventories, *actual) } +func TestGetResourceProvidersAllocations(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleResourceProviderGetAllocations(t) + + actual, err := resourceproviders.GetAllocations(fake.ServiceClient(), ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocations, *actual) +} + func TestGetResourceProvidersTraits(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/openstack/placement/v1/resourceproviders/urls.go b/openstack/placement/v1/resourceproviders/urls.go index 5f0ef2e347..6cc2a57c96 100644 --- a/openstack/placement/v1/resourceproviders/urls.go +++ b/openstack/placement/v1/resourceproviders/urls.go @@ -18,6 +18,10 @@ func getResourceProviderInventoriesURL(client *gophercloud.ServiceClient, resour return client.ServiceURL(apiName, resourceProviderID, "inventories") } +func getResourceProviderAllocationsURL(client *gophercloud.ServiceClient, resourceProviderID string) string { + return client.ServiceURL(apiName, resourceProviderID, "allocations") +} + func getResourceProviderTraitsURL(client *gophercloud.ServiceClient, resourceProviderID string) string { return client.ServiceURL(apiName, resourceProviderID, "traits") } diff --git a/provider_client.go b/provider_client.go index f56c1375fe..7797af61c5 100644 --- a/provider_client.go +++ b/provider_client.go @@ -440,7 +440,7 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts respErr := ErrUnexpectedResponseCode{ URL: url, Method: method, - Expected: options.OkCodes, + Expected: okc, Actual: resp.StatusCode, Body: body, ResponseHeader: resp.Header, diff --git a/script/acceptancetest b/script/acceptancetest index 766fc61a9d..6ad683bef3 100755 --- a/script/acceptancetest +++ b/script/acceptancetest @@ -28,8 +28,8 @@ acceptance/openstack/blockstorage/extensions # snapshots_test.go:21: Unable to retrieve snapshots: Resource not found # acceptance/openstack/blockstorage/v1 +# acceptance/openstack/blockstorage/v2 -acceptance/openstack/blockstorage/v2 acceptance/openstack/blockstorage/v3 # No suitable endpoint could be found in the service catalog. diff --git a/testing/provider_client_test.go b/testing/provider_client_test.go index a10f8348f3..f7278b024d 100644 --- a/testing/provider_client_test.go +++ b/testing/provider_client_test.go @@ -631,3 +631,23 @@ func TestRequestRetryContext(t *testing.T) { t.Logf("retryCounter: %d, p.MaxBackoffRetries: %d", retryCounter, p.MaxBackoffRetries-1) th.AssertEquals(t, retryCounter, p.MaxBackoffRetries-1) } + +func TestRequestWrongOkCode(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "OK") + // Returns 200 OK + })) + defer ts.Close() + + p := &gophercloud.ProviderClient{} + + _, err := p.Request("DELETE", ts.URL, &gophercloud.RequestOpts{}) + th.AssertErr(t, err) + if urErr, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + // DELETE expects a 202 or 204 by default + // Make sure returned error contains the expected OK codes + th.AssertDeepEquals(t, []int{202, 204}, urErr.Expected) + } else { + t.Fatalf("expected error type gophercloud.ErrUnexpectedResponseCode but got %T", err) + } +}