From 034d7f37954e8bb46bb18df261adea51a0e3981f Mon Sep 17 00:00:00 2001
From: Matt Riedemann <mriedem.os@gmail.com>
Date: Wed, 25 Oct 2017 16:59:31 -0400
Subject: [PATCH] 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
---
 api-ref/source/flavors.inc                    |  84 +++++++--
 api-ref/source/parameters.yaml                |  23 +++
 .../v2.55/flavor-create-post-req.json         |  11 ++
 .../v2.55/flavor-create-post-resp.json        |  25 +++
 .../v2.55/flavor-update-req.json              |   5 +
 .../v2.55/flavor-update-resp.json             |  25 +++
 .../flavors/v2.55/flavor-get-resp.json        |  25 +++
 .../flavors/v2.55/flavors-detail-resp.json    | 165 ++++++++++++++++++
 .../flavors/v2.55/flavors-list-resp.json      | 109 ++++++++++++
 .../versions/v21-version-get-resp.json        |   2 +-
 .../versions/versions-get-resp.json           |   2 +-
 nova/api/openstack/api_version_request.py     |   3 +-
 nova/api/openstack/compute/flavor_access.py   |   7 +
 nova/api/openstack/compute/flavor_manage.py   |  38 +++-
 nova/api/openstack/compute/flavor_rxtx.py     |   4 +
 nova/api/openstack/compute/flavors.py         |   5 +-
 .../compute/rest_api_version_history.rst      |  13 ++
 nova/api/openstack/compute/routes.py          |   1 +
 .../compute/schemas/flavor_manage.py          |  34 ++++
 nova/api/openstack/compute/views/flavors.py   |  35 +++-
 nova/compute/flavors.py                       |   3 +-
 nova/policies/flavor_access.py                |   4 +
 nova/policies/flavor_manage.py                |  10 ++
 nova/policies/flavor_rxtx.py                  |   4 +
 .../v2.55/flavor-create-post-req.json.tpl     |  11 ++
 .../v2.55/flavor-create-post-resp.json.tpl    |  25 +++
 .../v2.55/flavor-update-req.json.tpl          |   5 +
 .../v2.55/flavor-update-resp.json.tpl         |  25 +++
 .../flavors/v2.55/flavor-get-resp.json.tpl    |  25 +++
 .../v2.55/flavors-detail-resp.json.tpl        | 165 ++++++++++++++++++
 .../flavors/v2.55/flavors-list-resp.json.tpl  | 109 ++++++++++++
 .../api_sample_tests/test_flavor_manage.py    |   9 +
 .../api_sample_tests/test_flavors.py          |  35 +++-
 .../notification_sample_tests/test_flavor.py  |  44 +++++
 .../openstack/compute/test_flavor_manage.py   | 128 +++++++++++++-
 .../api/openstack/compute/test_flavors.py     |  67 +++++--
 nova/tests/unit/api/openstack/fakes.py        |   2 +
 nova/tests/unit/test_policy.py                |   1 +
 .../flavor-description-02f8b8626da71a25.yaml  |  17 ++
 39 files changed, 1253 insertions(+), 52 deletions(-)
 create mode 100644 doc/api_samples/flavor-manage/v2.55/flavor-create-post-req.json
 create mode 100644 doc/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json
 create mode 100644 doc/api_samples/flavor-manage/v2.55/flavor-update-req.json
 create mode 100644 doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json
 create mode 100644 doc/api_samples/flavors/v2.55/flavor-get-resp.json
 create mode 100644 doc/api_samples/flavors/v2.55/flavors-detail-resp.json
 create mode 100644 doc/api_samples/flavors/v2.55/flavors-list-resp.json
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-req.json.tpl
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json.tpl
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-req.json.tpl
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-resp.json.tpl
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavor-get-resp.json.tpl
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-detail-resp.json.tpl
 create mode 100644 nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-list-resp.json.tpl
 create mode 100644 releasenotes/notes/flavor-description-02f8b8626da71a25.yaml

