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 convertAllocatedSizeToBytes(size): 

98 # Assume we have standard 2MB allocation blocks 

99 return size * 2 * 1024 * 1024 

100 

101 

102def getVHDInfo(path, extractUuidFunction, includeParent=True, resolveParent=True): 

103 """Get the VHD info. The parent info may optionally be omitted: vhd-util 

104 tries to verify the parent by opening it, which results in error if the VHD 

105 resides on an inactive LV""" 

106 opts = "-vsaf" 

107 if includeParent: 107 ↛ 112line 107 didn't jump to line 112, because the condition on line 107 was never false

108 opts += "p" 

109 if not resolveParent: 109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true

110 opts += "u" 

111 

112 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path] 

113 ret = ioretry(cmd) 

114 fields = ret.strip().split('\n') 

115 uuid = extractUuidFunction(path) 

116 vhdInfo = VHDInfo(uuid) 

117 vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024 

118 vhdInfo.sizePhys = int(fields[1]) 

119 nextIndex = 2 

120 if includeParent: 120 ↛ 125line 120 didn't jump to line 125, because the condition on line 120 was never false

121 if fields[nextIndex].find("no parent") == -1: 121 ↛ 122line 121 didn't jump to line 122, because the condition on line 121 was never true

122 vhdInfo.parentPath = fields[nextIndex] 

123 vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex]) 

124 nextIndex += 1 

125 vhdInfo.hidden = int(fields[nextIndex].replace("hidden: ", "")) 

126 vhdInfo.sizeAllocated = convertAllocatedSizeToBytes(int(fields[nextIndex+1])) 

127 vhdInfo.path = path 

128 return vhdInfo 

129 

130 

131def getVHDInfoLVM(lvName, extractUuidFunction, vgName): 

132 """Get the VHD info. This function does not require the container LV to be 

133 active, but uses lvs & vgs""" 

134 vhdInfo = None 

135 cmd = [VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName] 

136 ret = ioretry(cmd) 

137 return _parseVHDInfo(ret, extractUuidFunction) 

138 

139 

140def getAllVHDs(pattern, extractUuidFunction, vgName=None, \ 

141 parentsOnly=False, exitOnError=False): 

142 vhds = dict() 

143 cmd = [VHD_UTIL, "scan", "-f", "-m", pattern] 

144 if vgName: 

145 cmd.append("-l") 

146 cmd.append(vgName) 

147 if parentsOnly: 

148 cmd.append("-a") 

149 try: 

150 ret = ioretry(cmd) 

151 except Exception as e: 

152 util.SMlog("WARN: vhd scan failed: output: %s" % e) 

153 ret = ioretry(cmd + ["-c"]) 

154 util.SMlog("WARN: vhd scan with NOFAIL flag, output: %s" % ret) 

155 for line in ret.split('\n'): 

156 if len(line.strip()) == 0: 

157 continue 

158 vhdInfo = _parseVHDInfo(line, extractUuidFunction) 

159 if vhdInfo: 

160 if vhdInfo.error != 0 and exitOnError: 

161 # Just return an empty dict() so the scan will be done 

162 # again by getParentChain. See CA-177063 for details on 

163 # how this has been discovered during the stress tests. 

164 return dict() 

165 vhds[vhdInfo.uuid] = vhdInfo 

166 else: 

167 util.SMlog("WARN: vhdinfo line doesn't parse correctly: %s" % line) 

168 return vhds 

169 

170 

171def getParentChain(lvName, extractUuidFunction, vgName): 

172 """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's 

173 as well""" 

174 chain = dict() 

175 vdis = dict() 

176 retries = 0 

177 while (not vdis): 

178 if retries > 60: 

179 util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries) 

180 util.SMlog('ERROR: the VHD metadata might be corrupted') 

181 break 

182 vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True) 

183 if (not vdis): 

184 retries = retries + 1 

185 time.sleep(1) 

186 for uuid, vdi in vdis.items(): 

187 chain[uuid] = vdi.path 

188 #util.SMlog("Parent chain for %s: %s" % (lvName, chain)) 

