Coverage for libs/sm/SRCommand.py: 42%

253 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2026-07-10 07:59 +0000

1# 

2# Copyright (C) Citrix Systems Inc. 

3# 

4# This program is free software; you can redistribute it and/or modify 

5# it under the terms of the GNU Lesser General Public License as published 

6# by the Free Software Foundation; version 2.1 only. 

7# 

8# This program is distributed in the hope that it will be useful, 

9# but WITHOUT ANY WARRANTY; without even the implied warranty of 

10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

11# GNU Lesser General Public License for more details. 

12# 

13# You should have received a copy of the GNU Lesser General Public License 

14# along with this program; if not, write to the Free Software Foundation, Inc., 

15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

16# 

17# SRCommand: parse SR command-line objects 

18# 

19import traceback 

20 

21import XenAPI # pylint: disable=import-error 

22import sys 

23from sm.core import xs_errors 

24import xmlrpc.client 

25from xmlrpc.client import ProtocolError 

26from sm import SR 

27from sm.core import util 

28from sm import blktap2 

29from sm import resetvdis 

30import os 

31 

32NEEDS_VDI_OBJECT = [ 

33 "vdi_update", "vdi_create", "vdi_delete", "vdi_snapshot", "vdi_clone", 

34 "vdi_resize", "vdi_resize_online", "vdi_attach", "vdi_detach", 

35 "vdi_activate", "vdi_deactivate", "vdi_attach_from_config", "vdi_detach_from_config", 

36 "vdi_generate_config", "vdi_compose", "vdi_epoch_begin", 

37 "vdi_epoch_end", "vdi_enable_cbt", "vdi_disable_cbt", "vdi_data_destroy", 

38 "vdi_list_changed_blocks"] 

39 

40# don't log the commands that spam the log file too much 

41NO_LOGGING = { 

42 "iso": ["sr_scan"], 

43 "nfs_iso": ["sr_scan"] 

44} 

45 

46EXCEPTION_TYPE = { 

47 "sr_scan": "SRScan", 

48 "vdi_init": "VDILoad", 

49 "vdi_create": "VDICreate", 

50 "vdi_delete": "VDIDelete", 

51 "vdi_attach": "VDIUnavailable", 

52 "vdi_detach": "VDIUnavailable", 

53 "vdi_activate": "VDIUnavailable", 

54 "vdi_deactivate": "VDIUnavailable", 

55 "vdi_resize": "VDIResize", 

56 "vdi_resize_online": "VDIResize", 

57 "vdi_snapshot": "VDISnapshot", 

58 "vdi_clone": "VDIClone" 

59} 

60 

61 

62class SRCommand: 

63 def __init__(self, driver_info): 

64 self.dconf = '' 

65 self.type = '' 

66 self.sr_uuid = '' 

67 self.cmdname = '' 

68 self.cmdtype = '' 

69 self.cmd = None 

70 self.args = None 

71 self.driver_info = driver_info 

72 

73 def parse(self): 

74 if len(sys.argv) != 2: 74 ↛ 75line 74 didn't jump to line 75, because the condition on line 74 was never true

75 util.SMlog("Failed to parse commandline; wrong number of arguments; argv = %s" % (repr(sys.argv))) 

76 raise xs_errors.XenError('BadRequest') 

77 

78 # Debug logging of the actual incoming command from the caller. 

79 # util.SMlog( "" ) 

80 # util.SMlog( "SM.parse: DEBUG: args = %s,\n%s" % \ 

81 # ( sys.argv[0], \ 

82 # util.splitXmlText( util.hideMemberValuesInXmlParams( \ 

83 # sys.argv[1] ), showContd=True ) ), \ 

84 # priority=util.LOG_DEBUG ) 

85 

86 try: 

87 params, methodname = xmlrpc.client.loads(os.fsencode(sys.argv[1])) 

88 self.cmd = methodname 

89 params = params[0] # expect a single struct 

90 self.params = params 

91 

92 # params is a dictionary 

93 self.dconf = params['device_config'] 

94 if 'sr_uuid' in params: 

95 self.sr_uuid = params['sr_uuid'] 

96 if 'vdi_uuid' in params: 96 ↛ 97line 96 didn't jump to line 97, because the condition on line 96 was never true

97 self.vdi_uuid = params['vdi_uuid'] 

