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 util.SMlog("DEBUG SCAN {}".format(pattern)) 

169 for uuid in vhds: 

170 vhdinfo = vhds[uuid] 

171 util.SMlog("DEBUG: UUID {}: {}".format(uuid, vhdinfo.__dict__)) 

172 util.SMlog("DEBUG SCAN END") 

173 return vhds 

174 

175 

176def getParentChain(lvName, extractUuidFunction, vgName): 

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

178 as well""" 

179 chain = dict() 

180 vdis = dict() 

181 retries = 0 

182 while (not vdis): 

183 if retries > 60: 

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

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

186 break 

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

188 if (not vdis): 

189 retries = retries + 1 

190 time.sleep(1) 

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

192 chain[uuid] = vdi.path 

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

194 return chain 

195 

196 

197def getParent(path, extractUuidFunction): 

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

199 ret = ioretry(cmd) 

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

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

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

203 return None 

204 return extractUuidFunction(ret) 

205 

206 

207def hasParent(path): 

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

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

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

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

212 ret = ioretry(cmd) 

213 # pylint: disable=no-member 

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

215 vhd_type = m.group(1) 

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

217 return vhd_type == "Differencing" 

218 

219 

220def setParent(path, parentPath, parentRaw): 

221 normpath = os.path.normpath(parentPath) 

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

223 if parentRaw: 

224 cmd.append("-m") 

225 ioretry(cmd) 

226 

227 

228def getHidden(path): 

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

230 ret = ioretry(cmd) 

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

232 return hidden 

233 

234 

235def setHidden(path, hidden=True): 

236 opt = "1" 

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

238 opt = "0" 

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

240 ret = ioretry(cmd) 

241 

242 

243def getSizeVirt(path): 

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

245 ret = ioretry(cmd) 

246 size = int(ret) * 1024 * 1024 

247 return size 

248 

249 

250def setSizeVirt(path, size, jFile): 

251 "resize VHD offline" 

252 size_mb = size // (1024 * 1024) 

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

254 "-j", jFile] 

255 ioretry(cmd) 

256 

257 

258def setSizeVirtFast(path, size): 

259 "resize VHD online" 

260 size_mb = size // (1024 * 1024) 

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

262 ioretry(cmd) 

263 

264 

265def getMaxResizeSize(path): 

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

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

268 ret = ioretry(cmd) 

269 return int(ret) 

270 

271 

272def getSizePhys(path): 

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

274 ret = ioretry(cmd) 

275 return int(ret) 

276 

277 

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

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

280 if debug: 

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

282 else: 

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

284 ioretry(cmd) 

285 

286 

287def getAllocatedSize(path): 

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

289 ret = ioretry(cmd) 

290 return convertAllocatedSizeToBytes(int(ret)) 

291 

292def killData(path): 

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

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

295 ioretry(cmd) 

296 

297 

298def getDepth(path): 

299 "get the VHD parent chain depth" 

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

301 text = ioretry(cmd) 

302 depth = -1 

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

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

305 return depth 

306 

307 

308def getBlockBitmap(path): 

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

310 text = ioretry(cmd, text=False) 

311 return zlib.compress(text) 

312 

313 

314def coalesce(path): 

315 """ 

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

317 """ 

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

319 text = ioretry(cmd) 

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

321 if match: 

322 return int(match.group(1)) 

323 

324 return 0 

325 

326 

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

328 size_mb = size // (1024 * 1024) 

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

330 if static: 

331 cmd.append("-r") 

332 if msize: 

333 cmd.append("-S") 

334 cmd.append(str(msize)) 

335 ioretry(cmd) 

336 

337 

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

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

340 if parentRaw: 

341 cmd.append("-m") 

342 if msize: 

343 cmd.append("-S") 

344 cmd.append(str(msize)) 

345 if not checkEmpty: 

346 cmd.append("-e") 

347 ioretry(cmd) 

348 

349 

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

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

352 if ignoreMissingFooter: 

353 cmd.append("-i") 

354 if fast: 

355 cmd.append("-B") 

356 try: 

357 ioretry(cmd) 

358 return True 

359 except util.CommandException: 

360 return False 

361 

362 

363def revert(path, jFile): 

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

365 ioretry(cmd) 

366 

367 

368def _parseVHDInfo(line, extractUuidFunction): 

369 vhdInfo = None 

370 valueMap = line.split() 

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

372 return None 

373 for keyval in valueMap: 

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

375 if key == "vhd": 

376 uuid = extractUuidFunction(val) 

377 if not uuid: 

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

379 return None 

380 vhdInfo = VHDInfo(uuid) 

381 vhdInfo.path = val 

382 elif key == "scan-error": 

383 vhdInfo.error = line 

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

385 break 

386 elif key == "capacity": 

387 vhdInfo.sizeVirt = int(val) 

388 elif key == "size": 

389 vhdInfo.sizePhys = int(val) 

390 elif key == "hidden": 

391 vhdInfo.hidden = int(val) 

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

393 vhdInfo.parentPath = val 

394 vhdInfo.parentUuid = extractUuidFunction(val) 

395 return vhdInfo 

396 

397 

398def _getVHDParentNoCheck(path): 

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

400 text = util.pread(cmd) 

401 util.SMlog(text) 

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

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

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

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

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

407 vdi = vdi[1:] 

408 return vdi 

409 return None 

410 

411 

412def repair(path): 

413 """Repairs the VHD.""" 

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

415 

416 

417def validate_and_round_vhd_size(size): 

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

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

420 """ 

421 if size < 0 or size > MAX_VHD_SIZE: 

422 raise xs_errors.XenError( 

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

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

425 (MAX_VHD_SIZE // (1024 * 1024))) 

426 

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

428 size = MIN_VHD_SIZE 

429 

430 size = util.roundup(VHD_BLOCK_SIZE, size) 

431 

432 return size 

433 

434 

435def getKeyHash(path): 

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

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

438 ret = ioretry(cmd) 

439 ret = ret.strip() 

440 if ret == 'none': 

441 return None 

442 vals = ret.split() 

443 if len(vals) != 2: 

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

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

446 return None 

447 [_nonce, key_hash] = vals 

448 return key_hash 

449 

450 

451def setKey(path, key_hash): 

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

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

454 ioretry(cmd)