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

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 cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path] 

104 ret = ioretry(cmd) 

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

106 uuid = extractUuidFunction(path) 

107 vhdInfo = VHDInfo(uuid) 

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

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

110 nextIndex = 2 

111 if includeParent: 

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

113 vhdInfo.parentPath = fields[nextIndex] 

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

115 nextIndex += 1 

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

117 vhdInfo.path = path 

118 return vhdInfo 

119 

120 

121def getVHDInfoLVM(lvName, extractUuidFunction, vgName): 

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

123 active, but uses lvs & vgs""" 

124 vhdInfo = None 

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

126 ret = ioretry(cmd) 

127 return _parseVHDInfo(ret, extractUuidFunction) 

128 

129 

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

131 parentsOnly=False, exitOnError=False): 

132 vhds = dict() 

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

134 if vgName: 

135 cmd.append("-l") 

136 cmd.append(vgName) 

137 if parentsOnly: 

138 cmd.append("-a") 

139 try: 

140 ret = ioretry(cmd) 

141 except Exception as e: 

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

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

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

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

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

147 continue 

148 vhdInfo = _parseVHDInfo(line, extractUuidFunction) 

149 if vhdInfo: 

150 if vhdInfo.error != 0 and exitOnError: 

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

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

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

154 return dict() 

155 vhds[vhdInfo.uuid] = vhdInfo 

156 else: 

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

158 return vhds 

159 

160 

161def getParentChain(lvName, extractUuidFunction, vgName): 

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

163 as well""" 

164 chain = dict() 

165 vdis = dict() 

166 retries = 0 

167 while (not vdis): 

168 if retries > 60: 

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

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

171 break 

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

173 if (not vdis): 

174 retries = retries + 1 

175 time.sleep(1) 

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

177 chain[uuid] = vdi.path 

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

179 return chain 

180 

181 

182def getParent(path, extractUuidFunction): 

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

184 ret = ioretry(cmd) 

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

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

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

188 return None 

189 return extractUuidFunction(ret) 

190 

191 

192def hasParent(path): 

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

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

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

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

197 ret = ioretry(cmd) 

198 # pylint: disable=no-member 

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

200 vhd_type = m.group(1) 

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

202 return vhd_type == "Differencing" 

203 

204 

205def setParent(path, parentPath, parentRaw): 

206 normpath = os.path.normpath(parentPath) 

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

208 if parentRaw: 

209 cmd.append("-m") 

210 ioretry(cmd) 

211 

212 

213def getHidden(path): 

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

215 ret = ioretry(cmd) 

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

217 return hidden 

218 

219 

220def setHidden(path, hidden=True): 

221 opt = "1" 

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

223 opt = "0" 

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

225 ret = ioretry(cmd) 

226 

227 

228def getSizeVirt(path): 

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

230 ret = ioretry(cmd) 

231 size = int(ret) * 1024 * 1024 

232 return size 

233 

234 

235def setSizeVirt(path, size, jFile): 

236 "resize VHD offline" 

237 size_mb = size // (1024 * 1024) 

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

239 "-j", jFile] 

240 ioretry(cmd) 

241 

242 

243def setSizeVirtFast(path, size): 

244 "resize VHD online" 

245 size_mb = size // (1024 * 1024) 

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

247 ioretry(cmd) 

248 

249 

250def getMaxResizeSize(path): 

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

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

253 ret = ioretry(cmd) 

254 return int(ret) 

255 

256 

257def getSizePhys(path): 

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

259 ret = ioretry(cmd) 

260 return int(ret) 

261 

262 

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

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

265 if debug: 

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

267 else: 

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

269 ioretry(cmd) 

270 

271 

272def killData(path): 

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

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

275 ioretry(cmd) 

276 

277 

278def getDepth(path): 

279 "get the VHD parent chain depth" 

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

281 text = ioretry(cmd) 

282 depth = -1 

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

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

285 return depth 

286 

287 

288def getBlockBitmap(path): 

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

290 text = ioretry(cmd, text=False) 

291 return zlib.compress(text) 

292 

293 

294def coalesce(path): 

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

296 ioretry(cmd) 

297 

298 

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

300 size_mb = size // (1024 * 1024) 

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

302 if static: 

303 cmd.append("-r") 

304 if msize: 

305 cmd.append("-S") 

306 cmd.append(str(msize)) 

307 ioretry(cmd) 

308 

309 

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

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

312 if parentRaw: 

313 cmd.append("-m") 

314 if msize: 

315 cmd.append("-S") 

316 cmd.append(str(msize)) 

317 if not checkEmpty: 

318 cmd.append("-e") 

319 ioretry(cmd) 

320 

321 

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

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

324 if ignoreMissingFooter: 

325 cmd.append("-i") 

326 if fast: 

327 cmd.append("-B") 

328 try: 

329 ioretry(cmd) 

330 return True 

331 except util.CommandException: 

332 return False 

333 

334 

335def revert(path, jFile): 

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

337 ioretry(cmd) 

338 

339 

340def _parseVHDInfo(line, extractUuidFunction): 

341 vhdInfo = None 

342 valueMap = line.split() 

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

344 return None 

345 for keyval in valueMap: 

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

347 if key == "vhd": 

348 uuid = extractUuidFunction(val) 

349 if not uuid: 

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

351 return None 

352 vhdInfo = VHDInfo(uuid) 

353 vhdInfo.path = val 

354 elif key == "scan-error": 

355 vhdInfo.error = line 

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

357 break 

358 elif key == "capacity": 

359 vhdInfo.sizeVirt = int(val) 

360 elif key == "size": 

361 vhdInfo.sizePhys = int(val) 

362 elif key == "hidden": 

363 vhdInfo.hidden = int(val) 

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

365 vhdInfo.parentPath = val 

366 vhdInfo.parentUuid = extractUuidFunction(val) 

367 return vhdInfo 

368 

369 

370def _getVHDParentNoCheck(path): 

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

372 text = util.pread(cmd) 

373 util.SMlog(text) 

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

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

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

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

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

379 vdi = vdi[1:] 

380 return vdi 

381 return None 

382 

383 

384def repair(path): 

385 """Repairs the VHD.""" 

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

387 

388 

389def validate_and_round_vhd_size(size): 

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

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

392 """ 

393 if size < 0 or size > MAX_VHD_SIZE: 

394 raise xs_errors.XenError( 

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

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

397 (MAX_VHD_SIZE // (1024 * 1024))) 

398 

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

400 size = MIN_VHD_SIZE 

401 

402 size = util.roundup(VHD_BLOCK_SIZE, size) 

403 

404 return size 

405 

406 

407def getKeyHash(path): 

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

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

410 ret = ioretry(cmd) 

411 ret = ret.strip() 

412 if ret == 'none': 

413 return None 

414 vals = ret.split() 

415 if len(vals) != 2: 

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

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

418 return None 

419 [_nonce, key_hash] = vals 

420 return key_hash 

421 

422 

423def setKey(path, key_hash): 

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

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

426 ioretry(cmd)