#!/usr/bin/env python # # Copyright (C) 2002 Cluster File Systems, Inc. # Author: Robert Read # This file is part of Lustre, http://www.lustre.org. # # Lustre is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # Lustre 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 Lustre; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # lconf - lustre configuration tool # # lconf is the main driver script for starting and stopping # lustre filesystem services. # # Based in part on the XML obdctl modifications done by Brian Behlendorf import sys, getopt import string, os, stat, popen2 import re, exceptions import xml.dom.minidom # Global parameters TCP_ACCEPTOR = 'acceptor' options = {} # # Maximum number of devices to search for. # (the /dev/loop* nodes need to be created beforehand) MAX_LOOP_DEVICES = 256 def usage(): print """usage: lconf --ldap server | config.xml config.xml Lustre configuration in xml format. --ldap server LDAP server with lustre config database Options: -v|--verbose --debug Don't send lctl commenads --reformat Reformat all devices (will confirm) --lustre="src dir" Base directory of lustre sources. Used to search for modules. --portals=src Portals source --makeldiff Translate xml source to LDIFF --cleanup Cleans up config. (Shutdown) --iam myname ?? (SCRIPT STILL UNDER DEVELOPMENT, MOST FUNCTIONALITY UNIMPLEMENTED) """ # ============================================================ # debugging and error funcs def fixme(msg = "this feature"): raise RuntimeError, msg + ' not implmemented yet.' def panic(*args): msg = string.join(map(str,args)) print msg raise RuntimeError, msg def log(*args): msg = string.join(map(str,args)) print msg def logall(msgs): for s in msgs: print string.strip(s) def debug(*args): msg = string.join(map(str,args)) if isverbose(): print msg def isverbose(): return options.has_key('verbose') and options['verbose'] == 1 def isnotouch(): return options.has_key('debug') and options['debug'] == 1 # ============================================================ # locally defined exceptions class CommandError (exceptions.Exception): def __init__(self, args=None): self.args = args # ============================================================ # handle lctl interface class LCTLInterface: """ Manage communication with lctl """ def __init__(self, cmd): """ Initialize close by finding the lctl binary. """ syspath = string.split(os.environ['PATH'], ':') syspath.insert(0, "../utils"); self.lctlcmd = None for d in syspath: lctl = os.path.join(d,cmd) if os.access(lctl, os.X_OK): self.lctl = lctl break if not self.lctl: raise RuntimeError, "unable to find lctl binary." def run(self, cmds): """ run lctl the cmds are written to stdin of lctl lctl doesn't return errors when run in script mode, so stderr is checked should modify command line to accept multiple commands, or create complex command line options """ debug("+", self.lctl, cmds) if isnotouch(): return ([], 0) p = popen2.Popen3(self.lctl, 1) p.tochild.write(cmds + "\n") p.tochild.close() out = p.fromchild.readlines() ret = p.poll() err = p.childerr.readlines() if ret or len(err): log (self.lctl, "error:", ret) logall(err) raise CommandError, err return ret, out # create a new device with lctl def network(self, net, nid): cmds = """ network %s mynid %s quit""" % (net, nid) self.run(cmds) # create a new connection def connect(self, net, nid, port, servuuid): cmds = """ network %s connect %s %d add_uuid %s %s quit""" % (net, nid, port, servuuid, nid) self.run(cmds) # create a new device with lctl def disconnect(self, net, nid, port, servuuid): cmds = """ network %s disconnect %s quit""" % (net, nid) self.run(cmds) # create a new device with lctl def newdev(self, attach, setup): cmds = """ newdev attach %s setup %s quit""" % (attach, setup) self.run(cmds) # cleanup a device def cleanup(self, name, uuid): cmds = """ device $%s cleanup detach quit""" % (name) self.run(cmds) # create an lov def lovconfig(self, uuid, mdcuuid, stripe_cnt, stripe_sz, pattern, devlist): cmds = """ device $%s probe lovconfig %s %d %d %s %s quit""" % (mdcuuid, uuid, stripe_cnt, stripe_sz, pattern, devlist) self.run(cmds) # ============================================================ # Various system-level functions # (ideally moved to their own module) # Run a command and return the output and status. # stderr is sent to /dev/null, could use popen3 to # save it if necessary def run(*args): cmd = string.join(map(str,args)) debug ("+", cmd) if isnotouch(): return ([], 0) f = os.popen(cmd + ' 2>&1') out = f.readlines() ret = f.close() if ret: ret = ret >> 8 else: ret = 0 return (ret, out) # is the path a block device? def is_block(path): s = () try: s = os.stat(path) except OSError: return 0 return stat.S_ISBLK(s[stat.ST_MODE]) # build fs according to type # fixme: dangerous def mkfs(fstype, dev): if(fstype == 'ext3'): mkfs = 'mkfs.ext2 -j' elif (fstype == 'extN'): mkfs = 'mkfs.ext2 -j' else: print 'unsupported fs type: ', fstype if not is_block(dev): force = '-F' else: force = '' run (mkfs, force, dev) # some systems use /dev/loopN, some /dev/loop/N def loop_base(): import re loop = '/dev/loop' if not os.access(loop + str(0), os.R_OK): loop = loop + '/' if not os.access(loop + str(0), os.R_OK): panic ("can't access loop devices") return loop # find loop device assigned to thefile def find_loop(file): loop = loop_base() for n in xrange(0, MAX_LOOP_DEVICES): dev = loop + str(n) if os.access(dev, os.R_OK): (stat, out) = run('losetup', dev) if (out and stat == 0): m = re.search(r'\((.*)\)', out[0]) if m and file == m.group(1): return dev else: break return '' # create file if necessary and assign the first free loop device def init_loop(file, size, fstype): dev = find_loop(file) if dev: print 'WARNING file:', file, 'already mapped to', dev return dev if not os.access(file, os.R_OK | os.W_OK): run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size, file)) loop = loop_base() # find next free loop for n in xrange(0, MAX_LOOP_DEVICES): dev = loop + str(n) if os.access(dev, os.R_OK): (stat, out) = run('losetup', dev) if (stat): run('losetup', dev, file) return dev else: print "out of loop devices" return '' print "out of loop devices" return '' # undo loop assignment def clean_loop(file): dev = find_loop(file) if dev: ret, out = run('losetup -d', dev) if ret: log('unable to clean loop device:', dev, 'for file:', file) logall(out) # initialize a block device if needed def block_dev(dev, size, fstype, format): if isnotouch(): return dev if not is_block(dev): dev = init_loop(dev, size, fstype) if (format == 'yes'): mkfs(fstype, dev) return dev # ============================================================ # Functions to prepare the various objects def prepare_ldlm(node): (name, uuid) = getNodeAttr(node) print 'LDLM:', name, uuid lctl.newdev(attach="ldlm %s %s" % (name, uuid), setup ="") def prepare_lov(node): (name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist) = getLOVInfo(node) print 'LOV:', name, uuid lctl.lovconfig(uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist) def prepare_network(node): (name, uuid, type, nid, port) = getNetworkInfo(node) print 'NETWORK:', type, nid, port if type == 'tcp': run(TCP_ACCEPTOR, port) lctl.network(type, nid) # need to check /proc/mounts and /etc/mtab before # formatting anything. # FIXME: check if device is already formatted. def prepare_obd(obd): (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd) print "OBD: ", name, obdtype, dev, size, fstype, format dev = block_dev(dev, size, fstype, format) lctl.newdev(attach="%s %s %s" % (obdtype, name, uuid), setup ="%s %s" %(dev, fstype)) def prepare_ost(ost): name, uuid, obd = getOSTInfo(ost) print "OST: ", name, uuid, obd lctl.newdev(attach="ost %s %s" % (name, uuid), setup ="$%s" % (obd)) def prepare_mds(node): (name, uuid, dev, size, fstype, format) = getMDSInfo(node) print "MDS: ", name, dev, size, fstype # setup network for mds, too dev = block_dev(dev, size, fstype, format) lctl.newdev(attach="mds %s %s" % (name, uuid), setup ="%s %s" %(dev, fstype)) def prepare_osc(node): (name, uuid, obduuid, srvuuid) = getOSCInfo(node) print 'OSC:', name, uuid, obduuid, srvuuid net = lookup(node.parentNode, srvuuid) srvname, srvuuid, net, server, port = getNetworkInfo(net) lctl.connect(net, server, port, srvuuid) lctl.newdev(attach="osc %s %s" % (name, uuid), setup ="%s %s" %(obduuid, srvuuid)) def prepare_mdc(node): (name, uuid, mdsuuid, netuuid) = getMDCInfo(node) print 'MDC:', name, uuid, mdsuuid, netuuid lctl.newdev(attach="mdc %s %s" % (name, uuid), setup ="%s %s" %(mdsuuid, netuuid)) def prepare_mountpoint(node): print 'MTPT:' # ============================================================ # Functions to cleanup the various objects def cleanup_ldlm(node): (name, uuid) = getNodeAttr(node) print 'LDLM:', name, uuid try: lctl.cleanup(name, uuid) except CommandError: print "cleanup failed: ", name def cleanup_lov(node): (name, uuid) = getNodeAttr(node) print 'LOV:', name, uuid #lctl.cleanup(name, uuid) def cleanup_network(node): (name, uuid, type, nid, port) = getNetworkInfo(node) print 'NETWORK:', type, nid, port #lctl.network(type, nid) # need to check /proc/mounts and /etc/mtab before # formatting anything. # FIXME: check if device is already formatted. def cleanup_obd(obd): (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd) print "OBD: ", name, obdtype, dev, size, fstype, format try: lctl.cleanup(name, uuid) except CommandError: print "cleanup failed: ", name clean_loop(dev) def cleanup_ost(ost): name, uuid, obd = getOSTInfo(ost) print "OST: ", name, uuid, obd try: lctl.cleanup(name, uuid) except CommandError: print "cleanup failed: ", name def cleanup_mds(node): (name, uuid, dev, size, fstype, format) = getMDSInfo(node) print "MDS: ", name, dev, size, fstype try: lctl.cleanup(name, uuid) except CommandError: print "cleanup failed: ", name clean_loop(dev) def cleanup_mdc(node): (name, uuid, mdsuuid, netuuid) = getMDCInfo(node) print 'MDC:', name, uuid, mdsuuid, netuuid try: lctl.cleanup(name, uuid) except CommandError: print "cleanup failed: ", name def cleanup_osc(node): (name, uuid, obduuid, srvuuid) = getOSCInfo(node) print 'OSC:', name, uuid, obduuid, srvuuid net = lookup(node.parentNode, srvuuid) netname, netuuid, net, server, port = getNetworkInfo(net) try: lctl.disconnect(net, server, port, srvuuid) lctl.cleanup(name, uuid) except CommandError: print "cleanup failed: ", name def cleanup_mountpoint(node): print 'MTPT:' # ============================================================ # XML processing and query def getDevice(obd): dev = obd.getElementsByTagName('device')[0] dev.normalize(); try: size = int(dev.getAttribute('size')) except ValueError: size = 0 return dev.firstChild.data, size def getNetworkInfo(node): name, uuid = getNodeAttr(node); type = node.getAttribute('type') nid = getText(node, 'server', "") port = int(getText(node, 'port', 0)) return name, uuid, type, nid, port # extract device attributes for an obd def getNodeAttr(node): name = node.getAttribute('name') uuid = node.getAttribute('uuid') return name, uuid def getOBDInfo(obd): name, uuid = getNodeAttr(obd); obdtype = obd.getAttribute('type') devname, size = getDevice(obd) fstype = getText(obd, 'fstype') format = getText(obd, 'autoformat') return (name, uuid, obdtype, devname, size, fstype, format) # extract LOV def getLOVInfo(node): name, uuid = getNodeAttr(node) devs = node.getElementsByTagName('devices')[0] stripe_sz = int(devs.getAttribute('stripesize')) pattern = int(devs.getAttribute('pattern')) mdcref = node.getElementsByTagName('mdc_ref')[0] mdcuuid = mdcref.getAttribute('uuidref') mdc= lookup(node.parentNode, mdcuuid) mdcname = getName(mdc) devlist = "" stripe_cnt = 0 for child in devs.childNodes: if child.nodeName == 'obd_ref': devlist = devlist + child.getAttribute('uuidref') + " " strip_cnt = stripe_cnt + 1 return (name, uuid, mdcname, stripe_cnt, stripe_sz, pattern, devlist) # extract device attributes for an obd def getMDSInfo(node): name, uuid = getNodeAttr(node) devname, size = getDevice(node) fstype = getText(node, 'fstype') format = getText(node, 'autoformat', "no") return (name, uuid, devname, size, fstype, format) # extract device attributes for an obd def getMDCInfo(node): name, uuid = getNodeAttr(node) ref = node.getElementsByTagName('mds_ref')[0] mdsuuid = ref.getAttribute('uuidref') ref = node.getElementsByTagName('network_ref')[0] netuuid = ref.getAttribute('uuidref') return (name, uuid, mdsuuid, netuuid) # extract device attributes for an obd def getOSTInfo(node): name, uuid = getNodeAttr(node) ref = node.getElementsByTagName('obd_ref')[0] uuid = ref.getAttribute('uuidref') obd = lookup(node.parentNode, uuid) if obd: obdname = getOBDInfo(obd)[0] else: obdname = "OBD NOT FOUND" return (name, uuid, obdname) # extract device attributes for an obd def getOSCInfo(node): name, uuid = getNodeAttr(node) ref = node.getElementsByTagName('obd_ref')[0] obduuid = ref.getAttribute('uuidref') ref = node.getElementsByTagName('network_ref')[0] ostuuid = ref.getAttribute('uuidref') return (name, uuid, obduuid, ostuuid) # Get the text content from the first matching child def getText(node, tag, default=""): list = node.getElementsByTagName(tag) if len(list) > 0: node = list[0] node.normalize() return node.firstChild.data else: return default # Recusively search from node for a uuid def lookup(node, uuid): for n in node.childNodes: # this service_id check is ugly. need some other way to # differentiate between definitions and references if n.nodeType == n.ELEMENT_NODE: if getUUID(n) == uuid: return n else: n = lookup(n, uuid) if n: return n return None # Get name attribute of node def getName(node): return node.getAttribute('name') def getRef(node): return node.getAttribute('uuidref') # Get name attribute of node def getUUID(node): return node.getAttribute('uuid') # the tag name is the service type # fixme: this should do some checks to make sure the node is a service def getServiceType(node): return node.nodeName # # determine what "level" a particular node is at. # the order of iniitailization is based on level. objects # are assigned a level based on type: # net,devices:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5 def getServiceLevel(node): type = getServiceType(node) if type in ('network', 'device', 'ldlm'): return 1 elif type in ('obd', 'mdd'): return 2 elif type in ('mds','ost'): return 3 elif type in ('mdc','osc'): return 4 elif type in ('lov',): return 5 elif type in ('mountpoint',): return 6 return 0 # # return list of services in a profile. list is a list of tuples # [(level, node),] def getServices(lustreNode, profileNode): list = [] for n in profileNode.childNodes: if n.nodeType == n.ELEMENT_NODE: servNode = lookup(lustreNode, getRef(n)) if not servNode: panic('service not found: ' + getName(n)) level = getServiceLevel(servNode) list.append((level, servNode)) list.sort() return list def getProfile(lustreNode, profile): profList = lustreNode.getElementsByTagName('profile') for prof in profList: if getName(prof) == profile: return prof return None # ============================================================ # lconf type level logic # # # Start a service. def startService(node): type = getServiceType(node) debug('Starting service:', type, getName(node), getUUID(node)) # there must be a more dynamic way of doing this... if type == 'ldlm': prepare_ldlm(node) elif type == 'lov': prepare_lov(node) elif type == 'network': prepare_network(node) elif type == 'obd': prepare_obd(node) elif type == 'ost': prepare_ost(node) elif type == 'mds': prepare_mds(node) elif type == 'osc': prepare_osc(node) elif type == 'mdc': prepare_mdc(node) elif type == 'mountpoint': prepare_mountpoint(node) # # Prepare the system to run lustre using a particular profile # in a the configuration. # * load & the modules # * setup networking for the current node # * make sure partitions are in place and prepared # * initialize devices with lctl # Levels is important, and needs to be enforced. def startProfile(lustreNode, profile): profileNode = getProfile(lustreNode, profile) if not profileNode: panic("profile:", profile, "not found.") services = getServices(lustreNode, profileNode) for s in services: startService(s[1]) # Stop a service. def stopService(node): type = getServiceType(node) debug('Stopping service:', type, getName(node), getUUID(node)) # there must be a more dynamic way of doing this... if type == 'ldlm': cleanup_ldlm(node) elif type == 'lov': cleanup_lov(node) elif type == 'network': cleanup_network(node) elif type == 'obd': cleanup_obd(node) elif type == 'ost': cleanup_ost(node) elif type == 'mds': cleanup_mds(node) elif type == 'osc': cleanup_osc(node) elif type == 'mdc': cleanup_mdc(node) elif type == 'mountpoint': cleanup_mountpoint(node) # Shutdown services in reverse order than they were started def cleanupProfile(lustreNode, profile): profileNode = getProfile(lustreNode, profile) if not profileNode: panic("profile:", profile, "not found.") services = getServices(lustreNode, profileNode) services.reverse() for s in services: stopService(s[1]) # # Command line processing # def parse_cmdline(argv): short_opts = "hv" long_opts = ["ldap", "reformat", "lustre=", "portals=", "makeldiff", "cleanup", "iam=", "help", "debug"] opts = [] args = [] global options try: opts, args = getopt.getopt(argv, short_opts, long_opts) except getopt.GetoptError: print "invalid opt" usage() sys.exit(2) for o, a in opts: if o in ("-h", "--help"): usage() sys.exit() if o == "--cleanup": options['cleanup'] = 1 if o in ("-v", "--verbose"): options['verbose'] = 1 if o == "--debug": options['debug'] = 1 if o == "--portals": options['portals'] = a if o == "--lustre": options['lustre'] = a if o == "--reformat": options['reformat'] = 1 return args # # Initialize or shutdown lustre according to a configuration file # * prepare the system for lustre # * configure devices with lctl # Shutdown does steps in reverse # lctl = LCTLInterface('lctl') def main(): global options args = parse_cmdline(sys.argv[1:]) if len(args) > 0: dom = xml.dom.minidom.parse(args[0]) else: usage() fixme("ldap not implemented yet") if options.has_key('cleanup'): cleanupProfile(dom.childNodes[0], 'local-profile') else: startProfile(dom.childNodes[0], 'local-profile') # # try a different traceback style. (dare ya to try this in java) def my_traceback(file=None): """Print the list of tuples as returned by extract_tb() or extract_stack() as a formatted stack trace to the given file.""" import traceback (t,v, tb) = sys.exc_info() list = traceback.extract_tb(tb) if not file: file = sys.stderr for filename, lineno, name, line in list: if line: print '%s:%04d %-14s %s' % (filename,lineno, name, line.strip()) else: print '%s:%04d %s' % (filename,lineno, name) print '%s: %s' % (t, v) if __name__ == "__main__": try: main() except: my_traceback()