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
« 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#
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
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"
43class BaseISCSISR(SR.SR):
44 """ISCSI storage repository"""
46 @property
47 def force_tapdisk(self):
48 return self.dconf.get('force_tapdisk', 'false') == 'true'
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
57 @attached.setter
58 def attached(self, value):
59 self._attached = value
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
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
73 @adapter.setter
74 def adapter(self, value):
75 self._adapter = value
77 @property
78 def devs(self):
79 if not self._devs:
80 self._initPaths()
81 return self._devs
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
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
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
102 def handles(type):
103 return False
104 handles = staticmethod(handles)
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
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
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')
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')
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')
161 try:
162 self.target = util._convertDNS(self.dconf['target'].split(',')[0])
163 except:
164 raise xs_errors.XenError('DNSError')
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']
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']
181 self.chappassword = self.chappassword.encode('utf-8')
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']
193 self.incoming_chappassword = self.incoming_chappassword.encode('utf-8')
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')
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']
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
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')
219 self.targetIQN = self.dconf['targetIQN']
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
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)
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
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)
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)
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()
295 def attach(self, sr_uuid):
296 self._mpathHandle()
298 multiTargets = False
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
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')
329 # Test and set the initiatorname file
330 iscsi.ensure_daemon_running_ok(self.localIQN)
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
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
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')
382 # Allow the devices to settle
383 time.sleep(5)
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)
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")
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))
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)
427 if not os.path.exists(dev_path):
428 raise xs_errors.XenError('ConfigSCSIid')
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))
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)
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
458 if util._decr_iscsiSR_refcount(self.targetIQN, sr_uuid) != 0:
459 return
461 if self.direct and util._containsVDIinuse(self):
462 return
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')
476 self.attached = False
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)
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
507 def delete(self, sr_uuid):
508 self.detach(sr_uuid)
509 return
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)
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)
536 def vdi(self, uuid):
537 return LUNperVDI.RAWVDI(self, uuid)
539 def _scan_IQNs(self):
540 # Verify iSCSI target and port
541 util._testHost(self.target, self.port, 'ISCSITarget')
543 # Test and set the initiatorname file
544 iscsi.ensure_daemon_running_ok(self.localIQN)
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)
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]
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
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")
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
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
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))
638 def _attach_LUN_bySCSIid(self, SCSIid):
639 if not self.attached:
640 raise xs_errors.XenError('SRUnavailable')
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')
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
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]])
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)
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
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)
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)
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)
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)
735 def srlist_toxml(self, SRs):
736 dom = xml.dom.minidom.Document()
737 element = dom.createElement("SRlist")
738 dom.appendChild(element)
740 for val in SRs:
741 record = SRs[val]
742 entry = dom.createElement('SR')
743 element.appendChild(entry)
745 subentry = dom.createElement("UUID")
746 entry.appendChild(subentry)
747 textnode = dom.createTextNode(val)
748 subentry.appendChild(textnode)
750 subentry = dom.createElement("Target")
751 entry.appendChild(subentry)
752 textnode = dom.createTextNode(record['target'])
753 subentry.appendChild(textnode)
755 subentry = dom.createElement("TargetIQN")
756 entry.appendChild(subentry)
757 textnode = dom.createTextNode(record['targetIQN'])
758 subentry.appendChild(textnode)
759 return dom.toprettyxml()
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)