Coverage for libs/sm/BaseISCSI.py: 41%

578 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# ISCSISR: ISCSI software initiator SR driver 

18# 

19 

20from sm import SR 

21from sm.core import util 

22import time 

23from sm import LUNperVDI 

24import os 

25import sys 

26import re 

27import glob 

28import xml.dom.minidom 

29from sm.core import scsiutil 

30from sm.core import iscsi 

31from sm.core import xs_errors 

32 

33INITIATORNAME_FILE = '/etc/iscsi/initiatorname.iscsi' 

34SECTOR_SHIFT = 9 

35DEFAULT_PORT = 3260 

36# 2^16 Max port number value 

37MAXPORT = 65535 

38MAX_TIMEOUT = 15 

39MAX_LUNID_TIMEOUT = 60 

40ISCSI_PROCNAME = "iscsi_tcp" 

41 

42 

43class BaseISCSISR(SR.SR): 

44 """ISCSI storage repository""" 

45 

46 @property 

47 def force_tapdisk(self): 

48 return self.dconf.get('force_tapdisk', 'false') == 'true' 

49 

50 @property 

51 def attached(self): 

52 if not self._attached: 

53 self._attached = False 

54 self._attached = iscsi._checkTGT(self.targetIQN, self.target) 

55 return self._attached 

56 

57 @attached.setter 

58 def attached(self, value): 

59 self._attached = value 

60 

61 @property 

62 def pathdict(self): 

63 if not self._pathdict: 63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true

64 self._initPaths() 

65 return self._pathdict 

66 

67 @property 

68 def adapter(self): 

69 if not self._adapter: 69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true

70 self._initPaths() 

71 return self._adapter 

72 

73 @adapter.setter 

74 def adapter(self, value): 

75 self._adapter = value 

76 

77 @property 

78 def devs(self): 

79 if not self._devs: 

80 self._initPaths() 

81 return self._devs 

82 

83 @property 

84 def tgtidx(self): 

85 """This appears to only be referenced by a unit test. Do we really need it? """ 

86 if not self._tgtidx: 86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true

87 self._initPaths() 

88 return self._tgtidx 

89 

90 @property 

91 def path(self): 

92 if not self._path: 92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true

93 self._initPaths() 

94 return self._path 

95 

96 @property 

97 def address(self): 

98 if not self._address: 98 ↛ 99line 98 didn't jump to line 99, because the condition on line 98 was never true

99 self._initPaths() 

100 return self._address 

101 

102 def handles(type): 

103 return False 

104 handles = staticmethod(handles) 

105 

106 def _synchroniseAddrList(self, addrlist): 

107 if not self.multihomed: 107 ↛ 109line 107 didn't jump to line 109, because the condition on line 107 was never false

108 return 

109 change = False 

110 if 'multihomelist' not in self.dconf: 

111 change = True 

112 self.mlist = [] 

113 mstr = "" 

114 else: 

115 self.mlist = self.dconf['multihomelist'].split(',') 

116 mstr = self.dconf['multihomelist'] 

117 for val in addrlist: 

118 if not val in self.mlist: 

119 self.mlist.append(val) 

120 if len(mstr): 

121 mstr += "," 

122 mstr += val 

123 change = True 

124 if change: 

125 pbd = None 

126 try: 

127 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref) 

128 if pbd is not None: 

129 device_config = self.session.xenapi.PBD.get_device_config(pbd) 

130 device_config['multihomelist'] = mstr 

131 self.session.xenapi.PBD.set_device_config(pbd, device_config) 

132 except: 

133 pass 

134 

135 def load(self, sr_uuid): 

136 if self.force_tapdisk: 

137 self.sr_vditype = 'aio' 

138 else: 

139 self.sr_vditype = 'phy' 

140 self.discoverentry = 0 

141 self.default_vdi_visibility = False 

142 

143 # Required parameters 

144 if 'target' not in self.dconf or not self.dconf['target']: 144 ↛ 145line 144 didn't jump to line 145, because the condition on line 144 was never true

145 raise xs_errors.XenError('ConfigTargetMissing') 