98 elif self.cmd == "vdi_create": 

99 self.vdi_uuid = util.gen_uuid() 

100 

101 except Exception as e: 

102 util.SMlog("Failed to parse commandline; exception = %s argv = %s" % (str(e), repr(sys.argv))) 

103 raise xs_errors.XenError('BadRequest') 

104 

105 def run_statics(self): 

106 if self.params['command'] == 'sr_get_driver_info': 

107 print(util.sr_get_driver_info(self.driver_info)) 

108 sys.exit(0) 

109 

110 def run(self, sr): 

111 try: 

112 return self._run_locked(sr) 

113 except (util.CommandException, util.SMException, XenAPI.Failure) as e: 

114 util.logException(self.cmd) 

115 msg = str(e) 

116 if isinstance(e, util.CommandException): 

117 msg = "Command %s failed (%s): %s" % \ 

118 (e.cmd, e.reason, os.strerror(abs(e.code))) 

119 excType = EXCEPTION_TYPE.get(self.cmd) 

120 if not excType: 

121 excType = "SMGeneral" 

122 raise xs_errors.XenError(excType, opterr=msg) 

123 

124 except blktap2.TapdiskFailed as e: 

125 util.logException('tapdisk failed exception: %s' % e) 

126 raise xs_errors.XenError('TapdiskFailed', 

127 os.strerror( 

128 e.get_error().get_error_code())) 

129 

130 except blktap2.TapdiskExists as e: 

131 util.logException('tapdisk exists exception: %s' % e) 

132 raise xs_errors.XenError('TapdiskAlreadyRunning', e.__str__()) 

133 

134 except: 

135 util.logException('generic exception: %s' % self.cmd) 

136 raise 

137 

138 def _run_locked(self, sr): 

139 lockSR = False 

140 lockInitOnly = False 

141 rv = None 

142 e = None 

143 if self.cmd in sr.ops_exclusive: 143 ↛ 144line 143 didn't jump to line 144, because the condition on line 143 was never true

144 lockSR = True 

145 elif self.cmd in NEEDS_VDI_OBJECT and "vdi_init" in sr.ops_exclusive: 145 ↛ 146line 145 didn't jump to line 146, because the condition on line 145 was never true

146 lockInitOnly = True 

147 

148 target = None 

149 acquired = False 

150 if lockSR or lockInitOnly: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true

151 sr.lock.acquire() 

152 acquired = True 

153 try: 

154 try: 

155 if self.cmd in NEEDS_VDI_OBJECT: 155 ↛ 156line 155 didn't jump to line 156, because the condition on line 155 was never true

156 target = sr.vdi(self.vdi_uuid) 

157 finally: 

158 if acquired and lockInitOnly: 158 ↛ 159line 158 didn't jump to line 159, because the condition on line 158 was never true

159 sr.lock.release() 

160 acquired = False 

161 try: 

162 rv = self._run(sr, target) 

163 except Exception as exc: 

164 e = exc 

165 raise 

166 finally: 

167 if acquired: 167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true

168 sr.lock.release() 

169 try: 

170 sr.cleanup() 170 ↛ exitline 170 didn't except from function '_run_locked', because the raise on line 165 wasn't executed

171 except Exception as e1: 

172 msg = 'failed to clean up SR: %s' % e1 

173 if not e: 

174 util.SMlog(msg) 

175 raise e1 

176 else: 

177 util.SMlog('WARNING: %s (error ignored)' % msg) 

178 return rv 

179 

180 def _run(self, sr, target): 

181 dconf_type = sr.dconf.get("type") 

182 if not dconf_type or not NO_LOGGING.get(dconf_type) or \ 182 ↛ 195line 182 didn't jump to line 195, because the condition on line 182 was never false

183 self.cmd not in NO_LOGGING[dconf_type]: 

184 params_to_log = self.params 

185 

186 if 'device_config' in params_to_log: 186 ↛ 190line 186 didn't jump to line 190, because the condition on line 186 was never false

187 params_to_log = util.hidePasswdInParams( 

188 self.params, 'device_config') 

189 

190 if 'session_ref' in params_to_log: 190 ↛ 193line 190 didn't jump to line 193, because the condition on line 190 was never false

191 params_to_log['session_ref'] = '******' 

192 

193 util.SMlog("%s %s" % (self.cmd, repr(params_to_log))) 

