Coverage for drivers/vhdutil.py : 36%

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 hidden = False
56 parentUuid = ""
57 parentPath = ""
58 error = 0
60 def __init__(self, uuid):
61 self.uuid = uuid
64def calcOverheadEmpty(virtual_size):
65 """Calculate the VHD space overhead (metadata size) for an empty VDI of
66 size virtual_size"""
67 overhead = 0
68 size_mb = virtual_size // (1024 * 1024)
70 # Footer + footer copy + header + possible CoW parent locator fields
71 overhead = 3 * 1024
73 # BAT 4 Bytes per block segment
74 overhead += (size_mb // 2) * 4
75 overhead = util.roundup(512, overhead)
77 # BATMAP 1 bit per block segment
78 overhead += (size_mb // 2) // 8
79 overhead = util.roundup(4096, overhead)
81 return overhead
84def calcOverheadBitmap(virtual_size):
85 num_blocks = virtual_size // VHD_BLOCK_SIZE
86 if virtual_size % VHD_BLOCK_SIZE:
87 num_blocks += 1
88 return num_blocks * 4096
91def ioretry(cmd, text=True):
92 return util.ioretry(lambda: util.pread2(cmd, text=text),
93 errlist=[errno.EIO, errno.EAGAIN])
96def getVHDInfo(path, extractUuidFunction, includeParent=True, resolveParent=True):
97 """Get the VHD info. The parent info may optionally be omitted: vhd-util
98 tries to verify the parent by opening it, which results in error if the VHD
99 resides on an inactive LV"""
100 opts = "-vsf"
101 if includeParent:
102 opts += "p"
103 if not resolveParent:
104 opts += "u"
106 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path]
107 ret = ioretry(cmd)
108 fields = ret.strip().split('\n')
109 uuid = extractUuidFunction(path)
110 vhdInfo = VHDInfo(uuid)
111 vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024
112 vhdInfo.sizePhys = int(fields[1])
113 nextIndex = 2
114 if includeParent:
115 if fields[nextIndex].find("no parent") == -1:
116 vhdInfo.parentPath = fields[nextIndex]
117 vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex])
118 nextIndex += 1
119 vhdInfo.hidden = int(fields[nextIndex].replace("hidden: ", ""))
120 vhdInfo.path = path
121 return vhdInfo
124def getVHDInfoLVM(lvName, extractUuidFunction, vgName):
125 """Get the VHD info. This function does not require the container LV to be
126 active, but uses lvs & vgs"""
127 vhdInfo = None
128 cmd = [VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName]
129 ret = ioretry(cmd)
130 return _parseVHDInfo(ret, extractUuidFunction)
133def getAllVHDs(pattern, extractUuidFunction, vgName=None, \
134 parentsOnly=False, exitOnError=False):
135 vhds = dict()
136 cmd = [VHD_UTIL, "scan", "-f", "-m", pattern]
137 if vgName:
138 cmd.append("-l")
139 cmd.append(vgName)
140 if parentsOnly:
141 cmd.append("-a")
142 try:
143 ret = ioretry(cmd)
144 except Exception as e:
145 util.SMlog("WARN: vhd scan failed: output: %s" % e)
146 ret = ioretry(cmd + ["-c"])
147 util.SMlog("WARN: vhd scan with NOFAIL flag, output: %s" % ret)
148 for line in ret.split('\n'):
149 if len(line.strip()) == 0:
150 continue
151 vhdInfo = _parseVHDInfo(line, extractUuidFunction)
152 if vhdInfo:
153 if vhdInfo.error != 0 and exitOnError:
154 # Just return an empty dict() so the scan will be done
155 # again by getParentChain. See CA-177063 for details on
156 # how this has been discovered during the stress tests.
157 return dict()
158 vhds[vhdInfo.uuid] = vhdInfo
159 else:
160 util.SMlog("WARN: vhdinfo line doesn't parse correctly: %s" % line)
161 return vhds
164def getParentChain(lvName, extractUuidFunction, vgName):
165 """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's
166 as well"""
167 chain = dict()
168 vdis = dict()
169 retries = 0
170 while (not vdis):
171 if retries > 60:
172 util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries)
173 util.SMlog('ERROR: the VHD metadata might be corrupted')
174 break
175 vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True)
176 if (not vdis):
177 retries = retries + 1
178 time.sleep(1)
179 for uuid, vdi in vdis.items():
180 chain[uuid] = vdi.path
181 #util.SMlog("Parent chain for %s: %s" % (lvName, chain))
182 return chain
185def getParent(path, extractUuidFunction):
186 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path]
187 ret = ioretry(cmd)
188 if ret.find("query failed") != -1 or ret.find("Failed opening") != -1:
189 raise util.SMException("VHD query returned %s" % ret)
190 if ret.find("no parent") != -1:
191 return None
192 return extractUuidFunction(ret)
195def hasParent(path):
196 """Check if the VHD has a parent. A VHD has a parent iff its type is
197 'Differencing'. This function does not need the parent to actually
198 be present (e.g. the parent LV to be activated)."""
199 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path]
200 ret = ioretry(cmd)
201 # pylint: disable=no-member
202 m = re.match(".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S)
203 vhd_type = m.group(1)
204 assert(vhd_type == "Differencing" or vhd_type == "Dynamic")
205 return vhd_type == "Differencing"
208def setParent(path, parentPath, parentRaw):
209 normpath = os.path.normpath(parentPath)
210 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path]
211 if parentRaw:
212 cmd.append("-m")
213 ioretry(cmd)
216def getHidden(path):
217 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path]
218 ret = ioretry(cmd)
219 hidden = int(ret.split(':')[-1].strip())
220 return hidden
223def setHidden(path, hidden=True):
224 opt = "1"
225 if not hidden: 225 ↛ 226line 225 didn't jump to line 226, because the condition on line 225 was never true
226 opt = "0"
227 cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt]
228 ret = ioretry(cmd)
231def getSizeVirt(path):
232 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path]
233 ret = ioretry(cmd)
234 size = int(ret) * 1024 * 1024
235 return size
238def setSizeVirt(path, size, jFile):
239 "resize VHD offline"
240 size_mb = size // (1024 * 1024)
241 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path,
242 "-j", jFile]
243 ioretry(cmd)
246def setSizeVirtFast(path, size):
247 "resize VHD online"
248 size_mb = size // (1024 * 1024)
249 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"]
250 ioretry(cmd)
253def getMaxResizeSize(path):
254 """get the max virtual size for fast resize"""
255 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path]
256 ret = ioretry(cmd)
257 return int(ret)
260def getSizePhys(path):
261 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path]
262 ret = ioretry(cmd)
263 return int(ret)
266def setSizePhys(path, size, debug=True):
267 "set physical utilisation (applicable to VHD's on fixed-size files)"
268 if debug:
269 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path]
270 else:
271 cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path]
272 ioretry(cmd)
275def killData(path):
276 "zero out the disk (kill all data inside the VHD file)"
277 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path]
278 ioretry(cmd)
281def getDepth(path):
282 "get the VHD parent chain depth"
283 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path]
284 text = ioretry(cmd)
285 depth = -1
286 if text.startswith("chain depth:"):
287 depth = int(text.split(':')[1].strip())
288 return depth
291def getBlockBitmap(path):
292 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path]
293 text = ioretry(cmd, text=False)
294 return zlib.compress(text)
297def coalesce(path):
298 cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path]
299 ioretry(cmd)
302def create(path, size, static, msize=0):
303 size_mb = size // (1024 * 1024)
304 cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)]
305 if static:
306 cmd.append("-r")
307 if msize:
308 cmd.append("-S")
309 cmd.append(str(msize))
310 ioretry(cmd)
313def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True):
314 cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent]
315 if parentRaw:
316 cmd.append("-m")
317 if msize:
318 cmd.append("-S")
319 cmd.append(str(msize))
320 if not checkEmpty:
321 cmd.append("-e")
322 ioretry(cmd)
325def check(path, ignoreMissingFooter=False, fast=False):
326 cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path]
327 if ignoreMissingFooter:
328 cmd.append("-i")
329 if fast:
330 cmd.append("-B")
331 try:
332 ioretry(cmd)
333 return True
334 except util.CommandException:
335 return False
338def revert(path, jFile):
339 cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile]
340 ioretry(cmd)
343def _parseVHDInfo(line, extractUuidFunction):
344 vhdInfo = None
345 valueMap = line.split()
346 if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1:
347 return None
348 for keyval in valueMap:
349 (key, val) = keyval.split('=')
350 if key == "vhd":
351 uuid = extractUuidFunction(val)
352 if not uuid:
353 util.SMlog("***** malformed output, no UUID: %s" % valueMap)
354 return None
355 vhdInfo = VHDInfo(uuid)
356 vhdInfo.path = val
357 elif key == "scan-error":
358 vhdInfo.error = line
359 util.SMlog("***** VHD scan error: %s" % line)
360 break
361 elif key == "capacity":
362 vhdInfo.sizeVirt = int(val)
363 elif key == "size":
364 vhdInfo.sizePhys = int(val)
365 elif key == "hidden":
366 vhdInfo.hidden = int(val)
367 elif key == "parent" and val != "none":
368 vhdInfo.parentPath = val
369 vhdInfo.parentUuid = extractUuidFunction(val)
370 return vhdInfo
373def _getVHDParentNoCheck(path):
374 cmd = ["vhd-util", "read", "-p", "-n", "%s" % path]
375 text = util.pread(cmd)
376 util.SMlog(text)
377 for line in text.split('\n'):
378 if line.find("decoded name :") != -1:
379 val = line.split(':')[1].strip()
380 vdi = val.replace("--", "-")[-40:]
381 if vdi[1:].startswith("LV-"):
382 vdi = vdi[1:]
383 return vdi
384 return None
387def repair(path):
388 """Repairs the VHD."""
389 ioretry([VHD_UTIL, 'repair', '-n', path])
392def validate_and_round_vhd_size(size):
393 """ Take the supplied vhd size, in bytes, and check it is positive and less
394 that the maximum supported size, rounding up to the next block boundary
395 """
396 if size < 0 or size > MAX_VHD_SIZE:
397 raise xs_errors.XenError(
398 'VDISize', opterr='VDI size ' +
399 'must be between 1 MB and %d MB' %
400 (MAX_VHD_SIZE // (1024 * 1024)))
402 if size < MIN_VHD_SIZE: 402 ↛ 403line 402 didn't jump to line 403, because the condition on line 402 was never true
403 size = MIN_VHD_SIZE
405 size = util.roundup(VHD_BLOCK_SIZE, size)
407 return size
410def getKeyHash(path):
411 """Extract the hash of the encryption key from the header of an encrypted VHD"""
412 cmd = ["vhd-util", "key", "-p", "-n", path]
413 ret = ioretry(cmd)
414 ret = ret.strip()
415 if ret == 'none':
416 return None
417 vals = ret.split()
418 if len(vals) != 2:
419 util.SMlog('***** malformed output from vhd-util'
420 ' for VHD {}: "{}"'.format(path, ret))
421 return None
422 [_nonce, key_hash] = vals
423 return key_hash
426def setKey(path, key_hash):
427 """Set the encryption key for a VHD"""
428 cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash]
429 ioretry(cmd)