146 

147 # we are no longer putting hconf in the xml. 

148 # Instead we pass a session and host ref and let the SM backend query XAPI itself 

149 try: 

150 if 'localIQN' not in self.dconf: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true

151 self.localIQN = self.session.xenapi.host.get_other_config(self.host_ref)['iscsi_iqn'] 

152 else: 

153 self.localIQN = self.dconf['localIQN'] 

154 except: 

155 raise xs_errors.XenError('ConfigISCSIIQNMissing') 

156 

157 # Check for empty string 

158 if not self.localIQN: 158 ↛ 159line 158 didn't jump to line 159, because the condition on line 158 was never true

159 raise xs_errors.XenError('ConfigISCSIIQNMissing') 

160 

161 try: 

162 self.target = util._convertDNS(self.dconf['target'].split(',')[0]) 

163 except: 

164 raise xs_errors.XenError('DNSError') 

165 

166 self.targetlist = self.target 

167 if 'targetlist' in self.dconf: 167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true

168 self.targetlist = self.dconf['targetlist'] 

169 

170 # Optional parameters 

171 self.chapuser = "" 

172 self.chappassword = "" 

173 if 'chapuser' in self.dconf \ 

174 and ('chappassword' in self.dconf or 'chappassword_secret' in self.dconf): 

175 self.chapuser = self.dconf['chapuser'].encode('utf-8') 

176 if 'chappassword_secret' in self.dconf: 176 ↛ 177line 176 didn't jump to line 177, because the condition on line 176 was never true

177 self.chappassword = util.get_secret(self.session, self.dconf['chappassword_secret']) 

178 else: 

179 self.chappassword = self.dconf['chappassword'] 

180 

181 self.chappassword = self.chappassword.encode('utf-8') 

182 

183 self.incoming_chapuser = "" 

184 self.incoming_chappassword = "" 

185 if 'incoming_chapuser' in self.dconf \ 

186 and ('incoming_chappassword' in self.dconf or 'incoming_chappassword_secret' in self.dconf): 

187 self.incoming_chapuser = self.dconf['incoming_chapuser'].encode('utf-8') 

188 if 'incoming_chappassword_secret' in self.dconf: 188 ↛ 189line 188 didn't jump to line 189, because the condition on line 188 was never true

189 self.incoming_chappassword = util.get_secret(self.session, self.dconf['incoming_chappassword_secret']) 

190 else: 

191 self.incoming_chappassword = self.dconf['incoming_chappassword'] 

192 

193 self.incoming_chappassword = self.incoming_chappassword.encode('utf-8') 

194 

195 self.port = DEFAULT_PORT 

196 if 'port' in self.dconf and self.dconf['port']: 196 ↛ 197line 196 didn't jump to line 197, because the condition on line 196 was never true

197 try: 

198 self.port = int(self.dconf['port']) 

199 except: 

200 raise xs_errors.XenError('ISCSIPort') 

201 if self.port > MAXPORT or self.port < 1: 201 ↛ 202line 201 didn't jump to line 202, because the condition on line 201 was never true

202 raise xs_errors.XenError('ISCSIPort') 

203 

204 # For backwards compatibility 

205 if 'usediscoverynumber' in self.dconf: 205 ↛ 206line 205 didn't jump to line 206, because the condition on line 205 was never true

206 self.discoverentry = self.dconf['usediscoverynumber'] 

207 

208 self.multihomed = False 

209 if 'multihomed' in self.dconf: 209 ↛ 210line 209 didn't jump to line 210, because the condition on line 209 was never true

210 if self.dconf['multihomed'] == "true": 

211 self.multihomed = True 

212 elif self.mpath == 'true': 212 ↛ 213line 212 didn't jump to line 213, because the condition on line 212 was never true

213 self.multihomed = True 

214 

215 if 'targetIQN' not in self.dconf or not self.dconf['targetIQN']: 215 ↛ 216line 215 didn't jump to line 216, because the condition on line 215 was never true

216 self._scan_IQNs() 

217 raise xs_errors.XenError('ConfigTargetIQNMissing') 

