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