Coverage for drivers/vhdutil.py : 43%

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 util.SMlog("DEBUG SCAN {}".format(pattern))
169 for uuid in vhds:
170 vhdinfo = vhds[uuid]
171 util.SMlog("DEBUG: UUID {}: {}".format(uuid, vhdinfo.__dict__))
172 util.SMlog("DEBUG SCAN END")
173 return vhds
176def getParentChain(lvName, extractUuidFunction, vgName):
177 """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's
178 as well"""
179 chain = dict()
180 vdis = dict()
181 retries = 0
182 while (not vdis):
183 if retries > 60:
184 util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries)
185 util.SMlog('ERROR: the VHD metadata might be corrupted')
186 break
187 vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True)
188 if (not vdis):
189 retries = retries + 1
190 time.sleep(1)
191 for uuid, vdi in vdis.items():
192 chain[uuid] = vdi.path
193 #util.SMlog("Parent chain for %s: %s" % (lvName, chain))
194 return chain
197def getParent(path, extractUuidFunction):
198 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path]
199 ret = ioretry(cmd)
200 if ret.find("query failed") != -1 or ret.find("Failed opening") != -1:
201 raise util.SMException("VHD query returned %s" % ret)
202 if ret.find("no parent") != -1:
203 return None
204 return extractUuidFunction(ret)
207def hasParent(path):
208 """Check if the VHD has a parent. A VHD has a parent iff its type is
209 'Differencing'. This function does not need the parent to actually
210 be present (e.g. the parent LV to be activated)."""
211 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path]
212 ret = ioretry(cmd)
213 # pylint: disable=no-member
214 m = re.match(r".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S)
215 vhd_type = m.group(1)
216 assert(vhd_type == "Differencing" or vhd_type == "Dynamic")
217 return vhd_type == "Differencing"
220def setParent(path, parentPath, parentRaw):
221 normpath = os.path.normpath(parentPath)
222 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path]
223 if parentRaw:
224 cmd.append("-m")
225 ioretry(cmd)
228def getHidden(path):
229 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path]
230 ret = ioretry(cmd)
231 hidden = int(ret.split(':')[-1].strip())
232 return hidden
235def setHidden(path, hidden=True):
236 opt = "1"
237 if not hidden: 237 ↛ 238line 237 didn't jump to line 238, because the condition on line 237 was never true
238 opt = "0"
239 cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt]
240 ret = ioretry(cmd)
243def getSizeVirt(path):
244 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path]
245 ret = ioretry(cmd)
246 size = int(ret) * 1024 * 1024
247 return size
250def setSizeVirt(path, size, jFile):
251 "resize VHD offline"
252 size_mb = size // (1024 * 1024)
253 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path,
254 "-j", jFile]
255 ioretry(cmd)
258def setSizeVirtFast(path, size):
259 "resize VHD online"
260 size_mb = size // (1024 * 1024)
261 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"]
262 ioretry(cmd)
265def getMaxResizeSize(path):
266 """get the max virtual size for fast resize"""
267 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path]
268 ret = ioretry(cmd)
269 return int(ret)
272def getSizePhys(path):
273 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path]
274 ret = ioretry(cmd)
275 return int(ret)
278def setSizePhys(path, size, debug=True):
279 "set physical utilisation (applicable to VHD's on fixed-size files)"
280 if debug:
281 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path]
282 else:
283 cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path]
284 ioretry(cmd)
287def getAllocatedSize(path):
288 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, '-a', '-n', path]
289 ret = ioretry(cmd)
290 return convertAllocatedSizeToBytes(int(ret))
292def killData(path):
293 "zero out the disk (kill all data inside the VHD file)"
294 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path]
295 ioretry(cmd)
298def getDepth(path):
299 "get the VHD parent chain depth"
300 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path]
301 text = ioretry(cmd)
302 depth = -1
303 if text.startswith("chain depth:"):
304 depth = int(text.split(':')[1].strip())
305 return depth
308def getBlockBitmap(path):
309 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path]
310 text = ioretry(cmd, text=False)
311 return zlib.compress(text)
314def coalesce(path):
315 """
316 Coalesce the VHD, on success it returns the number of sectors coalesced
317 """
318 cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path]
319 text = ioretry(cmd)
320 match = re.match(r'^Coalesced (\d+) sectors', text)
321 if match:
322 return int(match.group(1))
324 return 0
327def create(path, size, static, msize=0):
328 size_mb = size // (1024 * 1024)
329 cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)]
330 if static:
331 cmd.append("-r")
332 if msize:
333 cmd.append("-S")
334 cmd.append(str(msize))
335 ioretry(cmd)
338def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True):
339 cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent]
340 if parentRaw:
341 cmd.append("-m")
342 if msize:
343 cmd.append("-S")
344 cmd.append(str(msize))
345 if not checkEmpty:
346 cmd.append("-e")
347 ioretry(cmd)
350def check(path, ignoreMissingFooter=False, fast=False):
351 cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path]
352 if ignoreMissingFooter:
353 cmd.append("-i")
354 if fast:
355 cmd.append("-B")
356 try:
357 ioretry(cmd)
358 return True
359 except util.CommandException:
360 return False
363def revert(path, jFile):
364 cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile]
365 ioretry(cmd)
368def _parseVHDInfo(line, extractUuidFunction):
369 vhdInfo = None
370 valueMap = line.split()
371 if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1:
372 return None
373 for keyval in valueMap:
374 (key, val) = keyval.split('=')
375 if key == "vhd":
376 uuid = extractUuidFunction(val)
377 if not uuid:
378 util.SMlog("***** malformed output, no UUID: %s" % valueMap)
379 return None
380 vhdInfo = VHDInfo(uuid)
381 vhdInfo.path = val
382 elif key == "scan-error":
383 vhdInfo.error = line
384 util.SMlog("***** VHD scan error: %s" % line)
385 break
386 elif key == "capacity":
387 vhdInfo.sizeVirt = int(val)
388 elif key == "size":
389 vhdInfo.sizePhys = int(val)
390 elif key == "hidden":
391 vhdInfo.hidden = int(val)
392 elif key == "parent" and val != "none":
393 vhdInfo.parentPath = val
394 vhdInfo.parentUuid = extractUuidFunction(val)
395 return vhdInfo
398def _getVHDParentNoCheck(path):
399 cmd = ["vhd-util", "read", "-p", "-n", "%s" % path]
400 text = util.pread(cmd)
401 util.SMlog(text)
402 for line in text.split('\n'):
403 if line.find("decoded name :") != -1:
404 val = line.split(':')[1].strip()
405 vdi = val.replace("--", "-")[-40:]
406 if vdi[1:].startswith("LV-"):
407 vdi = vdi[1:]
408 return vdi
409 return None
412def repair(path):
413 """Repairs the VHD."""
414 ioretry([VHD_UTIL, 'repair', '-n', path])
417def validate_and_round_vhd_size(size):
418 """ Take the supplied vhd size, in bytes, and check it is positive and less
419 that the maximum supported size, rounding up to the next block boundary
420 """
421 if size < 0 or size > MAX_VHD_SIZE:
422 raise xs_errors.XenError(
423 'VDISize', opterr='VDI size ' +
424 'must be between 1 MB and %d MB' %
425 (MAX_VHD_SIZE // (1024 * 1024)))
427 if size < MIN_VHD_SIZE: 427 ↛ 428line 427 didn't jump to line 428, because the condition on line 427 was never true
428 size = MIN_VHD_SIZE
430 size = util.roundup(VHD_BLOCK_SIZE, size)
432 return size
435def getKeyHash(path):
436 """Extract the hash of the encryption key from the header of an encrypted VHD"""
437 cmd = ["vhd-util", "key", "-p", "-n", path]
438 ret = ioretry(cmd)
439 ret = ret.strip()
440 if ret == 'none':
441 return None
442 vals = ret.split()
443 if len(vals) != 2:
444 util.SMlog('***** malformed output from vhd-util'
445 ' for VHD {}: "{}"'.format(path, ret))
446 return None
447 [_nonce, key_hash] = vals
448 return key_hash
451def setKey(path, key_hash):
452 """Set the encryption key for a VHD"""
453 cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash]
454 ioretry(cmd)