218 

219 self.targetIQN = self.dconf['targetIQN'] 

220 

221 self._attached = None 

222 self._pathdict = None 

223 self._adapter = None 

224 self._devs = None 

225 self._tgtidx = None 

226 self._path = None 

227 self._address = None 

228 

229 def _initPaths(self): 

230 self._init_adapters() 

231 # Generate a list of all possible paths 

232 self._pathdict = {} 

233 addrlist = [] 

234 rec = {} 

235 key = "%s:%d" % (self.target, self.port) 

236 rec['ipaddr'] = self.target 

237 rec['port'] = self.port 

238 rec['path'] = os.path.join("/dev/iscsi", self.targetIQN, \ 

239 key) 

240 self._pathdict[key] = rec 

241 util.SMlog("PATHDICT: key %s: %s" % (key, rec)) 

242 self._tgtidx = key 

243 addrlist.append(key) 

244 

245 self._path = rec['path'] 

246 self._address = self.tgtidx 

247 if not self.attached: 247 ↛ 248line 247 didn't jump to line 248, because the condition on line 247 was never true

248 return 

249 

250 if self.multihomed: 

251 map = iscsi.get_node_records(targetIQN=self.targetIQN) 

252 for i in range(0, len(map)): 

253 (portal, tpgt, iqn) = map[i] 

254 (ipaddr, port) = iscsi.parse_IP_port(portal) 

255 if self.target != ipaddr: 255 ↛ 252line 255 didn't jump to line 252, because the condition on line 255 was never false

256 key = "%s:%s" % (ipaddr, port) 

257 rec = {} 

258 rec['ipaddr'] = ipaddr 

259 rec['port'] = int(port) 

260 rec['path'] = os.path.join("/dev/iscsi", self.targetIQN, \ 

261 key) 

262 self._pathdict[key] = rec 

263 util.SMlog("PATHDICT: key %s: %s" % (key, rec)) 

264 addrlist.append(key) 

265 

266 if not os.path.exists(self.path): 

267 # Try to detect an active path in order of priority 

268 for key in self.pathdict: 268 ↛ 275line 268 didn't jump to line 275, because the loop on line 268 didn't complete

269 if key in self.adapter: 

270 self._tgtidx = key 

271 self._path = self.pathdict[self.tgtidx]['path'] 

272 if os.path.exists(self.path): 272 ↛ 268line 272 didn't jump to line 268, because the condition on line 272 was never false

273 util.SMlog("Path found: %s" % self.path) 

274 break 

275 self._address = self.tgtidx 

276 self._synchroniseAddrList(addrlist) 

277 

278 def _init_adapters(self): 

279 # Generate a list of active adapters 

280 ids = scsiutil._genHostList(ISCSI_PROCNAME) 

281 util.SMlog(ids) 

282 self._adapter = {} 

283 for host in ids: 

284 try: 

285 targetIQN = iscsi.get_targetIQN(host) 

286 if targetIQN != self.targetIQN: 286 ↛ 287line 286 didn't jump to line 287, because the condition on line 286 was never true

287 continue 

288 (addr, port) = iscsi.get_targetIP_and_port(host) 

289 entry = "%s:%s" % (addr, port) 

290 self._adapter[entry] = host 

291 except: 

292 pass 

293 self._devs = scsiutil.cacheSCSIidentifiers() 

294 

295 def attach(self, sr_uuid): 

296 self._mpathHandle() 

297 

298 multiTargets = False 

299 

300 npaths = 0 

301 try: 

302 pbdref = util.find_my_pbd(self.session, self.host_ref, self.sr_ref) 

303 if pbdref: 303 ↛ 309line 303 didn't jump to line 309, because the condition on line 303 was never false

304 other_config = self.session.xenapi.PBD.get_other_config(pbdref) 

305 multiTargets = util.sessions_less_than_targets(other_config, self.dconf) 

306 except: 

307 pass 

308 

309 if not self.attached or multiTargets: 309 ↛ 389line 309 didn't jump to line 389, because the condition on line 309 was never false

