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 hidden = False 

56 parentUuid = "" 

57 parentPath = "" 

58 error = 0 

59 

60 def __init__(self, uuid): 

61 self.uuid = uuid 

62 

63 

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) 

69 

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

71 overhead = 3 * 1024 

72 

73 # BAT 4 Bytes per block segment 

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

75 overhead = util.roundup(512, overhead) 

76 

77 # BATMAP 1 bit per block segment 

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

79 overhead = util.roundup(4096, overhead) 

80 

81 return overhead 

82 

83 

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 

89 

90 

91def ioretry(cmd, text=True): 

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

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

94 

95 

96def getVHDInfo(path, extractUuidFunction, includeParent=True, resolveParent=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 if not resolveParent: 

104 opts += "u" 

105 

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

107 ret = ioretry(cmd) 

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

109 uuid = extractUuidFunction(path) 

110 vhdInfo = VHDInfo(uuid) 

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

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

113 nextIndex = 2 

114 if includeParent: 

115 if fields[nextIndex].find("no parent") == -1: 

116 vhdInfo.parentPath = fields[nextIndex] 

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

118 nextIndex += 1 

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

120 vhdInfo.path = path 

121 return vhdInfo 

122 

123 

124def getVHDInfoLVM(lvName, extractUuidFunction, vgName): 

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

126 active, but uses lvs & vgs""" 

127 vhdInfo = None 

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

129 ret = ioretry(cmd) 

130 return _parseVHDInfo(ret, extractUuidFunction) 

131 

132 

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

134 parentsOnly=False, exitOnError=False): 

135 vhds = dict() 

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

137 if vgName: 

138 cmd.append("-l") 

139 cmd.append(vgName) 

140 if parentsOnly: 

141 cmd.append("-a") 

142 try: 

143 ret = ioretry(cmd) 

144 except Exception as e: 

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

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

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

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

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

150 continue 

151 vhdInfo = _parseVHDInfo(line, extractUuidFunction) 

152 if vhdInfo: 

153 if vhdInfo.error != 0 and exitOnError: 

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

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

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

157 return dict() 

158 vhds[vhdInfo.uuid] = vhdInfo 

159 else: 

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

161 return vhds 

162 

163 

164def getParentChain(lvName, extractUuidFunction, vgName): 

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

166 as well""" 

167 chain = dict() 

168 vdis = dict() 

169 retries = 0 

170 while (not vdis): 

171 if retries > 60: 

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

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

174 break 

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

176 if (not vdis): 

177 retries = retries + 1 

178 time.sleep(1) 

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

180 chain[uuid] = vdi.path 

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

182 return chain 

183 

184 

185def getParent(path, extractUuidFunction): 

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

187 ret = ioretry(cmd) 

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

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

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

191 return None 

192 return extractUuidFunction(ret) 

193 

194 

195def hasParent(path): 

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

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

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

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

200 ret = ioretry(cmd) 

201 # pylint: disable=no-member 

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

203 vhd_type = m.group(1) 

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

205 return vhd_type == "Differencing" 

206 

207 

208def setParent(path, parentPath, parentRaw): 

209 normpath = os.path.normpath(parentPath) 

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

211 if parentRaw: 

212 cmd.append("-m") 

213 ioretry(cmd) 

214 

215 

216def getHidden(path): 

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

218 ret = ioretry(cmd) 

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

220 return hidden 

221 

222 

223def setHidden(path, hidden=True): 

224 opt = "1" 

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

226 opt = "0" 

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

228 ret = ioretry(cmd) 

229 

230 

231def getSizeVirt(path): 

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

233 ret = ioretry(cmd) 

234 size = int(ret) * 1024 * 1024 

235 return size 

236 

237 

238def setSizeVirt(path, size, jFile): 

239 "resize VHD offline" 

240 size_mb = size // (1024 * 1024) 

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

242 "-j", jFile] 

243 ioretry(cmd) 

244 

245 

