Add support for backup of volume boot nova instance

Currently, nova backup only support image type
instance. When using 'freezer-agent --action backup
--engine nova --nova-inst-id xxx --mode nova
--no-incremental true' to backup instance that boot
from volume or snapshot, it gives us the result of
successful backup. But when we restore and launch
the instance from the backup data, it will fail
with 'no boot device error' message. This patch
add support for backup of volume boot nova
instance and will fix the issue.

Change-Id: Ibd87087c5f631fc47357395e9ed7ca23b7844a51
Closes-Bug: #1685763
This commit is contained in:
Pengju Jiao 2017-04-27 14:41:32 +08:00
parent a27d0e844e
commit 95292d10ef
4 changed files with 54 additions and 112 deletions

View File

@ -202,28 +202,54 @@ class NovaEngine(engine.BackupEngine):
)
image = self.glance.images.get(image_id)
image_block_mapping_info = image.get("block_device_mapping")
image_block_mapping = json.loads(image_block_mapping_info) \
if image_block_mapping_info else None
image_temporary_snapshot_id = \
image_block_mapping[0]['snapshot_id'] \
if image_block_mapping else None
stream = self.client.download_image(image)
LOG.info("Uploading image to swift")
image_temporary_snapshot_id = None
copied_volume = None
image_info = getattr(server, "image", None)
if image_info is not None and isinstance(image_info, dict):
LOG.info('Image type instance backup')
boot_device_type = "image"
stream = self.client.download_image(image)
else:
LOG.info('Volume or snapshot type instance backup')
boot_device_type = "volume"
image_block_mapping_info = image.get("block_device_mapping")
image_block_mapping = json.loads(image_block_mapping_info) \
if image_block_mapping_info else None
image_temporary_snapshot_id = \
image_block_mapping[0]['snapshot_id'] \
if image_block_mapping else None
copied_volume = self.client.do_copy_volume(
self.cinder.volume_snapshots.get(
image_temporary_snapshot_id))
LOG.debug("Deleting temporary glance image "
"generated by snapshot")
self.glance.images.delete(image.id)
LOG.debug("Creation temporary glance image")
image = self.client.make_glance_image(
copied_volume.id, copied_volume)
LOG.debug("Download temporary glance image {0}".format(image.id))
stream = self.client.download_image(image)
LOG.info("Uploading image to storage path")
headers = {"server_name": server.name,
"flavour_id": str(server.flavor.get('id')),
'length': str(len(stream))}
'length': str(len(stream)),
"boot_device_type": boot_device_type}
self.set_tenant_meta(manifest_path, headers)
for chunk in stream:
yield chunk
LOG.info("Deleting temporary image {0}".format(image.id))
self.glance.images.delete(image.id)
if image_temporary_snapshot_id is not None:
LOG.info("Deleting temporary snapshot {0}"
.format(image_temporary_snapshot_id))
self.cinder.volume_snapshots.delete(image_temporary_snapshot_id)
if copied_volume is not None:
LOG.info("Deleting temporary copied volume {0}"
.format(copied_volume.id))
self.cinder.volumes.delete(copied_volume)
LOG.info("Deleting temporary image {0}".format(image.id))
self.glance.images.delete(image.id)
@staticmethod
def image_active(glance_client, image_id):

View File