194 

195 caching_params = dict((k, self.params.get(k)) for k in 

196 [blktap2.VDI.CONF_KEY_ALLOW_CACHING, 

197 blktap2.VDI.CONF_KEY_MODE_ON_BOOT, 

198 blktap2.VDI.CONF_KEY_CACHE_SR, 

199 blktap2.VDI.CONF_KEY_O_DIRECT]) 

200 

201 if self.cmd == 'vdi_create': 201 ↛ 210line 201 didn't jump to line 210, because the condition on line 201 was never true

202 # These are the fields owned by the backend, passed on the 

203 # commandline: 

204 

205 # LVM SRs store their metadata in XML format. XML does not support 

206 # all unicode characters, so we must check if the label or the 

207 # description contain such characters. We must enforce this 

208 # restriction to other SRs as well (even if they do allow these 

209 # characters) in order to be consistent. 

210 target.label = self.params['args'][1] 

211 target.description = self.params['args'][2] 

212 

213 if not util.isLegalXMLString(target.label) \ 

214 or not util.isLegalXMLString(target.description): 

215 raise xs_errors.XenError('IllegalXMLChar', 

216 opterr='The name and/or description you supplied contains one or more unsupported characters. The name and/or description must contain valid XML characters. See http://www.w3.org/TR/2004/REC-xml-20040204/#charsets for more information.') 

217 

218 target.ty = self.params['vdi_type'] 

219 target.metadata_of_pool = self.params['args'][3] 

220 target.is_a_snapshot = self.params['args'][4] == "true" 

221 target.snapshot_time = self.params['args'][5] 

222 target.snapshot_of = self.params['args'][6] 

223 target.read_only = self.params['args'][7] == "true" 

224 

225 return target.create(self.params['sr_uuid'], self.vdi_uuid, int(self.params['args'][0])) 

226 

227 elif self.cmd == 'vdi_update': 227 ↛ 231line 227 didn't jump to line 231, because the condition on line 227 was never true

228 # Check for invalid XML characters, similar to VDI.create right 

229 # above. 

230 

231 vdi_ref = sr.session.xenapi.VDI.get_by_uuid(self.vdi_uuid) 

232 name_label = sr.session.xenapi.VDI.get_name_label(vdi_ref) 

233 description = sr.session.xenapi.VDI.get_name_description(vdi_ref) 

234 

235 if not util.isLegalXMLString(name_label) \ 

236 or not util.isLegalXMLString(description): 

237 raise xs_errors.XenError('IllegalXMLChar', 

238 opterr='The name and/or description you supplied contains one or more unsupported characters. The name and/or description must contain valid XML characters. See http://www.w3.org/TR/2004/REC-xml-20040204/#charsets for more information.') 

239 

240 return target.update(self.params['sr_uuid'], self.vdi_uuid) 

241 

242 elif self.cmd == 'vdi_introduce': 242 ↛ 243line 242 didn't jump to line 243, because the condition on line 242 was never true

243 target = sr.vdi(self.params['new_uuid']) 

244 return target.introduce(self.params['sr_uuid'], self.params['new_uuid']) 

245 

246 elif self.cmd == 'vdi_delete': 246 ↛ 247line 246 didn't jump to line 247, because the condition on line 246 was never true

247 if 'VDI_CONFIG_CBT' in util.sr_get_capability( 

248 self.params['sr_uuid'], 

249 session=sr.session): 

250 return target.delete(self.params['sr_uuid'], 

251 self.vdi_uuid, data_only=False) 

252 else: 

253 return target.delete(self.params['sr_uuid'], self.vdi_uuid) 

254 

255 elif self.cmd == 'vdi_attach': 255 ↛ 256line 255 didn't jump to line 256, because the condition on line 255 was never true

256 target = blktap2.VDI(self.vdi_uuid, target, self.driver_info) 

257 writable = self.params['args'][0] == 'true' 

258 return target.attach(self.params['sr_uuid'], self.vdi_uuid, 

259 writable, caching_params=caching_params) 

260 

261 elif self.cmd == 'vdi_detach': 261 ↛ 262line 261 didn't jump to line 262, because the condition on line 261 was never true

262 target = blktap2.VDI(self.vdi_uuid, target, self.driver_info) 

