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):
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 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path]
104 ret = ioretry(cmd)
105 fields = ret.strip().split('\n')
106 uuid = extractUuidFunction(path)
107 vhdInfo = VHDInfo(uuid)
108 vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024
109 vhdInfo.sizePhys = int(fields[1])
110 nextIndex = 2
111 if includeParent:
112 if fields[nextIndex].find("no parent") == -1:
113 vhdInfo.parentPath = fields[nextIndex]
114 vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex])
115 nextIndex += 1
116 vhdInfo.hidden = int(fields[nextIndex].replace("hidden: ", ""))
117 vhdInfo.path = path
118 return vhdInfo
121def getVHDInfoLVM(lvName, extractUuidFunction, vgName):
122 """Get the VHD info. This function does not require the container LV to be
123 active, but uses lvs & vgs"""
124 vhdInfo = None
125 cmd = [VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName]
126 ret = ioretry(cmd)
127 return _parseVHDInfo(ret, extractUuidFunction)
130def getAllVHDs(pattern, extractUuidFunction, vgName=None, \
131 parentsOnly=False, exitOnError=False):
132 vhds = dict()
133 cmd = [VHD_UTIL, "scan", "-f", "-m", pattern]
134 if vgName:
135 cmd.append("-l")
136 cmd.append(vgName)
137 if parentsOnly:
138 cmd.append("-a")
139 try:
140 ret = ioretry(cmd)
141 except Exception as e:
142 util.SMlog("WARN: vhd scan failed: output: %s" % e)
143 ret = ioretry(cmd + ["-c"])
144 util.SMlog("WARN: vhd scan with NOFAIL flag, output: %s" % ret)
145 for line in ret.split('\n'):
146 if len(line.strip()) == 0:
147 continue
148 vhdInfo = _parseVHDInfo(line, extractUuidFunction)
149 if vhdInfo:
150 if vhdInfo.error != 0 and exitOnError:
151 # Just return an empty dict() so the scan will be done
152 # again by getParentChain. See CA-177063 for details on
153 # how this has been discovered during the stress tests.
154 return dict()
155 vhds[vhdInfo.uuid] = vhdInfo
156 else:
157 util.SMlog("WARN: vhdinfo line doesn't parse correctly: %s" % line)
158 return vhds
161def getParentChain(lvName, extractUuidFunction, vgName):
162 """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's
163 as well"""
164 chain = dict()
165 vdis = dict()
166 retries = 0
167 while (not vdis):
168 if retries > 60:
169 util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries)
170 util.SMlog('ERROR: the VHD metadata might be corrupted')
171 break
172 vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True)
173 if (not vdis):
174 retries = retries + 1
175 time.sleep(1)
176 for uuid, vdi in vdis.items():
177 chain[uuid] = vdi.path
178 #util.SMlog("Parent chain for %s: %s" % (lvName, chain))
179 return chain
182def getParent(path, extractUuidFunction):
183 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path]
184 ret = ioretry(cmd)
185 if ret.find("query failed") != -1 or ret.find("Failed opening") != -1:
186 raise util.SMException("VHD query returned %s" % ret)
187 if ret.find("no parent") != -1:
188 return None
189 return extractUuidFunction(ret)
192def hasParent(path):
193 """Check if the VHD has a parent. A VHD has a parent iff its type is
194 'Differencing'. This function does not need the parent to actually
195 be present (e.g. the parent LV to be activated)."""
196 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path]
197 ret = ioretry(cmd)
198 # pylint: disable=no-member
199 m = re.match(".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S)
200 vhd_type = m.group(1)
201 assert(vhd_type == "Differencing" or vhd_type == "Dynamic")
202 return vhd_type == "Differencing"
205def setParent(path, parentPath, parentRaw):
206 normpath = os.path.normpath(parentPath)
207 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path]
208 if parentRaw:
209 cmd.append("-m")
210 ioretry(cmd)
213def getHidden(path):
214 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path]
215 ret = ioretry(cmd)
216 hidden = int(ret.split(':')[-1].strip())
217 return hidden
220def setHidden(path, hidden=True):
221 opt = "1"
222 if not hidden: 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true
223 opt = "0"
224 cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt]
225 ret = ioretry(cmd)
228def getSizeVirt(path):
229 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path]
230 ret = ioretry(cmd)
231 size = int(ret) * 1024 * 1024
232 return size
235def setSizeVirt(path, size, jFile):
236 "resize VHD offline"
237 size_mb = size // (1024 * 1024)
238 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path,
239 "-j", jFile]
240 ioretry(cmd)
243def setSizeVirtFast(path, size):
244 "resize VHD online"
245 size_mb = size // (1024 * 1024)
246 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"]
247 ioretry(cmd)
250def getMaxResizeSize(path):
251 """get the max virtual size for fast resize"""
252 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path]
253 ret = ioretry(cmd)
254 return int(ret)
257def getSizePhys(path):
258 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path]
259 ret = ioretry(cmd)
260 return int(ret)
263def setSizePhys(path, size, debug=True):
264 "set physical utilisation (applicable to VHD's on fixed-size files)"
265 if debug:
266 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path]
267 else:
268 cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path]
269 ioretry(cmd)
272def killData(path):
273 "zero out the disk (kill all data inside the VHD file)"
274 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path]
275 ioretry(cmd)
278def getDepth(path):
279 "get the VHD parent chain depth"
280 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path]
281 text = ioretry(cmd)
282 depth = -1
283 if text.startswith("chain depth:"):
284 depth = int(text.split(':')[1].strip())
285 return depth
288def getBlockBitmap(path):
289 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path]
290 text = ioretry(cmd, text=False)
291 return zlib.compress(text)
294def coalesce(path):
295 cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path]
296 ioretry(cmd)
299def create(path, size, static, msize=0):
300 size_mb = size // (1024 * 1024)
301 cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)]
302 if static:
303 cmd.append("-r")
304 if msize:
305 cmd.append("-S")
306 cmd.append(str(msize))
307 ioretry(cmd)
310def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True):
311 cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent]
312 if parentRaw:
313 cmd.append("-m")
314 if msize:
315 cmd.append("-S")
316 cmd.append(str(msize))
317 if not checkEmpty:
318 cmd.append("-e")
319 ioretry(cmd)
322def check(path, ignoreMissingFooter=False, fast=False):
323 cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path]
324 if ignoreMissingFooter:
325 cmd.append("-i")
326 if fast:
327 cmd.append("-B")
328 try:
329 ioretry(cmd)
330 return True
331 except util.CommandException:
332 return False
335def revert(path, jFile):
336 cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile]
337 ioretry(cmd)
340def _parseVHDInfo(line, extractUuidFunction):
341 vhdInfo = None
342 valueMap = line.split()
343 if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1:
344 return None
345 for keyval in valueMap:
346 (key, val) = keyval.split('=')
347 if key == "vhd":
348 uuid = extractUuidFunction(val)
349 if not uuid:
350 util.SMlog("***** malformed output, no UUID: %s" % valueMap)
351 return None
352 vhdInfo = VHDInfo(uuid)
353 vhdInfo.path = val
354 elif key == "scan-error":
355 vhdInfo.error = line
356 util.SMlog("***** VHD scan error: %s" % line)
357 break
358 elif key == "capacity":
359 vhdInfo.sizeVirt = int(val)
360 elif key == "size":
361 vhdInfo.sizePhys = int(val)
362 elif key == "hidden":
363 vhdInfo.hidden = int(val)
364 elif key == "parent" and val != "none":
365 vhdInfo.parentPath = val
366 vhdInfo.parentUuid = extractUuidFunction(val)
367 return vhdInfo
370def _getVHDParentNoCheck(path):
371 cmd = ["vhd-util", "read", "-p", "-n", "%s" % path]
372 text = util.pread(cmd)
373 util.SMlog(text)
374 for line in text.split('\n'):
375 if line.find("decoded name :") != -1:
376 val = line.split(':')[1].strip()
377 vdi = val.replace("--", "-")[-40:]
378 if vdi[1:].startswith("LV-"):
379 vdi = vdi[1:]
380 return vdi
381 return None
384def repair(path):
385 """Repairs the VHD."""
386 ioretry([VHD_UTIL, 'repair', '-n', path])
389def validate_and_round_vhd_size(size):
390 """ Take the supplied vhd size, in bytes, and check it is positive and less
391 that the maximum supported size, rounding up to the next block boundary
392 """
393 if size < 0 or size > MAX_VHD_SIZE:
394 raise xs_errors.XenError(
395 'VDISize', opterr='VDI size ' +
396 'must be between 1 MB and %d MB' %
397 (MAX_VHD_SIZE // (1024 * 1024)))
399 if size < MIN_VHD_SIZE: 399 ↛ 400line 399 didn't jump to line 400, because the condition on line 399 was never true
400 size = MIN_VHD_SIZE
402 size = util.roundup(VHD_BLOCK_SIZE, size)
404 return size
407def getKeyHash(path):
408 """Extract the hash of the encryption key from the header of an encrypted VHD"""
409 cmd = ["vhd-util", "key", "-p", "-n", path]
410 ret = ioretry(cmd)
411 ret = ret.strip()
412 if ret == 'none':
413 return None
414 vals = ret.split()
415 if len(vals) != 2:
416 util.SMlog('***** malformed output from vhd-util'
417 ' for VHD {}: "{}"'.format(path, ret))
418 return None
419 [_nonce, key_hash] = vals
420 return key_hash
423def setKey(path, key_hash):
424 """Set the encryption key for a VHD"""
425 cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash]
426 ioretry(cmd)