310 # Verify iSCSI target and port 

311 if 'multihomelist' in self.dconf and 'multiSession' not in self.dconf: 

312 targetlist = self.dconf['multihomelist'].split(',') 

313 else: 

314 targetlist = ['%s:%d' % (self.target, self.port)] 

315 conn = False 

316 for val in targetlist: 316 ↛ 326line 316 didn't jump to line 326, because the loop on line 316 didn't complete

317 (target, port) = iscsi.parse_IP_port(val) 

318 try: 

319 util._testHost(target, int(port), 'ISCSITarget') 

320 self.target = target 

321 self.port = int(port) 

322 conn = True 

323 break 

324 except: 

325 pass 

326 if not conn: 326 ↛ 327line 326 didn't jump to line 327, because the condition on line 326 was never true

327 raise xs_errors.XenError('ISCSITarget') 

328 

329 # Test and set the initiatorname file 

330 iscsi.ensure_daemon_running_ok(self.localIQN) 

331 

332 # Check to see if auto attach was set 

333 if not iscsi._checkTGT(self.targetIQN, tgt=self.target) or multiTargets: 333 ↛ 388line 333 didn't jump to line 388, because the condition on line 333 was never false

334 try: 

335 iqn_map = [] 

336 if 'any' != self.targetIQN: 336 ↛ 345line 336 didn't jump to line 345, because the condition on line 336 was never false

337 try: 

338 iqn_map = iscsi.get_node_records(self.targetIQN) 

339 except: 

340 # Pass the exception that is thrown, when there 

341 # are no nodes 

342 pass 

343 

344 # Check our current target is in the map 

345 portal = '%s:%d' % (self.target, self.port) 

346 if len(iqn_map) == 0 or not any([x[0] for x in iqn_map if x[0] == portal]): 346 ↛ 351line 346 didn't jump to line 351, because the condition on line 346 was never false

347 iqn_map = iscsi.discovery(self.target, self.port, 

348 self.chapuser, self.chappassword, 

349 self.targetIQN, 

350 iscsi.get_iscsi_interfaces()) 

351 if len(iqn_map) == 0: 351 ↛ 352line 351 didn't jump to line 352, because the condition on line 351 was never true

352 self._scan_IQNs() 

353 raise xs_errors.XenError('ISCSIDiscovery', 

354 opterr='check target settings') 

355 for i in range(0, len(iqn_map)): 

356 (portal, tpgt, iqn) = iqn_map[i] 

357 try: 

358 (ipaddr, port) = iscsi.parse_IP_port(portal) 

359 if not self.multihomed and ipaddr != self.target: 

360 continue 

361 util._testHost(ipaddr, int(port), 'ISCSITarget') 

362 util.SMlog("Logging in to [%s:%s]" % (ipaddr, port)) 

363 iscsi.login(portal, iqn, self.chapuser, 

364 self.chappassword, 

365 self.incoming_chapuser, 

366 self.incoming_chappassword, 

367 self.mpath == "true") 

368 npaths = npaths + 1 

369 except Exception as e: 

370 # Exceptions thrown in login are acknowledged, 

371 # the rest of exceptions are ignored since some of the 

372 # paths in multipath may not be reachable 

373 if str(e).startswith('ISCSI login'): 

374 raise 

375 else: 

376 pass 

377 

378 if not iscsi._checkTGT(self.targetIQN, tgt=self.target): 378 ↛ 379line 378 didn't jump to line 379, because the condition on line 378 was never true

379 raise xs_errors.XenError('ISCSIDevice', \ 

380 opterr='during login') 

381 

382 # Allow the devices to settle 

383 time.sleep(5) 

384 

385 except util.CommandException as inst: 

386 raise xs_errors.XenError('ISCSILogin', \ 

387 opterr='code is %d' % inst.code) 

388 self.attached = True 

389 self._initPaths() 

390 util._incr_iscsiSR_refcount(self.targetIQN, sr_uuid) 

391 IQNs = [] 

392 if "multiSession" in self.dconf: 

393 IQNs = "" 