@ -40,59 +40,6 @@ class BackupOs(object):
self.container = container
self.storage = storage
def backup_nova(self, instance_id):
"""
Implement nova backup
:param instance_id: Id of the instance for backup
:return:
"""
instance_id = instance_id
client_manager = self.client_manager
nova = client_manager.get_nova()
instance = nova.servers.get(instance_id)
glance = client_manager.get_glance()
def instance_finish_task():
instance = nova.servers.get(instance_id)
return not instance.__dict__['OS-EXT-STS:task_state']
utils.wait_for(
instance_finish_task, 1, CONF.timeout,
message="Waiting for instance {0} to finish {1} to start the "
"snapshot process".format(
instance_id,
instance.__dict__['OS-EXT-STS:task_state']
)
)
instance = nova.servers.get(instance)
image_id = nova.servers.create_image(instance,
"snapshot_of_%s" % instance_id)
image = glance.images.get(image_id)
def image_active():
image = glance.images.get(image_id)
return image.status == 'active'
utils.wait_for(image_active, 1, CONF.timeout,
message="Waiting for instance {0} snapshot {1} to "
"become active".format(instance_id, image_id))
try:
image = glance.images.get(image_id)
except Exception as e:
LOG.error(e)
stream = client_manager.download_image(image)
package = "{0}/{1}".format(instance_id, utils.DateTime.now().timestamp)
LOG.info("Saving image to {0}".format(self.storage.type))
headers = {"x-object-meta-name": instance.name,
"x-object-meta-flavor-id": str(instance.flavor.get('id')),
'x-object-meta-length': str(len(stream))}
self.storage.add_stream(stream, package, headers)
LOG.info("Deleting temporary image {0}".format(image))
glance.images.delete(image.id)
def backup_cinder_by_glance(self, volume_id):
"""
Implements cinder backup:

View File

@ -139,7 +139,6 @@ class RestoreOs(object):
:param restore_from_timestamp:
:return:
"""
backup = None
cinder = self.client_manager.get_cinder()
search_opts = {
'volume_id': volume_id,
@ -208,49 +207,3 @@ class RestoreOs(object):
LOG.info("Deleting temporary image {}".format(image.id))
self.client_manager.get_glance().images.delete(image.id)
def restore_nova(self, instance_id, restore_from_timestamp,
nova_network=None):
"""
:param restore_from_timestamp:
:type restore_from_timestamp: int
:param instance_id: id of attached nova instance
:param nova_network: id of network
:return:
"""
# TODO(yangyapeng): remove nova_network check use nova api,
# nova api list network is not accurate.
# Change validation use neutron api instead of nova api in
# a project, find all available network in restore nova.
# implementation it after tenant backup add get_neutron in
# openstack oslient.
nova = self.client_manager.get_nova()
(info, image) = self._create_image(instance_id, restore_from_timestamp)
flavor = nova.flavors.get(info['x-object-meta-flavor-id'])
LOG.info("Creating an instance")
instance = None
if nova_network:
nics_id = [nic.id for nic in nova.networks.findall()]
if nova_network not in nics_id:
raise Exception("The network %s is invalid" % nova_network)
instance = nova.servers.create(info['x-object-meta-name'],
image, flavor,
nics=[{'net-id': nova_network}])
else:
try:
instance = nova.servers.create(info['x-object-meta-name'],
image, flavor)
except Exception as e:
LOG.warn(e)
raise Exception("The parameter --nova-restore-network "
"is required")
# loop and wait till the server is up then remove the image
# let's wait 100 second
LOG.info('Delete instance image from glance {0}'.format(image))
for i in range(0, 360):
time.sleep(10)
instance = nova.servers.get(instance)
if not instance.__dict__['OS-EXT-STS:task_state']:
glance = self.client_manager.create_glance()
glance.images.delete(image.id)
return

View File

@ -0,0 +1,16 @@
---
prelude: >
Currently, when using 'freezer-agent --action backup --engine nova
--nova-inst-id xxx --mode nova --no-incremental true' to backup instance
that boot from volume or snapshot, it gives us the result of successful
backup. But when we restore the nova instance from the backup data and
launch the restored instance, it will fail with 'no boot device error'
message. This can be an issue.
fixes:
- |
With the above issue, freezer can not support the backup and restore of
instance that boot from volume or snapshot correctly. With this fix, when
using backup, freezer will create an image from the volume, and then
store the image data to storage media. After this fix, users can backup
and restore the nova instance no matter what type of the instance is.