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)
114 debug('! lctl not found')
117 raise CommandError, "unable to find lctl binary."
122 the cmds are written to stdin of lctl
123 lctl doesn't return errors when run in script mode, so
125 should modify command line to accept multiple commands, or
126 create complex command line options
128 debug("+", self.lctl, cmds)
129 if isnotouch(): return ([], 0)
130 p = popen2.Popen3(self.lctl, 1)
131 p.tochild.write(cmds + "\n")
133 out = p.fromchild.readlines()
136 debug('lctl:',string.strip(l))
137 err = p.childerr.readlines()
139 log (self.lctl, "error:", ret)
141 raise CommandError, err
144 def network(self, net, nid):
145 """ initialized network and add "self" """
146 # Idea: "mynid" could be used for all network types to add "self," and then
147 # this special case would be gone and the "self" hack would be hidden.
153 quit""" % (net, nid, nid)
162 # create a new connection
163 def connect(self, net, nid, port, servuuid, send_buf, read_buf):
164 # XXX: buf size params not used yet
169 quit""" % (net, nid, port, servuuid, nid)
172 # create a new connection
173 def add_route(self, net, to, via):
178 # create a new device with lctl
179 def disconnect(self, net, nid, port, servuuid):
186 # create a new device with lctl
187 def newdev(self, attach, setup = ""):
192 quit""" % (attach, setup)
196 def cleanup(self, name, uuid):
205 def lovconfig(self, uuid, mdsuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist):
209 lovconfig %s %d %d %d %s %s
210 quit""" % (mdsuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
213 # ============================================================
214 # Various system-level functions
215 # (ideally moved to their own module)
217 # Run a command and return the output and status.
218 # stderr is sent to /dev/null, could use popen3 to
219 # save it if necessary
221 cmd = string.join(map(str,args))
223 if isnotouch(): return (0, [])
224 f = os.popen(cmd + ' 2>&1')
233 # Run a command in the background.
234 def run_daemon(*args):
235 cmd = string.join(map(str,args))
237 if isnotouch(): return 0
238 f = os.popen(cmd + ' 2>&1')
247 # Determine full path to use for an external command
248 # searches dirname(argv[0]) first, then PATH
250 syspath = string.split(os.environ['PATH'], ':')
251 cmdpath = os.path.dirname(sys.argv[0])
252 syspath.insert(0, cmdpath);
253 syspath.insert(0, os.path.join(cmdpath, '../../portals/linux/utils/'))
255 prog = os.path.join(d,cmd)
256 if os.access(prog, os.X_OK):
261 # is the path a block device?
268 return stat.S_ISBLK(s[stat.ST_MODE])
270 # build fs according to type
272 def mkfs(fstype, dev):
273 if(fstype in ('ext3', 'extN')):
274 mkfs = 'mkfs.ext2 -j -b 4096'
276 print 'unsupported fs type: ', fstype
277 if not is_block(dev):
281 (ret, out) = run (mkfs, force, dev)
283 panic("Unable to build fs:", dev)
284 # enable hash tree indexing on fs
286 htree = 'echo "feature FEATURE_C5" | debugfs -w'
287 (ret, out) = run (htree, dev)
289 panic("Unable to enable htree:", dev)
291 # some systems use /dev/loopN, some /dev/loop/N
295 if not os.access(loop + str(0), os.R_OK):
297 if not os.access(loop + str(0), os.R_OK):
298 panic ("can't access loop devices")
301 # find loop device assigned to thefile
304 for n in xrange(0, MAX_LOOP_DEVICES):
306 if os.access(dev, os.R_OK):
307 (stat, out) = run('losetup', dev)
308 if (out and stat == 0):
309 m = re.search(r'\((.*)\)', out[0])
310 if m and file == m.group(1):
316 # create file if necessary and assign the first free loop device
317 def init_loop(file, size, fstype):
318 dev = find_loop(file)
320 print 'WARNING file:', file, 'already mapped to', dev
322 if not os.access(file, os.R_OK | os.W_OK):
323 run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size, file))
325 # find next free loop
326 for n in xrange(0, MAX_LOOP_DEVICES):
328 if os.access(dev, os.R_OK):
329 (stat, out) = run('losetup', dev)
331 run('losetup', dev, file)
334 print "out of loop devices"
336 print "out of loop devices"
339 # undo loop assignment
340 def clean_loop(file):
341 dev = find_loop(file)
343 ret, out = run('losetup -d', dev)
345 log('unable to clean loop device:', dev, 'for file:', file)
348 # initialize a block device if needed
349 def block_dev(dev, size, fstype, format):
350 if isnotouch(): return dev
351 if not is_block(dev):
352 dev = init_loop(dev, size, fstype)
353 if (format == 'yes'):
357 # ============================================================
358 # Classes to prepare and cleanup the various objects
361 """ Base class for the rest of the modules. The default cleanup method is
362 defined here, as well as some utilitiy funcs.
364 def __init__(self, tag_name, node):
366 self.tag_name = tag_name
367 self.name = node.getAttribute('name')
368 self.uuid = node.getAttribute('uuid')
370 def info(self, *args):
371 msg = string.join(map(str,args))
372 print self.tag_name + ":", self.name, self.uuid, msg
375 """ default cleanup, used for most modules """
378 lctl.cleanup(self.name, self.uuid)
380 print "cleanup failed: ", self.name
382 class Network(Module):
383 def __init__(self,node):
384 Module.__init__(self, 'NETWORK', node)
385 self.net_type = node.getAttribute('type')
386 self.nid = getText(node, 'server', "")
387 self.port = int(getText(node, 'port', 0))
388 self.send_buf = int(getText(node, 'send_buf', 0))
389 self.read_buf = int(getText(node, 'read_buf', 0))
392 self.info(self.net_type, self.nid, self.port)
393 if self.net_type == 'tcp':
394 ret = run_daemon(TCP_ACCEPTOR, self.port)
397 raise CommandError, "cannot run acceptor"
398 lctl.network(self.net_type, self.nid)
399 lctl.newdev(attach = "ptlrpc RPCDEV")
402 self.info(self.net_type, self.nid, self.port)
404 lctl.cleanup("RPCDEV", "")
406 print "cleanup failed: ", self.name
407 if self.net_type == 'tcp':
408 # yikes, this ugly! need to save pid in /var/something
409 run("killall acceptor")
412 def __init__(self,node):
413 Module.__init__(self, 'LDLM', node)
416 lctl.newdev(attach="ldlm %s %s" % (self.name, self.uuid),
420 def __init__(self,node):
421 Module.__init__(self, 'LOV', node)
422 devs = node.getElementsByTagName('devices')[0]
423 self.stripe_sz = int(devs.getAttribute('stripesize'))
424 self.stripe_off = int(devs.getAttribute('stripeoffset'))
425 self.pattern = int(devs.getAttribute('pattern'))
426 mdsref = node.getElementsByTagName('mds_ref')[0]
427 self.mdsuuid = mdsref.getAttribute('uuidref')
428 mds= lookup(node.parentNode, self.mdsuuid)
429 self.mdsname = getName(mds)
432 for child in devs.childNodes:
433 if child.nodeName == 'osc_ref':
434 devlist = devlist + child.getAttribute('uuidref') + " "
435 stripe_cnt = stripe_cnt + 1
436 self.devlist = devlist
437 self.stripe_cnt = stripe_cnt
440 self.info(self.mdsuuid, self.stripe_cnt, self.stripe_sz, self.stripe_off, self.pattern,
441 self.devlist, self.mdsname)
442 lctl.lovconfig(self.uuid, self.mdsname, self.stripe_cnt,
443 self.stripe_sz, self.stripe_off, self.pattern,
450 def __init__(self,node):
451 Module.__init__(self, 'MDS', node)
452 self.devname, self.size = getDevice(node)
453 self.fstype = getText(node, 'fstype')
454 self.format = getText(node, 'autoformat', "no")
457 self.info(self.devname, self.fstype, self.format)
458 blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
459 lctl.newdev(attach="mds %s %s" % (self.name, self.uuid),
460 setup ="%s %s" %(blkdev, self.fstype))
463 clean_loop(self.devname)
466 def __init__(self,node):
467 Module.__init__(self, 'MDC', node)
468 ref = node.getElementsByTagName('mds_ref')[0]
469 self.mds_uuid = ref.getAttribute('uuidref')
472 self.info(self.mds_uuid)
473 mds = lookup(self.dom_node.parentNode, self.mds_uuid)
475 panic(self.mdsuuid, "not found.")
476 net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
478 lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
479 lctl.newdev(attach="mdc %s %s" % (self.name, self.uuid),
480 setup ="%s %s" %(self.mds_uuid, srv.uuid))
483 self.info(self.mds_uuid)
484 net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
487 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
488 lctl.cleanup(self.name, self.uuid)
490 print "cleanup failed: ", self.name
493 def __init__(self, node):
494 Module.__init__(self, 'OBD', node)
495 self.obdtype = node.getAttribute('type')
496 self.devname, self.size = getDevice(node)
497 self.fstype = getText(node, 'fstype')
498 self.format = getText(node, 'autoformat', 'yes')
500 # need to check /proc/mounts and /etc/mtab before
501 # formatting anything.
502 # FIXME: check if device is already formatted.
504 self.info(self.obdtype, self.devname, self.size, self.fstype, self.format)
505 blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
506 lctl.newdev(attach="%s %s %s" % (self.obdtype, self.name, self.uuid),
507 setup ="%s %s" %(blkdev, self.fstype))
510 clean_loop(self.devname)
513 def __init__(self,node):
514 Module.__init__(self, 'OST', node)
515 ref = node.getElementsByTagName('obd_ref')[0]
516 self.obd_uuid = ref.getAttribute('uuidref')
519 self.info(self.obd_uuid)
520 lctl.newdev(attach="ost %s %s" % (self.name, self.uuid),
521 setup ="%s" % (self.obd_uuid))
524 def __init__(self,node):
525 Module.__init__(self, 'OSC', node)
526 ref = node.getElementsByTagName('obd_ref')[0]
527 self.obd_uuid = ref.getAttribute('uuidref')
528 ref = node.getElementsByTagName('ost_ref')[0]
529 self.ost_uuid = ref.getAttribute('uuidref')
532 self.info(self.obd_uuid, self.ost_uuid)
533 net = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
535 lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
536 lctl.newdev(attach="osc %s %s" % (self.name, self.uuid),
537 setup ="%s %s" %(self.obd_uuid, srv.uuid))
540 self.info(self.obd_uuid, self.ost_uuid)
541 net_uuid = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
544 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
545 lctl.cleanup(self.name, self.uuid)
547 print "cleanup failed: ", self.name
549 class Mountpoint(Module):
550 def __init__(self,node):
551 Module.__init__(self, 'MTPT', node)
552 self.path = getText(node, 'path')
553 ref = node.getElementsByTagName('mdc_ref')[0]
554 self.mdc_uuid = ref.getAttribute('uuidref')
555 ref = node.getElementsByTagName('osc_ref')[0]
556 self.lov_uuid = ref.getAttribute('uuidref')
559 l = lookup(self.dom_node.parentNode, self.lov_uuid)
560 if l.nodeName == 'lov':
562 for osc_uuid in string.split(dev.devlist):
563 osc = lookup(self.dom_node.parentNode, osc_uuid)
568 panic('osc not found:', osc_uuid)
573 self.info(self.path, self.mdc_uuid,self.lov_uuid)
574 lctl.newdev(attach="lov %s %s" % (dev.name, dev.uuid),
575 setup ="%s" % (self.mdc_uuid))
576 cmd = "mount -t lustre_lite -o osc=%s,mdc=%s none %s" % \
577 (self.lov_uuid, self.mdc_uuid, self.path)
578 run("mkdir", self.path)
581 panic("mount failed:", self.path)
583 self.info(self.path, self.mdc_uuid,self.lov_uuid)
584 run("umount", self.path)
586 # ============================================================
587 # XML processing and query
588 # TODO: Change query funcs to use XPath, which is muc cleaner
591 dev = obd.getElementsByTagName('device')[0]
594 size = int(dev.getAttribute('size'))
597 return dev.firstChild.data, size
599 # Get the text content from the first matching child
600 def getText(node, tag, default=""):
601 list = node.getElementsByTagName(tag)
605 return node.firstChild.data
609 def get_ost_net(node, uuid):
610 ost = lookup(node, uuid)
611 list = ost.getElementsByTagName('network_ref')
613 uuid = list[0].getAttribute('uuidref')
616 return lookup(node, uuid)
618 def lookup(node, uuid):
619 for n in node.childNodes:
620 if n.nodeType == n.ELEMENT_NODE:
621 if getUUID(n) == uuid:
628 # Get name attribute of node
630 return node.getAttribute('name')
633 return node.getAttribute('uuidref')
635 # Get name attribute of node
637 return node.getAttribute('uuid')
639 # the tag name is the service type
640 # fixme: this should do some checks to make sure the node is a service
641 def getServiceType(node):
645 # determine what "level" a particular node is at.
646 # the order of iniitailization is based on level. objects
647 # are assigned a level based on type:
648 # net,devices,ldlm:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5
649 def getServiceLevel(node):
650 type = getServiceType(node)
651 if type in ('network',):
653 if type in ('device', 'ldlm'):
655 elif type in ('obd', 'mdd'):
657 elif type in ('mds','ost'):
659 elif type in ('mdc','osc'):
661 elif type in ('lov',):
663 elif type in ('mountpoint',):
668 # return list of services in a profile. list is a list of tuples
670 def getServices(lustreNode, profileNode):
672 for n in profileNode.childNodes:
673 if n.nodeType == n.ELEMENT_NODE:
674 servNode = lookup(lustreNode, getRef(n))
677 panic('service not found: ' + getRef(n))
678 level = getServiceLevel(servNode)
679 list.append((level, servNode))
683 def getByName(lustreNode, tag, name):
684 ndList = lustreNode.getElementsByTagName(tag)
686 if getName(nd) == name:
691 # ============================================================
694 def startService(node, cleanFlag):
695 type = getServiceType(node)
696 debug('Starting service:', type, getName(node), getUUID(node))
697 # there must be a more dynamic way of doing this...
703 elif type == 'network':
715 elif type == 'mountpoint':
718 panic ("unknown service type:", type)
726 # Prepare the system to run lustre using a particular profile
727 # in a the configuration.
728 # * load & the modules
729 # * setup networking for the current node
730 # * make sure partitions are in place and prepared
731 # * initialize devices with lctl
732 # Levels is important, and needs to be enforced.
733 def startProfile(lustreNode, profileNode, cleanFlag):
735 panic("profile:", profile, "not found.")
736 services = getServices(lustreNode, profileNode)
740 startService(s[1], cleanFlag)
744 def doHost(lustreNode, hosts, cleanFlag):
747 node = getByName(lustreNode, 'node', h)
752 print 'No host entry found.'
755 reflist = node.getElementsByTagName('profile')
756 for profile in reflist:
757 startProfile(lustreNode, profile, cleanFlag)
759 # Command line processing
761 def parse_cmdline(argv):
763 long_opts = ["ldap", "reformat", "lustre=", "verbose",
764 "portals=", "makeldiff", "cleanup", "iam=",
765 "help", "debug", "host=", "get="]
770 opts, args = getopt.getopt(argv, short_opts, long_opts)
771 except getopt.GetoptError:
776 if o in ("-h", "--help"):
779 options['cleanup'] = 1
780 if o in ("-v", "--verbose"):
781 options['verbose'] = 1
782 if o in ("-d", "--debug"):
784 options['verbose'] = 1
786 options['portals'] = a
788 options['lustre'] = a
789 if o == "--reformat":
790 options['reformat'] = 1
792 options['hostname'] = [a]
801 s = urllib.urlopen(url)
807 # Initialize or shutdown lustre according to a configuration file
808 # * prepare the system for lustre
809 # * configure devices with lctl
810 # Shutdown does steps in reverse
813 global options, TCP_ACCEPTOR, lctl
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']
834 TCP_ACCEPTOR = find_prog('acceptor')
837 TCP_ACCEPTOR = 'acceptor'
838 debug('! acceptor not found')
840 panic('acceptor not found')
842 lctl = LCTLInterface('lctl')
844 doHost(dom.documentElement, options['hostname'], options.has_key('cleanup') )
846 if __name__ == "__main__":
852 print '<insert exception data here>'