394 for iqn in self.dconf['multiSession'].split("|"): 

395 if len(iqn): 

396 IQNs += iqn.split(',')[2] 

397 else: 

398 IQNs.append(self.targetIQN) 

399 

400 sessions = 0 

401 paths = iscsi.get_IQN_paths() 

402 for path in paths: 402 ↛ 403line 402 didn't jump to line 403, because the loop on line 402 never started

403 try: 

404 if util.get_single_entry(os.path.join(path, 'targetname')) in IQNs: 

405 sessions += 1 

406 util.SMlog("IQN match. Incrementing sessions to %d" % sessions) 

407 except: 

408 util.SMlog("Failed to read targetname path," \ 

409 + "iscsi_sessions value may be incorrect") 

410 

411 if pbdref: 411 ↛ 419line 411 didn't jump to line 419, because the condition on line 411 was never false

412 # Just to be safe in case of garbage left during crashes 

413 # we remove the key and add it 

414 self.session.xenapi.PBD.remove_from_other_config( 

415 pbdref, "iscsi_sessions") 

416 self.session.xenapi.PBD.add_to_other_config( 

417 pbdref, "iscsi_sessions", str(sessions)) 

418 

419 if 'SCSIid' in self.dconf: 419 ↛ exitline 419 didn't return from function 'attach', because the condition on line 419 was never false

420 if self.mpath == 'true': 420 ↛ 421line 420 didn't jump to line 421, because the condition on line 420 was never true

421 self.mpathmodule.refresh(self.dconf['SCSIid'], 0) 

422 dev_path = os.path.join("/dev/disk/by-scsid", self.dconf['SCSIid']) 

423 if not os.path.exists(dev_path): 

424 # LUN may have been added to the SAN since the session was created 

425 iscsi.refresh_luns(self.targetIQN, self.target) 

426 

427 if not os.path.exists(dev_path): 

428 raise xs_errors.XenError('ConfigSCSIid') 

429 

430 devs = os.listdir(dev_path) 

431 for dev in devs: 

432 realdev = os.path.realpath(os.path.join(dev_path, dev)) 

433 util.set_scheduler(os.path.basename(realdev)) 

434 

435 def detach(self, sr_uuid, delete=False): 

436 keys = [] 

437 pbdref = None 

438 try: 

439 pbdref = util.find_my_pbd(self.session, self.host_ref, self.sr_ref) 

440 except: 

441 pass 

442 if 'SCSIid' in self.dconf: 

443 scsi_id = self.dconf['SCSIid'] 

444 util.SMlog(f"Resetting mpath on {scsi_id}") 

445 self.mpathmodule.reset(scsi_id, explicit_unmap=True) 

446 keys.append("mpath-" + scsi_id) 

447 

448 # Remove iscsi_sessions and multipathed keys 

449 if pbdref is not None: 

450 if self.cmd == 'sr_detach': 

451 keys += ["multipathed", "iscsi_sessions"] 

452 for key in keys: 

453 try: 

454 self.session.xenapi.PBD.remove_from_other_config(pbdref, key) 

455 except: 

456 pass 

457 

458 if util._decr_iscsiSR_refcount(self.targetIQN, sr_uuid) != 0: 

459 return 

460 

461 if self.direct and util._containsVDIinuse(self): 

462 return 

463 

464 if iscsi._checkTGT(self.targetIQN): 

465 try: 

466 iscsi.logout(self.target, self.targetIQN, all=True) 

467 if delete: 

468 iscsi.delete(self.targetIQN) 

469 except util.CommandException as inst: 

470 raise xs_errors.XenError('ISCSIQueryDaemon', \ 

471 opterr='error is %d' % inst.code) 

472 if iscsi._checkTGT(self.targetIQN): 

473 raise xs_errors.XenError('ISCSIQueryDaemon', \ 

474 opterr='Failed to logout from target') 

475 

476 self.attached = False 

477 

478 def create(self, sr_uuid, size): 

479 # Check whether an SR already exists 

480 SRs = self.session.xenapi.SR.get_all_records() 

481 for sr in SRs: 

