mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
469 lines
14 KiB
Python
Executable File
469 lines
14 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Copyright (C) 2006-2007 XenSource Ltd.
|
|
# Copyright (C) 2008-2009 Citrix Ltd.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License as published
|
|
# by the Free Software Foundation; version 2.1 only.
|
|
#
|
|
# 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 Lesser General Public License for more details.
|
|
#
|
|
# Miscellaneous scsi utility functions
|
|
#
|
|
|
|
import util, SR
|
|
import os
|
|
import re
|
|
import xs_errors
|
|
import base64
|
|
import time
|
|
import errno
|
|
import glob
|
|
|
|
PREFIX_LEN = 4
|
|
SUFFIX_LEN = 12
|
|
SECTOR_SHIFT = 9
|
|
|
|
def gen_hash(st, len):
|
|
hs = 0
|
|
for i in st:
|
|
hs = ord(i) + (hs << 6) + (hs << 16) - hs
|
|
return str(hs)[0:len]
|
|
|
|
def gen_uuid_from_serial(iqn, serial):
|
|
if len(serial) < SUFFIX_LEN:
|
|
raise util.CommandException(1)
|
|
prefix = gen_hash(iqn, PREFIX_LEN)
|
|
suffix = gen_hash(serial, SUFFIX_LEN)
|
|
str = prefix.encode("hex") + suffix.encode("hex")
|
|
return str[0:8]+'-'+str[8:12]+'-'+str[12:16]+'-'+str[16:20]+'-'+str[20:32]
|
|
|
|
def gen_serial_from_uuid(iqn, uuid):
|
|
str = uuid.replace('-','')
|
|
prefix = gen_hash(iqn, PREFIX_LEN)
|
|
if str[0:(PREFIX_LEN * 2)].decode("hex") != prefix:
|
|
raise util.CommandException(1)
|
|
return str[(PREFIX_LEN * 2):].decode("hex")
|
|
|
|
def getsize(path):
|
|
dev = getdev(path)
|
|
sysfs = os.path.join('/sys/block',dev,'size')
|
|
size = 0
|
|
if os.path.exists(sysfs):
|
|
try:
|
|
f=open(sysfs, 'r')
|
|
size = (long(f.readline()) << SECTOR_SHIFT)
|
|
f.close()
|
|
except:
|
|
pass
|
|
return size
|
|
|
|
def getuniqueserial(path):
|
|
dev = getdev(path)
|
|
output = gen_rdmfile()
|
|
try:
|
|
cmd = ["md5sum"]
|
|
txt = util.pread3(cmd, getSCSIid(path))
|
|
return txt.split(' ')[0]
|
|
except:
|
|
return ''
|
|
|
|
def gen_uuid_from_string(str):
|
|
if len(str) < (PREFIX_LEN + SUFFIX_LEN):
|
|
raise util.CommandException(1)
|
|
return str[0:8]+'-'+str[8:12]+'-'+str[12:16]+'-'+str[16:20]+'-'+str[20:32]
|
|
|
|
def SCSIid_sanitise(str):
|
|
text = re.sub("^\s+","",str)
|
|
return re.sub("\s+","_",text)
|
|
|
|
def getSCSIid(path):
|
|
dev = rawdev(path)
|
|
cmd = ["scsi_id", "-g", "-s", "/block/%s" % dev]
|
|
return SCSIid_sanitise(util.pread2(cmd)[:-1])
|
|
|
|
def compareSCSIid_2_6_18(SCSIid, path):
|
|
serial = getserial(path)
|
|
len_serial = len(serial)
|
|
if (len_serial == 0 ) or (len_serial > (len(SCSIid) - 1)):
|
|
return False
|
|
list_SCSIid = list(SCSIid)
|
|
list_serial = list_SCSIid[1:(len_serial + 1)]
|
|
serial_2_6_18 = ''.join(list_serial)
|
|
if (serial == serial_2_6_18):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def getserial(path):
|
|
dev = os.path.join('/dev',getdev(path))
|
|
try:
|
|
cmd = ["sginfo", "-s", dev]
|
|
text = re.sub("\s+","",util.pread2(cmd))
|
|
except:
|
|
raise xs_errors.XenError('EIO', \
|
|
opterr='An error occured querying device serial number [%s]' \
|
|
% dev)
|
|
try:
|
|
return text.split("'")[1]
|
|
except:
|
|
return ''
|
|
|
|
def getmanufacturer(path):
|
|
cmd = ["sginfo", "-M", path]
|
|
try:
|
|
for line in filter(match_vendor, util.pread2(cmd).split('\n')):
|
|
return line.replace(' ','').split(':')[-1]
|
|
except:
|
|
return ''
|
|
|
|
def cacheSCSIidentifiers():
|
|
SCSI = {}
|
|
SYS_PATH = "/dev/disk/by-scsibus/*"
|
|
for node in glob.glob(SYS_PATH):
|
|
dev = os.path.realpath(node)
|
|
HBTL = os.path.basename(node).split("-")[-1].split(":")
|
|
line = "NONE %s %s %s %s 0 %s" % \
|
|
(HBTL[0],HBTL[1],HBTL[2],HBTL[3],dev)
|
|
ids = line.split()
|
|
SCSI[ids[6]] = ids
|
|
return SCSI
|
|
|
|
def scsi_dev_ctrl(ids, cmd):
|
|
f = -1
|
|
for i in range(0,10):
|
|
try:
|
|
str = "scsi %s-single-device %s %s %s %s" % \
|
|
(cmd, ids[1],ids[2],ids[3],ids[4])
|
|
util.SMlog(str)
|
|
f=open('/proc/scsi/scsi', 'w')
|
|
print >>f, str
|
|
f.close()
|
|
return
|
|
except IOError, e:
|
|
util.SMlog("SCSI_DEV_CTRL: Failure, %s [%d]" % (e.strerror,e.errno))
|
|
if f >= 0:
|
|
f.close()
|
|
if e.errno == errno.ENXIO:
|
|
util.SMlog("Device has disappeared already")
|
|
return
|
|
f = -1
|
|
time.sleep(6)
|
|
continue
|
|
raise xs_errors.XenError('EIO', \
|
|
opterr='An error occured during the scsi operation')
|
|
|
|
def getdev(path):
|
|
realpath = os.path.realpath(path)
|
|
if match_dm(realpath):
|
|
newpath = realpath.replace("/dev/mapper/","/dev/disk/by-id/scsi-")
|
|
else:
|
|
newpath = path
|
|
return os.path.realpath(newpath).split('/')[-1]
|
|
|
|
def rawdev(dev):
|
|
return re.sub("[0-9]*$","",getdev(dev))
|
|
|
|
def getSessionID(path):
|
|
for line in filter(match_session, util.listdir(path)):
|
|
return line.split('-')[-1]
|
|
|
|
def match_session(s):
|
|
regex = re.compile("^SESSIONID-")
|
|
return regex.search(s, 0)
|
|
|
|
def match_vendor(s):
|
|
regex = re.compile("^Vendor:")
|
|
return regex.search(s, 0)
|
|
|
|
def match_dm(s):
|
|
regex = re.compile("mapper/")
|
|
return regex.search(s, 0)
|
|
|
|
def match_sd(s):
|
|
regex = re.compile("/dev/sd")
|
|
return regex.search(s, 0)
|
|
|
|
def _isSCSIdev(dev):
|
|
if match_dm(dev):
|
|
path = dev.replace("/dev/mapper/","/dev/disk/by-id/scsi-")
|
|
else:
|
|
path = dev
|
|
return match_sd(os.path.realpath(path))
|
|
|
|
def gen_rdmfile():
|
|
return "/tmp/%s" % util.gen_uuid()
|
|
|
|
def add_serial_record(session, sr_ref, devstring):
|
|
try:
|
|
conf = session.xenapi.SR.get_sm_config(sr_ref)
|
|
conf['devserial'] = devstring
|
|
session.xenapi.SR.set_sm_config(sr_ref, conf)
|
|
except:
|
|
pass
|
|
|
|
def get_serial_record(session, sr_ref):
|
|
try:
|
|
conf = session.xenapi.SR.get_sm_config(sr_ref)
|
|
return conf['devserial']
|
|
except:
|
|
return ""
|
|
|
|
def devlist_to_serialstring(devlist):
|
|
serial = ''
|
|
for dev in devlist:
|
|
try:
|
|
devserial = "scsi-%s" % getSCSIid(dev)
|
|
if not len(devserial) > 0:
|
|
continue
|
|
if len(serial):
|
|
serial += ','
|
|
serial += devserial
|
|
except:
|
|
pass
|
|
|
|
return serial
|
|
|
|
def gen_synthetic_page_data(uuid):
|
|
# For generating synthetic page data for non-raw LUNs
|
|
# we set the vendor ID to XENSRC
|
|
# Note that the Page 80 serial number must be limited
|
|
# to 16 characters
|
|
page80 = ""
|
|
page80 += "\x00\x80"
|
|
page80 += "\x00\x12"
|
|
page80 += uuid[0:16]
|
|
page80 += " "
|
|
|
|
page83 = ""
|
|
page83 += "\x00\x83"
|
|
page83 += "\x00\x31"
|
|
page83 += "\x02\x01\x00\x2d"
|
|
page83 += "XENSRC "
|
|
page83 += uuid
|
|
page83 += " "
|
|
return ["",base64.b64encode(page80),base64.b64encode(page83)]
|
|
|
|
def gen_raw_page_data(path):
|
|
default = ""
|
|
page80 = ""
|
|
page83 = ""
|
|
try:
|
|
cmd = ["sg_inq", "-r", path]
|
|
text = util.pread2(cmd)
|
|
default = base64.b64encode(text)
|
|
|
|
cmd = ["sg_inq", "--page=0x80", "-r", path]
|
|
text = util.pread2(cmd)
|
|
page80 = base64.b64encode(text)
|
|
|
|
cmd = ["sg_inq", "--page=0x83", "-r", path]
|
|
text = util.pread2(cmd)
|
|
page83 = base64.b64encode(text)
|
|
except:
|
|
pass
|
|
return [default,page80,page83]
|
|
|
|
def update_XS_SCSIdata(session, vdi_ref, vdi_uuid, data):
|
|
try:
|
|
session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, "vdi-uuid")
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, "scsi/0x12/default")
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, "scsi/0x12/0x80")
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, "scsi/0x12/0x83")
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
session.xenapi.VDI.add_to_xenstore_data(vdi_ref, "vdi-uuid", vdi_uuid)
|
|
if len(data[0]):
|
|
session.xenapi.VDI.add_to_xenstore_data(vdi_ref, "scsi/0x12/default", data[0])
|
|
|
|
if len(data[1]):
|
|
session.xenapi.VDI.add_to_xenstore_data(vdi_ref, "scsi/0x12/0x80", data[1])
|
|
|
|
if len(data[2]):
|
|
session.xenapi.VDI.add_to_xenstore_data(vdi_ref, "scsi/0x12/0x83", data[2])
|
|
except:
|
|
pass
|
|
|
|
def rescan(ids, scanstring='- - -'):
|
|
for id in ids:
|
|
refresh_HostID(id, scanstring)
|
|
|
|
def _genArrayIdentifier(dev):
|
|
try:
|
|
cmd = ["sg_inq", "--page=0xc8", "-r", dev]
|
|
id = util.pread2(cmd)
|
|
return id.encode("hex")[180:212]
|
|
except:
|
|
return ""
|
|
|
|
|
|
def _genHostList(procname):
|
|
# loop through and check all adapters
|
|
ids = []
|
|
try:
|
|
for dir in util.listdir('/sys/class/scsi_host'):
|
|
filename = os.path.join('/sys/class/scsi_host',dir,'proc_name')
|
|
if os.path.exists(filename):
|
|
f = open(filename, 'r')
|
|
if f.readline().find(procname) != -1:
|
|
ids.append(dir.replace("host",""))
|
|
f.close()
|
|
except:
|
|
pass
|
|
return ids
|
|
|
|
def _genReverseSCSIidmap(SCSIid, pathname="scsibus"):
|
|
util.SMlog("map_by_scsibus: sid=%s" % SCSIid)
|
|
|
|
devices = []
|
|
for link in glob.glob('/dev/disk/by-id/scsi-%s' % SCSIid):
|
|
devices.append(os.path.realpath(link))
|
|
return devices
|
|
|
|
def _genReverseSCSidtoLUNidmap(SCSIid):
|
|
devices = []
|
|
for link in glob.glob('/dev/disk/by-scsibus/%s-*' % SCSIid):
|
|
devices.append(link.split('-')[-1])
|
|
return devices
|
|
|
|
def _dosgscan():
|
|
regex=re.compile("([^:]*):\s+scsi([0-9]+)\s+channel=([0-9]+)\s+id=([0-9]+)\s+lun=([0-9]+)")
|
|
scan=util.pread2(["/usr/bin/sg_scan"]).split('\n')
|
|
sgs=[]
|
|
for line in scan:
|
|
m=regex.match(line)
|
|
if m:
|
|
device=m.group(1)
|
|
host=m.group(2)
|
|
channel=m.group(3)
|
|
sid=m.group(4)
|
|
lun=m.group(5)
|
|
sgs.append([device,host,channel,sid,lun])
|
|
return sgs
|
|
|
|
def refresh_HostID(HostID, scanstring):
|
|
LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID)
|
|
li = []
|
|
for l in LUNs:
|
|
chan = re.sub(":[0-9]*$",'',os.path.basename(l))
|
|
if chan not in li:
|
|
li.append(chan)
|
|
|
|
fullrescan = True
|
|
if len(li) and scanstring == "- - -":
|
|
fullrescan = False
|
|
for c in li:
|
|
if not refresh_scsi_channel(c):
|
|
fullrescan = True
|
|
|
|
if fullrescan:
|
|
util.SMlog("Rescanning HostID %s with %s" % (HostID, scanstring))
|
|
path = '/sys/class/scsi_host/host%s/scan' % HostID
|
|
if os.path.exists(path):
|
|
try:
|
|
f=open(path, 'w')
|
|
f.write('%s\n' % scanstring)
|
|
f.close()
|
|
except:
|
|
pass
|
|
# Host Bus scan issued, now try to detect channels
|
|
if util.wait_for_path("/sys/class/scsi_disk/%s*" % HostID, 5):
|
|
# At least one LUN is mapped
|
|
LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID)
|
|
li = []
|
|
for l in LUNs:
|
|
chan = re.sub(":[0-9]*$",'',os.path.basename(l))
|
|
if chan not in li:
|
|
li.append(chan)
|
|
for c in li:
|
|
refresh_scsi_channel(c)
|
|
|
|
|
|
def refresh_scsi_channel(channel):
|
|
DEV_WAIT = 5
|
|
util.SMlog("Refreshing channel %s" % channel)
|
|
util.wait_for_path('/dev/disk/by-scsibus/*-%s*' % channel, DEV_WAIT)
|
|
LUNs = glob.glob('/dev/disk/by-scsibus/*-%s*' % channel)
|
|
try:
|
|
rootdevs = util.dom0_disks()
|
|
except:
|
|
util.SMlog("Failed to query root disk, failing operation")
|
|
return False
|
|
|
|
# a) Find a LUN to issue a Query LUNs command
|
|
li = []
|
|
Query = False
|
|
for lun in LUNs:
|
|
try:
|
|
hbtl = lun.split('-')[-1]
|
|
h = hbtl.split(':')
|
|
l=util.pread2(["/usr/bin/sg_luns","-q",lun]).split('\n')
|
|
li = []
|
|
for i in l:
|
|
if len(i):
|
|
li.append(int(i[0:4], 16))
|
|
util.SMlog("sg_luns query returned %s" % li)
|
|
Query = True
|
|
break
|
|
except:
|
|
pass
|
|
if not Query:
|
|
util.SMlog("Failed to detect or query LUN on Channel %s" % channel)
|
|
return False
|
|
|
|
# b) Remove stale LUNs
|
|
current = glob.glob('/dev/disk/by-scsibus/*-%s:%s:%s*' % (h[0],h[1],h[2]))
|
|
for cur in current:
|
|
lunID = int(cur.split(':')[-1])
|
|
newhbtl = ['',h[0],h[1],h[2],str(lunID)]
|
|
if os.path.realpath(cur) in rootdevs:
|
|
# Don't touch the rootdev
|
|
if lunID in li: li.remove(lunID)
|
|
continue
|
|
|
|
# Check if LUN is stale, and remove it
|
|
if not lunID in li:
|
|
util.SMlog("Stale LUN detected. Removing HBTL: %s" % newhbtl)
|
|
scsi_dev_ctrl(newhbtl,"remove")
|
|
util.wait_for_nopath(cur, DEV_WAIT)
|
|
continue
|
|
else:
|
|
li.remove(lunID)
|
|
|
|
# Query SCSIid, check it matches, if not, re-probe
|
|
cur_SCSIid = os.path.basename(cur).split("-%s:%s:%s" % (h[0],h[1],h[2]))[0]
|
|
real_SCSIid = getSCSIid(cur)
|
|
if cur_SCSIid != real_SCSIid:
|
|
util.SMlog("HBTL %s does not match, re-probing" % newhbtl)
|
|
scsi_dev_ctrl(newhbtl,"remove")
|
|
util.wait_for_nopath(cur, DEV_WAIT)
|
|
scsi_dev_ctrl(newhbtl,"add")
|
|
util.wait_for_path('/dev/disk/by-scsibus/%s-%s' % (real_SCSIid,hbtl), DEV_WAIT)
|
|
pass
|
|
|
|
# c) Probe for any LUNs that are not present in the system
|
|
for l in li:
|
|
newhbtl = ['',h[0],h[1],h[2],str(l)]
|
|
util.SMlog("Probing new HBTL: %s" % newhbtl)
|
|
scsi_dev_ctrl(newhbtl,"add")
|
|
util.wait_for_path('/dev/disk/by-scsibus/*-%s' % hbtl, DEV_WAIT)
|
|
|
|
return True
|