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 = '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 -v | --verbose Print system commands as they are run
48 -d | --debug Print system commands, but does not run them
49 --host <hostname> Load config for <hostname>
50 --cleanup Cleans up config. (Shutdown)
51 -h | --help Print this help
54 --ldap server LDAP server with lustre config database
55 --reformat Reformat all devices (will confirm)
56 --lustre="src dir" Base directory of lustre sources. Used to search
58 --portals=src Portals source
59 --makeldiff Translate xml source to LDIFF
64 # ============================================================
65 # debugging and error funcs
67 def fixme(msg = "this feature"):
68 raise RuntimeError, msg + ' not implmemented yet.'
71 msg = string.join(map(str,args))
73 raise RuntimeError, msg
76 msg = string.join(map(str,args))
84 msg = string.join(map(str,args))
85 if isverbose(): print msg
88 return options.has_key('verbose') and options['verbose'] == 1
91 return options.has_key('debug') and options['debug'] == 1
93 # ============================================================
94 # locally defined exceptions
95 class CommandError (exceptions.Exception):
96 def __init__(self, args=None):
99 # ============================================================
100 # handle lctl interface
103 Manage communication with lctl
106 def __init__(self, cmd):
108 Initialize close by finding the lctl binary.
110 syspath = string.split(os.environ['PATH'], ':')
111 syspath.insert(0, "../utils");
114 lctl = os.path.join(d,cmd)
115 if os.access(lctl, os.X_OK):
119 raise RuntimeError, "unable to find lctl binary."
124 the cmds are written to stdin of lctl
125 lctl doesn't return errors when run in script mode, so
127 should modify command line to accept multiple commands, or
128 create complex command line options
130 debug("+", self.lctl, cmds)
131 if isnotouch(): return ([], 0)
132 p = popen2.Popen3(self.lctl, 1)
133 p.tochild.write(cmds + "\n")
135 out = p.fromchild.readlines()
137 err = p.childerr.readlines()
139 log (self.lctl, "error:", ret)
141 raise CommandError, err
145 # create a new device with lctl
146 def network(self, net, nid):
153 # create a new connection
154 def connect(self, net, nid, port, servuuid):
159 quit""" % (net, nid, port, servuuid, nid)
162 # create a new device with lctl
163 def disconnect(self, net, nid, port, servuuid):
170 # create a new device with lctl
171 def newdev(self, attach, setup):
176 quit""" % (attach, setup)
180 def cleanup(self, name, uuid):
189 def lovconfig(self, uuid, mdcuuid, stripe_cnt, stripe_sz, pattern, devlist):
193 lovconfig %s %d %d %s %s
194 quit""" % (mdcuuid, uuid, stripe_cnt, stripe_sz, pattern, devlist)
197 # ============================================================
198 # Various system-level functions
199 # (ideally moved to their own module)
201 # Run a command and return the output and status.
202 # stderr is sent to /dev/null, could use popen3 to
203 # save it if necessary
205 cmd = string.join(map(str,args))
207 if isnotouch(): return ([], 0)
208 f = os.popen(cmd + ' 2>&1')
218 # is the path a block device?
225 return stat.S_ISBLK(s[stat.ST_MODE])
227 # build fs according to type
229 def mkfs(fstype, dev):
230 if(fstype == 'ext3'):
231 mkfs = 'mkfs.ext2 -j'
232 elif (fstype == 'extN'):
233 mkfs = 'mkfs.ext2 -j'
235 print 'unsupported fs type: ', fstype
236 if not is_block(dev):
240 run (mkfs, force, dev)
242 # some systems use /dev/loopN, some /dev/loop/N
246 if not os.access(loop + str(0), os.R_OK):
248 if not os.access(loop + str(0), os.R_OK):
249 panic ("can't access loop devices")
252 # find loop device assigned to thefile
255 for n in xrange(0, MAX_LOOP_DEVICES):
257 if os.access(dev, os.R_OK):
258 (stat, out) = run('losetup', dev)
259 if (out and stat == 0):
260 m = re.search(r'\((.*)\)', out[0])
261 if m and file == m.group(1):
267 # create file if necessary and assign the first free loop device
268 def init_loop(file, size, fstype):
269 dev = find_loop(file)
271 print 'WARNING file:', file, 'already mapped to', dev
273 if not os.access(file, os.R_OK | os.W_OK):
274 run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size, file))
276 # find next free loop
277 for n in xrange(0, MAX_LOOP_DEVICES):
279 if os.access(dev, os.R_OK):
280 (stat, out) = run('losetup', dev)
282 run('losetup', dev, file)
285 print "out of loop devices"
287 print "out of loop devices"
290 # undo loop assignment
291 def clean_loop(file):
292 dev = find_loop(file)
294 ret, out = run('losetup -d', dev)
296 log('unable to clean loop device:', dev, 'for file:', file)
299 # initialize a block device if needed
300 def block_dev(dev, size, fstype, format):
301 if isnotouch(): return dev
302 if not is_block(dev):
303 dev = init_loop(dev, size, fstype)
304 if (format == 'yes'):
308 # ============================================================
309 # Functions to prepare the various objects
311 def prepare_ldlm(node):
312 (name, uuid) = getNodeAttr(node)
313 print 'LDLM:', name, uuid
314 lctl.newdev(attach="ldlm %s %s" % (name, uuid),
317 def prepare_lov(node):
318 (name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname) = getLOVInfo(node)
319 print 'LOV:', name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname
320 lctl.lovconfig(uuid, mdsname, stripe_cnt, strip_sz, pattern, devlist)
321 lctl.newdev(attach="lov %s %s" % (name, uuid),
322 setup ="%s" % (mdcuuid))
324 def prepare_network(node):
325 (name, uuid, type, nid, port) = getNetworkInfo(node)
326 print 'NETWORK:', type, nid, port
328 run(TCP_ACCEPTOR, port)
329 lctl.network(type, nid)
332 # need to check /proc/mounts and /etc/mtab before
333 # formatting anything.
334 # FIXME: check if device is already formatted.
335 def prepare_obd(obd):
336 (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
337 print "OBD: ", name, obdtype, dev, size, fstype, format
338 dev = block_dev(dev, size, fstype, format)
339 lctl.newdev(attach="%s %s %s" % (obdtype, name, uuid),
340 setup ="%s %s" %(dev, fstype))
343 def prepare_ost(ost):
344 name, uuid, obd = getOSTInfo(ost)
345 print "OST: ", name, uuid, obd
346 lctl.newdev(attach="ost %s %s" % (name, uuid),
347 setup ="$%s" % (obd))
349 def prepare_mds(node):
350 (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
351 print "MDS: ", name, dev, size, fstype
352 # setup network for mds, too
353 dev = block_dev(dev, size, fstype, format)
354 lctl.newdev(attach="mds %s %s" % (name, uuid),
355 setup ="%s %s" %(dev, fstype))
357 def prepare_osc(node):
358 (name, uuid, obduuid, srvuuid) = getOSCInfo(node)
359 print 'OSC:', name, uuid, obduuid, srvuuid
360 net = lookup(node.parentNode, srvuuid)
361 srvname, srvuuid, net, server, port = getNetworkInfo(net)
362 lctl.connect(net, server, port, srvuuid)
363 lctl.newdev(attach="osc %s %s" % (name, uuid),
364 setup ="%s %s" %(obduuid, srvuuid))
366 def prepare_mdc(node):
367 (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
368 print 'MDC:', name, uuid, mdsuuid, netuuid
369 lctl.newdev(attach="mdc %s %s" % (name, uuid),
370 setup ="%s %s" %(mdsuuid, netuuid))
372 def prepare_mountpoint(node):
373 name, uuid, oscuuid, mdcuuid, mtpt = getMTPTInfo(node)
374 print 'MTPT:', name, uuid, oscuuid, mdcuuid, mtpt
375 cmd = "mount -t lustre_lite -o ost=%s,mds=%s none %s" % \
376 (oscuuid, mdcuuid, mtpt)
379 # ============================================================
380 # Functions to cleanup the various objects
382 def cleanup_ldlm(node):
383 (name, uuid) = getNodeAttr(node)
384 print 'LDLM:', name, uuid
386 lctl.cleanup(name, uuid)
388 print "cleanup failed: ", name
390 def cleanup_lov(node):
391 (name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname) = getLOVInfo(node)
392 print 'LOV:', name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist, mdsname
394 lctl.cleanup(name, uuid)
396 print "cleanup failed: ", name
398 def cleanup_network(node):
399 (name, uuid, type, nid, port) = getNetworkInfo(node)
400 print 'NETWORK:', type, nid, port
401 #lctl.network(type, nid)
403 # need to check /proc/mounts and /etc/mtab before
404 # formatting anything.
405 # FIXME: check if device is already formatted.
406 def cleanup_obd(obd):
407 (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
408 print "OBD: ", name, obdtype, dev, size, fstype, format
410 lctl.cleanup(name, uuid)
412 print "cleanup failed: ", name
415 def cleanup_ost(ost):
416 name, uuid, obd = getOSTInfo(ost)
417 print "OST: ", name, uuid, obd
419 lctl.cleanup(name, uuid)
421 print "cleanup failed: ", name
423 def cleanup_mds(node):
424 (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
425 print "MDS: ", name, dev, size, fstype
427 lctl.cleanup(name, uuid)
429 print "cleanup failed: ", name
433 def cleanup_mdc(node):
434 (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
435 print 'MDC:', name, uuid, mdsuuid, netuuid
437 lctl.cleanup(name, uuid)
439 print "cleanup failed: ", name
442 def cleanup_osc(node):
443 (name, uuid, obduuid, srvuuid) = getOSCInfo(node)
444 print 'OSC:', name, uuid, obduuid, srvuuid
445 net = lookup(node.parentNode, srvuuid)
446 netname, netuuid, net, server, port = getNetworkInfo(net)
448 lctl.disconnect(net, server, port, srvuuid)
449 lctl.cleanup(name, uuid)
451 print "cleanup failed: ", name
453 def cleanup_mountpoint(node):
454 name, uuid, oscuuid, mdcuuid, mtpt = getMTPTInfo(node)
455 print 'MTPT:', name, uuid, oscuuid, mdcuuid, mtpt
458 # ============================================================
459 # XML processing and query
462 dev = obd.getElementsByTagName('device')[0]
465 size = int(dev.getAttribute('size'))
468 return dev.firstChild.data, size
471 def getNetworkInfo(node):
472 name, uuid = getNodeAttr(node);
473 type = node.getAttribute('type')
474 nid = getText(node, 'server', "")
475 port = int(getText(node, 'port', 0))
476 return name, uuid, type, nid, port
478 # extract device attributes for an obd
479 def getNodeAttr(node):
480 name = node.getAttribute('name')
481 uuid = node.getAttribute('uuid')
485 name, uuid = getNodeAttr(obd);
486 obdtype = obd.getAttribute('type')
487 devname, size = getDevice(obd)
488 fstype = getText(obd, 'fstype')
489 format = getText(obd, 'autoformat')
490 return (name, uuid, obdtype, devname, size, fstype, format)
493 def getLOVInfo(node):
494 name, uuid = getNodeAttr(node)
495 devs = node.getElementsByTagName('devices')[0]
496 stripe_sz = int(devs.getAttribute('stripesize'))
497 pattern = int(devs.getAttribute('pattern'))
498 mdcref = node.getElementsByTagName('mdc_ref')[0]
499 mdcuuid = mdcref.getAttribute('uuidref')
500 mdc= lookup(node.parentNode, mdcuuid)
501 mdsref = mdc.getElementsByTagName('mds_ref')[0]
502 mdsuuid = mdsref.getAttribute('uuidref')
503 mds= lookup(node.parentNode, mdsuuid)
504 mdsname = getName(mds)
507 for child in devs.childNodes:
508 if child.nodeName == 'osc_ref':
509 devlist = devlist + child.getAttribute('uuidref') + " "
510 strip_cnt = stripe_cnt + 1
511 return (name, uuid, mdcuuid, stripe_cnt, stripe_sz, pattern, devlist, mdsname)
513 # extract device attributes for an obd
514 def getMDSInfo(node):
515 name, uuid = getNodeAttr(node)
516 devname, size = getDevice(node)
517 fstype = getText(node, 'fstype')
518 format = getText(node, 'autoformat', "no")
519 return (name, uuid, devname, size, fstype, format)
521 # extract device attributes for an obd
522 def getMDCInfo(node):
523 name, uuid = getNodeAttr(node)
524 ref = node.getElementsByTagName('mds_ref')[0]
525 mdsuuid = ref.getAttribute('uuidref')
526 ref = node.getElementsByTagName('network_ref')[0]
527 netuuid = ref.getAttribute('uuidref')
528 return (name, uuid, mdsuuid, netuuid)
531 # extract device attributes for an obd
532 def getOSTInfo(node):
533 name, uuid = getNodeAttr(node)
534 ref = node.getElementsByTagName('obd_ref')[0]
535 uuid = ref.getAttribute('uuidref')
536 obd = lookup(node.parentNode, uuid)
538 obdname = getOBDInfo(obd)[0]
540 obdname = "OBD NOT FOUND"
541 return (name, uuid, obdname)
543 # extract device attributes for an obd
544 def getOSCInfo(node):
545 name, uuid = getNodeAttr(node)
546 ref = node.getElementsByTagName('obd_ref')[0]
547 obduuid = ref.getAttribute('uuidref')
548 ref = node.getElementsByTagName('network_ref')[0]
549 ostuuid = ref.getAttribute('uuidref')
550 return (name, uuid, obduuid, ostuuid)
552 # extract device attributes for an obd
553 def getMTPTInfo(node):
554 name, uuid = getNodeAttr(node)
555 path = getText(node, 'path')
556 ref = node.getElementsByTagName('mdc_ref')[0]
557 mdcuuid = ref.getAttribute('uuidref')
558 ref = node.getElementsByTagName('lov_ref')[0]
559 lovuuid = ref.getAttribute('uuidref')
560 return (name, uuid, lovuuid, mdcuuid, path)
563 # Get the text content from the first matching child
564 def getText(node, tag, default=""):
565 list = node.getElementsByTagName(tag)
569 return node.firstChild.data
573 # Recusively search from node for a uuid
574 def lookup(node, uuid):
575 for n in node.childNodes:
576 # this service_id check is ugly. need some other way to
577 # differentiate between definitions and references
578 if n.nodeType == n.ELEMENT_NODE:
579 if getUUID(n) == uuid:
586 # Get name attribute of node
588 return node.getAttribute('name')
591 return node.getAttribute('uuidref')
593 # Get name attribute of node
595 return node.getAttribute('uuid')
597 # the tag name is the service type
598 # fixme: this should do some checks to make sure the node is a service
599 def getServiceType(node):
603 # determine what "level" a particular node is at.
604 # the order of iniitailization is based on level. objects
605 # are assigned a level based on type:
606 # net,devices:1, obd, mdd:2 mds,ost:3 osc,mdc:4 mounts:5
607 def getServiceLevel(node):
608 type = getServiceType(node)
609 if type in ('network', 'device', 'ldlm'):
611 elif type in ('obd', 'mdd'):
613 elif type in ('mds','ost'):
615 elif type in ('mdc','osc'):
617 elif type in ('lov',):
619 elif type in ('mountpoint',):
624 # return list of services in a profile. list is a list of tuples
626 def getServices(lustreNode, profileNode):
628 for n in profileNode.childNodes:
629 if n.nodeType == n.ELEMENT_NODE:
630 servNode = lookup(lustreNode, getRef(n))
632 panic('service not found: ' + getName(n))
633 level = getServiceLevel(servNode)
634 list.append((level, servNode))
638 def getByName(lustreNode, tag, name):
639 ndList = lustreNode.getElementsByTagName(tag)
641 if getName(nd) == name:
646 # ============================================================
647 # lconf type level logic
652 def startService(node):
653 type = getServiceType(node)
654 debug('Starting service:', type, getName(node), getUUID(node))
655 # there must be a more dynamic way of doing this...
660 elif type == 'network':
661 prepare_network(node)
672 elif type == 'mountpoint':
673 prepare_mountpoint(node)
676 # Prepare the system to run lustre using a particular profile
677 # in a the configuration.
678 # * load & the modules
679 # * setup networking for the current node
680 # * make sure partitions are in place and prepared
681 # * initialize devices with lctl
682 # Levels is important, and needs to be enforced.
683 def startProfile(lustreNode, profileNode):
685 panic("profile:", profile, "not found.")
686 services = getServices(lustreNode, profileNode)
692 def stopService(node):
693 type = getServiceType(node)
694 debug('Stopping service:', type, getName(node), getUUID(node))
695 # there must be a more dynamic way of doing this...
700 elif type == 'network':
701 cleanup_network(node)
712 elif type == 'mountpoint':
713 cleanup_mountpoint(node)
715 # Shutdown services in reverse order than they were started
716 def cleanupProfile(lustreNode, profileNode):
718 panic("profile:", profile, "not found.")
719 services = getServices(lustreNode, profileNode)
727 def doHost(lustreNode, hosts, cleanFlag):
729 node = getByName(lustreNode, 'node', h)
733 reflist = node.getElementsByTagName('profile_ref')
736 cleanupProfile(lustreNode, lookup(lustreNode, getRef(r)))
738 startProfile(lustreNode, lookup(lustreNode, getRef(r)))
741 # Command line processing
743 def parse_cmdline(argv):
745 long_opts = ["ldap", "reformat", "lustre=",
746 "portals=", "makeldiff", "cleanup", "iam=",
747 "help", "debug", "host="]
752 opts, args = getopt.getopt(argv, short_opts, long_opts)
753 except getopt.GetoptError:
758 if o in ("-h", "--help"):
761 options['cleanup'] = 1
762 if o in ("-v", "--verbose"):
763 options['verbose'] = 1
764 if o in ("-d", "--debug"):
766 options['verbose'] = 1
768 options['portals'] = a
770 options['lustre'] = a
771 if o == "--reformat":
772 options['reformat'] = 1
774 options['hostname'] = [a]
778 # Initialize or shutdown lustre according to a configuration file
779 # * prepare the system for lustre
780 # * configure devices with lctl
781 # Shutdown does steps in reverse
783 lctl = LCTLInterface('lctl')
786 args = parse_cmdline(sys.argv[1:])
788 dom = xml.dom.minidom.parse(args[0])
792 if not options.has_key('hostname'):
793 ret, host = run('hostname')
795 print "unable to determine hostname"
797 options['hostname'] = [host]
798 options['hostname'].append('localhost')
799 doHost(dom.childNodes[0], options['hostname'], options.has_key('cleanup') )
801 if __name__ == "__main__":