Browse Source

Enforce FlavorExtraSpecs Key format.

When creating a key name with characters like '/' that are not
being encoded or cannot be handled as expected by routes when
matching the url, then it's not possible to remove them anymore.
The new format apply the restriction: "[\w\.\- :]+$"

This change will fix the bug without API change.

Change-Id: I64bc098c4bde33c40fa149191a751ea3eb19f932
Closes-Bug: #1256119
tags/2014.1.b3
Leandro I. Costantino 5 years ago
parent
commit
050ce0e589

+ 8
- 1
nova/api/openstack/compute/contrib/flavorextraspecs.py View File

@@ -22,11 +22,11 @@ from webob import exc
22 22
 from nova.api.openstack import extensions
23 23
 from nova.api.openstack import wsgi
24 24
 from nova.api.openstack import xmlutil
25
+from nova.compute import flavors
25 26
 from nova import db
26 27
 from nova import exception
27 28
 from nova.openstack.common.gettextutils import _
28 29
 
29
-
30 30
 authorize = extensions.extension_authorizer('compute', 'flavorextraspecs')
31 31
 
32 32
 
@@ -57,6 +57,12 @@ class FlavorExtraSpecsController(object):
57 57
             expl = _('No Request Body')
58 58
             raise exc.HTTPBadRequest(explanation=expl)
59 59
 
60
+    def _check_key_names(self, keys):
61
+        try:
62
+            flavors.validate_extra_spec_keys(keys)
63
+        except exception.InvalidInput as error:
64
+            raise exc.HTTPBadRequest(explanation=error.format_message())
65
+
60 66
     @wsgi.serializers(xml=ExtraSpecsTemplate)
61 67
     def index(self, req, flavor_id):
62 68
         """Returns the list of extra specs for a given flavor."""
@@ -70,6 +76,7 @@ class FlavorExtraSpecsController(object):
70 76
         authorize(context, action='create')
71 77
         self._check_body(body)
72 78
         specs = body.get('extra_specs')
79
+        self._check_key_names(specs.keys())
73 80
         try:
74 81
             db.flavor_extra_specs_update_or_create(context,
75 82
                                                               flavor_id,

+ 8
- 0
nova/api/openstack/compute/plugins/v3/flavors_extraspecs.py View File

@@ -19,6 +19,7 @@ import webob
19 19
 
20 20
 from nova.api.openstack import extensions
21 21
 from nova.api.openstack import wsgi
22
+from nova.compute import flavors
22 23
 from nova import db
23 24
 from nova import exception
24 25
 from nova.openstack.common.db import exception as db_exc
@@ -43,6 +44,12 @@ class FlavorExtraSpecsController(object):
43 44
             expl = _('No Request Body')
44 45
             raise webob.exc.HTTPBadRequest(explanation=expl)
45 46
 
47
+    def _check_key_names(self, keys):
48
+        try:
49
+            flavors.validate_extra_spec_keys(keys)
50
+        except exception.InvalidInput as error:
51
+            raise webob.exc.HTTPBadRequest(explanation=error.format_message())
52
+
46 53
     @extensions.expected_errors(())
47 54
     def index(self, req, flavor_id):
48 55
         """Returns the list of extra specs for a given flavor."""
@@ -59,6 +66,7 @@ class FlavorExtraSpecsController(object):
59 66
         specs = body.get('extra_specs', {})
60 67
         if not specs or type(specs) is not dict:
61 68
             raise webob.exc.HTTPBadRequest(_('No or bad extra_specs provided'))
69
+        self._check_key_names(specs.keys())
62 70
         try:
63 71
             db.flavor_extra_specs_update_or_create(context, flavor_id,
64 72
                                                           specs)

+ 11
- 0
nova/compute/flavors.py View File

@@ -55,6 +55,9 @@ LOG = logging.getLogger(__name__)
55 55
 VALID_ID_REGEX = re.compile("^[\w\.\- ]*$")
56 56
 VALID_NAME_REGEX = re.compile("^[\w\.\- ]*$", re.UNICODE)
57 57
 
58
+# Validate extra specs key names.
59
+VALID_EXTRASPEC_NAME_REGEX = re.compile(r"[\w\.\- :]+$", re.UNICODE)
60
+
58 61
 
59 62
 def _int_or_none(val):
60 63
     if val is not None:
@@ -309,3 +312,11 @@ def delete_flavor_info(metadata, *prefixes):
309 312
             del metadata[to_key]
310 313
     pci_request.delete_flavor_pci_info(metadata, *prefixes)
311 314
     return metadata
315
+
316
+
317
+def validate_extra_spec_keys(key_names_list):
318
+    for key_name in key_names_list:
319
+        if not VALID_EXTRASPEC_NAME_REGEX.match(key_name):
320
+            expl = _('Key Names can only contain alphanumeric characters, '
321
+                     'periods, dashes, underscores, colons and spaces.')
322
+            raise exception.InvalidInput(message=expl)

+ 25
- 0
nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py View File

@@ -15,6 +15,7 @@
15 15
 #    License for the specific language governing permissions and limitations
16 16
 #    under the License.
17 17
 
18
+import mock
18 19
 import webob
19 20
 
20 21
 from nova.api.openstack.compute.contrib import flavorextraspecs
@@ -153,6 +154,30 @@ class FlavorsExtraSpecsTest(test.TestCase):
153 154
         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
154 155
                           req, 1, '')
