# # zfcp.py - mainframe zfcp configuration install data # # Copyright (C) 2001, 2002, 2003, 2004 Red Hat, Inc. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Author(s): Karsten Hopp # import string import os from . import ROOT_PATH from udev import udev_settle from . import util import gettext _ = lambda x: gettext.ldgettext("anaconda", x) import logging log = logging.getLogger("storage") def loggedWriteLineToFile(fn, value): f = open(fn, "w") log.debug("echo %s > %s" % (value, fn)) f.write("%s\n" % (value)) f.close() zfcpsysfs = "/sys/bus/ccw/drivers/zfcp" scsidevsysfs = "/sys/bus/scsi/devices" zfcpconf = "/etc/zfcp.conf" class ZFCPDevice: def __init__(self, devnum, wwpn, fcplun): self.devnum = self.sanitizeDeviceInput(devnum) self.wwpn = self.sanitizeWWPNInput(wwpn) self.fcplun = self.sanitizeFCPLInput(fcplun) if not self.checkValidDevice(self.devnum): raise ValueError, _("You have not specified a device number or the number is invalid") if not self.checkValidWWPN(self.wwpn): raise ValueError, _("You have not specified a worldwide port name or the name is invalid.") if not self.checkValidFCPLun(self.fcplun): raise ValueError, _("You have not specified a FCP LUN or the number is invalid.") def __str__(self): return "%s %s %s" %(self.devnum, self.wwpn, self.fcplun) def sanitizeDeviceInput(self, dev): if dev is None or dev == "": return None dev = dev.lower() bus = dev[:string.rfind(dev, ".") + 1] dev = dev[string.rfind(dev, ".") + 1:] dev = "0" * (4 - len(dev)) + dev if not len(bus): return "0.0." + dev else: return bus + dev def sanitizeWWPNInput(self, id): if id is None or id == "": return None id = id.lower() if id[:2] != "0x": return "0x" + id return id # ZFCP LUNs are usually entered as 16 bit, sysfs accepts only 64 bit # (#125632), expand with zeroes if necessary def sanitizeFCPLInput(self, lun): if lun is None or lun == "": return None lun = lun.lower() if lun[:2] == "0x": lun = lun[2:] lun = "0x" + "0" * (4 - len(lun)) + lun lun = lun + "0" * (16 - len(lun) + 2) return lun def _hextest(self, hex): try: int(hex, 16) return True except TypeError: return False def checkValidDevice(self, id): if id is None or id == "": return False if len(id) != 8: # p.e. 0.0.0600 return False if id[0] not in string.digits or id[2] not in string.digits: return False if id[1] != "." or id[3] != ".": return False return self._hextest(id[4:]) def checkValid64BitHex(self, hex): if hex is None or hex == "": return False if len(hex) != 18: return False return self._hextest(hex) checkValidWWPN = checkValidFCPLun = checkValid64BitHex def onlineDevice(self): online = "%s/%s/online" %(zfcpsysfs, self.devnum) portadd = "%s/%s/port_add" %(zfcpsysfs, self.devnum) portdir = "%s/%s/%s" %(zfcpsysfs, self.devnum, self.wwpn) unitadd = "%s/unit_add" %(portdir) unitdir = "%s/%s" %(portdir, self.fcplun) failed = "%s/failed" %(unitdir) if not os.path.exists(online): log.info("Freeing zFCP device %s" % (self.devnum,)) util.run_program(["zfcp_cio_free", "-d", self.devnum]) if not os.path.exists(online): raise ValueError, _( "zFCP device %s not found, not even in device ignore list." %(self.devnum,)) try: f = open(online, "r") devonline = f.readline().strip() f.close() if devonline != "1": loggedWriteLineToFile(online, "1") except IOError as e: raise ValueError, _("Could not set zFCP device %(devnum)s " "online (%(e)s).") \ % {'devnum': self.devnum, 'e': e} if not os.path.exists(portdir): if os.path.exists(portadd): # older zfcp sysfs interface try: loggedWriteLineToFile(portadd, self.wwpn) udev_settle() except IOError as e: raise ValueError, _("Could not add WWPN %(wwpn)s to zFCP " "device %(devnum)s (%(e)s).") \ % {'wwpn': self.wwpn, 'devnum': self.devnum, 'e': e} else: # newer zfcp sysfs interface with auto port scan raise ValueError, _("WWPN %(wwpn)s not found at zFCP device " "%(devnum)s.") % {'wwpn': self.wwpn, 'devnum': self.devnum} else: if os.path.exists(portadd): # older zfcp sysfs interface log.info("WWPN %(wwpn)s at zFCP device %(devnum)s already " "there." % {'wwpn': self.wwpn, 'devnum': self.devnum}) if not os.path.exists(unitdir): try: loggedWriteLineToFile(unitadd, self.fcplun) udev_settle() except IOError as e: raise ValueError, _("Could not add LUN %(fcplun)s to WWPN " "%(wwpn)s on zFCP device %(devnum)s " "(%(e)s).") \ % {'fcplun': self.fcplun, 'wwpn': self.wwpn, 'devnum': self.devnum, 'e': e} else: raise ValueError, _("LUN %(fcplun)s at WWPN %(wwpn)s on zFCP " "device %(devnum)s already configured.") \ % {'fcplun': self.fcplun, 'wwpn': self.wwpn, 'devnum': self.devnum} fail = "0" try: f = open(failed, "r") fail = f.readline().strip() f.close() except IOError as e: raise ValueError, _("Could not read failed attribute of LUN " "%(fcplun)s at WWPN %(wwpn)s on zFCP device " "%(devnum)s (%(e)s).") \ % {'fcplun': self.fcplun, 'wwpn': self.wwpn, 'devnum': self.devnum, 'e': e} if fail != "0": self.offlineDevice() raise ValueError, _("Failed LUN %(fcplun)s at WWPN %(wwpn)s on " "zFCP device %(devnum)s removed again.") \ % {'fcplun': self.fcplun, 'wwpn': self.wwpn, 'devnum': self.devnum} return True def offlineSCSIDevice(self): f = open("/proc/scsi/scsi", "r") lines = f.readlines() f.close() # alternatively iterate over /sys/bus/scsi/devices/*:0:*:*/ for line in lines: if not line.startswith("Host"): continue scsihost = string.split(line) host = scsihost[1] channel = "0" id = scsihost[5] lun = scsihost[7] scsidev = "%s:%s:%s:%s" % (host[4:], channel, id, lun) fcpsysfs = "%s/%s" % (scsidevsysfs, scsidev) scsidel = "%s/%s/delete" % (scsidevsysfs, scsidev) f = open("%s/hba_id" %(fcpsysfs), "r") fcphbasysfs = f.readline().strip() f.close() f = open("%s/wwpn" %(fcpsysfs), "r") fcpwwpnsysfs = f.readline().strip() f.close() f = open("%s/fcp_lun" %(fcpsysfs), "r") fcplunsysfs = f.readline().strip() f.close() if fcphbasysfs == self.devnum \ and fcpwwpnsysfs == self.wwpn \ and fcplunsysfs == self.fcplun: loggedWriteLineToFile(scsidel, "1") udev_settle() return log.warn("no scsi device found to delete for zfcp %s %s %s" %(self.devnum, self.wwpn, self.fcplun)) def offlineDevice(self): offline = "%s/%s/online" %(zfcpsysfs, self.devnum) portadd = "%s/%s/port_add" %(zfcpsysfs, self.devnum) portremove = "%s/%s/port_remove" %(zfcpsysfs, self.devnum) unitremove = "%s/%s/%s/unit_remove" %(zfcpsysfs, self.devnum, self.wwpn) portdir = "%s/%s/%s" %(zfcpsysfs, self.devnum, self.wwpn) devdir = "%s/%s" %(zfcpsysfs, self.devnum) try: self.offlineSCSIDevice() except IOError as e: raise ValueError, _("Could not correctly delete SCSI device of " "zFCP %(devnum)s %(wwpn)s %(fcplun)s " "(%(e)s).") \ % {'devnum': self.devnum, 'wwpn': self.wwpn, 'fcplun': self.fcplun, 'e': e} try: loggedWriteLineToFile(unitremove, self.fcplun) except IOError as e: raise ValueError, _("Could not remove LUN %(fcplun)s at WWPN " "%(wwpn)s on zFCP device %(devnum)s " "(%(e)s).") \ % {'fcplun': self.fcplun, 'wwpn': self.wwpn, 'devnum': self.devnum, 'e': e} if os.path.exists(portadd): # only try to remove ports with older zfcp sysfs interface for lun in os.listdir(portdir): if lun.startswith("0x") and \ os.path.isdir(os.path.join(portdir, lun)): log.info("Not removing WWPN %s at zFCP device %s since port still has other LUNs, e.g. %s." %(self.wwpn, self.devnum, lun)) return True try: loggedWriteLineToFile(portremove, self.wwpn) except IOError as e: raise ValueError, _("Could not remove WWPN %(wwpn)s on zFCP " "device %(devnum)s (%(e)s).") \ % {'wwpn': self.wwpn, 'devnum': self.devnum, 'e': e} if os.path.exists(portadd): # older zfcp sysfs interface for port in os.listdir(devdir): if port.startswith("0x") and \ os.path.isdir(os.path.join(devdir, port)): log.info("Not setting zFCP device %s offline since it still has other ports, e.g. %s." %(self.devnum, port)) return True else: # newer zfcp sysfs interface with auto port scan import glob luns = glob.glob("%s/0x????????????????/0x????????????????" %(devdir,)) if len(luns) != 0: log.info("Not setting zFCP device %s offline since it still has other LUNs, e.g. %s." %(self.devnum, luns[0])) return True try: loggedWriteLineToFile(offline, "0") except IOError as e: raise ValueError, _("Could not set zFCP device %(devnum)s " "offline (%(e)s).") \ % {'devnum': self.devnum, 'e': e} return True class ZFCP: """ ZFCP utility class. This class will automatically online to ZFCP drives configured in /tmp/fcpconfig when the startup() method gets called. It can also be used to manually configure ZFCP devices through the addFCP() method. As this class needs to make sure that /tmp/fcpconfig configured drives are only onlined once and as it keeps a global list of all ZFCP devices it is implemented as a Singleton. """ def __init__(self): self.intf = None self.fcpdevs = set() self.hasReadConfig = False self.down = True # So that users can write zfcp() to get the singleton instance def __call__(self): return self def readConfig(self): try: f = open(zfcpconf, "r") except IOError: log.info("no %s; not configuring zfcp" % (zfcpconf,)) return lines = map(lambda x: x.strip().lower(), f.readlines()) f.close() for line in lines: if line.startswith("#") or line == '': continue fields = line.split() if len(fields) == 3: devnum = fields[0] wwpn = fields[1] fcplun = fields[2] elif len(fields) == 5: # support old syntax of: # devno scsiid wwpn scsilun fcplun devnum = fields[0] wwpn = fields[2] fcplun = fields[4] else: log.warn("Invalid line found in %s: %s" % (zfcpconf, line,)) continue try: self.addFCP(devnum, wwpn, fcplun) except ValueError as e: if self.intf: self.intf.messageWindow(_("Error"), str(e)) else: log.warning(str(e)) def addFCP(self, devnum, wwpn, fcplun): d = ZFCPDevice(devnum, wwpn, fcplun) if d.onlineDevice(): self.fcpdevs.add(d) def shutdown(self): if self.down: return self.down = True if len(self.fcpdevs) == 0: return for d in self.fcpdevs: try: d.offlineDevice() except ValueError as e: log.warn(str(e)) def startup(self): if not self.down: return self.down = False if not self.hasReadConfig: self.readConfig() self.hasReadConfig = True # readConfig calls addFCP which calls onlineDevice already return if len(self.fcpdevs) == 0: return for d in self.fcpdevs: try: d.onlineDevice() except ValueError as e: log.warn(str(e)) def write(self): if len(self.fcpdevs) == 0: return f = open(ROOT_PATH + zfcpconf, "w") for d in self.fcpdevs: f.write("%s\n" %(d,)) f.close() f = open(ROOT_PATH + "/etc/modprobe.conf", "a") f.write("alias scsi_hostadapter zfcp\n") f.close() # Create ZFCP singleton ZFCP = ZFCP() # vim:tw=78:ts=4:et:sw=4