3 # Copyright (C) 2002 Cluster File Systems, Inc.
4 # Author: Robert Read <rread@clusterfs.com>
6 # This file is part of Lustre, http://www.lustre.org.
8 # Lustre is free software; you can redistribute it and/or
9 # modify it under the terms of version 2 of the GNU General Public
10 # License as published by the Free Software Foundation.
12 # Lustre is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Lustre; if not, write to the Free Software
19 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 # lconf - lustre configuration tool
23 # lconf is the main driver script for starting and stopping
24 # lustre filesystem services.
26 # Based in part on the XML obdctl modifications done by Brian Behlendorf
29 import string, os, stat, popen2
31 import xml.dom.minidom
34 TCP_ACCEPTOR = '../..//portals/linux/utils/acceptor'
38 # Maximum number of devices to search for.
39 # (the /dev/loop* nodes need to be created beforehand)
40 MAX_LOOP_DEVICES = 256
44 print """usage: lconf config.xml
46 config.xml Lustre configuration in xml format.
47 --get <url> URL to fetch a config file
48 -v | --verbose Print system commands as they are run
49 -d | --debug Print system commands, but does not run them
50 --host <hostname> Load config for <hostname>
51 --cleanup Cleans up config. (Shutdown)
52 -h | --help Print this help
55 --ldap server LDAP server with lustre config database
56 --reformat Reformat all devices (will confirm)
57 --lustre="src dir" Base directory of lustre sources. Used to search
59 --portals=src Portals source
60 --makeldiff Translate xml source to LDIFF
65 # ============================================================
66 # debugging and error funcs
68 def fixme(msg = "this feature"):
69 raise RuntimeError, msg + ' not implmemented yet.'
72 msg = string.join(map(str,args))
74 raise RuntimeError, msg
77 msg = string.join(map(str,args))
85 msg = string.join(map(str,args))
86 if isverbose(): print msg
89 return options.has_key('verbose') and options['verbose'] == 1
92 return options.has_key('debug') and options['debug'] == 1
94 # ============================================================
95 # locally defined exceptions
96 class CommandError (exceptions.Exception):
97 def __init__(self, args=None):
100 # ============================================================
101 # handle lctl interface
104 Manage communication with lctl
107 def __init__(self, cmd):
109 Initialize close by finding the lctl binary.
111 syspath = string.split(os.environ['PATH'], ':')
112 syspath.insert(0, "../utils");
115 lctl = os.path.join(d,cmd)
116 if os.access(lctl, os.X_OK):
120 raise RuntimeError, "unable to find lctl binary."
125 the cmds are written to stdin of lctl
126 lctl doesn't return errors when run in script mode, so
128 should modify command line to accept multiple commands, or
129 create complex command line options
131 debug("+", self.lctl, cmds)
132 if isnotouch(): return ([], 0)
133 p = popen2.Popen3(self.lctl, 1)
134 p.tochild.write(cmds + "\n")
136 out = p.fromchild.readlines()
138 err = p.childerr.readlines()
140 log (self.lctl, "error:", ret)
142 raise CommandError, err
145 # create a new device with lctl
146 def network(self, net, nid):
151 quit""" % (net, nid, nid)
154 # create a new connection
155 def connect(self, net, nid, port, servuuid):
160 quit""" % (net, nid, port, servuuid, nid)
163 # create a new device with lctl
164 def disconnect(self, net, nid, port, servuuid):
171 # create a new device with lctl
172 def newdev(self, attach, setup = ""):
177 quit""" % (attach, setup)
181 def cleanup(self, name, uuid):
190 def lovconfig(self, uuid, mdcuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist):
194 lovconfig %s %d %d %d %s %s
195 quit""" % (mdcuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
198 # ============================================================
199 # Various system-level functions
200 # (ideally moved to their own module)
202 # Run a command and return the output and status.
203 # stderr is sent to /dev/null, could use popen3 to
204 # save it if necessary
206 cmd = string.join(map(str,args))
208 if isnotouch(): return (0, [])
209 f = os.popen(cmd + ' 2>&1')
218 # Run a command in the background.
219 def run_daemon(*args):
220 cmd = string.join(map(str,args))
222 if isnotouch(): return 0
223 f = os.popen(cmd + ' 2>&1')
232 # is the path a block device?
239 return stat.S_ISBLK(s[stat.ST_MODE])
241 # build fs according to type
243 def mkfs(fstype, dev):
244 if(fstype == 'ext3'):
245 mkfs = 'mkfs.ext2 -j -b 4096'
246 elif (fstype == 'extN'):
247 mkfs = 'mkfs.ext2 -j -b 4096'
249 print 'unsupported fs type: ', fstype
250 if not is_block(dev):
254 run (mkfs, force, dev)
256 # some systems use /dev/loopN, some /dev/loop/N
260 if not os.access(loop + str(0), os.R_OK):
262 if not os.access(loop + str(0), os.R_OK):
263 panic ("can't access loop devices")
266 # find loop device assigned to thefile
269 for n in xrange(0, MAX_LOOP_DEVICES):
271 if os.access(dev, os.R_OK):
272 (stat, out) = run('losetup', dev)
273 if (out and stat == 0):
274 m = re.search(r'\((.*)\)', out[0])
275 if m and file == m.group(1):
281 # create file if necessary and assign the first free loop device
282 def init_loop(file, size, fstype):
283 dev = find_loop(file)
285 print 'WARNING file:', file, 'already mapped to', dev
287 if not os.access(file, os.R_OK | os.W_OK):
288 run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size, file))
290 # find next free loop
291 for n in xrange(0, MAX_LOOP_DEVICES):
293 if os.access(dev, os.R_OK):
294 (stat, out) = run('losetup', dev)
296 run('losetup', dev, file)
299 print "out of loop devices"
301 print "out of loop devices"
304 # undo loop assignment
305 def clean_loop(file):
306 dev = find_loop(file)
308 ret, out = run('losetup -d', dev)
310 log('unable to clean loop device:', dev, 'for file:', file)
313 # initialize a block device if needed
314 def block_dev(dev, size, fstype, format):
315 if isnotouch(): return dev
316 if not is_block(dev):
317 dev = init_loop(dev, size, fstype)
318 if (format == 'yes'):
322 # ============================================================
323 # Functions to prepare the various objects
325 def prepare_ldlm(node):
326 (name, uuid) = getNodeAttr(node)
327 print 'LDLM:', name, uuid
328 lctl.newdev(attach="ldlm %s %s" % (name, uuid),
331 def prepare_lov(node):
332 (name, uuid, mdcuuid, stripe_cnt, strip_sz, stripe_off, pattern, devlist, mdsname) = getLOVInfo(node)
333 print 'LOV:', name, uuid, mdcuuid, stripe_cnt, strip_sz, stripe_off, pattern, devlist, mdsname
334 lctl.lovconfig(uuid, mdsname, stripe_cnt, strip_sz, stripe_off, pattern, devlist)
335 lctl.newdev(attach="lov %s %s" % (name, uuid),
336 setup ="%s" % (mdcuuid))
338 def prepare_network(node):
339 (name, uuid, type, nid, port) = getNetworkInfo(node)
340 print 'NETWORK:', name, uuid, type, nid, port
342 ret = run_daemon(TCP_ACCEPTOR, port)
345 raise CommandError, "cannot run acceptor"
346 lctl.network(type, nid)
347 lctl.newdev(attach = "ptlrpc RPCDEV")
350 # need to check /proc/mounts and /etc/mtab before
351 # formatting anything.
352 # FIXME: check if device is already formatted.
353 def prepare_obd(obd):
354 (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
355 print 'OBD:', name, uuid, obdtype, dev, size, fstype, format
356 dev = block_dev(dev, size, fstype, format)
357 lctl.newdev(attach="%s %s %s" % (obdtype, name, uuid),
358 setup ="%s %s" %(dev, fstype))
361 def prepare_ost(ost):
362 name, uuid, obd = getOSTInfo(ost)
363 print 'OST:', name, uuid, obd
364 lctl.newdev(attach="ost %s %s" % (name, uuid),
367 def prepare_mds(node):
368 (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
369 print 'MDS:', name, uuid, dev, size, fstype
370 # setup network for mds, too
371 dev = block_dev(dev, size, fstype, format)
372 lctl.newdev(attach="mds %s %s" % (name, uuid),
373 setup ="%s %s" %(dev, fstype))
375 def prepare_osc(node):
376 (name, uuid, obduuid, ostuuid) = getOSCInfo(node)
377 print 'OSC:', name, uuid, obduuid, ostuuid
378 net = lookup(node.parentNode, ostuuid)
379 srvname, srvuuid, net, server, port = getNetworkInfo(net)
380 lctl.connect(net, server, port, ostuuid)
381 lctl.newdev(attach="osc %s %s" % (name, uuid),
382 setup ="%s %s" %(obduuid, ostuuid))
384 def prepare_mdc(node):
385 (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
386 print 'MDC:', name, uuid, mdsuuid, netuuid
387 net = lookup(node.parentNode, netuuid)
388 srvname, srvuuid, net, server, port = getNetworkInfo(net)
389 mds = lookup(node.parentNode, mdsuuid)
391 panic(mdsuuid, "not found.")
392 lctl.connect(net, server, port, netuuid)
393 lctl.newdev(attach="mdc %s %s" % (name, uuid),
394 setup ="%s %s" %(mdsuuid, netuuid))
396 def prepare_mountpoint(node):
397 name, uuid, oscuuid, mdcuuid, mtpt = getMTPTInfo(node)
398 print 'MTPT:', name, uuid, oscuuid, mdcuuid, mtpt
399 cmd = "mount -t lustre_lite -o osc=%s,mdc=%s none %s" % \
400 (oscuuid, mdcuuid, mtpt)
404 print mtpt, "mount failed."
405 # ============================================================
406 # Functions to cleanup the various objects
408 def cleanup_ldlm(node):
409 (name, uuid) = getNodeAttr(node)
410 print 'LDLM:', name, uuid
412 lctl.cleanup(name, uuid)
414 print "cleanup failed: ", name
416 def cleanup_lov(node):
417 (name, uuid, mdcuuid, stripe_cnt, strip_sz, stripe_off, pattern, devlist, mdsname) = getLOVInfo(node)
418 print 'LOV:', name, uuid, mdcuuid, stripe_cnt, strip_sz, stripe_off, pattern, devlist, mdsname
420 lctl.cleanup(name, uuid)
422 print "cleanup failed: ", name
424 def cleanup_network(node):
425 (name, uuid, type, nid, port) = getNetworkInfo(node)
426 print 'NETWORK:', name, uuid, type, nid, port
428 lctl.cleanup("RPCDEV", "")
430 print "cleanup failed: ", name
431 # yikes, this ugly! need to save pid in /var/something
432 run("killall acceptor")
435 # need to check /proc/mounts and /etc/mtab before
436 # formatting anything.
437 # FIXME: check if device is already formatted.
438 def cleanup_obd(obd):
439 (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
440 print "OBD: ", name, obdtype, dev, size, fstype, format
442 lctl.cleanup(name, uuid)
444 print "cleanup failed: ", name
447 def cleanup_ost(ost):
448 name, uuid, obd = getOSTInfo(ost)
449 print "OST: ", name, uuid, obd
451 lctl.cleanup(name, uuid)
453 print "cleanup failed: ", name
455 def cleanup_mds(node):
456 (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
457 print "MDS: ", name, dev, size, fstype
459 lctl.cleanup(name, uuid)
461 print "cleanup failed: ", name
465 def cleanup_mdc(node):
466 (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
467 print 'MDC:', name, uuid, mdsuuid, netuuid
468 net = lookup(node.parentNode, netuuid)
469 srvname, srvuuid, net, server, port = getNetworkInfo(net)
471 lctl.disconnect(net, server, port, netuuid)
472 lctl.cleanup(name, uuid)
474 print "cleanup failed: ", name
477 def cleanup_osc(node):
478 (name, uuid, obduuid, ostuuid) = getOSCInfo(node)
479 print 'OSC:', name, uuid, obduuid, ostuuid
480 net = lookup(node.parentNode, ostuuid)
481 srvname, srvuuid, net, server, port = getNetworkInfo(net)
483 lctl.disconnect(net, server, port, ostuuid)
484 lctl.cleanup(name, uuid)
486 print "cleanup failed: ", name
488 def cleanup_mountpoint(node):
489 name, uuid, oscuuid, mdcuuid, mtpt = getMTPTInfo(node)
490 print 'MTPT:', name, uuid, oscuuid, mdcuuid, mtpt
493 # ============================================================
494 # XML processing and query
497 dev = obd.getElementsByTagName('device')[0]
500 size = int(dev.getAttribute('size'))
503 return dev.firstChild.data, size
506 def getNetworkInfo(node):
507 name, uuid = getNodeAttr(node);
508 type = node.getAttribute('type')
509 nid = getText(node, 'server', "")
510 port = int(getText(node, 'port', 0))
511 return name, uuid, type, nid, port
513 # extract device attributes for an obd
514 def getNodeAttr(node):
515 name = node.getAttribute('name')
516 uuid = node.getAttribute('uuid')
520 name, uuid = getNodeAttr(obd);
521 obdtype = obd.getAttribute('type')
522 devname, size = getDevice(obd)
523 fstype = getText(obd, 'fstype')
524 format = getText(obd, 'autoformat')
525 return (name, uuid, obdtype, devname, size, fstype, format)
528 def getLOVInfo(node):
529 name, uuid = getNodeAttr(node)
530 devs = node.getElementsByTagName('devices')[0]
531 stripe_sz = int(devs.getAttribute('stripesize'))
532 stripe_off = int(devs.getAttribute('stripeoffset'))
533 pattern = int(devs.getAttribute('pattern'))
534 mdcref = node.getElementsByTagName('mdc_ref')[0]
535 mdcuuid = mdcref.getAttribute('uuidref')
536 mdc= lookup(node.parentNode, mdcuuid)
537 mdsref = mdc.getElementsByTagName('mds_ref')[0]
538 mdsuuid = mdsref.getAttribute('uuidref')
539 mds= lookup(node.parentNode, mdsuuid)
540 mdsname = getName(mds)
543 for child in devs.childNodes:
544 if child.nodeName == 'osc_ref':
545 devlist = devlist + child.getAttribute('uuidref') + " "
546 strip_cnt = stripe_cnt + 1
547 return (name, uuid, mdcuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist, mdsname)
549 # extract device attributes for an obd
550 def getMDSInfo(node):
551 name, uuid = getNodeAttr(node)
552 devname, size = getDevice(node)
553 fstype = getText(node, 'fstype')
554 format = getText(node, 'autoformat', "no")
555 return (name, uuid, devname, size, fstype, format)
557 # extract device attributes for an obd
558 def getMDCInfo(node):
559 name, uuid = getNodeAttr(node)
560 ref = node.getElementsByTagName('mds_ref')[0]
561 mdsuuid = ref.getAttribute('uuidref')
562 ref = node.getElementsByTagName('network_ref')[0]
563 netuuid = ref.getAttribute('uuidref')
564 return (name, uuid, mdsuuid, netuuid)
567 # extract device attributes for an obd
568 def getOSTInfo(node):
569 name, uuid = getNodeAttr(node)
570 ref = node.getElementsByTagName('obd_ref')[0]
571 uuid = ref.getAttribute('uuidref')
572 return (name, uuid, uuid)
574 # extract device attributes for an obd
575 def getOSCInfo(node):
576 name, uuid = getNodeAttr(node)
577 ref = node.getElementsByTagName('obd_ref')[0]
578 obduuid = ref.getAttribute('uuidref')
579 ref = node.getElementsByTagName('network_ref')[0]
580 ostuuid = ref.getAttribute('uuidref')
581 return (name, uuid, obduuid, ostuuid)
583 # extract device attributes for an obd
584 def getMTPTInfo(node):
585 name, uuid = getNodeAttr(node)
586 path = getText(node, 'path')
587 ref = node.getElementsByTagName('mdc_ref')[0]
588 mdcuuid = ref.getAttribute('uuidref')
589 ref = node.getElementsByTagName('osc_ref')[0]
590 lovuuid = ref.getAttribute('uuidref')
591 return (name, uuid, lovuuid, mdcuuid, path)
594 # Get the text content from the first matching child
595 def getText(node, tag, default=""):
596 list = node.getElementsByTagName(tag)
600 return node.firstChild.data
604 # Recusively search from node for a uuid
605 def lookup(node, uuid):
606 for n in node.childNodes:
607 # this service_id check is ugly. need some other way to
608 # differentiate between definitions and references
609 if n.nodeType == n.ELEMENT_NODE:
610 if getUUID(n) == uuid:
617 # Get name attribute of node
619 return node.getAttribute('name')
622 return node.getAttribute('uuidref')
624 # Get name attribute of node
626 return node.getAttribute('uuid')
628 # the tag name is the service type
629 # fixme: this should do some checks to make sure the node is a service
630 def getServiceType(node):
634 # determine what "level" a particular node is at.
635 # the order of iniitailization is based on level. objects
636 # are assigned a level based on type:
637 # net,devices,ldlm:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5
638 def getServiceLevel(node):
639 type = getServiceType(node)
640 if type in ('network',):
642 if type in ('device', 'ldlm'):
644 elif type in ('obd', 'mdd'):
646 elif type in ('mds','ost'):
648 elif type in ('mdc','osc'):
650 elif type in ('lov',):
652 elif type in ('mountpoint',):
657 # return list of services in a profile. list is a list of tuples
659 def getServices(lustreNode, profileNode):
661 for n in profileNode.childNodes:
662 if n.nodeType == n.ELEMENT_NODE:
663 servNode = lookup(lustreNode, getRef(n))
666 panic('service not found: ' + getRef(n))
667 level = getServiceLevel(servNode)
668 list.append((level, servNode))
672 def getByName(lustreNode, tag, name):
673 ndList = lustreNode.getElementsByTagName(tag)
675 if getName(nd) == name:
680 # ============================================================
681 # lconf type level logic
686 def startService(node):
687 type = getServiceType(node)
688 debug('Starting service:', type, getName(node), getUUID(node))
689 # there must be a more dynamic way of doing this...
694 elif type == 'network':
695 prepare_network(node)
706 elif type == 'mountpoint':
707 prepare_mountpoint(node)
710 # Prepare the system to run lustre using a particular profile
711 # in a the configuration.
712 # * load & the modules
713 # * setup networking for the current node
714 # * make sure partitions are in place and prepared
715 # * initialize devices with lctl
716 # Levels is important, and needs to be enforced.
717 def startProfile(lustreNode, profileNode):
719 panic("profile:", profile, "not found.")
720 services = getServices(lustreNode, profileNode)
726 def stopService(node):
727 type = getServiceType(node)
728 debug('Stopping service:', type, getName(node), getUUID(node))
729 # there must be a more dynamic way of doing this...
734 elif type == 'network':
735 cleanup_network(node)
746 elif type == 'mountpoint':
747 cleanup_mountpoint(node)
749 # Shutdown services in reverse order than they were started
750 def cleanupProfile(lustreNode, profileNode):
752 panic("profile:", profile, "not found.")
753 services = getServices(lustreNode, profileNode)
761 def doHost(lustreNode, hosts, cleanFlag):
764 node = getByName(lustreNode, 'node', h)
769 print 'No host entry found.'
772 reflist = node.getElementsByTagName('profile_ref')
775 cleanupProfile(lustreNode, lookup(lustreNode, getRef(r)))
777 startProfile(lustreNode, lookup(lustreNode, getRef(r)))
780 # Command line processing
782 def parse_cmdline(argv):
784 long_opts = ["ldap", "reformat", "lustre=", "verbose",
785 "portals=", "makeldiff", "cleanup", "iam=",
786 "help", "debug", "host=", "get="]
791 opts, args = getopt.getopt(argv, short_opts, long_opts)
792 except getopt.GetoptError:
797 if o in ("-h", "--help"):
800 options['cleanup'] = 1
801 if o in ("-v", "--verbose"):
802 options['verbose'] = 1
803 if o in ("-d", "--debug"):
805 options['verbose'] = 1
807 options['portals'] = a
809 options['lustre'] = a
810 if o == "--reformat":
811 options['reformat'] = 1
813 options['hostname'] = [a]
822 s = urllib.urlopen(url)
828 # Initialize or shutdown lustre according to a configuration file
829 # * prepare the system for lustre
830 # * configure devices with lctl
831 # Shutdown does steps in reverse
833 lctl = LCTLInterface('lctl')
836 args = parse_cmdline(sys.argv[1:])
838 dom = xml.dom.minidom.parse(args[0])
839 elif options.has_key('url'):
840 xmldata = fetch(options['url'])
841 dom = xml.dom.minidom.parseString(xmldata)
845 if not options.has_key('hostname'):
846 options['hostname'] = []
847 ret, host = run('hostname')
849 print "unable to determine hostname"
851 options['hostname'].append(string.strip(host[0]))
852 options['hostname'].append('localhost')
853 print "configuring for host: ", options['hostname']
854 doHost(dom.childNodes[0], options['hostname'], options.has_key('cleanup') )
856 if __name__ == "__main__":