1# Copyright (c) 2013-2014 OpenStack Foundation 2# All Rights Reserved. 3# 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 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, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13# implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import collections 19import copy 20import os 21 22from oslo_utils import strutils 23import six 24 25from cinderclient import base 26from cinderclient import exceptions 27from cinderclient import shell_utils 28from cinderclient import utils 29from cinderclient.v2 import availability_zones 30 31 32def _translate_attachments(info): 33 attachments = [] 34 attached_servers = [] 35 for attachment in info['attachments']: 36 attachments.append(attachment['attachment_id']) 37 attached_servers.append(attachment['server_id']) 38 info.pop('attachments', None) 39 info['attachment_ids'] = attachments 40 info['attached_servers'] = attached_servers 41 return info 42 43 44@utils.arg('--all-tenants', 45 dest='all_tenants', 46 metavar='<0|1>', 47 nargs='?', 48 type=int, 49 const=1, 50 default=0, 51 help='Shows details for all tenants. Admin only.') 52@utils.arg('--all_tenants', 53 nargs='?', 54 type=int, 55 const=1, 56 help=argparse.SUPPRESS) 57@utils.arg('--name', 58 metavar='<name>', 59 default=None, 60 help='Filters results by a name. Default=None.') 61@utils.arg('--display-name', 62 help=argparse.SUPPRESS) 63@utils.arg('--status', 64 metavar='<status>', 65 default=None, 66 help='Filters results by a status. Default=None.') 67@utils.arg('--bootable', 68 metavar='<True|true|False|false>', 69 const=True, 70 nargs='?', 71 choices=['True', 'true', 'False', 'false'], 72 help='Filters results by bootable status. Default=None.') 73@utils.arg('--migration_status', 74 metavar='<migration_status>', 75 default=None, 76 help='Filters results by a migration status. Default=None. ' 77 'Admin only.') 78@utils.arg('--metadata', 79 nargs='*', 80 metavar='<key=value>', 81 default=None, 82 help='Filters results by a image metadata key and value pair. ' 83 'Default=None.') 84@utils.arg('--marker', 85 metavar='<marker>', 86 default=None, 87 help='Begin returning volumes that appear later in the volume ' 88 'list than that represented by this volume id. ' 89 'Default=None.') 90@utils.arg('--limit', 91 metavar='<limit>', 92 default=None, 93 help='Maximum number of volumes to return. Default=None.') 94@utils.arg('--fields', 95 default=None, 96 metavar='<fields>', 97 help='Comma-separated list of fields to display. ' 98 'Use the show command to see which fields are available. ' 99 'Unavailable/non-existent fields will be ignored. ' 100 'Default=None.') 101@utils.arg('--sort', 102 metavar='<key>[:<direction>]', 103 default=None, 104 help=(('Comma-separated list of sort keys and directions in the ' 105 'form of <key>[:<asc|desc>]. ' 106 'Valid keys: %s. ' 107 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) 108@utils.arg('--tenant', 109 type=str, 110 dest='tenant', 111 nargs='?', 112 metavar='<tenant>', 113 help='Display information from single tenant (Admin only).') 114def do_list(cs, args): 115 """Lists all volumes.""" 116 # NOTE(thingee): Backwards-compatibility with v1 args 117 if args.display_name is not None: 118 args.name = args.display_name 119 120 all_tenants = 1 if args.tenant else \ 121 int(os.environ.get("ALL_TENANTS", args.all_tenants)) 122 search_opts = { 123 'all_tenants': all_tenants, 124 'project_id': args.tenant, 125 'name': args.name, 126 'status': args.status, 127 'bootable': args.bootable, 128 'migration_status': args.migration_status, 129 'metadata': (shell_utils.extract_metadata(args) if args.metadata 130 else None), 131 } 132 133 # If unavailable/non-existent fields are specified, these fields will 134 # be removed from key_list at the print_list() during key validation. 135 field_titles = [] 136 if args.fields: 137 for field_title in args.fields.split(','): 138 field_titles.append(field_title) 139 140 volumes = cs.volumes.list(search_opts=search_opts, marker=args.marker, 141 limit=args.limit, sort=args.sort) 142 shell_utils.translate_volume_keys(volumes) 143 144 # Create a list of servers to which the volume is attached 145 for vol in volumes: 146 servers = [s.get('server_id') for s in vol.attachments] 147 setattr(vol, 'attached_to', ','.join(map(str, servers))) 148 149 if field_titles: 150 # Remove duplicate fields 151 key_list = ['ID'] 152 unique_titles = [k for k in collections.OrderedDict.fromkeys( 153 [x.title().strip() for x in field_titles]) if k != 'Id'] 154 key_list.extend(unique_titles) 155 else: 156 key_list = ['ID', 'Status', 'Name', 'Size', 'Volume Type', 157 'Bootable', 'Attached to'] 158 # If all_tenants is specified, print 159 # Tenant ID as well. 160 if search_opts['all_tenants']: 161 key_list.insert(1, 'Tenant ID') 162 163 if args.sort: 164 sortby_index = None 165 else: 166 sortby_index = 0 167 utils.print_list(volumes, key_list, exclude_unavailable=True, 168 sortby_index=sortby_index) 169 170 171@utils.arg('volume', 172 metavar='<volume>', 173 help='Name or ID of volume.') 174def do_show(cs, args): 175 """Shows volume details.""" 176 info = dict() 177 volume = utils.find_volume(cs, args.volume) 178 info.update(volume._info) 179 180 if 'readonly' in info['metadata']: 181 info['readonly'] = info['metadata']['readonly'] 182 183 info.pop('links', None) 184 info = _translate_attachments(info) 185 utils.print_dict(info, 186 formatters=['metadata', 'volume_image_metadata', 187 'attachment_ids', 'attached_servers']) 188 189 190class CheckSizeArgForCreate(argparse.Action): 191 def __call__(self, parser, args, values, option_string=None): 192 if ((args.snapshot_id or args.source_volid) 193 is None and values is None): 194 if not hasattr(args, 'backup_id') or args.backup_id is None: 195 parser.error('Size is a required parameter if snapshot ' 196 'or source volume or backup is not specified.') 197 setattr(args, self.dest, values) 198 199 200@utils.arg('size', 201 metavar='<size>', 202 nargs='?', 203 type=int, 204 action=CheckSizeArgForCreate, 205 help='Size of volume, in GiBs. (Required unless ' 206 'snapshot-id/source-volid is specified).') 207@utils.arg('--consisgroup-id', 208 metavar='<consistencygroup-id>', 209 default=None, 210 help='ID of a consistency group where the new volume belongs to. ' 211 'Default=None.') 212@utils.arg('--snapshot-id', 213 metavar='<snapshot-id>', 214 default=None, 215 help='Creates volume from snapshot ID. Default=None.') 216@utils.arg('--snapshot_id', 217 help=argparse.SUPPRESS) 218@utils.arg('--source-volid', 219 metavar='<source-volid>', 220 default=None, 221 help='Creates volume from volume ID. Default=None.') 222@utils.arg('--source_volid', 223 help=argparse.SUPPRESS) 224@utils.arg('--image-id', 225 metavar='<image-id>', 226 default=None, 227 help='Creates volume from image ID. Default=None.') 228@utils.arg('--image_id', 229 help=argparse.SUPPRESS) 230@utils.arg('--image', 231 metavar='<image>', 232 default=None, 233 help='Creates a volume from image (ID or name). Default=None.') 234@utils.arg('--image_ref', 235 help=argparse.SUPPRESS) 236@utils.arg('--name', 237 metavar='<name>', 238 default=None, 239 help='Volume name. Default=None.') 240@utils.arg('--display-name', 241 help=argparse.SUPPRESS) 242@utils.arg('--display_name', 243 help=argparse.SUPPRESS) 244@utils.arg('--description', 245 metavar='<description>', 246 default=None, 247 help='Volume description. Default=None.') 248@utils.arg('--display-description', 249 help=argparse.SUPPRESS) 250@utils.arg('--display_description', 251 help=argparse.SUPPRESS) 252@utils.arg('--volume-type', 253 metavar='<volume-type>', 254 default=None, 255 help='Volume type. Default=None.') 256@utils.arg('--volume_type', 257 help=argparse.SUPPRESS) 258@utils.arg('--availability-zone', 259 metavar='<availability-zone>', 260 default=None, 261 help='Availability zone for volume. Default=None.') 262@utils.arg('--availability_zone', 263 help=argparse.SUPPRESS) 264@utils.arg('--metadata', 265 nargs='*', 266 metavar='<key=value>', 267 default=None, 268 help='Metadata key and value pairs. Default=None.') 269@utils.arg('--hint', 270 metavar='<key=value>', 271 dest='scheduler_hints', 272 action='append', 273 default=[], 274 help='Scheduler hint, similar to nova. Repeat option to set ' 275 'multiple hints. Values with the same key will be stored ' 276 'as a list.') 277def do_create(cs, args): 278 """Creates a volume.""" 279 # NOTE(thingee): Backwards-compatibility with v1 args 280 if args.display_name is not None: 281 args.name = args.display_name 282 283 if args.display_description is not None: 284 args.description = args.display_description 285 286 volume_metadata = None 287 if args.metadata is not None: 288 volume_metadata = shell_utils.extract_metadata(args) 289 290 # NOTE(N.S.): take this piece from novaclient 291 hints = {} 292 if args.scheduler_hints: 293 for hint in args.scheduler_hints: 294 key, _sep, value = hint.partition('=') 295 # NOTE(vish): multiple copies of same hint will 296 # result in a list of values 297 if key in hints: 298 if isinstance(hints[key], six.string_types): 299 hints[key] = [hints[key]] 300 hints[key] += [value] 301 else: 302 hints[key] = value 303 # NOTE(N.S.): end of taken piece 304 305 # Keep backward compatibility with image_id, favoring explicit ID 306 image_ref = args.image_id or args.image or args.image_ref 307 308 volume = cs.volumes.create(args.size, 309 args.consisgroup_id, 310 args.snapshot_id, 311 args.source_volid, 312 args.name, 313 args.description, 314 args.volume_type, 315 availability_zone=args.availability_zone, 316 imageRef=image_ref, 317 metadata=volume_metadata, 318 scheduler_hints=hints) 319 320 info = dict() 321 volume = cs.volumes.get(volume.id) 322 info.update(volume._info) 323 324 if 'readonly' in info['metadata']: 325 info['readonly'] = info['metadata']['readonly'] 326 327 info.pop('links', None) 328 info = _translate_attachments(info) 329 utils.print_dict(info) 330 331 332@utils.arg('--cascade', 333 action='store_true', 334 default=False, 335 help='Remove any snapshots along with volume. Default=False.') 336@utils.arg('volume', 337 metavar='<volume>', nargs='+', 338 help='Name or ID of volume or volumes to delete.') 339def do_delete(cs, args): 340 """Removes one or more volumes.""" 341 failure_count = 0 342 for volume in args.volume: 343 try: 344 utils.find_volume(cs, volume).delete(cascade=args.cascade) 345 print("Request to delete volume %s has been accepted." % (volume)) 346 except Exception as e: 347 failure_count += 1 348 print("Delete for volume %s failed: %s" % (volume, e)) 349 if failure_count == len(args.volume): 350 raise exceptions.CommandError("Unable to delete any of the specified " 351 "volumes.") 352 353 354@utils.arg('volume', 355 metavar='<volume>', nargs='+', 356 help='Name or ID of volume or volumes to delete.') 357def do_force_delete(cs, args): 358 """Attempts force-delete of volume, regardless of state.""" 359 failure_count = 0 360 for volume in args.volume: 361 try: 362 utils.find_volume(cs, volume).force_delete() 363 except Exception as e: 364 failure_count += 1 365 print("Delete for volume %s failed: %s" % (volume, e)) 366 if failure_count == len(args.volume): 367 raise exceptions.CommandError("Unable to force delete any of the " 368 "specified volumes.") 369 370 371@utils.arg('volume', metavar='<volume>', nargs='+', 372 help='Name or ID of volume to modify.') 373@utils.arg('--state', metavar='<state>', default=None, 374 help=('The state to assign to the volume. Valid values are ' 375 '"available", "error", "creating", "deleting", "in-use", ' 376 '"attaching", "detaching", "error_deleting" and ' 377 '"maintenance". ' 378 'NOTE: This command simply changes the state of the ' 379 'Volume in the DataBase with no regard to actual status, ' 380 'exercise caution when using. Default=None, that means the ' 381 'state is unchanged.')) 382@utils.arg('--attach-status', metavar='<attach-status>', default=None, 383 help=('The attach status to assign to the volume in the DataBase, ' 384 'with no regard to the actual status. Valid values are ' 385 '"attached" and "detached". Default=None, that means the ' 386 'status is unchanged.')) 387@utils.arg('--reset-migration-status', 388 action='store_true', 389 help=('Clears the migration status of the volume in the DataBase ' 390 'that indicates the volume is source or destination of ' 391 'volume migration, with no regard to the actual status.')) 392def do_reset_state(cs, args): 393 """Explicitly updates the volume state in the Cinder database. 394 395 Note that this does not affect whether the volume is actually attached to 396 the Nova compute host or instance and can result in an unusable volume. 397 Being a database change only, this has no impact on the true state of the 398 volume and may not match the actual state. This can render a volume 399 unusable in the case of change to the 'available' state. 400 """ 401 failure_flag = False 402 migration_status = 'none' if args.reset_migration_status else None 403 if not (args.state or args.attach_status or migration_status): 404 # Nothing specified, default to resetting state 405 args.state = 'available' 406 407 for volume in args.volume: 408 try: 409 utils.find_volume(cs, volume).reset_state(args.state, 410 args.attach_status, 411 migration_status) 412 except Exception as e: 413 failure_flag = True 414 msg = "Reset state for volume %s failed: %s" % (volume, e) 415 print(msg) 416 417 if failure_flag: 418 msg = "Unable to reset the state for the specified volume(s)." 419 raise exceptions.CommandError(msg) 420 421 422@utils.arg('volume', 423 metavar='<volume>', 424 help='Name or ID of volume to rename.') 425@utils.arg('name', 426 nargs='?', 427 metavar='<name>', 428 help='New name for volume.') 429@utils.arg('--description', metavar='<description>', 430 help='Volume description. Default=None.', 431 default=None) 432@utils.arg('--display-description', 433 help=argparse.SUPPRESS) 434@utils.arg('--display_description', 435 help=argparse.SUPPRESS) 436def do_rename(cs, args): 437 """Renames a volume.""" 438 kwargs = {} 439 440 if args.name is not None: 441 kwargs['name'] = args.name 442 if args.display_description is not None: 443 kwargs['description'] = args.display_description 444 elif args.description is not None: 445 kwargs['description'] = args.description 446 447 if not any(kwargs): 448 msg = 'Must supply either name or description.' 449 raise exceptions.ClientException(code=1, message=msg) 450 451 utils.find_volume(cs, args.volume).update(**kwargs) 452 453 454@utils.arg('volume', 455 metavar='<volume>', 456 help='Name or ID of volume for which to update metadata.') 457@utils.arg('action', 458 metavar='<action>', 459 choices=['set', 'unset'], 460 help='The action. Valid values are "set" or "unset."') 461@utils.arg('metadata', 462 metavar='<key=value>', 463 nargs='+', 464 default=[], 465 help='Metadata key and value pair to set or unset. ' 466 'For unset, specify only the key.') 467def do_metadata(cs, args): 468 """Sets or deletes volume metadata.""" 469 volume = utils.find_volume(cs, args.volume) 470 metadata = shell_utils.extract_metadata(args) 471 472 if args.action == 'set': 473 cs.volumes.set_metadata(volume, metadata) 474 elif args.action == 'unset': 475 # NOTE(zul): Make sure py2/py3 sorting is the same 476 cs.volumes.delete_metadata(volume, sorted(metadata.keys(), 477 reverse=True)) 478 479 480@utils.arg('volume', 481 metavar='<volume>', 482 help='Name or ID of volume for which to update metadata.') 483@utils.arg('action', 484 metavar='<action>', 485 choices=['set', 'unset'], 486 help="The action. Valid values are 'set' or 'unset.'") 487@utils.arg('metadata', 488 metavar='<key=value>', 489 nargs='+', 490 default=[], 491 help='Metadata key and value pair to set or unset. ' 492 'For unset, specify only the key.') 493def do_image_metadata(cs, args): 494 """Sets or deletes volume image metadata.""" 495 volume = utils.find_volume(cs, args.volume) 496 metadata = shell_utils.extract_metadata(args) 497 498 if args.action == 'set': 499 cs.volumes.set_image_metadata(volume, metadata) 500 elif args.action == 'unset': 501 cs.volumes.delete_image_metadata(volume, sorted(metadata.keys(), 502 reverse=True)) 503 504 505@utils.arg('--all-tenants', 506 dest='all_tenants', 507 metavar='<0|1>', 508 nargs='?', 509 type=int, 510 const=1, 511 default=0, 512 help='Shows details for all tenants. Admin only.') 513@utils.arg('--all_tenants', 514 nargs='?', 515 type=int, 516 const=1, 517 help=argparse.SUPPRESS) 518@utils.arg('--name', 519 metavar='<name>', 520 default=None, 521 help='Filters results by a name. Default=None.') 522@utils.arg('--display-name', 523 help=argparse.SUPPRESS) 524@utils.arg('--display_name', 525 help=argparse.SUPPRESS) 526@utils.arg('--status', 527 metavar='<status>', 528 default=None, 529 help='Filters results by a status. Default=None.') 530@utils.arg('--volume-id', 531 metavar='<volume-id>', 532 default=None, 533 help='Filters results by a volume ID. Default=None.') 534@utils.arg('--volume_id', 535 help=argparse.SUPPRESS) 536@utils.arg('--marker', 537 metavar='<marker>', 538 default=None, 539 help='Begin returning snapshots that appear later in the snapshot ' 540 'list than that represented by this id. ' 541 'Default=None.') 542@utils.arg('--limit', 543 metavar='<limit>', 544 default=None, 545 help='Maximum number of snapshots to return. Default=None.') 546@utils.arg('--sort', 547 metavar='<key>[:<direction>]', 548 default=None, 549 help=(('Comma-separated list of sort keys and directions in the ' 550 'form of <key>[:<asc|desc>]. ' 551 'Valid keys: %s. ' 552 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) 553@utils.arg('--tenant', 554 type=str, 555 dest='tenant', 556 nargs='?', 557 metavar='<tenant>', 558 help='Display information from single tenant (Admin only).') 559def do_snapshot_list(cs, args): 560 """Lists all snapshots.""" 561 all_tenants = (1 if args.tenant else 562 int(os.environ.get("ALL_TENANTS", args.all_tenants))) 563 564 if args.display_name is not None: 565 args.name = args.display_name 566 567 search_opts = { 568 'all_tenants': all_tenants, 569 'name': args.name, 570 'status': args.status, 571 'volume_id': args.volume_id, 572 'project_id': args.tenant, 573 } 574 575 snapshots = cs.volume_snapshots.list(search_opts=search_opts, 576 marker=args.marker, 577 limit=args.limit, 578 sort=args.sort) 579 shell_utils.translate_volume_snapshot_keys(snapshots) 580 if args.sort: 581 sortby_index = None 582 else: 583 sortby_index = 0 584 585 utils.print_list(snapshots, 586 ['ID', 'Volume ID', 'Status', 'Name', 'Size'], 587 sortby_index=sortby_index) 588 589 590@utils.arg('snapshot', 591 metavar='<snapshot>', 592 help='Name or ID of snapshot.') 593def do_snapshot_show(cs, args): 594 """Shows snapshot details.""" 595 snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) 596 shell_utils.print_volume_snapshot(snapshot) 597 598 599@utils.arg('volume', 600 metavar='<volume>', 601 help='Name or ID of volume to snapshot.') 602@utils.arg('--force', 603 metavar='<True|False>', 604 const=True, 605 nargs='?', 606 default=False, 607 help='Allows or disallows snapshot of ' 608 'a volume when the volume is attached to an instance. ' 609 'If set to True, ignores the current status of the ' 610 'volume when attempting to snapshot it rather ' 611 'than forcing it to be available. ' 612 'Default=False.') 613@utils.arg('--name', 614 metavar='<name>', 615 default=None, 616 help='Snapshot name. Default=None.') 617@utils.arg('--display-name', 618 help=argparse.SUPPRESS) 619@utils.arg('--display_name', 620 help=argparse.SUPPRESS) 621@utils.arg('--description', 622 metavar='<description>', 623 default=None, 624 help='Snapshot description. Default=None.') 625@utils.arg('--display-description', 626 help=argparse.SUPPRESS) 627@utils.arg('--display_description', 628 help=argparse.SUPPRESS) 629@utils.arg('--metadata', 630 nargs='*', 631 metavar='<key=value>', 632 default=None, 633 help='Snapshot metadata key and value pairs. Default=None.') 634def do_snapshot_create(cs, args): 635 """Creates a snapshot.""" 636 if args.display_name is not None: 637 args.name = args.display_name 638 639 if args.display_description is not None: 640 args.description = args.display_description 641 642 snapshot_metadata = None 643 if args.metadata is not None: 644 snapshot_metadata = shell_utils.extract_metadata(args) 645 646 volume = utils.find_volume(cs, args.volume) 647 snapshot = cs.volume_snapshots.create(volume.id, 648 args.force, 649 args.name, 650 args.description, 651 metadata=snapshot_metadata) 652 shell_utils.print_volume_snapshot(snapshot) 653 654 655@utils.arg('snapshot', 656 metavar='<snapshot>', nargs='+', 657 help='Name or ID of the snapshot(s) to delete.') 658@utils.arg('--force', 659 action="store_true", 660 help='Allows deleting snapshot of a volume ' 661 'when its status is other than "available" or "error". ' 662 'Default=False.') 663def do_snapshot_delete(cs, args): 664 """Removes one or more snapshots.""" 665 failure_count = 0 666 667 for snapshot in args.snapshot: 668 try: 669 shell_utils.find_volume_snapshot(cs, snapshot).delete(args.force) 670 except Exception as e: 671 failure_count += 1 672 print("Delete for snapshot %s failed: %s" % (snapshot, e)) 673 if failure_count == len(args.snapshot): 674 raise exceptions.CommandError("Unable to delete any of the specified " 675 "snapshots.") 676 677 678@utils.arg('snapshot', metavar='<snapshot>', 679 help='Name or ID of snapshot.') 680@utils.arg('name', nargs='?', metavar='<name>', 681 help='New name for snapshot.') 682@utils.arg('--description', metavar='<description>', 683 default=None, 684 help='Snapshot description. Default=None.') 685@utils.arg('--display-description', 686 help=argparse.SUPPRESS) 687@utils.arg('--display_description', 688 help=argparse.SUPPRESS) 689def do_snapshot_rename(cs, args): 690 """Renames a snapshot.""" 691 kwargs = {} 692 693 if args.name is not None: 694 kwargs['name'] = args.name 695 696 if args.description is not None: 697 kwargs['description'] = args.description 698 elif args.display_description is not None: 699 kwargs['description'] = args.display_description 700 701 if not any(kwargs): 702 msg = 'Must supply either name or description.' 703 raise exceptions.ClientException(code=1, message=msg) 704 705 shell_utils.find_volume_snapshot(cs, args.snapshot).update(**kwargs) 706 print("Request to rename snapshot '%s' has been accepted." % ( 707 args.snapshot)) 708 709 710@utils.arg('snapshot', metavar='<snapshot>', nargs='+', 711 help='Name or ID of snapshot to modify.') 712@utils.arg('--state', metavar='<state>', 713 default='available', 714 help=('The state to assign to the snapshot. Valid values are ' 715 '"available", "error", "creating", "deleting", and ' 716 '"error_deleting". NOTE: This command simply changes ' 717 'the state of the Snapshot in the DataBase with no regard ' 718 'to actual status, exercise caution when using. ' 719 'Default=available.')) 720def do_snapshot_reset_state(cs, args): 721 """Explicitly updates the snapshot state.""" 722 failure_count = 0 723 724 single = (len(args.snapshot) == 1) 725 726 for snapshot in args.snapshot: 727 try: 728 shell_utils.find_volume_snapshot( 729 cs, snapshot).reset_state(args.state) 730 except Exception as e: 731 failure_count += 1 732 msg = "Reset state for snapshot %s failed: %s" % (snapshot, e) 733 if not single: 734 print(msg) 735 736 if failure_count == len(args.snapshot): 737 if not single: 738 msg = ("Unable to reset the state for any of the specified " 739 "snapshots.") 740 raise exceptions.CommandError(msg) 741 742 743def do_type_list(cs, args): 744 """Lists available 'volume types'. 745 746 (Only admin and tenant users will see private types) 747 """ 748 vtypes = cs.volume_types.list() 749 shell_utils.print_volume_type_list(vtypes) 750 751 752def do_type_default(cs, args): 753 """List the default volume type.""" 754 vtype = cs.volume_types.default() 755 shell_utils.print_volume_type_list([vtype]) 756 757 758@utils.arg('volume_type', 759 metavar='<volume_type>', 760 help='Name or ID of the volume type.') 761def do_type_show(cs, args): 762 """Show volume type details.""" 763 vtype = shell_utils.find_vtype(cs, args.volume_type) 764 info = dict() 765 info.update(vtype._info) 766 767 info.pop('links', None) 768 utils.print_dict(info, formatters=['extra_specs']) 769 770 771@utils.arg('id', 772 metavar='<id>', 773 help='ID of the volume type.') 774@utils.arg('--name', 775 metavar='<name>', 776 help='Name of the volume type.') 777@utils.arg('--description', 778 metavar='<description>', 779 help='Description of the volume type.') 780@utils.arg('--is-public', 781 metavar='<is-public>', 782 help='Make type accessible to the public or not.') 783def do_type_update(cs, args): 784 """Updates volume type name, description, and/or is_public.""" 785 is_public = args.is_public 786 if args.name is None and args.description is None and is_public is None: 787 raise exceptions.CommandError('Specify a new type name, description, ' 788 'is_public or a combination thereof.') 789 790 if is_public is not None: 791 is_public = strutils.bool_from_string(args.is_public, strict=True) 792 vtype = cs.volume_types.update(args.id, args.name, args.description, 793 is_public) 794 shell_utils.print_volume_type_list([vtype]) 795 796 797def do_extra_specs_list(cs, args): 798 """Lists current volume types and extra specs.""" 799 vtypes = cs.volume_types.list() 800 utils.print_list(vtypes, ['ID', 'Name', 'extra_specs']) 801 802 803@utils.arg('name', 804 metavar='<name>', 805 help='Name of new volume type.') 806@utils.arg('--description', 807 metavar='<description>', 808 help='Description of new volume type.') 809@utils.arg('--is-public', 810 metavar='<is-public>', 811 default=True, 812 help='Make type accessible to the public (default true).') 813def do_type_create(cs, args): 814 """Creates a volume type.""" 815 is_public = strutils.bool_from_string(args.is_public, strict=True) 816 vtype = cs.volume_types.create(args.name, args.description, is_public) 817 shell_utils.print_volume_type_list([vtype]) 818 819 820@utils.arg('vol_type', 821 metavar='<vol_type>', nargs='+', 822 help='Name or ID of volume type or types to delete.') 823def do_type_delete(cs, args): 824 """Deletes volume type or types.""" 825 failure_count = 0 826 for vol_type in args.vol_type: 827 try: 828 vtype = shell_utils.find_volume_type(cs, vol_type) 829 cs.volume_types.delete(vtype) 830 print("Request to delete volume type %s has been accepted." 831 % (vol_type)) 832 except Exception as e: 833 failure_count += 1 834 print("Delete for volume type %s failed: %s" % (vol_type, e)) 835 if failure_count == len(args.vol_type): 836 raise exceptions.CommandError("Unable to delete any of the " 837 "specified types.") 838 839 840@utils.arg('vtype', 841 metavar='<vtype>', 842 help='Name or ID of volume type.') 843@utils.arg('action', 844 metavar='<action>', 845 choices=['set', 'unset'], 846 help='The action. Valid values are "set" or "unset."') 847@utils.arg('metadata', 848 metavar='<key=value>', 849 nargs='+', 850 default=[], 851 help='The extra specs key and value pair to set or unset. ' 852 'For unset, specify only the key.') 853def do_type_key(cs, args): 854 """Sets or unsets extra_spec for a volume type.""" 855 vtype = shell_utils.find_volume_type(cs, args.vtype) 856 keypair = shell_utils.extract_metadata(args) 857 858 if args.action == 'set': 859 vtype.set_keys(keypair) 860 elif args.action == 'unset': 861 vtype.unset_keys(list(keypair)) 862 863 864@utils.arg('--volume-type', metavar='<volume_type>', required=True, 865 help='Filter results by volume type name or ID.') 866def do_type_access_list(cs, args): 867 """Print access information about the given volume type.""" 868 volume_type = shell_utils.find_volume_type(cs, args.volume_type) 869 if volume_type.is_public: 870 raise exceptions.CommandError("Failed to get access list " 871 "for public volume type.") 872 access_list = cs.volume_type_access.list(volume_type) 873 874 columns = ['Volume_type_ID', 'Project_ID'] 875 utils.print_list(access_list, columns) 876 877 878@utils.arg('--volume-type', metavar='<volume_type>', required=True, 879 help='Volume type name or ID to add access for the given project.') 880@utils.arg('--project-id', metavar='<project_id>', required=True, 881 help='Project ID to add volume type access for.') 882def do_type_access_add(cs, args): 883 """Adds volume type access for the given project.""" 884 vtype = shell_utils.find_volume_type(cs, args.volume_type) 885 cs.volume_type_access.add_project_access(vtype, args.project_id) 886 887 888@utils.arg('--volume-type', metavar='<volume_type>', required=True, 889 help=('Volume type name or ID to remove access ' 890 'for the given project.')) 891@utils.arg('--project-id', metavar='<project_id>', required=True, 892 help='Project ID to remove volume type access for.') 893def do_type_access_remove(cs, args): 894 """Removes volume type access for the given project.""" 895 vtype = shell_utils.find_volume_type(cs, args.volume_type) 896 cs.volume_type_access.remove_project_access( 897 vtype, args.project_id) 898 899 900@utils.arg('tenant', 901 metavar='<tenant_id>', 902 help='ID of tenant for which to list quotas.') 903def do_quota_show(cs, args): 904 """Lists quotas for a tenant.""" 905 906 shell_utils.quota_show(cs.quotas.get(args.tenant)) 907 908 909@utils.arg('tenant', metavar='<tenant_id>', 910 help='ID of tenant for which to list quota usage.') 911def do_quota_usage(cs, args): 912 """Lists quota usage for a tenant.""" 913 914 shell_utils.quota_usage_show(cs.quotas.get(args.tenant, usage=True)) 915 916 917@utils.arg('tenant', 918 metavar='<tenant_id>', 919 help='ID of tenant for which to list quota defaults.') 920def do_quota_defaults(cs, args): 921 """Lists default quotas for a tenant.""" 922 923 shell_utils.quota_show(cs.quotas.defaults(args.tenant)) 924 925 926@utils.arg('tenant', 927 metavar='<tenant_id>', 928 help='ID of tenant for which to set quotas.') 929@utils.arg('--volumes', 930 metavar='<volumes>', 931 type=int, default=None, 932 help='The new "volumes" quota value. Default=None.') 933@utils.arg('--snapshots', 934 metavar='<snapshots>', 935 type=int, default=None, 936 help='The new "snapshots" quota value. Default=None.') 937@utils.arg('--gigabytes', 938 metavar='<gigabytes>', 939 type=int, default=None, 940 help='The new "gigabytes" quota value. Default=None.') 941@utils.arg('--backups', 942 metavar='<backups>', 943 type=int, default=None, 944 help='The new "backups" quota value. Default=None.') 945@utils.arg('--backup-gigabytes', 946 metavar='<backup_gigabytes>', 947 type=int, default=None, 948 help='The new "backup_gigabytes" quota value. Default=None.') 949@utils.arg('--volume-type', 950 metavar='<volume_type_name>', 951 default=None, 952 help='Volume type. Default=None.') 953@utils.arg('--per-volume-gigabytes', 954 metavar='<per_volume_gigabytes>', 955 type=int, default=None, 956 help='Set max volume size limit. Default=None.') 957def do_quota_update(cs, args): 958 """Updates quotas for a tenant.""" 959 960 shell_utils.quota_update(cs.quotas, args.tenant, args) 961 962 963@utils.arg('tenant', metavar='<tenant_id>', 964 help='UUID of tenant to delete the quotas for.') 965def do_quota_delete(cs, args): 966 """Delete the quotas for a tenant.""" 967 968 cs.quotas.delete(args.tenant) 969 970 971@utils.arg('class_name', 972 metavar='<class>', 973 help='Name of quota class for which to list quotas.') 974def do_quota_class_show(cs, args): 975 """Lists quotas for a quota class.""" 976 977 shell_utils.quota_show(cs.quota_classes.get(args.class_name)) 978 979 980@utils.arg('class_name', 981 metavar='<class_name>', 982 help='Name of quota class for which to set quotas.') 983@utils.arg('--volumes', 984 metavar='<volumes>', 985 type=int, default=None, 986 help='The new "volumes" quota value. Default=None.') 987@utils.arg('--snapshots', 988 metavar='<snapshots>', 989 type=int, default=None, 990 help='The new "snapshots" quota value. Default=None.') 991@utils.arg('--gigabytes', 992 metavar='<gigabytes>', 993 type=int, default=None, 994 help='The new "gigabytes" quota value. Default=None.') 995@utils.arg('--backups', 996 metavar='<backups>', 997 type=int, default=None, 998 help='The new "backups" quota value. Default=None.') 999@utils.arg('--backup-gigabytes', 1000 metavar='<backup_gigabytes>', 1001 type=int, default=None, 1002 help='The new "backup_gigabytes" quota value. Default=None.') 1003@utils.arg('--volume-type', 1004 metavar='<volume_type_name>', 1005 default=None, 1006 help='Volume type. Default=None.') 1007@utils.arg('--per-volume-gigabytes', 1008 metavar='<per_volume_gigabytes>', 1009 type=int, default=None, 1010 help='Set max volume size limit. Default=None.') 1011def do_quota_class_update(cs, args): 1012 """Updates quotas for a quota class.""" 1013 1014 shell_utils.quota_update(cs.quota_classes, args.class_name, args) 1015 1016 1017@utils.arg('tenant', 1018 metavar='<tenant_id>', 1019 nargs='?', 1020 default=None, 1021 help='Display information for a single tenant (Admin only).') 1022def do_absolute_limits(cs, args): 1023 """Lists absolute limits for a user.""" 1024 limits = cs.limits.get(args.tenant).absolute 1025 columns = ['Name', 'Value'] 1026 utils.print_list(limits, columns) 1027 1028 1029@utils.arg('tenant', 1030 metavar='<tenant_id>', 1031 nargs='?', 1032 default=None, 1033 help='Display information for a single tenant (Admin only).') 1034def do_rate_limits(cs, args): 1035 """Lists rate limits for a user.""" 1036 limits = cs.limits.get(args.tenant).rate 1037 columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] 1038 utils.print_list(limits, columns) 1039 1040 1041@utils.arg('volume', 1042 metavar='<volume>', 1043 help='Name or ID of volume to snapshot.') 1044@utils.arg('--force', 1045 metavar='<True|False>', 1046 const=True, 1047 nargs='?', 1048 default=False, 1049 help='Enables or disables upload of ' 1050 'a volume that is attached to an instance. ' 1051 'Default=False. ' 1052 'This option may not be supported by your cloud.') 1053@utils.arg('--container-format', 1054 metavar='<container-format>', 1055 default='bare', 1056 help='Container format type. ' 1057 'Default is bare.') 1058@utils.arg('--container_format', 1059 help=argparse.SUPPRESS) 1060@utils.arg('--disk-format', 1061 metavar='<disk-format>', 1062 default='raw', 1063 help='Disk format type. ' 1064 'Default is raw.') 1065@utils.arg('--disk_format', 1066 help=argparse.SUPPRESS) 1067@utils.arg('image_name', 1068 metavar='<image-name>', 1069 help='The new image name.') 1070@utils.arg('--image_name', 1071 help=argparse.SUPPRESS) 1072def do_upload_to_image(cs, args): 1073 """Uploads volume to Image Service as an image.""" 1074 volume = utils.find_volume(cs, args.volume) 1075 shell_utils.print_volume_image( 1076 volume.upload_to_image(args.force, 1077 args.image_name, 1078 args.container_format, 1079 args.disk_format)) 1080 1081 1082@utils.arg('volume', metavar='<volume>', help='ID of volume to migrate.') 1083@utils.arg('host', metavar='<host>', help='Destination host. Takes the form: ' 1084 'host@backend-name#pool') 1085@utils.arg('--force-host-copy', metavar='<True|False>', 1086 choices=['True', 'False'], 1087 required=False, 1088 const=True, 1089 nargs='?', 1090 default=False, 1091 help='Enables or disables generic host-based ' 1092 'force-migration, which bypasses driver ' 1093 'optimizations. Default=False.') 1094@utils.arg('--lock-volume', metavar='<True|False>', 1095 choices=['True', 'False'], 1096 required=False, 1097 const=True, 1098 nargs='?', 1099 default=False, 1100 help='Enables or disables the termination of volume migration ' 1101 'caused by other commands. This option applies to the ' 1102 'available volume. True means it locks the volume ' 1103 'state and does not allow the migration to be aborted. The ' 1104 'volume status will be in maintenance during the ' 1105 'migration. False means it allows the volume migration ' 1106 'to be aborted. The volume status is still in the original ' 1107 'status. Default=False.') 1108def do_migrate(cs, args): 1109 """Migrates volume to a new host.""" 1110 volume = utils.find_volume(cs, args.volume) 1111 try: 1112 volume.migrate_volume(args.host, args.force_host_copy, 1113 args.lock_volume) 1114 print("Request to migrate volume %s has been accepted." % (volume.id)) 1115 except Exception as e: 1116 print("Migration for volume %s failed: %s." % (volume.id, 1117 six.text_type(e))) 1118 1119 1120@utils.arg('volume', metavar='<volume>', 1121 help='Name or ID of volume for which to modify type.') 1122@utils.arg('new_type', metavar='<volume-type>', help='New volume type.') 1123@utils.arg('--migration-policy', metavar='<never|on-demand>', required=False, 1124 choices=['never', 'on-demand'], default='never', 1125 help='Migration policy during retype of volume.') 1126def do_retype(cs, args): 1127 """Changes the volume type for a volume.""" 1128 volume = utils.find_volume(cs, args.volume) 1129 volume.retype(args.new_type, args.migration_policy) 1130 1131 1132@utils.arg('volume', metavar='<volume>', 1133 help='Name or ID of volume to backup.') 1134@utils.arg('--container', metavar='<container>', 1135 default=None, 1136 help='Backup container name. Default=None.') 1137@utils.arg('--display-name', 1138 help=argparse.SUPPRESS) 1139@utils.arg('--name', metavar='<name>', 1140 default=None, 1141 help='Backup name. Default=None.') 1142@utils.arg('--display-description', 1143 help=argparse.SUPPRESS) 1144@utils.arg('--description', 1145 metavar='<description>', 1146 default=None, 1147 help='Backup description. Default=None.') 1148@utils.arg('--incremental', 1149 action='store_true', 1150 help='Incremental backup. Default=False.', 1151 default=False) 1152@utils.arg('--force', 1153 action='store_true', 1154 help='Allows or disallows backup of a volume ' 1155 'when the volume is attached to an instance. ' 1156 'If set to True, backs up the volume whether ' 1157 'its status is "available" or "in-use". The backup ' 1158 'of an "in-use" volume means your data is crash ' 1159 'consistent. Default=False.', 1160 default=False) 1161@utils.arg('--snapshot-id', 1162 metavar='<snapshot-id>', 1163 default=None, 1164 help='ID of snapshot to backup. Default=None.') 1165def do_backup_create(cs, args): 1166 """Creates a volume backup.""" 1167 if args.display_name is not None: 1168 args.name = args.display_name 1169 1170 if args.display_description is not None: 1171 args.description = args.display_description 1172 1173 volume = utils.find_volume(cs, args.volume) 1174 backup = cs.backups.create(volume.id, 1175 args.container, 1176 args.name, 1177 args.description, 1178 args.incremental, 1179 args.force, 1180 args.snapshot_id) 1181 1182 info = {"volume_id": volume.id} 1183 info.update(backup._info) 1184 1185 if 'links' in info: 1186 info.pop('links') 1187 1188 utils.print_dict(info) 1189 1190 1191@utils.arg('backup', metavar='<backup>', help='Name or ID of backup.') 1192def do_backup_show(cs, args): 1193 """Shows backup details.""" 1194 backup = shell_utils.find_backup(cs, args.backup) 1195 info = dict() 1196 info.update(backup._info) 1197 1198 info.pop('links', None) 1199 utils.print_dict(info) 1200 1201 1202@utils.arg('--all-tenants', 1203 metavar='<all_tenants>', 1204 nargs='?', 1205 type=int, 1206 const=1, 1207 default=0, 1208 help='Shows details for all tenants. Admin only.') 1209@utils.arg('--all_tenants', 1210 nargs='?', 1211 type=int, 1212 const=1, 1213 help=argparse.SUPPRESS) 1214@utils.arg('--name', 1215 metavar='<name>', 1216 default=None, 1217 help='Filters results by a name. Default=None.') 1218@utils.arg('--status', 1219 metavar='<status>', 1220 default=None, 1221 help='Filters results by a status. Default=None.') 1222@utils.arg('--volume-id', 1223 metavar='<volume-id>', 1224 default=None, 1225 help='Filters results by a volume ID. Default=None.') 1226@utils.arg('--volume_id', 1227 help=argparse.SUPPRESS) 1228@utils.arg('--marker', 1229 metavar='<marker>', 1230 default=None, 1231 help='Begin returning backups that appear later in the backup ' 1232 'list than that represented by this id. ' 1233 'Default=None.') 1234@utils.arg('--limit', 1235 metavar='<limit>', 1236 default=None, 1237 help='Maximum number of backups to return. Default=None.') 1238@utils.arg('--sort', 1239 metavar='<key>[:<direction>]', 1240 default=None, 1241 help=(('Comma-separated list of sort keys and directions in the ' 1242 'form of <key>[:<asc|desc>]. ' 1243 'Valid keys: %s. ' 1244 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) 1245def do_backup_list(cs, args): 1246 """Lists all backups.""" 1247 1248 search_opts = { 1249 'all_tenants': args.all_tenants, 1250 'name': args.name, 1251 'status': args.status, 1252 'volume_id': args.volume_id, 1253 } 1254 1255 backups = cs.backups.list(search_opts=search_opts, 1256 marker=args.marker, 1257 limit=args.limit, 1258 sort=args.sort) 1259 shell_utils.translate_volume_snapshot_keys(backups) 1260 columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', 1261 'Container'] 1262 if args.sort: 1263 sortby_index = None 1264 else: 1265 sortby_index = 0 1266 utils.print_list(backups, columns, sortby_index=sortby_index) 1267 1268 1269@utils.arg('--force', 1270 action="store_true", 1271 help='Allows deleting backup of a volume ' 1272 'when its status is other than "available" or "error". ' 1273 'Default=False.') 1274@utils.arg('backup', metavar='<backup>', nargs='+', 1275 help='Name or ID of backup(s) to delete.') 1276def do_backup_delete(cs, args): 1277 """Removes one or more backups.""" 1278 failure_count = 0 1279 for backup in args.backup: 1280 try: 1281 shell_utils.find_backup(cs, backup).delete(args.force) 1282 print("Request to delete backup %s has been accepted." % (backup)) 1283 except Exception as e: 1284 failure_count += 1 1285 print("Delete for backup %s failed: %s" % (backup, e)) 1286 if failure_count == len(args.backup): 1287 raise exceptions.CommandError("Unable to delete any of the specified " 1288 "backups.") 1289 1290 1291@utils.arg('backup', metavar='<backup>', 1292 help='Name or ID of backup to restore.') 1293@utils.arg('--volume-id', metavar='<volume>', 1294 default=None, 1295 help=argparse.SUPPRESS) 1296@utils.arg('--volume', metavar='<volume>', 1297 default=None, 1298 help='Name or ID of existing volume to which to restore. ' 1299 'This is mutually exclusive with --name and takes priority. ' 1300 'Default=None.') 1301@utils.arg('--name', metavar='<name>', 1302 default=None, 1303 help='Use the name for new volume creation to restore. ' 1304 'This is mutually exclusive with --volume (or the deprecated ' 1305 '--volume-id) and --volume (or --volume-id) takes priority. ' 1306 'Default=None.') 1307def do_backup_restore(cs, args): 1308 """Restores a backup.""" 1309 vol = args.volume or args.volume_id 1310 if vol: 1311 volume_id = utils.find_volume(cs, vol).id 1312 if args.name: 1313 args.name = None 1314 print('Mutually exclusive options are specified simultaneously: ' 1315 '"--volume (or the deprecated --volume-id) and --name". ' 1316 'The --volume (or --volume-id) option takes priority.') 1317 else: 1318 volume_id = None 1319 1320 backup = shell_utils.find_backup(cs, args.backup) 1321 restore = cs.restores.restore(backup.id, volume_id, args.name) 1322 1323 info = {"backup_id": backup.id} 1324 info.update(restore._info) 1325 1326 info.pop('links', None) 1327 1328 utils.print_dict(info) 1329 1330 1331@utils.arg('backup', metavar='<backup>', 1332 help='ID of the backup to export.') 1333def do_backup_export(cs, args): 1334 """Export backup metadata record.""" 1335 info = cs.backups.export_record(args.backup) 1336 utils.print_dict(info) 1337 1338 1339@utils.arg('backup_service', metavar='<backup_service>', 1340 help='Backup service to use for importing the backup.') 1341@utils.arg('backup_url', metavar='<backup_url>', 1342 help='Backup URL for importing the backup metadata.') 1343def do_backup_import(cs, args): 1344 """Import backup metadata record.""" 1345 info = cs.backups.import_record(args.backup_service, args.backup_url) 1346 info.pop('links', None) 1347 1348 utils.print_dict(info) 1349 1350 1351@utils.arg('backup', metavar='<backup>', nargs='+', 1352 help='Name or ID of the backup to modify.') 1353@utils.arg('--state', metavar='<state>', 1354 default='available', 1355 help='The state to assign to the backup. Valid values are ' 1356 '"available", "error". Default=available.') 1357def do_backup_reset_state(cs, args): 1358 """Explicitly updates the backup state.""" 1359 failure_count = 0 1360 1361 single = (len(args.backup) == 1) 1362 1363 for backup in args.backup: 1364 try: 1365 shell_utils.find_backup(cs, backup).reset_state(args.state) 1366 print("Request to update backup '%s' has been accepted." % backup) 1367 except Exception as e: 1368 failure_count += 1 1369 msg = "Reset state for backup %s failed: %s" % (backup, e) 1370 if not single: 1371 print(msg) 1372 1373 if failure_count == len(args.backup): 1374 if not single: 1375 msg = ("Unable to reset the state for any of the specified " 1376 "backups.") 1377 raise exceptions.CommandError(msg) 1378 1379 1380@utils.arg('volume', metavar='<volume>', 1381 help='Name or ID of volume to transfer.') 1382@utils.arg('--name', 1383 metavar='<name>', 1384 default=None, 1385 help='Transfer name. Default=None.') 1386@utils.arg('--display-name', 1387 help=argparse.SUPPRESS) 1388def do_transfer_create(cs, args): 1389 """Creates a volume transfer.""" 1390 if args.display_name is not None: 1391 args.name = args.display_name 1392 1393 volume = utils.find_volume(cs, args.volume) 1394 transfer = cs.transfers.create(volume.id, 1395 args.name) 1396 info = dict() 1397 info.update(transfer._info) 1398 1399 info.pop('links', None) 1400 utils.print_dict(info) 1401 1402 1403@utils.arg('transfer', metavar='<transfer>', 1404 help='Name or ID of transfer to delete.') 1405def do_transfer_delete(cs, args): 1406 """Undoes a transfer.""" 1407 transfer = shell_utils.find_transfer(cs, args.transfer) 1408 transfer.delete() 1409 1410 1411@utils.arg('transfer', metavar='<transfer>', 1412 help='ID of transfer to accept.') 1413@utils.arg('auth_key', metavar='<auth_key>', 1414 help='Authentication key of transfer to accept.') 1415def do_transfer_accept(cs, args): 1416 """Accepts a volume transfer.""" 1417 transfer = cs.transfers.accept(args.transfer, args.auth_key) 1418 info = dict() 1419 info.update(transfer._info) 1420 1421 info.pop('links', None) 1422 utils.print_dict(info) 1423 1424 1425@utils.arg('--all-tenants', 1426 dest='all_tenants', 1427 metavar='<0|1>', 1428 nargs='?', 1429 type=int, 1430 const=1, 1431 default=0, 1432 help='Shows details for all tenants. Admin only.') 1433@utils.arg('--all_tenants', 1434 nargs='?', 1435 type=int, 1436 const=1, 1437 help=argparse.SUPPRESS) 1438def do_transfer_list(cs, args): 1439 """Lists all transfers.""" 1440 all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) 1441 search_opts = { 1442 'all_tenants': all_tenants, 1443 } 1444 transfers = cs.transfers.list(search_opts=search_opts) 1445 columns = ['ID', 'Volume ID', 'Name'] 1446 utils.print_list(transfers, columns) 1447 1448 1449@utils.arg('transfer', metavar='<transfer>', 1450 help='Name or ID of transfer to accept.') 1451def do_transfer_show(cs, args): 1452 """Shows transfer details.""" 1453 transfer = shell_utils.find_transfer(cs, args.transfer) 1454 info = dict() 1455 info.update(transfer._info) 1456 1457 info.pop('links', None) 1458 utils.print_dict(info) 1459 1460 1461@utils.arg('volume', metavar='<volume>', 1462 help='Name or ID of volume to extend.') 1463@utils.arg('new_size', 1464 metavar='<new_size>', 1465 type=int, 1466 help='New size of volume, in GiBs.') 1467def do_extend(cs, args): 1468 """Attempts to extend size of an existing volume.""" 1469 volume = utils.find_volume(cs, args.volume) 1470 cs.volumes.extend(volume, args.new_size) 1471 1472 1473@utils.arg('--host', metavar='<hostname>', default=None, 1474 help='Host name. Default=None.') 1475@utils.arg('--binary', metavar='<binary>', default=None, 1476 help='Service binary. Default=None.') 1477@utils.arg('--withreplication', 1478 metavar='<True|False>', 1479 const=True, 1480 nargs='?', 1481 default=False, 1482 help='Enables or disables display of ' 1483 'Replication info for c-vol services. Default=False.') 1484def do_service_list(cs, args): 1485 """Lists all services. Filter by host and service binary.""" 1486 replication = strutils.bool_from_string(args.withreplication, 1487 strict=True) 1488 result = cs.services.list(host=args.host, binary=args.binary) 1489 columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"] 1490 if replication: 1491 columns.extend(["Replication Status", "Active Backend ID", "Frozen"]) 1492 # NOTE(jay-lau-513): we check if the response has disabled_reason 1493 # so as not to add the column when the extended ext is not enabled. 1494 if result and hasattr(result[0], 'disabled_reason'): 1495 columns.append("Disabled Reason") 1496 utils.print_list(result, columns) 1497 1498 1499@utils.arg('host', metavar='<hostname>', help='Host name.') 1500@utils.arg('binary', metavar='<binary>', help='Service binary.') 1501def do_service_enable(cs, args): 1502 """Enables the service.""" 1503 result = cs.services.enable(args.host, args.binary) 1504 columns = ["Host", "Binary", "Status"] 1505 utils.print_list([result], columns) 1506 1507 1508@utils.arg('host', metavar='<hostname>', help='Host name.') 1509@utils.arg('binary', metavar='<binary>', help='Service binary.') 1510@utils.arg('--reason', metavar='<reason>', 1511 help='Reason for disabling service.') 1512def do_service_disable(cs, args): 1513 """Disables the service.""" 1514 columns = ["Host", "Binary", "Status"] 1515 if args.reason: 1516 columns.append('Disabled Reason') 1517 result = cs.services.disable_log_reason(args.host, args.binary, 1518 args.reason) 1519 else: 1520 result = cs.services.disable(args.host, args.binary) 1521 utils.print_list([result], columns) 1522 1523 1524def treeizeAvailabilityZone(zone): 1525 """Builds a tree view for availability zones.""" 1526 AvailabilityZone = availability_zones.AvailabilityZone 1527 1528 az = AvailabilityZone(zone.manager, 1529 copy.deepcopy(zone._info), zone._loaded) 1530 result = [] 1531 1532 # Zone tree view item 1533 az.zoneName = zone.zoneName 1534 az.zoneState = ('available' 1535 if zone.zoneState['available'] else 'not available') 1536 az._info['zoneName'] = az.zoneName 1537 az._info['zoneState'] = az.zoneState 1538 result.append(az) 1539 1540 if getattr(zone, "hosts", None) and zone.hosts is not None: 1541 for (host, services) in zone.hosts.items(): 1542 # Host tree view item 1543 az = AvailabilityZone(zone.manager, 1544 copy.deepcopy(zone._info), zone._loaded) 1545 az.zoneName = '|- %s' % host 1546 az.zoneState = '' 1547 az._info['zoneName'] = az.zoneName 1548 az._info['zoneState'] = az.zoneState 1549 result.append(az) 1550 1551 for (svc, state) in services.items(): 1552 # Service tree view item 1553 az = AvailabilityZone(zone.manager, 1554 copy.deepcopy(zone._info), zone._loaded) 1555 az.zoneName = '| |- %s' % svc 1556 az.zoneState = '%s %s %s' % ( 1557 'enabled' if state['active'] else 'disabled', 1558 ':-)' if state['available'] else 'XXX', 1559 state['updated_at']) 1560 az._info['zoneName'] = az.zoneName 1561 az._info['zoneState'] = az.zoneState 1562 result.append(az) 1563 return result 1564 1565 1566def do_availability_zone_list(cs, _args): 1567 """Lists all availability zones.""" 1568 try: 1569 availability_zones = cs.availability_zones.list() 1570 except exceptions.Forbidden: # policy doesn't allow probably 1571 try: 1572 availability_zones = cs.availability_zones.list(detailed=False) 1573 except Exception: 1574 raise 1575 1576 result = [] 1577 for zone in availability_zones: 1578 result += treeizeAvailabilityZone(zone) 1579 shell_utils.translate_availability_zone_keys(result) 1580 utils.print_list(result, ['Name', 'Status']) 1581 1582 1583def do_encryption_type_list(cs, args): 1584 """Shows encryption type details for volume types. Admin only.""" 1585 result = cs.volume_encryption_types.list() 1586 utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher', 1587 'Key Size', 'Control Location']) 1588 1589 1590@utils.arg('volume_type', 1591 metavar='<volume_type>', 1592 type=str, 1593 help='Name or ID of volume type.') 1594def do_encryption_type_show(cs, args): 1595 """Shows encryption type details for a volume type. Admin only.""" 1596 volume_type = shell_utils.find_volume_type(cs, args.volume_type) 1597 1598 result = cs.volume_encryption_types.get(volume_type) 1599 1600 # Display result or an empty table if no result 1601 if hasattr(result, 'volume_type_id'): 1602 shell_utils.print_volume_encryption_type_list([result]) 1603 else: 1604 shell_utils.print_volume_encryption_type_list([]) 1605 1606 1607@utils.arg('volume_type', 1608 metavar='<volume_type>', 1609 type=str, 1610 help='Name or ID of volume type.') 1611@utils.arg('provider', 1612 metavar='<provider>', 1613 type=str, 1614 help='The encryption provider format. ' 1615 'For example, "luks" or "plain".') 1616@utils.arg('--cipher', 1617 metavar='<cipher>', 1618 type=str, 1619 required=False, 1620 default=None, 1621 help='The encryption algorithm or mode. ' 1622 'For example, aes-xts-plain64. Default=None.') 1623@utils.arg('--key-size', 1624 metavar='<key_size>', 1625 type=int, 1626 required=False, 1627 default=None, 1628 help='Size of encryption key, in bits. ' 1629 'For example, 128 or 256. Default=None.') 1630@utils.arg('--key_size', 1631 type=int, 1632 required=False, 1633 default=None, 1634 help=argparse.SUPPRESS) 1635@utils.arg('--control-location', 1636 metavar='<control_location>', 1637 choices=['front-end', 'back-end'], 1638 type=str, 1639 required=False, 1640 default='front-end', 1641 help='Notional service where encryption is performed. ' 1642 'Valid values are "front-end" or "back-end". ' 1643 'For example, front-end=Nova. Default is "front-end".') 1644@utils.arg('--control_location', 1645 type=str, 1646 required=False, 1647 default='front-end', 1648 help=argparse.SUPPRESS) 1649def do_encryption_type_create(cs, args): 1650 """Creates encryption type for a volume type. Admin only.""" 1651 volume_type = shell_utils.find_volume_type(cs, args.volume_type) 1652 1653 body = { 1654 'provider': args.provider, 1655 'cipher': args.cipher, 1656 'key_size': args.key_size, 1657 'control_location': args.control_location 1658 } 1659 1660 result = cs.volume_encryption_types.create(volume_type, body) 1661 shell_utils.print_volume_encryption_type_list([result]) 1662 1663 1664@utils.arg('volume_type', 1665 metavar='<volume-type>', 1666 type=str, 1667 help="Name or ID of the volume type") 1668@utils.arg('--provider', 1669 metavar='<provider>', 1670 type=str, 1671 required=False, 1672 default=argparse.SUPPRESS, 1673 help="Encryption provider format (e.g. 'luks' or 'plain').") 1674@utils.arg('--cipher', 1675 metavar='<cipher>', 1676 type=str, 1677 nargs='?', 1678 required=False, 1679 default=argparse.SUPPRESS, 1680 const=None, 1681 help="Encryption algorithm/mode to use (e.g., aes-xts-plain64). " 1682 "Provide parameter without value to set to provider default.") 1683@utils.arg('--key-size', 1684 dest='key_size', 1685 metavar='<key-size>', 1686 type=int, 1687 nargs='?', 1688 required=False, 1689 default=argparse.SUPPRESS, 1690 const=None, 1691 help="Size of the encryption key, in bits (e.g., 128, 256). " 1692 "Provide parameter without value to set to provider default. ") 1693@utils.arg('--control-location', 1694 dest='control_location', 1695 metavar='<control-location>', 1696 choices=['front-end', 'back-end'], 1697 type=str, 1698 required=False, 1699 default=argparse.SUPPRESS, 1700 help="Notional service where encryption is performed (e.g., " 1701 "front-end=Nova). Values: 'front-end', 'back-end'") 1702def do_encryption_type_update(cs, args): 1703 """Update encryption type information for a volume type (Admin Only).""" 1704 volume_type = shell_utils.find_volume_type(cs, args.volume_type) 1705 1706 # An argument should only be pulled if the user specified the parameter. 1707 body = {} 1708 for attr in ['provider', 'cipher', 'key_size', 'control_location']: 1709 if hasattr(args, attr): 1710 body[attr] = getattr(args, attr) 1711 1712 cs.volume_encryption_types.update(volume_type, body) 1713 result = cs.volume_encryption_types.get(volume_type) 1714 shell_utils.print_volume_encryption_type_list([result]) 1715 1716 1717@utils.arg('volume_type', 1718 metavar='<volume_type>', 1719 type=str, 1720 help='Name or ID of volume type.') 1721def do_encryption_type_delete(cs, args): 1722 """Deletes encryption type for a volume type. Admin only.""" 1723 volume_type = shell_utils.find_volume_type(cs, args.volume_type) 1724 cs.volume_encryption_types.delete(volume_type) 1725 1726 1727@utils.arg('name', 1728 metavar='<name>', 1729 help='Name of new QoS specifications.') 1730@utils.arg('metadata', 1731 metavar='<key=value>', 1732 nargs='+', 1733 default=[], 1734 help='QoS specifications.') 1735def do_qos_create(cs, args): 1736 """Creates a qos specs.""" 1737 keypair = None 1738 if args.metadata is not None: 1739 keypair = shell_utils.extract_metadata(args) 1740 qos_specs = cs.qos_specs.create(args.name, keypair) 1741 shell_utils.print_qos_specs(qos_specs) 1742 1743 1744def do_qos_list(cs, args): 1745 """Lists qos specs.""" 1746 qos_specs = cs.qos_specs.list() 1747 shell_utils.print_qos_specs_list(qos_specs) 1748 1749 1750@utils.arg('qos_specs', metavar='<qos_specs>', 1751 help='ID of QoS specifications to show.') 1752def do_qos_show(cs, args): 1753 """Shows qos specs details.""" 1754 qos_specs = shell_utils.find_qos_specs(cs, args.qos_specs) 1755 shell_utils.print_qos_specs(qos_specs) 1756 1757 1758@utils.arg('qos_specs', metavar='<qos_specs>', 1759 help='ID of QoS specifications to delete.') 1760@utils.arg('--force', 1761 metavar='<True|False>', 1762 const=True, 1763 nargs='?', 1764 default=False, 1765 help='Enables or disables deletion of in-use ' 1766 'QoS specifications. Default=False.') 1767def do_qos_delete(cs, args): 1768 """Deletes a specified qos specs.""" 1769 force = strutils.bool_from_string(args.force, 1770 strict=True) 1771 qos_specs = shell_utils.find_qos_specs(cs, args.qos_specs) 1772 cs.qos_specs.delete(qos_specs, force) 1773 1774 1775@utils.arg('qos_specs', metavar='<qos_specs>', 1776 help='ID of QoS specifications.') 1777@utils.arg('vol_type_id', metavar='<volume_type_id>', 1778 help='ID of volume type with which to associate ' 1779 'QoS specifications.') 1780def do_qos_associate(cs, args): 1781 """Associates qos specs with specified volume type.""" 1782 cs.qos_specs.associate(args.qos_specs, args.vol_type_id) 1783 1784 1785@utils.arg('qos_specs', metavar='<qos_specs>', 1786 help='ID of QoS specifications.') 1787@utils.arg('vol_type_id', metavar='<volume_type_id>', 1788 help='ID of volume type with which to associate ' 1789 'QoS specifications.') 1790def do_qos_disassociate(cs, args): 1791 """Disassociates qos specs from specified volume type.""" 1792 cs.qos_specs.disassociate(args.qos_specs, args.vol_type_id) 1793 1794 1795@utils.arg('qos_specs', metavar='<qos_specs>', 1796 help='ID of QoS specifications on which to operate.') 1797def do_qos_disassociate_all(cs, args): 1798 """Disassociates qos specs from all its associations.""" 1799 cs.qos_specs.disassociate_all(args.qos_specs) 1800 1801 1802@utils.arg('qos_specs', metavar='<qos_specs>', 1803 help='ID of QoS specifications.') 1804@utils.arg('action', 1805 metavar='<action>', 1806 choices=['set', 'unset'], 1807 help='The action. Valid values are "set" or "unset."') 1808@utils.arg('metadata', metavar='key=value', 1809 nargs='+', 1810 default=[], 1811 help='Metadata key and value pair to set or unset. ' 1812 'For unset, specify only the key.') 1813def do_qos_key(cs, args): 1814 """Sets or unsets specifications for a qos spec.""" 1815 keypair = shell_utils.extract_metadata(args) 1816 1817 if args.action == 'set': 1818 cs.qos_specs.set_keys(args.qos_specs, keypair) 1819 elif args.action == 'unset': 1820 cs.qos_specs.unset_keys(args.qos_specs, list(keypair)) 1821 1822 1823@utils.arg('qos_specs', metavar='<qos_specs>', 1824 help='ID of QoS specifications.') 1825def do_qos_get_association(cs, args): 1826 """Lists all associations for specified qos specs.""" 1827 associations = cs.qos_specs.get_associations(args.qos_specs) 1828 shell_utils.print_associations_list(associations) 1829 1830 1831@utils.arg('snapshot', 1832 metavar='<snapshot>', 1833 help='ID of snapshot for which to update metadata.') 1834@utils.arg('action', 1835 metavar='<action>', 1836 choices=['set', 'unset'], 1837 help='The action. Valid values are "set" or "unset."') 1838@utils.arg('metadata', 1839 metavar='<key=value>', 1840 nargs='+', 1841 default=[], 1842 help='Metadata key and value pair to set or unset. ' 1843 'For unset, specify only the key.') 1844def do_snapshot_metadata(cs, args): 1845 """Sets or deletes snapshot metadata.""" 1846 snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) 1847 metadata = shell_utils.extract_metadata(args) 1848 1849 if args.action == 'set': 1850 metadata = snapshot.set_metadata(metadata) 1851 utils.print_dict(metadata._info) 1852 elif args.action == 'unset': 1853 snapshot.delete_metadata(list(metadata.keys())) 1854 1855 1856@utils.arg('snapshot', metavar='<snapshot>', 1857 help='ID of snapshot.') 1858def do_snapshot_metadata_show(cs, args): 1859 """Shows snapshot metadata.""" 1860 snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) 1861 utils.print_dict(snapshot._info['metadata'], 'Metadata-property') 1862 1863 1864@utils.arg('volume', metavar='<volume>', 1865 help='ID of volume.') 1866def do_metadata_show(cs, args): 1867 """Shows volume metadata.""" 1868 volume = utils.find_volume(cs, args.volume) 1869 utils.print_dict(volume._info['metadata'], 'Metadata-property') 1870 1871 1872@utils.arg('volume', metavar='<volume>', 1873 help='ID of volume.') 1874def do_image_metadata_show(cs, args): 1875 """Shows volume image metadata.""" 1876 volume = utils.find_volume(cs, args.volume) 1877 resp, body = volume.show_image_metadata(volume) 1878 utils.print_dict(body['metadata'], 'Metadata-property') 1879 1880 1881@utils.arg('volume', 1882 metavar='<volume>', 1883 help='ID of volume for which to update metadata.') 1884@utils.arg('metadata', 1885 metavar='<key=value>', 1886 nargs='+', 1887 default=[], 1888 help='Metadata key and value pair or pairs to update.') 1889def do_metadata_update_all(cs, args): 1890 """Updates volume metadata.""" 1891 volume = utils.find_volume(cs, args.volume) 1892 metadata = shell_utils.extract_metadata(args) 1893 metadata = volume.update_all_metadata(metadata) 1894 utils.print_dict(metadata['metadata'], 'Metadata-property') 1895 1896 1897@utils.arg('snapshot', 1898 metavar='<snapshot>', 1899 help='ID of snapshot for which to update metadata.') 1900@utils.arg('metadata', 1901 metavar='<key=value>', 1902 nargs='+', 1903 default=[], 1904 help='Metadata key and value pair to update.') 1905def do_snapshot_metadata_update_all(cs, args): 1906 """Updates snapshot metadata.""" 1907 snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) 1908 metadata = shell_utils.extract_metadata(args) 1909 metadata = snapshot.update_all_metadata(metadata) 1910 utils.print_dict(metadata) 1911 1912 1913@utils.arg('volume', metavar='<volume>', help='ID of volume to update.') 1914@utils.arg('read_only', 1915 metavar='<True|true|False|false>', 1916 choices=['True', 'true', 'False', 'false'], 1917 help='Enables or disables update of volume to ' 1918 'read-only access mode.') 1919def do_readonly_mode_update(cs, args): 1920 """Updates volume read-only access-mode flag.""" 1921 volume = utils.find_volume(cs, args.volume) 1922 cs.volumes.update_readonly_flag(volume, 1923 strutils.bool_from_string(args.read_only, 1924 strict=True)) 1925 1926 1927@utils.arg('volume', metavar='<volume>', help='ID of the volume to update.') 1928@utils.arg('bootable', 1929 metavar='<True|true|False|false>', 1930 choices=['True', 'true', 'False', 'false'], 1931 help='Flag to indicate whether volume is bootable.') 1932def do_set_bootable(cs, args): 1933 """Update bootable status of a volume.""" 1934 volume = utils.find_volume(cs, args.volume) 1935 cs.volumes.set_bootable(volume, 1936 strutils.bool_from_string(args.bootable, 1937 strict=True)) 1938 1939 1940@utils.arg('host', 1941 metavar='<host>', 1942 help='Cinder host on which the existing volume resides; ' 1943 'takes the form: host@backend-name#pool') 1944@utils.arg('identifier', 1945 metavar='<identifier>', 1946 help='Name or other Identifier for existing volume') 1947@utils.arg('--id-type', 1948 metavar='<id-type>', 1949 default='source-name', 1950 help='Type of backend device identifier provided, ' 1951 'typically source-name or source-id (Default=source-name)') 1952@utils.arg('--name', 1953 metavar='<name>', 1954 help='Volume name (Default=None)') 1955@utils.arg('--description', 1956 metavar='<description>', 1957 help='Volume description (Default=None)') 1958@utils.arg('--volume-type', 1959 metavar='<volume-type>', 1960 help='Volume type (Default=None)') 1961@utils.arg('--availability-zone', 1962 metavar='<availability-zone>', 1963 help='Availability zone for volume (Default=None)') 1964@utils.arg('--metadata', 1965 nargs='*', 1966 metavar='<key=value>', 1967 help='Metadata key=value pairs (Default=None)') 1968@utils.arg('--bootable', 1969 action='store_true', 1970 help='Specifies that the newly created volume should be' 1971 ' marked as bootable') 1972def do_manage(cs, args): 1973 """Manage an existing volume.""" 1974 volume_metadata = None 1975 if args.metadata is not None: 1976 volume_metadata = shell_utils.extract_metadata(args) 1977 1978 # Build a dictionary of key/value pairs to pass to the API. 1979 ref_dict = {args.id_type: args.identifier} 1980 1981 # The recommended way to specify an existing volume is by ID or name, and 1982 # have the Cinder driver look for 'source-name' or 'source-id' elements in 1983 # the ref structure. To make things easier for the user, we have special 1984 # --source-name and --source-id CLI options that add the appropriate 1985 # element to the ref structure. 1986 # 1987 # Note how argparse converts hyphens to underscores. We use hyphens in the 1988 # dictionary so that it is consistent with what the user specified on the 1989 # CLI. 1990 1991 if hasattr(args, 'source_name') and args.source_name is not None: 1992 ref_dict['source-name'] = args.source_name 1993 if hasattr(args, 'source_id') and args.source_id is not None: 1994 ref_dict['source-id'] = args.source_id 1995 1996 volume = cs.volumes.manage(host=args.host, 1997 ref=ref_dict, 1998 name=args.name, 1999 description=args.description, 2000 volume_type=args.volume_type, 2001 availability_zone=args.availability_zone, 2002 metadata=volume_metadata, 2003 bootable=args.bootable) 2004 2005 info = {} 2006 volume = cs.volumes.get(volume.id) 2007 info.update(volume._info) 2008 info.pop('links', None) 2009 utils.print_dict(info) 2010 2011 2012@utils.arg('volume', metavar='<volume>', 2013 help='Name or ID of the volume to unmanage.') 2014def do_unmanage(cs, args): 2015 """Stop managing a volume.""" 2016 volume = utils.find_volume(cs, args.volume) 2017 cs.volumes.unmanage(volume.id) 2018 2019 2020@utils.arg('--all-tenants', 2021 dest='all_tenants', 2022 metavar='<0|1>', 2023 nargs='?', 2024 type=int, 2025 const=1, 2026 default=0, 2027 help='Shows details for all tenants. Admin only.') 2028def do_consisgroup_list(cs, args): 2029 """Lists all consistency groups.""" 2030 consistencygroups = cs.consistencygroups.list() 2031 2032 columns = ['ID', 'Status', 'Name'] 2033 utils.print_list(consistencygroups, columns) 2034 2035 2036@utils.arg('consistencygroup', 2037 metavar='<consistencygroup>', 2038 help='Name or ID of a consistency group.') 2039def do_consisgroup_show(cs, args): 2040 """Shows details of a consistency group.""" 2041 info = dict() 2042 consistencygroup = shell_utils.find_consistencygroup(cs, 2043 args.consistencygroup) 2044 info.update(consistencygroup._info) 2045 2046 info.pop('links', None) 2047 utils.print_dict(info) 2048 2049 2050@utils.arg('volumetypes', 2051 metavar='<volume-types>', 2052 help='Volume types.') 2053@utils.arg('--name', 2054 metavar='<name>', 2055 help='Name of a consistency group.') 2056@utils.arg('--description', 2057 metavar='<description>', 2058 default=None, 2059 help='Description of a consistency group. Default=None.') 2060@utils.arg('--availability-zone', 2061 metavar='<availability-zone>', 2062 default=None, 2063 help='Availability zone for volume. Default=None.') 2064def do_consisgroup_create(cs, args): 2065 """Creates a consistency group.""" 2066 2067 consistencygroup = cs.consistencygroups.create( 2068 args.volumetypes, 2069 args.name, 2070 args.description, 2071 availability_zone=args.availability_zone) 2072 2073 info = dict() 2074 consistencygroup = cs.consistencygroups.get(consistencygroup.id) 2075 info.update(consistencygroup._info) 2076 2077 info.pop('links', None) 2078 utils.print_dict(info) 2079 2080 2081@utils.arg('--cgsnapshot', 2082 metavar='<cgsnapshot>', 2083 help='Name or ID of a cgsnapshot. Default=None.') 2084@utils.arg('--source-cg', 2085 metavar='<source-cg>', 2086 help='Name or ID of a source CG. Default=None.') 2087@utils.arg('--name', 2088 metavar='<name>', 2089 help='Name of a consistency group. Default=None.') 2090@utils.arg('--description', 2091 metavar='<description>', 2092 help='Description of a consistency group. Default=None.') 2093def do_consisgroup_create_from_src(cs, args): 2094 """Creates a consistency group from a cgsnapshot or a source CG.""" 2095 if not args.cgsnapshot and not args.source_cg: 2096 msg = ('Cannot create consistency group because neither ' 2097 'cgsnapshot nor source CG is provided.') 2098 raise exceptions.ClientException(code=1, message=msg) 2099 if args.cgsnapshot and args.source_cg: 2100 msg = ('Cannot create consistency group because both ' 2101 'cgsnapshot and source CG are provided.') 2102 raise exceptions.ClientException(code=1, message=msg) 2103 cgsnapshot = None 2104 if args.cgsnapshot: 2105 cgsnapshot = shell_utils.find_cgsnapshot(cs, args.cgsnapshot) 2106 source_cg = None 2107 if args.source_cg: 2108 source_cg = shell_utils.find_consistencygroup(cs, args.source_cg) 2109 info = cs.consistencygroups.create_from_src( 2110 cgsnapshot.id if cgsnapshot else None, 2111 source_cg.id if source_cg else None, 2112 args.name, 2113 args.description) 2114 2115 info.pop('links', None) 2116 utils.print_dict(info) 2117 2118 2119@utils.arg('consistencygroup', 2120 metavar='<consistencygroup>', nargs='+', 2121 help='Name or ID of one or more consistency groups ' 2122 'to be deleted.') 2123@utils.arg('--force', 2124 action='store_true', 2125 default=False, 2126 help='Allows or disallows consistency groups ' 2127 'to be deleted. If the consistency group is empty, ' 2128 'it can be deleted without the force flag. ' 2129 'If the consistency group is not empty, the force ' 2130 'flag is required for it to be deleted.') 2131def do_consisgroup_delete(cs, args): 2132 """Removes one or more consistency groups.""" 2133 failure_count = 0 2134 for consistencygroup in args.consistencygroup: 2135 try: 2136 shell_utils.find_consistencygroup( 2137 cs, consistencygroup).delete(args.force) 2138 except Exception as e: 2139 failure_count += 1 2140 print("Delete for consistency group %s failed: %s" % 2141 (consistencygroup, e)) 2142 if failure_count == len(args.consistencygroup): 2143 raise exceptions.CommandError("Unable to delete any of the specified " 2144 "consistency groups.") 2145 2146 2147@utils.arg('consistencygroup', 2148 metavar='<consistencygroup>', 2149 help='Name or ID of a consistency group.') 2150@utils.arg('--name', metavar='<name>', 2151 help='New name for consistency group. Default=None.') 2152@utils.arg('--description', metavar='<description>', 2153 help='New description for consistency group. Default=None.') 2154@utils.arg('--add-volumes', 2155 metavar='<uuid1,uuid2,......>', 2156 help='UUID of one or more volumes ' 2157 'to be added to the consistency group, ' 2158 'separated by commas. Default=None.') 2159@utils.arg('--remove-volumes', 2160 metavar='<uuid3,uuid4,......>', 2161 help='UUID of one or more volumes ' 2162 'to be removed from the consistency group, ' 2163 'separated by commas. Default=None.') 2164def do_consisgroup_update(cs, args): 2165 """Updates a consistency group.""" 2166 kwargs = {} 2167 2168 if args.name is not None: 2169 kwargs['name'] = args.name 2170 2171 if args.description is not None: 2172 kwargs['description'] = args.description 2173 2174 if args.add_volumes is not None: 2175 kwargs['add_volumes'] = args.add_volumes 2176 2177 if args.remove_volumes is not None: 2178 kwargs['remove_volumes'] = args.remove_volumes 2179 2180 if not kwargs: 2181 msg = ('At least one of the following args must be supplied: ' 2182 'name, description, add-volumes, remove-volumes.') 2183 raise exceptions.ClientException(code=1, message=msg) 2184 2185 shell_utils.find_consistencygroup( 2186 cs, args.consistencygroup).update(**kwargs) 2187 print("Request to update consistency group '%s' has been accepted." % ( 2188 args.consistencygroup)) 2189 2190 2191@utils.arg('--all-tenants', 2192 dest='all_tenants', 2193 metavar='<0|1>', 2194 nargs='?', 2195 type=int, 2196 const=1, 2197 default=0, 2198 help='Shows details for all tenants. Admin only.') 2199@utils.arg('--status', 2200 metavar='<status>', 2201 default=None, 2202 help='Filters results by a status. Default=None.') 2203@utils.arg('--consistencygroup-id', 2204 metavar='<consistencygroup_id>', 2205 default=None, 2206 help='Filters results by a consistency group ID. Default=None.') 2207def do_cgsnapshot_list(cs, args): 2208 """Lists all cgsnapshots.""" 2209 2210 all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) 2211 2212 search_opts = { 2213 'all_tenants': all_tenants, 2214 'status': args.status, 2215 'consistencygroup_id': args.consistencygroup_id, 2216 } 2217 2218 cgsnapshots = cs.cgsnapshots.list(search_opts=search_opts) 2219 2220 columns = ['ID', 'Status', 'Name'] 2221 utils.print_list(cgsnapshots, columns) 2222 2223 2224@utils.arg('cgsnapshot', 2225 metavar='<cgsnapshot>', 2226 help='Name or ID of cgsnapshot.') 2227def do_cgsnapshot_show(cs, args): 2228 """Shows cgsnapshot details.""" 2229 info = dict() 2230 cgsnapshot = shell_utils.find_cgsnapshot(cs, args.cgsnapshot) 2231 info.update(cgsnapshot._info) 2232 2233 info.pop('links', None) 2234 utils.print_dict(info) 2235 2236 2237@utils.arg('consistencygroup', 2238 metavar='<consistencygroup>', 2239 help='Name or ID of a consistency group.') 2240@utils.arg('--name', 2241 metavar='<name>', 2242 default=None, 2243 help='Cgsnapshot name. Default=None.') 2244@utils.arg('--description', 2245 metavar='<description>', 2246 default=None, 2247 help='Cgsnapshot description. Default=None.') 2248def do_cgsnapshot_create(cs, args): 2249 """Creates a cgsnapshot.""" 2250 consistencygroup = shell_utils.find_consistencygroup(cs, 2251 args.consistencygroup) 2252 cgsnapshot = cs.cgsnapshots.create( 2253 consistencygroup.id, 2254 args.name, 2255 args.description) 2256 2257 info = dict() 2258 cgsnapshot = cs.cgsnapshots.get(cgsnapshot.id) 2259 info.update(cgsnapshot._info) 2260 2261 info.pop('links', None) 2262 utils.print_dict(info) 2263 2264 2265@utils.arg('cgsnapshot', 2266 metavar='<cgsnapshot>', nargs='+', 2267 help='Name or ID of one or more cgsnapshots to be deleted.') 2268def do_cgsnapshot_delete(cs, args): 2269 """Removes one or more cgsnapshots.""" 2270 failure_count = 0 2271 for cgsnapshot in args.cgsnapshot: 2272 try: 2273 shell_utils.find_cgsnapshot(cs, cgsnapshot).delete() 2274 except Exception as e: 2275 failure_count += 1 2276 print("Delete for cgsnapshot %s failed: %s" % (cgsnapshot, e)) 2277 if failure_count == len(args.cgsnapshot): 2278 raise exceptions.CommandError("Unable to delete any of the specified " 2279 "cgsnapshots.") 2280 2281 2282@utils.arg('--detail', 2283 action='store_true', 2284 help='Show detailed information about pools.') 2285def do_get_pools(cs, args): 2286 """Show pool information for backends. Admin only.""" 2287 pools = cs.volumes.get_pools(args.detail) 2288 infos = dict() 2289 infos.update(pools._info) 2290 2291 for info in infos['pools']: 2292 backend = dict() 2293 backend['name'] = info['name'] 2294 if args.detail: 2295 backend.update(info['capabilities']) 2296 utils.print_dict(backend) 2297 2298 2299@utils.arg('host', 2300 metavar='<host>', 2301 help='Cinder host to show backend volume stats and properties; ' 2302 'takes the form: host@backend-name') 2303def do_get_capabilities(cs, args): 2304 """Show backend volume stats and properties. Admin only.""" 2305 2306 capabilities = cs.capabilities.get(args.host) 2307 infos = dict() 2308 infos.update(capabilities._info) 2309 2310 prop = infos.pop('properties', None) 2311 utils.print_dict(infos, "Volume stats") 2312 utils.print_dict(prop, "Backend properties", 2313 formatters=sorted(prop.keys())) 2314 2315 2316@utils.arg('volume', 2317 metavar='<volume>', 2318 help='Cinder volume that already exists in the volume backend.') 2319@utils.arg('identifier', 2320 metavar='<identifier>', 2321 help='Name or other identifier for existing snapshot. This is ' 2322 'backend specific.') 2323@utils.arg('--id-type', 2324 metavar='<id-type>', 2325 default='source-name', 2326 help='Type of backend device identifier provided, ' 2327 'typically source-name or source-id (Default=source-name).') 2328@utils.arg('--name', 2329 metavar='<name>', 2330 help='Snapshot name (Default=None).') 2331@utils.arg('--description', 2332 metavar='<description>', 2333 help='Snapshot description (Default=None).') 2334@utils.arg('--metadata', 2335 nargs='*', 2336 metavar='<key=value>', 2337 help='Metadata key=value pairs (Default=None).') 2338def do_snapshot_manage(cs, args): 2339 """Manage an existing snapshot.""" 2340 snapshot_metadata = None 2341 if args.metadata is not None: 2342 snapshot_metadata = shell_utils.extract_metadata(args) 2343 2344 # Build a dictionary of key/value pairs to pass to the API. 2345 ref_dict = {args.id_type: args.identifier} 2346 2347 if hasattr(args, 'source_name') and args.source_name is not None: 2348 ref_dict['source-name'] = args.source_name 2349 if hasattr(args, 'source_id') and args.source_id is not None: 2350 ref_dict['source-id'] = args.source_id 2351 2352 volume = utils.find_volume(cs, args.volume) 2353 snapshot = cs.volume_snapshots.manage(volume_id=volume.id, 2354 ref=ref_dict, 2355 name=args.name, 2356 description=args.description, 2357 metadata=snapshot_metadata) 2358 2359 info = {} 2360 snapshot = cs.volume_snapshots.get(snapshot.id) 2361 info.update(snapshot._info) 2362 info.pop('links', None) 2363 utils.print_dict(info) 2364 2365 2366@utils.arg('snapshot', metavar='<snapshot>', 2367 help='Name or ID of the snapshot to unmanage.') 2368def do_snapshot_unmanage(cs, args): 2369 """Stop managing a snapshot.""" 2370 snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) 2371 cs.volume_snapshots.unmanage(snapshot.id) 2372 2373 2374@utils.arg('host', metavar='<hostname>', help='Host name.') 2375def do_freeze_host(cs, args): 2376 """Freeze and disable the specified cinder-volume host.""" 2377 cs.services.freeze_host(args.host) 2378 2379 2380@utils.arg('host', metavar='<hostname>', help='Host name.') 2381def do_thaw_host(cs, args): 2382 """Thaw and enable the specified cinder-volume host.""" 2383 cs.services.thaw_host(args.host) 2384 2385 2386@utils.arg('host', metavar='<hostname>', help='Host name.') 2387@utils.arg('--backend_id', 2388 metavar='<backend-id>', 2389 help='ID of backend to failover to (Default=None)') 2390def do_failover_host(cs, args): 2391 """Failover a replicating cinder-volume host.""" 2392 cs.services.failover_host(args.host, args.backend_id) 2393 2394 2395@utils.arg('host', 2396 metavar='<host>', 2397 help='Cinder host on which to list manageable volumes; ' 2398 'takes the form: host@backend-name#pool') 2399@utils.arg('--detailed', 2400 metavar='<detailed>', 2401 default=True, 2402 help='Returned detailed information (default true).') 2403@utils.arg('--marker', 2404 metavar='<marker>', 2405 default=None, 2406 help='Begin returning volumes that appear later in the volume ' 2407 'list than that represented by this volume id. ' 2408 'Default=None.') 2409@utils.arg('--limit', 2410 metavar='<limit>', 2411 default=None, 2412 help='Maximum number of volumes to return. Default=None.') 2413@utils.arg('--offset', 2414 metavar='<offset>', 2415 default=None, 2416 help='Number of volumes to skip after marker. Default=None.') 2417@utils.arg('--sort', 2418 metavar='<key>[:<direction>]', 2419 default=None, 2420 help=(('Comma-separated list of sort keys and directions in the ' 2421 'form of <key>[:<asc|desc>]. ' 2422 'Valid keys: %s. ' 2423 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) 2424def do_manageable_list(cs, args): 2425 """Lists all manageable volumes.""" 2426 detailed = strutils.bool_from_string(args.detailed) 2427 volumes = cs.volumes.list_manageable(host=args.host, detailed=detailed, 2428 marker=args.marker, limit=args.limit, 2429 offset=args.offset, sort=args.sort) 2430 columns = ['reference', 'size', 'safe_to_manage'] 2431 if detailed: 2432 columns.extend(['reason_not_safe', 'cinder_id', 'extra_info']) 2433 utils.print_list(volumes, columns, sortby_index=None) 2434 2435 2436@utils.arg('host', 2437 metavar='<host>', 2438 help='Cinder host on which to list manageable snapshots; ' 2439 'takes the form: host@backend-name#pool') 2440@utils.arg('--detailed', 2441 metavar='<detailed>', 2442 default=True, 2443 help='Returned detailed information (default true).') 2444@utils.arg('--marker', 2445 metavar='<marker>', 2446 default=None, 2447 help='Begin returning snapshots that appear later in the snapshot ' 2448 'list than that represented by this snapshot id. ' 2449 'Default=None.') 2450@utils.arg('--limit', 2451 metavar='<limit>', 2452 default=None, 2453 help='Maximum number of snapshots to return. Default=None.') 2454@utils.arg('--offset', 2455 metavar='<offset>', 2456 default=None, 2457 help='Number of snapshots to skip after marker. Default=None.') 2458@utils.arg('--sort', 2459 metavar='<key>[:<direction>]', 2460 default=None, 2461 help=(('Comma-separated list of sort keys and directions in the ' 2462 'form of <key>[:<asc|desc>]. ' 2463 'Valid keys: %s. ' 2464 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) 2465def do_snapshot_manageable_list(cs, args): 2466 """Lists all manageable snapshots.""" 2467 detailed = strutils.bool_from_string(args.detailed) 2468 snapshots = cs.volume_snapshots.list_manageable(host=args.host, 2469 detailed=detailed, 2470 marker=args.marker, 2471 limit=args.limit, 2472 offset=args.offset, 2473 sort=args.sort) 2474 columns = ['reference', 'size', 'safe_to_manage', 'source_reference'] 2475 if detailed: 2476 columns.extend(['reason_not_safe', 'cinder_id', 'extra_info']) 2477 utils.print_list(snapshots, columns, sortby_index=None) 2478