Transfer snapshots with volumes

This feature changes Cinder to transfer
snapshots with volumes at the same time by default.
If user doesn't want to transfer snapshots, they could use
a new optional argument '--no-snapshots' after microversion 3.55.
And we also introduce the new V3 api 'v3/volume_transfers'
to move this API out of contrib into Cinder V3 API.

The cinderclient patch: https://review.openstack.org/#/c/577611/

Change-Id: If848d131e5edcdb77d0b3c2ca45a99c4d5e14d1e
Implements: blueprint transfer-snps-with-vols
This commit is contained in:
wanghao
2018-01-15 16:46:13 +08:00
parent 9337f396ab
commit c0efaa1d46
27 changed files with 1014 additions and 28 deletions

View File

@@ -113,7 +113,7 @@ class API(base.Base):
auth_key = auth_key.encode('utf-8')
return hmac.new(salt, auth_key, hashlib.sha1).hexdigest()
def create(self, context, volume_id, display_name):
def create(self, context, volume_id, display_name, no_snapshots=False):
"""Creates an entry in the transfers table."""
LOG.info("Generating transfer record for volume %s", volume_id)
volume_ref = self.db.volume_get(context, volume_id)
@@ -124,6 +124,18 @@ class API(base.Base):
raise exception.InvalidVolume(
reason=_("transferring encrypted volume is not supported"))
if not no_snapshots:
snapshots = self.db.snapshot_get_all_for_volume(context, volume_id)
for snapshot in snapshots:
if snapshot['status'] != "available":
msg = _("snapshot: %s status must be "
"available") % snapshot['id']
raise exception.InvalidSnapshot(reason=msg)
if snapshot.get('encryption_key_id'):
msg = _("snapshot: %s encrypted snapshots cannot be "
"transferred") % snapshot['id']
raise exception.InvalidSnapshot(reason=msg)
volume_utils.notify_about_volume_usage(context, volume_ref,
"transfer.create.start")
# The salt is just a short random string.
@@ -136,7 +148,8 @@ class API(base.Base):
'display_name': display_name,
'salt': salt,
'crypt_hash': crypt_hash,
'expires_at': None}
'expires_at': None,
'no_snapshots': no_snapshots}
try:
transfer = self.db.transfer_create(context, transfer_rec)
@@ -149,7 +162,44 @@ class API(base.Base):
'volume_id': transfer['volume_id'],
'display_name': transfer['display_name'],
'auth_key': auth_key,
'created_at': transfer['created_at']}
'created_at': transfer['created_at'],
'no_snapshots': transfer['no_snapshots']}
def _handle_snapshot_quota(self, context, snapshots, volume_type_id,
donor_id):
snapshots_num = len(snapshots)
volume_sizes = 0
if not CONF.no_snapshot_gb_quota:
for snapshot in snapshots:
volume_sizes += snapshot.volume_size
try:
reserve_opts = {'snapshots': snapshots_num,
'gigabytes': volume_sizes}
QUOTAS.add_volume_type_opts(context,
reserve_opts,
volume_type_id)
reservations = QUOTAS.reserve(context, **reserve_opts)
except exception.OverQuota as e:
quota_utils.process_reserve_over_quota(
context, e,
resource='snapshots',
size=volume_sizes)
try:
reserve_opts = {'snapshots': -snapshots_num,
'gigabytes': -volume_sizes}
QUOTAS.add_volume_type_opts(context.elevated(),
reserve_opts,
volume_type_id)
donor_reservations = QUOTAS.reserve(context,
project_id=donor_id,
**reserve_opts)
except exception.OverQuota as e:
donor_reservations = None
LOG.exception("Failed to update volume providing snapshots quota:"
" Over quota.")
return reservations, donor_reservations
def accept(self, context, transfer_id, auth_key):
"""Accept a volume that has been offered for transfer."""
@@ -206,6 +256,15 @@ class API(base.Base):
LOG.exception("Failed to update quota donating volume"
" transfer id %s", transfer_id)
snap_res = None
snap_donor_res = None
if transfer['no_snapshots'] is False:
snapshots = objects.SnapshotList.get_all_for_volume(
context.elevated(), volume_id)
volume_type_id = vol_ref.volume_type_id
snap_res, snap_donor_res = self._handle_snapshot_quota(
context, snapshots, volume_type_id, vol_ref['project_id'])
volume_utils.notify_about_volume_usage(context, vol_ref,
"transfer.accept.start")
try:
@@ -214,21 +273,32 @@ class API(base.Base):
self.volume_api.accept_transfer(context,
vol_ref,
context.user_id,
context.project_id)
context.project_id,
transfer['no_snapshots'])
self.db.transfer_accept(context.elevated(),
transfer_id,
context.user_id,
context.project_id)
context.project_id,
transfer['no_snapshots'])
QUOTAS.commit(context, reservations)
if snap_res:
QUOTAS.commit(context, snap_res)
if donor_reservations:
QUOTAS.commit(context, donor_reservations, project_id=donor_id)
if snap_donor_res:
QUOTAS.commit(context, snap_donor_res, project_id=donor_id)
LOG.info("Volume %s has been transferred.", volume_id)
except Exception:
with excutils.save_and_reraise_exception():
QUOTAS.rollback(context, reservations)
if snap_res:
QUOTAS.rollback(context, snap_res)
if donor_reservations:
QUOTAS.rollback(context, donor_reservations,
project_id=donor_id)
if snap_donor_res:
QUOTAS.rollback(context, snap_donor_res,
project_id=donor_id)
vol_ref = self.db.volume_get(context, volume_id)
volume_utils.notify_about_volume_usage(context, vol_ref,