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:
parent
a27d0e844e
commit
95292d10ef
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue