213 lines
7.6 KiB
Python
213 lines
7.6 KiB
Python
# Copyright 2013 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.
|
|
|
|
"""
|
|
Utility class for VHD related operations.
|
|
|
|
Official VHD format specs can be retrieved at:
|
|
http://technet.microsoft.com/en-us/library/bb676673.aspx
|
|
See "Download the Specifications Without Registering"
|
|
|
|
Official VHDX format specs can be retrieved at:
|
|
http://www.microsoft.com/en-us/download/details.aspx?id=34750
|
|
"""
|
|
import struct
|
|
import sys
|
|
|
|
if sys.platform == 'win32':
|
|
import wmi
|
|
|
|
from xml.etree import ElementTree
|
|
|
|
from nova.i18n import _
|
|
from nova.virt.hyperv import constants
|
|
from nova.virt.hyperv import vmutils
|
|
|
|
|
|
VHD_HEADER_SIZE_FIX = 512
|
|
VHD_BAT_ENTRY_SIZE = 4
|
|
VHD_DYNAMIC_DISK_HEADER_SIZE = 1024
|
|
VHD_HEADER_SIZE_DYNAMIC = 512
|
|
VHD_FOOTER_SIZE_DYNAMIC = 512
|
|
VHD_BLK_SIZE_OFFSET = 544
|
|
|
|
VHD_SIGNATURE = 'conectix'
|
|
VHDX_SIGNATURE = 'vhdxfile'
|
|
|
|
|
|
class VHDUtils(object):
|
|
|
|
def __init__(self):
|
|
self._vmutils = vmutils.VMUtils()
|
|
if sys.platform == 'win32':
|
|
self._conn = wmi.WMI(moniker='//./root/virtualization')
|
|
|
|
def validate_vhd(self, vhd_path):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
|
|
(job_path, ret_val) = image_man_svc.ValidateVirtualHardDisk(
|
|
Path=vhd_path)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
def create_dynamic_vhd(self, path, max_internal_size, format):
|
|
if format != constants.DISK_FORMAT_VHD:
|
|
raise vmutils.HyperVException(_("Unsupported disk format: %s") %
|
|
format)
|
|
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
|
|
(job_path, ret_val) = image_man_svc.CreateDynamicVirtualHardDisk(
|
|
Path=path, MaxInternalSize=max_internal_size)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
def create_differencing_vhd(self, path, parent_path):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
|
|
(job_path, ret_val) = image_man_svc.CreateDifferencingVirtualHardDisk(
|
|
Path=path, ParentPath=parent_path)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
def reconnect_parent_vhd(self, child_vhd_path, parent_vhd_path):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
|
|
(job_path, ret_val) = image_man_svc.ReconnectParentVirtualHardDisk(
|
|
ChildPath=child_vhd_path,
|
|
ParentPath=parent_vhd_path,
|
|
Force=True)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
def merge_vhd(self, src_vhd_path, dest_vhd_path):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
|
|
(job_path, ret_val) = image_man_svc.MergeVirtualHardDisk(
|
|
SourcePath=src_vhd_path,
|
|
DestinationPath=dest_vhd_path)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
def _get_resize_method(self):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
return image_man_svc.ExpandVirtualHardDisk
|
|
|
|
def resize_vhd(self, vhd_path, new_max_size, is_file_max_size=True):
|
|
if is_file_max_size:
|
|
new_internal_max_size = self.get_internal_vhd_size_by_file_size(
|
|
vhd_path, new_max_size)
|
|
else:
|
|
new_internal_max_size = new_max_size
|
|
|
|
resize = self._get_resize_method()
|
|
|
|
(job_path, ret_val) = resize(
|
|
Path=vhd_path, MaxInternalSize=new_internal_max_size)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
def get_internal_vhd_size_by_file_size(self, vhd_path, new_vhd_file_size):
|
|
"""Fixed VHD size = Data Block size + 512 bytes
|
|
| Dynamic_VHD_size = Dynamic Disk Header
|
|
| + Copy of hard disk footer
|
|
| + Hard Disk Footer
|
|
| + Data Block
|
|
| + BAT
|
|
| Dynamic Disk header fields
|
|
| Copy of hard disk footer (512 bytes)
|
|
| Dynamic Disk Header (1024 bytes)
|
|
| BAT (Block Allocation table)
|
|
| Data Block 1
|
|
| Data Block 2
|
|
| Data Block n
|
|
| Hard Disk Footer (512 bytes)
|
|
| Default block size is 2M
|
|
| BAT entry size is 4byte
|
|
"""
|
|
base_vhd_info = self.get_vhd_info(vhd_path)
|
|
vhd_type = base_vhd_info['Type']
|
|
|
|
if vhd_type == constants.VHD_TYPE_FIXED:
|
|
vhd_header_size = VHD_HEADER_SIZE_FIX
|
|
return new_vhd_file_size - vhd_header_size
|
|
elif vhd_type == constants.VHD_TYPE_DYNAMIC:
|
|
bs = self._get_vhd_dynamic_blk_size(vhd_path)
|
|
bes = VHD_BAT_ENTRY_SIZE
|
|
ddhs = VHD_DYNAMIC_DISK_HEADER_SIZE
|
|
hs = VHD_HEADER_SIZE_DYNAMIC
|
|
fs = VHD_FOOTER_SIZE_DYNAMIC
|
|
|
|
max_internal_size = (new_vhd_file_size -
|
|
(hs + ddhs + fs)) * bs / (bes + bs)
|
|
return max_internal_size
|
|
else:
|
|
vhd_parent = self.get_vhd_parent_path(vhd_path)
|
|
return self.get_internal_vhd_size_by_file_size(vhd_parent,
|
|
new_vhd_file_size)
|
|
|
|
def _get_vhd_dynamic_blk_size(self, vhd_path):
|
|
blk_size_offset = VHD_BLK_SIZE_OFFSET
|
|
try:
|
|
with open(vhd_path, "rb") as f:
|
|
f.seek(blk_size_offset)
|
|
version = f.read(4)
|
|
except IOError:
|
|
raise vmutils.HyperVException(_("Unable to obtain block size from"
|
|
" VHD %(vhd_path)s") %
|
|
{"vhd_path": vhd_path})
|
|
return struct.unpack('>i', version)[0]
|
|
|
|
def get_vhd_parent_path(self, vhd_path):
|
|
return self.get_vhd_info(vhd_path).get("ParentPath")
|
|
|
|
def get_vhd_info(self, vhd_path):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
|
|
(vhd_info,
|
|
job_path,
|
|
ret_val) = image_man_svc.GetVirtualHardDiskInfo(vhd_path)
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
vhd_info_dict = {}
|
|
|
|
et = ElementTree.fromstring(vhd_info)
|
|
for item in et.findall("PROPERTY"):
|
|
name = item.attrib["NAME"]
|
|
value_text = item.find("VALUE").text
|
|
if name == "ParentPath":
|
|
vhd_info_dict[name] = value_text
|
|
elif name in ["FileSize", "MaxInternalSize"]:
|
|
vhd_info_dict[name] = long(value_text)
|
|
elif name in ["InSavedState", "InUse"]:
|
|
vhd_info_dict[name] = bool(value_text)
|
|
elif name == "Type":
|
|
vhd_info_dict[name] = int(value_text)
|
|
|
|
return vhd_info_dict
|
|
|
|
def get_vhd_format(self, path):
|
|
with open(path, 'rb') as f:
|
|
# Read header
|
|
if f.read(8) == VHDX_SIGNATURE:
|
|
return constants.DISK_FORMAT_VHDX
|
|
|
|
# Read footer
|
|
f.seek(0, 2)
|
|
file_size = f.tell()
|
|
if file_size >= 512:
|
|
f.seek(-512, 2)
|
|
if f.read(8) == VHD_SIGNATURE:
|
|
return constants.DISK_FORMAT_VHD
|
|
|
|
raise vmutils.HyperVException(_('Unsupported virtual disk format'))
|
|
|
|
def get_best_supported_vhd_format(self):
|
|
return constants.DISK_FORMAT_VHD
|