189 return chain 

190 

191 

192def getParent(path, extractUuidFunction): 

193 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path] 

194 ret = ioretry(cmd) 

195 if ret.find("query failed") != -1 or ret.find("Failed opening") != -1: 

196 raise util.SMException("VHD query returned %s" % ret) 

197 if ret.find("no parent") != -1: 

198 return None 

199 return extractUuidFunction(ret) 

200 

201 

202def hasParent(path): 

203 """Check if the VHD has a parent. A VHD has a parent iff its type is 

204 'Differencing'. This function does not need the parent to actually 

205 be present (e.g. the parent LV to be activated).""" 

206 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path] 

207 ret = ioretry(cmd) 

208 # pylint: disable=no-member 

209 m = re.match(r".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S) 

210 vhd_type = m.group(1) 

211 assert(vhd_type == "Differencing" or vhd_type == "Dynamic") 

212 return vhd_type == "Differencing" 

213 

214 

215def setParent(path, parentPath, parentRaw): 

216 normpath = os.path.normpath(parentPath) 

217 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path] 

218 if parentRaw: 

219 cmd.append("-m") 

220 ioretry(cmd) 

221 

222 

223def getHidden(path): 

224 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path] 

225 ret = ioretry(cmd) 

226 hidden = int(ret.split(':')[-1].strip()) 

227 return hidden 

228 

229 

230def setHidden(path, hidden=True): 

231 opt = "1" 

232 if not hidden: 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true

233 opt = "0" 

234 cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt] 

235 ret = ioretry(cmd) 

236 

237 

238def getSizeVirt(path): 

239 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path] 

240 ret = ioretry(cmd) 

241 size = int(ret) * 1024 * 1024 

242 return size 

243 

244 

245def setSizeVirt(path, size, jFile): 

246 "resize VHD offline" 

247 size_mb = size // (1024 * 1024) 

248 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, 

249 "-j", jFile] 

250 ioretry(cmd) 

251 

252 

253def setSizeVirtFast(path, size): 

254 "resize VHD online" 

255 size_mb = size // (1024 * 1024) 

256 cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"] 

257 ioretry(cmd) 

258 

259 

260def getMaxResizeSize(path): 

261 """get the max virtual size for fast resize""" 

262 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path] 

263 ret = ioretry(cmd) 

264 return int(ret) 

265 

266 

267def getSizePhys(path): 

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

269 ret = ioretry(cmd) 

270 return int(ret) 

271 

272 

273def setSizePhys(path, size, debug=True): 

274 "set physical utilisation (applicable to VHD's on fixed-size files)" 

275 if debug: 

276 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path] 

277 else: 

278 cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path] 

279 ioretry(cmd) 

280 

281 

282def getAllocatedSize(path): 

283 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, '-a', '-n', path] 

284 ret = ioretry(cmd) 

285 return convertAllocatedSizeToBytes(int(ret)) 

286 

287def killData(path): 

288 "zero out the disk (kill all data inside the VHD file)" 

289 cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path] 

290 ioretry(cmd) 

291 

292 

293def getDepth(path): 

294 "get the VHD parent chain depth" 

295 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path] 

296 text = ioretry(cmd) 

297 depth = -1 

298 if text.startswith("chain depth:"): 

299 depth = int(text.split(':')[1].strip()) 

300 return depth 

301 

302 

303def getBlockBitmap(path): 

304 cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path] 

305 text = ioretry(cmd, text=False) 

306 return zlib.compress(text) 

307 

308 

309def coalesce(path): 

310 """ 

311 Coalesce the VHD, on success it returns the number of sectors coalesced 

312 """ 

313 cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path] 

314 text = ioretry(cmd) 

315 match = re.match(r'^Coalesced (\d+) sectors', text) 

316 if match: 

317 return int(match.group(1)) 

318 

319 return 0 

320 

321 

322def create(path, size, static, msize=0): 

323 size_mb = size // (1024 * 1024) 

324 cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)] 

325 if static: 

326 cmd.append("-r") 

327 if msize: 

