Attach/Detach V2
Add an attachments API This includes a new attachment controller and shell commands. To use you'll need to set your api version `export OS_VOLUME_API_VERSION=3.27` Now you can do things like attach a volume (cinders part at least): `cinder attachment-create --connect True ......` List/show/delete existing attachments: `cinder attachment-list` `cinder attachment-show <attachment-id>` `cinder attachment-delete <attachemnt-id>` Change-Id: I2c463f0910b6c9e37502869b7ec33073f12939f1
This commit is contained in:
parent
4c61c6556b
commit
22c3693f8c
cinderclient/v3
68
cinderclient/v3/attachments.py
Normal file
68
cinderclient/v3/attachments.py
Normal file
@ -0,0 +1,68 @@
|
||||
# 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.
|
||||
|
||||
"""Attachment interface."""
|
||||
|
||||
from cinderclient import base
|
||||
|
||||
|
||||
class VolumeAttachment(base.Resource):
|
||||
"""An attachment is a connected volume."""
|
||||
def __repr__(self):
|
||||
"""Obj to Str method."""
|
||||
return "<Attachment: %s>" % self.id
|
||||
|
||||
|
||||
class VolumeAttachmentManager(base.ManagerWithFind):
|
||||
resource_class = VolumeAttachment
|
||||
|
||||
def create(self, volume_id, connector, instance_id):
|
||||
"""Create a attachment for specified volume."""
|
||||
body = {'attachment': {'volume_uuid': volume_id,
|
||||
'instance_uuid': instance_id,
|
||||
'connector': connector}}
|
||||
retval = self._create('/attachments', body, 'attachment')
|
||||
return retval.to_dict()
|
||||
|
||||
def delete(self, attachment):
|
||||
"""Delete an attachment by ID."""
|
||||
return self._delete("/attachments/%s" % base.getid(attachment))
|
||||
|
||||
def list(self, detailed=False, search_opts=None, marker=None, limit=None,
|
||||
sort_key=None, sort_dir=None, sort=None):
|
||||
"""List all attachments."""
|
||||
resource_type = "attachments"
|
||||
url = self._build_list_url(resource_type,
|
||||
detailed=detailed,
|
||||
search_opts=search_opts,
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir, sort=sort)
|
||||
return self._list(url, resource_type, limit=limit)
|
||||
|
||||
def show(self, id):
|
||||
"""Attachment show.
|
||||
|
||||
:param name: Attachment ID.
|
||||
"""
|
||||
url = '/attachments/%s' % id
|
||||
resp, body = self.api.client.get(url)
|
||||
return self.resource_class(self, body['attachment'], loaded=True,
|
||||
resp=resp)
|
||||
|
||||
def update(self, id, connector):
|
||||
"""Attachment update."""
|
||||
body = {'attachment': {'connector': connector}}
|
||||
resp = self._update('/attachments/%s' % id, body)
|
||||
return self.resource_class(self, resp['attachment'], loaded=True,
|
||||
resp=resp)
|
@ -17,6 +17,7 @@ import logging
|
||||
|
||||
from cinderclient import client
|
||||
from cinderclient import api_versions
|
||||
from cinderclient.v3 import attachments
|
||||
from cinderclient.v3 import availability_zones
|
||||
from cinderclient.v3 import cgsnapshots
|
||||
from cinderclient.v3 import clusters
|
||||
@ -71,7 +72,6 @@ class Client(object):
|
||||
self.limits = limits.LimitsManager(self)
|
||||
self.api_version = api_version or api_versions.APIVersion(self.version)
|
||||
|
||||
# extensions
|
||||
self.volumes = volumes.VolumeManager(self)
|
||||
self.volume_snapshots = volume_snapshots.SnapshotManager(self)
|
||||
self.volume_types = volume_types.VolumeTypeManager(self)
|
||||
@ -98,6 +98,8 @@ class Client(object):
|
||||
availability_zones.AvailabilityZoneManager(self)
|
||||
self.pools = pools.PoolManager(self)
|
||||
self.capabilities = capabilities.CapabilitiesManager(self)
|
||||
self.attachments = \
|
||||
attachments.VolumeAttachmentManager(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
|
@ -1181,7 +1181,6 @@ def do_message_delete(cs, args):
|
||||
raise exceptions.CommandError("Unable to delete any of the specified "
|
||||
"messages.")
|
||||
|
||||
|
||||
@utils.arg('--all-tenants',
|
||||
dest='all_tenants',
|
||||
metavar='<0|1>',
|
||||
@ -1276,3 +1275,205 @@ def do_snapshot_list(cs, args):
|
||||
utils.print_list(snapshots,
|
||||
['ID', 'Volume ID', 'Status', 'Name', 'Size'],
|
||||
sortby_index=sortby_index)
|
||||
|
||||
@api_versions.wraps('3.27')
|
||||
@utils.arg('--all-tenants',
|
||||
dest='all_tenants',
|
||||
metavar='<0|1>',
|
||||
nargs='?',
|
||||
type=int,
|
||||
const=1,
|
||||
default=0,
|
||||
help='Shows details for all tenants. Admin only.')
|
||||
@utils.arg('--volume-id',
|
||||
metavar='<volume-id>',
|
||||
default=None,
|
||||
help='Filters results by a volume ID. Default=None.')
|
||||
@utils.arg('--status',
|
||||
metavar='<status>',
|
||||
default=None,
|
||||
help='Filters results by a status. Default=None.')
|
||||
@utils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
help='Begin returning attachments that appear later in '
|
||||
'attachment list than that represented by this id. '
|
||||
'Default=None.')
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
default=None,
|
||||
help='Maximum number of attachemnts to return. Default=None.')
|
||||
@utils.arg('--sort',
|
||||
metavar='<key>[:<direction>]',
|
||||
default=None,
|
||||
help=(('Comma-separated list of sort keys and directions in the '
|
||||
'form of <key>[:<asc|desc>]. '
|
||||
'Valid keys: %s. '
|
||||
'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
|
||||
@utils.arg('--tenant',
|
||||
type=str,
|
||||
dest='tenant',
|
||||
nargs='?',
|
||||
metavar='<tenant>',
|
||||
help='Display information from single tenant (Admin only).')
|
||||
def do_attachment_list(cs, args):
|
||||
"""Lists all attachments."""
|
||||
search_opts = {
|
||||
'all_tenants': args.all_tenants,
|
||||
'status': args.status,
|
||||
'volume_id': args.volume_id,
|
||||
}
|
||||
|
||||
attachments = cs.attachments.list(search_opts=search_opts,
|
||||
marker=args.marker,
|
||||
limit=args.limit,
|
||||
sort=args.sort)
|
||||
columns = ['ID', 'Volume ID', 'Status', 'Instance']
|
||||
if args.sort:
|
||||
sortby_index = None
|
||||
else:
|
||||
sortby_index = 0
|
||||
utils.print_list(attachments, columns, sortby_index=sortby_index)
|
||||
|
||||
|
||||
@api_versions.wraps('3.27')
|
||||
@utils.arg('attachment',
|
||||
metavar='<attachment>',
|
||||
help='ID of attachment.')
|
||||
def do_attachment_show(cs, args):
|
||||
"""Show detailed information for attachment."""
|
||||
attachment = cs.attachments.show(args.attachment)
|
||||
attachment_dict = attachment.to_dict()
|
||||
connection_dict = attachment_dict.pop('connection_info', {})
|
||||
utils.print_dict(attachment_dict)
|
||||
|
||||
# TODO(jdg): Need to add checks here like admin/policy for displaying the
|
||||
# connection_info, this is still experimental so we'll leave it enabled for
|
||||
# now
|
||||
if connection_dict:
|
||||
utils.print_dict(connection_dict)
|
||||
|
||||
|
||||
@api_versions.wraps('3.27')
|
||||
@utils.arg('volume',
|
||||
metavar='<volume>',
|
||||
help='Name or ID of volume or volumes to attach.')
|
||||
@utils.arg('--instance',
|
||||
metavar='<instance>',
|
||||
default=None,
|
||||
help='UUID of Instance attaching to. Default=None.')
|
||||
@utils.arg('--connect',
|
||||
metavar='<connect>',
|
||||
default=False,
|
||||
help='Make an active connection using provided connector info '
|
||||
'(True or False).')
|
||||
@utils.arg('--initiator',
|
||||
metavar='<initiator>',
|
||||
default=None,
|
||||
help='iqn of the initiator attaching to. Default=None.')
|
||||
@utils.arg('--ip',
|
||||
metavar='<ip>',
|
||||
default=None,
|
||||
help='ip of the system attaching to. Default=None.')
|
||||
@utils.arg('--host',
|
||||
metavar='<host>',
|
||||
default=None,
|
||||
help='Name of the host attaching to. Default=None.')
|
||||
@utils.arg('--platform',
|
||||
metavar='<platform>',
|
||||
default='x86_64',
|
||||
help='Platform type. Default=x86_64.')
|
||||
@utils.arg('--ostype',
|
||||
metavar='<ostype>',
|
||||
default='linux2',
|
||||
help='OS type. Default=linux2.')
|
||||
@utils.arg('--multipath',
|
||||
metavar='<multipath>',
|
||||
default=False,
|
||||
help='OS type. Default=False.')
|
||||
@utils.arg('--mountpoint',
|
||||
metavar='<mountpoint>',
|
||||
default=None,
|
||||
help='Mountpoint volume will be attached at. Default=None.')
|
||||
def do_attachment_create(cs, args):
|
||||
"""Create an attachment for a cinder volume."""
|
||||
|
||||
connector = {}
|
||||
if strutils.bool_from_string(args.connect, strict=True):
|
||||
# FIXME(jdg): Add in all the options when they're finalized
|
||||
connector = {'initiator': args.initiator,
|
||||
'ip': args.ip,
|
||||
'platform': args.platform,
|
||||
'host': args.host,
|
||||
'os_type': args.ostype,
|
||||
'multipath': args.multipath}
|
||||
attachment = cs.attachments.create(args.volume,
|
||||
connector,
|
||||
args.instance)
|
||||
connector_dict = attachment.pop('connection_info', None)
|
||||
utils.print_dict(attachment)
|
||||
if connector_dict:
|
||||
utils.print_dict(connector_dict)
|
||||
|
||||
|
||||
@api_versions.wraps('3.27')
|
||||
@utils.arg('attachment',
|
||||
metavar='<attachment>',
|
||||
help='ID of attachment.')
|
||||
@utils.arg('--initiator',
|
||||
metavar='<initiator>',
|
||||
default=None,
|
||||
help='iqn of the initiator attaching to. Default=None.')
|
||||
@utils.arg('--ip',
|
||||
metavar='<ip>',
|
||||
default=None,
|
||||
help='ip of the system attaching to. Default=None.')
|
||||
@utils.arg('--host',
|
||||
metavar='<host>',
|
||||
default=None,
|
||||
help='Name of the host attaching to. Default=None.')
|
||||
@utils.arg('--platform',
|
||||
metavar='<platform>',
|
||||
default='x86_64',
|
||||
help='Platform type. Default=x86_64.')
|
||||
@utils.arg('--ostype',
|
||||
metavar='<ostype>',
|
||||
default='linux2',
|
||||
help='OS type. Default=linux2.')
|
||||
@utils.arg('--multipath',
|
||||
metavar='<multipath>',
|
||||
default=False,
|
||||
help='OS type. Default=False.')
|
||||
@utils.arg('--mountpoint',
|
||||
metavar='<mountpoint>',
|
||||
default=None,
|
||||
help='Mountpoint volume will be attached at. Default=None.')
|
||||
def do_attachment_update(cs, args):
|
||||
"""Update an attachment for a cinder volume.
|
||||
This call is designed to be more of an attachment completion than anything
|
||||
else. It expects the value of a connector object to notify the driver that
|
||||
the volume is going to be connected and where it's being connected to.
|
||||
"""
|
||||
connector = {'initiator': args.initiator,
|
||||
'ip': args.ip,
|
||||
'platform': args.platform,
|
||||
'host': args.host,
|
||||
'os_type': args.ostype,
|
||||
'multipath': args.multipath}
|
||||
attachment = cs.attachments.update(args.attachment,
|
||||
connector)
|
||||
attachment_dict = attachment.to_dict()
|
||||
connector_dict = attachment_dict.pop('connection_info', None)
|
||||
utils.print_dict(attachment_dict)
|
||||
if connector_dict:
|
||||
utils.print_dict(connector_dict)
|
||||
|
||||
|
||||
@api_versions.wraps('3.27')
|
||||
@utils.arg('attachment',
|
||||
metavar='<attachment>', nargs='+',
|
||||
help='ID of attachment or attachments to delete.')
|
||||
def do_attachment_delete(cs, args):
|
||||
"""Delete an attachment for a cinder volume."""
|
||||
for attachment in args.attachment:
|
||||
cs.attachments.delete(attachment)
|
||||
|
Loading…
x
Reference in New Issue
Block a user