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
32 from xml.xpath import Evaluate
35 TCP_ACCEPTOR = '../..//portals/linux/utils/acceptor'
39 # Maximum number of devices to search for.
40 # (the /dev/loop* nodes need to be created beforehand)
41 MAX_LOOP_DEVICES = 256
45 print """usage: lconf config.xml
47 config.xml Lustre configuration in xml format.
48 --get <url> URL to fetch a config file
49 -v | --verbose Print system commands as they are run
50 -d | --debug Print system commands, but does not run them
51 --host <hostname> Load config for <hostname>
52 --cleanup Cleans up config. (Shutdown)
53 -h | --help Print this help
56 --ldap server LDAP server with lustre config database
57 --reformat Reformat all devices (will confirm)
58 --lustre="src dir" Base directory of lustre sources. Used to search
60 --portals=src Portals source
61 --makeldiff Translate xml source to LDIFF
66 # ============================================================
67 # debugging and error funcs
69 def fixme(msg = "this feature"):
70 raise RuntimeError, msg + ' not implmemented yet.'
73 msg = string.join(map(str,args))
75 raise RuntimeError, msg
78 msg = string.join(map(str,args))
86 msg = string.join(map(str,args))
87 if isverbose(): print msg
90 return options.has_key('verbose') and options['verbose'] == 1
93 return options.has_key('debug') and options['debug'] == 1
95 # ============================================================
96 # locally defined exceptions
97 class CommandError (exceptions.Exception):
98 def __init__(self, args=None):
101 # ============================================================
102 # handle lctl interface
105 Manage communication with lctl
108 def __init__(self, cmd):
110 Initialize close by finding the lctl binary.
112 syspath = string.split(os.environ['PATH'], ':')
113 syspath.insert(0, "../utils");
116 lctl = os.path.join(d,cmd)
117 if os.access(lctl, os.X_OK):
121 raise RuntimeError, "unable to find lctl binary."
126 the cmds are written to stdin of lctl
127 lctl doesn't return errors when run in script mode, so
129 should modify command line to accept multiple commands, or
130 create complex command line options
132 debug("+", self.lctl, cmds)
133 if isnotouch(): return ([], 0)
134 p = popen2.Popen3(self.lctl, 1)
135 p.tochild.write(cmds + "\n")
137 out = p.fromchild.readlines()
139 err = p.childerr.readlines()
141 log (self.lctl, "error:", ret)
143 raise CommandError, err
146 def network(self, net, nid):
147 """ initialized network and add "self" """
148 # Idea: "mynid" could be used for all network types to add "self," and then
149 # this special case would be gone and the "self" hack would be hidden.
155 quit""" % (net, nid, nid)
164 # create a new connection
165 def connect(self, net, nid, port, servuuid, send_buf, read_buf):
166 # XXX: buf size params not used yet
171 quit""" % (net, nid, port, servuuid, nid)
174 # create a new connection
175 def add_route(self, net, to, via):
180 # create a new device with lctl
181 def disconnect(self, net, nid, port, servuuid):
188 # create a new device with lctl
189 def newdev(self, attach, setup = ""):
194 quit""" % (attach, setup)
198 def cleanup(self, name, uuid):
207 def lovconfig(self, uuid, mdcuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist):
211 lovconfig %s %d %d %d %s %s
212 quit""" % (mdcuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
215 # ============================================================
216 # Various system-level functions
217 # (ideally moved to their own module)
219 # Run a command and return the output and status.
220 # stderr is sent to /dev/null, could use popen3 to
221 # save it if necessary
223 cmd = string.join(map(str,args))
225 if isnotouch(): return (0, [])
226 f = os.popen(cmd + ' 2>&1')
235 # Run a command in the background.
236 def run_daemon(*args):
237 cmd = string.join(map(str,args))
239 if isnotouch(): return 0
240 f = os.popen(cmd + ' 2>&1')
249 # is the path a block device?
256 return stat.S_ISBLK(s[stat.ST_MODE])
258 # build fs according to type
260 def mkfs(fstype, dev):
261 if(fstype in ('ext3', 'extN')):
262 mkfs = 'mkfs.ext2 -j -b 4096'
264 print 'unsupported fs type: ', fstype
265 if not is_block(dev):
269 (ret, out) = run (mkfs, force, dev)
271 panic("Unable to build fs:", dev)
273 # some systems use /dev/loopN, some /dev/loop/N
277 if not os.access(loop + str(0), os.R_OK):
279 if not os.access(loop + str(0), os.R_OK):
280 panic ("can't access loop devices")
283 # find loop device assigned to thefile
286 for n in xrange(0, MAX_LOOP_DEVICES):
288 if os.access(dev, os.R_OK):
289 (stat, out) = run('losetup', dev)
290 if (out and stat == 0):
291 m = re.search(r'\((.*)\)', out[0])
292 if m and file == m.group(1):
298 # create file if necessary and assign the first free loop device
299 def init_loop(file, size, fstype):
300 dev = find_loop(file)
302 print 'WARNING file:', file, 'already mapped to', dev
304 if not os.access(file, os.R_OK | os.W_OK):
305 run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size, file))
307 # find next free loop
308 for n in xrange(0, MAX_LOOP_DEVICES):
310 if os.access(dev, os.R_OK):
311 (stat, out) = run('losetup', dev)
313 run('losetup', dev, file)
316 print "out of loop devices"
318 print "out of loop devices"
321 # undo loop assignment
322 def clean_loop(file):
323 dev = find_loop(file)
325 ret, out = run('losetup -d', dev)
327 log('unable to clean loop device:', dev, 'for file:', file)
330 # initialize a block device if needed
331 def block_dev(dev, size, fstype, format):
332 if isnotouch(): return dev
333 if not is_block(dev):
334 dev = init_loop(dev, size, fstype)
335 if (format == 'yes'):
339 # ============================================================
340 # Classes to prepare and cleanup the various objects
343 """ Base class for the rest of the modules. The default cleanup method is
344 defined here, as well as some utilitiy funcs.
346 def __init__(self, tag_name, node):
348 self.tag_name = tag_name
349 self.name = node.getAttribute('name')
350 self.uuid = node.getAttribute('uuid')
352 def info(self, *args):
353 msg = string.join(map(str,args))
354 print self.tag_name + ":", self.name, self.uuid, msg
357 """ default cleanup, used for most modules """
360 lctl.cleanup(self.name, self.uuid)
362 print "cleanup failed: ", self.name
364 class Network(Module):
365 def __init__(self,node):
366 Module.__init__(self, 'NETWORK', node)
367 self.net_type = node.getAttribute('type')
368 self.nid = getText(node, 'server', "")
369 self.port = int(getText(node, 'port', 0))
370 self.send_buf = int(getText(node, 'send_buf', 0))
371 self.read_buf = int(getText(node, 'read_buf', 0))
374 self.info(self.net_type, self.nid, self.port)
376 ret = run_daemon(TCP_ACCEPTOR, port)
379 raise CommandError, "cannot run acceptor"
380 lctl.network(self.net_type, self.nid)
381 lctl.newdev(attach = "ptlrpc RPCDEV")
384 self.info(self.net_type, self.nid, self.port)
386 lctl.cleanup("RPCDEV", "")
388 print "cleanup failed: ", self.name
390 # yikes, this ugly! need to save pid in /var/something
391 run("killall acceptor")
394 def __init__(self,node):
395 Module.__init__(self, 'LDLM', node)
398 lctl.newdev(attach="ldlm %s %s" % (self.name, self.uuid),
402 def __init__(self,node):
403 Module.__init__(self, 'LOV', node)
404 devs = node.getElementsByTagName('devices')[0]
405 self.stripe_sz = int(devs.getAttribute('stripesize'))
406 self.stripe_off = int(devs.getAttribute('stripeoffset'))
407 self.pattern = int(devs.getAttribute('pattern'))
408 mdcref = node.getElementsByTagName('mdc_ref')[0]
409 self.mdcuuid = mdcref.getAttribute('uuidref')
410 mdc= lookup(node.parentNode, self.mdcuuid)
411 mdsref = mdc.getElementsByTagName('mds_ref')[0]
412 self.mdsuuid = mdsref.getAttribute('uuidref')
413 mds= lookup(node.parentNode, self.mdsuuid)
414 self.mdsname = getName(mds)
417 for child in devs.childNodes:
418 if child.nodeName == 'osc_ref':
419 devlist = devlist + child.getAttribute('uuidref') + " "
420 strip_cnt = stripe_cnt + 1
421 self.devlist = devlist
422 self.stripe_cnt = strip_cnt
425 for osc_uuid in string.split(self.devlist):
426 osc = lookup(self.dom_node.parentNode, osc_uuid)
431 panic('osc not found:', osc_uuid)
432 self.info(self.mdcuuid, self.stripe_cnt, self.stripe_sz, self.stripe_off, self.pattern,
433 self.devlist, self.mdsname)
434 lctl.lovconfig(self.uuid, self.mdsname, self.stripe_cnt,
435 self.stripe_sz, self.stripe_off, self.pattern,
437 lctl.newdev(attach="lov %s %s" % (self.name, self.uuid),
438 setup ="%s" % (self.mdcuuid))
441 def __init__(self,node):
442 Module.__init__(self, 'MDS', node)
443 self.devname, self.size = getDevice(node)
444 self.fstype = getText(node, 'fstype')
445 self.format = getText(node, 'autoformat', "no")
448 self.info(self.devname, self.fstype, self.format)
449 blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
450 lctl.newdev(attach="mds %s %s" % (self.name, self.uuid),
451 setup ="%s %s" %(blkdev, self.fstype))
454 clean_loop(self.devname)
457 def __init__(self,node):
458 Module.__init__(self, 'MDC', node)
459 ref = node.getElementsByTagName('mds_ref')[0]
460 self.mds_uuid = ref.getAttribute('uuidref')
463 self.info(self.mds_uuid)
464 mds = lookup(self.dom_node.parentNode, self.mds_uuid)
466 panic(self.mdsuuid, "not found.")
467 net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
469 lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
470 lctl.newdev(attach="mdc %s %s" % (self.name, self.uuid),
471 setup ="%s %s" %(self.mds_uuid, srv.uuid))
474 self.info(self.mds_uuid)
475 net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
478 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
479 lctl.cleanup(self.name, self.uuid)
481 print "cleanup failed: ", self.name
484 def __init__(self, node):
485 Module.__init__(self, 'OBD', node)
486 self.obdtype = node.getAttribute('type')
487 self.devname, self.size = getDevice(node)
488 self.fstype = getText(node, 'fstype')
489 self.format = getText(node, 'autoformat', 'yes')
491 # need to check /proc/mounts and /etc/mtab before
492 # formatting anything.
493 # FIXME: check if device is already formatted.
495 self.info(self.obdtype, self.devname, self.size, self.fstype, self.format)
496 blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
497 lctl.newdev(attach="%s %s %s" % (self.obdtype, self.name, self.uuid),
498 setup ="%s %s" %(blkdev, self.fstype))
501 clean_loop(self.devname)
504 def __init__(self,node):
505 Module.__init__(self, 'OST', node)
506 ref = node.getElementsByTagName('obd_ref')[0]
507 self.obd_uuid = ref.getAttribute('uuidref')
510 self.info(self.obd_uuid)
511 lctl.newdev(attach="ost %s %s" % (self.name, self.uuid),
512 setup ="%s" % (self.obd_uuid))
515 def __init__(self,node):
516 Module.__init__(self, 'OSC', node)
517 ref = node.getElementsByTagName('obd_ref')[0]
518 self.obd_uuid = ref.getAttribute('uuidref')
519 ref = node.getElementsByTagName('ost_ref')[0]
520 self.ost_uuid = ref.getAttribute('uuidref')
523 self.info(self.obd_uuid, self.ost_uuid)
524 net = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
526 lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
527 lctl.newdev(attach="osc %s %s" % (self.name, self.uuid),
528 setup ="%s %s" %(self.obd_uuid, self.ost_uuid))
531 self.info(self.obd_uuid, self.ost_uuid)
532 net_uuid = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
535 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
536 lctl.cleanup(self.name, self.uuid)
538 print "cleanup failed: ", self.name
540 class Mountpoint(Module):
541 def __init__(self,node):
542 Module.__init__(self, 'MTPT', node)
543 self.path = getText(node, 'path')
544 ref = node.getElementsByTagName('mdc_ref')[0]
545 self.mdc_uuid = ref.getAttribute('uuidref')
546 ref = node.getElementsByTagName('osc_ref')[0]
547 self.lov_uuid = ref.getAttribute('uuidref')
550 self.info(self.path, self.mdc_uuid,self.lov_uuid)
551 cmd = "mount -t lustre_lite -o osc=%s,mdc=%s none %s" % \
552 (self.lov_uuid, self.mdc_uuid, self.path)
553 run("mkdir", self.path)
556 panic("mount failed:", self.path)
558 self.info(self.path, self.mdc_uuid,self.lov_uuid)
559 run("umount", self.path)
561 # ============================================================
562 # XML processing and query
563 # TODO: Change query funcs to use XPath, which is muc cleaner
566 dev = obd.getElementsByTagName('device')[0]
569 size = int(dev.getAttribute('size'))
572 return dev.firstChild.data, size
574 # Get the text content from the first matching child
575 def getText(node, tag, default=""):
576 list = node.getElementsByTagName(tag)
580 return node.firstChild.data
584 def get_ost_net(node, uuid):
585 path = '//*[@uuid="%s"]/network_ref' % (uuid)
586 ret = Evaluate(path, node)
588 uuid = ret[0].getAttribute('uuidref')
591 net = lookup(node, uuid)
595 # Recusively search from node for a uuid
596 def lookup(node, uuid):
597 path = '//*[@uuid="%s"]' % (uuid)
598 ret = Evaluate(path, node)
604 # would like to do performance test on these two lookups
605 #def lookup(node, uuid):
606 # for n in node.childNodes:
607 # if n.nodeType == n.ELEMENT_NODE:
608 # if getUUID(n) == uuid:
611 # n = lookup(n, uuid)
615 # Get name attribute of node
617 return node.getAttribute('name')
620 return node.getAttribute('uuidref')
622 # Get name attribute of node
624 return node.getAttribute('uuid')
626 # the tag name is the service type
627 # fixme: this should do some checks to make sure the node is a service
628 def getServiceType(node):
632 # determine what "level" a particular node is at.
633 # the order of iniitailization is based on level. objects
634 # are assigned a level based on type:
635 # net,devices,ldlm:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5
636 def getServiceLevel(node):
637 type = getServiceType(node)
638 if type in ('network',):
640 if type in ('device', 'ldlm'):
642 elif type in ('obd', 'mdd'):
644 elif type in ('mds','ost'):
646 elif type in ('mdc','osc'):
648 elif type in ('lov',):
650 elif type in ('mountpoint',):
655 # return list of services in a profile. list is a list of tuples
657 def getServices(lustreNode, profileNode):
659 for n in profileNode.childNodes:
660 if n.nodeType == n.ELEMENT_NODE:
661 servNode = lookup(lustreNode, getRef(n))
664 panic('service not found: ' + getRef(n))
665 level = getServiceLevel(servNode)
666 list.append((level, servNode))
670 def getByName(lustreNode, tag, name):
671 ndList = lustreNode.getElementsByTagName(tag)
673 if getName(nd) == name:
678 # ============================================================
681 def startService(node, cleanFlag):
682 type = getServiceType(node)
683 debug('Starting service:', type, getName(node), getUUID(node))
684 # there must be a more dynamic way of doing this...
690 elif type == 'network':
702 elif type == 'mountpoint':
705 panic ("unknown service type:", type)
713 # Prepare the system to run lustre using a particular profile
714 # in a the configuration.
715 # * load & the modules
716 # * setup networking for the current node
717 # * make sure partitions are in place and prepared
718 # * initialize devices with lctl
719 # Levels is important, and needs to be enforced.
720 def startProfile(lustreNode, profileNode, cleanFlag):
722 panic("profile:", profile, "not found.")
723 services = getServices(lustreNode, profileNode)
727 startService(s[1], cleanFlag)
731 def doHost(lustreNode, hosts, cleanFlag):
734 node = getByName(lustreNode, 'node', h)
739 print 'No host entry found.'
742 reflist = node.getElementsByTagName('profile')
743 for profile in reflist:
744 startProfile(lustreNode, profile, cleanFlag)
746 # Command line processing
748 def parse_cmdline(argv):
750 long_opts = ["ldap", "reformat", "lustre=", "verbose",
751 "portals=", "makeldiff", "cleanup", "iam=",
752 "help", "debug", "host=", "get="]
757 opts, args = getopt.getopt(argv, short_opts, long_opts)
758 except getopt.GetoptError:
763 if o in ("-h", "--help"):
766 options['cleanup'] = 1
767 if o in ("-v", "--verbose"):
768 options['verbose'] = 1
769 if o in ("-d", "--debug"):
771 options['verbose'] = 1
773 options['portals'] = a
775 options['lustre'] = a
776 if o == "--reformat":
777 options['reformat'] = 1
779 options['hostname'] = [a]
788 s = urllib.urlopen(url)
794 # Initialize or shutdown lustre according to a configuration file
795 # * prepare the system for lustre
796 # * configure devices with lctl
797 # Shutdown does steps in reverse
799 lctl = LCTLInterface('lctl')
802 args = parse_cmdline(sys.argv[1:])
804 dom = xml.dom.minidom.parse(args[0])
805 elif options.has_key('url'):
806 xmldata = fetch(options['url'])
807 dom = xml.dom.minidom.parseString(xmldata)
811 if not options.has_key('hostname'):
812 options['hostname'] = []
813 host = socket.gethostname()
815 options['hostname'].append(host)
816 options['hostname'].append('localhost')
817 print "configuring for host: ", options['hostname']
818 doHost(dom.documentElement, options['hostname'], options.has_key('cleanup') )
820 if __name__ == "__main__":