Multiple API cleanup changes

This microversion implements below API cleanups:

1. 400 for unknown param for query param and for request body.

2. Making server representation always consistent among all APIs
   returning the complete server representation.

3. Change the default return value of ``swap`` field from the empty string
   to 0 (integer) in flavor APIs.

4. Return ``servers`` field always in the response of GET
   hypervisors API even there are no servers on hypervisor

Details: https://specs.openstack.org/openstack/nova-specs/specs/train/approved/api-consistency-cleanup.html

Partial-Implements: blueprint api-consistency-cleanup

Change-Id: I9d257a003d315b84b937dcef91f3cb41f3e24b53
This commit is contained in:
Ghanshyam Mann 2019-06-26 17:30:46 +00:00 committed by Eric Fried
parent 52b9359d6c
commit b26bc7fd7a
74 changed files with 2473 additions and 164 deletions

View File

@ -108,9 +108,9 @@ Response
- extra_specs: extra_specs_2_61
**Example Create Flavor (v2.61)**
**Example Create Flavor (v2.75)**
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json
:language: javascript
List Flavors With Details
@ -158,9 +158,9 @@ Response
- os-flavor-access:is_public: flavor_is_public
- extra_specs: extra_specs_2_61
**Example List Flavors With Details (v2.61)**
**Example List Flavors With Details (v2.75)**
.. literalinclude:: ../../doc/api_samples/flavors/v2.61/flavors-detail-resp.json
.. literalinclude:: ../../doc/api_samples/flavors/v2.75/flavors-detail-resp.json
:language: javascript
Show Flavor Details
@ -201,9 +201,9 @@ Response
- os-flavor-access:is_public: flavor_is_public
- extra_specs: extra_specs_2_61
**Example Show Flavor Details (v2.61)**
**Example Show Flavor Details (v2.75)**
.. literalinclude:: ../../doc/api_samples/flavors/v2.61/flavor-get-resp.json
.. literalinclude:: ../../doc/api_samples/flavors/v2.75/flavor-get-resp.json
:language: javascript
Update Flavor Description
@ -258,9 +258,9 @@ Response
- extra_specs: extra_specs_2_61
**Example Update Flavor Description (v2.61)**
**Example Update Flavor Description (v2.75)**
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.61/flavor-update-resp.json
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.75/flavor-update-resp.json
:language: javascript
Delete Flavor

View File