328 cmd.append("-S") 

329 cmd.append(str(msize)) 

330 ioretry(cmd) 

331 

332 

333def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True): 

334 cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent] 

335 if parentRaw: 

336 cmd.append("-m") 

337 if msize: 

338 cmd.append("-S") 

339 cmd.append(str(msize)) 

340 if not checkEmpty: 

341 cmd.append("-e") 

342 ioretry(cmd) 

343 

344 

345def check(path, ignoreMissingFooter=False, fast=False): 

346 cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path] 

347 if ignoreMissingFooter: 

348 cmd.append("-i") 

349 if fast: 

350 cmd.append("-B") 

351 try: 

352 ioretry(cmd) 

353 return True 

354 except util.CommandException: 

355 return False 

356 

357 

358def revert(path, jFile): 

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

360 ioretry(cmd) 

361 

362 

363def _parseVHDInfo(line, extractUuidFunction): 

364 vhdInfo = None 

365 valueMap = line.split() 

366 if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1: 

367 return None 

368 for keyval in valueMap: 

369 (key, val) = keyval.split('=') 

370 if key == "vhd": 

371 uuid = extractUuidFunction(val) 

372 if not uuid: 

373 util.SMlog("***** malformed output, no UUID: %s" % valueMap) 

374 return None 

375 vhdInfo = VHDInfo(uuid) 

376 vhdInfo.path = val 

377 elif key == "scan-error": 

378 vhdInfo.error = line 

379 util.SMlog("***** VHD scan error: %s" % line) 

380 break 

381 elif key == "capacity": 

382 vhdInfo.sizeVirt = int(val) 

383 elif key == "size": 

384 vhdInfo.sizePhys = int(val) 

385 elif key == "hidden": 

386 vhdInfo.hidden = int(val) 

387 elif key == "parent" and val != "none": 

388 vhdInfo.parentPath = val 

389 vhdInfo.parentUuid = extractUuidFunction(val) 

390 return vhdInfo 

391 

392 

393def _getVHDParentNoCheck(path): 

394 cmd = ["vhd-util", "read", "-p", "-n", "%s" % path] 

395 text = util.pread(cmd) 

396 util.SMlog(text) 

397 for line in text.split('\n'): 

398 if line.find("decoded name :") != -1: 

399 val = line.split(':')[1].strip() 

400 vdi = val.replace("--", "-")[-40:] 

401 if vdi[1:].startswith("LV-"): 

402 vdi = vdi[1:] 

403 return vdi 

404 return None 

405 

406 

407def repair(path): 

408 """Repairs the VHD.""" 

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

410 

411 

412def validate_and_round_vhd_size(size): 

413 """ Take the supplied vhd size, in bytes, and check it is positive and less 

414 that the maximum supported size, rounding up to the next block boundary 

415 """ 

416 if size < 0 or size > MAX_VHD_SIZE: 

417 raise xs_errors.XenError( 

418 'VDISize', opterr='VDI size ' + 

419 'must be between 1 MB and %d MB' % 

420 (MAX_VHD_SIZE // (1024 * 1024))) 

421 

422 if size < MIN_VHD_SIZE: 422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true

423 size = MIN_VHD_SIZE 

424 

425 size = util.roundup(VHD_BLOCK_SIZE, size) 

426 

427 return size 

428 

429 

430def getKeyHash(path): 

431 """Extract the hash of the encryption key from the header of an encrypted VHD""" 

432 cmd = ["vhd-util", "key", "-p", "-n", path] 

433 ret = ioretry(cmd) 

434 ret = ret.strip() 

435 if ret == 'none': 

436 return None 

437 vals = ret.split() 

438 if len(vals) != 2: 

439 util.SMlog('***** malformed output from vhd-util' 

440 ' for VHD {}: "{}"'.format(path, ret)) 

441 return None 

442 [_nonce, key_hash] = vals 

443 return key_hash 

444 

445 

446def setKey(path, key_hash): 

447 """Set the encryption key for a VHD""" 

448 cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash] 

449 ioretry(cmd)