Coverage for drivers/LVHDoISCSISR.py : 21%

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# LVHDoISCSISR: LVHD over ISCSI software initiator SR driver
19#
21import SR
22import LVHDSR
23import BaseISCSI
24import SRCommand
25import util
26import scsiutil
27import lvutil
28import time
29import os
30import sys
31import xs_errors
32import xmlrpc.client
33import mpath_cli
34import iscsilib
35import glob
36import copy
37import scsiutil
38import xml.dom.minidom
40CAPABILITIES = ["SR_PROBE", "SR_UPDATE", "SR_METADATA", "SR_TRIM",
41 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
42 "VDI_GENERATE_CONFIG", "VDI_CLONE", "VDI_SNAPSHOT",
43 "VDI_RESIZE", "ATOMIC_PAUSE", "VDI_RESET_ON_BOOT/2",
44 "VDI_UPDATE", "VDI_MIRROR", "VDI_CONFIG_CBT",
45 "VDI_ACTIVATE", "VDI_DEACTIVATE"]
47CONFIGURATION = [['SCSIid', 'The scsi_id of the destination LUN'], \
48 ['target', 'IP address or hostname of the iSCSI target'], \
49 ['targetIQN', 'The IQN of the target LUN group to be attached'], \
50 ['chapuser', 'The username to be used during CHAP authentication'], \
51 ['chappassword', 'The password to be used during CHAP authentication'], \
52 ['incoming_chapuser', 'The incoming username to be used during bi-directional CHAP authentication (optional)'], \
53 ['incoming_chappassword', 'The incoming password to be used during bi-directional CHAP authentication (optional)'], \
54 ['port', 'The network port number on which to query the target'], \
55 ['multihomed', 'Enable multi-homing to this target, true or false (optional, defaults to same value as host.other_config:multipathing)'], \
56 ['usediscoverynumber', 'The specific iscsi record index to use. (optional)'], \
57 ['allocation', 'Valid values are thick or thin (optional, defaults to thick)']]
59DRIVER_INFO = {
60 'name': 'LVHD over iSCSI',
61 'description': 'SR plugin which represents disks as Logical Volumes within a Volume Group created on an iSCSI LUN',
62 'vendor': 'Citrix Systems Inc',
63 'copyright': '(C) 2008 Citrix Systems Inc',
64 'driver_version': '1.0',
65 'required_api_version': '1.0',
66 'capabilities': CAPABILITIES,
67 'configuration': CONFIGURATION
68 }
71class LVHDoISCSISR(LVHDSR.LVHDSR):
72 """LVHD over ISCSI storage repository"""
74 def handles(type):
75 if __name__ == '__main__':
76 name = sys.argv[0]
77 else:
78 name = __name__
79 if name.endswith("LVMoISCSISR"):
80 return type == "lvmoiscsi"
81 if type == "lvhdoiscsi":
82 return True
83 return False
84 handles = staticmethod(handles)
86 def load(self, sr_uuid):
87 if not sr_uuid: 87 ↛ 89line 87 didn't jump to line 89, because the condition on line 87 was never true
88 # This is a probe call, generate a temp sr_uuid
89 sr_uuid = util.gen_uuid()
91 # If this is a vdi command, don't initialise SR
92 if util.isVDICommand(self.original_srcmd.cmd):
93 self.SCSIid = self.dconf['SCSIid']
94 else:
95 self.create_iscsi_sessions(sr_uuid)
97 LVHDSR.LVHDSR.load(self, sr_uuid)
99 def create_iscsi_sessions(self, sr_uuid):
100 if 'target' in self.original_srcmd.dconf: 100 ↛ 102line 100 didn't jump to line 102, because the condition on line 100 was never false
101 self.original_srcmd.dconf['targetlist'] = self.original_srcmd.dconf['target']
102 iscsi = BaseISCSI.BaseISCSISR(self.original_srcmd, sr_uuid)
103 self.iscsiSRs = []
104 self.iscsiSRs.append(iscsi)
105 saved_exc = None
106 if self.dconf['target'].find(',') == 0 or self.dconf['targetIQN'] == "*":
107 # Instantiate multiple sessions
108 self.iscsiSRs = []
109 if self.dconf['targetIQN'] == "*": 109 ↛ 112line 109 didn't jump to line 112, because the condition on line 109 was never false
110 IQN = "any"
111 else:
112 IQN = self.dconf['targetIQN']
113 dict = {}
114 IQNstring = ""
115 IQNs = []
116 try:
117 if 'multiSession' in self.dconf: 117 ↛ 118line 117 didn't jump to line 118, because the condition on line 117 was never true
118 IQNs = self.dconf['multiSession'].split("|")
119 for IQN in IQNs:
120 if IQN:
121 dict[IQN] = ""
122 else:
123 try:
124 IQNs.remove(IQN)
125 except:
126 # Exceptions are not expected but just in case
127 pass
128 # Order in multiSession must be preserved. It is important for dual-controllers.
129 # IQNstring cannot be built with a dictionary iteration because of this
130 IQNstring = self.dconf['multiSession']
131 else:
132 for tgt in self.dconf['target'].split(','): 132 ↛ 151line 132 didn't jump to line 151, because the loop on line 132 didn't complete
133 try:
134 tgt_ip = util._convertDNS(tgt)
135 except:
136 raise xs_errors.XenError('DNSError')
137 iscsilib.ensure_daemon_running_ok(iscsi.localIQN)
138 map = iscsilib.discovery(tgt_ip, iscsi.port, iscsi.chapuser, iscsi.chappassword, targetIQN=IQN)
139 util.SMlog("Discovery for IP %s returned %s" % (tgt, map))
140 for i in range(0, len(map)):
141 (portal, tpgt, iqn) = map[i]
142 (ipaddr, port) = iscsilib.parse_IP_port(portal)
143 try:
144 util._testHost(ipaddr, int(port), 'ISCSITarget')
145 except:
146 util.SMlog("Target Not reachable: (%s:%s)" % (ipaddr, port))
147 continue
148 key = "%s,%s,%s" % (ipaddr, port, iqn)
149 dict[key] = ""
150 # Again, do not mess up with IQNs order. Dual controllers will benefit from that
151 if IQNstring == "":
152 # Compose the IQNstring first
153 for key in dict.keys():
154 IQNstring += "%s|" % key
155 # Reinitialize and store iterator
156 key_iterator = iter(dict.keys())
157 else:
158 key_iterator = IQNs
160 # Now load the individual iSCSI base classes
161 for key in key_iterator:
162 (ipaddr, port, iqn) = key.split(',')
163 srcmd_copy = copy.deepcopy(self.original_srcmd)
164 srcmd_copy.dconf['target'] = ipaddr
165 srcmd_copy.dconf['targetIQN'] = iqn
166 srcmd_copy.dconf['multiSession'] = IQNstring
167 util.SMlog("Setting targetlist: %s" % srcmd_copy.dconf['targetlist'])
168 self.iscsiSRs.append(BaseISCSI.BaseISCSISR(srcmd_copy, sr_uuid))
169 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
170 if pbd is not None and 'multiSession' not in self.dconf:
171 dconf = self.session.xenapi.PBD.get_device_config(pbd)
172 dconf['multiSession'] = IQNstring
173 self.session.xenapi.PBD.set_device_config(pbd, dconf)
174 except Exception as exc:
175 util.logException("LVHDoISCSISR.load")
176 saved_exc = exc
177 try:
178 self.iscsi = self.iscsiSRs[0]
179 except IndexError as exc:
180 if isinstance(saved_exc, SR.SROSError):
181 raise saved_exc # pylint: disable-msg=E0702
182 elif isinstance(saved_exc, Exception): 182 ↛ 185line 182 didn't jump to line 185, because the condition on line 182 was never false
183 raise xs_errors.XenError('SMGeneral', str(saved_exc))
184 else:
185 raise xs_errors.XenError('SMGeneral', str(exc))
186 # Be extremely careful not to throw exceptions here since this function
187 # is the main one used by all operations including probing and creating
188 pbd = None
189 try:
190 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
191 except:
192 pass
193 # Apart from the upgrade case, user must specify a SCSIid
194 if 'SCSIid' not in self.dconf: 194 ↛ 196line 194 didn't jump to line 196, because the condition on line 194 was never true
195 # Dual controller issue
196 self.LUNs = {} # Dict for LUNs from all the iscsi objects
197 for ii in range(0, len(self.iscsiSRs)):
198 self.iscsi = self.iscsiSRs[ii]
199 self._LUNprint(sr_uuid)
200 for key in self.iscsi.LUNs:
201 self.LUNs[key] = self.iscsi.LUNs[key]
202 self.print_LUNs_XML()
203 self.iscsi = self.iscsiSRs[0] # back to original value
204 raise xs_errors.XenError('ConfigSCSIid')
205 self.SCSIid = self.dconf['SCSIid']
206 # This block checks if the first iscsi target contains the right SCSIid.
207 # If not it scans the other iscsi targets because chances are that more
208 # than one controller is present
209 dev_match = False
210 forced_login = False
211 # No need to check if only one iscsi target is present
212 if len(self.iscsiSRs) == 1: 212 ↛ 215line 212 didn't jump to line 215, because the condition on line 212 was never false
213 pass
214 else:
215 target_success = False
216 attempt_discovery = False
217 for iii in range(0, len(self.iscsiSRs)):
218 # Check we didn't leave any iscsi session open
219 # If exceptions happened before, the cleanup function has worked on the right target.
220 if forced_login == True:
221 try:
222 iscsilib.ensure_daemon_running_ok(self.iscsi.localIQN)
223 iscsilib.logout(self.iscsi.target, self.iscsi.targetIQN)
224 forced_login = False
225 except:
226 raise xs_errors.XenError('ISCSILogout')
227 self.iscsi = self.iscsiSRs[iii]
228 util.SMlog("path %s" % self.iscsi.path)
229 util.SMlog("iscsci data: targetIQN %s, portal %s" % (self.iscsi.targetIQN, self.iscsi.target))
230 iscsilib.ensure_daemon_running_ok(self.iscsi.localIQN)
231 if not iscsilib._checkTGT(self.iscsi.targetIQN):
232 attempt_discovery = True
233 try:
234 # Ensure iscsi db has been populated
235 map = iscsilib.discovery(
236 self.iscsi.target,
237 self.iscsi.port,
238 self.iscsi.chapuser,
239 self.iscsi.chappassword,
240 targetIQN=self.iscsi.targetIQN)
241 if len(map) == 0:
242 util.SMlog("Discovery for iscsi data targetIQN %s,"
243 " portal %s returned empty list"
244 " Trying another path if available" %
245 (self.iscsi.targetIQN,
246 self.iscsi.target))
247 continue
248 except:
249 util.SMlog("Discovery failed for iscsi data targetIQN"
250 " %s, portal %s. Trying another path if"
251 " available" %
252 (self.iscsi.targetIQN, self.iscsi.target))
253 continue
254 try:
255 iscsilib.login(self.iscsi.target,
256 self.iscsi.targetIQN,
257 self.iscsi.chapuser,
258 self.iscsi.chappassword,
259 self.iscsi.incoming_chapuser,
260 self.iscsi.incoming_chappassword,
261 self.mpath == "true")
262 except:
263 util.SMlog("Login failed for iscsi data targetIQN %s,"
264 " portal %s. Trying another path"
265 " if available" %
266 (self.iscsi.targetIQN, self.iscsi.target))
267 continue
268 target_success = True
269 forced_login = True
270 # A session should be active.
271 if not util.wait_for_path(self.iscsi.path, BaseISCSI.MAX_TIMEOUT):
272 util.SMlog("%s has no associated LUNs" % self.iscsi.targetIQN)
273 continue
274 scsiid_path = "/dev/disk/by-id/scsi-" + self.SCSIid
275 if not util.wait_for_path(scsiid_path, BaseISCSI.MAX_TIMEOUT):
276 util.SMlog("%s not found" % scsiid_path)
277 continue
278 for file in filter(self.iscsi.match_lun, util.listdir(self.iscsi.path)):
279 lun_path = os.path.join(self.iscsi.path, file)
280 lun_dev = scsiutil.getdev(lun_path)
281 try:
282 lun_scsiid = scsiutil.getSCSIid(lun_dev)
283 except:
284 util.SMlog("getSCSIid failed on %s in iscsi %s: LUN"
285 " offline or iscsi path down" %
286 (lun_dev, self.iscsi.path))
287 continue
288 util.SMlog("dev from lun %s %s" % (lun_dev, lun_scsiid))
289 if lun_scsiid == self.SCSIid:
290 util.SMlog("lun match in %s" % self.iscsi.path)
291 dev_match = True
292 # No more need to raise ISCSITarget exception.
293 # Resetting attempt_discovery
294 attempt_discovery = False
295 break
296 if dev_match:
297 if iii == 0:
298 break
299 util.SMlog("IQN reordering needed")
300 new_iscsiSRs = []
301 IQNs = {}
302 IQNstring = ""
303 # iscsiSRs can be seen as a circular buffer: the head now is the matching one
304 for kkk in list(range(iii, len(self.iscsiSRs))) + list(range(0, iii)):
305 new_iscsiSRs.append(self.iscsiSRs[kkk])
306 ipaddr = self.iscsiSRs[kkk].target
307 port = self.iscsiSRs[kkk].port
308 iqn = self.iscsiSRs[kkk].targetIQN
309 key = "%s,%s,%s" % (ipaddr, port, iqn)
310 # The final string must preserve the order without repetition
311 if key not in IQNs:
312 IQNs[key] = ""
313 IQNstring += "%s|" % key
314 util.SMlog("IQNstring is now %s" % IQNstring)
315 self.iscsiSRs = new_iscsiSRs
316 util.SMlog("iqn %s is leading now" % self.iscsiSRs[0].targetIQN)
317 # Updating pbd entry, if any
318 try:
319 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
320 if pbd is not None and 'multiSession' in self.dconf:
321 util.SMlog("Updating multiSession in PBD")
322 dconf = self.session.xenapi.PBD.get_device_config(pbd)
323 dconf['multiSession'] = IQNstring
324 self.session.xenapi.PBD.set_device_config(pbd, dconf)
325 except:
326 pass
327 break
328 if not target_success and attempt_discovery:
329 raise xs_errors.XenError('ISCSITarget')
331 # Check for any unneeded open iscsi sessions
332 if forced_login == True:
333 try:
334 iscsilib.ensure_daemon_running_ok(self.iscsi.localIQN)
335 iscsilib.logout(self.iscsi.target, self.iscsi.targetIQN)
336 forced_login = False
337 except:
338 raise xs_errors.XenError('ISCSILogout')
340 def print_LUNs_XML(self):
341 dom = xml.dom.minidom.Document()
342 element = dom.createElement("iscsi-target")
343 dom.appendChild(element)
344 for uuid in self.LUNs:
345 val = self.LUNs[uuid]
346 entry = dom.createElement('LUN')
347 element.appendChild(entry)
349 for attr in ('vendor', 'serial', 'LUNid', \
350 'size', 'SCSIid'):
351 try:
352 aval = getattr(val, attr)
353 except AttributeError:
354 continue
356 if aval:
357 subentry = dom.createElement(attr)
358 entry.appendChild(subentry)
359 textnode = dom.createTextNode(str(aval))
360 subentry.appendChild(textnode)
362 print(dom.toprettyxml(), file=sys.stderr)
364 def _getSCSIid_from_LUN(self, sr_uuid):
365 was_attached = True
366 self.iscsi.attach(sr_uuid)
367 dev = self.dconf['LUNid'].split(',')
368 if len(dev) > 1:
369 raise xs_errors.XenError('LVMOneLUN')
370 path = os.path.join(self.iscsi.path, "LUN%s" % dev[0])
371 if not util.wait_for_path(path, BaseISCSI.MAX_TIMEOUT):
372 util.SMlog("Unable to detect LUN attached to host [%s]" % path)
373 try:
374 SCSIid = scsiutil.getSCSIid(path)
375 except:
376 raise xs_errors.XenError('InvalidDev')
377 self.iscsi.detach(sr_uuid)
378 return SCSIid
380 def _LUNprint(self, sr_uuid):
381 if self.iscsi.attached:
382 # Force a rescan on the bus.
383 self.iscsi.refresh()
384# time.sleep(5)
385# Now call attach (handles the refcounting + session activa)
386 self.iscsi.attach(sr_uuid)
388 util.SMlog("LUNprint: waiting for path: %s" % self.iscsi.path)
389 if util.wait_for_path("%s/LUN*" % self.iscsi.path, BaseISCSI.MAX_TIMEOUT):
390 try:
391 adapter = self.iscsi.adapter[self.iscsi.address]
392 util.SMlog("adapter=%s" % adapter)
394 # find a scsi device on which to issue a report luns command:
395 devs = glob.glob("%s/LUN*" % self.iscsi.path)
396 sgdevs = []
397 for i in devs:
398 sgdevs.append(int(i.split("LUN")[1]))
399 sgdevs.sort()
400 sgdev = "%s/LUN%d" % (self.iscsi.path, sgdevs[0])
402 # issue a report luns:
403 luns = util.pread2(["/usr/bin/sg_luns", "-q", sgdev]).split('\n')
404 nluns = len(luns) - 1 # remove the line relating to the final \n
406 # make sure we've got that many sg devices present
407 for i in range(0, 30):
408 luns = scsiutil._dosgscan()
409 sgdevs = [r for r in luns if r[1] == adapter]
410 if len(sgdevs) >= nluns:
411 util.SMlog("Got all %d sg devices" % nluns)
412 break
413 else:
414 util.SMlog("Got %d sg devices - expecting %d" % (len(sgdevs), nluns))
415 time.sleep(1)
417 if os.path.exists("/sbin/udevsettle"):
418 util.pread2(["/sbin/udevsettle"])
419 else:
420 util.pread2(["/sbin/udevadm", "settle"])
421 except:
422 util.SMlog("Generic exception caught. Pass")
423 pass # Make sure we don't break the probe...
425 self.iscsi.print_LUNs()
426 self.iscsi.detach(sr_uuid)
428 def create(self, sr_uuid, size):
429 # Check SCSIid not already in use by other PBDs
430 if util.test_SCSIid(self.session, sr_uuid, self.SCSIid):
431 raise xs_errors.XenError('SRInUse')
433 self.iscsi.attach(sr_uuid)
434 try:
435 self.iscsi._attach_LUN_bySCSIid(self.SCSIid)
436 self._pathrefresh(LVHDoISCSISR)
437 LVHDSR.LVHDSR.create(self, sr_uuid, size)
438 except Exception as inst:
439 self.iscsi.detach(sr_uuid)
440 raise xs_errors.XenError("SRUnavailable", opterr=inst)
441 self.iscsi.detach(sr_uuid)
443 def delete(self, sr_uuid):
444 self._pathrefresh(LVHDoISCSISR)
445 LVHDSR.LVHDSR.delete(self, sr_uuid)
446 for i in self.iscsiSRs:
447 i.detach(sr_uuid)
449 def attach(self, sr_uuid):
450 try:
451 connected = False
452 for i in self.iscsiSRs:
453 try:
454 i.attach(sr_uuid)
455 except SR.SROSError as inst:
456 # Some iscsi objects can fail login/discovery but not all. Storing exception
457 if inst.errno in [141, 83]:
458 util.SMlog("Connection failed for target %s, continuing.." % i.target)
459 stored_exception = inst
460 continue
461 else:
462 raise
463 else:
464 connected = True
466 i._attach_LUN_bySCSIid(self.SCSIid)
468 # Check if at least one iscsi succeeded
469 if not connected:
470 raise stored_exception
472 if 'multiSession' in self.dconf:
473 # Force a manual bus refresh
474 for a in self.iscsi.adapter:
475 scsiutil.rescan([self.iscsi.adapter[a]])
477 self._pathrefresh(LVHDoISCSISR)
478 LVHDSR.LVHDSR.attach(self, sr_uuid)
479 except Exception as inst:
480 for i in self.iscsiSRs:
481 i.detach(sr_uuid)
482 raise xs_errors.XenError("SRUnavailable", opterr=inst)
483 self._setMultipathableFlag(SCSIid=self.SCSIid)
485 def detach(self, sr_uuid):
486 LVHDSR.LVHDSR.detach(self, sr_uuid)
487 for i in self.iscsiSRs:
488 i.detach(sr_uuid)
490 def scan(self, sr_uuid):
491 self._pathrefresh(LVHDoISCSISR)
492 if self.mpath == "true":
493 for i in self.iscsiSRs:
494 try:
495 i.attach(sr_uuid)
496 except SR.SROSError:
497 util.SMlog("Connection failed for target %s, continuing.." % i.target)
498 LVHDSR.LVHDSR.scan(self, sr_uuid)
500 def probe(self):
501 self.uuid = util.gen_uuid()
503 # When multipathing is enabled, since we don't refcount the multipath maps,
504 # we should not attempt to do the iscsi.attach/detach when the map is already present,
505 # as this will remove it (which may well be in use).
506 if self.mpath == 'true' and 'SCSIid' in self.dconf:
507 maps = []
508 try:
509 maps = mpath_cli.list_maps()
510 except:
511 pass
513 if self.dconf['SCSIid'] in maps:
514 raise xs_errors.XenError('SRInUse')
516 self.iscsi.attach(self.uuid)
517 self.iscsi._attach_LUN_bySCSIid(self.SCSIid)
518 self._pathrefresh(LVHDoISCSISR)
519 out = LVHDSR.LVHDSR.probe(self)
520 self.iscsi.detach(self.uuid)
521 return out
523 def check_sr(self, sr_uuid):
524 """Hook to check SR health"""
525 pbdref = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
526 if pbdref:
527 other_config = self.session.xenapi.PBD.get_other_config(pbdref)
528 if util.sessions_less_than_targets(other_config, self.dconf):
529 self.create_iscsi_sessions(sr_uuid)
530 for iscsi in self.iscsiSRs:
531 try:
532 iscsi.attach(sr_uuid)
533 except SR.SROSError:
534 util.SMlog("Failed to attach iSCSI target")
536 def vdi(self, uuid):
537 return LVHDoISCSIVDI(self, uuid)
540class LVHDoISCSIVDI(LVHDSR.LVHDVDI):
541 def generate_config(self, sr_uuid, vdi_uuid):
542 util.SMlog("LVHDoISCSIVDI.generate_config")
543 if not lvutil._checkLV(self.path):
544 raise xs_errors.XenError('VDIUnavailable')
545 dict = {}
546 self.sr.dconf['localIQN'] = self.sr.iscsi.localIQN
547 self.sr.dconf['multipathing'] = self.sr.mpath
548 self.sr.dconf['multipathhandle'] = self.sr.mpathhandle
549 dict['device_config'] = self.sr.dconf
550 if 'chappassword_secret' in dict['device_config']:
551 s = util.get_secret(self.session, dict['device_config']['chappassword_secret'])
552 del dict['device_config']['chappassword_secret']
553 dict['device_config']['chappassword'] = s
554 dict['sr_uuid'] = sr_uuid
555 dict['vdi_uuid'] = vdi_uuid
556 dict['command'] = 'vdi_attach_from_config'
557 # Return the 'config' encoded within a normal XMLRPC response so that
558 # we can use the regular response/error parsing code.
559 config = xmlrpc.client.dumps(tuple([dict]), "vdi_attach_from_config")
560 return xmlrpc.client.dumps((config, ), "", True)
562 def attach_from_config(self, sr_uuid, vdi_uuid):
563 util.SMlog("LVHDoISCSIVDI.attach_from_config")
564 try:
565 self.sr.iscsi.attach(sr_uuid)
566 self.sr.iscsi._attach_LUN_bySCSIid(self.sr.SCSIid)
567 return LVHDSR.LVHDVDI.attach(self, sr_uuid, vdi_uuid)
568 except:
569 util.logException("LVHDoISCSIVDI.attach_from_config")
570 raise xs_errors.XenError('SRUnavailable', \
571 opterr='Unable to attach the heartbeat disk')
574if __name__ == '__main__': 574 ↛ 575line 574 didn't jump to line 575, because the condition on line 574 was never true
575 SRCommand.run(LVHDoISCSISR, DRIVER_INFO)
576else:
577 SR.registerSR(LVHDoISCSISR)