263 return target.detach(self.params['sr_uuid'], self.vdi_uuid) 

264 

265 elif self.cmd == 'vdi_snapshot': 265 ↛ 266line 265 didn't jump to line 266, because the condition on line 265 was never true

266 return target.snapshot(self.params['sr_uuid'], self.vdi_uuid) 

267 

268 elif self.cmd == 'vdi_clone': 268 ↛ 269line 268 didn't jump to line 269, because the condition on line 268 was never true

269 return target.clone(self.params['sr_uuid'], self.vdi_uuid) 

270 

271 elif self.cmd == 'vdi_resize': 271 ↛ 272line 271 didn't jump to line 272, because the condition on line 271 was never true

272 return target.resize(self.params['sr_uuid'], self.vdi_uuid, int(self.params['args'][0])) 

273 

274 elif self.cmd == 'vdi_resize_online': 274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true

275 return target.resize_online(self.params['sr_uuid'], self.vdi_uuid, int(self.params['args'][0])) 

276 

277 elif self.cmd == 'vdi_activate': 277 ↛ 278line 277 didn't jump to line 278, because the condition on line 277 was never true

278 target = blktap2.VDI(self.vdi_uuid, target, self.driver_info) 

279 writable = self.params['args'][0] == 'true' 

280 return target.activate(self.params['sr_uuid'], self.vdi_uuid, 

281 writable, caching_params) 

282 

283 elif self.cmd == 'vdi_deactivate': 283 ↛ 284line 283 didn't jump to line 284, because the condition on line 283 was never true

284 target = blktap2.VDI(self.vdi_uuid, target, self.driver_info) 

285 return target.deactivate(self.params['sr_uuid'], self.vdi_uuid, 

286 caching_params) 

287 

288 elif self.cmd == 'vdi_epoch_begin': 288 ↛ 289line 288 didn't jump to line 289, because the condition on line 288 was never true

289 if caching_params.get(blktap2.VDI.CONF_KEY_MODE_ON_BOOT) != "reset": 

290 return 

291 if "VDI_RESET_ON_BOOT/2" not in self.driver_info['capabilities']: 

292 raise xs_errors.XenError('Unimplemented') 

293 return target.reset_leaf(self.params['sr_uuid'], self.vdi_uuid) 

294 

295 elif self.cmd == 'vdi_epoch_end': 295 ↛ 296line 295 didn't jump to line 296, because the condition on line 295 was never true

296 return 

297 

298 elif self.cmd == 'vdi_generate_config': 298 ↛ 299line 298 didn't jump to line 299, because the condition on line 298 was never true

299 return target.generate_config(self.params['sr_uuid'], self.vdi_uuid) 

300 

301 elif self.cmd == 'vdi_compose': 301 ↛ 302line 301 didn't jump to line 302, because the condition on line 301 was never true

302 vdi1_uuid = sr.session.xenapi.VDI.get_uuid(self.params['args'][0]) 

303 return target.compose(self.params['sr_uuid'], vdi1_uuid, self.vdi_uuid) 

304 

305 elif self.cmd == 'vdi_attach_from_config': 305 ↛ 306line 305 didn't jump to line 306, because the condition on line 305 was never true

306 ret = target.attach_from_config(self.params['sr_uuid'], self.vdi_uuid) 

307 if not target.sr.driver_config.get("ATTACH_FROM_CONFIG_WITH_TAPDISK"): 

308 return ret 

309 target = blktap2.VDI(self.vdi_uuid, target, self.driver_info) 

310 return target.attach(self.params['sr_uuid'], self.vdi_uuid, True, True) 

311 

312 elif self.cmd == 'vdi_detach_from_config': 312 ↛ 313line 312 didn't jump to line 313, because the condition on line 312 was never true

313 extras = {} 

314 if target.sr.driver_config.get("ATTACH_FROM_CONFIG_WITH_TAPDISK"): 

315 target = blktap2.VDI(self.vdi_uuid, target, self.driver_info) 

316 extras['deactivate'] = True 

317 extras['caching_params'] = caching_params 

318 target.detach(self.params['sr_uuid'], self.vdi_uuid, ** extras) 

319 

320 elif self.cmd == 'vdi_enable_cbt': 320 ↛ 321line 320 didn't jump to line 321, because the condition on line 320 was never true

