#!/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' LCTL = './lctl' # fix this... 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 # ============================================================ # 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) # 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 def run_lctl(cmds): debug("+", LCTL, cmds) if isnotouch(): return ([], 0) p = popen2.Popen3(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 (LCTL, "error:", ret) logall(err) raise CommandError, err 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 # create a new device with lctl def lctl_network(net, nid): cmds = """ network %s mynid %s quit""" % (net, nid) run_lctl(cmds) # create a new connection def lctl_connect(net, nid, port, servuuid): cmds = """ network %s connect %s %d add_uuid %s %s quit""" % (net, nid, port, servuuid, nid) run_lctl(cmds) # create a new device with lctl def lctl_disconnect(net, nid, port, servuuid): cmds = """ network %s disconnect %s quit""" % (net, nid) run_lctl(cmds) # create a new device with lctl def lctl_newdev(attach, setup): cmds = """ newdev attach %s setup %s quit""" % (attach, setup) run_lctl(cmds) # cleanup a device def lctl_cleanup(name, uuid): cmds = """ device $%s cleanup detach quit""" % (name) run_lctl(cmds) # create an lov def lctl_lovconfig(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) run_lctl(cmds) # ============================================================ # 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 # 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()