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 allocatedBlocks = int(fields[nextIndex+1]) 

122 vhdInfo.sizeAllocated = allocatedBlocks * 2 * 1024 * 1024 

123 vhdInfo.path = path 

124 return vhdInfo 

125 

126 

127def getVHDInfoLVM(lvName, extractUuidFunction, vgName): 

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

129 active, but uses lvs & vgs""" 

130 vhdInfo = None 

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

132 ret = ioretry(cmd) 

133 return _parseVHDInfo(ret, extractUuidFunction) 

134 

135 

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

137 parentsOnly=False, exitOnError=False): 

138 vhds = dict() 

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

140 if vgName: 

141 cmd.append("-l") 

142 cmd.append(vgName) 

143 if parentsOnly: 

144 cmd.append("-a") 

145 try: 

146 ret = ioretry(cmd) 

147 except Exception as e: 

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

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

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

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

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

153 continue 

154 vhdInfo = _parseVHDInfo(line, extractUuidFunction) 

155 if vhdInfo: 

156 if vhdInfo.error != 0 and exitOnError: 

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

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

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

160 return dict() 

161 vhds[vhdInfo.uuid] = vhdInfo 

162 else: 

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

164 return vhds 

165 

166 

167def getParentChain(lvName, extractUuidFunction, vgName): 

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

169 as well""" 

170 chain = dict() 

171 vdis = dict() 

172 retries = 0 

173 while (not vdis): 

174 if retries > 60: 

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

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

177 break 

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

179 if (not vdis): 

180 retries = retries + 1 

181 time.sleep(1) 

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

183 chain[uuid] = vdi.path 

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

185 return chain 

186 

187 

188def getParent(path, extractUuidFunction): 

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

190 ret = ioretry(cmd) 

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

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

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

194 return None 

195 return extractUuidFunction(ret) 

196 

197 

198def hasParent(path): 

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

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

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

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

203 ret = ioretry(cmd) 

204 # pylint: disable=no-member 

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

206 vhd_type = m.group(1) 

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

208 return vhd_type == "Differencing" 

209 

210 

211def setParent(path, parentPath, parentRaw): 

212 normpath = os.path.normpath(parentPath) 

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

214 if parentRaw: 

215 cmd.append("-m") 

216 ioretry(cmd) 

217 

218 

219def getHidden(path): 

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

221 ret = ioretry(cmd) 

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

223 return hidden 

224 

225 

226def setHidden(path, hidden=True): 

227 opt = "1" 

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

229 opt = "0" 

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

231 ret = ioretry(cmd) 

232 

233 

234def getSizeVirt(path): 

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

236 ret = ioretry(cmd) 

237 size = int(ret) * 1024 * 1024 

238 return size 

239 

240 

241def setSizeVirt(path, size, jFile): 

242 "resize VHD offline" 

243 size_mb = size // (1024 * 1024) 

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

245 "-j", jFile] 

246 ioretry(cmd) 

247 

248 

249def setSizeVirtFast(path, size): 

250 "resize VHD online" 

251 size_mb = size // (1024 * 1024) 

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

253 ioretry(cmd) 

254 

255 

256def getMaxResizeSize(path): 

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

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

259 ret = ioretry(cmd) 

260 return int(ret) 

261 

262 

263def getSizePhys(path): 

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

265 ret = ioretry(cmd) 

266 return int(ret) 

267 

268 

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

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

271 if debug: 

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

273 else: 

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

275 ioretry(cmd) 

276 

277 

278def getAllocatedSize(path): 

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

280 ret = ioretry(cmd) 

281 # Assume we have standard 2MB allocation blocks 

282 return int(ret) * 2 * 1024 * 1024 

283 

284def killData(path): 

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

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

287 ioretry(cmd) 

288 

289 

290def getDepth(path): 

291 "get the VHD parent chain depth" 

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

293 text = ioretry(cmd) 

294 depth = -1 

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

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

297 return depth 

298 

299 

300def getBlockBitmap(path): 

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

302 text = ioretry(cmd, text=False) 

303 return zlib.compress(text) 

304 

305 

306def coalesce(path): 

307 """ 

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

309 """ 

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

311 text = ioretry(cmd) 

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

313 if match: 

314 return int(match.group(1)) 

315 

316 return 0 

317 

318 

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

320 size_mb = size // (1024 * 1024) 

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

322 if static: 

323 cmd.append("-r") 

324 if msize: 

325 cmd.append("-S") 

326 cmd.append(str(msize)) 

327 ioretry(cmd) 

328 

329 

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

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

332 if parentRaw: 

333 cmd.append("-m") 

334 if msize: 

335 cmd.append("-S") 

336 cmd.append(str(msize)) 

337 if not checkEmpty: 

338 cmd.append("-e") 

339 ioretry(cmd) 

340 

341 

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

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

344 if ignoreMissingFooter: 

345 cmd.append("-i") 

346 if fast: 

347 cmd.append("-B") 

348 try: 

349 ioretry(cmd) 

350 return True 

351 except util.CommandException: 

352 return False 

353 

354 

355def revert(path, jFile): 

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

357 ioretry(cmd) 

358 

359 

360def _parseVHDInfo(line, extractUuidFunction): 

361 vhdInfo = None 

362 valueMap = line.split() 

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

364 return None 

365 for keyval in valueMap: 

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

367 if key == "vhd": 

368 uuid = extractUuidFunction(val) 

369 if not uuid: 

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

371 return None 

372 vhdInfo = VHDInfo(uuid) 

373 vhdInfo.path = val 

374 elif key == "scan-error": 

375 vhdInfo.error = line 

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

377 break 

378 elif key == "capacity": 

379 vhdInfo.sizeVirt = int(val) 

380 elif key == "size": 

381 vhdInfo.sizePhys = int(val) 

382 elif key == "hidden": 

383 vhdInfo.hidden = int(val) 

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

385 vhdInfo.parentPath = val 

386 vhdInfo.parentUuid = extractUuidFunction(val) 

387 return vhdInfo 

388 

389 

390def _getVHDParentNoCheck(path): 

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

392 text = util.pread(cmd) 

393 util.SMlog(text) 

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

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

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

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

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

399 vdi = vdi[1:] 

400 return vdi 

401 return None 

402 

403 

404def repair(path): 

405 """Repairs the VHD.""" 

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

407 

408 

409def validate_and_round_vhd_size(size): 

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

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

412 """ 

413 if size < 0 or size > MAX_VHD_SIZE: 

414 raise xs_errors.XenError( 

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

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

417 (MAX_VHD_SIZE // (1024 * 1024))) 

418 

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

420 size = MIN_VHD_SIZE 

421 

422 size = util.roundup(VHD_BLOCK_SIZE, size) 

423 

424 return size 

425 

426 

427def getKeyHash(path): 

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

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

430 ret = ioretry(cmd) 

431 ret = ret.strip() 

432 if ret == 'none': 

433 return None 

434 vals = ret.split() 

435 if len(vals) != 2: 

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

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

438 return None 

439 [_nonce, key_hash] = vals 

440 return key_hash 

441 

442 

443def setKey(path, key_hash): 

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

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

446 ioretry(cmd)