Coverage for drivers/BaseISCSI.py : 40%

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#!/usr/bin/python3
2#
3# Copyright (C) Citrix Systems Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published
7# by the Free Software Foundation; version 2.1 only.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17#
18# ISCSISR: ISCSI software initiator SR driver
19#
21from sm_typing import override
23import SR
24import VDI
25import util
26import time
27import LUNperVDI
28import os
29import sys
30import re
31import glob
32import xml.dom.minidom
33import scsiutil
34import iscsilib
35import xs_errors
37INITIATORNAME_FILE = '/etc/iscsi/initiatorname.iscsi'
38SECTOR_SHIFT = 9
39DEFAULT_PORT = 3260
40# 2^16 Max port number value
41MAXPORT = 65535
42MAX_TIMEOUT = 15
43MAX_LUNID_TIMEOUT = 60
44ISCSI_PROCNAME = "iscsi_tcp"
47class BaseISCSISR(SR.SR):
48 """ISCSI storage repository"""
50 @property
51 def force_tapdisk(self):
52 return self.dconf.get('force_tapdisk', 'false') == 'true'
54 @property
55 def attached(self):
56 if not self._attached:
57 self._attached = False
58 self._attached = iscsilib._checkTGT(self.targetIQN, self.target)
59 return self._attached
61 @attached.setter
62 def attached(self, value):
63 self._attached = value
65 @property
66 def pathdict(self):
67 if not self._pathdict: 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true
68 self._initPaths()
69 return self._pathdict
71 @property
72 def adapter(self):
73 if not self._adapter: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true
74 self._initPaths()
75 return self._adapter
77 @adapter.setter
78 def adapter(self, value):
79 self._adapter = value
81 @property
82 def devs(self):
83 if not self._devs:
84 self._initPaths()
85 return self._devs
87 @property
88 def tgtidx(self):
89 """This appears to only be referenced by a unit test. Do we really need it? """
90 if not self._tgtidx: 90 ↛ 91line 90 didn't jump to line 91, because the condition on line 90 was never true
91 self._initPaths()
92 return self._tgtidx
94 @property
95 def path(self):
96 if not self._path: 96 ↛ 97line 96 didn't jump to line 97, because the condition on line 96 was never true
97 self._initPaths()
98 return self._path
100 @property
101 def address(self):
102 if not self._address: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true
103 self._initPaths()
104 return self._address
106 @override
107 @staticmethod
108 def handles(type) -> bool:
109 return False
111 def _synchroniseAddrList(self, addrlist) -> None:
112 if not self.multihomed: 112 ↛ 114line 112 didn't jump to line 114, because the condition on line 112 was never false
113 return
114 change = False
115 if 'multihomelist' not in self.dconf:
116 change = True
117 self.mlist = []
118 mstr = ""
119 else:
120 self.mlist = self.dconf['multihomelist'].split(',')
121 mstr = self.dconf['multihomelist']
122 for val in addrlist:
123 if not val in self.mlist:
124 self.mlist.append(val)
125 if len(mstr):
126 mstr += ","
127 mstr += val
128 change = True
129 if change:
130 pbd = None
131 try:
132 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
133 if pbd is not None:
134 device_config = self.session.xenapi.PBD.get_device_config(pbd)
135 device_config['multihomelist'] = mstr
136 self.session.xenapi.PBD.set_device_config(pbd, device_config)
137 except:
138 pass
140 @override
141 def load(self, sr_uuid) -> None:
142 if self.force_tapdisk:
143 self.sr_vditype = 'aio'
144 else:
145 self.sr_vditype = 'phy'
146 self.discoverentry = 0
147 self.default_vdi_visibility = False
149 # Required parameters
150 if 'target' not in self.dconf or not self.dconf['target']: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true
151 raise xs_errors.XenError('ConfigTargetMissing')
153 # we are no longer putting hconf in the xml.
154 # Instead we pass a session and host ref and let the SM backend query XAPI itself
155 try:
156 if 'localIQN' not in self.dconf: 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true
157 self.localIQN = self.session.xenapi.host.get_other_config(self.host_ref)['iscsi_iqn']
158 else:
159 self.localIQN = self.dconf['localIQN']
160 except:
161 raise xs_errors.XenError('ConfigISCSIIQNMissing')
163 # Check for empty string
164 if not self.localIQN: 164 ↛ 165line 164 didn't jump to line 165, because the condition on line 164 was never true
165 raise xs_errors.XenError('ConfigISCSIIQNMissing')
167 try:
168 self.target = util._convertDNS(self.dconf['target'].split(',')[0])
169 except:
170 raise xs_errors.XenError('DNSError')
172 self.targetlist = self.target
173 if 'targetlist' in self.dconf: 173 ↛ 174line 173 didn't jump to line 174, because the condition on line 173 was never true
174 self.targetlist = self.dconf['targetlist']
176 # Optional parameters
177 self.chapuser = ""
178 self.chappassword = ""
179 if 'chapuser' in self.dconf \
180 and ('chappassword' in self.dconf or 'chappassword_secret' in self.dconf):
181 self.chapuser = self.dconf['chapuser'].encode('utf-8')
182 if 'chappassword_secret' in self.dconf: 182 ↛ 183line 182 didn't jump to line 183, because the condition on line 182 was never true
183 chappassword = util.get_secret(self.session, self.dconf['chappassword_secret'])
184 else:
185 chappassword = self.dconf['chappassword']
187 self.chappassword = chappassword.encode('utf-8')
189 self.incoming_chapuser = ""
190 self.incoming_chappassword = ""
191 if 'incoming_chapuser' in self.dconf \
192 and ('incoming_chappassword' in self.dconf or 'incoming_chappassword_secret' in self.dconf):
193 self.incoming_chapuser = self.dconf['incoming_chapuser'].encode('utf-8')
194 if 'incoming_chappassword_secret' in self.dconf: 194 ↛ 195line 194 didn't jump to line 195, because the condition on line 194 was never true
195 incoming_chappassword = util.get_secret(self.session, self.dconf['incoming_chappassword_secret'])
196 else:
197 incoming_chappassword = self.dconf['incoming_chappassword']
199 self.incoming_chappassword = incoming_chappassword.encode('utf-8')
201 self.port = DEFAULT_PORT
202 if 'port' in self.dconf and self.dconf['port']: 202 ↛ 203line 202 didn't jump to line 203, because the condition on line 202 was never true
203 try:
204 self.port = int(self.dconf['port'])
205 except:
206 raise xs_errors.XenError('ISCSIPort')
207 if self.port > MAXPORT or self.port < 1: 207 ↛ 208line 207 didn't jump to line 208, because the condition on line 207 was never true
208 raise xs_errors.XenError('ISCSIPort')
210 # For backwards compatibility
211 if 'usediscoverynumber' in self.dconf: 211 ↛ 212line 211 didn't jump to line 212, because the condition on line 211 was never true
212 self.discoverentry = self.dconf['usediscoverynumber']
214 self.multihomed = False
215 if 'multihomed' in self.dconf: 215 ↛ 216line 215 didn't jump to line 216, because the condition on line 215 was never true
216 if self.dconf['multihomed'] == "true":
217 self.multihomed = True
218 elif self.mpath == 'true': 218 ↛ 219line 218 didn't jump to line 219, because the condition on line 218 was never true
219 self.multihomed = True
221 if 'targetIQN' not in self.dconf or not self.dconf['targetIQN']: 221 ↛ 222line 221 didn't jump to line 222, because the condition on line 221 was never true
222 self._scan_IQNs()
223 raise xs_errors.XenError('ConfigTargetIQNMissing')
225 self.targetIQN = self.dconf['targetIQN']
227 self._attached = None
228 self._pathdict = None
229 self._adapter = None
230 self._devs = None
231 self._tgtidx = None
232 self._path = None
233 self._address = None
235 def _initPaths(self):
236 self._init_adapters()
237 # Generate a list of all possible paths
238 self._pathdict = {}
239 addrlist = []
240 rec = {}
241 key = "%s:%d" % (self.target, self.port)
242 rec['ipaddr'] = self.target
243 rec['port'] = self.port
244 rec['path'] = os.path.join("/dev/iscsi", self.targetIQN, \
245 key)
246 self._pathdict[key] = rec
247 util.SMlog("PATHDICT: key %s: %s" % (key, rec))
248 self._tgtidx = key
249 addrlist.append(key)
251 self._path = rec['path']
252 self._address = self.tgtidx
253 if not self.attached: 253 ↛ 254line 253 didn't jump to line 254, because the condition on line 253 was never true
254 return
256 if self.multihomed:
257 map = iscsilib.get_node_records(targetIQN=self.targetIQN)
258 for i in range(0, len(map)):
259 (portal, tpgt, iqn) = map[i]
260 (ipaddr, port) = iscsilib.parse_IP_port(portal)
261 if self.target != ipaddr: 261 ↛ 258line 261 didn't jump to line 258, because the condition on line 261 was never false
262 key = "%s:%s" % (ipaddr, port)
263 rec = {}
264 rec['ipaddr'] = ipaddr
265 rec['port'] = int(port)
266 rec['path'] = os.path.join("/dev/iscsi", self.targetIQN, \
267 key)
268 self._pathdict[key] = rec
269 util.SMlog("PATHDICT: key %s: %s" % (key, rec))
270 addrlist.append(key)
272 if not os.path.exists(self.path):
273 # Try to detect an active path in order of priority
274 for key in self.pathdict: 274 ↛ 281line 274 didn't jump to line 281, because the loop on line 274 didn't complete
275 if key in self.adapter:
276 self._tgtidx = key
277 self._path = self.pathdict[self.tgtidx]['path']
278 if os.path.exists(self.path): 278 ↛ 274line 278 didn't jump to line 274, because the condition on line 278 was never false
279 util.SMlog("Path found: %s" % self.path)
280 break
281 self._address = self.tgtidx
282 self._synchroniseAddrList(addrlist)
284 def _init_adapters(self) -> None:
285 # Generate a list of active adapters
286 ids = scsiutil._genHostList(ISCSI_PROCNAME)
287 util.SMlog(ids)
288 self._adapter = {}
289 for host in ids:
290 try:
291 targetIQN = iscsilib.get_targetIQN(host)
292 if targetIQN != self.targetIQN: 292 ↛ 293line 292 didn't jump to line 293, because the condition on line 292 was never true
293 continue
294 (addr, port) = iscsilib.get_targetIP_and_port(host)
295 entry = "%s:%s" % (addr, port)
296 self._adapter[entry] = host
297 except:
298 pass
299 self._devs = scsiutil.cacheSCSIidentifiers()
301 @override
302 def attach(self, sr_uuid) -> None:
303 self._mpathHandle()
305 multiTargets = False
307 npaths = 0
308 try:
309 pbdref = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
310 if pbdref: 310 ↛ 316line 310 didn't jump to line 316, because the condition on line 310 was never false
311 other_config = self.session.xenapi.PBD.get_other_config(pbdref)
312 multiTargets = util.sessions_less_than_targets(other_config, self.dconf)
313 except:
314 pass
316 if not self.attached or multiTargets: 316 ↛ 396line 316 didn't jump to line 396, because the condition on line 316 was never false
317 # Verify iSCSI target and port
318 if 'multihomelist' in self.dconf and 'multiSession' not in self.dconf:
319 targetlist = self.dconf['multihomelist'].split(',')
320 else:
321 targetlist = ['%s:%d' % (self.target, self.port)]
322 conn = False
323 for val in targetlist: 323 ↛ 333line 323 didn't jump to line 333, because the loop on line 323 didn't complete
324 (target, port) = iscsilib.parse_IP_port(val)
325 try:
326 util._testHost(target, int(port), 'ISCSITarget')
327 self.target = target
328 self.port = int(port)
329 conn = True
330 break
331 except:
332 pass
333 if not conn: 333 ↛ 334line 333 didn't jump to line 334, because the condition on line 333 was never true
334 raise xs_errors.XenError('ISCSITarget')
336 # Test and set the initiatorname file
337 iscsilib.ensure_daemon_running_ok(self.localIQN)
339 # Check to see if auto attach was set
340 if not iscsilib._checkTGT(self.targetIQN, tgt=self.target) or multiTargets: 340 ↛ 395line 340 didn't jump to line 395, because the condition on line 340 was never false
341 try:
342 iqn_map = []
343 if 'any' != self.targetIQN: 343 ↛ 352line 343 didn't jump to line 352, because the condition on line 343 was never false
344 try:
345 iqn_map = iscsilib.get_node_records(self.targetIQN)
346 except:
347 # Pass the exception that is thrown, when there
348 # are no nodes
349 pass
351 # Check our current target is in the map
352 portal = '%s:%d' % (self.target, self.port)
353 if len(iqn_map) == 0 or not any([x[0] for x in iqn_map if x[0] == portal]): 353 ↛ exit, 353 ↛ 3582 missed branches: 1) line 353 didn't run the list comprehension on line 353, 2) line 353 didn't jump to line 358, because the condition on line 353 was never false
354 iqn_map = iscsilib.discovery(self.target, self.port,
355 self.chapuser, self.chappassword,
356 self.targetIQN,
357 iscsilib.get_iscsi_interfaces())
358 if len(iqn_map) == 0: 358 ↛ 359line 358 didn't jump to line 359, because the condition on line 358 was never true
359 self._scan_IQNs()
360 raise xs_errors.XenError('ISCSIDiscovery',
361 opterr='check target settings')
362 for i in range(0, len(iqn_map)):
363 (portal, tpgt, iqn) = iqn_map[i]
364 try:
365 (ipaddr, port) = iscsilib.parse_IP_port(portal)
366 if not self.multihomed and ipaddr != self.target:
367 continue
368 util._testHost(ipaddr, int(port), 'ISCSITarget')
369 util.SMlog("Logging in to [%s:%s]" % (ipaddr, port))
370 iscsilib.login(portal, iqn, self.chapuser,
371 self.chappassword,
372 self.incoming_chapuser,
373 self.incoming_chappassword,
374 self.mpath == "true")
375 npaths = npaths + 1
376 except Exception as e:
377 # Exceptions thrown in login are acknowledged,
378 # the rest of exceptions are ignored since some of the
379 # paths in multipath may not be reachable
380 if str(e).startswith('ISCSI login'):
381 raise
382 else:
383 pass
385 if not iscsilib._checkTGT(self.targetIQN, tgt=self.target): 385 ↛ 386line 385 didn't jump to line 386, because the condition on line 385 was never true
386 raise xs_errors.XenError('ISCSIDevice', \
387 opterr='during login')
389 # Allow the devices to settle
390 time.sleep(5)
392 except util.CommandException as inst:
393 raise xs_errors.XenError('ISCSILogin', \
394 opterr='code is %d' % inst.code)
395 self.attached = True
396 self._initPaths()
397 util._incr_iscsiSR_refcount(self.targetIQN, sr_uuid)
398 IQNs = []
399 if "multiSession" in self.dconf:
400 for iqn in self.dconf['multiSession'].split("|"):
401 if len(iqn):
402 IQNs.append(iqn.split(',')[2])
403 else:
404 IQNs.append(self.targetIQN)
406 sessions = 0
407 paths = iscsilib.get_IQN_paths()
408 for path in paths: 408 ↛ 409line 408 didn't jump to line 409, because the loop on line 408 never started
409 try:
410 if util.get_single_entry(os.path.join(path, 'targetname')) in IQNs:
411 sessions += 1
412 util.SMlog("IQN match. Incrementing sessions to %d" % sessions)
413 except:
414 util.SMlog("Failed to read targetname path," \
415 + "iscsi_sessions value may be incorrect")
417 if pbdref: 417 ↛ 425line 417 didn't jump to line 425, because the condition on line 417 was never false
418 # Just to be safe in case of garbage left during crashes
419 # we remove the key and add it
420 self.session.xenapi.PBD.remove_from_other_config(
421 pbdref, "iscsi_sessions")
422 self.session.xenapi.PBD.add_to_other_config(
423 pbdref, "iscsi_sessions", str(sessions))
425 if 'SCSIid' in self.dconf: 425 ↛ exitline 425 didn't return from function 'attach', because the condition on line 425 was never false
426 if self.mpath == 'true': 426 ↛ 427line 426 didn't jump to line 427, because the condition on line 426 was never true
427 self.mpathmodule.refresh(self.dconf['SCSIid'], 0)
428 dev_path = os.path.join("/dev/disk/by-scsid", self.dconf['SCSIid'])
429 if not os.path.exists(dev_path):
430 # LUN may have been added to the SAN since the session was created
431 iscsilib.refresh_luns(self.targetIQN, self.target)
433 if not os.path.exists(dev_path):
434 raise xs_errors.XenError('ConfigSCSIid')
436 devs = os.listdir(dev_path)
437 for dev in devs:
438 realdev = os.path.realpath(os.path.join(dev_path, dev))
439 util.set_scheduler(os.path.basename(realdev))
441 @override
442 def detach(self, sr_uuid) -> None:
443 self.detach_and_delete(sr_uuid, delete=False)
445 def detach_and_delete(self, sr_uuid, delete=True) -> None:
446 keys = []
447 pbdref = None
448 try:
449 pbdref = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
450 except:
451 pass
452 if 'SCSIid' in self.dconf:
453 self.mpathmodule.reset(self.dconf['SCSIid'], explicit_unmap=True)
454 keys.append("mpath-" + self.dconf['SCSIid'])
456 # Remove iscsi_sessions and multipathed keys
457 if pbdref is not None:
458 if self.cmd == 'sr_detach':
459 keys += ["multipathed", "iscsi_sessions"]
460 for key in keys:
461 try:
462 self.session.xenapi.PBD.remove_from_other_config(pbdref, key)
463 except:
464 pass
466 if util._decr_iscsiSR_refcount(self.targetIQN, sr_uuid) != 0:
467 return
469 if self.direct and util._containsVDIinuse(self):
470 return
472 if iscsilib._checkTGT(self.targetIQN):
473 try:
474 iscsilib.logout(self.target, self.targetIQN, all=True)
475 if delete:
476 iscsilib.delete(self.targetIQN)
477 except util.CommandException as inst:
478 raise xs_errors.XenError('ISCSIQueryDaemon', \
479 opterr='error is %d' % inst.code)
480 if iscsilib._checkTGT(self.targetIQN):
481 raise xs_errors.XenError('ISCSIQueryDaemon', \
482 opterr='Failed to logout from target')
484 self.attached = False
486 @override
487 def create(self, sr_uuid, size) -> None:
488 # Check whether an SR already exists
489 SRs = self.session.xenapi.SR.get_all_records()
490 for sr in SRs:
491 record = SRs[sr]
492 sm_config = record["sm_config"]
493 if 'targetIQN' in sm_config and \
494 sm_config['targetIQN'] == self.targetIQN:
495 raise xs_errors.XenError('SRInUse')
496 self.attach(sr_uuid)
497 # Wait up to MAX_TIMEOUT for devices to appear
498 util.wait_for_path(self.path, MAX_TIMEOUT)
500 if self._loadvdis() > 0:
501 scanrecord = SR.ScanRecord(self)
502 scanrecord.synchronise()
503 try:
504 self.detach(sr_uuid)
505 except:
506 pass
507 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
508 self.sm_config['disktype'] = 'Raw'
509 self.sm_config['datatype'] = 'ISCSI'
510 self.sm_config['target'] = self.target
511 self.sm_config['targetIQN'] = self.targetIQN
512 self.sm_config['multipathable'] = 'true'
513 self.session.xenapi.SR.set_sm_config(self.sr_ref, self.sm_config)
514 return
516 @override
517 def delete(self, sr_uuid) -> None:
518 self.detach(sr_uuid)
519 return
521 @override
522 def probe(self) -> str:
523 SRs = self.session.xenapi.SR.get_all_records()
524 Recs = {}
525 for sr in SRs:
526 record = SRs[sr]
527 sm_config = record["sm_config"]
528 if 'targetIQN' in sm_config and \
529 sm_config['targetIQN'] == self.targetIQN:
530 Recs[record["uuid"]] = sm_config
531 return self.srlist_toxml(Recs)
533 @override
534 def scan(self, sr_uuid) -> None:
535 if not self.passthrough:
536 if not self.attached:
537 raise xs_errors.XenError('SRUnavailable')
538 self.refresh()
539 time.sleep(2) # it seems impossible to tell when a scan's finished
540 self._loadvdis()
541 self.physical_utilisation = self.physical_size
542 for uuid, vdi in self.vdis.items():
543 if vdi.managed:
544 self.physical_utilisation += vdi.size
545 self.virtual_allocation = self.physical_utilisation
546 super(BaseISCSISR, self).scan(sr_uuid)
548 @override
549 def vdi(self, uuid) -> VDI.VDI:
550 return LUNperVDI.RAWVDI(self, uuid)
552 def _scan_IQNs(self):
553 # Verify iSCSI target and port
554 util._testHost(self.target, self.port, 'ISCSITarget')
556 # Test and set the initiatorname file
557 iscsilib.ensure_daemon_running_ok(self.localIQN)
559 map = iscsilib.discovery(self.target, self.port, self.chapuser,
560 self.chappassword,
561 interfaceArray=iscsilib.get_iscsi_interfaces())
562 map.append(("%s:%d" % (self.targetlist, self.port), "0", "*"))
563 self.print_entries(map)
565 def _attach_LUN_bylunid(self, lunid):
566 if not self.attached:
567 raise xs_errors.XenError('SRUnavailable')
568 connected = []
569 for val in self.adapter:
570 if val not in self.pathdict:
571 continue
572 rec = self.pathdict[val]
573 path = os.path.join(rec['path'], "LUN%s" % lunid)
574 realpath = os.path.realpath(path)
575 host = self.adapter[val]
576 l = [realpath, host, 0, 0, lunid]
578 addDevice = True
579 if realpath in self.devs:
580 # if the device is stale remove it before adding again
581 real_SCSIid = None
582 try:
583 real_SCSIid = scsiutil.getSCSIid(realpath)
584 except:
585 pass
587 if real_SCSIid is not None:
588 # make sure this is the same scsiid, if not remove the device
589 cur_scsibuspath = glob.glob('/dev/disk/by-scsibus/*-%s:0:0:%s' % (host, lunid))
590 cur_SCSIid = os.path.basename(cur_scsibuspath[0]).split("-")[0]
591 if cur_SCSIid != real_SCSIid:
592 # looks stale, remove it
593 scsiutil.scsi_dev_ctrl(l, "remove")
594 else:
595 util.SMlog("Not attaching LUNID %s for adapter %s" \
596 " since the device exists and the scsi id %s seems" \
597 " to be valid. " % (lunid, val, real_SCSIid))
598 addDevice = False
599 else:
600 # looks stale, remove it
601 scsiutil.scsi_dev_ctrl(l, "remove")
603 if addDevice:
604 # add the device
605 scsiutil.scsi_dev_ctrl(l, "add")
606 if not util.wait_for_path(path, MAX_LUNID_TIMEOUT):
607 util.SMlog("Unable to detect LUN attached to host on path [%s]" % path)
608 continue
609 connected.append(path)
610 return connected
612 def _attach_LUN_byserialid(self, serialid):
613 if not self.attached:
614 raise xs_errors.XenError('SRUnavailable')
615 connected = []
616 for val in self.adapter:
617 if val not in self.pathdict:
618 continue
619 rec = self.pathdict[val]
620 path = os.path.join(rec['path'], "SERIAL-%s" % serialid)
621 realpath = os.path.realpath(path)
622 if realpath not in self.devs:
623 if not util.wait_for_path(path, 5):
624 util.SMlog("Unable to detect LUN attached to host on serial path [%s]" % path)
625 continue
626 connected.append(path)
627 return connected
629 def _detach_LUN_bylunid(self, lunid, SCSIid):
630 if not self.attached:
631 raise xs_errors.XenError('SRUnavailable')
632 if self.mpath == 'true' and len(SCSIid):
633 self.mpathmodule.reset(SCSIid, explicit_unmap=True)
634 util.remove_mpathcount_field(self.session, self.host_ref, self.sr_ref, SCSIid)
635 for val in self.adapter:
636 if val not in self.pathdict:
637 continue
638 rec = self.pathdict[val]
639 path = os.path.join(rec['path'], "LUN%s" % lunid)
640 realpath = os.path.realpath(path)
641 if realpath in self.devs:
642 util.SMlog("Found key: %s" % realpath)
643 scsiutil.scsi_dev_ctrl(self.devs[realpath], 'remove')
644 # Wait for device to disappear
645 if not util.wait_for_nopath(realpath, MAX_LUNID_TIMEOUT):
646 util.SMlog("Device has not disappeared after %d seconds" % \
647 MAX_LUNID_TIMEOUT)
648 else:
649 util.SMlog("Device [%s,%s] disappeared" % (realpath, path))
651 def _attach_LUN_bySCSIid(self, SCSIid):
652 if not self.attached:
653 raise xs_errors.XenError('SRUnavailable')
655 path = self.mpathmodule.path(SCSIid)
656 if not util.pathexists(path):
657 self.refresh()
658 if not util.wait_for_path(path, MAX_TIMEOUT):
659 util.SMlog("Unable to detect LUN attached to host [%s]"
660 % path)
661 raise xs_errors.XenError('ISCSIDevice')
663 # This function queries the session for the attached LUNs
664 def _loadvdis(self):
665 count = 0
666 if not os.path.exists(self.path):
667 return 0
668 for file in filter(self.match_lun, util.listdir(self.path)):
669 vdi_path = os.path.join(self.path, file)
670 LUNid = file.replace("LUN", "")
671 uuid = scsiutil.gen_uuid_from_string(scsiutil.getuniqueserial(vdi_path))
672 obj = self.vdi(uuid)
673 obj._query(vdi_path, LUNid)
674 self.vdis[uuid] = obj
675 self.physical_size += obj.size
676 count += 1
677 return count
679 def refresh(self):
680 for val in self.adapter:
681 util.SMlog("Rescanning host adapter %s" % self.adapter[val])
682 scsiutil.rescan([self.adapter[val]])
684 # Helper function for LUN-per-VDI VDI.introduce
685 def _getLUNbySMconfig(self, sm_config):
686 if 'LUNid' not in sm_config:
687 raise xs_errors.XenError('VDIUnavailable')
688 LUNid = int(sm_config['LUNid'])
689 if not len(self._attach_LUN_bylunid(LUNid)):
690 raise xs_errors.XenError('VDIUnavailable')
691 return os.path.join(self.path, "LUN%d" % LUNid)
693 # This function takes an ISCSI device and populate it with
694 # a dictionary of available LUNs on that target.
695 def print_LUNs(self):
696 self.LUNs = {}
697 if os.path.exists(self.path):
698 dom0_disks = util.dom0_disks()
699 for file in util.listdir(self.path):
700 if file.find("LUN") != -1 and file.find("_") == -1:
701 vdi_path = os.path.join(self.path, file)
702 if os.path.realpath(vdi_path) in dom0_disks:
703 util.SMlog("Hide dom0 boot disk LUN")
704 else:
705 LUNid = file.replace("LUN", "")
706 obj = self.vdi(self.uuid)
707 obj._query(vdi_path, LUNid)
708 self.LUNs[obj.uuid] = obj
710 def print_entries(self, map):
711 dom = xml.dom.minidom.Document()
712 element = dom.createElement("iscsi-target-iqns")
713 dom.appendChild(element)
714 count = 0
715 for address, tpgt, iqn in map:
716 entry = dom.createElement('TGT')
717 element.appendChild(entry)
718 subentry = dom.createElement('Index')
719 entry.appendChild(subentry)
720 textnode = dom.createTextNode(str(count))
721 subentry.appendChild(textnode)
723 try:
724 # We always expect a port so this holds
725 # regardless of IP version
726 (addr, port) = address.rsplit(':', 1)
727 except:
728 addr = address
729 port = DEFAULT_PORT
730 subentry = dom.createElement('IPAddress')
731 entry.appendChild(subentry)
732 textnode = dom.createTextNode(str(addr))
733 subentry.appendChild(textnode)
735 if int(port) != DEFAULT_PORT:
736 subentry = dom.createElement('Port')
737 entry.appendChild(subentry)
738 textnode = dom.createTextNode(str(port))
739 subentry.appendChild(textnode)
741 subentry = dom.createElement('TargetIQN')
742 entry.appendChild(subentry)
743 textnode = dom.createTextNode(str(iqn))
744 subentry.appendChild(textnode)
745 count += 1
746 print(dom.toprettyxml(), file=sys.stderr)
748 def srlist_toxml(self, SRs):
749 dom = xml.dom.minidom.Document()
750 element = dom.createElement("SRlist")
751 dom.appendChild(element)
753 for val in SRs:
754 record = SRs[val]
755 entry = dom.createElement('SR')
756 element.appendChild(entry)
758 subentry = dom.createElement("UUID")
759 entry.appendChild(subentry)
760 textnode = dom.createTextNode(val)
761 subentry.appendChild(textnode)
763 subentry = dom.createElement("Target")
764 entry.appendChild(subentry)
765 textnode = dom.createTextNode(record['target'])
766 subentry.appendChild(textnode)
768 subentry = dom.createElement("TargetIQN")
769 entry.appendChild(subentry)
770 textnode = dom.createTextNode(record['targetIQN'])
771 subentry.appendChild(textnode)
772 return dom.toprettyxml()
774 def match_lun(self, s):
775 regex = re.compile("_")
776 if regex.search(s, 0):
777 return False
778 regex = re.compile("LUN")
779 return regex.search(s, 0)