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