1# Copyright (c) 2013 OpenStack Foundation 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16from unittest import mock 17 18import ddt 19import fixtures 20from requests_mock.contrib import fixture as requests_mock_fixture 21from six.moves.urllib import parse 22 23from cinderclient import client 24from cinderclient import exceptions 25from cinderclient import shell 26from cinderclient.tests.unit.fixture_data import keystone_client 27from cinderclient.tests.unit import utils 28from cinderclient.tests.unit.v2 import fakes 29from cinderclient.v2 import shell as test_shell 30from cinderclient.v2 import volume_backups 31from cinderclient.v2 import volumes 32 33 34@ddt.ddt 35@mock.patch.object(client, 'Client', fakes.FakeClient) 36class ShellTest(utils.TestCase): 37 38 FAKE_ENV = { 39 'CINDER_USERNAME': 'username', 40 'CINDER_PASSWORD': 'password', 41 'CINDER_PROJECT_ID': 'project_id', 42 'OS_VOLUME_API_VERSION': '2', 43 'CINDER_URL': keystone_client.BASE_URL, 44 } 45 46 # Patch os.environ to avoid required auth info. 47 def setUp(self): 48 """Run before each test.""" 49 super(ShellTest, self).setUp() 50 for var in self.FAKE_ENV: 51 self.useFixture(fixtures.EnvironmentVariable(var, 52 self.FAKE_ENV[var])) 53 54 self.mock_completion() 55 56 self.shell = shell.OpenStackCinderShell() 57 58 self.requests = self.useFixture(requests_mock_fixture.Fixture()) 59 self.requests.register_uri( 60 'GET', keystone_client.BASE_URL, 61 text=keystone_client.keystone_request_callback) 62 63 self.cs = mock.Mock() 64 65 def _make_args(self, args): 66 class Args(object): 67 def __init__(self, entries): 68 self.__dict__.update(entries) 69 70 return Args(args) 71 72 def run_command(self, cmd): 73 self.shell.main(cmd.split()) 74 75 def assert_called(self, method, url, body=None, 76 partial_body=None, **kwargs): 77 return self.shell.cs.assert_called(method, url, body, 78 partial_body, **kwargs) 79 80 def test_list(self): 81 self.run_command('list') 82 # NOTE(jdg): we default to detail currently 83 self.assert_called('GET', '/volumes/detail') 84 85 def test_list_filter_tenant_with_all_tenants(self): 86 self.run_command('list --all-tenants=1 --tenant 123') 87 self.assert_called('GET', 88 '/volumes/detail?all_tenants=1&project_id=123') 89 90 def test_list_filter_tenant_without_all_tenants(self): 91 self.run_command('list --tenant 123') 92 self.assert_called('GET', 93 '/volumes/detail?all_tenants=1&project_id=123') 94 95 def test_metadata_args_with_limiter(self): 96 self.run_command('create --metadata key1="--test1" 1') 97 self.assert_called('GET', '/volumes/1234') 98 expected = {'volume': {'imageRef': None, 99 'size': 1, 100 'availability_zone': None, 101 'source_volid': None, 102 'consistencygroup_id': None, 103 'name': None, 104 'snapshot_id': None, 105 'metadata': {'key1': '"--test1"'}, 106 'volume_type': None, 107 'description': None, 108 }} 109 self.assert_called_anytime('POST', '/volumes', expected) 110 111 def test_metadata_args_limiter_display_name(self): 112 self.run_command('create --metadata key1="--t1" --name="t" 1') 113 self.assert_called('GET', '/volumes/1234') 114 expected = {'volume': {'imageRef': None, 115 'size': 1, 116 'availability_zone': None, 117 'source_volid': None, 118 'consistencygroup_id': None, 119 'name': '"t"', 120 'snapshot_id': None, 121 'metadata': {'key1': '"--t1"'}, 122 'volume_type': None, 123 'description': None, 124 }} 125 self.assert_called_anytime('POST', '/volumes', expected) 126 127 def test_delimit_metadata_args(self): 128 self.run_command('create --metadata key1="test1" key2="test2" 1') 129 expected = {'volume': {'imageRef': None, 130 'size': 1, 131 'availability_zone': None, 132 'source_volid': None, 133 'consistencygroup_id': None, 134 'name': None, 135 'snapshot_id': None, 136 'metadata': {'key1': '"test1"', 137 'key2': '"test2"'}, 138 'volume_type': None, 139 'description': None, 140 }} 141 self.assert_called_anytime('POST', '/volumes', expected) 142 143 def test_delimit_metadata_args_display_name(self): 144 self.run_command('create --metadata key1="t1" --name="t" 1') 145 self.assert_called('GET', '/volumes/1234') 146 expected = {'volume': {'imageRef': None, 147 'size': 1, 148 'availability_zone': None, 149 'source_volid': None, 150 'consistencygroup_id': None, 151 'name': '"t"', 152 'snapshot_id': None, 153 'metadata': {'key1': '"t1"'}, 154 'volume_type': None, 155 'description': None, 156 }} 157 self.assert_called_anytime('POST', '/volumes', expected) 158 159 def test_list_filter_status(self): 160 self.run_command('list --status=available') 161 self.assert_called('GET', '/volumes/detail?status=available') 162 163 def test_list_filter_bootable_true(self): 164 self.run_command('list --bootable=true') 165 self.assert_called('GET', '/volumes/detail?bootable=true') 166 167 def test_list_filter_bootable_false(self): 168 self.run_command('list --bootable=false') 169 self.assert_called('GET', '/volumes/detail?bootable=false') 170 171 def test_list_filter_name(self): 172 self.run_command('list --name=1234') 173 self.assert_called('GET', '/volumes/detail?name=1234') 174 175 def test_list_all_tenants(self): 176 self.run_command('list --all-tenants=1') 177 self.assert_called('GET', '/volumes/detail?all_tenants=1') 178 179 def test_list_marker(self): 180 self.run_command('list --marker=1234') 181 self.assert_called('GET', '/volumes/detail?marker=1234') 182 183 def test_list_limit(self): 184 self.run_command('list --limit=10') 185 self.assert_called('GET', '/volumes/detail?limit=10') 186 187 @mock.patch("cinderclient.utils.print_list") 188 def test_list_field(self, mock_print): 189 self.run_command('list --field Status,Name,Size,Bootable') 190 self.assert_called('GET', '/volumes/detail') 191 key_list = ['ID', 'Status', 'Name', 'Size', 'Bootable'] 192 mock_print.assert_called_once_with(mock.ANY, key_list, 193 exclude_unavailable=True, sortby_index=0) 194 195 @mock.patch("cinderclient.utils.print_list") 196 def test_list_field_with_all_tenants(self, mock_print): 197 self.run_command('list --field Status,Name,Size,Bootable ' 198 '--all-tenants 1') 199 self.assert_called('GET', '/volumes/detail?all_tenants=1') 200 key_list = ['ID', 'Status', 'Name', 'Size', 'Bootable'] 201 mock_print.assert_called_once_with(mock.ANY, key_list, 202 exclude_unavailable=True, sortby_index=0) 203 204 @mock.patch("cinderclient.utils.print_list") 205 def test_list_duplicate_fields(self, mock_print): 206 self.run_command('list --field Status,id,Size,status') 207 self.assert_called('GET', '/volumes/detail') 208 key_list = ['ID', 'Status', 'Size'] 209 mock_print.assert_called_once_with(mock.ANY, key_list, 210 exclude_unavailable=True, sortby_index=0) 211 212 @mock.patch("cinderclient.utils.print_list") 213 def test_list_field_with_tenant(self, mock_print): 214 self.run_command('list --field Status,Name,Size,Bootable ' 215 '--tenant 123') 216 self.assert_called('GET', 217 '/volumes/detail?all_tenants=1&project_id=123') 218 key_list = ['ID', 'Status', 'Name', 'Size', 'Bootable'] 219 mock_print.assert_called_once_with(mock.ANY, key_list, 220 exclude_unavailable=True, sortby_index=0) 221 222 def test_list_sort_name(self): 223 # Client 'name' key is mapped to 'display_name' 224 self.run_command('list --sort=name') 225 self.assert_called('GET', '/volumes/detail?sort=display_name') 226 227 def test_list_sort_single_key_only(self): 228 self.run_command('list --sort=id') 229 self.assert_called('GET', '/volumes/detail?sort=id') 230 231 def test_list_sort_single_key_trailing_colon(self): 232 self.run_command('list --sort=id:') 233 self.assert_called('GET', '/volumes/detail?sort=id') 234 235 def test_list_sort_single_key_and_dir(self): 236 self.run_command('list --sort=id:asc') 237 url = '/volumes/detail?%s' % parse.urlencode([('sort', 'id:asc')]) 238 self.assert_called('GET', url) 239 240 def test_list_sort_multiple_keys_only(self): 241 self.run_command('list --sort=id,status,size') 242 url = ('/volumes/detail?%s' % 243 parse.urlencode([('sort', 'id,status,size')])) 244 self.assert_called('GET', url) 245 246 def test_list_sort_multiple_keys_and_dirs(self): 247 self.run_command('list --sort=id:asc,status,size:desc') 248 url = ('/volumes/detail?%s' % 249 parse.urlencode([('sort', 'id:asc,status,size:desc')])) 250 self.assert_called('GET', url) 251 252 def test_list_reorder_with_sort(self): 253 # sortby_index is None if there is sort information 254 for cmd in ['list --sort=name', 255 'list --sort=name:asc']: 256 with mock.patch('cinderclient.utils.print_list') as mock_print: 257 self.run_command(cmd) 258 mock_print.assert_called_once_with( 259 mock.ANY, mock.ANY, exclude_unavailable=True, 260 sortby_index=None) 261 262 def test_list_reorder_without_sort(self): 263 # sortby_index is 0 without sort information 264 for cmd in ['list', 'list --all-tenants']: 265 with mock.patch('cinderclient.utils.print_list') as mock_print: 266 self.run_command(cmd) 267 mock_print.assert_called_once_with( 268 mock.ANY, mock.ANY, exclude_unavailable=True, 269 sortby_index=0) 270 271 def test_list_availability_zone(self): 272 self.run_command('availability-zone-list') 273 self.assert_called('GET', '/os-availability-zone') 274 275 def test_create_volume_from_snapshot(self): 276 expected = {'volume': {'size': None}} 277 278 expected['volume']['snapshot_id'] = '1234' 279 self.run_command('create --snapshot-id=1234') 280 self.assert_called_anytime('POST', '/volumes', partial_body=expected) 281 self.assert_called('GET', '/volumes/1234') 282 283 expected['volume']['size'] = 2 284 self.run_command('create --snapshot-id=1234 2') 285 self.assert_called_anytime('POST', '/volumes', partial_body=expected) 286 self.assert_called('GET', '/volumes/1234') 287 288 def test_create_volume_from_volume(self): 289 expected = {'volume': {'size': None}} 290 291 expected['volume']['source_volid'] = '1234' 292 self.run_command('create --source-volid=1234') 293 self.assert_called_anytime('POST', '/volumes', partial_body=expected) 294 self.assert_called('GET', '/volumes/1234') 295 296 expected['volume']['size'] = 2 297 self.run_command('create --source-volid=1234 2') 298 self.assert_called_anytime('POST', '/volumes', partial_body=expected) 299 self.assert_called('GET', '/volumes/1234') 300 301 def test_create_volume_from_image(self): 302 expected = {'volume': {'size': 1, 303 'imageRef': '1234'}} 304 self.run_command('create --image=1234 1') 305 self.assert_called_anytime('POST', '/volumes', partial_body=expected) 306 self.assert_called('GET', '/volumes/1234') 307 308 def test_upload_to_image(self): 309 expected = {'os-volume_upload_image': {'force': False, 310 'container_format': 'bare', 311 'disk_format': 'raw', 312 'image_name': 'test-image'}} 313 self.run_command('upload-to-image 1234 test-image') 314 self.assert_called_anytime('GET', '/volumes/1234') 315 self.assert_called_anytime('POST', '/volumes/1234/action', 316 body=expected) 317 318 def test_upload_to_image_force(self): 319 expected = {'os-volume_upload_image': {'force': 'True', 320 'container_format': 'bare', 321 'disk_format': 'raw', 322 'image_name': 'test-image'}} 323 self.run_command('upload-to-image --force=True 1234 test-image') 324 self.assert_called_anytime('GET', '/volumes/1234') 325 self.assert_called_anytime('POST', '/volumes/1234/action', 326 body=expected) 327 328 def test_create_size_required_if_not_snapshot_or_clone(self): 329 self.assertRaises(SystemExit, self.run_command, 'create') 330 331 def test_create_size_zero_if_not_snapshot_or_clone(self): 332 expected = {'volume': {'size': 0}} 333 self.run_command('create 0') 334 self.assert_called_anytime('POST', '/volumes', partial_body=expected) 335 self.assert_called('GET', '/volumes/1234') 336 337 def test_show(self): 338 self.run_command('show 1234') 339 self.assert_called('GET', '/volumes/1234') 340 341 def test_delete(self): 342 self.run_command('delete 1234') 343 self.assert_called('DELETE', '/volumes/1234') 344 345 def test_delete_by_name(self): 346 self.run_command('delete sample-volume') 347 self.assert_called_anytime('GET', '/volumes/detail?all_tenants=1&' 348 'name=sample-volume') 349 self.assert_called('DELETE', '/volumes/1234') 350 351 def test_delete_multiple(self): 352 self.run_command('delete 1234 5678') 353 self.assert_called_anytime('DELETE', '/volumes/1234') 354 self.assert_called('DELETE', '/volumes/5678') 355 356 def test_delete_with_cascade_true(self): 357 self.run_command('delete 1234 --cascade') 358 self.assert_called('DELETE', '/volumes/1234?cascade=True') 359 self.run_command('delete --cascade 1234') 360 self.assert_called('DELETE', '/volumes/1234?cascade=True') 361 362 def test_delete_with_cascade_with_invalid_value(self): 363 self.assertRaises(SystemExit, self.run_command, 364 'delete 1234 --cascade 1234') 365 366 def test_backup(self): 367 self.run_command('backup-create 1234') 368 self.assert_called('POST', '/backups') 369 370 def test_backup_incremental(self): 371 self.run_command('backup-create 1234 --incremental') 372 self.assert_called('POST', '/backups') 373 374 def test_backup_force(self): 375 self.run_command('backup-create 1234 --force') 376 self.assert_called('POST', '/backups') 377 378 def test_backup_snapshot(self): 379 self.run_command('backup-create 1234 --snapshot-id 4321') 380 self.assert_called('POST', '/backups') 381 382 def test_multiple_backup_delete(self): 383 self.run_command('backup-delete 1234 5678') 384 self.assert_called_anytime('DELETE', '/backups/1234') 385 self.assert_called('DELETE', '/backups/5678') 386 387 def test_restore(self): 388 self.run_command('backup-restore 1234') 389 self.assert_called('POST', '/backups/1234/restore') 390 391 def test_restore_with_name(self): 392 self.run_command('backup-restore 1234 --name restore_vol') 393 expected = {'restore': {'volume_id': None, 'name': 'restore_vol'}} 394 self.assert_called('POST', '/backups/1234/restore', 395 body=expected) 396 397 def test_restore_with_name_error(self): 398 self.assertRaises(exceptions.CommandError, self.run_command, 399 'backup-restore 1234 --volume fake_vol --name ' 400 'restore_vol') 401 402 @ddt.data('backup_name', '1234') 403 @mock.patch('cinderclient.shell_utils.find_backup') 404 @mock.patch('cinderclient.utils.print_dict') 405 @mock.patch('cinderclient.utils.find_volume') 406 def test_do_backup_restore_with_name(self, 407 value, 408 mock_find_volume, 409 mock_print_dict, 410 mock_find_backup): 411 backup_id = '1234' 412 volume_id = '5678' 413 name = None 414 input = { 415 'backup': value, 416 'volume': volume_id, 417 'name': None 418 } 419 420 args = self._make_args(input) 421 with mock.patch.object(self.cs.restores, 422 'restore') as mocked_restore: 423 mock_find_volume.return_value = volumes.Volume(self, 424 {'id': volume_id}, 425 loaded=True) 426 mock_find_backup.return_value = volume_backups.VolumeBackup( 427 self, 428 {'id': backup_id}, 429 loaded=True) 430 test_shell.do_backup_restore(self.cs, args) 431 mock_find_backup.assert_called_once_with( 432 self.cs, 433 value) 434 mocked_restore.assert_called_once_with( 435 backup_id, 436 volume_id, 437 name) 438 self.assertTrue(mock_print_dict.called) 439 440 def test_record_export(self): 441 self.run_command('backup-export 1234') 442 self.assert_called('GET', '/backups/1234/export_record') 443 444 def test_record_import(self): 445 self.run_command('backup-import fake.driver URL_STRING') 446 expected = {'backup-record': {'backup_service': 'fake.driver', 447 'backup_url': 'URL_STRING'}} 448 self.assert_called('POST', '/backups/import_record', expected) 449 450 def test_snapshot_list_filter_volume_id(self): 451 self.run_command('snapshot-list --volume-id=1234') 452 self.assert_called('GET', '/snapshots/detail?volume_id=1234') 453 454 def test_snapshot_list_filter_status_and_volume_id(self): 455 self.run_command('snapshot-list --status=available --volume-id=1234') 456 self.assert_called('GET', '/snapshots/detail?' 457 'status=available&volume_id=1234') 458 459 def test_snapshot_list_filter_name(self): 460 self.run_command('snapshot-list --name abc') 461 self.assert_called('GET', '/snapshots/detail?name=abc') 462 463 @mock.patch("cinderclient.utils.print_list") 464 def test_snapshot_list_sort(self, mock_print_list): 465 self.run_command('snapshot-list --sort id') 466 self.assert_called('GET', '/snapshots/detail?sort=id') 467 columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size'] 468 mock_print_list.assert_called_once_with(mock.ANY, columns, 469 sortby_index=None) 470 471 def test_snapshot_list_filter_tenant_with_all_tenants(self): 472 self.run_command('snapshot-list --all-tenants=1 --tenant 123') 473 self.assert_called('GET', 474 '/snapshots/detail?all_tenants=1&project_id=123') 475 476 def test_snapshot_list_filter_tenant_without_all_tenants(self): 477 self.run_command('snapshot-list --tenant 123') 478 self.assert_called('GET', 479 '/snapshots/detail?all_tenants=1&project_id=123') 480 481 def test_rename(self): 482 # basic rename with positional arguments 483 self.run_command('rename 1234 new-name') 484 expected = {'volume': {'name': 'new-name'}} 485 self.assert_called('PUT', '/volumes/1234', body=expected) 486 # change description only 487 self.run_command('rename 1234 --description=new-description') 488 expected = {'volume': {'description': 'new-description'}} 489 self.assert_called('PUT', '/volumes/1234', body=expected) 490 # rename and change description 491 self.run_command('rename 1234 new-name ' 492 '--description=new-description') 493 expected = {'volume': { 494 'name': 'new-name', 495 'description': 'new-description', 496 }} 497 self.assert_called('PUT', '/volumes/1234', body=expected) 498 499 # Call rename with no arguments 500 self.assertRaises(SystemExit, self.run_command, 'rename') 501 502 def test_rename_invalid_args(self): 503 """Ensure that error generated does not reference an HTTP code.""" 504 505 self.assertRaisesRegex(exceptions.ClientException, 506 '(?!HTTP)', 507 self.run_command, 508 'rename volume-1234-abcd') 509 510 def test_rename_snapshot(self): 511 # basic rename with positional arguments 512 self.run_command('snapshot-rename 1234 new-name') 513 expected = {'snapshot': {'name': 'new-name'}} 514 self.assert_called('PUT', '/snapshots/1234', body=expected) 515 # change description only 516 self.run_command('snapshot-rename 1234 ' 517 '--description=new-description') 518 expected = {'snapshot': {'description': 'new-description'}} 519 self.assert_called('PUT', '/snapshots/1234', body=expected) 520 # snapshot-rename and change description 521 self.run_command('snapshot-rename 1234 new-name ' 522 '--description=new-description') 523 expected = {'snapshot': { 524 'name': 'new-name', 525 'description': 'new-description', 526 }} 527 self.assert_called('PUT', '/snapshots/1234', body=expected) 528 529 # Call snapshot-rename with no arguments 530 self.assertRaises(SystemExit, self.run_command, 'snapshot-rename') 531 532 def test_rename_snapshot_invalid_args(self): 533 self.assertRaises(exceptions.ClientException, 534 self.run_command, 535 'snapshot-rename snapshot-1234') 536 537 def test_set_metadata_set(self): 538 self.run_command('metadata 1234 set key1=val1 key2=val2') 539 self.assert_called('POST', '/volumes/1234/metadata', 540 {'metadata': {'key1': 'val1', 'key2': 'val2'}}) 541 542 def test_set_metadata_delete_dict(self): 543 self.run_command('metadata 1234 unset key1=val1 key2=val2') 544 self.assert_called('DELETE', '/volumes/1234/metadata/key1') 545 self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2) 546 547 def test_set_metadata_delete_keys(self): 548 self.run_command('metadata 1234 unset key1 key2') 549 self.assert_called('DELETE', '/volumes/1234/metadata/key1') 550 self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2) 551 552 def test_reset_state(self): 553 self.run_command('reset-state 1234') 554 expected = {'os-reset_status': {'status': 'available'}} 555 self.assert_called('POST', '/volumes/1234/action', body=expected) 556 557 def test_reset_state_attach(self): 558 self.run_command('reset-state --state in-use 1234') 559 expected = {'os-reset_status': {'status': 'in-use'}} 560 self.assert_called('POST', '/volumes/1234/action', body=expected) 561 562 def test_reset_state_with_flag(self): 563 self.run_command('reset-state --state error 1234') 564 expected = {'os-reset_status': {'status': 'error'}} 565 self.assert_called('POST', '/volumes/1234/action', body=expected) 566 567 def test_reset_state_with_attach_status(self): 568 self.run_command('reset-state --attach-status detached 1234') 569 expected = {'os-reset_status': {'attach_status': 'detached'}} 570 self.assert_called('POST', '/volumes/1234/action', body=expected) 571 572 def test_reset_state_with_attach_status_with_flag(self): 573 self.run_command('reset-state --state in-use ' 574 '--attach-status attached 1234') 575 expected = {'os-reset_status': {'status': 'in-use', 576 'attach_status': 'attached'}} 577 self.assert_called('POST', '/volumes/1234/action', body=expected) 578 579 def test_reset_state_with_reset_migration_status(self): 580 self.run_command('reset-state --reset-migration-status 1234') 581 expected = {'os-reset_status': {'migration_status': 'none'}} 582 self.assert_called('POST', '/volumes/1234/action', body=expected) 583 584 def test_reset_state_multiple(self): 585 self.run_command('reset-state 1234 5678 --state error') 586 expected = {'os-reset_status': {'status': 'error'}} 587 self.assert_called_anytime('POST', '/volumes/1234/action', 588 body=expected) 589 self.assert_called_anytime('POST', '/volumes/5678/action', 590 body=expected) 591 592 def test_reset_state_two_with_one_nonexistent(self): 593 cmd = 'reset-state 1234 123456789' 594 self.assertRaises(exceptions.CommandError, self.run_command, cmd) 595 expected = {'os-reset_status': {'status': 'available'}} 596 self.assert_called_anytime('POST', '/volumes/1234/action', 597 body=expected) 598 599 def test_reset_state_one_with_one_nonexistent(self): 600 cmd = 'reset-state 123456789' 601 self.assertRaises(exceptions.CommandError, self.run_command, cmd) 602 603 def test_snapshot_reset_state(self): 604 self.run_command('snapshot-reset-state 1234') 605 expected = {'os-reset_status': {'status': 'available'}} 606 self.assert_called('POST', '/snapshots/1234/action', body=expected) 607 608 def test_snapshot_reset_state_with_flag(self): 609 self.run_command('snapshot-reset-state --state error 1234') 610 expected = {'os-reset_status': {'status': 'error'}} 611 self.assert_called('POST', '/snapshots/1234/action', body=expected) 612 613 def test_snapshot_reset_state_multiple(self): 614 self.run_command('snapshot-reset-state 1234 5678') 615 expected = {'os-reset_status': {'status': 'available'}} 616 self.assert_called_anytime('POST', '/snapshots/1234/action', 617 body=expected) 618 self.assert_called_anytime('POST', '/snapshots/5678/action', 619 body=expected) 620 621 def test_backup_reset_state(self): 622 self.run_command('backup-reset-state 1234') 623 expected = {'os-reset_status': {'status': 'available'}} 624 self.assert_called('POST', '/backups/1234/action', body=expected) 625 626 def test_backup_reset_state_with_flag(self): 627 self.run_command('backup-reset-state --state error 1234') 628 expected = {'os-reset_status': {'status': 'error'}} 629 self.assert_called('POST', '/backups/1234/action', body=expected) 630 631 def test_backup_reset_state_multiple(self): 632 self.run_command('backup-reset-state 1234 5678') 633 expected = {'os-reset_status': {'status': 'available'}} 634 self.assert_called_anytime('POST', '/backups/1234/action', 635 body=expected) 636 self.assert_called_anytime('POST', '/backups/5678/action', 637 body=expected) 638 639 def test_type_list(self): 640 self.run_command('type-list') 641 self.assert_called_anytime('GET', '/types?is_public=None') 642 643 def test_type_show(self): 644 self.run_command('type-show 1') 645 self.assert_called('GET', '/types/1') 646 647 def test_type_create(self): 648 self.run_command('type-create test-type-1') 649 self.assert_called('POST', '/types') 650 651 def test_type_create_public(self): 652 expected = {'volume_type': {'name': 'test-type-1', 653 'description': 'test_type-1-desc', 654 'os-volume-type-access:is_public': True}} 655 self.run_command('type-create test-type-1 ' 656 '--description=test_type-1-desc ' 657 '--is-public=True') 658 self.assert_called('POST', '/types', body=expected) 659 660 def test_type_create_private(self): 661 expected = {'volume_type': {'name': 'test-type-3', 662 'description': 'test_type-3-desc', 663 'os-volume-type-access:is_public': False}} 664 self.run_command('type-create test-type-3 ' 665 '--description=test_type-3-desc ' 666 '--is-public=False') 667 self.assert_called('POST', '/types', body=expected) 668 669 def test_type_create_with_invalid_bool(self): 670 self.assertRaises(ValueError, 671 self.run_command, 672 ('type-create test-type-3 ' 673 '--description=test_type-3-desc ' 674 '--is-public=invalid_bool')) 675 676 def test_type_update(self): 677 expected = {'volume_type': {'name': 'test-type-1', 678 'description': 'test_type-1-desc', 679 'is_public': False}} 680 self.run_command('type-update --name test-type-1 ' 681 '--description=test_type-1-desc ' 682 '--is-public=False 1') 683 self.assert_called('PUT', '/types/1', body=expected) 684 685 def test_type_update_with_invalid_bool(self): 686 self.assertRaises(ValueError, 687 self.run_command, 688 'type-update --name test-type-1 ' 689 '--description=test_type-1-desc ' 690 '--is-public=invalid_bool 1') 691 692 def test_type_update_without_args(self): 693 self.assertRaises(exceptions.CommandError, self.run_command, 694 'type-update 1') 695 696 def test_type_access_list(self): 697 self.run_command('type-access-list --volume-type 3') 698 self.assert_called('GET', '/types/3/os-volume-type-access') 699 700 def test_type_access_add_project(self): 701 expected = {'addProjectAccess': {'project': '101'}} 702 self.run_command('type-access-add --volume-type 3 --project-id 101') 703 self.assert_called_anytime('GET', '/types/3') 704 self.assert_called('POST', '/types/3/action', 705 body=expected) 706 707 def test_type_access_add_project_by_name(self): 708 expected = {'addProjectAccess': {'project': '101'}} 709 with mock.patch('cinderclient.utils.find_resource') as mock_find: 710 mock_find.return_value = '3' 711 self.run_command('type-access-add --volume-type type_name \ 712 --project-id 101') 713 mock_find.assert_called_once_with(mock.ANY, 'type_name') 714 self.assert_called('POST', '/types/3/action', 715 body=expected) 716 717 def test_type_access_remove_project(self): 718 expected = {'removeProjectAccess': {'project': '101'}} 719 self.run_command('type-access-remove ' 720 '--volume-type 3 --project-id 101') 721 self.assert_called_anytime('GET', '/types/3') 722 self.assert_called('POST', '/types/3/action', 723 body=expected) 724 725 def test_type_delete(self): 726 self.run_command('type-delete 1') 727 self.assert_called('DELETE', '/types/1') 728 729 def test_type_delete_multiple(self): 730 self.run_command('type-delete 1 3') 731 self.assert_called_anytime('DELETE', '/types/1') 732 self.assert_called('DELETE', '/types/3') 733 734 def test_type_delete_by_name(self): 735 self.run_command('type-delete test-type-1') 736 self.assert_called_anytime('GET', '/types?is_public=None') 737 self.assert_called('DELETE', '/types/1') 738 739 def test_encryption_type_list(self): 740 """ 741 Test encryption-type-list shell command. 742 743 Verify a series of GET requests are made: 744 - one to get the volume type list information 745 - one per volume type to retrieve the encryption type information 746 """ 747 self.run_command('encryption-type-list') 748 self.assert_called_anytime('GET', '/types?is_public=None') 749 self.assert_called_anytime('GET', '/types/1/encryption') 750 self.assert_called_anytime('GET', '/types/2/encryption') 751 752 def test_encryption_type_show(self): 753 """ 754 Test encryption-type-show shell command. 755 756 Verify two GET requests are made per command invocation: 757 - one to get the volume type information 758 - one to get the encryption type information 759 """ 760 self.run_command('encryption-type-show 1') 761 self.assert_called('GET', '/types/1/encryption') 762 self.assert_called_anytime('GET', '/types/1') 763 764 def test_encryption_type_create(self): 765 """ 766 Test encryption-type-create shell command. 767 768 Verify GET and POST requests are made per command invocation: 769 - one GET request to retrieve the relevant volume type information 770 - one POST request to create the new encryption type 771 """ 772 773 expected = {'encryption': {'cipher': None, 'key_size': None, 774 'provider': 'TestProvider', 775 'control_location': 'front-end'}} 776 self.run_command('encryption-type-create 2 TestProvider') 777 self.assert_called('POST', '/types/2/encryption', body=expected) 778 self.assert_called_anytime('GET', '/types/2') 779 780 @ddt.data('--key-size 512 --control-location front-end', 781 '--key_size 512 --control_location front-end') # old style 782 def test_encryption_type_create_with_args(self, arg): 783 expected = {'encryption': {'cipher': None, 784 'key_size': 512, 785 'provider': 'TestProvider', 786 'control_location': 'front-end'}} 787 self.run_command('encryption-type-create 2 TestProvider ' + arg) 788 self.assert_called('POST', '/types/2/encryption', body=expected) 789 self.assert_called_anytime('GET', '/types/2') 790 791 def test_encryption_type_update(self): 792 """ 793 Test encryption-type-update shell command. 794 795 Verify two GETs/one PUT requests are made per command invocation: 796 - one GET request to retrieve the relevant volume type information 797 - one GET request to retrieve the relevant encryption type information 798 - one PUT request to update the encryption type information 799 Verify that the PUT request correctly parses encryption-type-update 800 parameters from sys.argv 801 """ 802 parameters = {'--provider': 'EncryptionProvider', '--cipher': 'des', 803 '--key-size': 1024, '--control-location': 'back-end'} 804 805 # Construct the argument string for the update call and the 806 # expected encryption-type body that should be produced by it 807 args = ' '.join(['%s %s' % (k, v) for k, v in parameters.items()]) 808 expected = {'encryption': {'provider': 'EncryptionProvider', 809 'cipher': 'des', 810 'key_size': 1024, 811 'control_location': 'back-end'}} 812 813 self.run_command('encryption-type-update 1 %s' % args) 814 self.assert_called('GET', '/types/1/encryption') 815 self.assert_called_anytime('GET', '/types/1') 816 self.assert_called_anytime('PUT', '/types/1/encryption/provider', 817 body=expected) 818 819 def test_encryption_type_update_no_attributes(self): 820 """ 821 Test encryption-type-update shell command. 822 823 Verify two GETs/one PUT requests are made per command invocation: 824 - one GET request to retrieve the relevant volume type information 825 - one GET request to retrieve the relevant encryption type information 826 - one PUT request to update the encryption type information 827 """ 828 expected = {'encryption': {}} 829 self.run_command('encryption-type-update 1') 830 self.assert_called('GET', '/types/1/encryption') 831 self.assert_called_anytime('GET', '/types/1') 832 self.assert_called_anytime('PUT', '/types/1/encryption/provider', 833 body=expected) 834 835 def test_encryption_type_update_default_attributes(self): 836 """ 837 Test encryption-type-update shell command. 838 839 Verify two GETs/one PUT requests are made per command invocation: 840 - one GET request to retrieve the relevant volume type information 841 - one GET request to retrieve the relevant encryption type information 842 - one PUT request to update the encryption type information 843 Verify that the encryption-type body produced contains default None 844 values for all specified parameters. 845 """ 846 parameters = ['--cipher', '--key-size'] 847 848 # Construct the argument string for the update call and the 849 # expected encryption-type body that should be produced by it 850 args = ' '.join(['%s' % (p) for p in parameters]) 851 expected_pairs = [(k.strip('-').replace('-', '_'), None) for k in 852 parameters] 853 expected = {'encryption': dict(expected_pairs)} 854 855 self.run_command('encryption-type-update 1 %s' % args) 856 self.assert_called('GET', '/types/1/encryption') 857 self.assert_called_anytime('GET', '/types/1') 858 self.assert_called_anytime('PUT', '/types/1/encryption/provider', 859 body=expected) 860 861 def test_encryption_type_delete(self): 862 """ 863 Test encryption-type-delete shell command. 864 865 Verify one GET/one DELETE requests are made per command invocation: 866 - one GET request to retrieve the relevant volume type information 867 - one DELETE request to delete the encryption type information 868 """ 869 self.run_command('encryption-type-delete 1') 870 self.assert_called('DELETE', '/types/1/encryption/provider') 871 self.assert_called_anytime('GET', '/types/1') 872 873 def test_migrate_volume(self): 874 self.run_command('migrate 1234 fakehost --force-host-copy=True ' 875 '--lock-volume=True') 876 expected = {'os-migrate_volume': {'force_host_copy': 'True', 877 'lock_volume': 'True', 878 'host': 'fakehost'}} 879 self.assert_called('POST', '/volumes/1234/action', body=expected) 880 881 def test_migrate_volume_bool_force(self): 882 self.run_command('migrate 1234 fakehost --force-host-copy ' 883 '--lock-volume') 884 expected = {'os-migrate_volume': {'force_host_copy': True, 885 'lock_volume': True, 886 'host': 'fakehost'}} 887 self.assert_called('POST', '/volumes/1234/action', body=expected) 888 889 def test_migrate_volume_bool_force_false(self): 890 # Set both --force-host-copy and --lock-volume to False. 891 self.run_command('migrate 1234 fakehost --force-host-copy=False ' 892 '--lock-volume=False') 893 expected = {'os-migrate_volume': {'force_host_copy': 'False', 894 'lock_volume': 'False', 895 'host': 'fakehost'}} 896 self.assert_called('POST', '/volumes/1234/action', body=expected) 897 898 # Do not set the values to --force-host-copy and --lock-volume. 899 self.run_command('migrate 1234 fakehost') 900 expected = {'os-migrate_volume': {'force_host_copy': False, 901 'lock_volume': False, 902 'host': 'fakehost'}} 903 self.assert_called('POST', '/volumes/1234/action', 904 body=expected) 905 906 def test_snapshot_metadata_set(self): 907 self.run_command('snapshot-metadata 1234 set key1=val1 key2=val2') 908 self.assert_called('POST', '/snapshots/1234/metadata', 909 {'metadata': {'key1': 'val1', 'key2': 'val2'}}) 910 911 def test_snapshot_metadata_unset_dict(self): 912 self.run_command('snapshot-metadata 1234 unset key1=val1 key2=val2') 913 self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key1') 914 self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key2') 915 916 def test_snapshot_metadata_unset_keys(self): 917 self.run_command('snapshot-metadata 1234 unset key1 key2') 918 self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key1') 919 self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key2') 920 921 def test_volume_metadata_update_all(self): 922 self.run_command('metadata-update-all 1234 key1=val1 key2=val2') 923 self.assert_called('PUT', '/volumes/1234/metadata', 924 {'metadata': {'key1': 'val1', 'key2': 'val2'}}) 925 926 def test_snapshot_metadata_update_all(self): 927 self.run_command('snapshot-metadata-update-all\ 928 1234 key1=val1 key2=val2') 929 self.assert_called('PUT', '/snapshots/1234/metadata', 930 {'metadata': {'key1': 'val1', 'key2': 'val2'}}) 931 932 def test_readonly_mode_update(self): 933 self.run_command('readonly-mode-update 1234 True') 934 expected = {'os-update_readonly_flag': {'readonly': True}} 935 self.assert_called('POST', '/volumes/1234/action', body=expected) 936 937 self.run_command('readonly-mode-update 1234 False') 938 expected = {'os-update_readonly_flag': {'readonly': False}} 939 self.assert_called('POST', '/volumes/1234/action', body=expected) 940 941 def test_service_disable(self): 942 self.run_command('service-disable host cinder-volume') 943 self.assert_called('PUT', '/os-services/disable', 944 {"binary": "cinder-volume", "host": "host"}) 945 946 def test_services_disable_with_reason(self): 947 cmd = 'service-disable host cinder-volume --reason no_reason' 948 self.run_command(cmd) 949 body = {'host': 'host', 'binary': 'cinder-volume', 950 'disabled_reason': 'no_reason'} 951 self.assert_called('PUT', '/os-services/disable-log-reason', body) 952 953 def test_service_enable(self): 954 self.run_command('service-enable host cinder-volume') 955 self.assert_called('PUT', '/os-services/enable', 956 {"binary": "cinder-volume", "host": "host"}) 957 958 def test_retype_with_policy(self): 959 self.run_command('retype 1234 foo --migration-policy=on-demand') 960 expected = {'os-retype': {'new_type': 'foo', 961 'migration_policy': 'on-demand'}} 962 self.assert_called('POST', '/volumes/1234/action', body=expected) 963 964 def test_retype_default_policy(self): 965 self.run_command('retype 1234 foo') 966 expected = {'os-retype': {'new_type': 'foo', 967 'migration_policy': 'never'}} 968 self.assert_called('POST', '/volumes/1234/action', body=expected) 969 970 def test_snapshot_delete(self): 971 """Tests delete snapshot without force parameter""" 972 self.run_command('snapshot-delete 1234') 973 self.assert_called('DELETE', '/snapshots/1234') 974 975 def test_snapshot_delete_multiple(self): 976 """Tests delete multiple snapshots without force parameter""" 977 self.run_command('snapshot-delete 5678 1234') 978 self.assert_called_anytime('DELETE', '/snapshots/5678') 979 self.assert_called('DELETE', '/snapshots/1234') 980 981 def test_force_snapshot_delete(self): 982 """Tests delete snapshot with default force parameter value(True)""" 983 self.run_command('snapshot-delete 1234 --force') 984 expected_body = {'os-force_delete': None} 985 self.assert_called('POST', 986 '/snapshots/1234/action', 987 expected_body) 988 989 def test_force_snapshot_delete_multiple(self): 990 """ 991 Tests delete multiple snapshots with force parameter 992 993 Snapshot delete with force parameter allows deleting snapshot of a 994 volume when its status is other than "available" or "error". 995 """ 996 self.run_command('snapshot-delete 5678 1234 --force') 997 expected_body = {'os-force_delete': None} 998 self.assert_called_anytime('POST', 999 '/snapshots/5678/action', 1000 expected_body) 1001 self.assert_called_anytime('POST', 1002 '/snapshots/1234/action', 1003 expected_body) 1004 1005 def test_quota_delete(self): 1006 self.run_command('quota-delete 1234') 1007 self.assert_called('DELETE', '/os-quota-sets/1234') 1008 1009 def test_volume_manage(self): 1010 self.run_command('manage host1 some_fake_name ' 1011 '--name foo --description bar ' 1012 '--volume-type baz --availability-zone az ' 1013 '--metadata k1=v1 k2=v2') 1014 expected = {'volume': {'host': 'host1', 1015 'ref': {'source-name': 'some_fake_name'}, 1016 'name': 'foo', 1017 'description': 'bar', 1018 'volume_type': 'baz', 1019 'availability_zone': 'az', 1020 'metadata': {'k1': 'v1', 'k2': 'v2'}, 1021 'bootable': False}} 1022 self.assert_called_anytime('POST', '/os-volume-manage', body=expected) 1023 1024 def test_volume_manage_bootable(self): 1025 """ 1026 Tests the --bootable option 1027 1028 If this flag is specified, then the resulting POST should contain 1029 bootable: True. 1030 """ 1031 self.run_command('manage host1 some_fake_name ' 1032 '--name foo --description bar --bootable ' 1033 '--volume-type baz --availability-zone az ' 1034 '--metadata k1=v1 k2=v2') 1035 expected = {'volume': {'host': 'host1', 1036 'ref': {'source-name': 'some_fake_name'}, 1037 'name': 'foo', 1038 'description': 'bar', 1039 'volume_type': 'baz', 1040 'availability_zone': 'az', 1041 'metadata': {'k1': 'v1', 'k2': 'v2'}, 1042 'bootable': True}} 1043 self.assert_called_anytime('POST', '/os-volume-manage', body=expected) 1044 1045 def test_volume_manage_source_name(self): 1046 """ 1047 Tests the --source-name option. 1048 1049 Checks that the --source-name option correctly updates the 1050 ref structure that is passed in the HTTP POST 1051 """ 1052 self.run_command('manage host1 VolName ' 1053 '--name foo --description bar ' 1054 '--volume-type baz --availability-zone az ' 1055 '--metadata k1=v1 k2=v2') 1056 expected = {'volume': {'host': 'host1', 1057 'ref': {'source-name': 'VolName'}, 1058 'name': 'foo', 1059 'description': 'bar', 1060 'volume_type': 'baz', 1061 'availability_zone': 'az', 1062 'metadata': {'k1': 'v1', 'k2': 'v2'}, 1063 'bootable': False}} 1064 self.assert_called_anytime('POST', '/os-volume-manage', body=expected) 1065 1066 def test_volume_manage_source_id(self): 1067 """ 1068 Tests the --source-id option. 1069 1070 Checks that the --source-id option correctly updates the 1071 ref structure that is passed in the HTTP POST 1072 """ 1073 self.run_command('manage host1 1234 ' 1074 '--id-type source-id ' 1075 '--name foo --description bar ' 1076 '--volume-type baz --availability-zone az ' 1077 '--metadata k1=v1 k2=v2') 1078 expected = {'volume': {'host': 'host1', 1079 'ref': {'source-id': '1234'}, 1080 'name': 'foo', 1081 'description': 'bar', 1082 'volume_type': 'baz', 1083 'availability_zone': 'az', 1084 'metadata': {'k1': 'v1', 'k2': 'v2'}, 1085 'bootable': False}} 1086 self.assert_called_anytime('POST', '/os-volume-manage', body=expected) 1087 1088 def test_volume_manageable_list(self): 1089 self.run_command('manageable-list fakehost') 1090 self.assert_called('GET', '/os-volume-manage/detail?host=fakehost') 1091 1092 def test_volume_manageable_list_details(self): 1093 self.run_command('manageable-list fakehost --detailed True') 1094 self.assert_called('GET', '/os-volume-manage/detail?host=fakehost') 1095 1096 def test_volume_manageable_list_no_details(self): 1097 self.run_command('manageable-list fakehost --detailed False') 1098 self.assert_called('GET', '/os-volume-manage?host=fakehost') 1099 1100 def test_volume_unmanage(self): 1101 self.run_command('unmanage 1234') 1102 self.assert_called('POST', '/volumes/1234/action', 1103 body={'os-unmanage': None}) 1104 1105 def test_create_snapshot_from_volume_with_metadata(self): 1106 """ 1107 Tests create snapshot with --metadata parameter. 1108 1109 Checks metadata params are set during create snapshot 1110 when metadata is passed 1111 """ 1112 expected = {'snapshot': {'volume_id': 1234, 1113 'metadata': {'k1': 'v1', 1114 'k2': 'v2'}}} 1115 self.run_command('snapshot-create 1234 --metadata k1=v1 k2=v2 ' 1116 '--force=True') 1117 self.assert_called_anytime('POST', '/snapshots', partial_body=expected) 1118 1119 def test_create_snapshot_from_volume_with_metadata_bool_force(self): 1120 """ 1121 Tests create snapshot with --metadata parameter. 1122 1123 Checks metadata params are set during create snapshot 1124 when metadata is passed 1125 """ 1126 expected = {'snapshot': {'volume_id': 1234, 1127 'metadata': {'k1': 'v1', 1128 'k2': 'v2'}}} 1129 self.run_command('snapshot-create 1234 --metadata k1=v1 k2=v2 --force') 1130 self.assert_called_anytime('POST', '/snapshots', partial_body=expected) 1131 1132 def test_get_pools(self): 1133 self.run_command('get-pools') 1134 self.assert_called('GET', '/scheduler-stats/get_pools') 1135 1136 def test_get_pools_detail(self): 1137 self.run_command('get-pools --detail') 1138 self.assert_called('GET', '/scheduler-stats/get_pools?detail=True') 1139 1140 def test_list_transfer(self): 1141 self.run_command('transfer-list') 1142 self.assert_called('GET', '/os-volume-transfer/detail?all_tenants=0') 1143 1144 def test_list_transfer_all_tenants(self): 1145 self.run_command('transfer-list --all-tenants=1') 1146 self.assert_called('GET', '/os-volume-transfer/detail?all_tenants=1') 1147 1148 def test_consistencygroup_update(self): 1149 self.run_command('consisgroup-update ' 1150 '--name cg2 --description desc2 ' 1151 '--add-volumes uuid1,uuid2 ' 1152 '--remove-volumes uuid3,uuid4 ' 1153 '1234') 1154 expected = {'consistencygroup': {'name': 'cg2', 1155 'description': 'desc2', 1156 'add_volumes': 'uuid1,uuid2', 1157 'remove_volumes': 'uuid3,uuid4'}} 1158 self.assert_called('PUT', '/consistencygroups/1234', 1159 body=expected) 1160 1161 def test_consistencygroup_update_invalid_args(self): 1162 self.assertRaises(exceptions.ClientException, 1163 self.run_command, 1164 'consisgroup-update 1234') 1165 1166 def test_consistencygroup_create_from_src_snap(self): 1167 self.run_command('consisgroup-create-from-src ' 1168 '--name cg ' 1169 '--cgsnapshot 1234') 1170 expected = { 1171 'consistencygroup-from-src': { 1172 'name': 'cg', 1173 'cgsnapshot_id': '1234', 1174 'description': None, 1175 'user_id': None, 1176 'project_id': None, 1177 'status': 'creating', 1178 'source_cgid': None 1179 } 1180 } 1181 self.assert_called('POST', '/consistencygroups/create_from_src', 1182 expected) 1183 1184 def test_consistencygroup_create_from_src_cg(self): 1185 self.run_command('consisgroup-create-from-src ' 1186 '--name cg ' 1187 '--source-cg 1234') 1188 expected = { 1189 'consistencygroup-from-src': { 1190 'name': 'cg', 1191 'cgsnapshot_id': None, 1192 'description': None, 1193 'user_id': None, 1194 'project_id': None, 1195 'status': 'creating', 1196 'source_cgid': '1234' 1197 } 1198 } 1199 self.assert_called('POST', '/consistencygroups/create_from_src', 1200 expected) 1201 1202 def test_consistencygroup_create_from_src_fail_no_snap_cg(self): 1203 self.assertRaises(exceptions.ClientException, 1204 self.run_command, 1205 'consisgroup-create-from-src ' 1206 '--name cg') 1207 1208 def test_consistencygroup_create_from_src_fail_both_snap_cg(self): 1209 self.assertRaises(exceptions.ClientException, 1210 self.run_command, 1211 'consisgroup-create-from-src ' 1212 '--name cg ' 1213 '--cgsnapshot 1234 ' 1214 '--source-cg 5678') 1215 1216 def test_set_image_metadata(self): 1217 self.run_command('image-metadata 1234 set key1=val1') 1218 expected = {"os-set_image_metadata": {"metadata": {"key1": "val1"}}} 1219 self.assert_called('POST', '/volumes/1234/action', 1220 body=expected) 1221 1222 def test_unset_image_metadata(self): 1223 self.run_command('image-metadata 1234 unset key1') 1224 expected = {"os-unset_image_metadata": {"key": "key1"}} 1225 self.assert_called('POST', '/volumes/1234/action', 1226 body=expected) 1227 1228 def _get_params_from_stack(self, pos=-1): 1229 method, url = self.shell.cs.client.callstack[pos][0:2] 1230 path, query = parse.splitquery(url) 1231 params = parse.parse_qs(query) 1232 return path, params 1233 1234 def test_backup_list_all_tenants(self): 1235 self.run_command('backup-list --all-tenants=1 --name=bc ' 1236 '--status=available --volume-id=1234') 1237 expected = { 1238 'all_tenants': ['1'], 1239 'name': ['bc'], 1240 'status': ['available'], 1241 'volume_id': ['1234'], 1242 } 1243 1244 path, params = self._get_params_from_stack() 1245 1246 self.assertEqual('/backups/detail', path) 1247 self.assertEqual(4, len(params)) 1248 1249 for k in params.keys(): 1250 self.assertEqual(expected[k], params[k]) 1251 1252 def test_backup_list_volume_id(self): 1253 self.run_command('backup-list --volume-id=1234') 1254 self.assert_called('GET', '/backups/detail?volume_id=1234') 1255 1256 def test_backup_list(self): 1257 self.run_command('backup-list') 1258 self.assert_called('GET', '/backups/detail') 1259 1260 @mock.patch("cinderclient.utils.print_list") 1261 def test_backup_list_sort(self, mock_print_list): 1262 self.run_command('backup-list --sort id') 1263 self.assert_called('GET', '/backups/detail?sort=id') 1264 columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', 1265 'Container'] 1266 mock_print_list.assert_called_once_with(mock.ANY, columns, 1267 sortby_index=None) 1268 1269 def test_backup_list_data_timestamp(self): 1270 self.run_command('backup-list --sort data_timestamp') 1271 self.assert_called('GET', '/backups/detail?sort=data_timestamp') 1272 1273 def test_get_capabilities(self): 1274 self.run_command('get-capabilities host') 1275 self.assert_called('GET', '/capabilities/host') 1276 1277 def test_image_metadata_show(self): 1278 # since the request is not actually sent to cinder API but is 1279 # calling the method in :class:`v2.fakes.FakeHTTPClient` instead. 1280 # Thus, ignore any exception which is false negative compare 1281 # with real API call. 1282 try: 1283 self.run_command('image-metadata-show 1234') 1284 except Exception: 1285 pass 1286 expected = {"os-show_image_metadata": None} 1287 self.assert_called('POST', '/volumes/1234/action', body=expected) 1288 1289 def test_snapshot_manage(self): 1290 self.run_command('snapshot-manage 1234 some_fake_name ' 1291 '--name foo --description bar ' 1292 '--metadata k1=v1 k2=v2') 1293 expected = {'snapshot': {'volume_id': 1234, 1294 'ref': {'source-name': 'some_fake_name'}, 1295 'name': 'foo', 1296 'description': 'bar', 1297 'metadata': {'k1': 'v1', 'k2': 'v2'} 1298 }} 1299 self.assert_called_anytime('POST', '/os-snapshot-manage', 1300 body=expected) 1301 1302 def test_snapshot_manageable_list(self): 1303 self.run_command('snapshot-manageable-list fakehost') 1304 self.assert_called('GET', '/os-snapshot-manage/detail?host=fakehost') 1305 1306 def test_snapshot_manageable_list_details(self): 1307 self.run_command('snapshot-manageable-list fakehost --detailed True') 1308 self.assert_called('GET', '/os-snapshot-manage/detail?host=fakehost') 1309 1310 def test_snapshot_manageable_list_no_details(self): 1311 self.run_command('snapshot-manageable-list fakehost --detailed False') 1312 self.assert_called('GET', '/os-snapshot-manage?host=fakehost') 1313 1314 def test_snapshot_unmanage(self): 1315 self.run_command('snapshot-unmanage 1234') 1316 self.assert_called('POST', '/snapshots/1234/action', 1317 body={'os-unmanage': None}) 1318 1319 def test_extra_specs_list(self): 1320 self.run_command('extra-specs-list') 1321 self.assert_called('GET', '/types?is_public=None') 1322 1323 def test_quota_class_show(self): 1324 self.run_command('quota-class-show test') 1325 self.assert_called('GET', '/os-quota-class-sets/test') 1326 1327 def test_quota_class_update(self): 1328 expected = {'quota_class_set': {'volumes': 2, 1329 'snapshots': 2, 1330 'gigabytes': 1, 1331 'backups': 1, 1332 'backup_gigabytes': 1, 1333 'per_volume_gigabytes': 1}} 1334 self.run_command('quota-class-update test ' 1335 '--volumes 2 ' 1336 '--snapshots 2 ' 1337 '--gigabytes 1 ' 1338 '--backups 1 ' 1339 '--backup-gigabytes 1 ' 1340 '--per-volume-gigabytes 1') 1341 self.assert_called('PUT', '/os-quota-class-sets/test', body=expected) 1342 1343 def test_translate_attachments(self): 1344 attachment_id = 'aaaa' 1345 server_id = 'bbbb' 1346 obj_id = 'cccc' 1347 info = { 1348 'attachments': [{ 1349 'attachment_id': attachment_id, 1350 'id': obj_id, 1351 'server_id': server_id}] 1352 } 1353 1354 new_info = test_shell._translate_attachments(info) 1355 1356 self.assertEqual(attachment_id, new_info['attachment_ids'][0]) 1357 self.assertEqual(server_id, new_info['attached_servers'][0]) 1358 self.assertNotIn('id', new_info) 1359