482 record = SRs[sr] 

483 sm_config = record["sm_config"] 

484 if 'targetIQN' in sm_config and \ 

485 sm_config['targetIQN'] == self.targetIQN: 

486 raise xs_errors.XenError('SRInUse') 

487 self.attach(sr_uuid) 

488 # Wait up to MAX_TIMEOUT for devices to appear 

489 util.wait_for_path(self.path, MAX_TIMEOUT) 

490 

491 if self._loadvdis() > 0: 

492 scanrecord = SR.ScanRecord(self) 

493 scanrecord.synchronise() 

494 try: 

495 self.detach(sr_uuid) 

496 except: 

497 pass 

498 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref) 

499 self.sm_config['disktype'] = 'Raw' 

500 self.sm_config['datatype'] = 'ISCSI' 

501 self.sm_config['target'] = self.target 

502 self.sm_config['targetIQN'] = self.targetIQN 

503 self.sm_config['multipathable'] = 'true' 

504 self.session.xenapi.SR.set_sm_config(self.sr_ref, self.sm_config) 

505 return 

506 

507 def delete(self, sr_uuid): 

508 self.detach(sr_uuid) 

509 return 

510 

511 def probe(self): 

512 SRs = self.session.xenapi.SR.get_all_records() 

513 Recs = {} 

514 for sr in SRs: 

515 record = SRs[sr] 

516 sm_config = record["sm_config"] 

517 if 'targetIQN' in sm_config and \ 

518 sm_config['targetIQN'] == self.targetIQN: 

519 Recs[record["uuid"]] = sm_config 

520 return self.srlist_toxml(Recs) 

521 

522 def scan(self, sr_uuid): 

523 if not self.passthrough: 

524 if not self.attached: 

525 raise xs_errors.XenError('SRUnavailable') 

526 self.refresh() 

527 time.sleep(2) # it seems impossible to tell when a scan's finished 

528 self._loadvdis() 

529 self.physical_utilisation = self.physical_size 

530 for uuid, vdi in self.vdis.items(): 

531 if vdi.managed: 

532 self.physical_utilisation += vdi.size 

533 self.virtual_allocation = self.physical_utilisation 

534 return super(BaseISCSISR, self).scan(sr_uuid) 

535 

536 def vdi(self, uuid): 

537 return LUNperVDI.RAWVDI(self, uuid) 

538 

539 def _scan_IQNs(self): 

540 # Verify iSCSI target and port 

541 util._testHost(self.target, self.port, 'ISCSITarget') 

542 

543 # Test and set the initiatorname file 

544 iscsi.ensure_daemon_running_ok(self.localIQN) 

545 

546 map = iscsi.discovery(self.target, self.port, self.chapuser, 

547 self.chappassword, 

548 interface_array=iscsi.get_iscsi_interfaces()) 

549 map.append(("%s:%d" % (self.targetlist, self.port), "0", "*")) 

550 self.print_entries(map) 

551 

552 def _attach_LUN_bylunid(self, lunid): 

553 if not self.attached: 

554 raise xs_errors.XenError('SRUnavailable') 

555 connected = [] 

556 for val in self.adapter: 

557 if val not in self.pathdict: 

558 continue 

559 rec = self.pathdict[val] 

560 path = os.path.join(rec['path'], "LUN%s" % lunid) 

561 realpath = os.path.realpath(path) 

562 host = self.adapter[val] 

563 l = [realpath, host, 0, 0, lunid] 

564 

565 addDevice = True 

566 if realpath in self.devs: 

567 # if the device is stale remove it before adding again 

568 real_SCSIid = None 

569 try: 

570 real_SCSIid = scsiutil.getSCSIid(realpath) 

571 except: 

572 pass 

573 

574 if real_SCSIid is not None: 

575 # make sure this is the same scsiid, if not remove the device 

576 cur_scsibuspath = glob.glob('/dev/disk/by-scsibus/*-%s:0:0:%s' % (host, lunid)) 

577 cur_SCSIid = os.path.basename(cur_scsibuspath[0]).split("-")[0] 

