Coverage for drivers/vhdutil.py : 44%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15#
16# Helper functions pertaining to VHD operations
17#
19import os
20import util
21import errno
22import zlib
23import re
24import xs_errors
25import time
27MIN_VHD_SIZE = 2 * 1024 * 1024
28MAX_VHD_SIZE = 2040 * 1024 * 1024 * 1024
29MAX_VHD_JOURNAL_SIZE = 6 * 1024 * 1024 # 2MB VHD block size, max 2TB VHD size
30MAX_CHAIN_SIZE = 30 # max VHD parent chain size
31VHD_UTIL = "/usr/bin/vhd-util"
32OPT_LOG_ERR = "--debug"
33VHD_BLOCK_SIZE = 2 * 1024 * 1024
34VHD_FOOTER_SIZE = 512
36# lock to lock the entire SR for short ops
37LOCK_TYPE_SR = "sr"
39VDI_TYPE_VHD = 'vhd'
40VDI_TYPE_RAW = 'aio'
42FILE_EXTN_VHD = ".vhd"
43FILE_EXTN_RAW = ".raw"
44FILE_EXTN = {
45 VDI_TYPE_VHD: FILE_EXTN_VHD,
46 VDI_TYPE_RAW: FILE_EXTN_RAW
47}
50class VHDInfo:
51 uuid = ""
52 path = ""
53 sizeVirt = -1
54 sizePhys = -1
55 sizeAllocated = -1
56 hidden = False
57 parentUuid = ""
58 parentPath = ""
59 error = 0
61 def __init__(self, uuid):
62 self.uuid = uuid
65def calcOverheadEmpty(virtual_size):
66 """Calculate the VHD space overhead (metadata size) for an empty VDI of
67 size virtual_size"""
68 overhead = 0
69 size_mb = virtual_size // (1024 * 1024)
71 # Footer + footer copy + header + possible CoW parent locator fields
72 overhead = 3 * 1024
74 # BAT 4 Bytes per block segment
75 overhead += (size_mb // 2) * 4
76 overhead = util.roundup(512, overhead)
78 # BATMAP 1 bit per block segment
79 overhead += (size_mb // 2) // 8
80 overhead = util.roundup(4096, overhead)
82 return overhead
85def calcOverheadBitmap(virtual_size):
86 num_blocks = virtual_size // VHD_BLOCK_SIZE
87 if virtual_size % VHD_BLOCK_SIZE:
88 num_blocks += 1
89 return num_blocks * 4096
92def ioretry(cmd, text=True):
93 return util.ioretry(lambda: util.pread2(cmd, text=text),
94 errlist=[errno.EIO, errno.EAGAIN])
97def convertAllocatedSizeToBytes(size):
98 # Assume we have standard 2MB allocation blocks
99 return size * 2 * 1024 * 1024
102def getVHDInfo(path, extractUuidFunction, includeParent=True, resolveParent=True):
103 """Get the VHD info. The parent info may optionally be omitted: vhd-util
104 tries to verify the parent by opening it, which results in error if the VHD
105 resides on an inactive LV"""
106 opts = "-vsaf"
107 if includeParent: 107 ↛ 112line 107 didn't jump to line 112, because the condition on line 107 was never false
108 opts += "p"
109 if not resolveParent: 109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true
110 opts += "u"
112 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path]
113 ret = ioretry(cmd)
114 fields = ret.strip().split('\n')
115 uuid = extractUuidFunction(path)
116 vhdInfo = VHDInfo(uuid)
117 vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024
118 vhdInfo.sizePhys = int(fields[1])
119 nextIndex = 2
120 if includeParent: 120 ↛ 125line 120 didn't jump to line 125, because the condition on line 120 was never false
121 if fields[nextIndex].find("no parent") == -1: 121 ↛ 122line 121 didn't jump to line 122, because the condition on line 121 was never true
122 vhdInfo.parentPath = fields[nextIndex]
123 vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex])
124 nextIndex += 1
125 vhdInfo.hidden = int(fields[nextIndex].replace("hidden: ", ""))
126 vhdInfo.sizeAllocated = convertAllocatedSizeToBytes(int(fields[nextIndex+1]))
127 vhdInfo.path = path
128 return vhdInfo
131def getVHDInfoLVM(lvName, extractUuidFunction, vgName):
132 """Get the VHD info. This function does not require the container LV to be
133 active, but uses lvs & vgs"""
134 vhdInfo = None
135 cmd = [VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName]
136 ret = ioretry(cmd)
137 return _parseVHDInfo(ret, extractUuidFunction)
140def getAllVHDs(pattern, extractUuidFunction, vgName=None, \
141 parentsOnly=False, exitOnError=False):
142 vhds = dict()
143 cmd = [VHD_UTIL, "scan", "-f", "-m", pattern]
144 if vgName:
145 cmd.append("-l")
146 cmd.append(vgName)
147 if parentsOnly:
148 cmd.append("-a")
149 try:
150 ret = ioretry(cmd)
151 except Exception as e:
152 util.SMlog("WARN: vhd scan failed: output: %s" % e)
153 ret = ioretry(cmd + ["-c"])
154 util.SMlog("WARN: vhd scan with NOFAIL flag, output: %s" % ret)
155 for line in ret.split('\n'):
156 if len(line.strip()) == 0:
157 continue
158 vhdInfo = _parseVHDInfo(line, extractUuidFunction)
159 if vhdInfo:
160 if vhdInfo.error != 0 and exitOnError:
161 # Just return an empty dict() so the scan will be done
162 # again by getParentChain. See CA-177063 for details on
163 # how this has been discovered during the stress tests.
164 return dict()
165 vhds[vhdInfo.uuid] = vhdInfo
166 else:
167 util.SMlog("WARN: vhdinfo line doesn't parse correctly: %s" % line)
168 return vhds
171def getParentChain(lvName, extractUuidFunction, vgName):
172 """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's
173 as well"""
174 chain = dict()
175 vdis = dict()
176 retries = 0
177 while (not vdis):
178 if retries > 60:
179 util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries)
180 util.SMlog('ERROR: the VHD metadata might be corrupted')
181 break
182 vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True)
183 if (not vdis):
184 retries = retries + 1
185 time.sleep(1)
186 for uuid, vdi in vdis.items():
187 chain[uuid] = vdi.path
188 #util.SMlog("Parent chain for %s: %s" % (lvName, chain))
189 return chain
192def getParent(path, extractUuidFunction):
193 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path]
194 ret = ioretry(cmd)
195 if ret.find("query failed") != -1 or ret.find("Failed opening") != -1:
196 raise util.SMException("VHD query returned %s" % ret)
197 if ret.find("no parent") != -1:
198 return None
199 return extractUuidFunction(ret)
202def hasParent(path):
203 """Check if the VHD has a parent. A VHD has a parent iff its type is
204 'Differencing'. This function does not need the parent to actually
205 be present (e.g. the parent LV to be activated)."""
206 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path]
207 ret = ioretry(cmd)
208 # pylint: disable=no-member
209 m = re.match(r".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S)
210 vhd_type = m.group(1)
211 assert(vhd_type == "Differencing" or vhd_type == "Dynamic")
212 return vhd_type == "Differencing"
215def setParent(path, parentPath, parentRaw):
216 normpath = os.path.normpath(parentPath)
217 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path]
218 if parentRaw:
219 cmd.append("-m")
220 ioretry(cmd)
223def getHidden(path):
224 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path]
225 ret = ioretry(cmd)
226 hidden = int(ret.split(':')[-1].strip())
227 return hidden
230def setHidden(path, hidden=True):
231 opt = "1"
232 if not hidden: 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true
233 opt = "0"
234 cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt]
235 ret = ioretry(cmd)
238def getSizeVirt(path):
239 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path]
240 ret = ioretry(cmd)
241 size = int(ret) * 1024 * 1024
242 return size
245def setSizeVirt(path, size, jFile):
246 "resize VHD offline"
247 size_mb = size // (1024 * 1024)
248 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path,
249 "-j", jFile]
250 ioretry(cmd)
253def setSizeVirtFast(path, size):
254 "resize VHD online"
255 size_mb = size // (1024 * 1024)
256 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"]
257 ioretry(cmd)
260def getMaxResizeSize(path):
261 """get the max virtual size for fast resize"""
262 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path]
263 ret = ioretry(cmd)
264 return int(ret)
267def getSizePhys(path):
268 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path]
269 ret = ioretry(cmd)
270 return int(ret)
273def setSizePhys(path, size, debug=True):
274 "set physical utilisation (applicable to VHD's on fixed-size files)"
275 if debug:
276 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path]
277 else:
278 cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path]
279 ioretry(cmd)
282def getAllocatedSize(path):
283 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, '-a', '-n', path]
284 ret = ioretry(cmd)
285 return convertAllocatedSizeToBytes(int(ret))
287def killData(path):
288 "zero out the disk (kill all data inside the VHD file)"
289 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path]
290 ioretry(cmd)
293def getDepth(path):
294 "get the VHD parent chain depth"
295 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path]
296 text = ioretry(cmd)
297 depth = -1
298 if text.startswith("chain depth:"):
299 depth = int(text.split(':')[1].strip())
300 return depth
303def getBlockBitmap(path):
304 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path]
305 text = ioretry(cmd, text=False)
306 return zlib.compress(text)
309def coalesce(path):
310 """
311 Coalesce the VHD, on success it returns the number of sectors coalesced
312 """
313 cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path]
314 text = ioretry(cmd)
315 match = re.match(r'^Coalesced (\d+) sectors', text)
316 if match:
317 return int(match.group(1))
319 return 0
322def create(path, size, static, msize=0):
323 size_mb = size // (1024 * 1024)
324 cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)]
325 if static:
326 cmd.append("-r")
327 if msize:
328 cmd.append("-S")
329 cmd.append(str(msize))
330 ioretry(cmd)
333def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True):
334 cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent]
335 if parentRaw:
336 cmd.append("-m")
337 if msize:
338 cmd.append("-S")
339 cmd.append(str(msize))
340 if not checkEmpty:
341 cmd.append("-e")
342 ioretry(cmd)
345def check(path, ignoreMissingFooter=False, fast=False):
346 cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path]
347 if ignoreMissingFooter:
348 cmd.append("-i")
349 if fast:
350 cmd.append("-B")
351 try:
352 ioretry(cmd)
353 return True
354 except util.CommandException:
355 return False
358def revert(path, jFile):
359 cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile]
360 ioretry(cmd)
363def _parseVHDInfo(line, extractUuidFunction):
364 vhdInfo = None
365 valueMap = line.split()
366 if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1:
367 return None
368 for keyval in valueMap:
369 (key, val) = keyval.split('=')
370 if key == "vhd":
371 uuid = extractUuidFunction(val)
372 if not uuid:
373 util.SMlog("***** malformed output, no UUID: %s" % valueMap)
374 return None
375 vhdInfo = VHDInfo(uuid)
376 vhdInfo.path = val
377 elif key == "scan-error":
378 vhdInfo.error = line
379 util.SMlog("***** VHD scan error: %s" % line)
380 break
381 elif key == "capacity":
382 vhdInfo.sizeVirt = int(val)
383 elif key == "size":
384 vhdInfo.sizePhys = int(val)
385 elif key == "hidden":
386 vhdInfo.hidden = int(val)
387 elif key == "parent" and val != "none":
388 vhdInfo.parentPath = val
389 vhdInfo.parentUuid = extractUuidFunction(val)
390 return vhdInfo
393def _getVHDParentNoCheck(path):
394 cmd = ["vhd-util", "read", "-p", "-n", "%s" % path]
395 text = util.pread(cmd)
396 util.SMlog(text)
397 for line in text.split('\n'):
398 if line.find("decoded name :") != -1:
399 val = line.split(':')[1].strip()
400 vdi = val.replace("--", "-")[-40:]
401 if vdi[1:].startswith("LV-"):
402 vdi = vdi[1:]
403 return vdi
404 return None
407def repair(path):
408 """Repairs the VHD."""
409 ioretry([VHD_UTIL, 'repair', '-n', path])
412def validate_and_round_vhd_size(size):
413 """ Take the supplied vhd size, in bytes, and check it is positive and less
414 that the maximum supported size, rounding up to the next block boundary
415 """
416 if size < 0 or size > MAX_VHD_SIZE:
417 raise xs_errors.XenError(
418 'VDISize', opterr='VDI size ' +
419 'must be between 1 MB and %d MB' %
420 (MAX_VHD_SIZE // (1024 * 1024)))
422 if size < MIN_VHD_SIZE: 422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true
423 size = MIN_VHD_SIZE
425 size = util.roundup(VHD_BLOCK_SIZE, size)
427 return size
430def getKeyHash(path):
431 """Extract the hash of the encryption key from the header of an encrypted VHD"""
432 cmd = ["vhd-util", "key", "-p", "-n", path]
433 ret = ioretry(cmd)
434 ret = ret.strip()
435 if ret == 'none':
436 return None
437 vals = ret.split()
438 if len(vals) != 2:
439 util.SMlog('***** malformed output from vhd-util'
440 ' for VHD {}: "{}"'.format(path, ret))
441 return None
442 [_nonce, key_hash] = vals
443 return key_hash
446def setKey(path, key_hash):
447 """Set the encryption key for a VHD"""
448 cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash]
449 ioretry(cmd)