# Copyright 2013 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import os import sys import time from manilaclient import exceptions from manilaclient import utils def _poll_for_status(poll_fn, obj_id, action, final_ok_states, poll_period=5, show_progress=True): """Block while action is performed, periodically printing progress.""" def print_progress(progress): if show_progress: msg = ('\rInstance %(action)s... %(progress)s%% complete' % dict(action=action, progress=progress)) else: msg = '\rInstance %(action)s...' % dict(action=action) sys.stdout.write(msg) sys.stdout.flush() print while True: obj = poll_fn(obj_id) status = obj.status.lower() progress = getattr(obj, 'progress', None) or 0 if status in final_ok_states: print_progress(100) print "\nFinished" break elif status == "error": print "\nError %(action)s instance" % locals() break else: print_progress(progress) time.sleep(poll_period) def _find_volume(cs, volume): """Get a volume by ID.""" return utils.find_resource(cs.volumes, volume) def _find_volume_snapshot(cs, snapshot): """Get a volume snapshot by ID.""" return utils.find_resource(cs.volume_snapshots, snapshot) def _find_backup(cs, backup): """Get a backup by ID.""" return utils.find_resource(cs.backups, backup) def _print_volume_snapshot(snapshot): utils.print_dict(snapshot._info) def _find_share(cs, share): """Get a share by ID.""" return utils.find_resource(cs.shares, share) def _print_share(cs, share): info = share._info.copy() info.pop('links') utils.print_dict(info) def _find_share_snapshot(cs, snapshot): """Get a snapshot by ID.""" return utils.find_resource(cs.share_snapshots, snapshot) def _print_share_snapshot(cs, snapshot): info = snapshot._info.copy() info.pop('links') utils.print_dict(info) def _translate_keys(collection, convert): for item in collection: keys = item.__dict__.keys() for from_key, to_key in convert: if from_key in keys and to_key not in keys: setattr(item, to_key, item._info[from_key]) def _translate_volume_keys(collection): convert = [('volumeType', 'volume_type')] _translate_keys(collection, convert) def _translate_volume_snapshot_keys(collection): convert = [('volumeId', 'volume_id')] _translate_keys(collection, convert) def _extract_metadata(args): metadata = {} for metadatum in args.metadata[0]: # unset doesn't require a val, so we have the if/else if '=' in metadatum: (key, value) = metadatum.split('=', 1) else: key = metadatum value = None metadata[key] = value return metadata @utils.arg('--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg('--all_tenants', nargs='?', type=int, const=1, help=argparse.SUPPRESS) @utils.arg('--name', metavar='', default=None, help='Filter results by name') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--status', metavar='', default=None, help='Filter results by status') @utils.service_type('share') def do_list(cs, args): """List all the volumes.""" # NOTE(thingee): Backwards-compatibility with v1 args if args.display_name is not None: args.name = args.display_name all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, 'name': args.name, 'status': args.status, } volumes = cs.volumes.list(search_opts=search_opts) _translate_volume_keys(volumes) # Create a list of servers to which the volume is attached for vol in volumes: servers = [s.get('server_id') for s in vol.attachments] setattr(vol, 'attached_to', ','.join(map(str, servers))) utils.print_list(volumes, ['ID', 'Status', 'Name', 'Size', 'Volume Type', 'Bootable', 'Attached to']) @utils.arg('volume', metavar='', help='ID of the volume.') @utils.service_type('share') def do_show(cs, args): """Show details about a volume.""" info = dict() volume = _find_volume(cs, args.volume) info.update(volume._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.arg('size', metavar='', type=int, help='Size of volume in GB') @utils.arg('--snapshot-id', metavar='', default=None, help='Create volume from snapshot id (Optional, Default=None)') @utils.arg('--snapshot_id', help=argparse.SUPPRESS) @utils.arg('--source-volid', metavar='', default=None, help='Create volume from volume id (Optional, Default=None)') @utils.arg('--source_volid', help=argparse.SUPPRESS) @utils.arg('--image-id', metavar='', default=None, help='Create volume from image id (Optional, Default=None)') @utils.arg('--image_id', help=argparse.SUPPRESS) @utils.arg('--name', metavar='', default=None, help='Volume name (Optional, Default=None)') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', help=argparse.SUPPRESS) @utils.arg('--description', metavar='', default=None, help='Volume description (Optional, Default=None)') @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.arg('--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.arg('--volume_type', help=argparse.SUPPRESS) @utils.arg('--availability-zone', metavar='', default=None, help='Availability zone for volume (Optional, Default=None)') @utils.arg('--availability_zone', help=argparse.SUPPRESS) @utils.arg('--metadata', type=str, nargs='*', metavar='', help='Metadata key=value pairs (Optional, Default=None)', default=None) @utils.service_type('share') def do_create(cs, args): """Add a new volume.""" # NOTE(thingee): Backwards-compatibility with v1 args if args.display_name is not None: args.name = args.display_name if args.display_description is not None: args.description = args.display_description volume_metadata = None if args.metadata is not None: volume_metadata = _extract_metadata(args) volume = cs.volumes.create(args.size, args.snapshot_id, args.source_volid, args.name, args.description, args.volume_type, availability_zone=args.availability_zone, imageRef=args.image_id, metadata=volume_metadata) info = dict() volume = cs.volumes.get(info['id']) info.update(volume._info) info.pop('links') utils.print_dict(info) @utils.arg('volume', metavar='', help='ID of the volume to delete.') @utils.service_type('share') def do_delete(cs, args): """Remove a volume.""" volume = _find_volume(cs, args.volume) volume.delete() @utils.arg('volume', metavar='', help='ID of the volume to delete.') @utils.service_type('share') def do_force_delete(cs, args): """Attempt forced removal of a volume, regardless of its state.""" volume = _find_volume(cs, args.volume) volume.force_delete() @utils.arg('volume', metavar='', help='ID of the volume to rename.') @utils.arg('name', nargs='?', metavar='', help='New name for the volume.') @utils.arg('--description', metavar='', help='Optional volume description. (Default=None)', default=None) @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.service_type('share') def do_rename(cs, args): """Rename a volume.""" kwargs = {} if args.name is not None: kwargs['name'] = args.name if args.display_description is not None: kwargs['description'] = args.display_description elif args.description is not None: kwargs['description'] = args.description _find_volume(cs, args.volume).update(**kwargs) @utils.arg('volume', metavar='', help='ID of the volume to update metadata on.') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', action='append', default=[], help='Metadata to set/unset (only key is necessary on unset)') @utils.service_type('share') def do_metadata(cs, args): """Set or Delete metadata on a volume.""" volume = _find_volume(cs, args.volume) metadata = _extract_metadata(args) if args.action == 'set': cs.volumes.set_metadata(volume, metadata) elif args.action == 'unset': cs.volumes.delete_metadata(volume, metadata.keys()) @utils.arg('--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg('--all_tenants', nargs='?', type=int, const=1, help=argparse.SUPPRESS) @utils.arg('--name', metavar='', default=None, help='Filter results by name') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', help=argparse.SUPPRESS) @utils.arg('--status', metavar='', default=None, help='Filter results by status') @utils.arg('--volume-id', metavar='', default=None, help='Filter results by volume-id') @utils.arg('--volume_id', help=argparse.SUPPRESS) @utils.service_type('share') def do_snapshot_list(cs, args): """List all the snapshots.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) if args.display_name is not None: args.name = args.display_name search_opts = { 'all_tenants': all_tenants, 'display_name': args.name, 'status': args.status, 'volume_id': args.volume_id, } snapshots = cs.volume_snapshots.list(search_opts=search_opts) _translate_volume_snapshot_keys(snapshots) utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Name', 'Size']) @utils.arg('snapshot', metavar='', help='ID of the snapshot.') @utils.service_type('share') def do_snapshot_show(cs, args): """Show details about a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) _print_volume_snapshot(snapshot) @utils.arg('volume-id', metavar='', help='ID of the volume to snapshot') @utils.arg('--force', metavar='', help='Optional flag to indicate whether ' 'to snapshot a volume even if it\'s ' 'attached to an instance. (Default=False)', default=False) @utils.arg('--name', metavar='', default=None, help='Optional snapshot name. (Default=None)') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', help=argparse.SUPPRESS) @utils.arg('--description', metavar='', default=None, help='Optional snapshot description. (Default=None)') @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.service_type('share') def do_snapshot_create(cs, args): """Add a new snapshot.""" if args.display_name is not None: args.name = args.display_name if args.display_description is not None: args.description = args.display_description snapshot = cs.volume_snapshots.create(args.volume_id, args.force, args.name, args.description) _print_volume_snapshot(snapshot) @utils.arg('snapshot-id', metavar='', help='ID of the snapshot to delete.') @utils.service_type('share') def do_snapshot_delete(cs, args): """Remove a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot_id) snapshot.delete() @utils.arg('snapshot', metavar='', help='ID of the snapshot.') @utils.arg('name', nargs='?', metavar='', help='New name for the snapshot.') @utils.arg('--description', metavar='', help='Optional snapshot description. (Default=None)', default=None) @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.service_type('share') def do_snapshot_rename(cs, args): """Rename a snapshot.""" kwargs = {} if args.name is not None: kwargs['name'] = args.name if args.description is not None: kwargs['description'] = args.description elif args.display_description is not None: kwargs['description'] = args.display_description _find_volume_snapshot(cs, args.snapshot).update(**kwargs) def _print_volume_type_list(vtypes): utils.print_list(vtypes, ['ID', 'Name']) def _print_type_and_extra_specs_list(vtypes): formatters = {'extra_specs': _print_type_extra_specs} utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'], formatters) @utils.service_type('share') def do_type_list(cs, args): """Print a list of available 'volume types'.""" vtypes = cs.volume_types.list() _print_volume_type_list(vtypes) @utils.service_type('share') def do_extra_specs_list(cs, args): """Print a list of current 'volume types and extra specs' (Admin Only).""" vtypes = cs.volume_types.list() _print_type_and_extra_specs_list(vtypes) @utils.arg('name', metavar='', help="Name of the new volume type") @utils.service_type('share') def do_type_create(cs, args): """Create a new volume type.""" vtype = cs.volume_types.create(args.name) _print_volume_type_list([vtype]) @utils.arg('id', metavar='', help="Unique ID of the volume type to delete") @utils.service_type('share') def do_type_delete(cs, args): """Delete a specific volume type""" cs.volume_types.delete(args.id) @utils.arg('vtype', metavar='', help="Name or ID of the volume type") @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', action='append', default=[], help='Extra_specs to set/unset (only key is necessary on unset)') @utils.service_type('share') def do_type_key(cs, args): "Set or unset extra_spec for a volume type.""" vtype = _find_volume_type(cs, args.vtype) keypair = _extract_metadata(args) if args.action == 'set': vtype.set_keys(keypair) elif args.action == 'unset': vtype.unset_keys(keypair.keys()) def do_endpoints(cs, args): """Discover endpoints that get returned from the authenticate services""" catalog = cs.client.service_catalog.catalog for e in catalog['access']['serviceCatalog']: utils.print_dict(e['endpoints'][0], e['name']) def do_credentials(cs, args): """Show user credentials returned from auth""" catalog = cs.client.service_catalog.catalog utils.print_dict(catalog['access']['user'], "User Credentials") utils.print_dict(catalog['access']['token'], "Token") _quota_resources = ['volumes', 'snapshots', 'gigabytes'] def _quota_show(quotas): quota_dict = {} for resource in _quota_resources: quota_dict[resource] = getattr(quotas, resource, None) utils.print_dict(quota_dict) def _quota_update(manager, identifier, args): updates = {} for resource in _quota_resources: val = getattr(args, resource, None) if val is not None: updates[resource] = val if updates: manager.update(identifier, **updates) @utils.arg('tenant', metavar='', help='UUID of tenant to list the quotas for.') @utils.service_type('share') def do_quota_show(cs, args): """List the quotas for a tenant.""" _quota_show(cs.quotas.get(args.tenant)) @utils.arg('tenant', metavar='', help='UUID of tenant to list the default quotas for.') @utils.service_type('share') def do_quota_defaults(cs, args): """List the default quotas for a tenant.""" _quota_show(cs.quotas.defaults(args.tenant)) @utils.arg('tenant', metavar='', help='UUID of tenant to set the quotas for.') @utils.arg('--volumes', metavar='', type=int, default=None, help='New value for the "volumes" quota.') @utils.arg('--snapshots', metavar='', type=int, default=None, help='New value for the "snapshots" quota.') @utils.arg('--gigabytes', metavar='', type=int, default=None, help='New value for the "gigabytes" quota.') @utils.service_type('share') def do_quota_update(cs, args): """Update the quotas for a tenant.""" _quota_update(cs.quotas, args.tenant, args) @utils.arg('class_name', metavar='', help='Name of quota class to list the quotas for.') @utils.service_type('share') def do_quota_class_show(cs, args): """List the quotas for a quota class.""" _quota_show(cs.quota_classes.get(args.class_name)) @utils.arg('class-name', metavar='', help='Name of quota class to set the quotas for.') @utils.arg('--volumes', metavar='', type=int, default=None, help='New value for the "volumes" quota.') @utils.arg('--snapshots', metavar='', type=int, default=None, help='New value for the "snapshots" quota.') @utils.arg('--gigabytes', metavar='', type=int, default=None, help='New value for the "gigabytes" quota.') @utils.service_type('share') def do_quota_class_update(cs, args): """Update the quotas for a quota class.""" _quota_update(cs.quota_classes, args.class_name, args) @utils.service_type('share') def do_absolute_limits(cs, args): """Print a list of absolute limits for a user""" limits = cs.limits.get().absolute columns = ['Name', 'Value'] utils.print_list(limits, columns) @utils.service_type('share') def do_rate_limits(cs, args): """Print a list of rate limits for a user""" limits = cs.limits.get().rate columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] utils.print_list(limits, columns) def _print_type_extra_specs(vol_type): try: return vol_type.get_keys() except exceptions.NotFound: return "N/A" def _find_volume_type(cs, vtype): """Get a volume type by name or ID.""" return utils.find_resource(cs.volume_types, vtype) @utils.arg('volume-id', metavar='', help='ID of the volume to snapshot') @utils.arg('--force', metavar='', help='Optional flag to indicate whether ' 'to upload a volume even if it\'s ' 'attached to an instance. (Default=False)', default=False) @utils.arg('--container-format', metavar='', help='Optional type for container format ' '(Default=bare)', default='bare') @utils.arg('--container_format', help=argparse.SUPPRESS) @utils.arg('--disk-format', metavar='', help='Optional type for disk format ' '(Default=raw)', default='raw') @utils.arg('--disk_format', help=argparse.SUPPRESS) @utils.arg('image-name', metavar='', help='Name for created image') @utils.arg('--image_name', help=argparse.SUPPRESS) @utils.service_type('share') def do_upload_to_image(cs, args): """Upload volume to image service as image.""" volume = _find_volume(cs, args.volume_id) volume.upload_to_image(args.force, args.image_name, args.container_format, args.disk_format) @utils.arg('volume', metavar='', help='ID of the volume to backup.') @utils.arg('--container', metavar='', help='Optional backup container name. (Default=None)', default=None) @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--name', metavar='', help='Optional backup name. (Default=None)', default=None) @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--description', metavar='', default=None, help='Options backup description (Default=None)') @utils.service_type('share') def do_backup_create(cs, args): """Creates a backup.""" if args.display_name is not None: args.name = args.display_name if args.display_description is not None: args.description = args.display_description cs.backups.create(args.volume, args.container, args.name, args.description) @utils.arg('backup', metavar='', help='ID of the backup.') @utils.service_type('share') def do_backup_show(cs, args): """Show details about a backup.""" backup = _find_backup(cs, args.backup) info = dict() info.update(backup._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.service_type('share') def do_backup_list(cs, args): """List all the backups.""" backups = cs.backups.list() columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', 'Container'] utils.print_list(backups, columns) @utils.arg('backup', metavar='', help='ID of the backup to delete.') @utils.service_type('share') def do_backup_delete(cs, args): """Remove a backup.""" backup = _find_backup(cs, args.backup) backup.delete() @utils.arg('backup', metavar='', help='ID of the backup to restore.') @utils.arg('--volume-id', metavar='', help='Optional ID of the volume to restore to.', default=None) @utils.service_type('share') def do_backup_restore(cs, args): """Restore a backup.""" cs.restores.restore(args.backup, args.volume_id) @utils.arg( 'share_protocol', metavar='', type=str, help='Share type (NFS or CIFS)') @utils.arg( 'size', metavar='', type=int, help='Share size in GB') @utils.arg( '--snapshot-id', metavar='', help='Optional snapshot id to create the share from. (Default=None)', default=None) @utils.arg( '--name', metavar='', help='Optional share name. (Default=None)', default=None) @utils.arg( '--description', metavar='', help='Optional share description. (Default=None)', default=None) @utils.service_type('share') def do_share_create(cs, args): """Creates new NAS storage (NFS or CIFS).""" share = cs.shares.create(args.share_protocol, args.size, args.snapshot_id, args.name, args.description) _print_share(cs, share) @utils.arg( 'share', metavar='', help='ID of the NAS to delete.') @utils.service_type('share') def do_share_delete(cs, args): """Deletes NAS storage.""" cs.shares.delete(args.share) @utils.arg( 'share', metavar='', help='ID of the NAS share.') @utils.service_type('share') def do_share_show(cs, args): """Show details about a NAS share.""" share = _find_share(cs, args.share) _print_share(cs, share) @utils.arg( 'share', metavar='', help='ID of the NAS share to modify.') @utils.arg( 'access_type', metavar='', help='access rule type (only "ip" is supported).') @utils.arg( 'access_to', metavar='', help='Value that defines access') @utils.service_type('share') def do_share_allow(cs, args): """Allow access to the share.""" share = _find_share(cs, args.share) share.allow(args.access_type, args.access_to) @utils.arg( 'share', metavar='', help='ID of the NAS share to modify.') @utils.arg( 'id', metavar='', help='id of the access rule to be deleted.') @utils.service_type('share') def do_share_deny(cs, args): """Deny access to a share.""" share = _find_share(cs, args.share) share.deny(args.id) @utils.arg( 'share', metavar='', help='ID of the share.') @utils.service_type('share') def do_share_access_list(cs, args): """Show access list for share.""" share = _find_share(cs, args.share) access_list = share.access_list() utils.print_list(access_list, ['id', 'access type', 'access to', 'state']) @utils.arg( '--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg( '--name', metavar='', default=None, help='Filter results by name') @utils.arg( '--status', metavar='', default=None, help='Filter results by status') @utils.service_type('share') def do_share_list(cs, args): """List all NAS shares.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, 'name': args.name, 'status': args.status, } shares = cs.shares.list(search_opts=search_opts) utils.print_list(shares, ['ID', 'Name', 'Size', 'Share Proto', 'Status', 'Export location']) @utils.arg( '--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg( '--name', metavar='', default=None, help='Filter results by name') @utils.arg( '--status', metavar='', default=None, help='Filter results by status') @utils.arg( '--share-id', metavar='', default=None, help='Filter results by share-id') @utils.service_type('share') def do_share_snapshot_list(cs, args): """List all the snapshots.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, 'name': args.name, 'status': args.status, 'share_id': args.share_id, } snapshots = cs.share_snapshots.list(search_opts=search_opts) utils.print_list(snapshots, ['ID', 'Share ID', 'Status', 'Name', 'Share Size']) @utils.arg( 'snapshot', metavar='', help='ID of the snapshot.') @utils.service_type('share') def do_share_snapshot_show(cs, args): """Show details about a snapshot.""" snapshot = _find_share_snapshot(cs, args.snapshot) _print_share_snapshot(cs, snapshot) @utils.arg( 'share_id', metavar='', help='ID of the share to snapshot') @utils.arg( '--force', metavar='', help='Optional flag to indicate whether ' 'to snapshot a share even if it\'s busy.' ' (Default=False)', default=False) @utils.arg( '--name', metavar='', default=None, help='Optional snapshot name. (Default=None)') @utils.arg( '--description', metavar='', default=None, help='Optional snapshot description. (Default=None)') @utils.service_type('share') def do_share_snapshot_create(cs, args): """Add a new snapshot.""" snapshot = cs.share_snapshots.create(args.share_id, args.force, args.name, args.description) _print_share_snapshot(cs, snapshot) @utils.arg( 'snapshot_id', metavar='', help='ID of the snapshot to delete.') @utils.service_type('share') def do_share_snapshot_delete(cs, args): """Remove a snapshot.""" snapshot = _find_share_snapshot(cs, args.snapshot_id) snapshot.delete()