578 if cur_SCSIid != real_SCSIid: 

579 # looks stale, remove it 

580 scsiutil.scsi_dev_ctrl(l, "remove") 

581 else: 

582 util.SMlog("Not attaching LUNID %s for adapter %s" \ 

583 " since the device exists and the scsi id %s seems" \ 

584 " to be valid. " % (lunid, val, real_SCSIid)) 

585 addDevice = False 

586 else: 

587 # looks stale, remove it 

588 scsiutil.scsi_dev_ctrl(l, "remove") 

589 

590 if addDevice: 

591 # add the device 

592 scsiutil.scsi_dev_ctrl(l, "add") 

593 if not util.wait_for_path(path, MAX_LUNID_TIMEOUT): 

594 util.SMlog("Unable to detect LUN attached to host on path [%s]" % path) 

595 continue 

596 connected.append(path) 

597 return connected 

598 

599 def _attach_LUN_byserialid(self, serialid): 

600 if not self.attached: 

601 raise xs_errors.XenError('SRUnavailable') 

602 connected = [] 

603 for val in self.adapter: 

604 if val not in self.pathdict: 

605 continue 

606 rec = self.pathdict[val] 

607 path = os.path.join(rec['path'], "SERIAL-%s" % serialid) 

608 realpath = os.path.realpath(path) 

609 if realpath not in self.devs: 

610 if not util.wait_for_path(path, 5): 

611 util.SMlog("Unable to detect LUN attached to host on serial path [%s]" % path) 

612 continue 

613 connected.append(path) 

614 return connected 

615 

616 def _detach_LUN_bylunid(self, lunid, SCSIid): 

617 if not self.attached: 

618 raise xs_errors.XenError('SRUnavailable') 

619 if self.mpath == 'true' and len(SCSIid): 

620 self.mpathmodule.reset(SCSIid, explicit_unmap=True) 

621 util.remove_mpathcount_field(self.session, self.host_ref, self.sr_ref, SCSIid) 

622 for val in self.adapter: 

623 if val not in self.pathdict: 

624 continue 

625 rec = self.pathdict[val] 

626 path = os.path.join(rec['path'], "LUN%s" % lunid) 

627 realpath = os.path.realpath(path) 

628 if realpath in self.devs: 

629 util.SMlog("Found key: %s" % realpath) 

630 scsiutil.scsi_dev_ctrl(self.devs[realpath], 'remove') 

631 # Wait for device to disappear 

632 if not util.wait_for_nopath(realpath, MAX_LUNID_TIMEOUT): 

633 util.SMlog("Device has not disappeared after %d seconds" % \ 

634 MAX_LUNID_TIMEOUT) 

635 else: 

636 util.SMlog("Device [%s,%s] disappeared" % (realpath, path)) 

637 

638 def _attach_LUN_bySCSIid(self, SCSIid): 

639 if not self.attached: 

640 raise xs_errors.XenError('SRUnavailable') 

641 

642 path = self.mpathmodule.path(SCSIid) 

643 if not util.pathexists(path): 

644 self.refresh() 

645 if not util.wait_for_path(path, MAX_TIMEOUT): 

646 util.SMlog("Unable to detect LUN attached to host [%s]" 

647 % path) 

648 raise xs_errors.XenError('ISCSIDevice') 

649 

650 # This function queries the session for the attached LUNs 

651 def _loadvdis(self): 

652 count = 0 

653 if not os.path.exists(self.path): 

654 return 0 

655 for file in filter(self.match_lun, util.listdir(self.path)): 

656 vdi_path = os.path.join(self.path, file) 

657 LUNid = file.replace("LUN", "") 

658 uuid = scsiutil.gen_uuid_from_string(scsiutil.getuniqueserial(vdi_path)) 

659 obj = self.vdi(uuid) 

660 obj._query(vdi_path, LUNid) 

661 self.vdis[uuid] = obj 

662 self.physical_size += obj.size 

663 count += 1 

664 return count 

665 

666 def refresh(self): 

667 for val in self.adapter: 

