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, socket
31 import xml.dom.minidom
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 self.lctl = find_prog(cmd)
113 raise RuntimeError, "unable to find lctl binary."
118 the cmds are written to stdin of lctl
119 lctl doesn't return errors when run in script mode, so
121 should modify command line to accept multiple commands, or
122 create complex command line options
124 debug("+", self.lctl, cmds)
125 if isnotouch(): return ([], 0)
126 p = popen2.Popen3(self.lctl, 1)
127 p.tochild.write(cmds + "\n")
129 out = p.fromchild.readlines()
132 debug('lctl:',string.strip(l))
133 err = p.childerr.readlines()
135 log (self.lctl, "error:", ret)
137 raise CommandError, err
140 def network(self, net, nid):
141 """ initialized network and add "self" """
142 # Idea: "mynid" could be used for all network types to add "self," and then
143 # this special case would be gone and the "self" hack would be hidden.
149 quit""" % (net, nid, nid)
158 # create a new connection
159 def connect(self, net, nid, port, servuuid, send_buf, read_buf):
160 # XXX: buf size params not used yet
165 quit""" % (net, nid, port, servuuid, nid)
168 # create a new connection
169 def add_route(self, net, to, via):
174 # create a new device with lctl
175 def disconnect(self, net, nid, port, servuuid):
182 # create a new device with lctl
183 def newdev(self, attach, setup = ""):
188 quit""" % (attach, setup)
192 def cleanup(self, name, uuid):
201 def lovconfig(self, uuid, mdsuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist):
205 lovconfig %s %d %d %d %s %s
206 quit""" % (mdsuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
209 # ============================================================
210 # Various system-level functions
211 # (ideally moved to their own module)
213 # Run a command and return the output and status.
214 # stderr is sent to /dev/null, could use popen3 to
215 # save it if necessary
217 cmd = string.join(map(str,args))
219 if isnotouch(): return (0, [])
220 f = os.popen(cmd + ' 2>&1')
229 # Run a command in the background.
230 def run_daemon(*args):
231 cmd = string.join(map(str,args))
233 if isnotouch(): return 0
234 f = os.popen(cmd + ' 2>&1')
243 # Determine full path to use for an external command
244 # searches dirname(argv[0]) first, then PATH
246 syspath = string.split(os.environ['PATH'], ':')
247 cmdpath = os.path.dirname(sys.argv[0])
248 syspath.insert(0, cmdpath);
249 syspath.insert(0, os.path.join(cmdpath, '../../portals/linux/utils/'))
251 prog = os.path.join(d,cmd)
252 if os.access(prog, os.X_OK):
257 # is the path a block device?
264 return stat.S_ISBLK(s[stat.ST_MODE])
266 # build fs according to type
268 def mkfs(fstype, dev):
269 if(fstype in ('ext3', 'extN')):
270 mkfs = 'mkfs.ext2 -j -b 4096'
272 print 'unsupported fs type: ', fstype
273 if not is_block(dev):
277 (ret, out) = run (mkfs, force, dev)
279 panic("Unable to build fs:", dev)
280 # enable hash tree indexing on fs
282 htree = 'echo "feature FEATURE_C5" | debugfs -w'
283 (ret, out) = run (htree, dev)
285 panic("Unable to enable htree:", dev)
287 # some systems use /dev/loopN, some /dev/loop/N
291 if not os.access(loop + str(0), os.R_OK):
293 if not os.access(loop + str(0), os.R_OK):
294 panic ("can't access loop devices")
297 # find loop device assigned to thefile
300 for n in xrange(0, MAX_LOOP_DEVICES):
302 if os.access(dev, os.R_OK):
303 (stat, out) = run('losetup', dev)
304 if (out and stat == 0):
305 m = re.search(r'\((.*)\)', out[0])
306 if m and file == m.group(1):
312 # create file if necessary and assign the first free loop device
313 def init_loop(file, size, fstype):
314 dev = find_loop(file)
316 print 'WARNING file:', file, 'already mapped to', dev
318 if not os.access(file, os.R_OK | os.W_OK):
319 run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size, file))
321 # find next free loop
322 for n in xrange(0, MAX_LOOP_DEVICES):
324 if os.access(dev, os.R_OK):
325 (stat, out) = run('losetup', dev)
327 run('losetup', dev, file)
330 print "out of loop devices"
332 print "out of loop devices"
335 # undo loop assignment
336 def clean_loop(file):
337 dev = find_loop(file)
339 ret, out = run('losetup -d', dev)
341 log('unable to clean loop device:', dev, 'for file:', file)
344 # initialize a block device if needed
345 def block_dev(dev, size, fstype, format):
346 if isnotouch(): return dev
347 if not is_block(dev):
348 dev = init_loop(dev, size, fstype)
349 if (format == 'yes'):
353 # ============================================================
354 # Classes to prepare and cleanup the various objects
357 """ Base class for the rest of the modules. The default cleanup method is
358 defined here, as well as some utilitiy funcs.
360 def __init__(self, tag_name, node):
362 self.tag_name = tag_name
363 self.name = node.getAttribute('name')
364 self.uuid = node.getAttribute('uuid')
366 def info(self, *args):
367 msg = string.join(map(str,args))
368 print self.tag_name + ":", self.name, self.uuid, msg
371 """ default cleanup, used for most modules """
374 lctl.cleanup(self.name, self.uuid)
376 print "cleanup failed: ", self.name
378 class Network(Module):
379 def __init__(self,node):
380 Module.__init__(self, 'NETWORK', node)
381 self.net_type = node.getAttribute('type')
382 self.nid = getText(node, 'server', "")
383 self.port = int(getText(node, 'port', 0))
384 self.send_buf = int(getText(node, 'send_buf', 0))
385 self.read_buf = int(getText(node, 'read_buf', 0))
388 self.info(self.net_type, self.nid, self.port)
389 if self.net_type == 'tcp':
390 ret = run_daemon(TCP_ACCEPTOR, self.port)
393 raise CommandError, "cannot run acceptor"
394 lctl.network(self.net_type, self.nid)
395 lctl.newdev(attach = "ptlrpc RPCDEV")
398 self.info(self.net_type, self.nid, self.port)
400 lctl.cleanup("RPCDEV", "")
402 print "cleanup failed: ", self.name
403 if self.net_type == 'tcp':
404 # yikes, this ugly! need to save pid in /var/something
405 run("killall acceptor")
408 def __init__(self,node):
409 Module.__init__(self, 'LDLM', node)
412 lctl.newdev(attach="ldlm %s %s" % (self.name, self.uuid),
416 def __init__(self,node):
417 Module.__init__(self, 'LOV', node)
418 devs = node.getElementsByTagName('devices')[0]
419 self.stripe_sz = int(devs.getAttribute('stripesize'))
420 self.stripe_off = int(devs.getAttribute('stripeoffset'))
421 self.pattern = int(devs.getAttribute('pattern'))
422 mdsref = node.getElementsByTagName('mds_ref')[0]
423 self.mdsuuid = mdsref.getAttribute('uuidref')
424 mds= lookup(node.parentNode, self.mdsuuid)
425 self.mdsname = getName(mds)
428 for child in devs.childNodes:
429 if child.nodeName == 'osc_ref':
430 devlist = devlist + child.getAttribute('uuidref') + " "
431 stripe_cnt = stripe_cnt + 1
432 self.devlist = devlist
433 self.stripe_cnt = stripe_cnt
436 self.info(self.mdsuuid, self.stripe_cnt, self.stripe_sz, self.stripe_off, self.pattern,
437 self.devlist, self.mdsname)
438 lctl.lovconfig(self.uuid, self.mdsname, self.stripe_cnt,
439 self.stripe_sz, self.stripe_off, self.pattern,
446 def __init__(self,node):
447 Module.__init__(self, 'MDS', node)
448 self.devname, self.size = getDevice(node)
449 self.fstype = getText(node, 'fstype')
450 self.format = getText(node, 'autoformat', "no")
453 self.info(self.devname, self.fstype, self.format)
454 blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
455 lctl.newdev(attach="mds %s %s" % (self.name, self.uuid),
456 setup ="%s %s" %(blkdev, self.fstype))
459 clean_loop(self.devname)
462 def __init__(self,node):
463 Module.__init__(self, 'MDC', node)
464 ref = node.getElementsByTagName('mds_ref')[0]
465 self.mds_uuid = ref.getAttribute('uuidref')
468 self.info(self.mds_uuid)
469 mds = lookup(self.dom_node.parentNode, self.mds_uuid)
471 panic(self.mdsuuid, "not found.")
472 net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
474 lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
475 lctl.newdev(attach="mdc %s %s" % (self.name, self.uuid),
476 setup ="%s %s" %(self.mds_uuid, srv.uuid))
479 self.info(self.mds_uuid)
480 net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
483 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
484 lctl.cleanup(self.name, self.uuid)
486 print "cleanup failed: ", self.name
489 def __init__(self, node):
490 Module.__init__(self, 'OBD', node)
491 self.obdtype = node.getAttribute('type')
492 self.devname, self.size = getDevice(node)
493 self.fstype = getText(node, 'fstype')
494 self.format = getText(node, 'autoformat', 'yes')
496 # need to check /proc/mounts and /etc/mtab before
497 # formatting anything.
498 # FIXME: check if device is already formatted.
500 self.info(self.obdtype, self.devname, self.size, self.fstype, self.format)
501 blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
502 lctl.newdev(attach="%s %s %s" % (self.obdtype, self.name, self.uuid),
503 setup ="%s %s" %(blkdev, self.fstype))
506 clean_loop(self.devname)
509 def __init__(self,node):
510 Module.__init__(self, 'OST', node)
511 ref = node.getElementsByTagName('obd_ref')[0]
512 self.obd_uuid = ref.getAttribute('uuidref')
515 self.info(self.obd_uuid)
516 lctl.newdev(attach="ost %s %s" % (self.name, self.uuid),
517 setup ="%s" % (self.obd_uuid))
520 def __init__(self,node):
521 Module.__init__(self, 'OSC', node)
522 ref = node.getElementsByTagName('obd_ref')[0]
523 self.obd_uuid = ref.getAttribute('uuidref')
524 ref = node.getElementsByTagName('ost_ref')[0]
525 self.ost_uuid = ref.getAttribute('uuidref')
528 self.info(self.obd_uuid, self.ost_uuid)
529 net = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
531 lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
532 lctl.newdev(attach="osc %s %s" % (self.name, self.uuid),
533 setup ="%s %s" %(self.obd_uuid, srv.uuid))
536 self.info(self.obd_uuid, self.ost_uuid)
537 net_uuid = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
540 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
541 lctl.cleanup(self.name, self.uuid)
543 print "cleanup failed: ", self.name
545 class Mountpoint(Module):
546 def __init__(self,node):
547 Module.__init__(self, 'MTPT', node)
548 self.path = getText(node, 'path')
549 ref = node.getElementsByTagName('mdc_ref')[0]
550 self.mdc_uuid = ref.getAttribute('uuidref')
551 ref = node.getElementsByTagName('osc_ref')[0]
552 self.lov_uuid = ref.getAttribute('uuidref')
555 l = lookup(self.dom_node.parentNode, self.lov_uuid)
556 if l.nodeName == 'lov':
558 for osc_uuid in string.split(dev.devlist):
559 osc = lookup(self.dom_node.parentNode, osc_uuid)
564 panic('osc not found:', osc_uuid)
569 self.info(self.path, self.mdc_uuid,self.lov_uuid)
570 lctl.newdev(attach="lov %s %s" % (dev.name, dev.uuid),
571 setup ="%s" % (self.mdc_uuid))
572 cmd = "mount -t lustre_lite -o osc=%s,mdc=%s none %s" % \
573 (self.lov_uuid, self.mdc_uuid, self.path)
574 run("mkdir", self.path)
577 panic("mount failed:", self.path)
579 self.info(self.path, self.mdc_uuid,self.lov_uuid)
580 run("umount", self.path)
582 # ============================================================
583 # XML processing and query
584 # TODO: Change query funcs to use XPath, which is muc cleaner
587 dev = obd.getElementsByTagName('device')[0]
590 size = int(dev.getAttribute('size'))
593 return dev.firstChild.data, size
595 # Get the text content from the first matching child
596 def getText(node, tag, default=""):
597 list = node.getElementsByTagName(tag)
601 return node.firstChild.data
605 def get_ost_net(node, uuid):
606 ost = lookup(node, uuid)
607 list = ost.getElementsByTagName('network_ref')
609 uuid = list[0].getAttribute('uuidref')
612 return lookup(node, uuid)
614 def lookup(node, uuid):
615 for n in node.childNodes:
616 if n.nodeType == n.ELEMENT_NODE:
617 if getUUID(n) == uuid:
624 # Get name attribute of node
626 return node.getAttribute('name')
629 return node.getAttribute('uuidref')
631 # Get name attribute of node
633 return node.getAttribute('uuid')
635 # the tag name is the service type
636 # fixme: this should do some checks to make sure the node is a service
637 def getServiceType(node):
641 # determine what "level" a particular node is at.
642 # the order of iniitailization is based on level. objects
643 # are assigned a level based on type:
644 # net,devices,ldlm:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5
645 def getServiceLevel(node):
646 type = getServiceType(node)
647 if type in ('network',):
649 if type in ('device', 'ldlm'):
651 elif type in ('obd', 'mdd'):
653 elif type in ('mds','ost'):
655 elif type in ('mdc','osc'):
657 elif type in ('lov',):
659 elif type in ('mountpoint',):
664 # return list of services in a profile. list is a list of tuples
666 def getServices(lustreNode, profileNode):
668 for n in profileNode.childNodes:
669 if n.nodeType == n.ELEMENT_NODE:
670 servNode = lookup(lustreNode, getRef(n))
673 panic('service not found: ' + getRef(n))
674 level = getServiceLevel(servNode)
675 list.append((level, servNode))
679 def getByName(lustreNode, tag, name):
680 ndList = lustreNode.getElementsByTagName(tag)
682 if getName(nd) == name:
687 # ============================================================
690 def startService(node, cleanFlag):
691 type = getServiceType(node)
692 debug('Starting service:', type, getName(node), getUUID(node))
693 # there must be a more dynamic way of doing this...
699 elif type == 'network':
711 elif type == 'mountpoint':
714 panic ("unknown service type:", type)
722 # Prepare the system to run lustre using a particular profile
723 # in a the configuration.
724 # * load & the modules
725 # * setup networking for the current node
726 # * make sure partitions are in place and prepared
727 # * initialize devices with lctl
728 # Levels is important, and needs to be enforced.
729 def startProfile(lustreNode, profileNode, cleanFlag):
731 panic("profile:", profile, "not found.")
732 services = getServices(lustreNode, profileNode)
736 startService(s[1], cleanFlag)
740 def doHost(lustreNode, hosts, cleanFlag):
743 node = getByName(lustreNode, 'node', h)
748 print 'No host entry found.'
751 reflist = node.getElementsByTagName('profile')
752 for profile in reflist:
753 startProfile(lustreNode, profile, cleanFlag)
755 # Command line processing
757 def parse_cmdline(argv):
759 long_opts = ["ldap", "reformat", "lustre=", "verbose",
760 "portals=", "makeldiff", "cleanup", "iam=",
761 "help", "debug", "host=", "get="]
766 opts, args = getopt.getopt(argv, short_opts, long_opts)
767 except getopt.GetoptError:
772 if o in ("-h", "--help"):
775 options['cleanup'] = 1
776 if o in ("-v", "--verbose"):
777 options['verbose'] = 1
778 if o in ("-d", "--debug"):
780 options['verbose'] = 1
782 options['portals'] = a
784 options['lustre'] = a
785 if o == "--reformat":
786 options['reformat'] = 1
788 options['hostname'] = [a]
797 s = urllib.urlopen(url)
803 # Initialize or shutdown lustre according to a configuration file
804 # * prepare the system for lustre
805 # * configure devices with lctl
806 # Shutdown does steps in reverse
808 lctl = LCTLInterface('lctl')
810 global options, TCP_ACCEPTOR
811 TCP_ACCEPTOR = find_prog('acceptor')
813 panic('acceptor not found')
814 args = parse_cmdline(sys.argv[1:])
816 if not os.access(args[0], os.R_OK | os.W_OK):
817 print 'File not found:', args[0]
819 dom = xml.dom.minidom.parse(args[0])
820 elif options.has_key('url'):
821 xmldata = fetch(options['url'])
822 dom = xml.dom.minidom.parseString(xmldata)
826 if not options.has_key('hostname'):
827 options['hostname'] = []
828 host = socket.gethostname()
830 options['hostname'].append(host)
831 options['hostname'].append('localhost')
832 print "configuring for host: ", options['hostname']
833 doHost(dom.documentElement, options['hostname'], options.has_key('cleanup') )
835 if __name__ == "__main__":
841 print '<insert exception data here>'