@ -3074,6 +3074,8 @@ flavor_swap:
The size of a dedicated swap disk that will be allocated, in
MiB. If 0 (the default), no dedicated swap disk will be created.
Currently, the empty string ('') is used to represent 0.
As of microversion 2.75 default return value of swap is 0
instead of empty string.
in: body
required: true
type: integer
@ -3519,6 +3521,20 @@ host_status_body_in:
in: body
required: false
type: string
host_status_update_rebuild:
description: |
The host status. Values where next value in list can override the previous:
- ``UP`` if nova-compute up.
- ``UNKNOWN`` if nova-compute not reported by servicegroup driver.
- ``DOWN`` if nova-compute forced down.
- ``MAINTENANCE`` if nova-compute is disabled.
- Empty string indicates there is no host for server.
This attribute appears in the response only if the policy permits.
By default, only administrators can get this parameter.
in: body
required: false
type: string
min_version: 2.75
host_zone:
description: |
The available zone of the host.
@ -3650,8 +3666,10 @@ hypervisor_os_diagnostics:
hypervisor_servers:
description: |
A list of ``server`` objects.
This field has become mandatory in microversion 2.75. If no servers is on hypervisor
then empty list is returned.
in: body
required: false
required: true
type: array
min_version: 2.53
hypervisor_servers_name:
@ -4140,6 +4158,13 @@ key_name_resp:
in: body
required: true
type: string
key_name_resp_update:
description: |
The name of associated key pair, if any.
in: body
required: true
type: string
min_version: 2.75
key_pairs: &key_pairs
description: |
The number of allowed key pairs for each user.
@ -4730,6 +4755,13 @@ name_server_group:
in: body
required: true
type: string
name_update_rebuild:
description: |
The security group name.
in: body
required: true
type: string
min_version: 2.75
namespace:
description: |
A URL pointing to the namespace for this extension.
@ -4970,6 +5002,13 @@ OS-EXT-AZ:availability_zone_optional:
in: body
required: false
type: string
OS-EXT-AZ:availability_zone_update_rebuild:
description: |
The availability zone name.
in: body
required: true
type: string
min_version: 2.75
OS-EXT-SRV-ATTR:host:
description: |
The name of the compute host on which this instance is running.
@ -4977,6 +5016,14 @@ OS-EXT-SRV-ATTR:host:
in: body
required: true
type: string
OS-EXT-SRV-ATTR:host_update_rebuild:
description: |
The name of the compute host on which this instance is running.
Appears in the response for administrative users only.
in: body
required: true
type: string
min_version: 2.75
OS-EXT-SRV-ATTR:hypervisor_hostname:
description: |
The hypervisor host name provided by the Nova virt driver. For the Ironic driver,
@ -4984,6 +5031,14 @@ OS-EXT-SRV-ATTR:hypervisor_hostname:
in: body
required: true
type: string
OS-EXT-SRV-ATTR:hypervisor_hostname_update_rebuild:
description: |
The hypervisor host name provided by the Nova virt driver. For the Ironic driver,
it is the Ironic node uuid. Appears in the response for administrative users only.
in: body
required: true
type: string
min_version: 2.75
OS-EXT-SRV-ATTR:instance_name:
description: |
The instance name. The Compute API generates the instance name from the instance
@ -4991,6 +5046,14 @@ OS-EXT-SRV-ATTR:instance_name:
in: body
required: true
type: string
OS-EXT-SRV-ATTR:instance_name_update_rebuild:
description: |
The instance name. The Compute API generates the instance name from the instance
name template. Appears in the response for administrative users only.
in: body
required: true
type: string
min_version: 2.75
OS-EXT-STS:power_state:
description: |
The power state of the instance. This is an enum value that is mapped as::
@ -5004,18 +5067,46 @@ OS-EXT-STS:power_state:
in: body
required: true
type: integer
OS-EXT-STS:power_state_update_rebuild:
description: |
The power state of the instance. This is an enum value that is mapped as::
0: NOSTATE
1: RUNNING
3: PAUSED
4: SHUTDOWN
6: CRASHED
7: SUSPENDED
in: body
required: true
type: integer
min_version: 2.75
OS-EXT-STS:task_state:
description: |
The task state of the instance.
in: body
required: true
type: string
OS-EXT-STS:task_state_update_rebuild:
description: |
The task state of the instance.
in: body
required: true
type: string
min_version: 2.75
OS-EXT-STS:vm_state:
description: |
The VM state.
in: body
required: true
type: string
OS-EXT-STS:vm_state_update_rebuild:
description: |
The VM state.
in: body
required: true
type: string
min_version: 2.75
os-extended-volumes:volumes_attached:
description: |
The attached volumes, if any.
@ -5032,12 +5123,36 @@ os-extended-volumes:volumes_attached.delete_on_termination:
required: true
type: boolean
min_version: 2.3
os-extended-volumes:volumes_attached.delete_on_termination_update_rebuild:
description: |
A flag indicating if the attached volume will be deleted
when the server is deleted. By default this is False and
can only be set when creating a volume while creating a
server, which is commonly referred to as boot from volume.
in: body
required: true
type: boolean
min_version: 2.75
os-extended-volumes:volumes_attached.id:
description: |
The attached volume ID.
in: body
required: true
type: string
os-extended-volumes:volumes_attached.id_update_rebuild:
description: |
The attached volume ID.
in: body
required: true
type: string
min_version: 2.75
os-extended-volumes:volumes_attached_update_rebuild:
description: |
The attached volumes, if any.
in: body
required: true
type: array
min_version: 2.75
os-getConsoleOutput:
description: |
The action to get console output of the server.
@ -5151,6 +5266,24 @@ OS-SRV-USG:launched_at:
in: body
required: true
type: string
OS-SRV-USG:launched_at_update_rebuild:
description: |
The date and time when the server was launched.
The date and time stamp format is `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_:
::
CCYY-MM-DDThh:mm:ss±hh:mm
For example, ``2015-08-27T09:49:58-05:00``.
The ``hh±:mm`` value, if included, is the time zone as an offset from UTC.
If the ``deleted_at`` date and time stamp is not set, its value is ``null``.
in: body
required: true
type: string
min_version: 2.75
OS-SRV-USG:terminated_at:
description: |
The date and time when the server was deleted.
@ -5167,6 +5300,23 @@ OS-SRV-USG:terminated_at:
in: body
required: true
type: string
OS-SRV-USG:terminated_at_update_rebuild:
description: |
The date and time when the server was deleted.
The date and time stamp format is `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_:
::
CCYY-MM-DDThh:mm:ss±hh:mm
For example, ``2015-08-27T09:49:58-05:00``.
The ``±hh:mm`` value, if included, is the time zone as an offset from UTC.
If the ``deleted_at`` date and time stamp is not set, its value is ``null``.
in: body
required: true
type: string
min_version: 2.75
os-start:
description: |
The action to start a stopped server.
@ -5852,6 +6002,13 @@ security_groups_obj:
in: body
required: true
type: array
security_groups_obj_update_rebuild:
description: |
One or more security groups objects.
in: body
required: true
type: array
min_version: 2.75
security_groups_quota:
description: |
The number of allowed security groups for each tenant.
@ -6002,6 +6159,14 @@ server_hostname:
The hostname set on the instance when it is booted.
By default, it appears in the response for administrative users only.
min_version: 2.3
server_hostname_update_rebuild:
in: body
required: false
type: string
description: |
The hostname set on the instance when it is booted.
By default, it appears in the response for administrative users only.
min_version: 2.75
# This is the hypervisor_hostname in a POST (create instance) request body.
server_hypervisor_hostname_create:
description: |
@ -6032,6 +6197,14 @@ server_kernel_id:
The UUID of the kernel image when using an AMI. Will be null if not.
By default, it appears in the response for administrative users only.
min_version: 2.3
server_kernel_id_update_rebuild:
in: body
required: false
type: string
description: |
The UUID of the kernel image when using an AMI. Will be null if not.
By default, it appears in the response for administrative users only.
min_version: 2.75
server_launch_index:
in: body
required: false
@ -6041,6 +6214,15 @@ server_launch_index:
sequence in which the servers were launched.
By default, it appears in the response for administrative users only.
min_version: 2.3
server_launch_index_update_rebuild:
in: body
required: false
type: integer
description: |
When servers are launched via multiple create, this is the
sequence in which the servers were launched.
By default, it appears in the response for administrative users only.
min_version: 2.75
server_links:
description: |
Links pertaining to the server. See `API Guide / Links and
@ -6070,6 +6252,14 @@ server_ramdisk_id:
The UUID of the ramdisk image when using an AMI. Will be null if not.
By default, it appears in the response for administrative users only.
min_version: 2.3
server_ramdisk_id_update_rebuild:
in: body
required: false
type: string
description: |
The UUID of the ramdisk image when using an AMI. Will be null if not.
By default, it appears in the response for administrative users only.
min_version: 2.75
server_reservation_id:
in: body
required: false
@ -6080,6 +6270,16 @@ server_reservation_id:
create, that will all have the same reservation_id.
By default, it appears in the response for administrative users only.
min_version: 2.3
server_reservation_id_update_rebuild:
in: body
required: false
type: string
description: |
The reservation id for the server. This is an id that can
be useful in tracking groups of servers created with multiple
create, that will all have the same reservation_id.
By default, it appears in the response for administrative users only.
min_version: 2.75
server_root_device_name:
in: body
required: false
@ -6088,6 +6288,14 @@ server_root_device_name:
The root device name for the instance
By default, it appears in the response for administrative users only.
min_version: 2.3
server_root_device_name_update_rebuild:
in: body
required: false
type: string
description: |
The root device name for the instance
By default, it appears in the response for administrative users only.
min_version: 2.75
server_status:
description: |
The server status.
@ -6169,6 +6377,14 @@ server_user_data:
The user_data the instance was created with.
By default, it appears in the response for administrative users only.
min_version: 2.3
server_user_data_update:
in: body
required: false
type: string
description: |
The user_data the instance was created with.
By default, it appears in the response for administrative users only.
min_version: 2.75
server_uuid:
description: |
The UUID of the server instance to which the API dispatches the event. You must

View File

@ -634,10 +634,31 @@ Response
- trusted_image_certificates: server_trusted_image_certificates_resp
- server_groups: server_groups_2_71
- locked_reason: locked_reason_resp
- OS-EXT-AZ:availability_zone: OS-EXT-AZ:availability_zone_update_rebuild
- OS-EXT-SRV-ATTR:host: OS-EXT-SRV-ATTR:host_update_rebuild
- OS-EXT-SRV-ATTR:hypervisor_hostname: OS-EXT-SRV-ATTR:hypervisor_hostname_update_rebuild
- OS-EXT-SRV-ATTR:instance_name: OS-EXT-SRV-ATTR:instance_name_update_rebuild
- OS-EXT-STS:power_state: OS-EXT-STS:power_state_update_rebuild
- OS-EXT-STS:task_state: OS-EXT-STS:task_state_update_rebuild
- OS-EXT-STS:vm_state: OS-EXT-STS:vm_state_update_rebuild
- OS-EXT-SRV-ATTR:hostname: server_hostname_update_rebuild
- OS-EXT-SRV-ATTR:reservation_id: server_reservation_id_update_rebuild
- OS-EXT-SRV-ATTR:launch_index: server_launch_index_update_rebuild
- OS-EXT-SRV-ATTR:kernel_id: server_kernel_id_update_rebuild
- OS-EXT-SRV-ATTR:ramdisk_id: server_ramdisk_id_update_rebuild
- OS-EXT-SRV-ATTR:root_device_name: server_root_device_name_update_rebuild
- os-extended-volumes:volumes_attached: os-extended-volumes:volumes_attached_update_rebuild
- os-extended-volumes:volumes_attached.id: os-extended-volumes:volumes_attached.id_update_rebuild
- os-extended-volumes:volumes_attached.delete_on_termination: os-extended-volumes:volumes_attached.delete_on_termination_update_rebuild
- OS-SRV-USG:launched_at: OS-SRV-USG:launched_at_update_rebuild
- OS-SRV-USG:terminated_at: OS-SRV-USG:terminated_at_update_rebuild
- security_groups: security_groups_obj_update_rebuild
- security_group.name: name_update_rebuild
- host_status: host_status_update_rebuild
**Example Rebuild Server (rebuild Action) (v2.73)**
**Example Rebuild Server (rebuild Action) (v2.75)**
.. literalinclude:: ../../doc/api_samples/servers/v2.73/server-action-rebuild-resp.json
.. literalinclude:: ../../doc/api_samples/servers/v2.75/server-action-rebuild-resp.json
:language: javascript
Remove (Disassociate) Floating Ip (removeFloatingIp Action) (DEPRECATED)

View File

@ -877,10 +877,33 @@ Response
- trusted_image_certificates: server_trusted_image_certificates_resp
- server_groups: server_groups_2_71
- locked_reason: locked_reason_resp
- OS-EXT-AZ:availability_zone: OS-EXT-AZ:availability_zone_update_rebuild
- OS-EXT-SRV-ATTR:host: OS-EXT-SRV-ATTR:host_update_rebuild
- OS-EXT-SRV-ATTR:hypervisor_hostname: OS-EXT-SRV-ATTR:hypervisor_hostname_update_rebuild
- OS-EXT-SRV-ATTR:instance_name: OS-EXT-SRV-ATTR:instance_name_update_rebuild
- OS-EXT-STS:power_state: OS-EXT-STS:power_state_update_rebuild
- OS-EXT-STS:task_state: OS-EXT-STS:task_state_update_rebuild
- OS-EXT-STS:vm_state: OS-EXT-STS:vm_state_update_rebuild
- OS-EXT-SRV-ATTR:hostname: server_hostname_update_rebuild
- OS-EXT-SRV-ATTR:reservation_id: server_reservation_id_update_rebuild
- OS-EXT-SRV-ATTR:launch_index: server_launch_index_update_rebuild
- OS-EXT-SRV-ATTR:kernel_id: server_kernel_id_update_rebuild
- OS-EXT-SRV-ATTR:ramdisk_id: server_ramdisk_id_update_rebuild
- OS-EXT-SRV-ATTR:root_device_name: server_root_device_name_update_rebuild
- OS-EXT-SRV-ATTR:user_data: server_user_data_update
- os-extended-volumes:volumes_attached: os-extended-volumes:volumes_attached_update_rebuild
- os-extended-volumes:volumes_attached.id: os-extended-volumes:volumes_attached.id_update_rebuild
- os-extended-volumes:volumes_attached.delete_on_termination: os-extended-volumes:volumes_attached.delete_on_termination_update_rebuild
- OS-SRV-USG:launched_at: OS-SRV-USG:launched_at_update_rebuild
- OS-SRV-USG:terminated_at: OS-SRV-USG:terminated_at_update_rebuild
- security_groups: security_groups_obj_update_rebuild
- security_group.name: name_update_rebuild
- host_status: host_status_update_rebuild
- key_name: key_name_resp_update
**Example Update Server (2.73)**
**Example Update Server (2.75)**
.. literalinclude:: ../../doc/api_samples/servers/v2.73/server-update-resp.json
.. literalinclude:: ../../doc/api_samples/servers/v2.75/server-update-resp.json
:language: javascript
Delete Server

View File

@ -0,0 +1,11 @@
{
"flavor": {
"name": "test_flavor",
"ram": 1024,
"vcpus": 2,
"disk": 10,
"id": "10",
"rxtx_factor": 2.0,
"description": "test description"
}
}

View File

@ -0,0 +1,26 @@
{
"flavor": {
"OS-FLV-DISABLED:disabled": false,
"disk": 10,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "10",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/10",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/10",
"rel": "bookmark"
}
],
"name": "test_flavor",
"ram": 1024,
"swap": 0,
"rxtx_factor": 2.0,
"vcpus": 2,
"description": "test description",
"extra_specs": {}
}
}

View File

@ -0,0 +1,5 @@
{
"flavor": {
"description": "updated description"
}
}

View File

@ -0,0 +1,26 @@
{
"flavor": {
"OS-FLV-DISABLED:disabled": false,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": "updated description",
"extra_specs": {}
}
}

View File

@ -0,0 +1,29 @@
{
"flavor": {
"OS-FLV-DISABLED:disabled": false,
"disk": 20,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "7",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7",
"rel": "bookmark"
}
],
"name": "m1.small.description",
"ram": 2048,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": "test description",
"extra_specs": {
"key1": "value1",
"key2": "value2"
}
}
}

View File

@ -0,0 +1,178 @@
{
"flavors": [
{
"OS-FLV-DISABLED:disabled": false,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 20,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "2",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/2",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/2",
"rel": "bookmark"
}
],
"name": "m1.small",
"ram": 2048,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 40,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "3",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/3",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/3",
"rel": "bookmark"
}
],
"name": "m1.medium",
"ram": 4096,
"swap": 0,
"vcpus": 2,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 80,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "4",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/4",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/4",
"rel": "bookmark"
}
],
"name": "m1.large",
"ram": 8192,
"swap": 0,
"vcpus": 4,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 160,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "5",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/5",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/5",
"rel": "bookmark"
}
],
"name": "m1.xlarge",
"ram": 16384,
"swap": 0,
"vcpus": 8,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "6",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/6",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/6",
"rel": "bookmark"
}
],
"name": "m1.tiny.specs",
"ram": 512,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {
"hw:mem_page_size": "2048",
"hw:cpu_policy": "dedicated"
}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 20,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "7",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7",
"rel": "bookmark"
}
],
"name": "m1.small.description",
"ram": 2048,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": "test description",
"extra_specs": {
"key1": "value1",
"key2": "value2"
}
}
]
}

View File

@ -0,0 +1,109 @@
{
"flavors": [
{
"id": "1",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny",
"description": null
},
{
"id": "2",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/2",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/2",
"rel": "bookmark"
}
],
"name": "m1.small",
"description": null
},
{
"id": "3",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/3",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/3",
"rel": "bookmark"
}
],
"name": "m1.medium",
"description": null
},
{
"id": "4",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/4",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/4",
"rel": "bookmark"
}
],
"name": "m1.large",
"description": null
},
{
"id": "5",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/5",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/5",
"rel": "bookmark"
}
],
"name": "m1.xlarge",
"description": null
},
{
"id": "6",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/6",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/6",
"rel": "bookmark"
}
],
"name": "m1.tiny.specs",
"description": null
},
{
"id": "7",
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7",
"rel": "bookmark"
}
],
"name": "m1.small.description",
"description": "test description"
}
]
}

View File

@ -0,0 +1,89 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "us-west",
"OS-EXT-SRV-ATTR:host": "compute",
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "r-t61j9da6",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "2019-04-23T15:19:10.855016",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"addresses": {
"private": [
{
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
"version": 4
}
]
},
"adminPass": "seekr3t",
"config_drive": "",
"created": "2019-04-23T17:10:22Z",
"description": null,
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
"host_status": "UP",
"id": "0c37a84a-c757-4f22-8c7f-0bf8b6970886",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/0c37a84a-c757-4f22-8c7f-0bf8b6970886",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/0c37a84a-c757-4f22-8c7f-0bf8b6970886",
"rel": "bookmark"
}
],
"locked": false,
"locked_reason": null,
"metadata": {
"meta_var": "meta_val"
},
"name": "foobar",
"os-extended-volumes:volumes_attached": [],
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"server_groups": [],
"status": "ACTIVE",
"tags": [],
"tenant_id": "6f70656e737461636b20342065766572",
"trusted_image_certificates": null,
"updated": "2019-04-23T17:10:24Z",
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
"user_id": "fake"
}
}

View File

@ -0,0 +1,14 @@
{
"rebuild" : {
"accessIPv4" : "1.2.3.4",
"accessIPv6" : "80fe::",
"OS-DCF:diskConfig": "AUTO",
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
"name" : "foobar",
"adminPass" : "seekr3t",
"metadata" : {
"meta_var" : "meta_val"
},
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}

View File

@ -0,0 +1,9 @@
{
"server": {
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"OS-DCF:diskConfig": "AUTO",
"name": "new-server-test",
"description": "Sample description"
}
}

View File

@ -0,0 +1,88 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "us-west",
"OS-EXT-SRV-ATTR:host": "compute",
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "r-t61j9da6",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "2019-04-23T15:19:10.855016",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"addresses": {
"private": [
{
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
"version": 4
}
]
},
"config_drive": "",
"created": "2012-12-02T02:11:57Z",
"description": "Sample description",
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "6e84af987b4e7ec1c039b16d21f508f4a505672bd94fb0218b668d07",
"host_status": "UP",
"id": "324dfb7d-f4a9-419a-9a19-237df04b443b",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/servers/324dfb7d-f4a9-419a-9a19-237df04b443b",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/324dfb7d-f4a9-419a-9a19-237df04b443b",
"rel": "bookmark"
}
],
"locked": false,
"locked_reason": null,
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"os-extended-volumes:volumes_attached": [],
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"server_groups": [],
"status": "ACTIVE",
"tags": [],
"tenant_id": "6f70656e737461636b20342065766572",
"trusted_image_certificates": null,
"updated": "2012-12-02T02:11:58Z",
"user_id": "fake"
}
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.74",
"version": "2.75",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.74",
"version": "2.75",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -188,6 +188,14 @@ REST_API_VERSION_HISTORY = """REST API Version History:
in request body to ``POST /servers``. Allow users to specify which
host/node they want their servers to land on and still be
validated by the scheduler.
* 2.75 - Multiple API cleanup listed below:
- 400 for unknown param for query param and for request body.
- Making server representation always consistent among GET, PUT
and Rebuild serevr APIs response.
- Change the default return value of swap field from the empty
string to 0 (integer) in flavor APIs.
- Return ``servers`` field always in the response of GET
hypervisors API even there are no servers on hypervisor.
"""
# The minimum and maximum versions of the API supported
@ -196,7 +204,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.74"
_MAX_API_VERSION = "2.75"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal

View File

@ -46,7 +46,8 @@ class AgentController(wsgi.Controller):
http://wiki.openstack.org/GuestAgent
http://wiki.openstack.org/GuestAgentXenStoreCommunication
"""
@validation.query_schema(schema.index_query)
@validation.query_schema(schema.index_query_275, '2.75')
@validation.query_schema(schema.index_query, '2.0', '2.74')
@wsgi.expected_errors(())
def index(self, req):
"""Return a list of all agent builds. Filter by hypervisor."""

View File

@ -62,7 +62,10 @@ class AssistedVolumeSnapshotsController(wsgi.Controller):
raise exc.HTTPBadRequest(explanation=e.format_message())
@wsgi.response(204)
@validation.query_schema(assisted_volume_snapshots.delete_query)
@validation.query_schema(assisted_volume_snapshots.delete_query_275,
'2.75')
@validation.query_schema(assisted_volume_snapshots.delete_query, '2.0',
'2.74')
@wsgi.expected_errors((400, 404))
def delete(self, req, id):
"""Delete a snapshot."""

View File

@ -35,14 +35,16 @@ class FlavorsController(wsgi.Controller):
_view_builder_class = flavors_view.ViewBuilder
@validation.query_schema(schema.index_query)
@validation.query_schema(schema.index_query_275, '2.75')
@validation.query_schema(schema.index_query, '2.0', '2.74')
@wsgi.expected_errors(400)
def index(self, req):
"""Return all flavors in brief."""
limited_flavors = self._get_flavors(req)
return self._view_builder.index(req, limited_flavors)
@validation.query_schema(schema.index_query)
@validation.query_schema(schema.index_query_275, '2.75')
@validation.query_schema(schema.index_query, '2.0', '2.74')
@wsgi.expected_errors(400)
def detail(self, req):
"""Return all flavors in detail."""

View File

@ -50,7 +50,7 @@ class HypervisorsController(wsgi.Controller):
self.servicegroup_api = servicegroup.API()
def _view_hypervisor(self, hypervisor, service, detail, req, servers=None,
**kwargs):
with_servers=False, **kwargs):
alive = self.servicegroup_api.service_is_up(service)
# The 2.53 microversion returns the compute node uuid rather than id.
uuid_for_id = api_version_request.is_supported(
@ -89,6 +89,12 @@ class HypervisorsController(wsgi.Controller):
if servers:
hyp_dict['servers'] = [dict(name=serv['name'], uuid=serv['uuid'])
for serv in servers]
# The 2.75 microversion adds 'servers' field always in response.
# Empty list if there are no servers on hypervisors and it is
# requested in request.
elif with_servers and api_version_request.is_supported(
req, min_version='2.75'):
hyp_dict['servers'] = []
# Add any additional info
if kwargs:
@ -169,7 +175,8 @@ class HypervisorsController(wsgi.Controller):
context, hyp.host)
hypervisors_list.append(
self._view_hypervisor(
hyp, service, detail, req, servers=instances))
hyp, service, detail, req, servers=instances,
with_servers=with_servers))
except (exception.ComputeHostNotFound,
exception.HostMappingNotFound):
# The compute service could be deleted which doesn't delete
@ -312,7 +319,7 @@ class HypervisorsController(wsgi.Controller):
msg = _("Hypervisor with ID '%s' could not be found.") % id
raise webob.exc.HTTPNotFound(explanation=msg)
return dict(hypervisor=self._view_hypervisor(
hyp, service, True, req, instances))
hyp, service, True, req, instances, with_servers))
@wsgi.expected_errors((400, 404, 501))
def uptime(self, req, id):

View File

@ -160,7 +160,8 @@ class KeypairController(wsgi.Controller):
self._delete(req, id)
@wsgi.Controller.api_version("2.10") # noqa
@validation.query_schema(keypairs.delete_query_schema_v210)
@validation.query_schema(keypairs.delete_query_schema_v275, '2.75')
@validation.query_schema(keypairs.delete_query_schema_v210, '2.10', '2.74')
@wsgi.response(204)
@wsgi.expected_errors(404)
def delete(self, req, id):
@ -187,7 +188,8 @@ class KeypairController(wsgi.Controller):
return user_id
@wsgi.Controller.api_version("2.10")
@validation.query_schema(keypairs.show_query_schema_v210)
@validation.query_schema(keypairs.show_query_schema_v275, '2.75')
@validation.query_schema(keypairs.show_query_schema_v210, '2.10', '2.74')
@wsgi.expected_errors(404)
def show(self, req, id):
# handle optional user-id for admin only
@ -230,7 +232,8 @@ class KeypairController(wsgi.Controller):
return {'keypair': keypair}
@wsgi.Controller.api_version("2.35")
@validation.query_schema(keypairs.index_query_schema_v235)
@validation.query_schema(keypairs.index_query_schema_v275, '2.75')
@validation.query_schema(keypairs.index_query_schema_v235, '2.35', '2.74')
@wsgi.expected_errors(400)
def index(self, req):
user_id = self._get_user_id(req)

View File

@ -66,7 +66,8 @@ class LimitsController(wsgi.Controller):
@wsgi.Controller.api_version('2.57') # noqa
@wsgi.expected_errors(())
@validation.query_schema(limits.limits_query_schema)
@validation.query_schema(limits.limits_query_schema_275, '2.75')
@validation.query_schema(limits.limits_query_schema, '2.57', '2.74')
def index(self, req):
return self._index(req, FILTERED_LIMITS_2_57, max_image_meta=False)

View File

@ -120,7 +120,8 @@ class QuotaSetsController(wsgi.Controller):
def show(self, req, id):
return self._show(req, id, FILTERED_QUOTAS_2_57)
@validation.query_schema(quota_sets.query_schema)
@validation.query_schema(quota_sets.query_schema_275, '2.75')
@validation.query_schema(quota_sets.query_schema, '2.0', '2.74')
def _show(self, req, id, filtered_quotas):
context = req.environ['nova.context']
context.can(qs_policies.POLICY_ROOT % 'show', {'project_id': id})
@ -148,7 +149,8 @@ class QuotaSetsController(wsgi.Controller):
def detail(self, req, id):
return self._detail(req, id, FILTERED_QUOTAS_2_57)
@validation.query_schema(quota_sets.query_schema)
@validation.query_schema(quota_sets.query_schema_275, '2.75')
@validation.query_schema(quota_sets.query_schema, '2.0', '2.74')
def _detail(self, req, id, filtered_quotas):
context = req.environ['nova.context']
context.can(qs_policies.POLICY_ROOT % 'detail', {'project_id': id})
@ -179,7 +181,8 @@ class QuotaSetsController(wsgi.Controller):
def update(self, req, id, body):
return self._update(req, id, body, FILTERED_QUOTAS_2_57)
@validation.query_schema(quota_sets.query_schema)
@validation.query_schema(quota_sets.query_schema_275, '2.75')
@validation.query_schema(quota_sets.query_schema, '2.0', '2.74')
def _update(self, req, id, body, filtered_quotas):
context = req.environ['nova.context']
context.can(qs_policies.POLICY_ROOT % 'update', {'project_id': id})
@ -267,7 +270,8 @@ class QuotaSetsController(wsgi.Controller):
# +microversions because the resource quota-set has been deleted completely
# when returning a response.
@wsgi.expected_errors(())
@validation.query_schema(quota_sets.query_schema)
@validation.query_schema(quota_sets.query_schema_275, '2.75')
@validation.query_schema(quota_sets.query_schema, '2.0', '2.74')
@wsgi.response(202)
def delete(self, req, id):
context = req.environ['nova.context']

View File

@ -955,3 +955,22 @@ be raised.
There will be also a new policy named
``compute:servers:create:requested_destination``. By default,
it can be specified by administrators only.
2.75
----
Multiple API cleanups is done in API microversion 2.75:
* 400 for unknown param for query param and for request body.
* Making server representation always consistent among GET, PUT
and Rebuild serevr APIs response. ``PUT /servers/{server_id}``
and ``POST /servers/{server_id}/action {rebuild}`` API response
is modified to add all the missing fields which are return
by ``GET /servers/{server_id}``.
* Change the default return value of swap field from the empty
string to 0 (integer) in flavor APIs.
* Return ``servers`` field always in the response of GET
hypervisors API even there are no servers on hypervisor.

View File

@ -11,6 +11,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
create = {
@ -88,7 +91,10 @@ index_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
index_query_275 = copy.deepcopy(index_query)
index_query_275['additionalProperties'] = False

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
snapshots_create = {
@ -58,7 +60,10 @@ delete_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
delete_query_275 = copy.deepcopy(delete_query)
delete_query_275['additionalProperties'] = False

View File

@ -11,6 +11,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
# NOTE(takashin): The following sort keys are defined for backward
@ -39,7 +42,10 @@ index_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
index_query_275 = copy.deepcopy(index_query)
index_query_275['additionalProperties'] = False

View File

@ -44,7 +44,7 @@ index_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. This API is deprecated in microversion 2.43 so we
# do not to update the additionalProperties to False.
'additionalProperties': True
}

View File

@ -105,3 +105,10 @@ show_query_schema_v20 = index_query_schema_v20
show_query_schema_v210 = index_query_schema_v210
delete_query_schema_v20 = index_query_schema_v20
delete_query_schema_v210 = index_query_schema_v210
index_query_schema_v275 = copy.deepcopy(index_query_schema_v235)
index_query_schema_v275['additionalProperties'] = False
show_query_schema_v275 = copy.deepcopy(show_query_schema_v210)
show_query_schema_v275['additionalProperties'] = False
delete_query_schema_v275 = copy.deepcopy(delete_query_schema_v210)
delete_query_schema_v275['additionalProperties'] = False

View File

@ -11,6 +11,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
@ -20,5 +22,10 @@ limits_query_schema = {
'tenant_id': parameter_types.common_query_param,
},
# For backward compatible changes
# In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
limits_query_schema_275 = copy.deepcopy(limits_query_schema)
limits_query_schema_275['additionalProperties'] = False

View File

@ -85,7 +85,10 @@ query_schema = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
query_schema_275 = copy.deepcopy(query_schema)
query_schema_275['additionalProperties'] = False

View File

@ -25,7 +25,7 @@ index_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. This API is deprecated in microversion 2.36 so we
# do not to update the additionalProperties to False.
'additionalProperties': True
}

View File

@ -80,6 +80,10 @@ server_groups_query_param = {
'offset': parameter_types.multi_params(
parameter_types.non_negative_integer),
},
# For backward compatible changes
# For backward compatible changes. In microversion 2.75, we have
# blocked the additional parameters.
'additionalProperties': True
}
server_groups_query_param_275 = copy.deepcopy(server_groups_query_param)
server_groups_query_param_275['additionalProperties'] = False

View File

@ -625,6 +625,8 @@ query_params_v21 = {
# For backward-compatible additionalProperties is set to be True here.
# And we will either strip the extra params out or raise HTTP 400
# according to the params' value in the later process.
# This has been changed to False in microversion 2.75. From
# microversion 2.75, no additional unknown parameter will be allowed.
'additionalProperties': True,
# Prevent internal-attributes that are started with underscore from
# being striped out in schema validation, and raise HTTP 400 in API.
@ -659,3 +661,22 @@ query_params_v273['properties'].update({
'sort_key': multi_params(VALID_SORT_KEYS_V273),
'locked': parameter_types.common_query_param,
})
# Microversion 2.75 makes query schema to disallow any invalid or unknown
# query parameters (filter or sort keys).
# *****Schema updates for microversion 2.75 start here*******
query_params_v275 = copy.deepcopy(query_params_v273)
# 1. Update sort_keys to allow only valid sort keys:
# NOTE(gmann): Remove the ignored sort keys now because 'additionalProperties'
# is Flase for query schema. Starting from miceoversion 2.75, API will
# raise 400 for any not-allowed sort keys instead of ignoring them.
VALID_SORT_KEYS_V275 = copy.deepcopy(VALID_SORT_KEYS_V273)
VALID_SORT_KEYS_V275['enum'] = list(
set(VALID_SORT_KEYS_V273["enum"]) - set(
SERVER_LIST_IGNORE_SORT_KEY_V273))
query_params_v275['properties'].update({
'sort_key': multi_params(VALID_SORT_KEYS_V275),
})
# 2. Make 'additionalProperties' False.
query_params_v275['additionalProperties'] = False
# *****Schema updates for microversion 2.75 end here*******

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
service_update = {
@ -76,3 +78,6 @@ index_query_schema = {
# For backward compatible changes
'additionalProperties': True
}
index_query_schema_275 = copy.deepcopy(index_query_schema)
index_query_schema_275['additionalProperties'] = False

View File

@ -25,8 +25,8 @@ index_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
@ -38,8 +38,8 @@ show_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
@ -50,3 +50,9 @@ index_query_v240['properties'].update(
show_query_v240 = copy.deepcopy(show_query)
show_query_v240['properties'].update(
parameter_types.pagination_parameters)
index_query_275 = copy.deepcopy(index_query_v240)
index_query_275['additionalProperties'] = False
show_query_275 = copy.deepcopy(show_query_v240)
show_query_275['additionalProperties'] = False

View File

@ -101,9 +101,12 @@ index_query = {
},
# NOTE(gmann): This is kept True to keep backward compatibility.
# As of now Schema validation stripped out the additional parameters and
# does not raise 400. In the future, we may block the additional parameters
# by bump in Microversion.
# does not raise 400. In microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
detail_query = index_query
index_query_275 = copy.deepcopy(index_query)
index_query_275['additionalProperties'] = False

View File

@ -148,7 +148,8 @@ class ServerGroupController(wsgi.Controller):
raise webob.exc.HTTPNotFound(explanation=e.format_message())
@wsgi.expected_errors(())
@validation.query_schema(schema.server_groups_query_param)
@validation.query_schema(schema.server_groups_query_param_275, '2.75')
@validation.query_schema(schema.server_groups_query_param, '2.0', '2.74')
def index(self, req):
"""Returns a list of server groups."""
context = _authorize_context(req, 'index')

View File

@ -108,7 +108,8 @@ class ServersController(wsgi.Controller):
self.network_api = network_api.API()
@wsgi.expected_errors((400, 403))
@validation.query_schema(schema_servers.query_params_v273, '2.73')
@validation.query_schema(schema_servers.query_params_v275, '2.75')
@validation.query_schema(schema_servers.query_params_v273, '2.73', '2.74')
@validation.query_schema(schema_servers.query_params_v266, '2.66', '2.72')
@validation.query_schema(schema_servers.query_params_v226, '2.26', '2.65')
@validation.query_schema(schema_servers.query_params_v21, '2.1', '2.25')
@ -123,7 +124,8 @@ class ServersController(wsgi.Controller):
return servers
@wsgi.expected_errors((400, 403))
@validation.query_schema(schema_servers.query_params_v273, '2.73')
@validation.query_schema(schema_servers.query_params_v275, '2.75')
@validation.query_schema(schema_servers.query_params_v273, '2.73', '2.74')
@validation.query_schema(schema_servers.query_params_v266, '2.66', '2.72')
@validation.query_schema(schema_servers.query_params_v226, '2.26', '2.65')
@validation.query_schema(schema_servers.query_params_v21, '2.1', '2.25')
@ -828,19 +830,38 @@ class ServersController(wsgi.Controller):
try:
instance = self.compute_api.update_instance(ctxt, instance,
update_dict)
# NOTE(gmann): Starting from microversion 2.75, PUT and Rebuild
# API response will show all attributes like GET /servers API.
show_all_attributes = api_version_request.is_supported(
req, min_version='2.75')
extend_address = show_all_attributes
show_AZ = show_all_attributes
show_config_drive = show_all_attributes
show_keypair = show_all_attributes
show_srv_usg = show_all_attributes
show_sec_grp = show_all_attributes
show_extended_status = show_all_attributes
show_extended_volumes = show_all_attributes
# NOTE(gmann): Below attributes need to be added in response
# if respective policy allows.So setting these as None
# to perform the policy check in view builder.
show_extended_attr = None if show_all_attributes else False
show_host_status = None if show_all_attributes else False
return self._view_builder.show(
req, instance,
extend_address=False,
show_AZ=False,
show_config_drive=False,
show_extended_attr=False,
show_host_status=False,
show_keypair=False,
show_srv_usg=False,
show_sec_grp=False,
show_extended_status=False,
show_extended_volumes=False,
show_server_groups=show_server_groups)
req, instance,
extend_address=extend_address,
show_AZ=show_AZ,
show_config_drive=show_config_drive,
show_extended_attr=show_extended_attr,
show_host_status=show_host_status,
show_keypair=show_keypair,
show_srv_usg=show_srv_usg,
show_sec_grp=show_sec_grp,
show_extended_status=show_extended_status,
show_extended_volumes=show_extended_volumes,
show_server_groups=show_server_groups)
except exception.InstanceNotFound:
msg = _("Instance could not be found")
raise exc.HTTPNotFound(explanation=msg)
@ -1118,17 +1139,40 @@ class ServersController(wsgi.Controller):
show_server_groups = api_version_request.is_supported(
req, min_version='2.71')
view = self._view_builder.show(req, instance, extend_address=False,
show_AZ=False,
show_config_drive=False,
show_extended_attr=False,
show_host_status=False,
show_keypair=show_keypair,
show_srv_usg=False,
show_sec_grp=False,
show_extended_status=False,
show_extended_volumes=False,
show_server_groups=show_server_groups)
# NOTE(gmann): Starting from microversion 2.75, PUT and Rebuild
# API response will show all attributes like GET /servers API.
show_all_attributes = api_version_request.is_supported(
req, min_version='2.75')
extend_address = show_all_attributes
show_AZ = show_all_attributes
show_config_drive = show_all_attributes
show_srv_usg = show_all_attributes
show_sec_grp = show_all_attributes
show_extended_status = show_all_attributes
show_extended_volumes = show_all_attributes
# NOTE(gmann): Below attributes need to be added in response
# if respective policy allows.So setting these as None
# to perform the policy check in view builder.
show_extended_attr = None if show_all_attributes else False
show_host_status = None if show_all_attributes else False
view = self._view_builder.show(
req, instance,
extend_address=extend_address,
show_AZ=show_AZ,
show_config_drive=show_config_drive,
show_extended_attr=show_extended_attr,
show_host_status=show_host_status,
show_keypair=show_keypair,
show_srv_usg=show_srv_usg,
show_sec_grp=show_sec_grp,
show_extended_status=show_extended_status,
show_extended_volumes=show_extended_volumes,
show_server_groups=show_server_groups,
# NOTE(gmann): user_data has been added in response (by code at
# the end of this API method) since microversion 2.57 so tell
# view builder not to include it.
show_user_data=False)
# Add on the admin_password attribute since the view doesn't do it
# unless instance passwords are disabled

View File

@ -292,7 +292,8 @@ class ServiceController(wsgi.Controller):
explanation = _("Service id %s refers to multiple services.") % id
raise webob.exc.HTTPBadRequest(explanation=explanation)
@validation.query_schema(services.index_query_schema)
@validation.query_schema(services.index_query_schema_275, '2.75')
@validation.query_schema(services.index_query_schema, '2.0', '2.74')
@wsgi.expected_errors(())
def index(self, req):
"""Return a list of all running services. Filter by host & service

View File

@ -263,7 +263,8 @@ class SimpleTenantUsageController(wsgi.Controller):
return (period_start, period_stop, detailed)
@wsgi.Controller.api_version("2.40")
@validation.query_schema(schema.index_query_v240)
@validation.query_schema(schema.index_query_275, '2.75')
@validation.query_schema(schema.index_query_v240, '2.40', '2.74')
@wsgi.expected_errors(400)
def index(self, req):
"""Retrieve tenant_usage for all tenants."""
@ -277,7 +278,8 @@ class SimpleTenantUsageController(wsgi.Controller):
return self._index(req)
@wsgi.Controller.api_version("2.40")
@validation.query_schema(schema.show_query_v240)
@validation.query_schema(schema.show_query_275, '2.75')
@validation.query_schema(schema.show_query_v240, '2.40', '2.74')
@wsgi.expected_errors(400)
def show(self, req, id):
"""Retrieve tenant_usage for a specified tenant."""

View File

@ -70,6 +70,9 @@ class ViewBuilder(common.ViewBuilder):
if include_extra_specs:
flavor_dict['flavor']['extra_specs'] = flavor.extra_specs
if api_version_request.is_supported(request, '2.75'):
flavor_dict['flavor']['swap'] = flavor["swap"] or 0
return flavor_dict
def index(self, request, flavors):

View File

@ -88,13 +88,15 @@ class ViewBuilder(common.ViewBuilder):
'AUTO' if instance.get('auto_disk_config') else 'MANUAL'),
},
}
self._add_security_grps(request, [server["server"]], [instance])
self._add_security_grps(request, [server["server"]], [instance],
create_request=True)
return server
def basic(self, request, instance, show_extra_specs=False,
show_extended_attr=None, show_host_status=None,
show_sec_grp=None, bdms=None, cell_down_support=False):
show_sec_grp=None, bdms=None, cell_down_support=False,
show_user_data=False):
"""Generic, non-detailed view of an instance."""
if cell_down_support and 'display_name' not in instance:
# NOTE(tssurya): If the microversion is >= 2.69, this boolean will
@ -187,7 +189,8 @@ class ViewBuilder(common.ViewBuilder):
show_extended_attr=None, show_host_status=None,
show_keypair=True, show_srv_usg=True, show_sec_grp=True,
show_extended_status=True, show_extended_volumes=True,
bdms=None, cell_down_support=False, show_server_groups=False):
bdms=None, cell_down_support=False, show_server_groups=False,
show_user_data=True):
"""Detailed view of a single instance."""
if show_extra_specs is None:
# detail will pre-calculate this for us. If we're doing show,
@ -284,7 +287,15 @@ class ViewBuilder(common.ViewBuilder):
# the OS-EXT-SRV-ATTR prefix.
properties += ['reservation_id', 'launch_index',
'hostname', 'kernel_id', 'ramdisk_id',
'root_device_name', 'user_data']
'root_device_name']
# NOTE(gmann): Since microversion 2.75, PUT and Rebuild
# response include all the server attributes including these
# extended attributes also. But microversion 2.57 already
# adding the 'user_data' in Rebuild response in API method.
# so we will skip adding the user data attribute for rebuild
# case. 'show_user_data' is false only in case of rebuild.
if show_user_data:
properties += ['user_data']
for attr in properties:
if attr == 'name':
key = "OS-EXT-SRV-ATTR:instance_%s" % attr
@ -585,7 +596,8 @@ class ViewBuilder(common.ViewBuilder):
if server['id'] in host_statuses:
server['host_status'] = host_statuses[server['id']]
def _add_security_grps(self, req, servers, instances):
def _add_security_grps(self, req, servers, instances,
create_request=False):
if not len(servers):
return
if not openstack_driver.is_neutron_security_groups():
@ -597,11 +609,14 @@ class ViewBuilder(common.ViewBuilder):
server['security_groups'] = [{"name": group.name}
for group in groups]
else:
# If method is a POST we get the security groups intended for an
# instance from the request. The reason for this is if using
# neutron security groups the requested security groups for the
# instance are not in the db and have not been sent to neutron yet.
if req.method != 'POST':
# If request is a POST create server we get the security groups
# intended for an instance from the request. The reason for this
# is if using neutron security groups the requested security
# groups for the instance are not in the db and have not been
# sent to neutron yet.
# Starting from microversion 2.75, security groups is returned in
# PUT and POST Rebuild response also.
if not create_request:
context = req.environ['nova.context']
sg_instance_bindings = (
self.security_group_api
@ -612,8 +627,8 @@ class ViewBuilder(common.ViewBuilder):
if groups:
server['security_groups'] = groups
# This section is for POST request. There can be only one security
# group for POST request.
# This section is for POST create server request. There can be
# only one security group for POST create server request.
else:
# try converting to json
req_obj = jsonutils.loads(req.body)

View File

@ -267,7 +267,8 @@ class VolumeAttachmentController(wsgi.Controller):
super(VolumeAttachmentController, self).__init__()
@wsgi.expected_errors(404)
@validation.query_schema(volumes_schema.index_query)
@validation.query_schema(volumes_schema.index_query_275, '2.75')
@validation.query_schema(volumes_schema.index_query, '2.0', '2.74')
def index(self, req, server_id):
"""Returns the list of volume attachments for a given instance."""
context = req.environ['nova.context']

View File

@ -186,8 +186,8 @@ def query_schema(query_params_schema, min_version=None,
# out when `additionalProperties=True`. This is for backward
# compatible with v2.1 API and legacy v2 API. But it makes the
# system more safe for no more unexpected parameters pass down
# to the system. In the future, we may block all of those
# additional parameters by Microversion.
# to the system. In microversion 2.75, we have blocked all of
# those additional parameters.
_strip_additional_query_parameters(query_params_schema, req)
return func(*args, **kwargs)
return wrapper

View File

@ -0,0 +1,11 @@
{
"flavor": {
"name": "%(flavor_name)s",
"ram": 1024,
"vcpus": 2,
"disk": 10,
"id": "%(flavor_id)s",
"rxtx_factor": 2.0,
"description": "test description"
}
}

View File

@ -0,0 +1,26 @@
{
"flavor": {
"disk": 10,
"id": "%(flavor_id)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/%(flavor_id)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/%(flavor_id)s",
"rel": "bookmark"
}
],
"name": "%(flavor_name)s",
"os-flavor-access:is_public": true,
"ram": 1024,
"vcpus": 2,
"OS-FLV-DISABLED:disabled": false,
"OS-FLV-EXT-DATA:ephemeral": 0,
"swap": 0,
"rxtx_factor": 2.0,
"description": "test description",
"extra_specs": {}
}
}

View File

@ -0,0 +1,5 @@
{
"flavor": {
"description": "updated description"
}
}

View File

@ -0,0 +1,26 @@
{
"flavor": {
"OS-FLV-DISABLED:disabled": false,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": "updated description",
"extra_specs": {}
}
}

View File

@ -0,0 +1,29 @@
{
"flavor": {
"OS-FLV-DISABLED:disabled": false,
"disk": 20,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "%(flavorid)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/%(flavorid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/%(flavorid)s",
"rel": "bookmark"
}
],
"name": "m1.small.description",
"os-flavor-access:is_public": true,
"ram": 2048,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": "test description",
"extra_specs": {
"key1": "value1",
"key2": "value2"
}
}
}

View File

@ -0,0 +1,178 @@
{
"flavors": [
{
"OS-FLV-DISABLED:disabled": false,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "1",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/1",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny",
"os-flavor-access:is_public": true,
"ram": 512,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 20,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "2",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/2",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/2",
"rel": "bookmark"
}
],
"name": "m1.small",
"os-flavor-access:is_public": true,
"ram": 2048,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 40,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "3",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/3",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/3",
"rel": "bookmark"
}
],
"name": "m1.medium",
"os-flavor-access:is_public": true,
"ram": 4096,
"swap": 0,
"vcpus": 2,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 80,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "4",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/4",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/4",
"rel": "bookmark"
}
],
"name": "m1.large",
"os-flavor-access:is_public": true,
"ram": 8192,
"swap": 0,
"vcpus": 4,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 160,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "5",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/5",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/5",
"rel": "bookmark"
}
],
"name": "m1.xlarge",
"os-flavor-access:is_public": true,
"ram": 16384,
"swap": 0,
"vcpus": 8,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "6",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/6",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/6",
"rel": "bookmark"
}
],
"name": "m1.tiny.specs",
"os-flavor-access:is_public": true,
"ram": 512,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": null,
"extra_specs": {
"hw:mem_page_size": "2048",
"hw:cpu_policy": "dedicated"
}
},
{
"OS-FLV-DISABLED:disabled": false,
"disk": 20,
"OS-FLV-EXT-DATA:ephemeral": 0,
"id": "%(flavorid)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/flavors/%(flavorid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/flavors/%(flavorid)s",
"rel": "bookmark"
}
],
"name": "m1.small.description",
"os-flavor-access:is_public": true,
"ram": 2048,
"swap": 0,
"vcpus": 1,
"rxtx_factor": 1.0,
"description": "test description",
"extra_specs": {
"key1": "value1",
"key2": "value2"
}
}
]
}

View File

@ -0,0 +1,109 @@
{
"flavors": [
{
"description": null,
"id": "1",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny"
},
{
"description": null,
"id": "2",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/2",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/2",
"rel": "bookmark"
}
],
"name": "m1.small"
},
{
"description": null,
"id": "3",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/3",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/3",
"rel": "bookmark"
}
],
"name": "m1.medium"
},
{
"description": null,
"id": "4",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/4",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/4",
"rel": "bookmark"
}
],
"name": "m1.large"
},
{
"description": null,
"id": "5",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/5",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/5",
"rel": "bookmark"
}
],
"name": "m1.xlarge"
},
{
"description": null,
"id": "6",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/6",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/6",
"rel": "bookmark"
}
],
"name": "m1.tiny.specs"
},
{
"description": "test description",
"id": "7",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/7",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7",
"rel": "bookmark"
}
],
"name": "m1.small.description"
}
]
}

View File

@ -0,0 +1,89 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "us-west",
"OS-EXT-SRV-ATTR:host": "compute",
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "%(strtime)s",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"addresses": {
"private": [
{
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
"version": 4
}
]
},
"adminPass": "seekr3t",
"config_drive": "",
"created": "%(isotime)s",
"description": null,
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
"host_status": "UP",
"id": "%(id)s",
"image": {
"id": "%(uuid)s",
"links": [
{
"href": "%(compute_endpoint)s/images/%(uuid)s",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(id)s",
"rel": "bookmark"
}
],
"locked": false,
"locked_reason": null,
"metadata": {
"meta_var": "meta_val"
},
"name": "foobar",
"os-extended-volumes:volumes_attached": [],
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"server_groups": [],
"status": "ACTIVE",
"tags": [],
"tenant_id": "6f70656e737461636b20342065766572",
"trusted_image_certificates": null,
"updated": "%(isotime)s",
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
"user_id": "fake"
}
}

View File

@ -0,0 +1,14 @@
{
"rebuild" : {
"accessIPv4" : "%(access_ip_v4)s",
"accessIPv6" : "%(access_ip_v6)s",
"OS-DCF:diskConfig": "AUTO",
"imageRef" : "%(uuid)s",
"name" : "%(name)s",
"adminPass" : "%(pass)s",
"metadata" : {
"meta_var" : "meta_val"
},
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}

View File

@ -0,0 +1,9 @@
{
"server": {
"accessIPv4": "%(access_ip_v4)s",
"accessIPv6": "%(access_ip_v6)s",
"OS-DCF:diskConfig": "AUTO",
"name": "new-server-test",
"description": "Sample description"
}
}

View File

@ -0,0 +1,88 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "us-west",
"OS-EXT-SRV-ATTR:host": "compute",
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "%(strtime)s",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "%(access_ip_v4)s",
"accessIPv6": "%(access_ip_v6)s",
"addresses": {
"private": [
{
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
"version": 4
}
]
},
"config_drive": "",
"created": "%(isotime)s",
"description": "Sample description",
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "%(hostid)s",
"host_status": "UP",
"id": "%(id)s",
"image": {
"id": "%(uuid)s",
"links": [
{
"href": "%(compute_endpoint)s/images/%(uuid)s",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(id)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(id)s",
"rel": "bookmark"
}
],
"locked": false,
"locked_reason": null,
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"os-extended-volumes:volumes_attached": [],
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"server_groups": [],
"status": "ACTIVE",
"tags": [],
"tenant_id": "6f70656e737461636b20342065766572",
"trusted_image_certificates": null,
"updated": "%(isotime)s",
"user_id": "fake"
}
}

View File

@ -46,3 +46,8 @@ class FlavorManageSampleJsonTests2_55(FlavorManageSampleJsonTests):
def test_update_flavor_description(self):
response = self._do_put("flavors/1", "flavor-update-req", {})
self._verify_response("flavor-update-resp", {}, response, 200)
class FlavorManageSampleJsonTests2_75(FlavorManageSampleJsonTests2_55):
microversion = '2.75'
scenarios = [('v2_75', {'api_major_version': 'v2.1'})]

View File

@ -125,3 +125,11 @@ class FlavorsSampleJsonTest2_61(FlavorsSampleJsonTest):
new_flavor.create()
self.flavor_show_id = new_flavor_id
self.subs = {'flavorid': new_flavor_id}
class FlavorsSampleJsonTest2_75(FlavorsSampleJsonTest2_61):
microversion = '2.75'
scenarios = [('v2_75', {'api_major_version': 'v2.1'})]
def test_flavors_list(self):
pass

View File

@ -623,6 +623,29 @@ class ServersUpdateSampleJson247Test(ServersUpdateSampleJsonTest):
scenarios = [('v2_47', {'api_major_version': 'v2.1'})]
class ServersSampleJson275Test(ServersUpdateSampleJsonTest):
microversion = '2.75'
scenarios = [('v2_75', {'api_major_version': 'v2.1'})]
def test_server_rebuild(self):
uuid = self._post_server()
image = fake.get_valid_image_id()
params = {
'uuid': image,
'name': 'foobar',
'pass': 'seekr3t',
'hostid': '[a-f0-9]+',
'access_ip_v4': '1.2.3.4',
'access_ip_v6': '80fe::',
}
resp = self._do_post('servers/%s/action' % uuid,
'server-action-rebuild', params)
subs = params.copy()
del subs['uuid']
self._verify_response('server-action-rebuild-resp', subs, resp, 202)
class ServerSortKeysJsonTests(ServersSampleBase):
sample_dir = 'servers-sort'

View File

@ -78,6 +78,7 @@ def fake_agent_build_create(context, values):
class AgentsTestV21(test.NoDBTestCase):
controller = agents_v21.AgentController()
validation_error = exception.ValidationError
microversion = '2.1'
def setUp(self):
super(AgentsTestV21, self).setUp()
@ -93,7 +94,7 @@ class AgentsTestV21(test.NoDBTestCase):
self.req = self._get_http_request()
def _get_http_request(self):
return fakes.HTTPRequest.blank('')
return fakes.HTTPRequest.blank('', version=self.microversion)
def test_agents_create(self):
body = {'agent': {'hypervisor': 'kvm',
@ -210,7 +211,8 @@ class AgentsTestV21(test.NoDBTestCase):
def _test_agents_list(self, query_string=None):
req = fakes.HTTPRequest.blank('', use_admin_context=True,
query_string=query_string)
query_string=query_string,
version=self.microversion)
res_dict = self.controller.index(req)
agents_list = [{'hypervisor': 'kvm', 'os': 'win',
'architecture': 'x86',
@ -244,7 +246,8 @@ class AgentsTestV21(test.NoDBTestCase):
def test_agents_list_with_hypervisor(self):
req = fakes.HTTPRequest.blank('', use_admin_context=True,
query_string='hypervisor=kvm')
query_string='hypervisor=kvm',
version=self.microversion)
res_dict = self.controller.index(req)
response = [{'hypervisor': 'kvm', 'os': 'win',
'architecture': 'x86',
@ -264,7 +267,8 @@ class AgentsTestV21(test.NoDBTestCase):
def test_agents_list_with_multi_hypervisor_filter(self):
query_string = 'hypervisor=xen&hypervisor=kvm'
req = fakes.HTTPRequest.blank('', use_admin_context=True,
query_string=query_string)
query_string=query_string,
version=self.microversion)
res_dict = self.controller.index(req)
response = [{'hypervisor': 'kvm', 'os': 'win',
'architecture': 'x86',
@ -283,13 +287,15 @@ class AgentsTestV21(test.NoDBTestCase):
def test_agents_list_query_allow_negative_int_as_string(self):
req = fakes.HTTPRequest.blank('', use_admin_context=True,
query_string='hypervisor=-1')
query_string='hypervisor=-1',
version=self.microversion)
res_dict = self.controller.index(req)
self.assertEqual(res_dict, {'agents': []})
def test_agents_list_query_allow_int_as_string(self):
req = fakes.HTTPRequest.blank('', use_admin_context=True,
query_string='hypervisor=1')
query_string='hypervisor=1',
version=self.microversion)
res_dict = self.controller.index(req)
self.assertEqual(res_dict, {'agents': []})
@ -300,7 +306,8 @@ class AgentsTestV21(test.NoDBTestCase):
def test_agents_list_with_hypervisor_and_additional_filter(self):
req = fakes.HTTPRequest.blank(
'', use_admin_context=True,
query_string='hypervisor=kvm&additional_filter=abc')
query_string='hypervisor=kvm&additional_filter=abc',
version=self.microversion)
res_dict = self.controller.index(req)
response = [{'hypervisor': 'kvm', 'os': 'win',
'architecture': 'x86',
@ -397,6 +404,33 @@ class AgentsTestV21(test.NoDBTestCase):
self.controller.update, self.req, 1, body=body)
class AgentsTestV275(AgentsTestV21):
microversion = '2.75'
def test_agents_list_additional_filter_old_version(self):
req = fakes.HTTPRequest.blank(
'', use_admin_context=True,
query_string='additional_filter=abc',
version='2.74')
self.controller.index(req)
def test_agents_list_with_unknown_filter(self):
req = fakes.HTTPRequest.blank(
'', use_admin_context=True,
query_string='unknown_filter=abc',
version=self.microversion)
self.assertRaises(exception.ValidationError,
self.controller.index, req)
def test_agents_list_with_hypervisor_and_additional_filter(self):
req = fakes.HTTPRequest.blank(
'', use_admin_context=True,
query_string='hypervisor=kvm&additional_filter=abc',
version=self.microversion)
self.assertRaises(exception.ValidationError,
self.controller.index, req)
class AgentsPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self):

View File

@ -44,6 +44,20 @@ def fake_create(newflavor):
newflavor["disabled"] = False
def fake_create_without_swap(newflavor):
newflavor['flavorid'] = 1234
newflavor["name"] = 'test'
newflavor["memory_mb"] = 512
newflavor["vcpus"] = 2
newflavor["root_gb"] = 1
newflavor["ephemeral_gb"] = 1
newflavor["swap"] = 0
newflavor["rxtx_factor"] = 1.0
newflavor["is_public"] = True
newflavor["disabled"] = False
newflavor["extra_specs"] = {"key1": "value1"}
class FlavorManageTestV21(test.NoDBTestCase):
controller = flavormanage_v21.FlavorManageController()
validation_error = exception.ValidationError
@ -128,10 +142,11 @@ class FlavorManageTestV21(test.NoDBTestCase):
def test_create_missing_disk(self):
self._test_create_missing_parameter('disk')
def _create_flavor_success_case(self, body, req=None):
def _create_flavor_success_case(self, body, req=None, version=None):
req = req if req else self._get_http_request(url=self.base_url)
req.headers['Content-Type'] = 'application/json'
req.headers['X-OpenStack-Nova-API-Version'] = self.microversion
req.headers['X-OpenStack-Nova-API-Version'] = (
version or self.microversion)
req.method = 'POST'
req.body = jsonutils.dump_as_bytes(body)
res = req.get_response(self.app)
@ -466,6 +481,71 @@ class FlavorManageTestV2_61(FlavorManageTestV2_55):
self.assertEqual({"key1": "value1"}, flavor['extra_specs'])
class FlavorManageTestV2_75(FlavorManageTestV2_61):
microversion = '2.75'
FLAVOR_WITH_NO_SWAP = objects.Flavor(
name='test',
memory_mb=512,
vcpus=2,
root_gb=1,
ephemeral_gb=1,
flavorid=1234,
rxtx_factor=1.0,
disabled=False,
is_public=True,
swap=0,
extra_specs={"key1": "value1"}
)
def test_create_flavor_default_swap_value_old_version(self):
self.stub_out("nova.objects.Flavor.create", fake_create_without_swap)
del self.request_body['flavor']['swap']
resp = self._create_flavor_success_case(self.request_body,
version='2.74')
self.assertEqual(resp['flavor']['swap'], "")
@mock.patch('nova.objects.Flavor.get_by_flavor_id')
@mock.patch('nova.objects.Flavor.save')
def test_update_flavor_default_swap_value_old_version(self, mock_save,
mock_get):
self.stub_out("nova.objects.Flavor.create", fake_create_without_swap)
del self.request_body['flavor']['swap']
flavor = self._create_flavor_success_case(self.request_body,
version='2.74')['flavor']
mock_get.return_value = self.FLAVOR_WITH_NO_SWAP
req = fakes.HTTPRequest.blank(
'/fake/flavors',
version='2.74')
req.method = 'PUT'
response = self.controller._update(
req, flavor['id'],
body={'flavor': {'description': None}})['flavor']
self.assertEqual(response['swap'], '')
@mock.patch('nova.objects.FlavorList.get_all')
def test_create_flavor_default_swap_value(self, mock_get):
self.stub_out("nova.objects.Flavor.create", fake_create_without_swap)
del self.request_body['flavor']['swap']
resp = self._create_flavor_success_case(self.request_body)
self.assertEqual(resp['flavor']['swap'], 0)
@mock.patch('nova.objects.Flavor.get_by_flavor_id')
@mock.patch('nova.objects.Flavor.save')
def test_update_flavor_default_swap_value(self, mock_save, mock_get):
self.stub_out("nova.objects.Flavor.create", fake_create_without_swap)
del self.request_body['flavor']['swap']
mock_get.return_value = self.FLAVOR_WITH_NO_SWAP
flavor = self._create_flavor_success_case(self.request_body)['flavor']
req = fakes.HTTPRequest.blank(
'/fake/flavors',
version=self.microversion)
response = self.controller._update(
req, flavor['id'],
body={'flavor': {'description': None}})['flavor']
self.assertEqual(response['swap'], 0)
class PrivateFlavorManageTestV21(test.TestCase):
controller = flavormanage_v21.FlavorManageController()
base_url = '/v2/fake/flavors'

View File

@ -619,7 +619,7 @@ class FlavorsTestV21(test.TestCase):
'/flavors/detail', expected)
def _test_list_flavors_with_allowed_filter(
self, url, expected=None):
self, url, expected=None, req=None):
controller_list = self.controller.index
if 'detail' in url:
controller_list = self.controller.detail
@ -647,7 +647,7 @@ class FlavorsTestV21(test.TestCase):
if 'detail' in url and self.expect_extra_specs:
expected_resp[0]['extra_specs'] = (
fakes.FLAVORS['2'].extra_specs)
req = self._build_request(url + '&limit=1&marker=1')
req = req or self._build_request(url + '&limit=1&marker=1')
result = controller_list(req)
self.assertEqual(expected_resp, result['flavors'])
@ -791,6 +791,110 @@ class FlavorsTestV2_61(FlavorsTestV2_55):
expect_extra_specs = True
class FlavorsTestV2_75(FlavorsTestV2_61):
microversion = '2.75'
FLAVOR_WITH_NO_SWAP = objects.Flavor(
id=1,
name='flavor 1',
memory_mb=256,
vcpus=1,
root_gb=10,
ephemeral_gb=20,
flavorid='1',
rxtx_factor=1.0,
vcpu_weight=None,
disabled=False,
is_public=True,
swap=0,
description=None,
extra_specs={"key1": "value1", "key2": "value2"}
)
def test_list_flavors_with_additional_filter_old_version(self):
req = self.fake_request.blank(
'/fake/flavors?limit=1&marker=1&additional=something',
version='2.74')
self._test_list_flavors_with_allowed_filter(
'/fake/flavors?limit=1&marker=1&additional=something', req=req)
def test_list_detail_flavors_with_additional_filter_old_version(self):
expected = {
"ram": fakes.FLAVORS['2'].memory_mb,
"disk": fakes.FLAVORS['2'].root_gb,
"vcpus": fakes.FLAVORS['2'].vcpus,
"os-flavor-access:is_public": True,
"rxtx_factor": '',
"OS-FLV-EXT-DATA:ephemeral": fakes.FLAVORS['2'].ephemeral_gb,
"OS-FLV-DISABLED:disabled": fakes.FLAVORS['2'].disabled,
"swap": fakes.FLAVORS['2'].swap
}
req = self.fake_request.blank(
'/fake/flavors?limit=1&marker=1&additional=something',
version='2.74')
self._test_list_flavors_with_allowed_filter(
'/fake/flavors/detail?limit=1&marker=1&additional=something',
expected, req=req)
def _test_list_flavors_with_additional_filter(self, url):
controller_list = self.controller.index
if 'detail' in url:
controller_list = self.controller.detail
req = self._build_request(url)
self.assertRaises(exception.ValidationError,
controller_list, req)
def test_list_flavors_with_additional_filter(self):
self._test_list_flavors_with_additional_filter(
'/flavors?limit=1&marker=1&additional=something')
def test_list_detail_flavors_with_additional_filter(self):
self._test_list_flavors_with_additional_filter(
'/flavors/detail?limit=1&marker=1&additional=something')
@mock.patch('nova.objects.FlavorList.get_all')
def test_list_flavor_detail_default_swap_value_old_version(self, mock_get):
mock_get.return_value = objects.FlavorList(
objects=[self.FLAVOR_WITH_NO_SWAP])
req = self.fake_request.blank(
'/fake/flavors/detail?limit=1',
version='2.74')
response = self.controller.detail(req)
response_list = response["flavors"]
self.assertEqual(response_list[0]['swap'], "")
@mock.patch('nova.objects.Flavor.get_by_flavor_id')
def test_show_flavor_default_swap_value_old_version(self, mock_get):
mock_get.return_value = self.FLAVOR_WITH_NO_SWAP
req = self.fake_request.blank(
'/fake/flavors/detail?limit=1',
version='2.74')
response = self.controller.show(req, 1)
response_list = response["flavor"]
self.assertEqual(response_list['swap'], "")
@mock.patch('nova.objects.FlavorList.get_all')
def test_list_flavor_detail_default_swap_value(self, mock_get):
mock_get.return_value = objects.FlavorList(
objects=[self.FLAVOR_WITH_NO_SWAP])
req = self.fake_request.blank(
'/fake/flavors/detail?limit=1',
version=self.microversion)
response = self.controller.detail(req)
response_list = response["flavors"]
self.assertEqual(response_list[0]['swap'], 0)
@mock.patch('nova.objects.Flavor.get_by_flavor_id')
def test_show_flavor_default_swap_value(self, mock_get):
mock_get.return_value = self.FLAVOR_WITH_NO_SWAP
req = self.fake_request.blank(
'/fake/flavors/detail?limit=1',
version=self.microversion)
response = self.controller.show(req, 1)
response_list = response["flavor"]
self.assertEqual(response_list['swap'], 0)
class DisabledFlavorsWithRealDBTestV21(test.TestCase):
"""Tests that disabled flavors should not be shown nor listed."""
Controller = flavors_v21.FlavorsController

View File

@ -1387,3 +1387,53 @@ class HypervisorsTestV253(HypervisorsTestV252):
uuids.hyper1)
self.assertRaises(exception.ValidationError,
self.controller.show, req, uuids.hyper1)
class HypervisorsTestV275(HypervisorsTestV253):
api_version = '2.75'
def _test_servers_with_no_server(self, func, version=None, **kwargs):
"""Tests GET APIs return 'servers' field in response even
no servers on hypervisors.
"""
with mock.patch.object(self.controller.host_api,
'instance_get_all_by_host',
return_value=[]):
req = fakes.HTTPRequest.blank('/os-hypervisors?with_servers=1',
use_admin_context=True,
version=version or self.api_version)
result = func(req, **kwargs)
return result
def test_list_servers_with_no_server_old_version(self):
result = self._test_servers_with_no_server(self.controller.index,
version='2.74')
for hyper in result['hypervisors']:
self.assertNotIn('servers', hyper)
def test_list_detail_servers_with_no_server_old_version(self):
result = self._test_servers_with_no_server(self.controller.detail,
version='2.74')
for hyper in result['hypervisors']:
self.assertNotIn('servers', hyper)
def test_show_servers_with_no_server_old_version(self):
result = self._test_servers_with_no_server(self.controller.show,
version='2.74',
id=uuids.hyper1)
self.assertNotIn('servers', result['hypervisor'])
def test_servers_with_no_server(self):
result = self._test_servers_with_no_server(self.controller.index)
for hyper in result['hypervisors']:
self.assertEqual(0, len(hyper['servers']))
def test_list_detail_servers_with_empty_server_list(self):
result = self._test_servers_with_no_server(self.controller.detail)
for hyper in result['hypervisors']:
self.assertEqual(0, len(hyper['servers']))
def test_show_servers_with_empty_server_list(self):
result = self._test_servers_with_no_server(self.controller.show,
id=uuids.hyper1)
self.assertEqual(0, len(result['hypervisor']['servers']))

View File

@ -605,3 +605,43 @@ class KeypairsTestV235(test.TestCase):
mock_kp_get.assert_called_once_with(
req.environ['nova.context'], 'fake_user',
limit=None, marker=None)
class KeypairsTestV275(test.TestCase):
def setUp(self):
super(KeypairsTestV275, self).setUp()
self.controller = keypairs_v21.KeypairController()
@mock.patch("nova.db.api.key_pair_get_all_by_user")
@mock.patch('nova.objects.KeyPair.get_by_name')
def test_keypair_list_additional_param_old_version(self, mock_get_by_name,
mock_kp_get):
req = fakes.HTTPRequest.blank(
'/os-keypairs?unknown=3',
version='2.74', use_admin_context=True)
self.controller.index(req)
self.controller.show(req, 1)
with mock.patch.object(self.controller.api,
'delete_key_pair'):
self.controller.delete(req, 1)
def test_keypair_list_additional_param(self):
req = fakes.HTTPRequest.blank(
'/os-keypairs?unknown=3',
version='2.75', use_admin_context=True)
self.assertRaises(exception.ValidationError, self.controller.index,
req)
def test_keypair_show_additional_param(self):
req = fakes.HTTPRequest.blank(
'/os-keypairs?unknown=3',
version='2.75', use_admin_context=True)
self.assertRaises(exception.ValidationError, self.controller.show,
req, 1)
def test_keypair_delete_additional_param(self):
req = fakes.HTTPRequest.blank(
'/os-keypairs?unknown=3',
version='2.75', use_admin_context=True)
self.assertRaises(exception.ValidationError, self.controller.delete,
req, 1)

View File

@ -492,3 +492,32 @@ class LimitsControllerTestV239(BaseLimitTestSuite):
},
}
self.assertEqual(expected_response, response)
class LimitsControllerTestV275(BaseLimitTestSuite):
def setUp(self):
super(LimitsControllerTestV275, self).setUp()
self.controller = limits_v21.LimitsController()
def test_index_additional_query_param_old_version(self):
absolute_limits = {
"metadata_items": 1,
}
req = fakes.HTTPRequest.blank("/?unkown=fake",
version='2.74')
def _get_project_quotas(context, project_id, usages=True):
return {k: dict(limit=v, in_use=v // 2)
for k, v in absolute_limits.items()}
with mock.patch('nova.quota.QUOTAS.get_project_quotas') as \
get_project_quotas:
get_project_quotas.side_effect = _get_project_quotas
self.controller.index(req)
def test_index_additional_query_param(self):
req = fakes.HTTPRequest.blank("/?unkown=fake",
version='2.75')
self.assertRaises(
exception.ValidationError,
self.controller.index, req=req)

View File

@ -675,3 +675,53 @@ class QuotaSetsTestV257(QuotaSetsTestV236):
def setUp(self):
super(QuotaSetsTestV257, self).setUp()
self.filtered_quotas.extend(quotas_v21.FILTERED_QUOTAS_2_57)
class QuotaSetsTestV275(QuotaSetsTestV257):
microversion = '2.75'
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
@mock.patch('nova.objects.Quotas.create_limit')
@mock.patch('nova.quota.QUOTAS.get_settable_quotas')
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
def test_quota_additional_filter_older_version(self, mock_quotas,
mock_settable,
mock_create_limit,
mock_destroy):
mock_quotas.return_value = self.quotas
mock_settable.return_value = {'cores': {'maximum': -1, 'minimum': 0}}
query_string = 'additional_filter=2'
req = fakes.HTTPRequest.blank('', version='2.74',
query_string=query_string)
self.controller.show(req, 1234)
self.controller.update(req, 1234, body={'quota_set': {}})
self.controller.detail(req, 1234)
self.controller.delete(req, 1234)
def test_quota_update_additional_filter(self):
query_string = 'user_id=1&additional_filter=2'
req = fakes.HTTPRequest.blank('', version=self.microversion,
query_string=query_string)
self.assertRaises(exception.ValidationError, self.controller.update,
req, 'update_me', body={'quota_set': {}})
def test_quota_show_additional_filter(self):
query_string = 'user_id=1&additional_filter=2'
req = fakes.HTTPRequest.blank('', version=self.microversion,
query_string=query_string)
self.assertRaises(exception.ValidationError, self.controller.show,
req, 1234)
def test_quota_detail_additional_filter(self):
query_string = 'user_id=1&additional_filter=2'
req = fakes.HTTPRequest.blank('', version=self.microversion,
query_string=query_string)
self.assertRaises(exception.ValidationError, self.controller.detail,
req, 1234)
def test_quota_delete_additional_filter(self):
query_string = 'user_id=1&additional_filter=2'
req = fakes.HTTPRequest.blank('', version=self.microversion,
query_string=query_string)
self.assertRaises(exception.ValidationError, self.controller.delete,
req, 1234)

View File

@ -84,6 +84,7 @@ def server_group_db(sg):
class ServerGroupTestV21(test.NoDBTestCase):
USES_DB_SELF = True
validation_error = exception.ValidationError
wsgi_api_version = '2.1'
def setUp(self):
super(ServerGroupTestV21, self).setUp()
@ -306,7 +307,7 @@ class ServerGroupTestV21(test.NoDBTestCase):
mock_get_by_project.return_value = return_tenant_server_groups()
path = '/os-server-groups?all_projects=True'
path = path or '/os-server-groups?all_projects=True'
if limited:
path += limited
req = fakes.HTTPRequest.blank(path, version=api_version)
@ -548,16 +549,19 @@ class ServerGroupTestV21(test.NoDBTestCase):
self.controller.create, self.req, body=body)
def test_list_server_group_by_tenant(self):
self._test_list_server_group_by_tenant(api_version='2.1')
self._test_list_server_group_by_tenant(
api_version=self.wsgi_api_version)
def test_list_server_group_all_v20(self):
self._test_list_server_group_all(api_version='2.0')
def test_list_server_group_all(self):
self._test_list_server_group_all(api_version='2.1')
self._test_list_server_group_all(
api_version=self.wsgi_api_version)
def test_list_server_group_offset_and_limit(self):
self._test_list_server_group_offset_and_limit(api_version='2.1')
self._test_list_server_group_offset_and_limit(
api_version=self.wsgi_api_version)
def test_list_server_groups_rbac_default(self):
# test as admin
@ -567,59 +571,59 @@ class ServerGroupTestV21(test.NoDBTestCase):
self.controller.index(self.req)
def test_list_server_group_multiple_param(self):
self._test_list_server_group(api_version='2.1',
self._test_list_server_group(api_version=self.wsgi_api_version,
limited='&offset=2&limit=2&limit=1&offset=1',
path='/os-server-groups?all_projects=False&all_projects=True')
def test_list_server_group_additional_param(self):
self._test_list_server_group(api_version='2.1',
self._test_list_server_group(api_version=self.wsgi_api_version,
limited='&offset=1&limit=1',
path='/os-server-groups?dummy=False&all_projects=True')
def test_list_server_group_param_as_int(self):
self._test_list_server_group(api_version='2.1',
self._test_list_server_group(api_version=self.wsgi_api_version,
limited='&offset=1&limit=1',
path='/os-server-groups?all_projects=1')
def test_list_server_group_negative_int_as_offset(self):
self.assertRaises(exception.ValidationError,
self._test_list_server_group,
api_version='2.1',
api_version=self.wsgi_api_version,
limited='&offset=-1',
path='/os-server-groups?all_projects=1')
def test_list_server_group_string_int_as_offset(self):
self.assertRaises(exception.ValidationError,
self._test_list_server_group,
api_version='2.1',
api_version=self.wsgi_api_version,
limited='&offset=dummy',
path='/os-server-groups?all_projects=1')
def test_list_server_group_multiparam_string_as_offset(self):
self.assertRaises(exception.ValidationError,
self._test_list_server_group,
api_version='2.1',
api_version=self.wsgi_api_version,
limited='&offset=dummy&offset=1',
path='/os-server-groups?all_projects=1')
def test_list_server_group_negative_int_as_limit(self):
self.assertRaises(exception.ValidationError,
self._test_list_server_group,
api_version='2.1',
api_version=self.wsgi_api_version,
limited='&limit=-1',
path='/os-server-groups?all_projects=1')
def test_list_server_group_string_int_as_limit(self):
self.assertRaises(exception.ValidationError,
self._test_list_server_group,
api_version='2.1',
api_version=self.wsgi_api_version,
limited='&limit=dummy',
path='/os-server-groups?all_projects=1')
def test_list_server_group_multiparam_string_as_limit(self):
self.assertRaises(exception.ValidationError,
self._test_list_server_group,
api_version='2.1',
api_version=self.wsgi_api_version,
limited='&limit=dummy&limit=1',
path='/os-server-groups?all_projects=1')
@ -848,3 +852,18 @@ class ServerGroupTestV264(ServerGroupTestV213):
sgroup = server_group_template(unknown='unknown')
self.assertRaises(self.validation_error, self.controller.create,
req, body={'server_group': sgroup})
class ServerGroupTestV275(ServerGroupTestV264):
wsgi_api_version = '2.75'
def test_list_server_group_additional_param_old_version(self):
self._test_list_server_group(api_version='2.74',
limited='&offset=1&limit=1',
path='/os-server-groups?dummy=False&all_projects=True')
def test_list_server_group_additional_param(self):
req = fakes.HTTPRequest.blank('/os-server-groups?dummy=False',
version=self.wsgi_api_version)
self.assertRaises(self.validation_error, self.controller.index,
req)

View File

@ -15,6 +15,7 @@
# under the License.
import collections
import copy
import datetime
import ddt
@ -39,6 +40,7 @@ from nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack import compute
from nova.api.openstack.compute import ips
from nova.api.openstack.compute.schemas import servers as servers_schema
from nova.api.openstack.compute import servers
from nova.api.openstack.compute import views
from nova.api.openstack import wsgi as os_wsgi
@ -83,6 +85,23 @@ UUID2 = '00000000-0000-0000-0000-000000000002'
INSTANCE_IDS = {FAKE_UUID: 1}
FIELDS = instance_obj.INSTANCE_DEFAULT_FIELDS
GET_ONLY_FIELDS = ['OS-EXT-AZ:availability_zone', 'config_drive',
'OS-EXT-SRV-ATTR:host',
'OS-EXT-SRV-ATTR:hypervisor_hostname',
'OS-EXT-SRV-ATTR:instance_name',
'OS-EXT-SRV-ATTR:hostname',
'OS-EXT-SRV-ATTR:kernel_id',
'OS-EXT-SRV-ATTR:launch_index',
'OS-EXT-SRV-ATTR:ramdisk_id',
'OS-EXT-SRV-ATTR:reservation_id',
'OS-EXT-SRV-ATTR:root_device_name',
'OS-EXT-SRV-ATTR:user_data', 'host_status',
'key_name', 'OS-SRV-USG:launched_at',
'OS-SRV-USG:terminated_at',
'OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state',
'OS-EXT-STS:power_state', 'security_groups',
'os-extended-volumes:volumes_attached']
def instance_update_and_get_original(context, instance_uuid, values,
columns_to_join=None,
@ -2655,6 +2674,128 @@ class ServersControllerTestV273(ControllerTest):
cell_down_support=False, all_tenants=False)
class ServersControllerTestV275(ControllerTest):
wsgi_api_version = '2.75'
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
@mock.patch('nova.compute.api.API.get_all')
def test_get_servers_additional_query_param_old_version(self, mock_get):
req = fakes.HTTPRequest.blank('/fake/servers?unknown=1',
use_admin_context=True,
version='2.74')
self.controller.index(req)
@mock.patch('nova.compute.api.API.get_all')
def test_get_servers_ignore_sort_key_old_version(self, mock_get):
req = fakes.HTTPRequest.blank('/fake/servers?sort_key=deleted',
use_admin_context=True,
version='2.74')
self.controller.index(req)
def test_get_servers_additional_query_param(self):
req = fakes.HTTPRequest.blank('/fake/servers?unknown=1',
use_admin_context=True,
version=self.wsgi_api_version)
self.assertRaises(exception.ValidationError, self.controller.index,
req)
def test_get_servers_previously_ignored_sort_key(self):
for s_ignore in servers_schema.SERVER_LIST_IGNORE_SORT_KEY_V273:
req = fakes.HTTPRequest.blank(
'/fake/servers?sort_key=%s' % s_ignore,
use_admin_context=True,
version=self.wsgi_api_version)
self.assertRaises(exception.ValidationError, self.controller.index,
req)
def test_get_servers_additional_sort_key(self):
req = fakes.HTTPRequest.blank('/fake/servers?sort_key=unknown',
use_admin_context=True,
version=self.wsgi_api_version)
self.assertRaises(exception.ValidationError, self.controller.index,
req)
def test_update_response_no_show_server_only_attributes_old_version(self):
# There are some old server attributes which were added only for
# GET server APIs not for PUT. GET server and PUT server share the
# same view builder method SHOW() to build the response, So make sure
# attributes which are not supposed to be included for PUT
# response are not present.
body = {'server': {'name': 'server_test'}}
req = fakes.HTTPRequest.blank('/fake/servers?unknown=1',
use_admin_context=True,
version='2.74')
res_dict = self.controller.update(req, FAKE_UUID, body=body)
for field in GET_ONLY_FIELDS:
self.assertNotIn(field, res_dict['server'])
for items in res_dict['server']['addresses'].values():
for item in items:
self.assertNotIn('OS-EXT-IPS:type', item)
self.assertNotIn('OS-EXT-IPS-MAC:mac_addr', item)
def test_update_response_has_show_server_all_attributes(self):
body = {'server': {'name': 'server_test'}}
req = fakes.HTTPRequest.blank('/fake/servers?unknown=1',
use_admin_context=True,
version=self.wsgi_api_version)
res_dict = self.controller.update(req, FAKE_UUID, body=body)
for field in GET_ONLY_FIELDS:
self.assertIn(field, res_dict['server'])
for items in res_dict['server']['addresses'].values():
for item in items:
self.assertIn('OS-EXT-IPS:type', item)
self.assertIn('OS-EXT-IPS-MAC:mac_addr', item)
def test_rebuild_response_no_show_server_only_attributes_old_version(self):
# There are some old server attributes which were added only for
# GET server APIs not for Rebuild. GET server and Rebuild server share
# same view builder method SHOW() to build the response, So make sure
# the attributes which are not supposed to be included for Rebuild
# response are not present.
body = {'rebuild': {"imageRef": self.image_uuid}}
req = fakes.HTTPRequest.blank('/fake/servers?unknown=1',
use_admin_context=True,
version='2.74')
fake_get = fakes.fake_compute_get(
vm_state=vm_states.ACTIVE,
project_id=req.environ['nova.context'].project_id,
user_id=req.environ['nova.context'].user_id)
self.mock_get.side_effect = fake_get
res_dict = self.controller._action_rebuild(req, FAKE_UUID,
body=body).obj
get_only_fields_Rebuild = copy.deepcopy(GET_ONLY_FIELDS)
get_only_fields_Rebuild.remove('key_name')
for field in get_only_fields_Rebuild:
self.assertNotIn(field, res_dict['server'])
for items in res_dict['server']['addresses'].values():
for item in items:
self.assertNotIn('OS-EXT-IPS:type', item)
self.assertNotIn('OS-EXT-IPS-MAC:mac_addr', item)
def test_rebuild_response_has_show_server_all_attributes(self):
body = {'rebuild': {"imageRef": self.image_uuid}}
req = fakes.HTTPRequest.blank('/fake/servers?unknown=1',
use_admin_context=True,
version=self.wsgi_api_version)
fake_get = fakes.fake_compute_get(
vm_state=vm_states.ACTIVE,
project_id=req.environ['nova.context'].project_id,
user_id=req.environ['nova.context'].user_id)
self.mock_get.side_effect = fake_get
res_dict = self.controller._action_rebuild(req, FAKE_UUID,
body=body).obj
for field in GET_ONLY_FIELDS:
if field == 'OS-EXT-SRV-ATTR:user_data':
self.assertNotIn(field, res_dict['server'])
field = 'user_data'
self.assertIn(field, res_dict['server'])
for items in res_dict['server']['addresses'].values():
for item in items:
self.assertIn('OS-EXT-IPS:type', item)
self.assertIn('OS-EXT-IPS-MAC:mac_addr', item)
class ServersControllerDeleteTest(ControllerTest):
def setUp(self):
@ -3007,22 +3148,9 @@ class ServersControllerRebuildInstanceTest(ControllerTest):
body = self.controller._action_rebuild(self.req, FAKE_UUID,
body=body).obj
get_only_fields = ['OS-EXT-AZ:availability_zone', 'config_drive',
'OS-EXT-SRV-ATTR:host',
'OS-EXT-SRV-ATTR:hypervisor_hostname',
'OS-EXT-SRV-ATTR:instance_name',
'OS-EXT-SRV-ATTR:hostname'
'OS-EXT-SRV-ATTR:kernel_id',
'OS-EXT-SRV-ATTR:launch_index',
'OS-EXT-SRV-ATTR:ramdisk_id',
'OS-EXT-SRV-ATTR:reservation_id',
'OS-EXT-SRV-ATTR:root_device_name',
'OS-EXT-SRV-ATTR:user_data', 'host_status',
'OS-SRV-USG:launched_at',
'OS-SRV-USG:terminated_at']
if not self.expected_key_name:
get_only_fields.append('key_name')
get_only_fields = copy.deepcopy(GET_ONLY_FIELDS)
if self.expected_key_name:
get_only_fields.remove('key_name')
for field in get_only_fields:
self.assertNotIn(field, body['server'])
@ -3637,20 +3765,7 @@ class ServersControllerUpdateTest(ControllerTest):
body = {'server': {'name': 'server_test'}}
req = self._get_request(body)
res_dict = self.controller.update(req, FAKE_UUID, body=body)
get_only_fields = ['OS-EXT-AZ:availability_zone', 'config_drive',
'OS-EXT-SRV-ATTR:host',
'OS-EXT-SRV-ATTR:hypervisor_hostname',
'OS-EXT-SRV-ATTR:instance_name',
'OS-EXT-SRV-ATTR:hostname'
'OS-EXT-SRV-ATTR:kernel_id',
'OS-EXT-SRV-ATTR:launch_index',
'OS-EXT-SRV-ATTR:ramdisk_id',
'OS-EXT-SRV-ATTR:reservation_id',
'OS-EXT-SRV-ATTR:root_device_name',
'OS-EXT-SRV-ATTR:user_data', 'host_status',
'key_name', 'OS-SRV-USG:launched_at',
'OS-SRV-USG:terminated_at']
for field in get_only_fields:
for field in GET_ONLY_FIELDS:
self.assertNotIn(field, res_dict['server'])
def test_update_server_name_too_long(self):

View File

@ -1323,6 +1323,27 @@ class ServicesTestV253(test.TestCase):
six.text_type(ex))
class ServicesTestV275(test.TestCase):
wsgi_api_version = '2.75'
def setUp(self):
super(ServicesTestV275, self).setUp()
self.controller = services_v21.ServiceController()
def test_services_list_with_additional_filter_old_version(self):
url = '/fake/services?host=host1&binary=nova-compute&unknown=abc'
req = fakes.HTTPRequest.blank(url, use_admin_context=True,
version='2.74')
self.controller.index(req)
def test_services_list_with_additional_filter(self):
url = '/fake/services?host=host1&binary=nova-compute&unknown=abc'
req = fakes.HTTPRequest.blank(url, use_admin_context=True,
version=self.wsgi_api_version)
self.assertRaises(exception.ValidationError,
self.controller.index, req)
class ServicesPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self):

View File

@ -454,6 +454,38 @@ class SimpleTenantUsageTestV40(SimpleTenantUsageTestV21):
self._test_show_duplicate_query_parameters_validation(params)
class SimpleTenantUsageTestV2_75(SimpleTenantUsageTestV40):
version = '2.75'
def test_index_additional_query_param_old_version(self):
req = fakes.HTTPRequest.blank('?start=%s&end=%s&additional=1' %
(START.isoformat(), STOP.isoformat()),
version='2.74')
res = self.controller.index(req)
self.assertIn('tenant_usages', res)
def test_index_additional_query_parameters(self):
req = fakes.HTTPRequest.blank('?start=%s&end=%s&additional=1' %
(START.isoformat(), STOP.isoformat()),
version=self.version)
self.assertRaises(exception.ValidationError, self.controller.index,
req)
def test_show_additional_query_param_old_version(self):
req = fakes.HTTPRequest.blank('?start=%s&end=%s&additional=1' %
(START.isoformat(), STOP.isoformat()),
version='2.74')
res = self.controller.show(req, 1)
self.assertIn('tenant_usage', res)
def test_show_additional_query_parameters(self):
req = fakes.HTTPRequest.blank('?start=%s&end=%s&additional=1' %
(START.isoformat(), STOP.isoformat()),
version=self.version)
self.assertRaises(exception.ValidationError, self.controller.show,
req, 1)
class SimpleTenantUsageLimitsTestV21(test.TestCase):
version = '2.1'

View File

@ -26,6 +26,7 @@ from six.moves import urllib
import webob
from webob import exc
from nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack.compute import assisted_volume_snapshots \
as assisted_snaps_v21
@ -419,6 +420,8 @@ class VolumeApiTestV21(test.NoDBTestCase):
class VolumeAttachTestsV21(test.NoDBTestCase):
validation_error = exception.ValidationError
microversion = '2.1'
_prefix = '/servers/id/os-volume_attachments'
def setUp(self):
super(VolumeAttachTestsV21, self).setUp()
@ -436,12 +439,15 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
}}
self.attachments = volumes_v21.VolumeAttachmentController()
self.req = fakes.HTTPRequest.blank(
'/v2/servers/id/os-volume_attachments/uuid')
self.req = self._build_request('/uuid')
self.req.body = jsonutils.dump_as_bytes({})
self.req.headers['content-type'] = 'application/json'
self.req.environ['nova.context'] = self.context
def _build_request(self, url=''):
return fakes.HTTPRequest.blank(
self._prefix + url, version=self.microversion)
def test_show(self):
result = self.attachments.show(self.req, FAKE_UUID, FAKE_UUID_A)
self.assertEqual(self.expected_show, result)
@ -638,10 +644,12 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
'device': '/dev/fake'}}
self.assertRaises(webob.exc.HTTPConflict, self.attachments.create,
self.req, FAKE_UUID, body=body)
supports_multiattach = api_version_request.is_supported(
self.req, '2.60')
mock_attach_volume.assert_called_once_with(
self.req.environ['nova.context'],
test.MatchType(objects.Instance), FAKE_UUID_A, '/dev/fake',
supports_multiattach=False, tag=None)
supports_multiattach=supports_multiattach, tag=None)
def test_attach_volume_bad_id(self):
self.stub_out('nova.compute.api.API.attach_volume',
@ -701,7 +709,7 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
body = {'volumeAttachment': {'volumeId': FAKE_UUID_A,
'device': '/dev/fake'}}
req = fakes.HTTPRequest.blank('/v2/servers/id/os-volume_attachments')
req = self._build_request()
req.method = 'POST'
req.body = jsonutils.dump_as_bytes({})
req.headers['content-type'] = 'application/json'
@ -796,8 +804,7 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
'id': FAKE_UUID_B})
def _test_list_with_invalid_filter(self, url):
prefix = '/servers/id/os-volume_attachments'
req = fakes.HTTPRequest.blank(prefix + url)
req = self._build_request(url)
self.assertRaises(exception.ValidationError,
self.attachments.index,
req,
@ -833,8 +840,7 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
'offset': 1
}
for param, value in params.items():
req = fakes.HTTPRequest.blank(
'/servers/id/os-volume_attachments' + '?%s=%s&%s=%s' %
req = self._build_request('?%s=%s&%s=%s' %
(param, value, param, value))
self.attachments.index(req, FAKE_UUID)
@ -843,8 +849,8 @@ class VolumeAttachTestsV21(test.NoDBTestCase):
def test_list_with_additional_filter(self, mock_get):
fake_bdms = objects.BlockDeviceMappingList()
mock_get.return_value = fake_bdms
req = fakes.HTTPRequest.blank(
'/servers/id/os-volume_attachments?limit=1&additional=something')
req = self._build_request(
'?limit=1&additional=something')
self.attachments.index(req, FAKE_UUID)
@ -957,6 +963,36 @@ class VolumeAttachTestsV260(test.NoDBTestCase):
'shelved-offloaded instances.', six.text_type(ex))
class VolumeAttachTestsV2_75(VolumeAttachTestsV21):
microversion = '2.75'
def setUp(self):
super(VolumeAttachTestsV2_75, self).setUp()
self.expected_show = {'volumeAttachment':
{'device': '/dev/fake0',
'serverId': FAKE_UUID,
'id': FAKE_UUID_A,
'volumeId': FAKE_UUID_A,
'tag': None,
}}
@mock.patch.object(objects.BlockDeviceMappingList,
'get_by_instance_uuid')
def test_list_with_additional_filter_old_version(self, mock_get):
fake_bdms = objects.BlockDeviceMappingList()
mock_get.return_value = fake_bdms
req = fakes.HTTPRequest.blank(
'/os-volumes?limit=1&offset=1&additional=something',
version='2.74')
self.attachments.index(req, FAKE_UUID)
def test_list_with_additional_filter(self):
req = self._build_request(
'?limit=1&additional=something')
self.assertRaises(self.validation_error, self.attachments.index,
req, FAKE_UUID)
class SwapVolumeMultiattachTestCase(test.NoDBTestCase):
@mock.patch('nova.api.openstack.common.get_instance')
@ -1185,6 +1221,7 @@ class AssistedSnapshotCreateTestCaseV21(test.NoDBTestCase):
class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
assisted_snaps = assisted_snaps_v21
microversion = '2.1'
def _check_status(self, expected_status, res, controller_method):
self.assertEqual(expected_status, controller_method.wsgi_code)
@ -1204,13 +1241,15 @@ class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
}
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?%s' %
urllib.parse.urlencode(params))
urllib.parse.urlencode(params),
version=self.microversion)
req.method = 'DELETE'
result = self.controller.delete(req, '5')
self._check_status(204, result, self.controller.delete)
def test_assisted_delete_missing_delete_info(self):
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots')
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots',
version=self.microversion)
req.method = 'DELETE'
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete,
req, '5')
@ -1223,7 +1262,8 @@ class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
}
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?%s' %
urllib.parse.urlencode(params))
urllib.parse.urlencode(params),
version=self.microversion)
req.method = 'DELETE'
with mock.patch.object(compute_api.API, 'volume_snapshot_delete',
side_effect=api_error):
@ -1248,7 +1288,8 @@ class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
}
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?%s' %
urllib.parse.urlencode(params))
urllib.parse.urlencode(params),
version=self.microversion)
req.method = 'DELETE'
self.controller.delete(req, '5')
@ -1259,7 +1300,8 @@ class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
}
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?%s' %
urllib.parse.urlencode(params))
urllib.parse.urlencode(params),
version=self.microversion)
req.method = 'DELETE'
self.controller.delete(req, '5')
@ -1269,7 +1311,8 @@ class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
}
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?%s' %
urllib.parse.urlencode(params))
urllib.parse.urlencode(params),
version=self.microversion)
req.method = 'DELETE'
ex = self.assertRaises(webob.exc.HTTPBadRequest,
@ -1279,6 +1322,29 @@ class AssistedSnapshotDeleteTestCaseV21(test.NoDBTestCase):
self.assertIn('volume_id', six.text_type(ex))
class AssistedSnapshotDeleteTestCaseV275(AssistedSnapshotDeleteTestCaseV21):
assisted_snaps = assisted_snaps_v21
microversion = '2.75'
def test_delete_additional_query_parameters_old_version(self):
params = {
'delete_info': jsonutils.dumps({'volume_id': '1'}),
'additional': 123
}
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?%s' %
urllib.parse.urlencode(params),
version='2.74')
self.controller.delete(req, 1)
def test_delete_additional_query_parameters(self):
req = fakes.HTTPRequest.blank(
'/v2/fake/os-assisted-volume-snapshots?unknown=1',
version=self.microversion)
self.assertRaises(exception.ValidationError,
self.controller.delete, req, 1)
class TestAssistedVolumeSnapshotsPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self):

View File

@ -0,0 +1,17 @@
---
features:
- |
Multiple API cleanups is done in API microversion 2.75:
* 400 for unknown param for query param and for request body.
* Making server representation always consistent among GET, PUT
and Rebuild serevr APIs response. ``PUT /servers/{server_id}``
and ``POST /servers/{server_id}/action {rebuild}`` API response
is modified to add all the missing fields which are return
by ``GET /servers/{server_id}``.
* Change the default return value of swap field from the empty
string to 0 (integer) in flavor APIs.
* Return ``servers`` field always in the response of GET
hypervisors API even there are no servers on hypervisor.