Support cross AZ backups
We currently limit backups to the same AZ of the volume while allowing volume restoration to any AZ. When having multiple AZs it is ideal to store your backups in a different AZ than the one where the source volumes are stored. This patch adds microversion 3.51 where we allow requesting the AZ where we want to store backups which will allow us to have as many backup AZs as volume AZs and do cross AZ backups for increased security, this feature also supports having an additional AZ just for backups or the less desirable solution where you store all your backups in a single AZ shared with some of the volumes. Change-Id: I595932276088d25abd464025c99dce33a2cc502b
This commit is contained in:
parent
015b105399
commit
5feaf74ccf
@ -252,6 +252,7 @@ Request
|
||||
- name: name_optional
|
||||
- snapshot_id: snapshot_id_3
|
||||
- metadata: metadata_9
|
||||
- availability_zone: availability_zone_4
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
|
@ -512,6 +512,13 @@ availability_zone_3:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
availability_zone_4:
|
||||
description: |
|
||||
The backup availability zone key value pair.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
min_version: 3.51
|
||||
backend_id:
|
||||
description: |
|
||||
ID of backend to failover to. Default is ``None``.
|
||||
|
@ -5,6 +5,7 @@
|
||||
"name": "backup001",
|
||||
"volume_id": "64f5d2fb-d836-4063-b7e2-544d5c1ff607",
|
||||
"incremental": true,
|
||||
"availability_zone": "AZ2",
|
||||
"metadata": null
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,8 @@ class BackupsController(wsgi.Controller):
|
||||
# - maybe also do validation of swift container name
|
||||
@wsgi.response(http_client.ACCEPTED)
|
||||
@validation.schema(backup.create, '2.0', '3.42')
|
||||
@validation.schema(backup.create_backup_v343, '3.43')
|
||||
@validation.schema(backup.create_backup_v343, '3.43', '3.50')
|
||||
@validation.schema(backup.create_backup_v351, mv.BACKUP_AZ)
|
||||
def create(self, req, body):
|
||||
"""Create a new backup."""
|
||||
LOG.debug('Creating new backup %s', body)
|
||||
@ -166,16 +167,24 @@ class BackupsController(wsgi.Controller):
|
||||
snapshot_id = backup.get('snapshot_id', None)
|
||||
metadata = backup.get('metadata', None) if req_version.matches(
|
||||
mv.BACKUP_METADATA) else None
|
||||
|
||||
if req_version.matches(mv.BACKUP_AZ):
|
||||
availability_zone = backup.get('availability_zone', None)
|
||||
else:
|
||||
availability_zone = None
|
||||
az_text = ' in az %s' % availability_zone if availability_zone else ''
|
||||
|
||||
LOG.info("Creating backup of volume %(volume_id)s in container"
|
||||
" %(container)s",
|
||||
{'volume_id': volume_id, 'container': container},
|
||||
" %(container)s%(az)s",
|
||||
{'volume_id': volume_id, 'container': container,
|
||||
'az': az_text},
|
||||
context=context)
|
||||
|
||||
try:
|
||||
new_backup = self.backup_api.create(context, name, description,
|
||||
volume_id, container,
|
||||
incremental, None, force,
|
||||
snapshot_id, metadata)
|
||||
incremental, availability_zone,
|
||||
force, snapshot_id, metadata)
|
||||
except (exception.InvalidVolume,
|
||||
exception.InvalidSnapshot,
|
||||
exception.InvalidVolumeMetadata,
|
||||
|
@ -139,6 +139,8 @@ BACKEND_STATE_REPORT = '3.49'
|
||||
|
||||
MULTIATTACH_VOLUMES = '3.50'
|
||||
|
||||
BACKUP_AZ = '3.51'
|
||||
|
||||
|
||||
def get_mv_header(version):
|
||||
"""Gets a formatted HTTP microversion header.
|
||||
|
@ -114,6 +114,7 @@ REST_API_VERSION_HISTORY = """
|
||||
* 3.48 - Add ``shared_targets`` and ``service_uuid`` fields to volume.
|
||||
* 3.49 - Support report backend storage state in service list.
|
||||
* 3.50 - Add multiattach capability
|
||||
* 3.51 - Add support for cross AZ backups.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -121,7 +122,7 @@ REST_API_VERSION_HISTORY = """
|
||||
# minimum version of the API supported.
|
||||
# Explicitly using /v2 endpoints will still work
|
||||
_MIN_API_VERSION = "3.0"
|
||||
_MAX_API_VERSION = "3.50"
|
||||
_MAX_API_VERSION = "3.51"
|
||||
_LEGACY_API_VERSION2 = "2.0"
|
||||
UPDATED = "2017-09-19T20:18:14Z"
|
||||
|
||||
|
@ -400,3 +400,7 @@ Support report backend storage state in service list.
|
||||
Services supporting this microversion are capable of volume multiattach.
|
||||
This version does not need to be requested when creating the volume, but can
|
||||
be used as a way to query if the capability exists in the Cinder service.
|
||||
|
||||
3.51
|
||||
----
|
||||
Add support for cross AZ backups.
|
||||
|
@ -51,6 +51,11 @@ create_backup_v343['properties']['backup']['properties'][
|
||||
'metadata'] = parameter_types.metadata_allows_null
|
||||
|
||||
|
||||
create_backup_v351 = copy.deepcopy(create_backup_v343)
|
||||
create_backup_v351['properties']['backup']['properties'][
|
||||
'availability_zone'] = parameter_types.nullable_string
|
||||
|
||||
|
||||
update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
|
@ -185,3 +185,8 @@ backup_url = {'type': 'string', 'minLength': 1, 'format': 'base64'}
|
||||
|
||||
|
||||
backup_service = {'type': 'string', 'minLength': 0, 'maxLength': 255}
|
||||
|
||||
|
||||
nullable_string = {
|
||||
'type': ('string', 'null'), 'minLength': 0, 'maxLength': 255
|
||||
}
|
||||
|
@ -227,8 +227,9 @@ class API(base.Base):
|
||||
|
||||
previous_status = volume['status']
|
||||
volume_host = volume_utils.extract_host(volume.host, 'host')
|
||||
host = self._get_available_backup_service_host(
|
||||
volume_host, volume.availability_zone)
|
||||
availability_zone = availability_zone or volume.availability_zone
|
||||
host = self._get_available_backup_service_host(volume_host,
|
||||
availability_zone)
|
||||
|
||||
# Reserve a quota before setting volume status and backup status
|
||||
try:
|
||||
@ -307,6 +308,7 @@ class API(base.Base):
|
||||
'parent_id': parent_id,
|
||||
'size': volume['size'],
|
||||
'host': host,
|
||||
'availability_zone': availability_zone,
|
||||
'snapshot_id': snapshot_id,
|
||||
'data_timestamp': data_timestamp,
|
||||
'metadata': metadata or {}
|
||||
|
@ -595,6 +595,38 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume.destroy()
|
||||
|
||||
@mock.patch('cinder.objects.Service.is_up', mock.Mock(return_value=True))
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
def test_create_backup_with_availability_zone(self, _mock_service_get_all):
|
||||
vol_az = 'az1'
|
||||
backup_svc_az = 'az2'
|
||||
_mock_service_get_all.return_value = [
|
||||
{'availability_zone': backup_svc_az, 'host': 'testhost',
|
||||
'disabled': 0, 'updated_at': timeutils.utcnow(),
|
||||
'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}]
|
||||
|
||||
volume = utils.create_volume(self.context, availability_zone=vol_az,
|
||||
size=1)
|
||||
# Create a backup with metadata
|
||||
body = {'backup': {'name': 'nightly001',
|
||||
'volume_id': volume.id,
|
||||
'container': 'nightlybackups',
|
||||
'availability_zone': backup_svc_az}}
|
||||
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
|
||||
req.method = 'POST'
|
||||
req.headers = mv.get_mv_header(mv.BACKUP_AZ)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_context))
|
||||
|
||||
self.assertEqual(202, res.status_code)
|
||||
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
backup = objects.Backup.get_by_id(self.context,
|
||||
res_dict['backup']['id'])
|
||||
self.assertEqual(backup_svc_az, backup.availability_zone)
|
||||
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
def test_create_backup_inuse_no_force(self,
|
||||
_mock_service_get_all):
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Cinder backup creation can now (since microversion 3.51) receive the
|
||||
availability zone where the backup should be stored.
|
Loading…
Reference in New Issue
Block a user