Add microversion to allow setting flavor description
This adds the new microversion to allow providing a description when creating a flavor, returning a flavor description when showing flavor details, and updating the description on an existing flavor. Implements blueprint flavor-description Change-Id: Ib16b0de82f9f9492f5cacf646dc3165a0849d75e
This commit is contained in:
parent
f93f10e7b6
commit
034d7f3795
api-ref/source
doc/api_samples
flavor-manage/v2.55
flavor-create-post-req.jsonflavor-create-post-resp.jsonflavor-update-req.jsonflavor-update-resp.json
flavors/v2.55
versions
nova
api/openstack
api_version_request.py
compute
compute
policies
tests
functional
api_sample_tests
api_samples
flavor-manage/v2.55
flavor-create-post-req.json.tplflavor-create-post-resp.json.tplflavor-update-req.json.tplflavor-update-resp.json.tpl
flavors/v2.55
notification_sample_tests
unit
releasenotes/notes
@ -42,14 +42,12 @@ Response
|
||||
- flavors: flavors
|
||||
- id: flavor_id_body
|
||||
- name: flavor_name
|
||||
- description: flavor_description_resp
|
||||
- links: links
|
||||
|
||||
**Example List Flavors**
|
||||
**Example List Flavors (v2.55)**
|
||||
|
||||
Showing all the default flavors of a Liberty era Nova installation
|
||||
that was not customized by the site operators.
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavors/flavors-list-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/flavors/v2.55/flavors-list-resp.json
|
||||
:language: javascript
|
||||
|
||||
Create Flavor
|
||||
@ -74,6 +72,7 @@ Request
|
||||
|
||||
- flavor: flavor
|
||||
- name: flavor_name
|
||||
- description: flavor_description
|
||||
- id: flavor_id_body_create
|
||||
- ram: flavor_ram
|
||||
- disk: flavor_disk
|
||||
@ -83,9 +82,9 @@ Request
|
||||
- rxtx_factor: flavor_rxtx_factor_in
|
||||
- os-flavor-access:is_public: flavor_is_public_in
|
||||
|
||||
**Example Create Flavor**
|
||||
**Example Create Flavor (v2.55)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavor-manage/flavor-create-post-req.json
|
||||
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.55/flavor-create-post-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
@ -95,6 +94,7 @@ Response
|
||||
|
||||
- flavor: flavor
|
||||
- name: flavor_name
|
||||
- description: flavor_description_resp
|
||||
- id: flavor_id_body
|
||||
- ram: flavor_ram
|
||||
- disk: flavor_disk
|
||||
@ -107,9 +107,9 @@ Response
|
||||
- os-flavor-access:is_public: flavor_is_public
|
||||
|
||||
|
||||
**Example Create Flavor**
|
||||
**Example Create Flavor (v2.55)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavor-manage/flavor-create-post-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json
|
||||
:language: javascript
|
||||
|
||||
List Flavors With Details
|
||||
@ -144,6 +144,7 @@ Response
|
||||
|
||||
- flavors: flavors
|
||||
- name: flavor_name
|
||||
- description: flavor_description_resp
|
||||
- id: flavor_id_body
|
||||
- ram: flavor_ram
|
||||
- disk: flavor_disk
|
||||
@ -155,9 +156,9 @@ Response
|
||||
- rxtx_factor: flavor_rxtx_factor
|
||||
- os-flavor-access:is_public: flavor_is_public
|
||||
|
||||
**Example List Flavors With Details**
|
||||
**Example List Flavors With Details (v2.55)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavors/flavors-detail-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/flavors/v2.55/flavors-detail-resp.json
|
||||
:language: javascript
|
||||
|
||||
Show Flavor Details
|
||||
@ -185,6 +186,7 @@ Response
|
||||
|
||||
- flavor: flavor
|
||||
- name: flavor_name
|
||||
- description: flavor_description_resp
|
||||
- id: flavor_id_body
|
||||
- ram: flavor_ram
|
||||
- disk: flavor_disk
|
||||
@ -196,9 +198,65 @@ Response
|
||||
- rxtx_factor: flavor_rxtx_factor
|
||||
- os-flavor-access:is_public: flavor_is_public
|
||||
|
||||
**Example Show Flavor Details**
|
||||
**Example Show Flavor Details (v2.55)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavors/flavor-get-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/flavors/v2.55/flavor-get-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Flavor Description
|
||||
=========================
|
||||
|
||||
.. rest_method:: PUT /flavors/{flavor_id}
|
||||
|
||||
Updates a flavor description.
|
||||
|
||||
This API is available starting with microversion 2.55.
|
||||
|
||||
Policy defaults enable only users with the administrative role to
|
||||
perform this operation. Cloud providers can change these permissions
|
||||
through the ``policy.json`` file.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- flavor_id: flavor_id
|
||||
- flavor: flavor
|
||||
- description: flavor_description_required
|
||||
|
||||
**Example Update Flavor Description (v2.55)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.55/flavor-update-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- flavor: flavor
|
||||
- name: flavor_name
|
||||
- description: flavor_description_resp
|
||||
- id: flavor_id_body
|
||||
- ram: flavor_ram
|
||||
- disk: flavor_disk
|
||||
- vcpus: flavor_cpus
|
||||
- links: links
|
||||
- OS-FLV-EXT-DATA:ephemeral: flavor_ephem_disk
|
||||
- OS-FLV-DISABLED:disabled: flavor_disabled
|
||||
- swap: flavor_swap
|
||||
- rxtx_factor: flavor_rxtx_factor
|
||||
- os-flavor-access:is_public: flavor_is_public
|
||||
|
||||
|
||||
**Example Update Flavor Description (v2.55)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json
|
||||
:language: javascript
|
||||
|
||||
Delete Flavor
|
||||
|
@ -2365,6 +2365,29 @@ flavor_cpus_2_47:
|
||||
type: integer
|
||||
description: |
|
||||
The number of virtual CPUs that were allocated to the server.
|
||||
flavor_description:
|
||||
type: string
|
||||
in: body
|
||||
required: false
|
||||
min_version: 2.55
|
||||
description: |
|
||||
A free form description of the flavor. Limited to 65535 characters
|
||||
in length. Only printable characters are allowed.
|
||||
flavor_description_required:
|
||||
type: string
|
||||
in: body
|
||||
required: true
|
||||
min_version: 2.55
|
||||
description: |
|
||||
A free form description of the flavor. Limited to 65535 characters
|
||||
in length. Only printable characters are allowed.
|
||||
flavor_description_resp:
|
||||
description: |
|
||||
The description of the flavor.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
min_version: 2.55
|
||||
flavor_disabled:
|
||||
in: body
|
||||
required: false
|
||||
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"flavor": {
|
||||
"name": "test_flavor",
|
||||
"ram": 1024,
|
||||
"vcpus": 2,
|
||||
"disk": 10,
|
||||
"id": "10",
|
||||
"rxtx_factor": 2.0,
|
||||
"description": "test description"
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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": "",
|
||||
"rxtx_factor": 2.0,
|
||||
"vcpus": 2,
|
||||
"description": "test description"
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"flavor": {
|
||||
"description": "updated description"
|
||||
}
|
||||
}
|
25
doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json
Normal file
25
doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": "updated description"
|
||||
}
|
||||
}
|
25
doc/api_samples/flavors/v2.55/flavor-get-resp.json
Normal file
25
doc/api_samples/flavors/v2.55/flavor-get-resp.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": "test description"
|
||||
}
|
||||
}
|
165
doc/api_samples/flavors/v2.55/flavors-detail-resp.json
Normal file
165
doc/api_samples/flavors/v2.55/flavors-detail-resp.json
Normal file
@ -0,0 +1,165 @@
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 2,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 4,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 8,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": "test description"
|
||||
}
|
||||
]
|
||||
}
|
109
doc/api_samples/flavors/v2.55/flavors-list-resp.json
Normal file
109
doc/api_samples/flavors/v2.55/flavors-list-resp.json
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"flavors": [
|
||||
{
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/1",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.tiny",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/2",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/2",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.small",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/3",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/3",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.medium",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/4",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/4",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.large",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/5",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/5",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.xlarge",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/6",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/6",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.tiny.specs",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.small.description",
|
||||
"description": "test description"
|
||||
}
|
||||
]
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.54",
|
||||
"version": "2.55",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.54",
|
||||
"version": "2.55",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -129,6 +129,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
id field, and takes a uuid in requests. PUT and GET requests
|
||||
and responses are also changed.
|
||||
* 2.54 - Enable reset key pair while rebuilding instance.
|
||||
* 2.55 - Added flavor.description to GET/POST/PUT flavors APIs.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -137,7 +138,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.54"
|
||||
_MAX_API_VERSION = "2.55"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# Almost all proxy APIs which are related to network, images and baremetal
|
||||
|
@ -87,6 +87,13 @@ class FlavorActionController(wsgi.Controller):
|
||||
|
||||
self._extend_flavor(resp_obj.obj['flavor'], db_flavor)
|
||||
|
||||
@wsgi.extends(action='update')
|
||||
def update(self, req, id, body, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
if context.can(fa_policies.BASE_POLICY_NAME, fatal=False):
|
||||
db_flavor = req.get_db_flavor(resp_obj.obj['flavor']['id'])
|
||||
self._extend_flavor(resp_obj.obj['flavor'], db_flavor)
|
||||
|
||||
@extensions.expected_errors((400, 403, 404, 409))
|
||||
@wsgi.action("addTenantAccess")
|
||||
@validation.schema(flavor_access.add_tenant_access)
|
||||
|
@ -14,6 +14,7 @@ import webob
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack.compute.schemas import flavor_manage
|
||||
from nova.api.openstack.compute.views import flavors as flavors_view
|
||||
from nova.api.openstack import extensions
|
||||
@ -67,7 +68,9 @@ class FlavorManageController(wsgi.Controller):
|
||||
@wsgi.action("create")
|
||||
@extensions.expected_errors((400, 409))
|
||||
@validation.schema(flavor_manage.create_v20, '2.0', '2.0')
|
||||
@validation.schema(flavor_manage.create, '2.1')
|
||||
@validation.schema(flavor_manage.create, '2.1', '2.54')
|
||||
@validation.schema(flavor_manage.create_v2_55,
|
||||
flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
def _create(self, req, body):
|
||||
context = req.environ['nova.context']
|
||||
# TODO(rb560u): remove this check in future release
|
||||
@ -92,12 +95,18 @@ class FlavorManageController(wsgi.Controller):
|
||||
rxtx_factor = vals.get('rxtx_factor', 1.0)
|
||||
is_public = vals.get('os-flavor-access:is_public', True)
|
||||
|
||||
# The user can specify a description starting with microversion 2.55.
|
||||
include_description = api_version_request.is_supported(
|
||||
req, flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
description = vals.get('description') if include_description else None
|
||||
|
||||
try:
|
||||
flavor = flavors.create(name, memory, vcpus, root_gb,
|
||||
ephemeral_gb=ephemeral_gb,
|
||||
flavorid=flavorid, swap=swap,
|
||||
rxtx_factor=rxtx_factor,
|
||||
is_public=is_public)
|
||||
is_public=is_public,
|
||||
description=description)
|
||||
# NOTE(gmann): For backward compatibility, non public flavor
|
||||
# access is not being added for created tenant. Ref -bug/1209101
|
||||
req.cache_db_flavor(flavor)
|
||||
@ -105,4 +114,27 @@ class FlavorManageController(wsgi.Controller):
|
||||
exception.FlavorIdExists) as err:
|
||||
raise webob.exc.HTTPConflict(explanation=err.format_message())
|
||||
|
||||
return self._view_builder.show(req, flavor)
|
||||
return self._view_builder.show(req, flavor, include_description)
|
||||
|
||||
@wsgi.Controller.api_version(flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
@wsgi.action('update')
|
||||
@extensions.expected_errors((400, 404))
|
||||
@validation.schema(flavor_manage.update_v2_55,
|
||||
flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
def _update(self, req, id, body):
|
||||
# Validate the policy.
|
||||
context = req.environ['nova.context']
|
||||
context.can(fm_policies.POLICY_ROOT % 'update')
|
||||
|
||||
# Get the flavor and update the description.
|
||||
try:
|
||||
flavor = objects.Flavor.get_by_flavor_id(context, id)
|
||||
flavor.description = body['flavor']['description']
|
||||
flavor.save()
|
||||
except exception.FlavorNotFound as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
# Cache the flavor so the flavor_access and flavor_rxtx extensions
|
||||
# can add stuff to the response.
|
||||
req.cache_db_flavor(flavor)
|
||||
return self._view_builder.show(req, flavor, include_description=True)
|
||||
|
@ -42,6 +42,10 @@ class FlavorRxtxController(wsgi.Controller):
|
||||
def create(self, req, resp_obj, body):
|
||||
return self._show(req, resp_obj)
|
||||
|
||||
@wsgi.extends(action='update')
|
||||
def update(self, req, id, body, resp_obj):
|
||||
return self._show(req, resp_obj)
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
|
@ -16,6 +16,7 @@
|
||||
from oslo_utils import strutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack.compute.views import flavors as flavors_view
|
||||
from nova.api.openstack import extensions
|
||||
@ -57,7 +58,9 @@ class FlavorsController(wsgi.Controller):
|
||||
except exception.FlavorNotFound as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
return self._view_builder.show(req, flavor)
|
||||
include_description = api_version_request.is_supported(
|
||||
req, flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
return self._view_builder.show(req, flavor, include_description)
|
||||
|
||||
def _parse_is_public(self, is_public):
|
||||
"""Parse is_public into something usable."""
|
||||
|
@ -688,3 +688,16 @@ uniqueness across cells. This microversion brings the following changes:
|
||||
----
|
||||
|
||||
Allow the user to set the server key pair while rebuilding.
|
||||
|
||||
2.55
|
||||
----
|
||||
|
||||
Adds a ``description`` field to the flavor resource in the following APIs:
|
||||
|
||||
* ``GET /flavors``
|
||||
* ``GET /flavors/detail``
|
||||
* ``GET /flavors/{flavor_id}``
|
||||
* ``POST /flavors``
|
||||
* ``PUT /flavors/{flavor_id}``
|
||||
|
||||
The embedded flavor description will not be included in server representations.
|
||||
|
@ -429,6 +429,7 @@ ROUTE_LIST = (
|
||||
}),
|
||||
('/flavors/{id}', {
|
||||
'GET': [flavor_controller, 'show'],
|
||||
'PUT': [flavor_controller, 'update'],
|
||||
'DELETE': [flavor_controller, 'delete']
|
||||
}),
|
||||
('/flavors/{id}/action', {
|
||||
|
@ -65,3 +65,37 @@ create = {
|
||||
create_v20 = copy.deepcopy(create)
|
||||
create_v20['properties']['flavor']['properties']['name'] = (parameter_types.
|
||||
name_with_leading_trailing_spaces)
|
||||
|
||||
|
||||
# 2.55 adds an optional description field with a max length of 65535 since the
|
||||
# backing database column is a TEXT column which is 64KiB.
|
||||
flavor_description = {
|
||||
'type': ['string', 'null'], 'minLength': 0, 'maxLength': 65535,
|
||||
'pattern': parameter_types.valid_description_regex,
|
||||
}
|
||||
|
||||
|
||||
create_v2_55 = copy.deepcopy(create)
|
||||
create_v2_55['properties']['flavor']['properties']['description'] = (
|
||||
flavor_description)
|
||||
|
||||
|
||||
update_v2_55 = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'flavor': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'description': flavor_description
|
||||
},
|
||||
# Since the only property that can be specified on update is the
|
||||
# description field, it is required. If we allow updating other
|
||||
# flavor attributes in a later microversion, we should reconsider
|
||||
# what is required.
|
||||
'required': ['description'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['flavor'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
@ -13,15 +13,18 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack import common
|
||||
|
||||
FLAVOR_DESCRIPTION_MICROVERSION = '2.55'
|
||||
|
||||
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
|
||||
_collection_name = "flavors"
|
||||
|
||||
def basic(self, request, flavor):
|
||||
return {
|
||||
def basic(self, request, flavor, include_description=False):
|
||||
flavor_dict = {
|
||||
"flavor": {
|
||||
"id": flavor["flavorid"],
|
||||
"name": flavor["name"],
|
||||
@ -31,7 +34,12 @@ class ViewBuilder(common.ViewBuilder):
|
||||
},
|
||||
}
|
||||
|
||||
def show(self, request, flavor):
|
||||
if include_description:
|
||||
flavor_dict['flavor']['description'] = flavor.description
|
||||
|
||||
return flavor_dict
|
||||
|
||||
def show(self, request, flavor, include_description=False):
|
||||
flavor_dict = {
|
||||
"flavor": {
|
||||
"id": flavor["flavorid"],
|
||||
@ -48,19 +56,29 @@ class ViewBuilder(common.ViewBuilder):
|
||||
},
|
||||
}
|
||||
|
||||
if include_description:
|
||||
flavor_dict['flavor']['description'] = flavor.description
|
||||
|
||||
return flavor_dict
|
||||
|
||||
def index(self, request, flavors):
|
||||
"""Return the 'index' view of flavors."""
|
||||
coll_name = self._collection_name
|
||||
return self._list_view(self.basic, request, flavors, coll_name)
|
||||
include_description = api_version_request.is_supported(
|
||||
request, FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
return self._list_view(self.basic, request, flavors, coll_name,
|
||||
include_description=include_description)
|
||||
|
||||
def detail(self, request, flavors):
|
||||
"""Return the 'detail' view of flavors."""
|
||||
coll_name = self._collection_name + '/detail'
|
||||
return self._list_view(self.show, request, flavors, coll_name)
|
||||
include_description = api_version_request.is_supported(
|
||||
request, FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
return self._list_view(self.show, request, flavors, coll_name,
|
||||
include_description=include_description)
|
||||
|
||||
def _list_view(self, func, request, flavors, coll_name):
|
||||
def _list_view(self, func, request, flavors, coll_name,
|
||||
include_description=False):
|
||||
"""Provide a view for a list of flavors.
|
||||
|
||||
:param func: Function used to format the flavor data
|
||||
@ -68,10 +86,13 @@ class ViewBuilder(common.ViewBuilder):
|
||||
:param flavors: List of flavors in dictionary format
|
||||
:param coll_name: Name of collection, used to generate the next link
|
||||
for a pagination query
|
||||
:param include_description: If the flavor.description should be
|
||||
included in the response dict.
|
||||
|
||||
:returns: Flavor reply data in dictionary format
|
||||
"""
|
||||
flavor_list = [func(request, flavor)["flavor"] for flavor in flavors]
|
||||
flavor_list = [func(request, flavor, include_description)["flavor"]
|
||||
for flavor in flavors]
|
||||
flavors_links = self._get_collection_links(request,
|
||||
flavors,
|
||||
coll_name,
|
||||
|
@ -69,7 +69,7 @@ system_metadata_flavor_extra_props = [
|
||||
|
||||
|
||||
def create(name, memory, vcpus, root_gb, ephemeral_gb=0, flavorid=None,
|
||||
swap=0, rxtx_factor=1.0, is_public=True):
|
||||
swap=0, rxtx_factor=1.0, is_public=True, description=None):
|
||||
"""Creates flavors."""
|
||||
if not flavorid:
|
||||
flavorid = uuidutils.generate_uuid()
|
||||
@ -81,6 +81,7 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=0, flavorid=None,
|
||||
'ephemeral_gb': ephemeral_gb,
|
||||
'swap': swap,
|
||||
'rxtx_factor': rxtx_factor,
|
||||
'description': description
|
||||
}
|
||||
|
||||
if isinstance(name, six.string_types):
|
||||
|
@ -71,6 +71,10 @@ to a flavor via an os-flavor-access API.
|
||||
'method': 'POST',
|
||||
'path': '/flavors'
|
||||
},
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/flavors/{flavor_id}'
|
||||
},
|
||||
]),
|
||||
]
|
||||
|
||||
|
@ -52,6 +52,16 @@ flavor_manage_policies = [
|
||||
'path': '/flavors'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
POLICY_ROOT % 'update',
|
||||
base.RULE_ADMIN_API,
|
||||
"Update a flavor",
|
||||
[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/flavors/{flavor_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
POLICY_ROOT % 'delete',
|
||||
BASE_POLICY_RULE,
|
||||
|
@ -40,6 +40,10 @@ flavor_rxtx_policies = [
|
||||
'method': 'POST',
|
||||
'path': '/flavors'
|
||||
},
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/flavors/{flavor_id}'
|
||||
},
|
||||
]),
|
||||
]
|
||||
|
||||
|
11
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-req.json.tpl
Normal file
11
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-req.json.tpl
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"flavor": {
|
||||
"name": "%(flavor_name)s",
|
||||
"ram": 1024,
|
||||
"vcpus": 2,
|
||||
"disk": 10,
|
||||
"id": "%(flavor_id)s",
|
||||
"rxtx_factor": 2.0,
|
||||
"description": "test description"
|
||||
}
|
||||
}
|
25
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json.tpl
Normal file
25
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json.tpl
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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": "",
|
||||
"rxtx_factor": 2.0,
|
||||
"description": "test description"
|
||||
}
|
||||
}
|
5
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-req.json.tpl
Normal file
5
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-req.json.tpl
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"flavor": {
|
||||
"description": "updated description"
|
||||
}
|
||||
}
|
25
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-resp.json.tpl
Normal file
25
nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-resp.json.tpl
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": "updated description"
|
||||
}
|
||||
}
|
25
nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavor-get-resp.json.tpl
Normal file
25
nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavor-get-resp.json.tpl
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": "test description"
|
||||
}
|
||||
}
|
165
nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-detail-resp.json.tpl
Normal file
165
nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-detail-resp.json.tpl
Normal file
@ -0,0 +1,165 @@
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 2,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 4,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 8,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"vcpus": 1,
|
||||
"rxtx_factor": 1.0,
|
||||
"description": "test description"
|
||||
}
|
||||
]
|
||||
}
|
109
nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-list-resp.json.tpl
Normal file
109
nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-list-resp.json.tpl
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"flavors": [
|
||||
{
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/flavors/1",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.tiny",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/flavors/2",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/flavors/2",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.small",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/flavors/3",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/flavors/3",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.medium",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/flavors/4",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/flavors/4",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.large",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/flavors/5",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/flavors/5",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.xlarge",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/flavors/6",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/flavors/6",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "m1.tiny.specs",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"description": "test description"
|
||||
}
|
||||
]
|
||||
}
|
@ -37,3 +37,12 @@ class FlavorManageSampleJsonTests(api_sample_base.ApiSampleTestBaseV21):
|
||||
response = self._do_delete("flavors/10")
|
||||
self.assertEqual(202, response.status_code)
|
||||
self.assertEqual('', response.text)
|
||||
|
||||
|
||||
class FlavorManageSampleJsonTests2_55(FlavorManageSampleJsonTests):
|
||||
microversion = '2.55'
|
||||
scenarios = [('v2_55', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def test_update_flavor_description(self):
|
||||
response = self._do_put("flavors/1", "flavor-update-req", {})
|
||||
self._verify_response("flavor-update-resp", {}, response, 200)
|
||||
|
@ -13,20 +13,47 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nova import context as nova_context
|
||||
from nova import objects
|
||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||
|
||||
|
||||
class FlavorsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
sample_dir = 'flavors'
|
||||
flavor_show_id = '1'
|
||||
subs = {}
|
||||
|
||||
def test_flavors_get(self):
|
||||
response = self._do_get('flavors/1')
|
||||
self._verify_response('flavor-get-resp', {}, response, 200)
|
||||
response = self._do_get('flavors/%s' % self.flavor_show_id)
|
||||
self._verify_response('flavor-get-resp', self.subs, response, 200)
|
||||
|
||||
def test_flavors_list(self):
|
||||
response = self._do_get('flavors')
|
||||
self._verify_response('flavors-list-resp', {}, response, 200)
|
||||
self._verify_response('flavors-list-resp', self.subs, response, 200)
|
||||
|
||||
def test_flavors_detail(self):
|
||||
response = self._do_get('flavors/detail')
|
||||
self._verify_response('flavors-detail-resp', {}, response, 200)
|
||||
self._verify_response('flavors-detail-resp', self.subs, response,
|
||||
200)
|
||||
|
||||
|
||||
class FlavorsSampleJsonTest2_55(FlavorsSampleJsonTest):
|
||||
microversion = '2.55'
|
||||
scenarios = [('v2_55', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def setUp(self):
|
||||
super(FlavorsSampleJsonTest2_55, self).setUp()
|
||||
# Get the existing flavors created by DefaultFlavorsFixture.
|
||||
ctxt = nova_context.get_admin_context()
|
||||
flavors = objects.FlavorList.get_all(ctxt)
|
||||
# Flavors are sorted by flavorid in ascending order by default, so
|
||||
# get the last flavor in the list and create a new flavor with an
|
||||
# incremental flavorid so we have a predictable sort order for the
|
||||
# sample response.
|
||||
new_flavor_id = int(flavors[-1].flavorid) + 1
|
||||
new_flavor = objects.Flavor(
|
||||
ctxt, memory_mb=2048, vcpus=1, root_gb=20, flavorid=new_flavor_id,
|
||||
name='m1.small.description', description='test description')
|
||||
new_flavor.create()
|
||||
self.flavor_show_id = new_flavor_id
|
||||
self.subs = {'flavorid': new_flavor_id}
|
||||
|
@ -86,3 +86,47 @@ class TestFlavorNotificationSample(
|
||||
|
||||
self._verify_notification(
|
||||
'flavor-update', actual=fake_notifier.VERSIONED_NOTIFICATIONS[2])
|
||||
|
||||
|
||||
class TestFlavorNotificationSamplev2_55(
|
||||
notification_sample_base.NotificationSampleTestBase):
|
||||
"""Tests PUT /flavors/{flavor_id} with a description."""
|
||||
|
||||
MAX_MICROVERSION = '2.55'
|
||||
|
||||
def test_flavor_udpate_with_description(self):
|
||||
# First create a flavor without a description.
|
||||
body = {
|
||||
"flavor": {
|
||||
"name": "test_flavor",
|
||||
"ram": 1024,
|
||||
"vcpus": 2,
|
||||
"disk": 10,
|
||||
"id": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||
"os-flavor-access:is_public": False,
|
||||
"rxtx_factor": 2.0
|
||||
}
|
||||
}
|
||||
# Create a flavor.
|
||||
flavor = self.admin_api.api_post('flavors', body).body['flavor']
|
||||
# Check the notification; should be the same as the sample where there
|
||||
# is no description set.
|
||||
self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
self._verify_notification(
|
||||
'flavor-create',
|
||||
replacements={'is_public': False},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
|
||||
|
||||
# Update and set the flavor description.
|
||||
self.admin_api.api_put(
|
||||
'flavors/%s' % flavor['id'],
|
||||
{'flavor': {'description': 'test description'}}).body['flavor']
|
||||
|
||||
# Assert the notifications, one for create and one for update.
|
||||
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
self._verify_notification(
|
||||
'flavor-update',
|
||||
replacements={'description': 'test description',
|
||||
'extra_specs': {},
|
||||
'projects': []},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
||||
|
@ -18,11 +18,13 @@ from oslo_serialization import jsonutils
|
||||
import six
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack.compute import flavor_access as flavor_access_v21
|
||||
from nova.api.openstack.compute import flavor_manage as flavormanage_v21
|
||||
from nova.compute import flavors
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova import policy
|
||||
from nova import test
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
@ -45,6 +47,7 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
||||
controller = flavormanage_v21.FlavorManageController()
|
||||
validation_error = exception.ValidationError
|
||||
base_url = '/v2/fake/flavors'
|
||||
microversion = '2.1'
|
||||
|
||||
def setUp(self):
|
||||
super(FlavorManageTestV21, self).setUp()
|
||||
@ -66,7 +69,8 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
||||
self.expected_flavor = self.request_body
|
||||
|
||||
def _get_http_request(self, url=''):
|
||||
return fakes.HTTPRequest.blank(url)
|
||||
return fakes.HTTPRequest.blank(url, version=self.microversion,
|
||||
use_admin_context=True)
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
@ -126,6 +130,7 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
||||
def _create_flavor_success_case(self, body, req=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.method = 'POST'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
res = req.get_response(self.app)
|
||||
@ -293,7 +298,7 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
||||
}
|
||||
|
||||
def fake_create(name, memory_mb, vcpus, root_gb, ephemeral_gb,
|
||||
flavorid, swap, rxtx_factor, is_public):
|
||||
flavorid, swap, rxtx_factor, is_public, description):
|
||||
raise exception.FlavorExists(name=name)
|
||||
|
||||
self.stub_out('nova.compute.flavors.create', fake_create)
|
||||
@ -313,6 +318,111 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
||||
self.assertRaises(exception.InvalidInput, flavors.create, "abcdef",
|
||||
"test_memory_mb", 2, None, 1, 1234, 512, 1, True)
|
||||
|
||||
def test_create_with_description(self):
|
||||
"""With microversion <2.55 this should return a failure."""
|
||||
self.request_body['flavor']['description'] = 'invalid'
|
||||
ex = self.assertRaises(
|
||||
self.validation_error, self.controller._create,
|
||||
self._get_http_request(), body=self.request_body)
|
||||
self.assertIn('description', six.text_type(ex))
|
||||
|
||||
def test_flavor_update_description(self):
|
||||
"""With microversion <2.55 this should return a failure."""
|
||||
flavor = self._create_flavor_success_case(self.request_body)['flavor']
|
||||
self.assertRaises(
|
||||
exception.VersionNotFoundForAPIMethod, self.controller._update,
|
||||
self._get_http_request(), flavor['id'],
|
||||
body={'flavor': {'description': 'nope'}})
|
||||
|
||||
|
||||
class FlavorManageTestV2_55(FlavorManageTestV21):
|
||||
microversion = '2.55'
|
||||
|
||||
def setUp(self):
|
||||
super(FlavorManageTestV2_55, self).setUp()
|
||||
# Send a description in POST /flavors requests.
|
||||
self.request_body['flavor']['description'] = 'test description'
|
||||
|
||||
def test_create_with_description(self):
|
||||
# test_create already tests this.
|
||||
pass
|
||||
|
||||
@mock.patch('nova.objects.Flavor.get_by_flavor_id')
|
||||
@mock.patch('nova.objects.Flavor.save')
|
||||
def test_flavor_update_description(self, mock_flavor_save, mock_get):
|
||||
"""Tests updating a flavor description."""
|
||||
# First create a flavor.
|
||||
flavor = self._create_flavor_success_case(self.request_body)['flavor']
|
||||
self.assertEqual('test description', flavor['description'])
|
||||
mock_get.return_value = objects.Flavor(
|
||||
flavorid=flavor['id'], name=flavor['name'],
|
||||
memory_mb=flavor['ram'], vcpus=flavor['vcpus'],
|
||||
root_gb=flavor['disk'], swap=flavor['swap'],
|
||||
ephemeral_gb=flavor['OS-FLV-EXT-DATA:ephemeral'],
|
||||
disabled=flavor['OS-FLV-DISABLED:disabled'],
|
||||
is_public=flavor['os-flavor-access:is_public'],
|
||||
description=flavor['description'])
|
||||
# Now null out the flavor description.
|
||||
flavor = self.controller._update(
|
||||
self._get_http_request(), flavor['id'],
|
||||
body={'flavor': {'description': None}})['flavor']
|
||||
self.assertIsNone(flavor['description'])
|
||||
mock_get.assert_called_once_with(
|
||||
test.MatchType(fakes.FakeRequestContext), flavor['id'])
|
||||
mock_flavor_save.assert_called_once_with()
|
||||
|
||||
@mock.patch('nova.objects.Flavor.get_by_flavor_id',
|
||||
side_effect=exception.FlavorNotFound(flavor_id='notfound'))
|
||||
def test_flavor_update_not_found(self, mock_get):
|
||||
"""Tests that a 404 is returned if the flavor is not found."""
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller._update,
|
||||
self._get_http_request(), 'notfound',
|
||||
body={'flavor': {'description': None}})
|
||||
|
||||
def test_flavor_update_missing_description(self):
|
||||
"""Tests that a schema validation error is raised if no description
|
||||
is provided in the update request body.
|
||||
"""
|
||||
self.assertRaises(self.validation_error,
|
||||
self.controller._update,
|
||||
self._get_http_request(), 'invalid',
|
||||
body={'flavor': {}})
|
||||
|
||||
def test_create_with_invalid_description(self):
|
||||
# NOTE(mriedem): Intentionally not using ddt for this since ddt will
|
||||
# create a test name that has 65536 'a's in the name which blows up
|
||||
# the console output.
|
||||
for description in ('bad !@#!$%\x00 description', # printable chars
|
||||
'a' * 65536): # maxLength
|
||||
self.request_body['flavor']['description'] = description
|
||||
self.assertRaises(self.validation_error, self.controller._create,
|
||||
self._get_http_request(), body=self.request_body)
|
||||
|
||||
@mock.patch('nova.objects.Flavor.get_by_flavor_id')
|
||||
@mock.patch('nova.objects.Flavor.save')
|
||||
def test_update_with_invalid_description(self, mock_flavor_save, mock_get):
|
||||
# First create a flavor.
|
||||
flavor = self._create_flavor_success_case(self.request_body)['flavor']
|
||||
self.assertEqual('test description', flavor['description'])
|
||||
mock_get.return_value = objects.Flavor(
|
||||
flavorid=flavor['id'], name=flavor['name'],
|
||||
memory_mb=flavor['ram'], vcpus=flavor['vcpus'],
|
||||
root_gb=flavor['disk'], swap=flavor['swap'],
|
||||
ephemeral_gb=flavor['OS-FLV-EXT-DATA:ephemeral'],
|
||||
disabled=flavor['OS-FLV-DISABLED:disabled'],
|
||||
is_public=flavor['os-flavor-access:is_public'],
|
||||
description=flavor['description'])
|
||||
# NOTE(mriedem): Intentionally not using ddt for this since ddt will
|
||||
# create a test name that has 65536 'a's in the name which blows up
|
||||
# the console output.
|
||||
for description in ('bad !@#!$%\x00 description', # printable chars
|
||||
'a' * 65536): # maxLength
|
||||
self.request_body['flavor']['description'] = description
|
||||
self.assertRaises(self.validation_error, self.controller._update,
|
||||
self._get_http_request(), flavor['id'],
|
||||
body={'flavor': {'description': description}})
|
||||
|
||||
|
||||
class PrivateFlavorManageTestV21(test.TestCase):
|
||||
controller = flavormanage_v21.FlavorManageController()
|
||||
@ -574,3 +684,17 @@ class FlavorManagerPolicyEnforcementV21(test.TestCase):
|
||||
self.assertEqual(
|
||||
"Policy doesn't allow %s to be performed." % delete_flavor_policy,
|
||||
exc.format_message())
|
||||
|
||||
def test_flavor_update_non_admin_fails(self):
|
||||
"""Tests that trying to update a flavor as a non-admin fails due
|
||||
to the default policy.
|
||||
"""
|
||||
self.req.api_version_request = api_version_request.APIVersionRequest(
|
||||
'2.55')
|
||||
exc = self.assertRaises(
|
||||
exception.PolicyNotAuthorized,
|
||||
self.controller._update, self.req, 'fake_id',
|
||||
body={"flavor": {"description": "not authorized"}})
|
||||
self.assertEqual(
|
||||
"Policy doesn't allow os_compute_api:os-flavor-manage:update to "
|
||||
"be performed.", exc.format_message())
|
||||
|
@ -49,6 +49,9 @@ class FlavorsTestV21(test.TestCase):
|
||||
fake_request = fakes.HTTPRequestV21
|
||||
_rspv = "v2/fake"
|
||||
_fake = "/fake"
|
||||
microversion = '2.1'
|
||||
# Flag to tell the test if a description should be expected in a response.
|
||||
expect_description = False
|
||||
|
||||
def setUp(self):
|
||||
super(FlavorsTestV21, self).setUp()
|
||||
@ -57,6 +60,10 @@ class FlavorsTestV21(test.TestCase):
|
||||
fakes.stub_out_flavor_get_by_flavor_id(self)
|
||||
self.controller = self.Controller()
|
||||
|
||||
def _build_request(self, url):
|
||||
return self.fake_request.blank(
|
||||
self._prefix + url, version=self.microversion)
|
||||
|
||||
def _set_expected_body(self, expected, flavor):
|
||||
# NOTE(oomichi): On v2.1 API, some extensions of v2.0 are merged
|
||||
# as core features and we can get the following parameters as the
|
||||
@ -64,16 +71,18 @@ class FlavorsTestV21(test.TestCase):
|
||||
expected['OS-FLV-EXT-DATA:ephemeral'] = flavor.ephemeral_gb
|
||||
expected['OS-FLV-DISABLED:disabled'] = flavor.disabled
|
||||
expected['swap'] = flavor.swap
|
||||
if self.expect_description:
|
||||
expected['description'] = flavor.description
|
||||
|
||||
@mock.patch('nova.objects.Flavor.get_by_flavor_id',
|
||||
side_effect=return_flavor_not_found)
|
||||
def test_get_flavor_by_invalid_id(self, mock_get):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors/asdf')
|
||||
req = self._build_request('/flavors/asdf')
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.show, req, 'asdf')
|
||||
|
||||
def test_get_flavor_by_id(self):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors/1')
|
||||
req = self._build_request('/flavors/1')
|
||||
flavor = self.controller.show(req, '1')
|
||||
expected = {
|
||||
"flavor": {
|
||||
@ -103,7 +112,7 @@ class FlavorsTestV21(test.TestCase):
|
||||
self.flags(compute_link_prefix='http://zoo.com:42',
|
||||
glance_link_prefix='http://circus.com:34',
|
||||
group='api')
|
||||
req = self.fake_request.blank(self._prefix + '/flavors/1')
|
||||
req = self._build_request('/flavors/1')
|
||||
flavor = self.controller.show(req, '1')
|
||||
expected = {
|
||||
"flavor": {
|
||||
@ -130,7 +139,7 @@ class FlavorsTestV21(test.TestCase):
|
||||
self.assertEqual(expected, flavor)
|
||||
|
||||
def test_get_flavor_list(self):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors')
|
||||
req = self._build_request('/flavors')
|
||||
flavor = self.controller.index(req)
|
||||
expected = {
|
||||
"flavors": [
|
||||
@ -168,12 +177,16 @@ class FlavorsTestV21(test.TestCase):
|
||||
},
|
||||
],
|
||||
}
|
||||
if self.expect_description:
|
||||
for idx, _flavor in enumerate(expected['flavors']):
|
||||
expected['flavors'][idx]['description'] = (
|
||||
fakes.FLAVORS[_flavor['id']].description)
|
||||
self.assertEqual(flavor, expected)
|
||||
|
||||
def test_get_flavor_list_with_marker(self):
|
||||
self.maxDiff = None
|
||||
url = self._prefix + '/flavors?limit=1&marker=1'
|
||||
req = self.fake_request.blank(url)
|
||||
url = '/flavors?limit=1&marker=1'
|
||||
req = self._build_request(url)
|
||||
flavor = self.controller.index(req)
|
||||
expected = {
|
||||
"flavors": [
|
||||
@ -200,16 +213,19 @@ class FlavorsTestV21(test.TestCase):
|
||||
'rel': 'next'}
|
||||
]
|
||||
}
|
||||
if self.expect_description:
|
||||
expected['flavors'][0]['description'] = (
|
||||
fakes.FLAVORS['2'].description)
|
||||
self.assertThat(flavor, matchers.DictMatches(expected))
|
||||
|
||||
def test_get_flavor_list_with_invalid_marker(self):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors?marker=99999')
|
||||
req = self._build_request('/flavors?marker=99999')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index, req)
|
||||
|
||||
def test_get_flavor_detail_with_limit(self):
|
||||
url = self._prefix + '/flavors/detail?limit=1'
|
||||
req = self.fake_request.blank(url)
|
||||
url = '/flavors/detail?limit=1'
|
||||
req = self._build_request(url)
|
||||
response = self.controller.detail(req)
|
||||
response_list = response["flavors"]
|
||||
response_links = response["flavors_links"]
|
||||
@ -247,7 +263,7 @@ class FlavorsTestV21(test.TestCase):
|
||||
matchers.DictMatches(params))
|
||||
|
||||
def test_get_flavor_with_limit(self):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors?limit=2')
|
||||
req = self._build_request('/flavors?limit=2')
|
||||
response = self.controller.index(req)
|
||||
response_list = response["flavors"]
|
||||
response_links = response["flavors_links"]
|
||||
@ -286,6 +302,10 @@ class FlavorsTestV21(test.TestCase):
|
||||
],
|
||||
}
|
||||
]
|
||||
if self.expect_description:
|
||||
for idx, _flavor in enumerate(expected_flavors):
|
||||
expected_flavors[idx]['description'] = (
|
||||
fakes.FLAVORS[_flavor['id']].description)
|
||||
self.assertEqual(response_list, expected_flavors)
|
||||
self.assertEqual(response_links[0]['rel'], 'next')
|
||||
|
||||
@ -330,7 +350,7 @@ class FlavorsTestV21(test.TestCase):
|
||||
matchers.DictMatches(params))
|
||||
|
||||
def test_get_flavor_list_detail(self):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors/detail')
|
||||
req = self._build_request('/flavors/detail')
|
||||
flavor = self.controller.detail(req)
|
||||
expected = {
|
||||
"flavors": [
|
||||
@ -381,14 +401,14 @@ class FlavorsTestV21(test.TestCase):
|
||||
@mock.patch('nova.objects.FlavorList.get_all',
|
||||
return_value=objects.FlavorList())
|
||||
def test_get_empty_flavor_list(self, mock_get):
|
||||
req = self.fake_request.blank(self._prefix + '/flavors')
|
||||
req = self._build_request('/flavors')
|
||||
flavors = self.controller.index(req)
|
||||
expected = {'flavors': []}
|
||||
self.assertEqual(flavors, expected)
|
||||
|
||||
def test_get_flavor_list_filter_min_ram(self):
|
||||
# Flavor lists may be filtered by minRam.
|
||||
req = self.fake_request.blank(self._prefix + '/flavors?minRam=512')
|
||||
req = self._build_request('/flavors?minRam=512')
|
||||
flavor = self.controller.index(req)
|
||||
expected = {
|
||||
"flavors": [
|
||||
@ -410,17 +430,20 @@ class FlavorsTestV21(test.TestCase):
|
||||
},
|
||||
],
|
||||
}
|
||||
if self.expect_description:
|
||||
expected['flavors'][0]['description'] = (
|
||||
fakes.FLAVORS['2'].description)
|
||||
self.assertEqual(flavor, expected)
|
||||
|
||||
def test_get_flavor_list_filter_invalid_min_ram(self):
|
||||
# Ensure you cannot list flavors with invalid minRam param.
|
||||
req = self.fake_request.blank(self._prefix + '/flavors?minRam=NaN')
|
||||
req = self._build_request('/flavors?minRam=NaN')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index, req)
|
||||
|
||||
def test_get_flavor_list_filter_min_disk(self):
|
||||
# Flavor lists may be filtered by minDisk.
|
||||
req = self.fake_request.blank(self._prefix + '/flavors?minDisk=20')
|
||||
req = self._build_request('/flavors?minDisk=20')
|
||||
flavor = self.controller.index(req)
|
||||
expected = {
|
||||
"flavors": [
|
||||
@ -442,11 +465,14 @@ class FlavorsTestV21(test.TestCase):
|
||||
},
|
||||
],
|
||||
}
|
||||
if self.expect_description:
|
||||
expected['flavors'][0]['description'] = (
|
||||
fakes.FLAVORS['2'].description)
|
||||
self.assertEqual(flavor, expected)
|
||||
|
||||
def test_get_flavor_list_filter_invalid_min_disk(self):
|
||||
# Ensure you cannot list flavors with invalid minDisk param.
|
||||
req = self.fake_request.blank(self._prefix + '/flavors?minDisk=NaN')
|
||||
req = self._build_request('/flavors?minDisk=NaN')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index, req)
|
||||
|
||||
@ -454,8 +480,7 @@ class FlavorsTestV21(test.TestCase):
|
||||
"""Tests that filtering work on flavor details and that minRam and
|
||||
minDisk filters can be combined
|
||||
"""
|
||||
req = self.fake_request.blank(self._prefix + '/flavors/detail'
|
||||
'?minRam=256&minDisk=20')
|
||||
req = self._build_request('/flavors/detail?minRam=256&minDisk=20')
|
||||
flavor = self.controller.detail(req)
|
||||
expected = {
|
||||
"flavors": [
|
||||
@ -484,6 +509,12 @@ class FlavorsTestV21(test.TestCase):
|
||||
self.assertEqual(expected, flavor)
|
||||
|
||||
|
||||
class FlavorsTestV2_55(FlavorsTestV21):
|
||||
"""Run the same tests as we would for v2.1 but with a description."""
|
||||
microversion = '2.55'
|
||||
expect_description = True
|
||||
|
||||
|
||||
class DisabledFlavorsWithRealDBTestV21(test.TestCase):
|
||||
"""Tests that disabled flavors should not be shown nor listed."""
|
||||
Controller = flavors_v21.FlavorsController
|
||||
|
@ -716,6 +716,7 @@ FLAVORS = {
|
||||
vcpu_weight=None,
|
||||
disabled=False,
|
||||
is_public=True,
|
||||
description=None
|
||||
),
|
||||
'2': objects.Flavor(
|
||||
id=2,
|
||||
@ -730,6 +731,7 @@ FLAVORS = {
|
||||
vcpu_weight=None,
|
||||
disabled=True,
|
||||
is_public=True,
|
||||
description='flavor 2 description'
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -313,6 +313,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
|
||||
"os_compute_api:os-flavor-extra-specs:delete",
|
||||
"os_compute_api:os-flavor-manage",
|
||||
"os_compute_api:os-flavor-manage:create",
|
||||
"os_compute_api:os-flavor-manage:update",
|
||||
"os_compute_api:os-flavor-manage:delete",
|
||||
"os_compute_api:os-floating-ips-bulk",
|
||||
"os_compute_api:os-floating-ip-dns:domain:delete",
|
||||
|
17
releasenotes/notes/flavor-description-02f8b8626da71a25.yaml
Normal file
17
releasenotes/notes/flavor-description-02f8b8626da71a25.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Microversion 2.55 adds a ``description`` field to the flavor resource in
|
||||
the following APIs:
|
||||
|
||||
* ``GET /flavors``
|
||||
* ``GET /flavors/detail``
|
||||
* ``GET /flavors/{flavor_id}``
|
||||
* ``POST /flavors``
|
||||
* ``PUT /flavors/{flavor_id}``
|
||||
|
||||
The embedded flavor description will not be included in server
|
||||
representations.
|
||||
|
||||
A new policy rule ``os_compute_api:os-flavor-manage:update`` is added
|
||||
to control access to the ``PUT /flavors/{flavor_id}`` API.
|
Loading…
x
Reference in New Issue
Block a user