1
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
2
# Copyright (c) 2011 OpenStack Foundation
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
16
from datetime import datetime
21
import urllib.parse as urlparse
23
from cinderclient import client as base_client
24
from cinderclient.tests.unit import fakes
25
import cinderclient.tests.unit.utils as utils
26
from cinderclient.v1 import client
29
def _stub_volume(**kwargs):
33
'display_description': None,
36
"availability_zone": "cinder",
37
"created_at": "2012-08-27T00:00:00.000000",
38
"id": '00000000-0000-0000-0000-000000000000',
42
"status": "available",
43
"volume_type": "None",
49
def _stub_snapshot(**kwargs):
51
"created_at": "2012-08-28T16:30:31.000000",
52
"display_description": None,
54
"id": '11111111-1111-1111-1111-111111111111',
56
"status": "available",
57
"volume_id": '00000000-0000-0000-0000-000000000000',
59
snapshot.update(kwargs)
63
def _self_href(base_uri, tenant_id, backup_id):
64
return '%s/v1/%s/backups/%s' % (base_uri, tenant_id, backup_id)
67
def _bookmark_href(base_uri, tenant_id, backup_id):
68
return '%s/%s/backups/%s' % (base_uri, tenant_id, backup_id)
71
def _stub_backup_full(id, base_uri, tenant_id):
75
'description': 'nightly backup',
76
'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b',
77
'container': 'volumebackups',
80
'availability_zone': 'az1',
81
'created_at': '2013-04-12T08:16:37.000000',
82
'status': 'available',
85
'href': _self_href(base_uri, tenant_id, id),
89
'href': _bookmark_href(base_uri, tenant_id, id),
96
def _stub_backup(id, base_uri, tenant_id):
102
'href': _self_href(base_uri, tenant_id, id),
106
'href': _bookmark_href(base_uri, tenant_id, id),
114
return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'}
117
def _stub_qos_full(id, base_uri, tenant_id, name=None, specs=None):
127
'consumer': 'back-end',
131
'href': _bookmark_href(base_uri, tenant_id, id),
137
def _stub_qos_associates(id, name):
139
'assoications_type': 'volume_type',
145
def _stub_transfer_full(id, base_uri, tenant_id):
149
'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc',
150
'created_at': '2013-04-12T08:16:37.000000',
151
'auth_key': '123456',
154
'href': _self_href(base_uri, tenant_id, id),
158
'href': _bookmark_href(base_uri, tenant_id, id),
165
def _stub_transfer(id, base_uri, tenant_id):
169
'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc',
172
'href': _self_href(base_uri, tenant_id, id),
176
'href': _bookmark_href(base_uri, tenant_id, id),
183
def _stub_extend(id, new_size):
184
return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'}
187
class FakeClient(fakes.FakeClient, client.Client):
189
def __init__(self, *args, **kwargs):
190
client.Client.__init__(self, 'username', 'password',
191
'project_id', 'auth_url',
192
extensions=kwargs.get('extensions'))
193
self.client = FakeHTTPClient(**kwargs)
195
def get_volume_api_version_from_endpoint(self):
196
return self.client.get_volume_api_version_from_endpoint()
199
class FakeHTTPClient(base_client.HTTPClient):
201
def __init__(self, **kwargs):
202
self.username = 'username'
203
self.password = 'password'
204
self.auth_url = 'auth_url'
206
self.management_url = 'http://10.0.2.15:8776/v1/fake'
208
def _cs_request(self, url, method, **kwargs):
209
# Check that certain things are called correctly
210
if method in ['GET', 'DELETE']:
211
assert 'body' not in kwargs
212
elif method == 'PUT':
213
assert 'body' in kwargs
216
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
218
munged_url = url.rsplit('?', 1)[0]
219
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
220
munged_url = munged_url.replace('-', '_')
222
callback = "%s_%s" % (method.lower(), munged_url)
224
if not hasattr(self, callback):
225
raise AssertionError('Called unknown API method: %s %s, '
226
'expected fakes method name: %s' %
227
(method, url, callback))
230
self.callstack.append((method, url, kwargs.get('body', None)))
231
status, headers, body = getattr(self, callback)(**kwargs)
232
r = utils.TestResponse({
233
"status_code": status,
239
if hasattr(status, 'items'):
240
return utils.TestResponse(status), body
242
return utils.TestResponse({"status": status}), body
244
def get_volume_api_version_from_endpoint(self):
245
magic_tuple = urlparse.urlsplit(self.management_url)
246
scheme, netloc, path, query, frag = magic_tuple
247
return path.lstrip('/').split('/')[0][1:]
253
def get_snapshots_detail(self, **kw):
254
return (200, {}, {'snapshots': [
258
def get_snapshots_1234(self, **kw):
259
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
261
def get_snapshots_5678(self, **kw):
262
return (200, {}, {'snapshot': _stub_snapshot(id='5678')})
264
def put_snapshots_1234(self, **kw):
265
snapshot = _stub_snapshot(id='1234')
266
snapshot.update(kw['body']['snapshot'])
267
return (200, {}, {'snapshot': snapshot})
269
def post_snapshots_1234_action(self, body, **kw):
272
assert len(list(body)) == 1
273
action = list(body)[0]
274
if action == 'os-reset_status':
275
assert 'status' in body['os-reset_status']
276
elif action == 'os-update_snapshot_status':
277
assert 'status' in body['os-update_snapshot_status']
279
raise AssertionError("Unexpected action: %s" % action)
280
return (resp, {}, _body)
282
def post_snapshots_5678_action(self, body, **kw):
283
return self.post_snapshots_1234_action(body, **kw)
285
def delete_snapshots_1234(self, **kw):
288
def delete_snapshots_5678(self, **kw):
295
def put_volumes_1234(self, **kw):
296
volume = _stub_volume(id='1234')
297
volume.update(kw['body']['volume'])
298
return (200, {}, {'volume': volume})
300
def get_volumes(self, **kw):
301
return (200, {}, {"volumes": [
302
{'id': 1234, 'name': 'sample-volume'},
303
{'id': 5678, 'name': 'sample-volume2'}
306
# TODO(jdg): This will need to change
307
# at the very least it's not complete
308
def get_volumes_detail(self, **kw):
309
return (200, {}, {"volumes": [
310
{'id': kw.get('id', 1234),
311
'name': 'sample-volume',
312
'attachments': [{'server_id': 1234}]},
315
def get_volumes_1234(self, **kw):
316
r = {'volume': self.get_volumes_detail(id=1234)[2]['volumes'][0]}
319
def get_volumes_5678(self, **kw):
320
r = {'volume': self.get_volumes_detail(id=5678)[2]['volumes'][0]}
323
def get_volumes_1234_encryption(self, **kw):
324
r = {'encryption_key_id': 'id'}
327
def post_volumes_1234_action(self, body, **kw):
330
assert len(list(body)) == 1
331
action = list(body)[0]
332
if action == 'os-attach':
333
assert sorted(list(body[action])) == ['instance_uuid',
336
elif action == 'os-detach':
337
assert body[action] is None
338
elif action == 'os-reserve':
339
assert body[action] is None
340
elif action == 'os-unreserve':
341
assert body[action] is None
342
elif action == 'os-initialize_connection':
343
assert list(body[action]) == ['connector']
344
return (202, {}, {'connection_info': 'foos'})
345
elif action == 'os-terminate_connection':
346
assert list(body[action]) == ['connector']
347
elif action == 'os-begin_detaching':
348
assert body[action] is None
349
elif action == 'os-roll_detaching':
350
assert body[action] is None
351
elif action == 'os-reset_status':
352
assert 'status' in body[action]
353
elif action == 'os-extend':
354
assert list(body[action]) == ['new_size']
355
elif action == 'os-migrate_volume':
356
assert 'host' in body[action]
357
assert 'force_host_copy' in body[action]
358
elif action == 'os-update_readonly_flag':
359
assert list(body[action]) == ['readonly']
360
elif action == 'os-set_bootable':
361
assert list(body[action]) == ['bootable']
363
raise AssertionError("Unexpected action: %s" % action)
364
return (resp, {}, _body)
366
def post_volumes_5678_action(self, body, **kw):
367
return self.post_volumes_1234_action(body, **kw)
369
def post_volumes(self, **kw):
370
return (202, {}, {'volume': {}})
372
def delete_volumes_1234(self, **kw):
373
return (202, {}, None)
375
def delete_volumes_5678(self, **kw):
376
return (202, {}, None)
382
def get_os_quota_sets_test(self, **kw):
383
return (200, {}, {'quota_set': {
385
'metadata_items': [],
390
'backup_gigabytes': 1}})
392
def get_os_quota_sets_test_defaults(self):
393
return (200, {}, {'quota_set': {
395
'metadata_items': [],
400
'backup_gigabytes': 1}})
402
def put_os_quota_sets_test(self, body, **kw):
403
assert list(body) == ['quota_set']
404
fakes.assert_has_keys(body['quota_set'],
405
required=['tenant_id'])
406
return (200, {}, {'quota_set': {
408
'metadata_items': [],
413
'backup_gigabytes': 1}})
415
def delete_os_quota_sets_1234(self, **kw):
418
def delete_os_quota_sets_test(self, **kw):
425
def get_os_quota_class_sets_test(self, **kw):
426
return (200, {}, {'quota_class_set': {
427
'class_name': 'test',
428
'metadata_items': [],
433
'backup_gigabytes': 1}})
435
def put_os_quota_class_sets_test(self, body, **kw):
436
assert list(body) == ['quota_class_set']
437
fakes.assert_has_keys(body['quota_class_set'],
438
required=['class_name'])
439
return (200, {}, {'quota_class_set': {
440
'class_name': 'test',
441
'metadata_items': [],
446
'backup_gigabytes': 1}})
451
def get_types(self, **kw):
453
'volume_types': [{'id': 1,
454
'name': 'test-type-1',
457
'name': 'test-type-2',
458
'extra_specs': {}}]})
460
def get_types_1(self, **kw):
461
return (200, {}, {'volume_type': {'id': 1,
462
'name': 'test-type-1',
465
def get_types_2(self, **kw):
466
return (200, {}, {'volume_type': {'id': 2,
467
'name': 'test-type-2',
470
def post_types(self, body, **kw):
471
return (202, {}, {'volume_type': {'id': 3,
472
'name': 'test-type-3',
475
def post_types_1_extra_specs(self, body, **kw):
476
assert list(body) == ['extra_specs']
477
return (200, {}, {'extra_specs': {'k': 'v'}})
479
def delete_types_1_extra_specs_k(self, **kw):
480
return(204, {}, None)
482
def delete_types_1(self, **kw):
483
return (202, {}, None)
486
# VolumeEncryptionTypes
488
def get_types_1_encryption(self, **kw):
489
return (200, {}, {'id': 1, 'volume_type_id': 1, 'provider': 'test',
490
'cipher': 'test', 'key_size': 1,
491
'control_location': 'front-end'})
493
def get_types_2_encryption(self, **kw):
496
def post_types_2_encryption(self, body, **kw):
497
return (200, {}, {'encryption': body})
499
def put_types_1_encryption_1(self, body, **kw):
502
def delete_types_1_encryption_provider(self, **kw):
503
return (202, {}, None)
508
def delete_volumes_1234_metadata_test_key(self, **kw):
509
return (204, {}, None)
511
def delete_volumes_1234_metadata_key1(self, **kw):
512
return (204, {}, None)
514
def delete_volumes_1234_metadata_key2(self, **kw):
515
return (204, {}, None)
517
def post_volumes_1234_metadata(self, **kw):
518
return (204, {}, {'metadata': {'test_key': 'test_value'}})
521
# List all extensions
523
def get_extensions(self, **kw):
527
"description": "Fake extension number 1",
530
"namespace": ("http://docs.openstack.org/"
531
"/ext/fake1/api/v1.1"),
532
"updated": "2011-06-09T00:00:00+00:00"
536
"description": "Fake extension number 2",
539
"namespace": ("http://docs.openstack.org/"
540
"/ext/fake1/api/v1.1"),
541
"updated": "2011-06-09T00:00:00+00:00"
544
return (200, {}, {"extensions": exts, })
550
def get_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
551
base_uri = 'http://localhost:8776'
552
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
553
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
555
{'backup': _stub_backup_full(backup1, base_uri, tenant_id)})
557
def get_backups_detail(self, **kw):
558
base_uri = 'http://localhost:8776'
559
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
560
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
561
backup2 = 'd09534c6-08b8-4441-9e87-8976f3a8f699'
564
_stub_backup_full(backup1, base_uri, tenant_id),
565
_stub_backup_full(backup2, base_uri, tenant_id)]})
567
def delete_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
568
return (202, {}, None)
570
def post_backups(self, **kw):
571
base_uri = 'http://localhost:8776'
572
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
573
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
575
{'backup': _stub_backup(backup1, base_uri, tenant_id)})
577
def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_restore(self, **kw):
579
{'restore': _stub_restore()})
581
def post_backups_1234_restore(self, **kw):
583
{'restore': _stub_restore()})
589
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C(self, **kw):
590
base_uri = 'http://localhost:8776'
591
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
592
qos_id1 = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
594
_stub_qos_full(qos_id1, base_uri, tenant_id))
596
def get_qos_specs(self, **kw):
597
base_uri = 'http://localhost:8776'
598
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
599
qos_id1 = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
600
qos_id2 = '0FD8DD14-A396-4E55-9573-1FE59042E95B'
603
_stub_qos_full(qos_id1, base_uri, tenant_id, 'name-1'),
604
_stub_qos_full(qos_id2, base_uri, tenant_id)]})
606
def post_qos_specs(self, **kw):
607
base_uri = 'http://localhost:8776'
608
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
609
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
610
qos_name = 'qos-name'
612
_stub_qos_full(qos_id, base_uri, tenant_id, qos_name))
614
def put_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C(self, **kw):
615
return (202, {}, None)
617
def put_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_delete_keys(
619
return (202, {}, None)
621
def delete_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C(self, **kw):
622
return (202, {}, None)
624
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_associations(
626
type_id1 = '4230B13A-7A37-4E84-B777-EFBA6FCEE4FF'
627
type_id2 = '4230B13A-AB37-4E84-B777-EFBA6FCEE4FF'
631
{'qos_associations': [
632
_stub_qos_associates(type_id1, type_name1),
633
_stub_qos_associates(type_id2, type_name2)]})
635
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_associate(
637
return (202, {}, None)
639
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_disassociate(
641
return (202, {}, None)
643
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_disassociate_all(
645
return (202, {}, None)
651
def get_os_volume_transfer_5678(self, **kw):
652
base_uri = 'http://localhost:8776'
653
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
657
_stub_transfer_full(transfer1, base_uri, tenant_id)})
659
def get_os_volume_transfer_detail(self, **kw):
660
base_uri = 'http://localhost:8776'
661
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
663
transfer2 = 'f625ec3e-13dd-4498-a22a-50afd534cc41'
666
_stub_transfer_full(transfer1, base_uri, tenant_id),
667
_stub_transfer_full(transfer2, base_uri, tenant_id)]})
669
def delete_os_volume_transfer_5678(self, **kw):
670
return (202, {}, None)
672
def post_os_volume_transfer(self, **kw):
673
base_uri = 'http://localhost:8776'
674
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
677
{'transfer': _stub_transfer(transfer1, base_uri, tenant_id)})
679
def post_os_volume_transfer_5678_accept(self, **kw):
680
base_uri = 'http://localhost:8776'
681
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
684
{'transfer': _stub_transfer(transfer1, base_uri, tenant_id)})
689
def get_os_services(self, **kw):
690
host = kw.get('host', None)
691
binary = kw.get('binary', None)
694
'binary': 'cinder-volume',
699
'updated_at': datetime(2012, 10, 29, 13, 42, 2)
702
'binary': 'cinder-volume',
705
'status': 'disabled',
707
'updated_at': datetime(2012, 9, 18, 8, 3, 38)
710
'binary': 'cinder-scheduler',
713
'status': 'disabled',
715
'updated_at': datetime(2012, 9, 18, 8, 3, 38)
719
services = filter(lambda i: i['host'] == host, services)
721
services = filter(lambda i: i['binary'] == binary, services)
722
return (200, {}, {'services': services})
724
def put_os_services_enable(self, body, **kw):
725
return (200, {}, {'host': body['host'], 'binary': body['binary'],
726
'status': 'enabled'})
728
def put_os_services_disable(self, body, **kw):
729
return (200, {}, {'host': body['host'], 'binary': body['binary'],
730
'status': 'disabled'})
732
def put_os_services_disable_log_reason(self, body, **kw):
733
return (200, {}, {'host': body['host'], 'binary': body['binary'],
734
'status': 'disabled',
735
'disabled_reason': body['disabled_reason']})
737
def get_os_availability_zone(self, **kw):
739
"availabilityZoneInfo": [
741
"zoneName": "zone-1",
742
"zoneState": {"available": True},
746
"zoneName": "zone-2",
747
"zoneState": {"available": False},
753
def get_os_availability_zone_detail(self, **kw):
755
"availabilityZoneInfo": [
757
"zoneName": "zone-1",
758
"zoneState": {"available": True},
765
datetime(2012, 12, 26, 14, 45, 25, 0)
771
"zoneName": "internal",
772
"zoneState": {"available": True},
779
datetime(2012, 12, 26, 14, 45, 24, 0)
785
"zoneName": "zone-2",
786
"zoneState": {"available": False},
792
def post_snapshots_1234_metadata(self, **kw):
793
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
795
def delete_snapshots_1234_metadata_key1(self, **kw):
796
return (200, {}, None)
798
def delete_snapshots_1234_metadata_key2(self, **kw):
799
return (200, {}, None)
801
def put_volumes_1234_metadata(self, **kw):
802
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
804
def put_snapshots_1234_metadata(self, **kw):
805
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})