summaryrefslogtreecommitdiffstats
path: root/nova/virt/hyperv/snapshotops.py
blob: c75b54e9e0d68e3b9dbedbb55810f3fbf2b10992 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# 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 oslo.config import cfg

from nova.compute import task_states
from nova.image import glance
from nova.openstack.common import log as logging
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import vhdutils
from nova.virt.hyperv import vmutils

CONF = cfg.CONF
LOG = logging.getLogger(__name__)


class SnapshotOps(object):
    def __init__(self):
        self._pathutils = pathutils.PathUtils()
        self._vmutils = vmutils.VMUtils()
        self._vhdutils = vhdutils.VHDUtils()

    def _save_glance_image(self, context, name, image_vhd_path):
        (glance_image_service,
         image_id) = glance.get_remote_image_service(context, name)
        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, name, 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.get_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 %(name)s with content from "
                        "merged disk %(image_vhd_path)s"),
                      {'image_id': name, '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, name, image_vhd_path)

            LOG.debug(_("Snapshot image %(name)s updated for VM "
                        "%(instance_name)s"),
                      {'name': name, 'instance_name': instance_name})
        finally:
            try:
                LOG.debug(_("Removing snapshot %s"), name)
                self._vmutils.remove_vm_snapshot(snapshot_path)
            except Exception as ex:
                LOG.exception(ex)
                LOG.warning(_('Failed to remove snapshot for VM %s')
                            % instance_name)
            if export_dir:
                LOG.debug(_('Removing directory: %s'), export_dir)
                self._pathutils.rmtree(export_dir)