668 util.SMlog("Rescanning host adapter %s" % self.adapter[val]) 

669 scsiutil.rescan([self.adapter[val]]) 

670 

671 # Helper function for LUN-per-VDI VDI.introduce 

672 def _getLUNbySMconfig(self, sm_config): 

673 if 'LUNid' not in sm_config: 

674 raise xs_errors.XenError('VDIUnavailable') 

675 LUNid = int(sm_config['LUNid']) 

676 if not len(self._attach_LUN_bylunid(LUNid)): 

677 raise xs_errors.XenError('VDIUnavailable') 

678 return os.path.join(self.path, "LUN%d" % LUNid) 

679 

680 # This function takes an ISCSI device and populate it with 

681 # a dictionary of available LUNs on that target. 

682 def print_LUNs(self): 

683 self.LUNs = {} 

684 if os.path.exists(self.path): 

685 dom0_disks = util.dom0_disks() 

686 for file in util.listdir(self.path): 

687 if file.find("LUN") != -1 and file.find("_") == -1: 

688 vdi_path = os.path.join(self.path, file) 

689 if os.path.realpath(vdi_path) in dom0_disks: 

690 util.SMlog("Hide dom0 boot disk LUN") 

691 else: 

692 LUNid = file.replace("LUN", "") 

693 obj = self.vdi(self.uuid) 

694 obj._query(vdi_path, LUNid) 

695 self.LUNs[obj.uuid] = obj 

696 

697 def print_entries(self, map): 

698 dom = xml.dom.minidom.Document() 

699 element = dom.createElement("iscsi-target-iqns") 

700 dom.appendChild(element) 

701 count = 0 

702 for address, tpgt, iqn in map: 

703 entry = dom.createElement('TGT') 

704 element.appendChild(entry) 

705 subentry = dom.createElement('Index') 

706 entry.appendChild(subentry) 

707 textnode = dom.createTextNode(str(count)) 

708 subentry.appendChild(textnode) 

709 

710 try: 

711 # We always expect a port so this holds 

712 # regardless of IP version 

713 (addr, port) = address.rsplit(':', 1) 

714 except: 

715 addr = address 

716 port = DEFAULT_PORT 

717 subentry = dom.createElement('IPAddress') 

718 entry.appendChild(subentry) 

719 textnode = dom.createTextNode(str(addr)) 

720 subentry.appendChild(textnode) 

721 

722 if int(port) != DEFAULT_PORT: 

723 subentry = dom.createElement('Port') 

724 entry.appendChild(subentry) 

725 textnode = dom.createTextNode(str(port)) 

726 subentry.appendChild(textnode) 

727 

728 subentry = dom.createElement('TargetIQN') 

729 entry.appendChild(subentry) 

730 textnode = dom.createTextNode(str(iqn)) 

731 subentry.appendChild(textnode) 

732 count += 1 

733 print(dom.toprettyxml(), file=sys.stderr) 

734 

735 def srlist_toxml(self, SRs): 

736 dom = xml.dom.minidom.Document() 

737 element = dom.createElement("SRlist") 

738 dom.appendChild(element) 

739 

740 for val in SRs: 

741 record = SRs[val] 

742 entry = dom.createElement('SR') 

743 element.appendChild(entry) 

744 

745 subentry = dom.createElement("UUID") 

746 entry.appendChild(subentry) 

747 textnode = dom.createTextNode(val) 

748 subentry.appendChild(textnode) 

749 

750 subentry = dom.createElement("Target") 

751 entry.appendChild(subentry) 

752 textnode = dom.createTextNode(record['target']) 

753 subentry.appendChild(textnode) 

754 

755 subentry = dom.createElement("TargetIQN") 

756 entry.appendChild(subentry) 

757 textnode = dom.createTextNode(record['targetIQN']) 

758 subentry.appendChild(textnode) 

759 return dom.toprettyxml() 

760 

761 def match_lun(self, s): 

762 regex = re.compile("_") 

763 if regex.search(s, 0): 

764 return False 

765 regex = re.compile("LUN") 

766 return regex.search(s, 0)