321 return target.configure_blocktracking(self.params['sr_uuid'], 

322 self.vdi_uuid, True) 

323 

324 elif self.cmd == 'vdi_disable_cbt': 324 ↛ 325line 324 didn't jump to line 325, because the condition on line 324 was never true

325 return target.configure_blocktracking(self.params['sr_uuid'], 

326 self.vdi_uuid, False) 

327 

328 elif self.cmd == 'vdi_data_destroy': 328 ↛ 329line 328 didn't jump to line 329, because the condition on line 328 was never true

329 return target.data_destroy(self.params['sr_uuid'], self.vdi_uuid) 

330 

331 elif self.cmd == 'vdi_list_changed_blocks': 331 ↛ 332line 331 didn't jump to line 332, because the condition on line 331 was never true

332 return target.list_changed_blocks() 

333 

334 elif self.cmd == 'sr_create': 334 ↛ 335line 334 didn't jump to line 335, because the condition on line 334 was never true

335 return sr.create(self.params['sr_uuid'], int(self.params['args'][0])) 

336 

337 elif self.cmd == 'sr_delete': 337 ↛ 340line 337 didn't jump to line 340, because the condition on line 337 was never false

338 return sr.delete(self.params['sr_uuid']) 

339 

340 elif self.cmd == 'sr_update': 

341 return sr.update(self.params['sr_uuid']) 

342 

343 elif self.cmd == 'sr_probe': 

344 txt = sr.probe() 

345 util.SMlog("sr_probe result: %s" % util.splitXmlText(txt, showContd=True)) 

346 # return the XML document as a string 

347 return xmlrpc.client.dumps((txt, ), "", True) 

348 

349 elif self.cmd == 'sr_attach': 

350 is_master = False 

351 if sr.dconf.get("SRmaster") == "true": 

352 is_master = True 

353 

354 sr_uuid = self.params['sr_uuid'] 

355 

356 resetvdis.reset_sr(sr.session, util.get_this_host(), 

357 sr_uuid, is_master) 

358 

359 if is_master: 

360 # Schedule a scan only when attaching on the SRmaster 

361 util.set_dirty(sr.session, self.params["sr_ref"]) 

362 

363 try: 

364 return sr.attach(sr_uuid) 

365 finally: 

366 if is_master: 

367 sr.after_master_attach(sr_uuid) 

368 

369 elif self.cmd == 'sr_detach': 

370 return sr.detach(self.params['sr_uuid']) 

371 

372 elif self.cmd == 'sr_content_type': 

373 return sr.content_type(self.params['sr_uuid']) 

374 

375 elif self.cmd == 'sr_scan': 

376 return sr.scan(self.params['sr_uuid']) 

377 

378 else: 

379 util.SMlog("Unknown command: %s" % self.cmd) 

380 raise xs_errors.XenError('BadRequest') 

381 

382 

383def run(driver, driver_info): 

384 """Convenience method to run command on the given driver""" 

385 cmd = SRCommand(driver_info) 

386 try: 

387 cmd.parse() 

388 cmd.run_statics() 

389 sr = driver(cmd, cmd.sr_uuid) 

390 sr.direct = True 

391 ret = cmd.run(sr) 

392 

393 if ret is None: 

394 print(util.return_nil()) 

395 else: 

396 print(ret) 

397 

398 except ProtocolError: 

399 # xs_errors.XenError.__new__ returns a different class 

400 # pylint: disable-msg=E1101 

401 exception_trace = traceback.format_exc() 

402 util.SMlog(f'XenAPI Protocol error {exception_trace}') 

403 print(xs_errors.XenError( 

404 'APIProtocolError', 

405 opterr=exception_trace).toxml()) 

406 except (Exception, xs_errors.SRException) as e: 

407 try: 

408 util.logException(driver_info['name']) 

409 except KeyError: 

410 util.SMlog('driver_info does not contain a \'name\' key.') 

411 except: 

412 pass 

413 

414 # If exception is of type SR.SRException, pass to Xapi. 

415 # If generic python Exception, print a generic xmlrpclib 

416 # dump and pass to XAPI. 

417 if isinstance(e, xs_errors.SRException): 

418 print(e.toxml()) 

419 else: 

420 print(xmlrpc.client.dumps(xmlrpc.client.Fault(1200, str(e)), "", True)) 

421 

422 sys.exit(0)