Adds local i18n and replaces the usage of nova i18n with the local one. This way, there is one fewer dependencies on nova.
243 lines
9.4 KiB
Python
243 lines
9.4 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.
|
|
Based on the "root/virtualization/v2" namespace available starting with
|
|
Hyper-V Server / Windows Server 2012.
|
|
"""
|
|
import struct
|
|
import sys
|
|
|
|
if sys.platform == 'win32':
|
|
import wmi
|
|
|
|
from xml.etree import ElementTree
|
|
|
|
from oslo_utils import units
|
|
|
|
from hyperv.i18n import _
|
|
from hyperv.nova import constants
|
|
from hyperv.nova import vhdutils
|
|
from hyperv.nova import vmutils
|
|
from hyperv.nova import vmutilsv2
|
|
|
|
|
|
VHDX_BAT_ENTRY_SIZE = 8
|
|
VHDX_HEADER_OFFSETS = [64 * units.Ki, 128 * units.Ki]
|
|
VHDX_HEADER_SECTION_SIZE = units.Mi
|
|
VHDX_LOG_LENGTH_OFFSET = 68
|
|
VHDX_METADATA_SIZE_OFFSET = 64
|
|
VHDX_REGION_TABLE_OFFSET = 192 * units.Ki
|
|
VHDX_BS_METADATA_ENTRY_OFFSET = 48
|
|
|
|
|
|
class VHDUtilsV2(vhdutils.VHDUtils):
|
|
|
|
_VHD_TYPE_DYNAMIC = 3
|
|
_VHD_TYPE_DIFFERENCING = 4
|
|
|
|
_vhd_format_map = {
|
|
constants.DISK_FORMAT_VHD: 2,
|
|
constants.DISK_FORMAT_VHDX: 3,
|
|
}
|
|
|
|
def __init__(self):
|
|
self._vmutils = vmutilsv2.VMUtilsV2()
|
|
if sys.platform == 'win32':
|
|
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
|
|
|
|
def create_dynamic_vhd(self, path, max_internal_size, format):
|
|
vhd_format = self._vhd_format_map.get(format)
|
|
if not vhd_format:
|
|
raise vmutils.HyperVException(_("Unsupported disk format: %s") %
|
|
format)
|
|
|
|
self._create_vhd(self._VHD_TYPE_DYNAMIC, vhd_format, path,
|
|
max_internal_size=max_internal_size)
|
|
|
|
def create_differencing_vhd(self, path, parent_path):
|
|
# Although this method can take a size argument in case of VHDX
|
|
# images, avoid it as the underlying Win32 is currently not
|
|
# resizing the disk properly. This can be reconsidered once the
|
|
# Win32 issue is fixed.
|
|
parent_vhd_info = self.get_vhd_info(parent_path)
|
|
self._create_vhd(self._VHD_TYPE_DIFFERENCING,
|
|
parent_vhd_info["Format"],
|
|
path, parent_path=parent_path)
|
|
|
|
def _create_vhd(self, vhd_type, format, path, max_internal_size=None,
|
|
parent_path=None):
|
|
vhd_info = self._conn.Msvm_VirtualHardDiskSettingData.new()
|
|
|
|
vhd_info.Type = vhd_type
|
|
vhd_info.Format = format
|
|
vhd_info.Path = path
|
|
vhd_info.ParentPath = parent_path
|
|
|
|
if max_internal_size:
|
|
vhd_info.MaxInternalSize = max_internal_size
|
|
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
(job_path, ret_val) = image_man_svc.CreateVirtualHardDisk(
|
|
VirtualDiskSettingData=vhd_info.GetText_(1))
|
|
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]
|
|
vhd_info_xml = self._get_vhd_info_xml(image_man_svc, child_vhd_path)
|
|
|
|
et = ElementTree.fromstring(vhd_info_xml)
|
|
item = et.find(".//PROPERTY[@NAME='ParentPath']/VALUE")
|
|
if item is not None:
|
|
item.text = parent_vhd_path
|
|
else:
|
|
msg = (_("Failed to reconnect image %(child_vhd_path)s to "
|
|
"parent %(parent_vhd_path)s. The child image has no "
|
|
"parent path property.") %
|
|
{'child_vhd_path': child_vhd_path,
|
|
'parent_vhd_path': parent_vhd_path})
|
|
raise vmutils.HyperVException(msg)
|
|
|
|
vhd_info_xml = ElementTree.tostring(et)
|
|
|
|
(job_path, ret_val) = image_man_svc.SetVirtualHardDiskSettingData(
|
|
VirtualDiskSettingData=vhd_info_xml)
|
|
|
|
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.ResizeVirtualHardDisk
|
|
|
|
def get_internal_vhd_size_by_file_size(self, vhd_path,
|
|
new_vhd_file_size):
|
|
"""VHDX Size = Header (1 MB)
|
|
+ Log
|
|
+ Metadata Region
|
|
+ BAT
|
|
+ Payload Blocks
|
|
Chunk size = maximum number of bytes described by a SB block
|
|
= 2 ** 23 * LogicalSectorSize
|
|
"""
|
|
vhd_format = self.get_vhd_format(vhd_path)
|
|
if vhd_format == constants.DISK_FORMAT_VHD:
|
|
return super(VHDUtilsV2,
|
|
self).get_internal_vhd_size_by_file_size(
|
|
vhd_path, new_vhd_file_size)
|
|
else:
|
|
vhd_info = self.get_vhd_info(vhd_path)
|
|
vhd_type = vhd_info['Type']
|
|
if vhd_type == self._VHD_TYPE_DIFFERENCING:
|
|
vhd_parent = self.get_vhd_parent_path(vhd_path)
|
|
return self.get_internal_vhd_size_by_file_size(vhd_parent,
|
|
new_vhd_file_size)
|
|
else:
|
|
try:
|
|
with open(vhd_path, 'rb') as f:
|
|
hs = VHDX_HEADER_SECTION_SIZE
|
|
bes = VHDX_BAT_ENTRY_SIZE
|
|
|
|
lss = vhd_info['LogicalSectorSize']
|
|
bs = self._get_vhdx_block_size(f)
|
|
ls = self._get_vhdx_log_size(f)
|
|
ms = self._get_vhdx_metadata_size_and_offset(f)[0]
|
|
|
|
chunk_ratio = (1 << 23) * lss / bs
|
|
size = new_vhd_file_size
|
|
|
|
max_internal_size = (bs * chunk_ratio * (size - hs -
|
|
ls - ms - bes - bes / chunk_ratio) / (bs *
|
|
chunk_ratio + bes * chunk_ratio + bes))
|
|
|
|
return max_internal_size - (max_internal_size % bs)
|
|
|
|
except IOError as ex:
|
|
raise vmutils.HyperVException(_("Unable to obtain "
|
|
"internal size from VHDX: "
|
|
"%(vhd_path)s. Exception: "
|
|
"%(ex)s") %
|
|
{"vhd_path": vhd_path,
|
|
"ex": ex})
|
|
|
|
def _get_vhdx_current_header_offset(self, vhdx_file):
|
|
sequence_numbers = []
|
|
for offset in VHDX_HEADER_OFFSETS:
|
|
vhdx_file.seek(offset + 8)
|
|
sequence_numbers.append(struct.unpack('<Q',
|
|
vhdx_file.read(8))[0])
|
|
current_header = sequence_numbers.index(max(sequence_numbers))
|
|
return VHDX_HEADER_OFFSETS[current_header]
|
|
|
|
def _get_vhdx_log_size(self, vhdx_file):
|
|
current_header_offset = self._get_vhdx_current_header_offset(vhdx_file)
|
|
offset = current_header_offset + VHDX_LOG_LENGTH_OFFSET
|
|
vhdx_file.seek(offset)
|
|
log_size = struct.unpack('<I', vhdx_file.read(4))[0]
|
|
return log_size
|
|
|
|
def _get_vhdx_metadata_size_and_offset(self, vhdx_file):
|
|
offset = VHDX_METADATA_SIZE_OFFSET + VHDX_REGION_TABLE_OFFSET
|
|
vhdx_file.seek(offset)
|
|
metadata_offset = struct.unpack('<Q', vhdx_file.read(8))[0]
|
|
metadata_size = struct.unpack('<I', vhdx_file.read(4))[0]
|
|
return metadata_size, metadata_offset
|
|
|
|
def _get_vhdx_block_size(self, vhdx_file):
|
|
metadata_offset = self._get_vhdx_metadata_size_and_offset(vhdx_file)[1]
|
|
offset = metadata_offset + VHDX_BS_METADATA_ENTRY_OFFSET
|
|
vhdx_file.seek(offset)
|
|
file_parameter_offset = struct.unpack('<I', vhdx_file.read(4))[0]
|
|
|
|
vhdx_file.seek(file_parameter_offset + metadata_offset)
|
|
block_size = struct.unpack('<I', vhdx_file.read(4))[0]
|
|
return block_size
|
|
|
|
def _get_vhd_info_xml(self, image_man_svc, vhd_path):
|
|
(job_path,
|
|
ret_val,
|
|
vhd_info_xml) = image_man_svc.GetVirtualHardDiskSettingData(vhd_path)
|
|
|
|
self._vmutils.check_ret_val(ret_val, job_path)
|
|
|
|
return vhd_info_xml.encode('utf8', 'xmlcharrefreplace')
|
|
|
|
def get_vhd_info(self, vhd_path):
|
|
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
|
vhd_info_xml = self._get_vhd_info_xml(image_man_svc, vhd_path)
|
|
|
|
vhd_info_dict = {}
|
|
et = ElementTree.fromstring(vhd_info_xml)
|
|
for item in et.findall("PROPERTY"):
|
|
name = item.attrib["NAME"]
|
|
value_item = item.find("VALUE")
|
|
if value_item is None:
|
|
value_text = None
|
|
else:
|
|
value_text = value_item.text
|
|
|
|
if name in ["Path", "ParentPath"]:
|
|
vhd_info_dict[name] = value_text
|
|
elif name in ["BlockSize", "LogicalSectorSize",
|
|
"PhysicalSectorSize", "MaxInternalSize"]:
|
|
vhd_info_dict[name] = long(value_text)
|
|
elif name in ["Type", "Format"]:
|
|
vhd_info_dict[name] = int(value_text)
|
|
|
|
return vhd_info_dict
|
|
|
|
def get_best_supported_vhd_format(self):
|
|
return constants.DISK_FORMAT_VHDX
|