diff --git a/freezer/engine/nova/nova.py b/freezer/engine/nova/nova.py index 297b1647..9aa8979b 100644 --- a/freezer/engine/nova/nova.py +++ b/freezer/engine/nova/nova.py @@ -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): diff --git a/freezer/openstack/backup.py b/freezer/openstack/backup.py index 381c828a..9683f49a 100644 --- a/freezer/openstack/backup.py +++ b/freezer/openstack/backup.py @@ -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: diff --git a/freezer/openstack/restore.py b/freezer/openstack/restore.py index 0fc04e40..e6b3d8fe 100644 --- a/freezer/openstack/restore.py +++ b/freezer/openstack/restore.py @@ -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 diff --git a/releasenotes/notes/volume-boot-nova-instance-backup-support-3c8d090370518f43.yaml b/releasenotes/notes/volume-boot-nova-instance-backup-support-3c8d090370518f43.yaml new file mode 100644 index 00000000..ab42c852 --- /dev/null +++ b/releasenotes/notes/volume-boot-nova-instance-backup-support-3c8d090370518f43.yaml @@ -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.