2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
20 from oslo_config import cfg
21 from oslo_utils import uuidutils
24 from neutron.api.v2 import attributes as attr
25 from neutron import context
26 from neutron.db import api as dbapi
27 from neutron.db import flavors_db
28 from neutron.db import servicetype_db
29 from neutron.extensions import flavors
30 from neutron.plugins.common import constants
31 from neutron.services.flavors import flavors_plugin
32 from neutron.services import provider_configuration as provconf
33 from neutron.tests import base
34 from neutron.tests.unit.api.v2 import test_base
35 from neutron.tests.unit.db import test_db_base_plugin_v2
36 from neutron.tests.unit.extensions import base as extension
38 _uuid = uuidutils.generate_uuid
39 _get_path = test_base._get_path
41 _driver = ('neutron.tests.unit.extensions.test_flavors.'
44 _long_name = 'x' * (attr.NAME_MAX_LEN + 1)
45 _long_description = 'x' * (attr.LONG_DESCRIPTION_MAX_LEN + 1)
48 class FlavorExtensionTestCase(extension.ExtensionTestCase):
51 super(FlavorExtensionTestCase, self).setUp()
53 'neutron.services.flavors.flavors_plugin.FlavorsPlugin',
54 constants.FLAVORS, flavors.RESOURCE_ATTRIBUTE_MAP,
55 flavors.Flavors, '', supported_extension_aliases='flavors')
57 def test_create_flavor(self):
58 tenant_id = uuidutils.generate_uuid()
59 # Use service_type FLAVORS since plugin must be loaded to validate
60 data = {'flavor': {'name': 'GOLD',
61 'service_type': constants.FLAVORS,
62 'description': 'the best flavor',
63 'tenant_id': tenant_id,
66 expected = copy.deepcopy(data)
67 expected['flavor']['service_profiles'] = []
69 instance = self.plugin.return_value
70 instance.create_flavor.return_value = expected['flavor']
71 res = self.api.post(_get_path('flavors', fmt=self.fmt),
73 content_type='application/%s' % self.fmt)
75 instance.create_flavor.assert_called_with(mock.ANY,
77 res = self.deserialize(res)
78 self.assertIn('flavor', res)
79 self.assertEqual(expected, res)
81 def test_create_flavor_invalid_service_type(self):
82 tenant_id = uuidutils.generate_uuid()
83 data = {'flavor': {'name': 'GOLD',
84 'service_type': 'BROKEN',
85 'description': 'the best flavor',
86 'tenant_id': tenant_id,
88 self.api.post(_get_path('flavors', fmt=self.fmt),
90 content_type='application/%s' % self.fmt,
91 status=exc.HTTPBadRequest.code)
93 def test_create_flavor_too_long_name(self):
94 tenant_id = uuidutils.generate_uuid()
95 data = {'flavor': {'name': _long_name,
96 'service_type': constants.FLAVORS,
97 'description': 'the best flavor',
98 'tenant_id': tenant_id,
100 self.api.post(_get_path('flavors', fmt=self.fmt),
101 self.serialize(data),
102 content_type='application/%s' % self.fmt,
103 status=exc.HTTPBadRequest.code)
105 def test_create_flavor_too_long_description(self):
106 tenant_id = uuidutils.generate_uuid()
107 data = {'flavor': {'name': _long_name,
108 'service_type': constants.FLAVORS,
109 'description': _long_description,
110 'tenant_id': tenant_id,
112 self.api.post(_get_path('flavors', fmt=self.fmt),
113 self.serialize(data),
114 content_type='application/%s' % self.fmt,
115 status=exc.HTTPBadRequest.code)
117 def test_create_flavor_invalid_enabled(self):
118 tenant_id = uuidutils.generate_uuid()
119 data = {'flavor': {'name': _long_name,
120 'service_type': constants.FLAVORS,
121 'description': 'the best flavor',
122 'tenant_id': tenant_id,
123 'enabled': 'BROKEN'}}
124 self.api.post(_get_path('flavors', fmt=self.fmt),
125 self.serialize(data),
126 content_type='application/%s' % self.fmt,
127 status=exc.HTTPBadRequest.code)
129 def test_update_flavor(self):
130 flavor_id = 'fake_id'
131 data = {'flavor': {'name': 'GOLD',
132 'description': 'the best flavor',
134 expected = copy.copy(data)
135 expected['flavor']['service_profiles'] = []
137 instance = self.plugin.return_value
138 instance.update_flavor.return_value = expected['flavor']
139 res = self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
140 self.serialize(data),
141 content_type='application/%s' % self.fmt)
143 instance.update_flavor.assert_called_with(mock.ANY,
146 res = self.deserialize(res)
147 self.assertIn('flavor', res)
148 self.assertEqual(expected, res)
150 def test_update_flavor_too_long_name(self):
151 flavor_id = 'fake_id'
152 data = {'flavor': {'name': _long_name,
153 'description': 'the best flavor',
155 self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
156 self.serialize(data),
157 content_type='application/%s' % self.fmt,
158 status=exc.HTTPBadRequest.code)
160 def test_update_flavor_too_long_description(self):
161 flavor_id = 'fake_id'
162 data = {'flavor': {'name': 'GOLD',
163 'description': _long_description,
165 self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
166 self.serialize(data),
167 content_type='application/%s' % self.fmt,
168 status=exc.HTTPBadRequest.code)
170 def test_update_flavor_invalid_enabled(self):
171 flavor_id = 'fake_id'
172 data = {'flavor': {'name': 'GOLD',
173 'description': _long_description,
174 'enabled': 'BROKEN'}}
175 self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
176 self.serialize(data),
177 content_type='application/%s' % self.fmt,
178 status=exc.HTTPBadRequest.code)
180 def test_delete_flavor(self):
181 flavor_id = 'fake_id'
182 instance = self.plugin.return_value
183 self.api.delete(_get_path('flavors', id=flavor_id, fmt=self.fmt),
184 content_type='application/%s' % self.fmt)
186 instance.delete_flavor.assert_called_with(mock.ANY,
189 def test_show_flavor(self):
190 flavor_id = 'fake_id'
191 expected = {'flavor': {'id': flavor_id,
193 'description': 'the best flavor',
195 'service_profiles': ['profile-1']}}
196 instance = self.plugin.return_value
197 instance.get_flavor.return_value = expected['flavor']
198 res = self.api.get(_get_path('flavors', id=flavor_id, fmt=self.fmt))
199 instance.get_flavor.assert_called_with(mock.ANY,
202 res = self.deserialize(res)
203 self.assertEqual(expected, res)
205 def test_get_flavors(self):
206 data = {'flavors': [{'id': 'id1',
208 'description': 'the best flavor',
210 'service_profiles': ['profile-1']},
213 'description': 'the best flavor',
215 'service_profiles': ['profile-2', 'profile-1']}]}
216 instance = self.plugin.return_value
217 instance.get_flavors.return_value = data['flavors']
218 res = self.api.get(_get_path('flavors', fmt=self.fmt))
219 instance.get_flavors.assert_called_with(mock.ANY,
222 res = self.deserialize(res)
223 self.assertEqual(data, res)
225 def test_create_service_profile(self):
226 tenant_id = uuidutils.generate_uuid()
227 expected = {'service_profile': {'description': 'the best sp',
229 'tenant_id': tenant_id,
231 'metainfo': '{"data": "value"}'}}
233 instance = self.plugin.return_value
234 instance.create_service_profile.return_value = (
235 expected['service_profile'])
236 res = self.api.post(_get_path('service_profiles', fmt=self.fmt),
237 self.serialize(expected),
238 content_type='application/%s' % self.fmt)
239 instance.create_service_profile.assert_called_with(
241 service_profile=expected)
242 res = self.deserialize(res)
243 self.assertIn('service_profile', res)
244 self.assertEqual(expected, res)
246 def test_create_service_profile_too_long_description(self):
247 tenant_id = uuidutils.generate_uuid()
248 expected = {'service_profile': {'description': _long_description,
250 'tenant_id': tenant_id,
252 'metainfo': '{"data": "value"}'}}
253 self.api.post(_get_path('service_profiles', fmt=self.fmt),
254 self.serialize(expected),
255 content_type='application/%s' % self.fmt,
256 status=exc.HTTPBadRequest.code)
258 def test_create_service_profile_too_long_driver(self):
259 tenant_id = uuidutils.generate_uuid()
260 expected = {'service_profile': {'description': 'the best sp',
261 'driver': _long_description,
262 'tenant_id': tenant_id,
264 'metainfo': '{"data": "value"}'}}
265 self.api.post(_get_path('service_profiles', fmt=self.fmt),
266 self.serialize(expected),
267 content_type='application/%s' % self.fmt,
268 status=exc.HTTPBadRequest.code)
270 def test_create_service_profile_invalid_enabled(self):
271 tenant_id = uuidutils.generate_uuid()
272 expected = {'service_profile': {'description': 'the best sp',
274 'tenant_id': tenant_id,
276 'metainfo': '{"data": "value"}'}}
277 self.api.post(_get_path('service_profiles', fmt=self.fmt),
278 self.serialize(expected),
279 content_type='application/%s' % self.fmt,
280 status=exc.HTTPBadRequest.code)
282 def test_update_service_profile(self):
284 expected = {'service_profile': {'description': 'the best sp',
286 'metainfo': '{"data1": "value3"}'}}
288 instance = self.plugin.return_value
289 instance.update_service_profile.return_value = (
290 expected['service_profile'])
291 res = self.api.put(_get_path('service_profiles',
292 id=sp_id, fmt=self.fmt),
293 self.serialize(expected),
294 content_type='application/%s' % self.fmt)
296 instance.update_service_profile.assert_called_with(
299 service_profile=expected)
300 res = self.deserialize(res)
301 self.assertIn('service_profile', res)
302 self.assertEqual(expected, res)
304 def test_update_service_profile_too_long_description(self):
306 expected = {'service_profile': {'description': 'the best sp',
308 'metainfo': '{"data1": "value3"}'}}
309 self.api.put(_get_path('service_profiles',
310 id=sp_id, fmt=self.fmt),
311 self.serialize(expected),
312 content_type='application/%s' % self.fmt,
313 status=exc.HTTPBadRequest.code)
315 def test_update_service_profile_invalid_enabled(self):
317 expected = {'service_profile': {'description': 'the best sp',
319 'metainfo': '{"data1": "value3"}'}}
320 self.api.put(_get_path('service_profiles',
321 id=sp_id, fmt=self.fmt),
322 self.serialize(expected),
323 content_type='application/%s' % self.fmt,
324 status=exc.HTTPBadRequest.code)
326 def test_delete_service_profile(self):
328 instance = self.plugin.return_value
329 self.api.delete(_get_path('service_profiles', id=sp_id, fmt=self.fmt),
330 content_type='application/%s' % self.fmt)
331 instance.delete_service_profile.assert_called_with(mock.ANY,
334 def test_show_service_profile(self):
336 expected = {'service_profile': {'id': 'id1',
338 'description': 'desc',
341 instance = self.plugin.return_value
342 instance.get_service_profile.return_value = (
343 expected['service_profile'])
344 res = self.api.get(_get_path('service_profiles',
345 id=sp_id, fmt=self.fmt))
346 instance.get_service_profile.assert_called_with(mock.ANY,
349 res = self.deserialize(res)
350 self.assertEqual(expected, res)
352 def test_get_service_profiles(self):
353 expected = {'service_profiles': [{'id': 'id1',
355 'description': 'desc',
360 'description': 'desc',
363 instance = self.plugin.return_value
364 instance.get_service_profiles.return_value = (
365 expected['service_profiles'])
366 res = self.api.get(_get_path('service_profiles', fmt=self.fmt))
367 instance.get_service_profiles.assert_called_with(mock.ANY,
370 res = self.deserialize(res)
371 self.assertEqual(expected, res)
373 def test_associate_service_profile_with_flavor(self):
374 tenant_id = uuidutils.generate_uuid()
375 expected = {'service_profile': {'id': _uuid(),
376 'tenant_id': tenant_id}}
377 instance = self.plugin.return_value
378 instance.create_flavor_service_profile.return_value = (
379 expected['service_profile'])
380 res = self.api.post('/flavors/fl_id/service_profiles',
381 self.serialize(expected),
382 content_type='application/%s' % self.fmt)
383 instance.create_flavor_service_profile.assert_called_with(
384 mock.ANY, service_profile=expected, flavor_id='fl_id')
385 res = self.deserialize(res)
386 self.assertEqual(expected, res)
388 def test_disassociate_service_profile_with_flavor(self):
389 instance = self.plugin.return_value
390 instance.delete_flavor_service_profile.return_value = None
391 self.api.delete('/flavors/fl_id/service_profiles/%s' % 'fake_spid',
392 content_type='application/%s' % self.fmt)
393 instance.delete_flavor_service_profile.assert_called_with(
398 def test_update_association_error(self):
399 """Confirm that update is not permitted with user error."""
400 new_id = uuidutils.generate_uuid()
401 data = {'service_profile': {'id': new_id}}
402 self.api.put('/flavors/fl_id/service_profiles/%s' % 'fake_spid',
403 self.serialize(data),
404 content_type='application/%s' % self.fmt,
405 status=exc.HTTPBadRequest.code)
408 class DummyCorePlugin(object):
412 class DummyServicePlugin(object):
414 def driver_loaded(self, driver, service_profile):
417 def get_plugin_type(self):
418 return constants.DUMMY
420 def get_plugin_description(self):
421 return "Dummy service plugin, aware of flavors"
424 class DummyServiceDriver(object):
427 def get_service_type():
428 return constants.DUMMY
430 def __init__(self, plugin):
434 class FlavorPluginTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
437 super(FlavorPluginTestCase, self).setUp()
440 cfg.CONF.set_override(
442 'neutron.tests.unit.extensions.test_flavors.DummyCorePlugin')
443 cfg.CONF.set_override(
445 ['neutron.tests.unit.extensions.test_flavors.DummyServicePlugin'])
448 fixtures.MonkeyPatch('neutron.manager.NeutronManager._instance'))
450 self.plugin = flavors_plugin.FlavorsPlugin()
451 self.ctx = context.get_admin_context()
453 providers = [DummyServiceDriver.get_service_type() +
454 ":" + _provider + ":" + _driver]
455 self.service_manager = servicetype_db.ServiceTypeManager.get_instance()
456 self.service_providers = mock.patch.object(
457 provconf.NeutronModule, 'service_providers').start()
458 self.service_providers.return_value = providers
459 for provider in providers:
460 self.service_manager.add_provider_configuration(
461 provider.split(':')[0], provconf.ProviderConfiguration())
465 def _create_flavor(self, description=None):
466 flavor = {'flavor': {'name': 'GOLD',
467 'service_type': constants.DUMMY,
468 'description': description or 'the best flavor',
470 return self.plugin.create_flavor(self.ctx, flavor), flavor
472 def test_create_flavor(self):
473 self._create_flavor()
474 res = self.ctx.session.query(flavors_db.Flavor).all()
475 self.assertEqual(1, len(res))
476 self.assertEqual('GOLD', res[0]['name'])
477 self.assertEqual(constants.DUMMY, res[0]['service_type'])
479 def test_update_flavor(self):
480 fl, flavor = self._create_flavor()
481 flavor = {'flavor': {'name': 'Silver',
483 self.plugin.update_flavor(self.ctx, fl['id'], flavor)
484 res = (self.ctx.session.query(flavors_db.Flavor).
485 filter_by(id=fl['id']).one())
486 self.assertEqual('Silver', res['name'])
487 self.assertFalse(res['enabled'])
489 def test_delete_flavor(self):
490 fl, data = self._create_flavor()
491 self.plugin.delete_flavor(self.ctx, fl['id'])
492 res = (self.ctx.session.query(flavors_db.Flavor).all())
493 self.assertFalse(res)
495 def test_show_flavor(self):
496 fl, data = self._create_flavor()
497 show_fl = self.plugin.get_flavor(self.ctx, fl['id'])
498 self.assertEqual(fl, show_fl)
500 def test_get_flavors(self):
501 fl, flavor = self._create_flavor()
502 flavor['flavor']['name'] = 'SILVER'
503 self.plugin.create_flavor(self.ctx, flavor)
504 show_fl = self.plugin.get_flavors(self.ctx)
505 self.assertEqual(2, len(show_fl))
507 def _create_service_profile(self, description=None):
508 data = {'service_profile':
509 {'description': description or 'the best sp',
512 'metainfo': '{"data": "value"}'}}
513 sp = self.plugin.create_service_profile(self.ctx,
517 def test_create_service_profile(self):
518 sp, data = self._create_service_profile()
519 res = (self.ctx.session.query(flavors_db.ServiceProfile).
520 filter_by(id=sp['id']).one())
521 self.assertEqual(data['service_profile']['driver'], res['driver'])
522 self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
524 def test_create_service_profile_empty_driver(self):
525 data = {'service_profile':
526 {'description': 'the best sp',
529 'metainfo': '{"data": "value"}'}}
530 sp = self.plugin.create_service_profile(self.ctx,
532 res = (self.ctx.session.query(flavors_db.ServiceProfile).
533 filter_by(id=sp['id']).one())
534 self.assertEqual(data['service_profile']['driver'], res['driver'])
535 self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
537 def test_create_service_profile_invalid_driver(self):
538 data = {'service_profile':
539 {'description': 'the best sp',
542 'metainfo': '{"data": "value"}'}}
543 self.assertRaises(flavors.ServiceProfileDriverNotFound,
544 self.plugin.create_service_profile,
548 def test_create_service_profile_invalid_empty(self):
549 data = {'service_profile':
554 self.assertRaises(flavors.ServiceProfileEmpty,
555 self.plugin.create_service_profile,
559 def test_update_service_profile(self):
560 sp, data = self._create_service_profile()
561 data['service_profile']['metainfo'] = '{"data": "value1"}'
562 sp = self.plugin.update_service_profile(self.ctx, sp['id'],
564 res = (self.ctx.session.query(flavors_db.ServiceProfile).
565 filter_by(id=sp['id']).one())
566 self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
568 def test_delete_service_profile(self):
569 sp, data = self._create_service_profile()
570 self.plugin.delete_service_profile(self.ctx, sp['id'])
571 res = self.ctx.session.query(flavors_db.ServiceProfile).all()
572 self.assertFalse(res)
574 def test_show_service_profile(self):
575 sp, data = self._create_service_profile()
576 sp_show = self.plugin.get_service_profile(self.ctx, sp['id'])
577 self.assertEqual(sp, sp_show)
579 def test_get_service_profiles(self):
580 self._create_service_profile()
581 self._create_service_profile(description='another sp')
582 self.assertEqual(2, len(self.plugin.get_service_profiles(self.ctx)))
584 def test_associate_service_profile_with_flavor(self):
585 sp, data = self._create_service_profile()
586 fl, data = self._create_flavor()
587 self.plugin.create_flavor_service_profile(
589 {'service_profile': {'id': sp['id']}},
592 self.ctx.session.query(flavors_db.FlavorServiceProfileBinding).
594 self.assertEqual(fl['id'], binding['flavor_id'])
595 self.assertEqual(sp['id'], binding['service_profile_id'])
597 res = self.plugin.get_flavor(self.ctx, fl['id'])
598 self.assertEqual(1, len(res['service_profiles']))
599 self.assertEqual(sp['id'], res['service_profiles'][0])
601 res = self.plugin.get_service_profile(self.ctx, sp['id'])
602 self.assertEqual(1, len(res['flavors']))
603 self.assertEqual(fl['id'], res['flavors'][0])
605 def test_autodelete_flavor_associations(self):
606 sp, data = self._create_service_profile()
607 fl, data = self._create_flavor()
608 self.plugin.create_flavor_service_profile(
610 {'service_profile': {'id': sp['id']}},
612 self.plugin.delete_flavor(self.ctx, fl['id'])
614 self.ctx.session.query(flavors_db.FlavorServiceProfileBinding).
616 self.assertIsNone(binding)
618 def test_associate_service_profile_with_flavor_exists(self):
619 sp, data = self._create_service_profile()
620 fl, data = self._create_flavor()
621 self.plugin.create_flavor_service_profile(
623 {'service_profile': {'id': sp['id']}},
625 self.assertRaises(flavors.FlavorServiceProfileBindingExists,
626 self.plugin.create_flavor_service_profile,
628 {'service_profile': {'id': sp['id']}},
631 def test_disassociate_service_profile_with_flavor(self):
632 sp, data = self._create_service_profile()
633 fl, data = self._create_flavor()
634 self.plugin.create_flavor_service_profile(
636 {'service_profile': {'id': sp['id']}},
638 self.plugin.delete_flavor_service_profile(
639 self.ctx, sp['id'], fl['id'])
641 self.ctx.session.query(flavors_db.FlavorServiceProfileBinding).
643 self.assertIsNone(binding)
646 flavors.FlavorServiceProfileBindingNotFound,
647 self.plugin.delete_flavor_service_profile,
648 self.ctx, sp['id'], fl['id'])
650 def test_delete_service_profile_in_use(self):
651 sp, data = self._create_service_profile()
652 fl, data = self._create_flavor()
653 self.plugin.create_flavor_service_profile(
655 {'service_profile': {'id': sp['id']}},
658 flavors.ServiceProfileInUse,
659 self.plugin.delete_service_profile,
663 def test_get_flavor_next_provider_no_binding(self):
664 fl, data = self._create_flavor()
666 flavors.FlavorServiceProfileBindingNotFound,
667 self.plugin.get_flavor_next_provider,
671 def test_get_flavor_next_provider_disabled(self):
672 data = {'service_profile':
673 {'description': 'the best sp',
676 'metainfo': '{"data": "value"}'}}
677 sp = self.plugin.create_service_profile(self.ctx,
679 fl, data = self._create_flavor()
680 self.plugin.create_flavor_service_profile(
682 {'service_profile': {'id': sp['id']}},
685 flavors.ServiceProfileDisabled,
686 self.plugin.get_flavor_next_provider,
690 def test_get_flavor_next_provider_no_driver(self):
691 data = {'service_profile':
692 {'description': 'the best sp',
695 'metainfo': '{"data": "value"}'}}
696 sp = self.plugin.create_service_profile(self.ctx,
698 fl, data = self._create_flavor()
699 self.plugin.create_flavor_service_profile(
701 {'service_profile': {'id': sp['id']}},
704 flavors.ServiceProfileDriverNotFound,
705 self.plugin.get_flavor_next_provider,
709 def test_get_flavor_next_provider(self):
710 sp, data = self._create_service_profile()
711 fl, data = self._create_flavor()
712 self.plugin.create_flavor_service_profile(
714 {'service_profile': {'id': sp['id']}},
716 providers = self.plugin.get_flavor_next_provider(
719 self.assertEqual(_provider, providers[0].get('provider', None))