155 156
 
157
+    @mock.patch('nova.db.flavor_extra_specs_update_or_create')
158
+    def test_create_invalid_specs_key(self, mock_flavor_extra_specs):
159
+        invalid_keys = ("key1/", "<key>", "$$akey$", "!akey", "")
160
+        mock_flavor_extra_specs.side_effects = return_create_flavor_extra_specs
161
+
162
+        for key in invalid_keys:
163
+            body = {"extra_specs": {key: "value1"}}
164
+            req = fakes.HTTPRequest.blank('/v2/fake/flavors/1/os-extra_specs',
165
+                                       use_admin_context=True)
166
+            self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
167
+                              req, 1, body)
168
+
169
+    @mock.patch('nova.db.flavor_extra_specs_update_or_create')
170
+    def test_create_valid_specs_key(self, mock_flavor_extra_specs):
171
+        valid_keys = ("key1", "month.price", "I_am-a Key", "finance:g2")
172
+        mock_flavor_extra_specs.side_effects = return_create_flavor_extra_specs
173
+
174
+        for key in valid_keys:
175
+            body = {"extra_specs": {key: "value1"}}
176
+            req = fakes.HTTPRequest.blank('/v2/fake/flavors/1/os-extra_specs',
177
+                                       use_admin_context=True)
178
+            res_dict = self.controller.create(req, 1, body)
179
+            self.assertEqual('value1', res_dict['extra_specs'][key])
180
+
156 181
     def test_update_item(self):
157 182
         self.stubs.Set(nova.db,
158 183
                        'flavor_extra_specs_update_or_create',

+ 28
- 0
nova/tests/api/openstack/compute/plugins/v3/test_flavors_extra_specs.py View File

@@ -15,6 +15,7 @@
15 15
 #    License for the specific language governing permissions and limitations
16 16
 #    under the License.
17 17
 
18
+import mock
18 19
 import webob
19 20
 
20 21
 from nova.api.openstack.compute.plugins.v3 import flavors_extraspecs
@@ -178,6 +179,33 @@ class FlavorsExtraSpecsTest(test.TestCase):
178 179
         self.assertRaises(webob.exc.HTTPConflict, self.controller.create,
179 180
                           req, 1, body)
180 181
 
182
+    @mock.patch('nova.db.flavor_extra_specs_update_or_create')
183
+    def test_create_invalid_specs_key(self, mock_flavor_extra_specs):
184
+        invalid_keys = ("key1/", "<key>", "$$akey$", "!akey", "")
185
+        mock_flavor_extra_specs.side_effects = return_create_flavor_extra_specs
186
+
187
+        for key in invalid_keys:
188
+            body = {"extra_specs": {key: "value1"}}
189
+
190
+            req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs',
191
+                                       use_admin_context=True)
192
+            self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
193
+                          req, 1, body)
194
+
195
+    @mock.patch('nova.db.flavor_extra_specs_update_or_create')
196
+    def test_create_valid_specs_key(self, mock_flavor_extra_specs):
197
+        valid_keys = ("key1", "month.price", "I_am-a Key", "finance:g2")
198
+        mock_flavor_extra_specs.side_effects = return_create_flavor_extra_specs
199
+
200
+        for key in valid_keys:
201
+            body = {"extra_specs": {key: "value1"}}
202
+
203
+            req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs',
204
+                                       use_admin_context=True)
205
+            res_dict = self.controller.create(req, 1, body)
206
+            self.assertEqual('value1', res_dict['extra_specs'][key])
207
+            self.assertEqual(self.controller.create.wsgi_code, 201)
208
+
181 209
     def test_update_item(self):
182 210
         self.stubs.Set(nova.db,
183 211
                        'flavor_extra_specs_update_or_create',

Loading…
Cancel
Save