1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
# Copyright 2012 OpenStack LLC.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
17
# @author: Salvatore Orlando, VMware
22
import unittest2 as unittest
25
import webob.exc as webexc
28
from quantum.api import extensions
29
from quantum import context
30
from quantum.db import api as db_api
31
from quantum.db import models_v2
32
from quantum.db import servicetype_db
33
from quantum.extensions import servicetype
34
from quantum import manager
35
from quantum.openstack.common import cfg
36
from quantum.plugins.common import constants
37
from quantum.tests.unit import dummy_plugin as dp
38
from quantum.tests.unit import test_api_v2
39
from quantum.tests.unit import test_db_plugin
40
from quantum.tests.unit import test_extensions
43
LOG = logging.getLogger(__name__)
44
DEFAULT_SERVICE_DEFS = [{'service_class': constants.DUMMY,
45
'plugin': dp.DUMMY_PLUGIN_NAME}]
47
_uuid = test_api_v2._uuid
48
_get_path = test_api_v2._get_path
51
class TestServiceTypeExtensionManager(object):
52
""" Mock extensions manager """
54
def get_resources(self):
55
return (servicetype.Servicetype.get_resources() +
56
dp.Dummy.get_resources())
58
def get_actions(self):
61
def get_request_extensions(self):
65
class ServiceTypeTestCaseBase(unittest.TestCase):
68
# This is needed because otherwise a failure will occur due to
69
# nonexisting core_plugin
70
cfg.CONF.set_override('core_plugin', test_db_plugin.DB_PLUGIN_KLASS)
71
cfg.CONF.set_override('service_plugins',
72
["%s.%s" % (dp.__name__,
73
dp.DummyServicePlugin.__name__)])
74
# Make sure at each test a new instance of the plugin is returned
75
manager.QuantumManager._instance = None
76
# Ensure existing ExtensionManager is not used
77
extensions.PluginAwareExtensionManager._instance = None
78
ext_mgr = TestServiceTypeExtensionManager()
79
self.ext_mdw = test_extensions.setup_extensions_middleware(ext_mgr)
80
self.api = webtest.TestApp(self.ext_mdw)
81
self.resource_name = servicetype.RESOURCE_NAME.replace('-', '_')
88
class ServiceTypeExtensionTestCase(ServiceTypeTestCaseBase):
91
self._patcher = mock.patch(
92
"%s.%s" % (servicetype_db.__name__,
93
servicetype_db.ServiceTypeManager.__name__),
95
self.mock_mgr = self._patcher.start()
96
self.mock_mgr.get_instance.return_value = self.mock_mgr.return_value
97
super(ServiceTypeExtensionTestCase, self).setUp()
101
super(ServiceTypeExtensionTestCase, self).tearDown()
103
def _test_service_type_create(self, env=None,
104
expected_status=webexc.HTTPCreated.code):
106
if env and 'quantum.context' in env:
107
tenant_id = env['quantum.context'].tenant_id
109
data = {self.resource_name:
111
'tenant_id': tenant_id,
112
'service_definitions':
113
[{'service_class': constants.DUMMY,
114
'plugin': dp.DUMMY_PLUGIN_NAME}]}}
115
return_value = data[self.resource_name].copy()
116
svc_type_id = _uuid()
117
return_value['id'] = svc_type_id
119
instance = self.mock_mgr.return_value
120
instance.create_service_type.return_value = return_value
121
expect_errors = expected_status >= webexc.HTTPBadRequest.code
122
res = self.api.post_json(_get_path('service-types'), data,
124
expect_errors=expect_errors)
125
self.assertEqual(res.status_int, expected_status)
126
if not expect_errors:
127
instance.create_service_type.assert_called_with(mock.ANY,
129
self.assertTrue(self.resource_name in res.json)
130
svc_type = res.json[self.resource_name]
131
self.assertEqual(svc_type['id'], svc_type_id)
132
# NOTE(salvatore-orlando): The following two checks are
133
# probably not essential
134
self.assertEqual(svc_type['service_definitions'],
135
data[self.resource_name]['service_definitions'])
137
def _test_service_type_update(self, env=None,
138
expected_status=webexc.HTTPOk.code):
139
svc_type_name = 'updated'
141
if env and 'quantum.context' in env:
142
tenant_id = env['quantum.context'].tenant_id
143
data = {self.resource_name: {'name': svc_type_name,
144
'tenant-id': tenant_id}}
145
svc_type_id = _uuid()
146
return_value = {'id': svc_type_id,
147
'name': svc_type_name}
149
instance = self.mock_mgr.return_value
150
expect_errors = expected_status >= webexc.HTTPBadRequest.code
151
instance.update_service_type.return_value = return_value
152
res = self.api.put_json(_get_path('service-types/%s' % svc_type_id),
154
if not expect_errors:
155
instance.update_service_type.assert_called_with(mock.ANY,
158
self.assertEqual(res.status_int, webexc.HTTPOk.code)
159
self.assertTrue(self.resource_name in res.json)
160
svc_type = res.json[self.resource_name]
161
self.assertEqual(svc_type['id'], svc_type_id)
162
self.assertEqual(svc_type['name'],
163
data[self.resource_name]['name'])
165
def test_service_type_create(self):
166
self._test_service_type_create()
168
def test_service_type_update(self):
169
self._test_service_type_update()
171
def test_service_type_delete(self):
173
instance = self.mock_mgr.return_value
174
res = self.api.delete(_get_path('service-types/%s' % svctype_id))
175
instance.delete_service_type.assert_called_with(mock.ANY,
177
self.assertEqual(res.status_int, webexc.HTTPNoContent.code)
179
def test_service_type_get(self):
181
return_value = {self.resource_name: {'name': 'test',
182
'service_definitions': [],
185
instance = self.mock_mgr.return_value
186
instance.get_service_type.return_value = return_value
188
res = self.api.get(_get_path('service-types/%s' % svctype_id))
190
instance.get_service_type.assert_called_with(mock.ANY,
193
self.assertEqual(res.status_int, webexc.HTTPOk.code)
195
def test_service_type_list(self):
197
return_value = [{self.resource_name: {'name': 'test',
198
'service_definitions': [],
201
instance = self.mock_mgr.return_value
202
instance.get_service_types.return_value = return_value
204
res = self.api.get(_get_path('service-types'))
206
instance.get_service_types.assert_called_with(mock.ANY,
209
self.assertEqual(res.status_int, webexc.HTTPOk.code)
211
def test_create_service_type_nonadminctx_returns_403(self):
213
env = {'quantum.context': context.Context('', tenant_id,
215
self._test_service_type_create(
216
env=env, expected_status=webexc.HTTPForbidden.code)
218
def test_create_service_type_adminctx_returns_200(self):
219
env = {'quantum.context': context.Context('', '', is_admin=True)}
220
self._test_service_type_create(env=env)
222
def test_update_service_type_nonadminctx_returns_403(self):
224
env = {'quantum.context': context.Context('', tenant_id,
226
self._test_service_type_update(
227
env=env, expected_status=webexc.HTTPForbidden.code)
229
def test_update_service_type_adminctx_returns_200(self):
230
env = {'quantum.context': context.Context('', '', is_admin=True)}
231
self._test_service_type_update(env=env)
234
class ServiceTypeManagerTestCase(ServiceTypeTestCaseBase):
237
db_api._ENGINE = None
239
# Blank out service type manager instance
240
servicetype_db.ServiceTypeManager._instance = None
241
plugin_name = "%s.%s" % (dp.__name__, dp.DummyServicePlugin.__name__)
242
cfg.CONF.set_override('service_definition', ['dummy:%s' % plugin_name],
243
group='DEFAULT_SERVICETYPE')
244
super(ServiceTypeManagerTestCase, self).setUp()
247
super(ServiceTypeManagerTestCase, self).tearDown()
250
@contextlib.contextmanager
251
def service_type(self, name='svc_type',
256
service_defs = [{'service_class': constants.DUMMY,
257
'plugin': dp.DUMMY_PLUGIN_NAME}]
258
res = self._create_service_type(name, service_defs)
260
if res.status_int >= 400:
261
raise webexc.HTTPClientError(code=res.status_int)
265
# The do_delete parameter allows you to control whether the
266
# created network is immediately deleted again. Therefore, this
267
# function is also usable in tests, which require the creation
269
self._delete_service_type(svc_type[self.resource_name]['id'])
271
def _list_service_types(self):
272
return self.api.get(_get_path('service-types'))
274
def _show_service_type(self, svctype_id, expect_errors=False):
275
return self.api.get(_get_path('service-types/%s' % str(svctype_id)),
276
expect_errors=expect_errors)
278
def _create_service_type(self, name, service_defs,
279
default=None, expect_errors=False):
280
data = {self.resource_name:
282
'service_definitions': service_defs}
285
data[self.resource_name]['default'] = default
286
if not 'tenant_id' in data[self.resource_name]:
287
data[self.resource_name]['tenant_id'] = 'fake'
288
return self.api.post_json(_get_path('service-types'), data,
289
expect_errors=expect_errors)
291
def _create_dummy(self, dummyname='dummyobject'):
292
data = {'dummy': {'name': dummyname,
293
'tenant_id': 'fake'}}
294
dummy_res = self.api.post_json(_get_path('dummys'), data)
295
return dummy_res.json['dummy']
297
def _update_service_type(self, svc_type_id, name, service_defs,
298
default=None, expect_errors=False):
299
data = {self.resource_name:
301
if service_defs is not None:
302
data[self.resource_name]['service_definitions'] = service_defs
303
# set this attribute only if True
305
data[self.resource_name]['default'] = default
306
return self.api.put_json(
307
_get_path('service-types/%s' % str(svc_type_id)), data,
308
expect_errors=expect_errors)
310
def _delete_service_type(self, svctype_id, expect_errors=False):
311
return self.api.delete(_get_path('service-types/%s' % str(svctype_id)),
312
expect_errors=expect_errors)
314
def _validate_service_type(self, res, name, service_defs,
316
self.assertTrue(self.resource_name in res.json)
317
svc_type = res.json[self.resource_name]
319
self.assertEqual(svc_type['id'], svc_type_id)
321
self.assertEqual(svc_type['name'], name)
323
# unspecified drivers will value None in response
324
for svc_def in service_defs:
325
svc_def['driver'] = svc_def.get('driver')
326
self.assertEqual(svc_type['service_definitions'],
328
self.assertEqual(svc_type['default'], False)
330
def _test_service_type_create(self, name='test',
331
service_defs=DEFAULT_SERVICE_DEFS,
333
expected_status=webexc.HTTPCreated.code):
334
expect_errors = expected_status >= webexc.HTTPBadRequest.code
335
res = self._create_service_type(name, service_defs,
336
default, expect_errors)
337
self.assertEqual(res.status_int, expected_status)
338
if not expect_errors:
339
self.assertEqual(res.status_int, webexc.HTTPCreated.code)
340
self._validate_service_type(res, name, service_defs)
342
def _test_service_type_update(self, svc_type_id, name='test-updated',
343
default=None, service_defs=None,
344
expected_status=webexc.HTTPOk.code):
345
expect_errors = expected_status >= webexc.HTTPBadRequest.code
346
res = self._update_service_type(svc_type_id, name, service_defs,
347
default, expect_errors)
348
if not expect_errors:
349
self.assertEqual(res.status_int, webexc.HTTPOk.code)
350
self._validate_service_type(res, name, service_defs, svc_type_id)
352
def test_service_type_create(self):
353
self._test_service_type_create()
355
def test_create_service_type_default_returns_400(self):
356
self._test_service_type_create(
357
default=True, expected_status=webexc.HTTPBadRequest.code)
359
def test_create_service_type_no_svcdef_returns_400(self):
360
self._test_service_type_create(
362
expected_status=webexc.HTTPBadRequest.code)
364
def test_service_type_update_name(self):
365
with self.service_type() as svc_type:
366
self._test_service_type_update(svc_type[self.resource_name]['id'])
368
def test_service_type_update_set_default_returns_400(self):
369
with self.service_type() as svc_type:
370
self._test_service_type_update(
371
svc_type[self.resource_name]['id'], default=True,
372
expected_status=webexc.HTTPBadRequest.code)
374
def test_service_type_update_clear_svc_defs_returns_400(self):
375
with self.service_type() as svc_type:
376
self._test_service_type_update(
377
svc_type[self.resource_name]['id'], service_defs=[],
378
expected_status=webexc.HTTPBadRequest.code)
380
def test_service_type_update_svc_defs(self):
381
with self.service_type() as svc_type:
382
svc_defs = [{'service': constants.DUMMY,
384
self._test_service_type_update(
385
svc_type[self.resource_name]['id'], service_defs=svc_defs,
386
expected_status=webexc.HTTPBadRequest.code)
388
def test_list_service_types(self):
389
with contextlib.nested(self.service_type('st1'),
390
self.service_type('st2')):
391
res = self._list_service_types()
392
self.assertEqual(res.status_int, webexc.HTTPOk.code)
394
self.assertTrue('service_types' in data)
395
# it must be 3 because we have the default service type too!
396
self.assertEquals(len(data['service_types']), 3)
398
def test_get_default_service_type(self):
399
res = self._list_service_types()
400
self.assertEqual(res.status_int, webexc.HTTPOk.code)
402
self.assertTrue('service_types' in data)
403
self.assertEquals(len(data['service_types']), 1)
404
def_svc_type = data['service_types'][0]
405
self.assertEqual(def_svc_type['default'], True)
407
def test_get_service_type(self):
408
with self.service_type() as svc_type:
409
svc_type_data = svc_type[self.resource_name]
410
res = self._show_service_type(svc_type_data['id'])
411
self.assertEqual(res.status_int, webexc.HTTPOk.code)
412
self._validate_service_type(res, svc_type_data['name'],
413
svc_type_data['service_definitions'],
416
def test_delete_service_type_in_use_returns_409(self):
417
with self.service_type() as svc_type:
418
svc_type_data = svc_type[self.resource_name]
419
mgr = servicetype_db.ServiceTypeManager.get_instance()
420
ctx = context.Context('', '', is_admin=True)
421
mgr.increase_service_type_refcount(ctx, svc_type_data['id'])
422
res = self._delete_service_type(svc_type_data['id'], True)
423
self.assertEquals(res.status_int, webexc.HTTPConflict.code)
424
mgr.decrease_service_type_refcount(ctx, svc_type_data['id'])
426
def test_create_dummy_increases_service_type_refcount(self):
427
dummy = self._create_dummy()
428
svc_type_res = self._show_service_type(dummy['service_type'])
429
svc_type = svc_type_res.json[self.resource_name]
430
self.assertEquals(svc_type['num_instances'], 1)
432
def test_delete_dummy_decreases_service_type_refcount(self):
433
dummy = self._create_dummy()
434
svc_type_res = self._show_service_type(dummy['service_type'])
435
svc_type = svc_type_res.json[self.resource_name]
436
self.assertEquals(svc_type['num_instances'], 1)
437
self.api.delete(_get_path('dummys/%s' % str(dummy['id'])))
438
svc_type_res = self._show_service_type(dummy['service_type'])
439
svc_type = svc_type_res.json[self.resource_name]
440
self.assertEquals(svc_type['num_instances'], 0)