From b26bc7fd7a2e66e7659704d902bf6f8af902cfd8 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Wed, 26 Jun 2019 17:30:46 +0000 Subject: [PATCH] Multiple API cleanup changes This microversion implements below API cleanups: 1. 400 for unknown param for query param and for request body. 2. Making server representation always consistent among all APIs returning the complete server representation. 3. Change the default return value of ``swap`` field from the empty string to 0 (integer) in flavor APIs. 4. Return ``servers`` field always in the response of GET hypervisors API even there are no servers on hypervisor Details: https://specs.openstack.org/openstack/nova-specs/specs/train/approved/api-consistency-cleanup.html Partial-Implements: blueprint api-consistency-cleanup Change-Id: I9d257a003d315b84b937dcef91f3cb41f3e24b53 --- api-ref/source/flavors.inc | 16 +- api-ref/source/parameters.yaml | 218 +++++++++++++++++- api-ref/source/servers-actions.inc | 25 +- api-ref/source/servers.inc | 27 ++- .../v2.75/flavor-create-post-req.json | 11 + .../v2.75/flavor-create-post-resp.json | 26 +++ .../v2.75/flavor-update-req.json | 5 + .../v2.75/flavor-update-resp.json | 26 +++ .../flavors/v2.75/flavor-get-resp.json | 29 +++ .../flavors/v2.75/flavors-detail-resp.json | 178 ++++++++++++++ .../flavors/v2.75/flavors-list-resp.json | 109 +++++++++ .../v2.75/server-action-rebuild-resp.json | 89 +++++++ .../servers/v2.75/server-action-rebuild.json | 14 ++ .../servers/v2.75/server-update-req.json | 9 + .../servers/v2.75/server-update-resp.json | 88 +++++++ .../versions/v21-version-get-resp.json | 2 +- .../versions/versions-get-resp.json | 2 +- nova/api/openstack/api_version_request.py | 10 +- nova/api/openstack/compute/agents.py | 3 +- .../compute/assisted_volume_snapshots.py | 5 +- nova/api/openstack/compute/flavors.py | 6 +- nova/api/openstack/compute/hypervisors.py | 13 +- nova/api/openstack/compute/keypairs.py | 9 +- nova/api/openstack/compute/limits.py | 3 +- nova/api/openstack/compute/quota_sets.py | 12 +- .../compute/rest_api_version_history.rst | 19 ++ nova/api/openstack/compute/schemas/agents.py | 10 +- .../schemas/assisted_volume_snapshots.py | 9 +- nova/api/openstack/compute/schemas/flavors.py | 10 +- nova/api/openstack/compute/schemas/hosts.py | 4 +- .../api/openstack/compute/schemas/keypairs.py | 7 + nova/api/openstack/compute/schemas/limits.py | 7 + .../openstack/compute/schemas/quota_sets.py | 7 +- .../compute/schemas/security_groups.py | 4 +- .../compute/schemas/server_groups.py | 6 +- nova/api/openstack/compute/schemas/servers.py | 21 ++ .../api/openstack/compute/schemas/services.py | 5 + .../compute/schemas/simple_tenant_usage.py | 14 +- nova/api/openstack/compute/schemas/volumes.py | 7 +- nova/api/openstack/compute/server_groups.py | 3 +- nova/api/openstack/compute/servers.py | 94 ++++++-- nova/api/openstack/compute/services.py | 3 +- .../openstack/compute/simple_tenant_usage.py | 6 +- nova/api/openstack/compute/views/flavors.py | 3 + nova/api/openstack/compute/views/servers.py | 39 +++- nova/api/openstack/compute/volumes.py | 3 +- nova/api/validation/__init__.py | 4 +- .../v2.75/flavor-create-post-req.json.tpl | 11 + .../v2.75/flavor-create-post-resp.json.tpl | 26 +++ .../v2.75/flavor-update-req.json.tpl | 5 + .../v2.75/flavor-update-resp.json.tpl | 26 +++ .../flavors/v2.75/flavor-get-resp.json.tpl | 29 +++ .../v2.75/flavors-detail-resp.json.tpl | 178 ++++++++++++++ .../flavors/v2.75/flavors-list-resp.json.tpl | 109 +++++++++ .../v2.75/server-action-rebuild-resp.json.tpl | 89 +++++++ .../v2.75/server-action-rebuild.json.tpl | 14 ++ .../servers/v2.75/server-update-req.json.tpl | 9 + .../servers/v2.75/server-update-resp.json.tpl | 88 +++++++ .../api_sample_tests/test_flavor_manage.py | 5 + .../api_sample_tests/test_flavors.py | 8 + .../api_sample_tests/test_servers.py | 23 ++ .../unit/api/openstack/compute/test_agents.py | 48 +++- .../openstack/compute/test_flavor_manage.py | 84 ++++++- .../api/openstack/compute/test_flavors.py | 108 ++++++++- .../api/openstack/compute/test_hypervisors.py | 50 ++++ .../api/openstack/compute/test_keypairs.py | 40 ++++ .../unit/api/openstack/compute/test_limits.py | 29 +++ .../unit/api/openstack/compute/test_quotas.py | 50 ++++ .../openstack/compute/test_server_groups.py | 45 ++-- .../api/openstack/compute/test_serversV21.py | 175 +++++++++++--- .../api/openstack/compute/test_services.py | 21 ++ .../compute/test_simple_tenant_usage.py | 32 +++ .../api/openstack/compute/test_volumes.py | 98 ++++++-- ...-consistency-cleanup-700b260ced206d92.yaml | 17 ++ 74 files changed, 2473 insertions(+), 164 deletions(-) create mode 100644 doc/api_samples/flavor-manage/v2.75/flavor-create-post-req.json create mode 100644 doc/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json create mode 100644 doc/api_samples/flavor-manage/v2.75/flavor-update-req.json create mode 100644 doc/api_samples/flavor-manage/v2.75/flavor-update-resp.json create mode 100644 doc/api_samples/flavors/v2.75/flavor-get-resp.json create mode 100644 doc/api_samples/flavors/v2.75/flavors-detail-resp.json create mode 100644 doc/api_samples/flavors/v2.75/flavors-list-resp.json create mode 100644 doc/api_samples/servers/v2.75/server-action-rebuild-resp.json create mode 100644 doc/api_samples/servers/v2.75/server-action-rebuild.json create mode 100644 doc/api_samples/servers/v2.75/server-update-req.json create mode 100644 doc/api_samples/servers/v2.75/server-update-resp.json create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-create-post-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.75/flavor-update-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavor-get-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-detail-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavors/v2.75/flavors-list-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-action-rebuild.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/servers/v2.75/server-update-resp.json.tpl create mode 100644 releasenotes/notes/api-consistency-cleanup-700b260ced206d92.yaml 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.