Coverage for drivers/vhdutil.py : 37%

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 getVHDInfo(path, extractUuidFunction, includeParent=True, resolveParent=True):
98 """Get the VHD info. The parent info may optionally be omitted: vhd-util
99 tries to verify the parent by opening it, which results in error if the VHD
100 resides on an inactive LV"""
101 opts = "-vsaf"
102 if includeParent:
103 opts += "p"
104 if not resolveParent:
105 opts += "u"
107 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path]
108 ret = ioretry(cmd)
109 fields = ret.strip().split('\n')
110 uuid = extractUuidFunction(path)
111 vhdInfo = VHDInfo(uuid)
112 vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024
113 vhdInfo.sizePhys = int(fields[1])
114 nextIndex = 2
115 if includeParent:
116 if fields[nextIndex].find("no parent") == -1:
117 vhdInfo.parentPath = fields[nextIndex]
118 vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex])
119 nextIndex += 1
120 vhdInfo.hidden = int(fields[nextIndex].replace("hidden: ", ""))
121 vhdInfo.sizeAllocated = int(fields[nextIndex+1])
122 vhdInfo.path = path
123 return vhdInfo
126def getVHDInfoLVM(lvName, extractUuidFunction, vgName):
127 """Get the VHD info. This function does not require the container LV to be
128 active, but uses lvs & vgs"""
129 vhdInfo = None
130 cmd = [VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName]
131 ret = ioretry(cmd)
132 return _parseVHDInfo(ret, extractUuidFunction)
135def getAllVHDs(pattern, extractUuidFunction, vgName=None, \
136 parentsOnly=False, exitOnError=False):
137 vhds = dict()
138 cmd = [VHD_UTIL, "scan", "-f", "-m", pattern]
139 if vgName:
140 cmd.append("-l")
141 cmd.append(vgName)
142 if parentsOnly:
143 cmd.append("-a")
144 try:
145 ret = ioretry(cmd)
146 except Exception as e:
147 util.SMlog("WARN: vhd scan failed: output: %s" % e)
148 ret = ioretry(cmd + ["-c"])
149 util.SMlog("WARN: vhd scan with NOFAIL flag, output: %s" % ret)
150 for line in ret.split('\n'):
151 if len(line.strip()) == 0:
152 continue
153 vhdInfo = _parseVHDInfo(line, extractUuidFunction)
154 if vhdInfo:
155 if vhdInfo.error != 0 and exitOnError:
156 # Just return an empty dict() so the scan will be done
157 # again by getParentChain. See CA-177063 for details on
158 # how this has been discovered during the stress tests.
159 return dict()
160 vhds[vhdInfo.uuid] = vhdInfo
161 else:
162 util.SMlog("WARN: vhdinfo line doesn't parse correctly: %s" % line)
163 return vhds
166def getParentChain(lvName, extractUuidFunction, vgName):
167 """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's
168 as well"""
169 chain = dict()
170 vdis = dict()
171 retries = 0
172 while (not vdis):
173 if retries > 60:
174 util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries)
175 util.SMlog('ERROR: the VHD metadata might be corrupted')
176 break
177 vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True)
178 if (not vdis):
179 retries = retries + 1
180 time.sleep(1)
181 for uuid, vdi in vdis.items():
182 chain[uuid] = vdi.path
183 #util.SMlog("Parent chain for %s: %s" % (lvName, chain))
184 return chain
187def getParent(path, extractUuidFunction):
188 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path]
189 ret = ioretry(cmd)
190 if ret.find("query failed") != -1 or ret.find("Failed opening") != -1:
191 raise util.SMException("VHD query returned %s" % ret)
192 if ret.find("no parent") != -1:
193 return None
194 return extractUuidFunction(ret)
197def hasParent(path):
198 """Check if the VHD has a parent. A VHD has a parent iff its type is
199 'Differencing'. This function does not need the parent to actually
200 be present (e.g. the parent LV to be activated)."""
201 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path]
202 ret = ioretry(cmd)
203 # pylint: disable=no-member
204 m = re.match(r".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S)
205 vhd_type = m.group(1)
206 assert(vhd_type == "Differencing" or vhd_type == "Dynamic")
207 return vhd_type == "Differencing"
210def setParent(path, parentPath, parentRaw):
211 normpath = os.path.normpath(parentPath)
212 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path]
213 if parentRaw:
214 cmd.append("-m")
215 ioretry(cmd)
218def getHidden(path):
219 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path]
220 ret = ioretry(cmd)
221 hidden = int(ret.split(':')[-1].strip())
222 return hidden
225def setHidden(path, hidden=True):
226 opt = "1"
227 if not hidden: 227 ↛ 228line 227 didn't jump to line 228, because the condition on line 227 was never true
228 opt = "0"
229 cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt]
230 ret = ioretry(cmd)
233def getSizeVirt(path):
234 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path]
235 ret = ioretry(cmd)
236 size = int(ret) * 1024 * 1024
237 return size
240def setSizeVirt(path, size, jFile):
241 "resize VHD offline"
242 size_mb = size // (1024 * 1024)
243 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path,
244 "-j", jFile]
245 ioretry(cmd)
248def setSizeVirtFast(path, size):
249 "resize VHD online"
250 size_mb = size // (1024 * 1024)
251 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"]
252 ioretry(cmd)
255def getMaxResizeSize(path):
256 """get the max virtual size for fast resize"""
257 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path]
258 ret = ioretry(cmd)
259 return int(ret)
262def getSizePhys(path):
263 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path]
264 ret = ioretry(cmd)
265 return int(ret)
268def setSizePhys(path, size, debug=True):
269 "set physical utilisation (applicable to VHD's on fixed-size files)"
270 if debug:
271 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path]
272 else:
273 cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path]
274 ioretry(cmd)
277def getAllocatedSize(path):
278 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, '-a', '-n', path]
279 ret = ioretry(cmd)
280 # Assume we have standard 2MB allocation blocks
281 return int(ret) * 2 * 1024 * 1024
283def killData(path):
284 "zero out the disk (kill all data inside the VHD file)"
285 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path]
286 ioretry(cmd)
289def getDepth(path):
290 "get the VHD parent chain depth"
291 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path]
292 text = ioretry(cmd)
293 depth = -1
294 if text.startswith("chain depth:"):
295 depth = int(text.split(':')[1].strip())
296 return depth
299def getBlockBitmap(path):
300 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path]
301 text = ioretry(cmd, text=False)
302 return zlib.compress(text)
305def coalesce(path):
306 """
307 Coalesce the VHD, on success it returns the number of sectors coalesced
308 """
309 cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path]
310 text = ioretry(cmd)
311 match = re.match(r'^Coalesced (\d+) sectors', text)
312 if match:
313 return int(match.group(1))
315 return 0
318def create(path, size, static, msize=0):
319 size_mb = size // (1024 * 1024)
320 cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)]
321 if static:
322 cmd.append("-r")
323 if msize:
324 cmd.append("-S")
325 cmd.append(str(msize))
326 ioretry(cmd)
329def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True):
330 cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent]
331 if parentRaw:
332 cmd.append("-m")
333 if msize:
334 cmd.append("-S")
335 cmd.append(str(msize))
336 if not checkEmpty:
337 cmd.append("-e")
338 ioretry(cmd)
341def check(path, ignoreMissingFooter=False, fast=False):
342 cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path]
343 if ignoreMissingFooter:
344 cmd.append("-i")
345 if fast:
346 cmd.append("-B")
347 try:
348 ioretry(cmd)
349 return True
350 except util.CommandException:
351 return False
354def revert(path, jFile):
355 cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile]
356 ioretry(cmd)
359def _parseVHDInfo(line, extractUuidFunction):
360 vhdInfo = None
361 valueMap = line.split()
362 if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1:
363 return None
364 for keyval in valueMap:
365 (key, val) = keyval.split('=')
366 if key == "vhd":
367 uuid = extractUuidFunction(val)
368 if not uuid:
369 util.SMlog("***** malformed output, no UUID: %s" % valueMap)
370 return None
371 vhdInfo = VHDInfo(uuid)
372 vhdInfo.path = val
373 elif key == "scan-error":
374 vhdInfo.error = line
375 util.SMlog("***** VHD scan error: %s" % line)
376 break
377 elif key == "capacity":
378 vhdInfo.sizeVirt = int(val)
379 elif key == "size":
380 vhdInfo.sizePhys = int(val)
381 elif key == "hidden":
382 vhdInfo.hidden = int(val)
383 elif key == "parent" and val != "none":
384 vhdInfo.parentPath = val
385 vhdInfo.parentUuid = extractUuidFunction(val)
386 return vhdInfo
389def _getVHDParentNoCheck(path):
390 cmd = ["vhd-util", "read", "-p", "-n", "%s" % path]
391 text = util.pread(cmd)
392 util.SMlog(text)
393 for line in text.split('\n'):
394 if line.find("decoded name :") != -1:
395 val = line.split(':')[1].strip()
396 vdi = val.replace("--", "-")[-40:]
397 if vdi[1:].startswith("LV-"):
398 vdi = vdi[1:]
399 return vdi
400 return None
403def repair(path):
404 """Repairs the VHD."""
405 ioretry([VHD_UTIL, 'repair', '-n', path])
408def validate_and_round_vhd_size(size):
409 """ Take the supplied vhd size, in bytes, and check it is positive and less
410 that the maximum supported size, rounding up to the next block boundary
411 """
412 if size < 0 or size > MAX_VHD_SIZE:
413 raise xs_errors.XenError(
414 'VDISize', opterr='VDI size ' +
415 'must be between 1 MB and %d MB' %
416 (MAX_VHD_SIZE // (1024 * 1024)))
418 if size < MIN_VHD_SIZE: 418 ↛ 419line 418 didn't jump to line 419, because the condition on line 418 was never true
419 size = MIN_VHD_SIZE
421 size = util.roundup(VHD_BLOCK_SIZE, size)
423 return size
426def getKeyHash(path):
427 """Extract the hash of the encryption key from the header of an encrypted VHD"""
428 cmd = ["vhd-util", "key", "-p", "-n", path]
429 ret = ioretry(cmd)
430 ret = ret.strip()
431 if ret == 'none':
432 return None
433 vals = ret.split()
434 if len(vals) != 2:
435 util.SMlog('***** malformed output from vhd-util'
436 ' for VHD {}: "{}"'.format(path, ret))
437 return None
438 [_nonce, key_hash] = vals
439 return key_hash
442def setKey(path, key_hash):
443 """Set the encryption key for a VHD"""
444 cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash]
445 ioretry(cmd)