# Copyright 2012 Cloudbase Solutions Srl # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Management class for VM snapshot operations. """ import os from nova.compute import task_states from nova.image import glance from oslo_config import cfg from oslo_log import log as logging from hyperv.i18n import _LW from hyperv.nova import utilsfactory CONF = cfg.CONF LOG = logging.getLogger(__name__) class SnapshotOps(object): def __init__(self): self._pathutils = utilsfactory.get_pathutils() self._vmutils = utilsfactory.get_vmutils() self._vhdutils = utilsfactory.get_vhdutils() def _save_glance_image(self, context, image_id, image_vhd_path): (glance_image_service, image_id) = glance.get_remote_image_service(context, image_id) image_metadata = {"is_public": False, "disk_format": "vhd", "container_format": "bare", "properties": {}} with self._pathutils.open(image_vhd_path, 'rb') as f: glance_image_service.update(context, image_id, image_metadata, f) def snapshot(self, context, instance, image_id, update_task_state): """Create snapshot from a running VM instance.""" instance_name = instance.name LOG.debug("Creating snapshot for instance %s", instance_name) snapshot_path = self._vmutils.take_vm_snapshot(instance_name) update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) export_dir = None try: src_vhd_path = self._pathutils.lookup_root_vhd_path(instance_name) LOG.debug("Getting info for VHD %s", src_vhd_path) src_base_disk_path = self._vhdutils.get_vhd_parent_path( src_vhd_path) export_dir = self._pathutils.get_export_dir(instance_name) dest_vhd_path = os.path.join(export_dir, os.path.basename( src_vhd_path)) LOG.debug('Copying VHD %(src_vhd_path)s to %(dest_vhd_path)s', {'src_vhd_path': src_vhd_path, 'dest_vhd_path': dest_vhd_path}) self._pathutils.copyfile(src_vhd_path, dest_vhd_path) image_vhd_path = None if not src_base_disk_path: image_vhd_path = dest_vhd_path else: basename = os.path.basename(src_base_disk_path) dest_base_disk_path = os.path.join(export_dir, basename) LOG.debug('Copying base disk %(src_vhd_path)s to ' '%(dest_base_disk_path)s', {'src_vhd_path': src_vhd_path, 'dest_base_disk_path': dest_base_disk_path}) self._pathutils.copyfile(src_base_disk_path, dest_base_disk_path) LOG.debug("Reconnecting copied base VHD " "%(dest_base_disk_path)s and diff " "VHD %(dest_vhd_path)s", {'dest_base_disk_path': dest_base_disk_path, 'dest_vhd_path': dest_vhd_path}) self._vhdutils.reconnect_parent_vhd(dest_vhd_path, dest_base_disk_path) LOG.debug("Merging base disk %(dest_base_disk_path)s and " "diff disk %(dest_vhd_path)s", {'dest_base_disk_path': dest_base_disk_path, 'dest_vhd_path': dest_vhd_path}) self._vhdutils.merge_vhd(dest_vhd_path, dest_base_disk_path) image_vhd_path = dest_base_disk_path LOG.debug("Updating Glance image %(image_id)s with content from " "merged disk %(image_vhd_path)s", {'image_id': image_id, 'image_vhd_path': image_vhd_path}) update_task_state(task_state=task_states.IMAGE_UPLOADING, expected_state=task_states.IMAGE_PENDING_UPLOAD) self._save_glance_image(context, image_id, image_vhd_path) LOG.debug("Snapshot image %(image_id)s updated for VM " "%(instance_name)s", {'image_id': image_id, 'instance_name': instance_name}) finally: try: LOG.debug("Removing snapshot %s", image_id) self._vmutils.remove_vm_snapshot(snapshot_path) except Exception as ex: LOG.exception(ex) LOG.warning(_LW('Failed to remove snapshot for VM %s'), instance_name) if export_dir: LOG.debug('Removing directory: %s', export_dir) self._pathutils.rmtree(export_dir)