diff --git a/api-ref/source/flavors.inc b/api-ref/source/flavors.inc
index 6ef3feef1c7e..941e54783bab 100644
--- a/api-ref/source/flavors.inc
+++ b/api-ref/source/flavors.inc
@@ -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
diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml
index 774506e730ad..68b20fe20119 100644
--- a/api-ref/source/parameters.yaml
+++ b/api-ref/source/parameters.yaml
@@ -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
diff --git a/doc/api_samples/flavor-manage/v2.55/flavor-create-post-req.json b/doc/api_samples/flavor-manage/v2.55/flavor-create-post-req.json
new file mode 100644
index 000000000000..0d9926d72027
--- /dev/null
+++ b/doc/api_samples/flavor-manage/v2.55/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.55/flavor-create-post-resp.json b/doc/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json
new file mode 100644
index 000000000000..18ff7727cbae
--- /dev/null
+++ b/doc/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json
@@ -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"
+    }
+}
diff --git a/doc/api_samples/flavor-manage/v2.55/flavor-update-req.json b/doc/api_samples/flavor-manage/v2.55/flavor-update-req.json
new file mode 100644
index 000000000000..93c8e1e8ab23
--- /dev/null
+++ b/doc/api_samples/flavor-manage/v2.55/flavor-update-req.json
@@ -0,0 +1,5 @@
+{
+    "flavor": {
+        "description": "updated description"
+    }
+}
diff --git a/doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json b/doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json
new file mode 100644
index 000000000000..27c903d934cb
--- /dev/null
+++ b/doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json
@@ -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"
+    }
+}
diff --git a/doc/api_samples/flavors/v2.55/flavor-get-resp.json b/doc/api_samples/flavors/v2.55/flavor-get-resp.json
new file mode 100644
index 000000000000..575777240945
--- /dev/null
+++ b/doc/api_samples/flavors/v2.55/flavor-get-resp.json
@@ -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"
+    }
+}
diff --git a/doc/api_samples/flavors/v2.55/flavors-detail-resp.json b/doc/api_samples/flavors/v2.55/flavors-detail-resp.json
new file mode 100644
index 000000000000..2f181cca91fe
--- /dev/null
+++ b/doc/api_samples/flavors/v2.55/flavors-detail-resp.json
@@ -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"
+        }
+    ]
+}
diff --git a/doc/api_samples/flavors/v2.55/flavors-list-resp.json b/doc/api_samples/flavors/v2.55/flavors-list-resp.json
new file mode 100644
index 000000000000..f368ed5c66fd
--- /dev/null
+++ b/doc/api_samples/flavors/v2.55/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/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json
index 92d5a3e2eff7..f0bf9cd8a149 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.54",
+        "version": "2.55",
         "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 43956e0fb4b6..30fa5b2cc6c2 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.54",
+            "version": "2.55",
             "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 b72586934068..c3444df22804 100644
