Hide keyboard shortcuts

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# 

18 

19import os 

20import util 

21import errno 

22import zlib 

23import re 

24import xs_errors 

25import time 

26 

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 

35 

36# lock to lock the entire SR for short ops 

37LOCK_TYPE_SR = "sr" 

38 

39VDI_TYPE_VHD = 'vhd' 

40VDI_TYPE_RAW = 'aio' 

41 

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} 

48 

49 

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 

60 

61 def __init__(self, uuid): 

62 self.uuid = uuid 

63 

64 

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) 

70 

71 # Footer + footer copy + header + possible CoW parent locator fields 

72 overhead = 3 * 1024 

73 

74 # BAT 4 Bytes per block segment 

75 overhead += (size_mb // 2) * 4 

76 overhead = util.roundup(512, overhead) 

77 

78 # BATMAP 1 bit per block segment 

79 overhead += (size_mb // 2) // 8 

80 overhead = util.roundup(4096, overhead) 

81 

82 return overhead 

83 

84 

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 

90 

91 

92def ioretry(cmd, text=True): 

93 return util.ioretry(lambda: util.pread2(cmd, text=text), 

94 errlist=[errno.EIO, errno.EAGAIN]) 

95 

96 

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" 

106 

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 

124 

125 

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) 

133 

134 

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 

164 

165 

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 

185 

186 

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) 

195 

196 

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" 

208 

209 

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) 

216 

217 

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 

223 

224 

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) 

231 

232 

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 

238 

239 

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) 

246 

247 

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) 

253 

254 

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) 

260 

261 

262def getSizePhys(path): 

263 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path] 

264 ret = ioretry(cmd) 

265 return int(ret) 

266 

267 

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) 

275 

276 

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 

282 

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) 

287 

288 

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 

297 

298 

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) 

303 

304 

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)) 

314 

315 return 0 

316 

317 

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) 

327 

328 

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) 

339 

340 

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 

352 

353 

354def revert(path, jFile): 

355 cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile] 

356 ioretry(cmd) 

357 

358 

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 

387 

388 

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 

401 

402 

403def repair(path): 

404 """Repairs the VHD.""" 

405 ioretry([VHD_UTIL, 'repair', '-n', path]) 

406 

407 

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))) 

417 

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 

420 

421 size = util.roundup(VHD_BLOCK_SIZE, size) 

422 

423 return size 

424 

425 

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 

440 

441 

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)