246def setSizeVirtFast(path, size): 

247 "resize VHD online" 

248 size_mb = size // (1024 * 1024) 

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

250 ioretry(cmd) 

251 

252 

253def getMaxResizeSize(path): 

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

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

256 ret = ioretry(cmd) 

257 return int(ret) 

258 

259 

260def getSizePhys(path): 

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

262 ret = ioretry(cmd) 

263 return int(ret) 

264 

265 

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

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

268 if debug: 

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

270 else: 

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

272 ioretry(cmd) 

273 

274 

275def killData(path): 

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

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

278 ioretry(cmd) 

279 

280 

281def getDepth(path): 

282 "get the VHD parent chain depth" 

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

284 text = ioretry(cmd) 

285 depth = -1 

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

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

288 return depth 

289 

290 

291def getBlockBitmap(path): 

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

293 text = ioretry(cmd, text=False) 

294 return zlib.compress(text) 

295 

296 

297def coalesce(path): 

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

299 ioretry(cmd) 

300 

301 

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

303 size_mb = size // (1024 * 1024) 

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

305 if static: 

306 cmd.append("-r") 

307 if msize: 

308 cmd.append("-S") 

309 cmd.append(str(msize)) 

310 ioretry(cmd) 

311 

312 

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

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

315 if parentRaw: 

316 cmd.append("-m") 

317 if msize: 

318 cmd.append("-S") 

319 cmd.append(str(msize)) 

320 if not checkEmpty: 

321 cmd.append("-e") 

322 ioretry(cmd) 

323 

324 

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

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

327 if ignoreMissingFooter: 

328 cmd.append("-i") 

329 if fast: 

330 cmd.append("-B") 

331 try: 

332 ioretry(cmd) 

333 return True 

334 except util.CommandException: 

335 return False 

336 

337 

338def revert(path, jFile): 

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

340 ioretry(cmd) 

341 

342 

343def _parseVHDInfo(line, extractUuidFunction): 

344 vhdInfo = None 

345 valueMap = line.split() 

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

347 return None 

348 for keyval in valueMap: 

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

350 if key == "vhd": 

351 uuid = extractUuidFunction(val) 

352 if not uuid: 

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

354 return None 

355 vhdInfo = VHDInfo(uuid) 

356 vhdInfo.path = val 

357 elif key == "scan-error": 

358 vhdInfo.error = line 

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

360 break 

361 elif key == "capacity": 

362 vhdInfo.sizeVirt = int(val) 

363 elif key == "size": 

364 vhdInfo.sizePhys = int(val) 

365 elif key == "hidden": 

366 vhdInfo.hidden = int(val) 

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

368 vhdInfo.parentPath = val 

369 vhdInfo.parentUuid = extractUuidFunction(val) 

370 return vhdInfo 

371 

372 

373def _getVHDParentNoCheck(path): 

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

375 text = util.pread(cmd) 

376 util.SMlog(text) 

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

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

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

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

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

382 vdi = vdi[1:] 

383 return vdi 

384 return None 

385 

386 

387def repair(path): 

388 """Repairs the VHD.""" 

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

390 

391 

392def validate_and_round_vhd_size(size): 

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

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

395 """ 

396 if size < 0 or size > MAX_VHD_SIZE: 

397 raise xs_errors.XenError( 

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

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

400 (MAX_VHD_SIZE // (1024 * 1024))) 

401 

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

403 size = MIN_VHD_SIZE 

404 

405 size = util.roundup(VHD_BLOCK_SIZE, size) 

406 

407 return size 

408 

409 

410def getKeyHash(path): 

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

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

413 ret = ioretry(cmd) 

414 ret = ret.strip() 

415 if ret == 'none': 

416 return None 

417 vals = ret.split() 

418 if len(vals) != 2: 

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

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

421 return None 

422 [_nonce, key_hash] = vals 

423 return key_hash 

424 

425 

426def setKey(path, key_hash): 

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

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

429 ioretry(cmd)