--- a/nova/api/openstack/api_version_request.py
+++ b/nova/api/openstack/api_version_request.py
@@ -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
diff --git a/nova/api/openstack/compute/flavor_access.py b/nova/api/openstack/compute/flavor_access.py
index b591212d6919..7ad7ff1cc38a 100644
--- a/nova/api/openstack/compute/flavor_access.py
+++ b/nova/api/openstack/compute/flavor_access.py
@@ -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)
diff --git a/nova/api/openstack/compute/flavor_manage.py b/nova/api/openstack/compute/flavor_manage.py
index b55da6a2ec8a..58ad59743006 100644
--- a/nova/api/openstack/compute/flavor_manage.py
+++ b/nova/api/openstack/compute/flavor_manage.py
@@ -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)
diff --git a/nova/api/openstack/compute/flavor_rxtx.py b/nova/api/openstack/compute/flavor_rxtx.py
index 374a561fd9a6..497490f30cf9 100644
--- a/nova/api/openstack/compute/flavor_rxtx.py
+++ b/nova/api/openstack/compute/flavor_rxtx.py
@@ -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']
diff --git a/nova/api/openstack/compute/flavors.py b/nova/api/openstack/compute/flavors.py
index f9b7e8bee732..d0f31f6eae7a 100644
--- a/nova/api/openstack/compute/flavors.py
+++ b/nova/api/openstack/compute/flavors.py
@@ -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."""
diff --git a/nova/api/openstack/compute/rest_api_version_history.rst b/nova/api/openstack/compute/rest_api_version_history.rst
index 8990fd7e0275..3f76b1683d2d 100644
--- a/nova/api/openstack/compute/rest_api_version_history.rst
+++ b/nova/api/openstack/compute/rest_api_version_history.rst
@@ -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.
diff --git a/nova/api/openstack/compute/routes.py b/nova/api/openstack/compute/routes.py
index 7b7d75cca694..929f825a8f19 100644
--- a/nova/api/openstack/compute/routes.py
+++ b/nova/api/openstack/compute/routes.py
@@ -429,6 +429,7 @@ ROUTE_LIST = (
     }),
     ('/flavors/{id}', {
         'GET': [flavor_controller, 'show'],
+        'PUT': [flavor_controller, 'update'],
         'DELETE': [flavor_controller, 'delete']
     }),
     ('/flavors/{id}/action', {
diff --git a/nova/api/openstack/compute/schemas/flavor_manage.py b/nova/api/openstack/compute/schemas/flavor_manage.py
index 5eb53263e9cd..347f557efce3 100644
--- a/nova/api/openstack/compute/schemas/flavor_manage.py
+++ b/nova/api/openstack/compute/schemas/flavor_manage.py
@@ -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,
+}
diff --git a/nova/api/openstack/compute/views/flavors.py b/nova/api/openstack/compute/views/flavors.py
index 0295929cf788..04f10831eff8 100644
--- a/nova/api/openstack/compute/views/flavors.py
+++ b/nova/api/openstack/compute/views/flavors.py
@@ -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,
diff --git a/nova/compute/flavors.py b/nova/compute/flavors.py
index 281d12b3f44b..5bc08e4f74ee 100644
--- a/nova/compute/flavors.py
+++ b/nova/compute/flavors.py
@@ -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):
diff --git a/nova/policies/flavor_access.py b/nova/policies/flavor_access.py
index 49f605c78439..5a780b293a44 100644
--- a/nova/policies/flavor_access.py
+++ b/nova/policies/flavor_access.py
@@ -71,6 +71,10 @@ to a flavor via an os-flavor-access API.
                 'method': 'POST',
                 'path': '/flavors'
             },
+            {
+                'method': 'PUT',
+                'path': '/flavors/{flavor_id}'
+            },
         ]),
 ]
 
diff --git a/nova/policies/flavor_manage.py b/nova/policies/flavor_manage.py
index ba12143e58ce..f91f26e71573 100644
--- a/nova/policies/flavor_manage.py
+++ b/nova/policies/flavor_manage.py
@@ -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,
diff --git a/nova/policies/flavor_rxtx.py b/nova/policies/flavor_rxtx.py
index 8ad443a25a93..8a654a867bfd 100644
--- a/nova/policies/flavor_rxtx.py
+++ b/nova/policies/flavor_rxtx.py
@@ -40,6 +40,10 @@ flavor_rxtx_policies = [
                 'method': 'POST',
                 'path': '/flavors'
             },
+            {
+                'method': 'PUT',
+                'path': '/flavors/{flavor_id}'
+            },
         ]),
 ]
 
diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-req.json.tpl
new file mode 100644
index 000000000000..2065b445cf3e
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/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.55/flavor-create-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json.tpl
new file mode 100644
index 000000000000..2fddca9b4cfd
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json.tpl
@@ -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"
+    }
+}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-req.json.tpl
new file mode 100644
index 000000000000..93c8e1e8ab23
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/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.55/flavor-update-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-resp.json.tpl
new file mode 100644
index 000000000000..27c903d934cb
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.55/flavor-update-resp.json.tpl
@@ -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"
+    }
+}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavor-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavor-get-resp.json.tpl
new file mode 100644
index 000000000000..1de13dd0e23d
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavor-get-resp.json.tpl
@@ -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"
+    }
+}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-detail-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-detail-resp.json.tpl
new file mode 100644
index 000000000000..164520a63d0c
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-detail-resp.json.tpl
@@ -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"
+        }
+    ]
+}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-list-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-list-resp.json.tpl
new file mode 100644
index 000000000000..79a23e8760ba
--- /dev/null
+++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.55/flavors-list-resp.json.tpl
@@ -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"
+        }
+    ]
+}
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 7b3e6149957a..6ecb1ddb4186 100644
--- a/nova/tests/functional/api_sample_tests/test_flavor_manage.py
+++ b/nova/tests/functional/api_sample_tests/test_flavor_manage.py
@@ -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)
diff --git a/nova/tests/functional/api_sample_tests/test_flavors.py b/nova/tests/functional/api_sample_tests/test_flavors.py
index 4745c8a52b5d..b2b42975b924 100644
--- a/nova/tests/functional/api_sample_tests/test_flavors.py
+++ b/nova/tests/functional/api_sample_tests/test_flavors.py
@@ -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}
diff --git a/nova/tests/functional/notification_sample_tests/test_flavor.py b/nova/tests/functional/notification_sample_tests/test_flavor.py
index b4011fa6b77e..e10e8df76007 100644
--- a/nova/tests/functional/notification_sample_tests/test_flavor.py
+++ b/nova/tests/functional/notification_sample_tests/test_flavor.py
@@ -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])
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 c4aae37bc649..f5f68f45ceab 100644
--- a/nova/tests/unit/api/openstack/compute/test_flavor_manage.py
+++ b/nova/tests/unit/api/openstack/compute/test_flavor_manage.py
@@ -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())
diff --git a/nova/tests/unit/api/openstack/compute/test_flavors.py b/nova/tests/unit/api/openstack/compute/test_flavors.py
index b842dc6a9dcf..77b233f1a51b 100644
--- a/nova/tests/unit/api/openstack/compute/test_flavors.py
+++ b/nova/tests/unit/api/openstack/compute/test_flavors.py
@@ -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
diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py
index 200d1335e9aa..5ccd3f1be68e 100644
--- a/nova/tests/unit/api/openstack/fakes.py
+++ b/nova/tests/unit/api/openstack/fakes.py
@@ -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'
     ),
 }
 
diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py
index 0d2914fcc44b..f0f7af35b4cf 100644
--- a/nova/tests/unit/test_policy.py
+++ b/nova/tests/unit/test_policy.py
@@ -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",
diff --git a/releasenotes/notes/flavor-description-02f8b8626da71a25.yaml b/releasenotes/notes/flavor-description-02f8b8626da71a25.yaml
new file mode 100644
index 000000000000..cebffa2c8288
--- /dev/null
+++ b/releasenotes/notes/flavor-description-02f8b8626da71a25.yaml
@@ -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.