Merge lp:~justin-fathomdb/nova/test-openstack-login into lp:~hudson-openstack/nova/trunk
- test-openstack-login
- Merge into trunk
Proposed by
justinsb
Status: | Superseded |
---|---|
Proposed branch: | lp:~justin-fathomdb/nova/test-openstack-login |
Merge into: | lp:~hudson-openstack/nova/trunk |
Prerequisite: | lp:~justin-fathomdb/nova/test-openstack-api |
Diff against target: |
1177 lines (+1107/-0) (has conflicts) 12 files modified
nova/api/openstack/accounts.py (+85/-0) nova/api/openstack/users.py (+93/-0) nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py (+51/-0) nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py (+83/-0) nova/tests/api/openstack/test_accounts.py (+125/-0) nova/tests/api/openstack/test_users.py (+141/-0) nova/tests/integrated/__init__.py (+20/-0) nova/tests/integrated/api/__init__.py (+20/-0) nova/tests/integrated/api/client.py (+213/-0) nova/tests/integrated/integrated_helpers.py (+188/-0) nova/tests/integrated/test_login.py (+79/-0) nova/virt/cpuinfo.xml.template (+9/-0) Conflict adding file nova/api/openstack/accounts.py. Moved existing file to nova/api/openstack/accounts.py.moved. Conflict adding file nova/api/openstack/users.py. Moved existing file to nova/api/openstack/users.py.moved. Conflict adding file nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py. Moved existing file to nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py.moved. Conflict adding file nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py. Moved existing file to nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py.moved. Conflict adding file nova/tests/api/openstack/test_accounts.py. Moved existing file to nova/tests/api/openstack/test_accounts.py.moved. Conflict adding file nova/tests/api/openstack/test_users.py. Moved existing file to nova/tests/api/openstack/test_users.py.moved. Conflict adding file nova/tests/integrated. Moved existing file to nova/tests/integrated.moved. Conflict adding file nova/virt/cpuinfo.xml.template. Moved existing file to nova/virt/cpuinfo.xml.template.moved. |
To merge this branch: | bzr merge lp:~justin-fathomdb/nova/test-openstack-login |
Related bugs: | |
Related blueprints: |
Achieve Stability in Cactus
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nova Core security contacts | Pending | ||
Review via email: mp+52933@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-03-15.
Commit message
Description of the change
Test the login behavior of the OpenStack API. Uncovered bug732866
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'nova/api/openstack/accounts.py' |
2 | --- nova/api/openstack/accounts.py 1970-01-01 00:00:00 +0000 |
3 | +++ nova/api/openstack/accounts.py 2011-03-15 05:15:51 +0000 |
4 | @@ -0,0 +1,85 @@ |
5 | +# Copyright 2011 OpenStack LLC. |
6 | +# All Rights Reserved. |
7 | +# |
8 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
9 | +# not use this file except in compliance with the License. You may obtain |
10 | +# a copy of the License at |
11 | +# |
12 | +# http://www.apache.org/licenses/LICENSE-2.0 |
13 | +# |
14 | +# Unless required by applicable law or agreed to in writing, software |
15 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
16 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
17 | +# License for the specific language governing permissions and limitations |
18 | +# under the License. |
19 | + |
20 | +import common |
21 | + |
22 | +from nova import exception |
23 | +from nova import flags |
24 | +from nova import log as logging |
25 | +from nova import wsgi |
26 | + |
27 | +from nova.auth import manager |
28 | +from nova.api.openstack import faults |
29 | + |
30 | +FLAGS = flags.FLAGS |
31 | +LOG = logging.getLogger('nova.api.openstack') |
32 | + |
33 | + |
34 | +def _translate_keys(account): |
35 | + return dict(id=account.id, |
36 | + name=account.name, |
37 | + description=account.description, |
38 | + manager=account.project_manager_id) |
39 | + |
40 | + |
41 | +class Controller(wsgi.Controller): |
42 | + |
43 | + _serialization_metadata = { |
44 | + 'application/xml': { |
45 | + "attributes": { |
46 | + "account": ["id", "name", "description", "manager"]}}} |
47 | + |
48 | + def __init__(self): |
49 | + self.manager = manager.AuthManager() |
50 | + |
51 | + def _check_admin(self, context): |
52 | + """We cannot depend on the db layer to check for admin access |
53 | + for the auth manager, so we do it here""" |
54 | + if not context.is_admin: |
55 | + raise exception.NotAuthorized(_("Not admin user.")) |
56 | + |
57 | + def index(self, req): |
58 | + raise faults.Fault(exc.HTTPNotImplemented()) |
59 | + |
60 | + def detail(self, req): |
61 | + raise faults.Fault(exc.HTTPNotImplemented()) |
62 | + |
63 | + def show(self, req, id): |
64 | + """Return data about the given account id""" |
65 | + account = self.manager.get_project(id) |
66 | + return dict(account=_translate_keys(account)) |
67 | + |
68 | + def delete(self, req, id): |
69 | + self._check_admin(req.environ['nova.context']) |
70 | + self.manager.delete_project(id) |
71 | + return {} |
72 | + |
73 | + def create(self, req): |
74 | + """We use update with create-or-update semantics |
75 | + because the id comes from an external source""" |
76 | + raise faults.Fault(exc.HTTPNotImplemented()) |
77 | + |
78 | + def update(self, req, id): |
79 | + """This is really create or update.""" |
80 | + self._check_admin(req.environ['nova.context']) |
81 | + env = self._deserialize(req.body, req.get_content_type()) |
82 | + description = env['account'].get('description') |
83 | + manager = env['account'].get('manager') |
84 | + try: |
85 | + account = self.manager.get_project(id) |
86 | + self.manager.modify_project(id, manager, description) |
87 | + except exception.NotFound: |
88 | + account = self.manager.create_project(id, manager, description) |
89 | + return dict(account=_translate_keys(account)) |
90 | |
91 | === renamed file 'nova/api/openstack/accounts.py' => 'nova/api/openstack/accounts.py.moved' |
92 | === added file 'nova/api/openstack/users.py' |
93 | --- nova/api/openstack/users.py 1970-01-01 00:00:00 +0000 |
94 | +++ nova/api/openstack/users.py 2011-03-15 05:15:51 +0000 |
95 | @@ -0,0 +1,93 @@ |
96 | +# Copyright 2011 OpenStack LLC. |
97 | +# All Rights Reserved. |
98 | +# |
99 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
100 | +# not use this file except in compliance with the License. You may obtain |
101 | +# a copy of the License at |
102 | +# |
103 | +# http://www.apache.org/licenses/LICENSE-2.0 |
104 | +# |
105 | +# Unless required by applicable law or agreed to in writing, software |
106 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
107 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
108 | +# License for the specific language governing permissions and limitations |
109 | +# under the License. |
110 | + |
111 | +import common |
112 | + |
113 | +from nova import exception |
114 | +from nova import flags |
115 | +from nova import log as logging |
116 | +from nova import wsgi |
117 | + |
118 | +from nova.auth import manager |
119 | + |
120 | +FLAGS = flags.FLAGS |
121 | +LOG = logging.getLogger('nova.api.openstack') |
122 | + |
123 | + |
124 | +def _translate_keys(user): |
125 | + return dict(id=user.id, |
126 | + name=user.name, |
127 | + access=user.access, |
128 | + secret=user.secret, |
129 | + admin=user.admin) |
130 | + |
131 | + |
132 | +class Controller(wsgi.Controller): |
133 | + |
134 | + _serialization_metadata = { |
135 | + 'application/xml': { |
136 | + "attributes": { |
137 | + "user": ["id", "name", "access", "secret", "admin"]}}} |
138 | + |
139 | + def __init__(self): |
140 | + self.manager = manager.AuthManager() |
141 | + |
142 | + def _check_admin(self, context): |
143 | + """We cannot depend on the db layer to check for admin access |
144 | + for the auth manager, so we do it here""" |
145 | + if not context.is_admin: |
146 | + raise exception.NotAuthorized(_("Not admin user")) |
147 | + |
148 | + def index(self, req): |
149 | + """Return all users in brief""" |
150 | + users = self.manager.get_users() |
151 | + users = common.limited(users, req) |
152 | + users = [_translate_keys(user) for user in users] |
153 | + return dict(users=users) |
154 | + |
155 | + def detail(self, req): |
156 | + """Return all users in detail""" |
157 | + return self.index(req) |
158 | + |
159 | + def show(self, req, id): |
160 | + """Return data about the given user id""" |
161 | + user = self.manager.get_user(id) |
162 | + return dict(user=_translate_keys(user)) |
163 | + |
164 | + def delete(self, req, id): |
165 | + self._check_admin(req.environ['nova.context']) |
166 | + self.manager.delete_user(id) |
167 | + return {} |
168 | + |
169 | + def create(self, req): |
170 | + self._check_admin(req.environ['nova.context']) |
171 | + env = self._deserialize(req.body, req.get_content_type()) |
172 | + is_admin = env['user'].get('admin') in ('T', 'True', True) |
173 | + name = env['user'].get('name') |
174 | + access = env['user'].get('access') |
175 | + secret = env['user'].get('secret') |
176 | + user = self.manager.create_user(name, access, secret, is_admin) |
177 | + return dict(user=_translate_keys(user)) |
178 | + |
179 | + def update(self, req, id): |
180 | + self._check_admin(req.environ['nova.context']) |
181 | + env = self._deserialize(req.body, req.get_content_type()) |
182 | + is_admin = env['user'].get('admin') |
183 | + if is_admin is not None: |
184 | + is_admin = is_admin in ('T', 'True', True) |
185 | + access = env['user'].get('access') |
186 | + secret = env['user'].get('secret') |
187 | + self.manager.modify_user(id, access, secret, is_admin) |
188 | + return dict(user=_translate_keys(self.manager.get_user(id))) |
189 | |
190 | === renamed file 'nova/api/openstack/users.py' => 'nova/api/openstack/users.py.moved' |
191 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py' |
192 | --- nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py 1970-01-01 00:00:00 +0000 |
193 | +++ nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py 2011-03-15 05:15:51 +0000 |
194 | @@ -0,0 +1,51 @@ |
195 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
196 | + |
197 | +# Copyright 2010 OpenStack LLC. |
198 | +# |
199 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
200 | +# not use this file except in compliance with the License. You may obtain |
201 | +# a copy of the License at |
202 | +# |
203 | +# http://www.apache.org/licenses/LICENSE-2.0 |
204 | +# |
205 | +# Unless required by applicable law or agreed to in writing, software |
206 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
207 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
208 | +# License for the specific language governing permissions and limitations |
209 | +# under the License. |
210 | + |
211 | +from sqlalchemy import * |
212 | +from sqlalchemy.sql import text |
213 | +from migrate import * |
214 | + |
215 | +from nova import log as logging |
216 | + |
217 | + |
218 | +meta = MetaData() |
219 | + |
220 | +instances = Table('instances', meta, |
221 | + Column('id', Integer(), primary_key=True, nullable=False), |
222 | + ) |
223 | + |
224 | +instances_os_type = Column('os_type', |
225 | + String(length=255, convert_unicode=False, |
226 | + assert_unicode=None, unicode_error=None, |
227 | + _warn_on_bytestring=False), |
228 | + nullable=True) |
229 | + |
230 | + |
231 | +def upgrade(migrate_engine): |
232 | + # Upgrade operations go here. Don't create your own engine; |
233 | + # bind migrate_engine to your metadata |
234 | + meta.bind = migrate_engine |
235 | + |
236 | + instances.create_column(instances_os_type) |
237 | + migrate_engine.execute(instances.update()\ |
238 | + .where(instances.c.os_type == None)\ |
239 | + .values(os_type='linux')) |
240 | + |
241 | + |
242 | +def downgrade(migrate_engine): |
243 | + meta.bind = migrate_engine |
244 | + |
245 | + instances.drop_column('os_type') |
246 | |
247 | === renamed file 'nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py' => 'nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py.moved' |
248 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py' |
249 | --- nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py 1970-01-01 00:00:00 +0000 |
250 | +++ nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py 2011-03-15 05:15:51 +0000 |
251 | @@ -0,0 +1,83 @@ |
252 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
253 | + |
254 | +# Copyright 2010 United States Government as represented by the |
255 | +# Administrator of the National Aeronautics and Space Administration. |
256 | +# All Rights Reserved. |
257 | +# |
258 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
259 | +# not use this file except in compliance with the License. You may obtain |
260 | +# a copy of the License at |
261 | +# |
262 | +# http://www.apache.org/licenses/LICENSE-2.0 |
263 | +# |
264 | +# Unless required by applicable law or agreed to in writing, software |
265 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
266 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
267 | +# License for the specific language governing permissions and limitations |
268 | +# under the License. |
269 | + |
270 | +from migrate import * |
271 | +from nova import log as logging |
272 | +from sqlalchemy import * |
273 | + |
274 | + |
275 | +meta = MetaData() |
276 | + |
277 | +instances = Table('instances', meta, |
278 | + Column('id', Integer(), primary_key=True, nullable=False), |
279 | + ) |
280 | + |
281 | +# |
282 | +# New Tables |
283 | +# |
284 | + |
285 | +compute_nodes = Table('compute_nodes', meta, |
286 | + Column('created_at', DateTime(timezone=False)), |
287 | + Column('updated_at', DateTime(timezone=False)), |
288 | + Column('deleted_at', DateTime(timezone=False)), |
289 | + Column('deleted', Boolean(create_constraint=True, name=None)), |
290 | + Column('id', Integer(), primary_key=True, nullable=False), |
291 | + Column('service_id', Integer(), nullable=False), |
292 | + |
293 | + Column('vcpus', Integer(), nullable=False), |
294 | + Column('memory_mb', Integer(), nullable=False), |
295 | + Column('local_gb', Integer(), nullable=False), |
296 | + Column('vcpus_used', Integer(), nullable=False), |
297 | + Column('memory_mb_used', Integer(), nullable=False), |
298 | + Column('local_gb_used', Integer(), nullable=False), |
299 | + Column('hypervisor_type', |
300 | + Text(convert_unicode=False, assert_unicode=None, |
301 | + unicode_error=None, _warn_on_bytestring=False), |
302 | + nullable=False), |
303 | + Column('hypervisor_version', Integer(), nullable=False), |
304 | + Column('cpu_info', |
305 | + Text(convert_unicode=False, assert_unicode=None, |
306 | + unicode_error=None, _warn_on_bytestring=False), |
307 | + nullable=False), |
308 | + ) |
309 | + |
310 | + |
311 | +# |
312 | +# Tables to alter |
313 | +# |
314 | +instances_launched_on = Column( |
315 | + 'launched_on', |
316 | + Text(convert_unicode=False, assert_unicode=None, |
317 | + unicode_error=None, _warn_on_bytestring=False), |
318 | + nullable=True) |
319 | + |
320 | + |
321 | +def upgrade(migrate_engine): |
322 | + # Upgrade operations go here. Don't create your own engine; |
323 | + # bind migrate_engine to your metadata |
324 | + meta.bind = migrate_engine |
325 | + |
326 | + try: |
327 | + compute_nodes.create() |
328 | + except Exception: |
329 | + logging.info(repr(compute_nodes)) |
330 | + logging.exception('Exception while creating table') |
331 | + meta.drop_all(tables=[compute_nodes]) |
332 | + raise |
333 | + |
334 | + instances.create_column(instances_launched_on) |
335 | |
336 | === renamed file 'nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py' => 'nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py.moved' |
337 | === added file 'nova/tests/api/openstack/test_accounts.py' |
338 | --- nova/tests/api/openstack/test_accounts.py 1970-01-01 00:00:00 +0000 |
339 | +++ nova/tests/api/openstack/test_accounts.py 2011-03-15 05:15:51 +0000 |
340 | @@ -0,0 +1,125 @@ |
341 | +# Copyright 2010 OpenStack LLC. |
342 | +# All Rights Reserved. |
343 | +# |
344 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
345 | +# not use this file except in compliance with the License. You may obtain |
346 | +# a copy of the License at |
347 | +# |
348 | +# http://www.apache.org/licenses/LICENSE-2.0 |
349 | +# |
350 | +# Unless required by applicable law or agreed to in writing, software |
351 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
352 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
353 | +# License for the specific language governing permissions and limitations |
354 | +# under the License. |
355 | + |
356 | + |
357 | +import json |
358 | + |
359 | +import stubout |
360 | +import webob |
361 | + |
362 | +import nova.api |
363 | +import nova.api.openstack.auth |
364 | +from nova import context |
365 | +from nova import flags |
366 | +from nova import test |
367 | +from nova.auth.manager import User |
368 | +from nova.tests.api.openstack import fakes |
369 | + |
370 | + |
371 | +FLAGS = flags.FLAGS |
372 | +FLAGS.verbose = True |
373 | + |
374 | + |
375 | +def fake_init(self): |
376 | + self.manager = fakes.FakeAuthManager() |
377 | + |
378 | + |
379 | +def fake_admin_check(self, req): |
380 | + return True |
381 | + |
382 | + |
383 | +class AccountsTest(test.TestCase): |
384 | + def setUp(self): |
385 | + super(AccountsTest, self).setUp() |
386 | + self.stubs = stubout.StubOutForTesting() |
387 | + self.stubs.Set(nova.api.openstack.accounts.Controller, '__init__', |
388 | + fake_init) |
389 | + self.stubs.Set(nova.api.openstack.accounts.Controller, '_check_admin', |
390 | + fake_admin_check) |
391 | + fakes.FakeAuthManager.clear_fakes() |
392 | + fakes.FakeAuthDatabase.data = {} |
393 | + fakes.stub_out_networking(self.stubs) |
394 | + fakes.stub_out_rate_limiting(self.stubs) |
395 | + fakes.stub_out_auth(self.stubs) |
396 | + |
397 | + self.allow_admin = FLAGS.allow_admin_api |
398 | + FLAGS.allow_admin_api = True |
399 | + fakemgr = fakes.FakeAuthManager() |
400 | + joeuser = User('guy1', 'guy1', 'acc1', 'fortytwo!', False) |
401 | + superuser = User('guy2', 'guy2', 'acc2', 'swordfish', True) |
402 | + fakemgr.add_user(joeuser.access, joeuser) |
403 | + fakemgr.add_user(superuser.access, superuser) |
404 | + fakemgr.create_project('test1', joeuser) |
405 | + fakemgr.create_project('test2', superuser) |
406 | + |
407 | + def tearDown(self): |
408 | + self.stubs.UnsetAll() |
409 | + FLAGS.allow_admin_api = self.allow_admin |
410 | + super(AccountsTest, self).tearDown() |
411 | + |
412 | + def test_get_account(self): |
413 | + req = webob.Request.blank('/v1.0/accounts/test1') |
414 | + res = req.get_response(fakes.wsgi_app()) |
415 | + res_dict = json.loads(res.body) |
416 | + |
417 | + self.assertEqual(res_dict['account']['id'], 'test1') |
418 | + self.assertEqual(res_dict['account']['name'], 'test1') |
419 | + self.assertEqual(res_dict['account']['manager'], 'guy1') |
420 | + self.assertEqual(res.status_int, 200) |
421 | + |
422 | + def test_account_delete(self): |
423 | + req = webob.Request.blank('/v1.0/accounts/test1') |
424 | + req.method = 'DELETE' |
425 | + res = req.get_response(fakes.wsgi_app()) |
426 | + self.assertTrue('test1' not in fakes.FakeAuthManager.projects) |
427 | + self.assertEqual(res.status_int, 200) |
428 | + |
429 | + def test_account_create(self): |
430 | + body = dict(account=dict(description='test account', |
431 | + manager='guy1')) |
432 | + req = webob.Request.blank('/v1.0/accounts/newacct') |
433 | + req.headers["Content-Type"] = "application/json" |
434 | + req.method = 'PUT' |
435 | + req.body = json.dumps(body) |
436 | + |
437 | + res = req.get_response(fakes.wsgi_app()) |
438 | + res_dict = json.loads(res.body) |
439 | + |
440 | + self.assertEqual(res.status_int, 200) |
441 | + self.assertEqual(res_dict['account']['id'], 'newacct') |
442 | + self.assertEqual(res_dict['account']['name'], 'newacct') |
443 | + self.assertEqual(res_dict['account']['description'], 'test account') |
444 | + self.assertEqual(res_dict['account']['manager'], 'guy1') |
445 | + self.assertTrue('newacct' in |
446 | + fakes.FakeAuthManager.projects) |
447 | + self.assertEqual(len(fakes.FakeAuthManager.projects.values()), 3) |
448 | + |
449 | + def test_account_update(self): |
450 | + body = dict(account=dict(description='test account', |
451 | + manager='guy2')) |
452 | + req = webob.Request.blank('/v1.0/accounts/test1') |
453 | + req.headers["Content-Type"] = "application/json" |
454 | + req.method = 'PUT' |
455 | + req.body = json.dumps(body) |
456 | + |
457 | + res = req.get_response(fakes.wsgi_app()) |
458 | + res_dict = json.loads(res.body) |
459 | + |
460 | + self.assertEqual(res.status_int, 200) |
461 | + self.assertEqual(res_dict['account']['id'], 'test1') |
462 | + self.assertEqual(res_dict['account']['name'], 'test1') |
463 | + self.assertEqual(res_dict['account']['description'], 'test account') |
464 | + self.assertEqual(res_dict['account']['manager'], 'guy2') |
465 | + self.assertEqual(len(fakes.FakeAuthManager.projects.values()), 2) |
466 | |
467 | === renamed file 'nova/tests/api/openstack/test_accounts.py' => 'nova/tests/api/openstack/test_accounts.py.moved' |
468 | === added file 'nova/tests/api/openstack/test_users.py' |
469 | --- nova/tests/api/openstack/test_users.py 1970-01-01 00:00:00 +0000 |
470 | +++ nova/tests/api/openstack/test_users.py 2011-03-15 05:15:51 +0000 |
471 | @@ -0,0 +1,141 @@ |
472 | +# Copyright 2010 OpenStack LLC. |
473 | +# All Rights Reserved. |
474 | +# |
475 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
476 | +# not use this file except in compliance with the License. You may obtain |
477 | +# a copy of the License at |
478 | +# |
479 | +# http://www.apache.org/licenses/LICENSE-2.0 |
480 | +# |
481 | +# Unless required by applicable law or agreed to in writing, software |
482 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
483 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
484 | +# License for the specific language governing permissions and limitations |
485 | +# under the License. |
486 | + |
487 | +import json |
488 | + |
489 | +import stubout |
490 | +import webob |
491 | + |
492 | +import nova.api |
493 | +import nova.api.openstack.auth |
494 | +from nova import context |
495 | +from nova import flags |
496 | +from nova import test |
497 | +from nova.auth.manager import User, Project |
498 | +from nova.tests.api.openstack import fakes |
499 | + |
500 | + |
501 | +FLAGS = flags.FLAGS |
502 | +FLAGS.verbose = True |
503 | + |
504 | + |
505 | +def fake_init(self): |
506 | + self.manager = fakes.FakeAuthManager() |
507 | + |
508 | + |
509 | +def fake_admin_check(self, req): |
510 | + return True |
511 | + |
512 | + |
513 | +class UsersTest(test.TestCase): |
514 | + def setUp(self): |
515 | + super(UsersTest, self).setUp() |
516 | + self.stubs = stubout.StubOutForTesting() |
517 | + self.stubs.Set(nova.api.openstack.users.Controller, '__init__', |
518 | + fake_init) |
519 | + self.stubs.Set(nova.api.openstack.users.Controller, '_check_admin', |
520 | + fake_admin_check) |
521 | + fakes.FakeAuthManager.auth_data = {} |
522 | + fakes.FakeAuthManager.projects = dict(testacct=Project('testacct', |
523 | + 'testacct', |
524 | + 'guy1', |
525 | + 'test', |
526 | + [])) |
527 | + fakes.FakeAuthDatabase.data = {} |
528 | + fakes.stub_out_networking(self.stubs) |
529 | + fakes.stub_out_rate_limiting(self.stubs) |
530 | + fakes.stub_out_auth(self.stubs) |
531 | + |
532 | + self.allow_admin = FLAGS.allow_admin_api |
533 | + FLAGS.allow_admin_api = True |
534 | + fakemgr = fakes.FakeAuthManager() |
535 | + fakemgr.add_user('acc1', User('guy1', 'guy1', 'acc1', |
536 | + 'fortytwo!', False)) |
537 | + fakemgr.add_user('acc2', User('guy2', 'guy2', 'acc2', |
538 | + 'swordfish', True)) |
539 | + |
540 | + def tearDown(self): |
541 | + self.stubs.UnsetAll() |
542 | + FLAGS.allow_admin_api = self.allow_admin |
543 | + super(UsersTest, self).tearDown() |
544 | + |
545 | + def test_get_user_list(self): |
546 | + req = webob.Request.blank('/v1.0/users') |
547 | + res = req.get_response(fakes.wsgi_app()) |
548 | + res_dict = json.loads(res.body) |
549 | + |
550 | + self.assertEqual(res.status_int, 200) |
551 | + self.assertEqual(len(res_dict['users']), 2) |
552 | + |
553 | + def test_get_user_by_id(self): |
554 | + req = webob.Request.blank('/v1.0/users/guy2') |
555 | + res = req.get_response(fakes.wsgi_app()) |
556 | + res_dict = json.loads(res.body) |
557 | + |
558 | + self.assertEqual(res_dict['user']['id'], 'guy2') |
559 | + self.assertEqual(res_dict['user']['name'], 'guy2') |
560 | + self.assertEqual(res_dict['user']['secret'], 'swordfish') |
561 | + self.assertEqual(res_dict['user']['admin'], True) |
562 | + self.assertEqual(res.status_int, 200) |
563 | + |
564 | + def test_user_delete(self): |
565 | + req = webob.Request.blank('/v1.0/users/guy1') |
566 | + req.method = 'DELETE' |
567 | + res = req.get_response(fakes.wsgi_app()) |
568 | + self.assertTrue('guy1' not in [u.id for u in |
569 | + fakes.FakeAuthManager.auth_data.values()]) |
570 | + self.assertEqual(res.status_int, 200) |
571 | + |
572 | + def test_user_create(self): |
573 | + body = dict(user=dict(name='test_guy', |
574 | + access='acc3', |
575 | + secret='invasionIsInNormandy', |
576 | + admin=True)) |
577 | + req = webob.Request.blank('/v1.0/users') |
578 | + req.headers["Content-Type"] = "application/json" |
579 | + req.method = 'POST' |
580 | + req.body = json.dumps(body) |
581 | + |
582 | + res = req.get_response(fakes.wsgi_app()) |
583 | + res_dict = json.loads(res.body) |
584 | + |
585 | + self.assertEqual(res.status_int, 200) |
586 | + self.assertEqual(res_dict['user']['id'], 'test_guy') |
587 | + self.assertEqual(res_dict['user']['name'], 'test_guy') |
588 | + self.assertEqual(res_dict['user']['access'], 'acc3') |
589 | + self.assertEqual(res_dict['user']['secret'], 'invasionIsInNormandy') |
590 | + self.assertEqual(res_dict['user']['admin'], True) |
591 | + self.assertTrue('test_guy' in [u.id for u in |
592 | + fakes.FakeAuthManager.auth_data.values()]) |
593 | + self.assertEqual(len(fakes.FakeAuthManager.auth_data.values()), 3) |
594 | + |
595 | + def test_user_update(self): |
596 | + body = dict(user=dict(name='guy2', |
597 | + access='acc2', |
598 | + secret='invasionIsInNormandy')) |
599 | + req = webob.Request.blank('/v1.0/users/guy2') |
600 | + req.headers["Content-Type"] = "application/json" |
601 | + req.method = 'PUT' |
602 | + req.body = json.dumps(body) |
603 | + |
604 | + res = req.get_response(fakes.wsgi_app()) |
605 | + res_dict = json.loads(res.body) |
606 | + |
607 | + self.assertEqual(res.status_int, 200) |
608 | + self.assertEqual(res_dict['user']['id'], 'guy2') |
609 | + self.assertEqual(res_dict['user']['name'], 'guy2') |
610 | + self.assertEqual(res_dict['user']['access'], 'acc2') |
611 | + self.assertEqual(res_dict['user']['secret'], 'invasionIsInNormandy') |
612 | + self.assertEqual(res_dict['user']['admin'], True) |
613 | |
614 | === renamed file 'nova/tests/api/openstack/test_users.py' => 'nova/tests/api/openstack/test_users.py.moved' |
615 | === added directory 'nova/tests/integrated' |
616 | === renamed directory 'nova/tests/integrated' => 'nova/tests/integrated.moved' |
617 | === added file 'nova/tests/integrated/__init__.py' |
618 | --- nova/tests/integrated/__init__.py 1970-01-01 00:00:00 +0000 |
619 | +++ nova/tests/integrated/__init__.py 2011-03-15 05:15:51 +0000 |
620 | @@ -0,0 +1,20 @@ |
621 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
622 | + |
623 | +# Copyright (c) 2011 Justin Santa Barbara |
624 | +# |
625 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
626 | +# not use this file except in compliance with the License. You may obtain |
627 | +# a copy of the License at |
628 | +# |
629 | +# http://www.apache.org/licenses/LICENSE-2.0 |
630 | +# |
631 | +# Unless required by applicable law or agreed to in writing, software |
632 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
633 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
634 | +# License for the specific language governing permissions and limitations |
635 | +# under the License. |
636 | + |
637 | +""" |
638 | +:mod:`integrated` -- Tests whole systems, using mock services where needed |
639 | +================================= |
640 | +""" |
641 | |
642 | === added directory 'nova/tests/integrated/api' |
643 | === added file 'nova/tests/integrated/api/__init__.py' |
644 | --- nova/tests/integrated/api/__init__.py 1970-01-01 00:00:00 +0000 |
645 | +++ nova/tests/integrated/api/__init__.py 2011-03-15 05:15:51 +0000 |
646 | @@ -0,0 +1,20 @@ |
647 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
648 | + |
649 | +# Copyright (c) 2011 Justin Santa Barbara |
650 | +# |
651 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
652 | +# not use this file except in compliance with the License. You may obtain |
653 | +# a copy of the License at |
654 | +# |
655 | +# http://www.apache.org/licenses/LICENSE-2.0 |
656 | +# |
657 | +# Unless required by applicable law or agreed to in writing, software |
658 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
659 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
660 | +# License for the specific language governing permissions and limitations |
661 | +# under the License. |
662 | + |
663 | +""" |
664 | +:mod:`api` -- OpenStack API client, for testing rather than production |
665 | +================================= |
666 | +""" |
667 | |
668 | === added file 'nova/tests/integrated/api/client.py' |
669 | --- nova/tests/integrated/api/client.py 1970-01-01 00:00:00 +0000 |
670 | +++ nova/tests/integrated/api/client.py 2011-03-15 05:15:51 +0000 |
671 | @@ -0,0 +1,213 @@ |
672 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
673 | + |
674 | +# Copyright (c) 2011 Justin Santa Barbara |
675 | +# |
676 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
677 | +# not use this file except in compliance with the License. You may obtain |
678 | +# a copy of the License at |
679 | +# |
680 | +# http://www.apache.org/licenses/LICENSE-2.0 |
681 | +# |
682 | +# Unless required by applicable law or agreed to in writing, software |
683 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
684 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
685 | +# License for the specific language governing permissions and limitations |
686 | +# under the License. |
687 | + |
688 | +import json |
689 | +import httplib |
690 | +import urlparse |
691 | + |
692 | +from nova import log as logging |
693 | + |
694 | + |
695 | +LOG = logging.getLogger('nova.tests.api') |
696 | + |
697 | + |
698 | +class OpenStackApiException(Exception): |
699 | + def __init__(self, message=None, response=None): |
700 | + self.response = response |
701 | + if not message: |
702 | + message = 'Unspecified error' |
703 | + |
704 | + if response: |
705 | + _status = response.status |
706 | + _body = response.read() |
707 | + |
708 | + message = _('%(message)s\nStatus Code: %(_status)s\n' |
709 | + 'Body: %(_body)s') % locals() |
710 | + |
711 | + super(OpenStackApiException, self).__init__(message) |
712 | + |
713 | + |
714 | +class OpenStackApiAuthenticationException(OpenStackApiException): |
715 | + def __init__(self, response=None, message=None): |
716 | + if not message: |
717 | + message = _("Authentication error") |
718 | + super(OpenStackApiAuthenticationException, self).__init__(message, |
719 | + response) |
720 | + |
721 | + |
722 | +class OpenStackApiNotFoundException(OpenStackApiException): |
723 | + def __init__(self, response=None, message=None): |
724 | + if not message: |
725 | + message = _("Item not found") |
726 | + super(OpenStackApiNotFoundException, self).__init__(message, response) |
727 | + |
728 | + |
729 | +class TestOpenStackClient(object): |
730 | + """ A really basic OpenStack API client that is under our control, |
731 | + so we can make changes / insert hooks for testing""" |
732 | + |
733 | + def __init__(self, auth_user, auth_key, auth_uri): |
734 | + super(TestOpenStackClient, self).__init__() |
735 | + self.auth_result = None |
736 | + self.auth_user = auth_user |
737 | + self.auth_key = auth_key |
738 | + self.auth_uri = auth_uri |
739 | + |
740 | + def request(self, url, method='GET', body=None, headers=None): |
741 | + if headers is None: |
742 | + headers = {} |
743 | + |
744 | + parsed_url = urlparse.urlparse(url) |
745 | + port = parsed_url.port |
746 | + hostname = parsed_url.hostname |
747 | + scheme = parsed_url.scheme |
748 | + |
749 | + if scheme == 'http': |
750 | + conn = httplib.HTTPConnection(hostname, |
751 | + port=port) |
752 | + elif scheme == 'https': |
753 | + conn = httplib.HTTPSConnection(hostname, |
754 | + port=port) |
755 | + else: |
756 | + raise OpenStackApiException("Unknown scheme: %s" % url) |
757 | + |
758 | + relative_url = parsed_url.path |
759 | + if parsed_url.query: |
760 | + relative_url = relative_url + parsed_url.query |
761 | + LOG.info(_("Doing %(method)s on %(relative_url)s") % locals()) |
762 | + if body: |
763 | + LOG.info(_("Body: %s") % body) |
764 | + |
765 | + conn.request(method, relative_url, body, headers) |
766 | + response = conn.getresponse() |
767 | + return response |
768 | + |
769 | + def _authenticate(self): |
770 | + if self.auth_result: |
771 | + return self.auth_result |
772 | + |
773 | + auth_uri = self.auth_uri |
774 | + headers = {'X-Auth-User': self.auth_user, |
775 | + 'X-Auth-Key': self.auth_key} |
776 | + response = self.request(auth_uri, |
777 | + headers=headers) |
778 | + |
779 | + http_status = response.status |
780 | + LOG.debug(_("%(auth_uri)s => code %(http_status)s") % locals()) |
781 | + |
782 | + # Until bug732866 is fixed, we can't check this properly... |
783 | + # bug732866 |
784 | + #if http_status == 401: |
785 | + if http_status != 204: |
786 | + raise OpenStackApiAuthenticationException(response=response) |
787 | + |
788 | + auth_headers = {} |
789 | + for k, v in response.getheaders(): |
790 | + auth_headers[k] = v |
791 | + |
792 | + self.auth_result = auth_headers |
793 | + return self.auth_result |
794 | + |
795 | + def api_request(self, relative_uri, check_response_status=None, **kwargs): |
796 | + auth_result = self._authenticate() |
797 | + |
798 | + #NOTE(justinsb): httplib 'helpfully' converts headers to lower case |
799 | + base_uri = auth_result['x-server-management-url'] |
800 | + full_uri = base_uri + relative_uri |
801 | + |
802 | + headers = kwargs.setdefault('headers', {}) |
803 | + headers['X-Auth-Token'] = auth_result['x-auth-token'] |
804 | + |
805 | + response = self.request(full_uri, **kwargs) |
806 | + |
807 | + http_status = response.status |
808 | + LOG.debug(_("%(relative_uri)s => code %(http_status)s") % locals()) |
809 | + |
810 | + if check_response_status: |
811 | + if not http_status in check_response_status: |
812 | + if http_status == 404: |
813 | + raise OpenStackApiNotFoundException(response=response) |
814 | + else: |
815 | + raise OpenStackApiException( |
816 | + message=_("Unexpected status code"), |
817 | + response=response) |
818 | + |
819 | + return response |
820 | + |
821 | + def _decode_json(self, response): |
822 | + body = response.read() |
823 | + LOG.debug(_("Decoding JSON: %s") % (body)) |
824 | + return json.loads(body) |
825 | + |
826 | + def api_get(self, relative_uri, **kwargs): |
827 | + kwargs.setdefault('check_response_status', [200]) |
828 | + response = self.api_request(relative_uri, **kwargs) |
829 | + return self._decode_json(response) |
830 | + |
831 | + def api_post(self, relative_uri, body, **kwargs): |
832 | + kwargs['method'] = 'POST' |
833 | + if body: |
834 | + headers = kwargs.setdefault('headers', {}) |
835 | + headers['Content-Type'] = 'application/json' |
836 | + kwargs['body'] = json.dumps(body) |
837 | + |
838 | + kwargs.setdefault('check_response_status', [200]) |
839 | + response = self.api_request(relative_uri, **kwargs) |
840 | + return self._decode_json(response) |
841 | + |
842 | + def api_delete(self, relative_uri, **kwargs): |
843 | + kwargs['method'] = 'DELETE' |
844 | + kwargs.setdefault('check_response_status', [200, 202]) |
845 | + return self.api_request(relative_uri, **kwargs) |
846 | + |
847 | + def get_server(self, server_id): |
848 | + return self.api_get('/servers/%s' % server_id)['server'] |
849 | + |
850 | + def get_servers(self, detail=True): |
851 | + rel_url = '/servers/detail' if detail else '/servers' |
852 | + return self.api_get(rel_url)['servers'] |
853 | + |
854 | + def post_server(self, server): |
855 | + return self.api_post('/servers', server)['server'] |
856 | + |
857 | + def delete_server(self, server_id): |
858 | + return self.api_delete('/servers/%s' % server_id) |
859 | + |
860 | + def get_image(self, image_id): |
861 | + return self.api_get('/images/%s' % image_id)['image'] |
862 | + |
863 | + def get_images(self, detail=True): |
864 | + rel_url = '/images/detail' if detail else '/images' |
865 | + return self.api_get(rel_url)['images'] |
866 | + |
867 | + def post_image(self, image): |
868 | + return self.api_post('/images', image)['image'] |
869 | + |
870 | + def delete_image(self, image_id): |
871 | + return self.api_delete('/images/%s' % image_id) |
872 | + |
873 | + def get_flavor(self, flavor_id): |
874 | + return self.api_get('/flavors/%s' % flavor_id)['flavor'] |
875 | + |
876 | + def get_flavors(self, detail=True): |
877 | + rel_url = '/flavors/detail' if detail else '/flavors' |
878 | + return self.api_get(rel_url)['flavors'] |
879 | + |
880 | + def post_flavor(self, flavor): |
881 | + return self.api_post('/flavors', flavor)['flavor'] |
882 | + |
883 | + def delete_flavor(self, flavor_id): |
884 | + return self.api_delete('/flavors/%s' % flavor_id) |
885 | |
886 | === added file 'nova/tests/integrated/integrated_helpers.py' |
887 | --- nova/tests/integrated/integrated_helpers.py 1970-01-01 00:00:00 +0000 |
888 | +++ nova/tests/integrated/integrated_helpers.py 2011-03-15 05:15:51 +0000 |
889 | @@ -0,0 +1,188 @@ |
890 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
891 | + |
892 | +# Copyright 2011 Justin Santa Barbara |
893 | +# All Rights Reserved. |
894 | +# |
895 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
896 | +# not use this file except in compliance with the License. You may obtain |
897 | +# a copy of the License at |
898 | +# |
899 | +# http://www.apache.org/licenses/LICENSE-2.0 |
900 | +# |
901 | +# Unless required by applicable law or agreed to in writing, software |
902 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
903 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
904 | +# License for the specific language governing permissions and limitations |
905 | +# under the License. |
906 | + |
907 | +""" |
908 | +Provides common functionality for integrated unit tests |
909 | +""" |
910 | + |
911 | +import random |
912 | +import string |
913 | + |
914 | +from nova import exception |
915 | +from nova import flags |
916 | +from nova import service |
917 | +from nova import test # For the flags |
918 | +from nova.auth import manager |
919 | +from nova.exception import Error |
920 | +from nova.log import logging |
921 | +from nova.tests.integrated.api import client |
922 | + |
923 | + |
924 | +FLAGS = flags.FLAGS |
925 | + |
926 | +LOG = logging.getLogger('nova.tests.integrated') |
927 | + |
928 | + |
929 | +def generate_random_alphanumeric(length): |
930 | + """Creates a random alphanumeric string of specified length""" |
931 | + return ''.join(random.choice(string.ascii_uppercase + string.digits) |
932 | + for _x in range(length)) |
933 | + |
934 | + |
935 | +def generate_random_numeric(length): |
936 | + """Creates a random numeric string of specified length""" |
937 | + return ''.join(random.choice(string.digits) |
938 | + for _x in range(length)) |
939 | + |
940 | + |
941 | +def generate_new_element(items, prefix, numeric=False): |
942 | + """Creates a random string with prefix, that is not in 'items' list""" |
943 | + while True: |
944 | + if numeric: |
945 | + candidate = prefix + generate_random_numeric(8) |
946 | + else: |
947 | + candidate = prefix + generate_random_alphanumeric(8) |
948 | + if not candidate in items: |
949 | + return candidate |
950 | + print "Random collision on %s" % candidate |
951 | + |
952 | + |
953 | +class TestUser(object): |
954 | + def __init__(self, name, secret, auth_url): |
955 | + self.name = name |
956 | + self.secret = secret |
957 | + self.auth_url = auth_url |
958 | + |
959 | + if not auth_url: |
960 | + raise exception.Error("auth_url is required") |
961 | + self.openstack_api = client.TestOpenStackClient(self.name, |
962 | + self.secret, |
963 | + self.auth_url) |
964 | + |
965 | + |
966 | +class IntegratedUnitTestContext(object): |
967 | + __INSTANCE = None |
968 | + |
969 | + def __init__(self): |
970 | + self.auth_manager = manager.AuthManager() |
971 | + |
972 | + self.wsgi_server = None |
973 | + self.wsgi_apps = [] |
974 | + self.api_service = None |
975 | + |
976 | + self.services = [] |
977 | + self.auth_url = None |
978 | + self.project_name = None |
979 | + |
980 | + self.setup() |
981 | + |
982 | + def setup(self): |
983 | + self._start_services() |
984 | + |
985 | + self._create_test_user() |
986 | + |
987 | + def _create_test_user(self): |
988 | + self.test_user = self._create_unittest_user() |
989 | + |
990 | + # No way to currently pass this through the OpenStack API |
991 | + self.project_name = 'openstack' |
992 | + self._configure_project(self.project_name, self.test_user) |
993 | + |
994 | + def _start_services(self): |
995 | + # WSGI shutdown broken :-( |
996 | + # bug731668 |
997 | + if not self.api_service: |
998 | + self._start_api_service() |
999 | + |
1000 | + def cleanup(self): |
1001 | + for service in self.services: |
1002 | + service.kill() |
1003 | + self.services = [] |
1004 | + # TODO(justinsb): Shutdown WSGI & anything else we startup |
1005 | + # bug731668 |
1006 | + # WSGI shutdown broken :-( |
1007 | + # self.wsgi_server.terminate() |
1008 | + # self.wsgi_server = None |
1009 | + self.test_user = None |
1010 | + |
1011 | + def _create_unittest_user(self): |
1012 | + users = self.auth_manager.get_users() |
1013 | + user_names = [user.name for user in users] |
1014 | + auth_name = generate_new_element(user_names, 'unittest_user_') |
1015 | + auth_key = generate_random_alphanumeric(16) |
1016 | + |
1017 | + # Right now there's a bug where auth_name and auth_key are reversed |
1018 | + # bug732907 |
1019 | + auth_key = auth_name |
1020 | + |
1021 | + self.auth_manager.create_user(auth_name, auth_name, auth_key, False) |
1022 | + return TestUser(auth_name, auth_key, self.auth_url) |
1023 | + |
1024 | + def _configure_project(self, project_name, user): |
1025 | + projects = self.auth_manager.get_projects() |
1026 | + project_names = [project.name for project in projects] |
1027 | + if not project_name in project_names: |
1028 | + project = self.auth_manager.create_project(project_name, |
1029 | + user.name, |
1030 | + description=None, |
1031 | + member_users=None) |
1032 | + else: |
1033 | + self.auth_manager.add_to_project(user.name, project_name) |
1034 | + |
1035 | + def _start_api_service(self): |
1036 | + api_service = service.ApiService.create() |
1037 | + api_service.start() |
1038 | + |
1039 | + if not api_service: |
1040 | + raise Exception("API Service was None") |
1041 | + |
1042 | + # WSGI shutdown broken :-( |
1043 | + #self.services.append(volume_service) |
1044 | + self.api_service = api_service |
1045 | + |
1046 | + self.auth_url = 'http://localhost:8774/v1.0' |
1047 | + |
1048 | + return api_service |
1049 | + |
1050 | + # WSGI shutdown broken :-( |
1051 | + # bug731668 |
1052 | + #@staticmethod |
1053 | + #def get(): |
1054 | + # if not IntegratedUnitTestContext.__INSTANCE: |
1055 | + # IntegratedUnitTestContext.startup() |
1056 | + # #raise Error("Must call IntegratedUnitTestContext::startup") |
1057 | + # return IntegratedUnitTestContext.__INSTANCE |
1058 | + |
1059 | + @staticmethod |
1060 | + def startup(): |
1061 | + # Because WSGI shutdown is broken at the moment, we have to recycle |
1062 | + # bug731668 |
1063 | + if IntegratedUnitTestContext.__INSTANCE: |
1064 | + #raise Error("Multiple calls to IntegratedUnitTestContext.startup") |
1065 | + IntegratedUnitTestContext.__INSTANCE.setup() |
1066 | + else: |
1067 | + IntegratedUnitTestContext.__INSTANCE = IntegratedUnitTestContext() |
1068 | + return IntegratedUnitTestContext.__INSTANCE |
1069 | + |
1070 | + @staticmethod |
1071 | + def shutdown(): |
1072 | + if not IntegratedUnitTestContext.__INSTANCE: |
1073 | + raise Error("Must call IntegratedUnitTestContext::startup") |
1074 | + IntegratedUnitTestContext.__INSTANCE.cleanup() |
1075 | + # WSGI shutdown broken :-( |
1076 | + # bug731668 |
1077 | + #IntegratedUnitTestContext.__INSTANCE = None |
1078 | |
1079 | === added file 'nova/tests/integrated/test_login.py' |
1080 | --- nova/tests/integrated/test_login.py 1970-01-01 00:00:00 +0000 |
1081 | +++ nova/tests/integrated/test_login.py 2011-03-15 05:15:51 +0000 |
1082 | @@ -0,0 +1,79 @@ |
1083 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
1084 | + |
1085 | +# Copyright 2011 Justin Santa Barbara |
1086 | +# All Rights Reserved. |
1087 | +# |
1088 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1089 | +# not use this file except in compliance with the License. You may obtain |
1090 | +# a copy of the License at |
1091 | +# |
1092 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1093 | +# |
1094 | +# Unless required by applicable law or agreed to in writing, software |
1095 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1096 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1097 | +# License for the specific language governing permissions and limitations |
1098 | +# under the License. |
1099 | + |
1100 | +import unittest |
1101 | + |
1102 | +from nova import flags |
1103 | +from nova import test |
1104 | +from nova.log import logging |
1105 | +from nova.tests.integrated import integrated_helpers |
1106 | +from nova.tests.integrated.api import client |
1107 | + |
1108 | + |
1109 | +LOG = logging.getLogger('nova.tests.integrated') |
1110 | + |
1111 | +FLAGS = flags.FLAGS |
1112 | +FLAGS.verbose = True |
1113 | + |
1114 | + |
1115 | +class LoginTest(test.TestCase): |
1116 | + def setUp(self): |
1117 | + super(LoginTest, self).setUp() |
1118 | + context = integrated_helpers.IntegratedUnitTestContext.startup() |
1119 | + self.user = context.test_user |
1120 | + self.api = self.user.openstack_api |
1121 | + |
1122 | + def tearDown(self): |
1123 | + integrated_helpers.IntegratedUnitTestContext.shutdown() |
1124 | + super(LoginTest, self).tearDown() |
1125 | + |
1126 | + def test_login(self): |
1127 | + """Simple check - we list flavors - so we know we're logged in""" |
1128 | + flavors = self.api.get_flavors() |
1129 | + for flavor in flavors: |
1130 | + LOG.debug(_("flavor: %s") % flavor) |
1131 | + |
1132 | + def test_bad_login_password(self): |
1133 | + """Test that I get a 401 with a bad username""" |
1134 | + bad_credentials_api = client.TestOpenStackClient(self.user.name, |
1135 | + "notso_password", |
1136 | + self.user.auth_url) |
1137 | + |
1138 | + self.assertRaises(client.OpenstackApiAuthenticationException, |
1139 | + bad_credentials_api.get_flavors) |
1140 | + |
1141 | + def test_bad_login_username(self): |
1142 | + """Test that I get a 401 with a bad password""" |
1143 | + bad_credentials_api = client.TestOpenStackClient("notso_username", |
1144 | + self.user.secret, |
1145 | + self.user.auth_url) |
1146 | + |
1147 | + self.assertRaises(client.OpenstackApiAuthenticationException, |
1148 | + bad_credentials_api.get_flavors) |
1149 | + |
1150 | + def test_bad_login_both_bad(self): |
1151 | + """Test that I get a 401 with both bad username and bad password""" |
1152 | + bad_credentials_api = client.TestOpenStackClient("notso_username", |
1153 | + "notso_password", |
1154 | + self.user.auth_url) |
1155 | + |
1156 | + self.assertRaises(client.OpenstackApiAuthenticationException, |
1157 | + bad_credentials_api.get_flavors) |
1158 | + |
1159 | + |
1160 | +if __name__ == "__main__": |
1161 | + unittest.main() |
1162 | |
1163 | === added file 'nova/virt/cpuinfo.xml.template' |
1164 | --- nova/virt/cpuinfo.xml.template 1970-01-01 00:00:00 +0000 |
1165 | +++ nova/virt/cpuinfo.xml.template 2011-03-15 05:15:51 +0000 |
1166 | @@ -0,0 +1,9 @@ |
1167 | +<cpu> |
1168 | + <arch>$arch</arch> |
1169 | + <model>$model</model> |
1170 | + <vendor>$vendor</vendor> |
1171 | + <topology sockets="$topology.sockets" cores="$topology.cores" threads="$topology.threads"/> |
1172 | +#for $var in $features |
1173 | + <features name="$var" /> |
1174 | +#end for |
1175 | +</cpu> |
1176 | |
1177 | === renamed file 'nova/virt/cpuinfo.xml.template' => 'nova/virt/cpuinfo.xml.template.moved' |