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, pattern, devlist):
194 lovconfig %s %d %d %s %s
195 quit""" % (mdcuuid, uuid, stripe_cnt, stripe_sz, 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, pattern, devlist, mdsname) = getLOVInfo(node)
333 print 'LOV:', name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname
334 lctl.lovconfig(uuid, mdsname, stripe_cnt, strip_sz, 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),
365 setup ="$%s" % (obd))
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 lctl.connect(net, server, port, netuuid)
390 lctl.newdev(attach="mdc %s %s" % (name, uuid),
391 setup ="%s %s" %(mdsuuid, netuuid))
393 def prepare_mountpoint(node):
394 name, uuid, oscuuid, mdcuuid, mtpt = getMTPTInfo(node)
395 print 'MTPT:', name, uuid, oscuuid, mdcuuid, mtpt
396 cmd = "mount -t lustre_lite -o ost=%s,mds=%s none %s" % \
397 (oscuuid, mdcuuid, mtpt)
400 # ============================================================
401 # Functions to cleanup the various objects
403 def cleanup_ldlm(node):
404 (name, uuid) = getNodeAttr(node)
405 print 'LDLM:', name, uuid
407 lctl.cleanup(name, uuid)
409 print "cleanup failed: ", name
411 def cleanup_lov(node):
412 (name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname) = getLOVInfo(node)
413 print 'LOV:', name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname
415 lctl.cleanup(name, uuid)
417 print "cleanup failed: ", name
419 def cleanup_network(node):
420 (name, uuid, type, nid, port) = getNetworkInfo(node)
421 print 'NETWORK:', name, uuid, type, nid, port
423 lctl.cleanup("RPCDEV", "")
425 print "cleanup failed: ", name
426 # yikes, this ugly! need to save pid in /var/something
427 run("killall acceptor")
430 # need to check /proc/mounts and /etc/mtab before
431 # formatting anything.
432 # FIXME: check if device is already formatted.
433 def cleanup_obd(obd):
434 (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
435 print "OBD: ", name, obdtype, dev, size, fstype, format
437 lctl.cleanup(name, uuid)
439 print "cleanup failed: ", name
442 def cleanup_ost(ost):
443 name, uuid, obd = getOSTInfo(ost)
444 print "OST: ", name, uuid, obd
446 lctl.cleanup(name, uuid)
448 print "cleanup failed: ", name
450 def cleanup_mds(node):
451 (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
452 print "MDS: ", name, dev, size, fstype
454 lctl.cleanup(name, uuid)
456 print "cleanup failed: ", name
460 def cleanup_mdc(node):
461 (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
462 print 'MDC:', name, uuid, mdsuuid, netuuid
463 net = lookup(node.parentNode, netuuid)
464 srvname, srvuuid, net, server, port = getNetworkInfo(net)
466 lctl.disconnect(net, server, port, netuuid)
467 lctl.cleanup(name, uuid)
469 print "cleanup failed: ", name
472 def cleanup_osc(node):
473 (name, uuid, obduuid, ostuuid) = getOSCInfo(node)
474 print 'OSC:', name, uuid, obduuid, ostuuid
475 net = lookup(node.parentNode, ostuuid)
476 srvname, srvuuid, net, server, port = getNetworkInfo(net)
478 lctl.disconnect(net, server, port, ostuuid)
479 lctl.cleanup(name, uuid)
481 print "cleanup failed: ", name
483 def cleanup_mountpoint(node):
484 name, uuid, oscuuid, mdcuuid, mtpt = getMTPTInfo(node)
485 print 'MTPT:', name, uuid, oscuuid, mdcuuid, mtpt
488 # ============================================================
489 # XML processing and query
492 dev = obd.getElementsByTagName('device')[0]
495 size = int(dev.getAttribute('size'))
498 return dev.firstChild.data, size
501 def getNetworkInfo(node):
502 name, uuid = getNodeAttr(node);
503 type = node.getAttribute('type')
504 nid = getText(node, 'server', "")
505 port = int(getText(node, 'port', 0))
506 return name, uuid, type, nid, port
508 # extract device attributes for an obd
509 def getNodeAttr(node):
510 name = node.getAttribute('name')
511 uuid = node.getAttribute('uuid')
515 name, uuid = getNodeAttr(obd);
516 obdtype = obd.getAttribute('type')
517 devname, size = getDevice(obd)
518 fstype = getText(obd, 'fstype')
519 format = getText(obd, 'autoformat')
520 return (name, uuid, obdtype, devname, size, fstype, format)
523 def getLOVInfo(node):
524 name, uuid = getNodeAttr(node)
525 devs = node.getElementsByTagName('devices')[0]
526 stripe_sz = int(devs.getAttribute('stripesize'))
527 pattern = int(devs.getAttribute('pattern'))
528 mdcref = node.getElementsByTagName('mdc_ref')[0]
529 mdcuuid = mdcref.getAttribute('uuidref')
530 mdc= lookup(node.parentNode, mdcuuid)
531 mdsref = mdc.getElementsByTagName('mds_ref')[0]
532 mdsuuid = mdsref.getAttribute('uuidref')
533 mds= lookup(node.parentNode, mdsuuid)
534 mdsname = getName(mds)
537 for child in devs.childNodes:
538 if child.nodeName == 'osc_ref':
539 devlist = devlist + child.getAttribute('uuidref') + " "
540 strip_cnt = stripe_cnt + 1
541 return (name, uuid, mdcuuid, stripe_cnt, stripe_sz, pattern, devlist, mdsname)
543 # extract device attributes for an obd
544 def getMDSInfo(node):
545 name, uuid = getNodeAttr(node)
546 devname, size = getDevice(node)
547 fstype = getText(node, 'fstype')
548 format = getText(node, 'autoformat', "no")
549 return (name, uuid, devname, size, fstype, format)
551 # extract device attributes for an obd
552 def getMDCInfo(node):
553 name, uuid = getNodeAttr(node)
554 ref = node.getElementsByTagName('mds_ref')[0]
555 mdsuuid = ref.getAttribute('uuidref')
556 ref = node.getElementsByTagName('network_ref')[0]
557 netuuid = ref.getAttribute('uuidref')
558 return (name, uuid, mdsuuid, netuuid)
561 # extract device attributes for an obd
562 def getOSTInfo(node):
563 name, uuid = getNodeAttr(node)
564 ref = node.getElementsByTagName('obd_ref')[0]
565 uuid = ref.getAttribute('uuidref')
566 obd = lookup(node.parentNode, uuid)
568 obdname = getOBDInfo(obd)[0]
570 obdname = "OBD NOT FOUND"
571 return (name, uuid, obdname)
573 # extract device attributes for an obd
574 def getOSCInfo(node):
575 name, uuid = getNodeAttr(node)
576 ref = node.getElementsByTagName('obd_ref')[0]
577 obduuid = ref.getAttribute('uuidref')
578 ref = node.getElementsByTagName('network_ref')[0]
579 ostuuid = ref.getAttribute('uuidref')
580 return (name, uuid, obduuid, ostuuid)
582 # extract device attributes for an obd
583 def getMTPTInfo(node):
584 name, uuid = getNodeAttr(node)
585 path = getText(node, 'path')
586 ref = node.getElementsByTagName('mdc_ref')[0]
587 mdcuuid = ref.getAttribute('uuidref')
588 ref = node.getElementsByTagName('osc_ref')[0]
589 lovuuid = ref.getAttribute('uuidref')
590 return (name, uuid, lovuuid, mdcuuid, path)
593 # Get the text content from the first matching child
594 def getText(node, tag, default=""):
595 list = node.getElementsByTagName(tag)
599 return node.firstChild.data
603 # Recusively search from node for a uuid
604 def lookup(node, uuid):
605 for n in node.childNodes:
606 # this service_id check is ugly. need some other way to
607 # differentiate between definitions and references
608 if n.nodeType == n.ELEMENT_NODE:
609 if getUUID(n) == uuid:
616 # Get name attribute of node
618 return node.getAttribute('name')
621 return node.getAttribute('uuidref')
623 # Get name attribute of node
625 return node.getAttribute('uuid')
627 # the tag name is the service type
628 # fixme: this should do some checks to make sure the node is a service
629 def getServiceType(node):
633 # determine what "level" a particular node is at.
634 # the order of iniitailization is based on level. objects
635 # are assigned a level based on type:
636 # net,devices,ldlm:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5
637 def getServiceLevel(node):
638 type = getServiceType(node)
639 if type in ('network',):
641 if type in ('device', 'ldlm'):
643 elif type in ('obd', 'mdd'):
645 elif type in ('mds','ost'):
647 elif type in ('mdc','osc'):
649 elif type in ('lov',):
651 elif type in ('mountpoint',):
656 # return list of services in a profile. list is a list of tuples
658 def getServices(lustreNode, profileNode):
660 for n in profileNode.childNodes:
661 if n.nodeType == n.ELEMENT_NODE:
662 servNode = lookup(lustreNode, getRef(n))
665 panic('service not found: ' + getRef(n))
666 level = getServiceLevel(servNode)
667 list.append((level, servNode))
671 def getByName(lustreNode, tag, name):
672 ndList = lustreNode.getElementsByTagName(tag)
674 if getName(nd) == name:
679 # ============================================================
680 # lconf type level logic
685 def startService(node):
686 type = getServiceType(node)
687 debug('Starting service:', type, getName(node), getUUID(node))
688 # there must be a more dynamic way of doing this...
693 elif type == 'network':
694 prepare_network(node)
705 elif type == 'mountpoint':
706 prepare_mountpoint(node)
709 # Prepare the system to run lustre using a particular profile
710 # in a the configuration.
711 # * load & the modules
712 # * setup networking for the current node
713 # * make sure partitions are in place and prepared
714 # * initialize devices with lctl
715 # Levels is important, and needs to be enforced.
716 def startProfile(lustreNode, profileNode):
718 panic("profile:", profile, "not found.")
719 services = getServices(lustreNode, profileNode)
725 def stopService(node):
726 type = getServiceType(node)
727 debug('Stopping service:', type, getName(node), getUUID(node))
728 # there must be a more dynamic way of doing this...
733 elif type == 'network':
734 cleanup_network(node)
745 elif type == 'mountpoint':
746 cleanup_mountpoint(node)
748 # Shutdown services in reverse order than they were started
749 def cleanupProfile(lustreNode, profileNode):
751 panic("profile:", profile, "not found.")
752 services = getServices(lustreNode, profileNode)
760 def doHost(lustreNode, hosts, cleanFlag):
762 node = getByName(lustreNode, 'node', h)
766 reflist = node.getElementsByTagName('profile_ref')
769 cleanupProfile(lustreNode, lookup(lustreNode, getRef(r)))
771 startProfile(lustreNode, lookup(lustreNode, getRef(r)))
774 # Command line processing
776 def parse_cmdline(argv):
778 long_opts = ["ldap", "reformat", "lustre=", "verbose",
779 "portals=", "makeldiff", "cleanup", "iam=",
780 "help", "debug", "host=", "get="]
785 opts, args = getopt.getopt(argv, short_opts, long_opts)
786 except getopt.GetoptError:
791 if o in ("-h", "--help"):
794 options['cleanup'] = 1
795 if o in ("-v", "--verbose"):
796 options['verbose'] = 1
797 if o in ("-d", "--debug"):
799 options['verbose'] = 1
801 options['portals'] = a
803 options['lustre'] = a
804 if o == "--reformat":
805 options['reformat'] = 1
807 options['hostname'] = [a]
816 s = urllib.urlopen(url)
822 # Initialize or shutdown lustre according to a configuration file
823 # * prepare the system for lustre
824 # * configure devices with lctl
825 # Shutdown does steps in reverse
827 lctl = LCTLInterface('lctl')
830 args = parse_cmdline(sys.argv[1:])
832 dom = xml.dom.minidom.parse(args[0])
833 elif options.has_key('url'):
834 xmldata = fetch(options['url'])
835 dom = xml.dom.minidom.parseString(xmldata)
839 if not options.has_key('hostname'):
840 ret, host = run('hostname')
842 print "unable to determine hostname"
844 options['hostname'] = [string.strip(host[0])]
845 options['hostname'].append('localhost')
846 print "configuring for host: ", options['hostname']
847 doHost(dom.childNodes[0], options['hostname'], options.has_key('cleanup') )
849 if __name__ == "__main__":