diff --git a/api-ref/source/flavors.inc b/api-ref/source/flavors.inc index dab61b2a97..0216ce2983 100644 --- a/api-ref/source/flavors.inc +++ b/api-ref/source/flavors.inc @@ -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 diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 8626913169..cb91c6acd0 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -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 `_: + + :: + + 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 `_: + + :: + + 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 diff --git a/api-ref/source/servers-actions.inc b/api-ref/source/servers-actions.inc index 4e162d2cf4..d118073d40 100644 --- a/api-ref/source/servers-actions.inc +++ b/api-ref/source/servers-actions.inc @@ -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) diff --git a/api-ref/source/servers.inc b/api-ref/source/servers.inc index d7779b7acd..2aa31f0b6f 100644 --- a/api-ref/source/servers.inc +++ b/api-ref/source/servers.inc @@ -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 diff --git a/doc/api_samples/flavor-manage/v2.75/flavor-create-post-req.json b/doc/api_samples/flavor-manage/v2.75/flavor-create-post-req.json new file mode 100644 index 0000000000..0d9926d720 --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.75/flavor-create-post-req.json @@ -0,0 +1,11 @@ +{ + "flavor": { + "name": "test_flavor", + "ram": 1024, + "vcpus": 2, + "disk": 10, + "id": "10", + "rxtx_factor": 2.0, + "description": "test description" + } +} diff --git a/doc/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json b/doc/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json new file mode 100644 index 0000000000..49dfd0c082 --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json @@ -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": {} + } +} diff --git a/doc/api_samples/flavor-manage/v2.75/flavor-update-req.json b/doc/api_samples/flavor-manage/v2.75/flavor-update-req.json new file mode 100644 index 0000000000..93c8e1e8ab --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.75/flavor-update-req.json @@ -0,0 +1,5 @@ +{ + "flavor": { + "description": "updated description" + } +} diff --git a/doc/api_samples/flavor-manage/v2.75/flavor-update-resp.json b/doc/api_samples/flavor-manage/v2.75/flavor-update-resp.json new file mode 100644 index 0000000000..4e92b10582 --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.75/flavor-update-resp.json @@ -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": {} + } +} diff --git a/doc/api_samples/flavors/v2.75/flavor-get-resp.json b/doc/api_samples/flavors/v2.75/flavor-get-resp.json new file mode 100644 index 0000000000..e5581409f9 --- /dev/null +++ b/doc/api_samples/flavors/v2.75/flavor-get-resp.json @@ -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" + } + } +} diff --git a/doc/api_samples/flavors/v2.75/flavors-detail-resp.json b/doc/api_samples/flavors/v2.75/flavors-detail-resp.json new file mode 100644 index 0000000000..58ca0e44ba --- /dev/null +++ b/doc/api_samples/flavors/v2.75/flavors-detail-resp.json @@ -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" + } + } + ] +} diff --git a/doc/api_samples/flavors/v2.75/flavors-list-resp.json b/doc/api_samples/flavors/v2.75/flavors-list-resp.json new file mode 100644 index 0000000000..f368ed5c66 --- /dev/null +++ b/doc/api_samples/flavors/v2.75/flavors-list-resp.json @@ -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" + } + ] +} diff --git a/doc/api_samples/servers/v2.75/server-action-rebuild-resp.json b/doc/api_samples/servers/v2.75/server-action-rebuild-resp.json new file mode 100644 index 0000000000..c50a17834f --- /dev/null +++ b/doc/api_samples/servers/v2.75/server-action-rebuild-resp.json @@ -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" + } +} diff --git a/doc/api_samples/servers/v2.75/server-action-rebuild.json b/doc/api_samples/servers/v2.75/server-action-rebuild.json new file mode 100644 index 0000000000..f1431a0506 --- /dev/null +++ b/doc/api_samples/servers/v2.75/server-action-rebuild.json @@ -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" + } +} \ No newline at end of file diff --git a/doc/api_samples/servers/v2.75/server-update-req.json b/doc/api_samples/servers/v2.75/server-update-req.json new file mode 100644 index 0000000000..1341355ce5 --- /dev/null +++ b/doc/api_samples/servers/v2.75/server-update-req.json @@ -0,0 +1,9 @@ +{ + "server": { + "accessIPv4": "1.2.3.4", + "accessIPv6": "80fe::", + "OS-DCF:diskConfig": "AUTO", + "name": "new-server-test", + "description": "Sample description" + } +} diff --git a/doc/api_samples/servers/v2.75/server-update-resp.json b/doc/api_samples/servers/v2.75/server-update-resp.json new file mode 100644 index 0000000000..5040ae6c72 --- /dev/null +++ b/doc/api_samples/servers/v2.75/server-update-resp.json @@ -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" + } +} diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index 48e874375b..ddcb8b65d8 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.74", + "version": "2.75", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index f520e6099e..2df2d3c69a 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.74", + "version": "2.75", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index f60fc77f09..b3ddea6f5d 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -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 diff --git a/nova/api/openstack/compute/agents.py b/nova/api/openstack/compute/agents.py index 0abcef8e16..c2578126c3 100644 --- a/nova/api/openstack/compute/agents.py +++ b/nova/api/openstack/compute/agents.py @@ -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.""" diff --git a/nova/api/openstack/compute/assisted_volume_snapshots.py b/nova/api/openstack/compute/assisted_volume_snapshots.py index 29039a09cc..10ae0790f6 100644 --- a/nova/api/openstack/compute/assisted_volume_snapshots.py +++ b/nova/api/openstack/compute/assisted_volume_snapshots.py @@ -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.""" diff --git a/nova/api/openstack/compute/flavors.py b/nova/api/openstack/compute/flavors.py index 8215a2e57e..3986e428b3 100644 --- a/nova/api/openstack/compute/flavors.py +++ b/nova/api/openstack/compute/flavors.py @@ -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.""" diff --git a/nova/api/openstack/compute/hypervisors.py b/nova/api/openstack/compute/hypervisors.py index a9184edb18..73ac906396 100644 --- a/nova/api/openstack/compute/hypervisors.py +++ b/nova/api/openstack/compute/hypervisors.py @@ -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): diff --git a/nova/api/openstack/compute/keypairs.py b/nova/api/openstack/compute/keypairs.py index b3dec117f7..ea80cae246 100644 --- a/nova/api/openstack/compute/keypairs.py +++ b/nova/api/openstack/compute/keypairs.py @@ -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) diff --git a/nova/api/openstack/compute/limits.py b/nova/api/openstack/compute/limits.py index 958bdf392c..9d28ffe70c 100644 --- a/nova/api/openstack/compute/limits.py +++ b/nova/api/openstack/compute/limits.py @@ -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) diff --git a/nova/api/openstack/compute/quota_sets.py b/nova/api/openstack/compute/quota_sets.py index 1112bf997a..f5db68b9d9 100644 --- a/nova/api/openstack/compute/quota_sets.py +++ b/nova/api/openstack/compute/quota_sets.py @@ -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'] diff --git a/nova/api/openstack/compute/rest_api_version_history.rst b/nova/api/openstack/compute/rest_api_version_history.rst index 55fe75a61f..ace773fdbc 100644 --- a/nova/api/openstack/compute/rest_api_version_history.rst +++ b/nova/api/openstack/compute/rest_api_version_history.rst @@ -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. diff --git a/nova/api/openstack/compute/schemas/agents.py b/nova/api/openstack/compute/schemas/agents.py index 3ef66c84eb..54add72d0f 100644 --- a/nova/api/openstack/compute/schemas/agents.py +++ b/nova/api/openstack/compute/schemas/agents.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/assisted_volume_snapshots.py b/nova/api/openstack/compute/schemas/assisted_volume_snapshots.py index 74e3da3434..0a13c50b11 100644 --- a/nova/api/openstack/compute/schemas/assisted_volume_snapshots.py +++ b/nova/api/openstack/compute/schemas/assisted_volume_snapshots.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/flavors.py b/nova/api/openstack/compute/schemas/flavors.py index 96a6f6b4e8..c6064f02ef 100644 --- a/nova/api/openstack/compute/schemas/flavors.py +++ b/nova/api/openstack/compute/schemas/flavors.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/hosts.py b/nova/api/openstack/compute/schemas/hosts.py index e708e8a1ca..983e428393 100644 --- a/nova/api/openstack/compute/schemas/hosts.py +++ b/nova/api/openstack/compute/schemas/hosts.py @@ -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 } diff --git a/nova/api/openstack/compute/schemas/keypairs.py b/nova/api/openstack/compute/schemas/keypairs.py index a3410eee18..7ebd3c7433 100644 --- a/nova/api/openstack/compute/schemas/keypairs.py +++ b/nova/api/openstack/compute/schemas/keypairs.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/limits.py b/nova/api/openstack/compute/schemas/limits.py index 6cd3da580b..e269cc55ab 100644 --- a/nova/api/openstack/compute/schemas/limits.py +++ b/nova/api/openstack/compute/schemas/limits.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/quota_sets.py b/nova/api/openstack/compute/schemas/quota_sets.py index 1942488ac2..fe45df74dc 100644 --- a/nova/api/openstack/compute/schemas/quota_sets.py +++ b/nova/api/openstack/compute/schemas/quota_sets.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/security_groups.py b/nova/api/openstack/compute/schemas/security_groups.py index 5e025b069a..8e0dc66171 100644 --- a/nova/api/openstack/compute/schemas/security_groups.py +++ b/nova/api/openstack/compute/schemas/security_groups.py @@ -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 } diff --git a/nova/api/openstack/compute/schemas/server_groups.py b/nova/api/openstack/compute/schemas/server_groups.py index d8401e1583..c5724bd687 100644 --- a/nova/api/openstack/compute/schemas/server_groups.py +++ b/nova/api/openstack/compute/schemas/server_groups.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/servers.py b/nova/api/openstack/compute/schemas/servers.py index 51887dcaae..ce1e2035df 100644 --- a/nova/api/openstack/compute/schemas/servers.py +++ b/nova/api/openstack/compute/schemas/servers.py @@ -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******* diff --git a/nova/api/openstack/compute/schemas/services.py b/nova/api/openstack/compute/schemas/services.py index 45cded092f..286403a8d1 100644 --- a/nova/api/openstack/compute/schemas/services.py +++ b/nova/api/openstack/compute/schemas/services.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/simple_tenant_usage.py b/nova/api/openstack/compute/schemas/simple_tenant_usage.py index c7e2a65723..7ac34416e6 100644 --- a/nova/api/openstack/compute/schemas/simple_tenant_usage.py +++ b/nova/api/openstack/compute/schemas/simple_tenant_usage.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/volumes.py b/nova/api/openstack/compute/schemas/volumes.py index 36bc1704b8..9a6a3d9b36 100644 --- a/nova/api/openstack/compute/schemas/volumes.py +++ b/nova/api/openstack/compute/schemas/volumes.py @@ -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 diff --git a/nova/api/openstack/compute/server_groups.py b/nova/api/openstack/compute/server_groups.py index 14fdd485f4..059103017e 100644 --- a/nova/api/openstack/compute/server_groups.py +++ b/nova/api/openstack/compute/server_groups.py @@ -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') diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index d633cc5489..06a54e1985 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -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 diff --git a/nova/api/openstack/compute/services.py b/nova/api/openstack/compute/services.py index 235fd9f61d..718e9e3574 100644 --- a/nova/api/openstack/compute/services.py +++ b/nova/api/openstack/compute/services.py @@ -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 diff --git a/nova/api/openstack/compute/simple_tenant_usage.py b/nova/api/openstack/compute/simple_tenant_usage.py index 7bcc084117..f1bef343f4 100644 --- a/nova/api/openstack/compute/simple_tenant_usage.py +++ b/nova/api/openstack/compute/simple_tenant_usage.py @@ -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.""" diff --git a/nova/api/openstack/compute/views/flavors.py b/nova/api/openstack/compute/views/flavors.py index 2c7e925f52..1df4f093ba 100644 --- a/nova/api/openstack/compute/views/flavors.py +++ b/nova/api/openstack/compute/views/flavors.py @@ -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): diff --git a/nova/api/openstack/compute/views/servers.py b/nova/api/openstack/compute/views/servers.py index 0d1328768e..087bc27091 100644 --- a/nova/api/openstack/compute/views/servers.py +++ b/nova/api/openstack/compute/views/servers.py @@ -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) diff --git a/nova/api/openstack/compute/volumes.py b/nova/api/openstack/compute/volumes.py index cfb38fb7a1..d593787bcb 100644 --- a/nova/api/openstack/compute/volumes.py +++ b/nova/api/openstack/compute/volumes.py @@ -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'] diff --git a/nova/api/validation/__init__.py b/nova/api/validation/__init__.py index 6f1d0b3475..289ca71f03 100644 --- a/nova/api/validation/__init__.py +++ b/nova/api/validation/__init__.py @@ -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 diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-req.json.tpl new file mode 100644 index 0000000000..2065b445cf --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-req.json.tpl @@ -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" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json.tpl new file mode 100644 index 0000000000..2ebb8f8360 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json.tpl @@ -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": {} + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-req.json.tpl new file mode 100644 index 0000000000..93c8e1e8ab --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-req.json.tpl @@ -0,0 +1,5 @@ +{ + "flavor": { + "description": "updated description" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-resp.json.tpl new file mode 100644 index 0000000000..4e92b10582 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-resp.json.tpl @@ -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": {} + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavor-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavor-get-resp.json.tpl new file mode 100644 index 0000000000..afa3eccbea --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavor-get-resp.json.tpl @@ -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" + } + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-detail-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-detail-resp.json.tpl new file mode 100644 index 0000000000..88bd02e2cc --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-detail-resp.json.tpl @@ -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" + } + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-list-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-list-resp.json.tpl new file mode 100644 index 0000000000..1c416099c4 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-list-resp.json.tpl @@ -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" + } + ] +} \ No newline at end of file diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild-resp.json.tpl new file mode 100644 index 0000000000..4a3ac19623 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild-resp.json.tpl @@ -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" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild.json.tpl new file mode 100644 index 0000000000..5b61faeed8 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild.json.tpl @@ -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" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-req.json.tpl new file mode 100644 index 0000000000..f1f436642f --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-req.json.tpl @@ -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" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-resp.json.tpl new file mode 100644 index 0000000000..920cd6e324 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-resp.json.tpl @@ -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" + } +} diff --git a/nova/tests/functional/api_sample_tests/test_flavor_manage.py b/nova/tests/functional/api_sample_tests/test_flavor_manage.py index 6ecb1ddb41..05d360ffde 100644 --- a/nova/tests/functional/api_sample_tests/test_flavor_manage.py +++ b/nova/tests/functional/api_sample_tests/test_flavor_manage.py @@ -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'})] diff --git a/nova/tests/functional/api_sample_tests/test_flavors.py b/nova/tests/functional/api_sample_tests/test_flavors.py index 09ac052b33..69cc2a9697 100644 --- a/nova/tests/functional/api_sample_tests/test_flavors.py +++ b/nova/tests/functional/api_sample_tests/test_flavors.py @@ -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 diff --git a/nova/tests/functional/api_sample_tests/test_servers.py b/nova/tests/functional/api_sample_tests/test_servers.py index 1f7fad40e8..3519703b0c 100644 --- a/nova/tests/functional/api_sample_tests/test_servers.py +++ b/nova/tests/functional/api_sample_tests/test_servers.py @@ -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' diff --git a/nova/tests/unit/api/openstack/compute/test_agents.py b/nova/tests/unit/api/openstack/compute/test_agents.py index c9e986d445..e17df7ee71 100644 --- a/nova/tests/unit/api/openstack/compute/test_agents.py +++ b/nova/tests/unit/api/openstack/compute/test_agents.py @@ -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): diff --git a/nova/tests/unit/api/openstack/compute/test_flavor_manage.py b/nova/tests/unit/api/openstack/compute/test_flavor_manage.py index 0a64d4d71a..e38705c39a 100644 --- a/nova/tests/unit/api/openstack/compute/test_flavor_manage.py +++ b/nova/tests/unit/api/openstack/compute/test_flavor_manage.py @@ -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' diff --git a/nova/tests/unit/api/openstack/compute/test_flavors.py b/nova/tests/unit/api/openstack/compute/test_flavors.py index bdf339dd54..01f510ebed 100644 --- a/nova/tests/unit/api/openstack/compute/test_flavors.py +++ b/nova/tests/unit/api/openstack/compute/test_flavors.py @@ -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 diff --git a/nova/tests/unit/api/openstack/compute/test_hypervisors.py b/nova/tests/unit/api/openstack/compute/test_hypervisors.py index 1c47f8c6af..cd343608e2 100644 --- a/nova/tests/unit/api/openstack/compute/test_hypervisors.py +++ b/nova/tests/unit/api/openstack/compute/test_hypervisors.py @@ -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'])) diff --git a/nova/tests/unit/api/openstack/compute/test_keypairs.py b/nova/tests/unit/api/openstack/compute/test_keypairs.py index af8e0696bf..fb8f9cb534 100644 --- a/nova/tests/unit/api/openstack/compute/test_keypairs.py +++ b/nova/tests/unit/api/openstack/compute/test_keypairs.py @@ -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) diff --git a/nova/tests/unit/api/openstack/compute/test_limits.py b/nova/tests/unit/api/openstack/compute/test_limits.py index c10c35dc5e..7bff0d1823 100644 --- a/nova/tests/unit/api/openstack/compute/test_limits.py +++ b/nova/tests/unit/api/openstack/compute/test_limits.py @@ -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) diff --git a/nova/tests/unit/api/openstack/compute/test_quotas.py b/nova/tests/unit/api/openstack/compute/test_quotas.py index c8d6e86c8e..3bb987cec8 100644 --- a/nova/tests/unit/api/openstack/compute/test_quotas.py +++ b/nova/tests/unit/api/openstack/compute/test_quotas.py @@ -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) diff --git a/nova/tests/unit/api/openstack/compute/test_server_groups.py b/nova/tests/unit/api/openstack/compute/test_server_groups.py index 9793bcc752..f99016a0aa 100644 --- a/nova/tests/unit/api/openstack/compute/test_server_groups.py +++ b/nova/tests/unit/api/openstack/compute/test_server_groups.py @@ -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) diff --git a/nova/tests/unit/api/openstack/compute/test_serversV21.py b/nova/tests/unit/api/openstack/compute/test_serversV21.py index b1040a013f..85f65327e9 100644 --- a/nova/tests/unit/api/openstack/compute/test_serversV21.py +++ b/nova/tests/unit/api/openstack/compute/test_serversV21.py @@ -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): diff --git a/nova/tests/unit/api/openstack/compute/test_services.py b/nova/tests/unit/api/openstack/compute/test_services.py index 67ee253f28..f9860d46c5 100644 --- a/nova/tests/unit/api/openstack/compute/test_services.py +++ b/nova/tests/unit/api/openstack/compute/test_services.py @@ -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): diff --git a/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py b/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py index e41e671fff..3fbe65742f 100644 --- a/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py +++ b/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py @@ -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' diff --git a/nova/tests/unit/api/openstack/compute/test_volumes.py b/nova/tests/unit/api/openstack/compute/test_volumes.py index 7825d259f6..34eb45cefb 100644 --- a/nova/tests/unit/api/openstack/compute/test_volumes.py +++ b/nova/tests/unit/api/openstack/compute/test_volumes.py @@ -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): diff --git a/releasenotes/notes/api-consistency-cleanup-700b260ced206d92.yaml b/releasenotes/notes/api-consistency-cleanup-700b260ced206d92.yaml new file mode 100644 index 0000000000..d69b2f0fe2 --- /dev/null +++ b/releasenotes/notes/api-consistency-cleanup-700b260ced206d92.yaml @@ -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.