Volume.backup API attr name fixes

Creating backup requires "incremental" attribute, while GETs use
"is_incremental". Fix that and add functional test for adding
incremental backup.

Change-Id: I9a4951132645756e81a618d84482614acf69ec39
This commit is contained in:
Artem Goncharov 2020-04-02 17:09:24 +02:00
parent b403c7b9c6
commit fc3b3d09ef
5 changed files with 211 additions and 3 deletions

View File

@ -76,6 +76,64 @@ class Backup(resource.Resource):
#: The UUID of the volume.
volume_id = resource.Body("volume_id")
def create(self, session, prepend_key=True, base_path=None, **params):
"""Create a remote resource based on this instance.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param prepend_key: A boolean indicating whether the resource_key
should be prepended in a resource creation
request. Default to True.
:param str base_path: Base part of the URI for creating resources, if
different from
:data:`~openstack.resource.Resource.base_path`.
:param dict params: Additional params to pass.
:return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_create` is not set to ``True``.
"""
if not self.allow_create:
raise exceptions.MethodNotSupported(self, "create")
session = self._get_session(session)
microversion = self._get_microversion_for(session, 'create')
requires_id = (self.create_requires_id
if self.create_requires_id is not None
else self.create_method == 'PUT')
if self.create_exclude_id_from_body:
self._body._dirty.discard("id")
if self.create_method == 'POST':
request = self._prepare_request(requires_id=requires_id,
prepend_key=prepend_key,
base_path=base_path)
# NOTE(gtema) this is a funny example of when attribute
# is called "incremental" on create, "is_incremental" on get
# and use of "alias" or "aka" is not working for such conflict,
# since our preferred attr name is exactly "is_incremental"
body = request.body
if 'is_incremental' in body['backup']:
body['backup']['incremental'] = \
body['backup'].pop('is_incremental')
response = session.post(request.url,
json=request.body, headers=request.headers,
microversion=microversion, params=params)
else:
# Just for safety of the implementation (since PUT removed)
raise exceptions.ResourceFailure(
msg="Invalid create method: %s" % self.create_method)
has_body = (self.has_body if self.create_returns_body is None
else self.create_returns_body)
self.microversion = microversion
self._translate_response(response, has_body=has_body)
# direct comparision to False since we need to rule out None
if self.has_body and self.create_returns_body is False:
# fetch the body if it's required but not returned by create
return self.fetch(session)
return self
def restore(self, session, volume_id=None, name=None):
"""Restore current backup to volume

View File

@ -86,6 +86,64 @@ class Backup(resource.Resource):
#: The UUID of the volume.
volume_id = resource.Body("volume_id")
def create(self, session, prepend_key=True, base_path=None, **params):
"""Create a remote resource based on this instance.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param prepend_key: A boolean indicating whether the resource_key
should be prepended in a resource creation
request. Default to True.
:param str base_path: Base part of the URI for creating resources, if
different from
:data:`~openstack.resource.Resource.base_path`.
:param dict params: Additional params to pass.
:return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_create` is not set to ``True``.
"""
if not self.allow_create:
raise exceptions.MethodNotSupported(self, "create")
session = self._get_session(session)
microversion = self._get_microversion_for(session, 'create')
requires_id = (self.create_requires_id
if self.create_requires_id is not None
else self.create_method == 'PUT')
if self.create_exclude_id_from_body:
self._body._dirty.discard("id")
if self.create_method == 'POST':
request = self._prepare_request(requires_id=requires_id,
prepend_key=prepend_key,
base_path=base_path)
# NOTE(gtema) this is a funny example of when attribute
# is called "incremental" on create, "is_incremental" on get
# and use of "alias" or "aka" is not working for such conflict,
# since our preferred attr name is exactly "is_incremental"
body = request.body
if 'is_incremental' in body['backup']:
body['backup']['incremental'] = \
body['backup'].pop('is_incremental')
response = session.post(request.url,
json=request.body, headers=request.headers,
microversion=microversion, params=params)
else:
# Just for safety of the implementation (since PUT removed)
raise exceptions.ResourceFailure(
msg="Invalid create method: %s" % self.create_method)
has_body = (self.has_body if self.create_returns_body is None
else self.create_returns_body)
self.microversion = microversion
self._translate_response(response, has_body=has_body)
# direct comparision to False since we need to rule out None
if self.has_body and self.create_returns_body is False:
# fetch the body if it's required but not returned by create
return self.fetch(session)
return self
def restore(self, session, volume_id=None, name=None):
"""Restore current backup to volume

View File

@ -42,7 +42,8 @@ class TestBackup(base.BaseBlockStorageTest):
backup = self.user_cloud.block_storage.create_backup(
name=self.BACKUP_NAME,
volume_id=volume.id)
volume_id=volume.id,
is_incremental=False)
self.user_cloud.block_storage.wait_for_status(
backup,
status='available',
@ -66,3 +67,22 @@ class TestBackup(base.BaseBlockStorageTest):
def test_get(self):
sot = self.user_cloud.block_storage.get_backup(self.BACKUP_ID)
self.assertEqual(self.BACKUP_NAME, sot.name)
self.assertEqual(False, sot.is_incremental)
def test_create_incremental(self):
incremental_backup = self.user_cloud.block_storage.create_backup(
name=self.getUniqueString(),
volume_id=self.VOLUME_ID,
is_incremental=True)
self.user_cloud.block_storage.wait_for_status(
incremental_backup,
status='available',
failures=['error'],
interval=5,
wait=self._wait_for_timeout)
self.assertEqual(True, incremental_backup.is_incremental)
self.user_cloud.block_storage.delete_backup(
incremental_backup.id,
ignore_missing=False)
self.user_cloud.block_storage.wait_for_delete(
incremental_backup)

View File

@ -52,7 +52,7 @@ class TestBackup(base.TestCase):
self.sess = mock.Mock(spec=adapter.Adapter)
self.sess.get = mock.Mock()
self.sess.post = mock.Mock(return_value=self.resp)
self.sess.default_microversion = mock.Mock(return_value='')
self.sess.default_microversion = None
def test_basic(self):
sot = backup.Backup(BACKUP)
@ -98,6 +98,42 @@ class TestBackup(base.TestCase):
self.assertEqual(BACKUP["has_dependent_backups"],
sot.has_dependent_backups)
def test_create_incremental(self):
sot = backup.Backup(is_incremental=True)
sot2 = backup.Backup(is_incremental=False)
create_response = mock.Mock()
create_response.status_code = 200
create_response.json.return_value = {}
create_response.headers = {}
self.sess.post.return_value = create_response
sot.create(self.sess)
self.sess.post.assert_called_with(
'/backups',
headers={},
json={
'backup': {
'incremental': True,
}
},
microversion=None,
params={}
)
sot2.create(self.sess)
self.sess.post.assert_called_with(
'/backups',
headers={},
json={
'backup': {
'incremental': False,
}
},
microversion=None,
params={}
)
def test_restore(self):
sot = backup.Backup(**BACKUP)

View File

@ -55,7 +55,7 @@ class TestBackup(base.TestCase):
self.sess = mock.Mock(spec=adapter.Adapter)
self.sess.get = mock.Mock()
self.sess.post = mock.Mock(return_value=self.resp)
self.sess.default_microversion = mock.Mock(return_value='')
self.sess.default_microversion = None
def test_basic(self):
sot = backup.Backup(BACKUP)
@ -105,6 +105,42 @@ class TestBackup(base.TestCase):
self.assertEqual(BACKUP['metadata'], sot.metadata)
self.assertEqual(BACKUP['user_id'], sot.user_id)
def test_create_incremental(self):
sot = backup.Backup(is_incremental=True)
sot2 = backup.Backup(is_incremental=False)
create_response = mock.Mock()
create_response.status_code = 200
create_response.json.return_value = {}
create_response.headers = {}
self.sess.post.return_value = create_response
sot.create(self.sess)
self.sess.post.assert_called_with(
'/backups',
headers={},
json={
'backup': {
'incremental': True,
}
},
microversion=None,
params={}
)
sot2.create(self.sess)
self.sess.post.assert_called_with(
'/backups',
headers={},
json={
'backup': {
'incremental': False,
}
},
microversion=None,
params={}
)
def test_restore(self):
sot = backup.Backup(**BACKUP)