Whamcloud - gitweb
* llecho.sh reimplemnted to use lmc/lconf
[fs/lustre-release.git] / lustre / utils / lconf
1 #!/usr/bin/env python
2 #
3 #  Copyright (C) 2002 Cluster File Systems, Inc.
4 #   Author: Robert Read <rread@clusterfs.com>
5
6 #   This file is part of Lustre, http://www.lustre.org.
7 #
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.
11 #
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.
16 #
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.
20 #
21 # lconf - lustre configuration tool
22 #
23 # lconf is the main driver script for starting and stopping
24 # lustre filesystem services.
25 #
26 # Based in part on the XML obdctl modifications done by Brian Behlendorf 
27
28 import sys, getopt
29 import string, os, stat, popen2, socket, time
30 import re, exceptions
31 import xml.dom.minidom
32
33 # Global parameters
34 TCP_ACCEPTOR = ''
35
36 #
37 # Maximum number of devices to search for.
38 # (the /dev/loop* nodes need to be created beforehand)
39 MAX_LOOP_DEVICES = 256
40
41
42 def usage():
43     print """usage: lconf config.xml
44
45 config.xml          Lustre configuration in xml format.
46 --get <url>         URL to fetch a config file
47 --node <nodename>   Load config for <nodename>
48 -d | --cleanup      Cleans up config. (Shutdown)
49 -v | --verbose      Print system commands as they are run
50 -h | --help         Print this help 
51 --gdb               Prints message after creating gdb module script
52                     and sleeps for 5 seconds.
53 -n | --noexec       Prints the commands and steps that will be run for a
54                     config without executing them. This can used to check if a
55                     config file is doing what it should be doing. (Implies -v)
56 --nomod             Skip load/unload module step.
57 --nosetup           Skip device setup/cleanup step.
58 """
59     TODO = """
60 --ldap server       LDAP server with lustre config database
61 --makeldiff         Translate xml source to LDIFF 
62 --reformat          Reformat all devices (will confirm)
63 This are perhaps not needed:
64 --lustre="src dir"  Base directory of lustre sources. Used to search
65                     for modules.
66 --portals=src       Portals source 
67 """
68     sys.exit()
69
70 # ============================================================
71 # Config parameters, encapsulated in a class
72 class Config:
73     def __init__(self):
74         # flags
75         self._noexec = 0
76         self._verbose = 0
77         self._reformat = 0
78         self._cleanup = 0
79         self._gdb = 0
80         self._nomod = 0
81         self._nosetup = 0
82         # parameters
83         self._modules = None
84         self._node = None
85         self._url = None
86         self._gdb_script = '/tmp/ogdb'
87         self._debug_path = '/tmp/lustre-log'
88         self._src_dir = None
89
90     def verbose(self, flag = None):
91         if flag: self._verbose = flag
92         return self._verbose
93
94     def noexec(self, flag = None):
95         if flag: self._noexec = flag
96         return self._noexec
97
98     def reformat(self, flag = None):
99         if flag: self._reformat = flag
100         return self._reformat
101
102     def cleanup(self, flag = None):
103         if flag: self._cleanup = flag
104         return self._cleanup
105
106     def gdb(self, flag = None):
107         if flag: self._gdb = flag
108         return self._gdb
109
110     def nomod(self, flag = None):
111         if flag: self._nomod = flag
112         return self._nomod
113
114     def nosetup(self, flag = None):
115         if flag: self._nosetup = flag
116         return self._nosetup
117
118     def node(self, val = None):
119         if val: self._node = val
120         return self._node
121
122     def url(self, val = None):
123         if val: self._url = val
124         return self._url
125
126     def gdb_script(self):
127         if os.path.isdir('/r'):
128             return '/r' + self._gdb_script
129         else:
130             return self._gdb_script
131
132     def debug_path(self):
133         if os.path.isdir('/r'):
134             return '/r' + self._debug_path
135         else:
136             return self._debug_path
137
138     def src_dir(self, val = None):
139         if val: self._url = val
140         return self._url
141
142 config = Config()
143
144 # ============================================================ 
145 # debugging and error funcs
146
147 def fixme(msg = "this feature"):
148     raise LconfError, msg + ' not implmemented yet.'
149
150 def panic(*args):
151     msg = string.join(map(str,args))
152     print msg
153     if not config.noexec():
154         raise LconfError(msg)
155
156 def log(*args):
157     msg = string.join(map(str,args))
158     print msg
159
160 def logall(msgs):
161     for s in msgs:
162         print string.strip(s)
163
164 def debug(*args):
165     if config.verbose():
166         msg = string.join(map(str,args))
167         print msg
168
169 # ============================================================
170 # locally defined exceptions
171 class CommandError (exceptions.Exception):
172     def __init__(self, cmd_name, cmd_err, rc=None):
173         self.cmd_name = cmd_name
174         self.cmd_err = cmd_err
175         self.rc = rc
176
177     def dump(self):
178         import types
179         if type(self.cmd_err) == types.StringType:
180             if self.rc:
181                 print "! %s (%d): %s" % (self.cmd_name, self.rc, self.cmd_err)
182             else:
183                 print "! %s: %s" % (self.cmd_name, self.cmd_err)
184         elif type(self.cmd_err) == types.ListType:
185             if self.rc:
186                 print "! %s (error %d):" % (self.cmd_name, self.rc)
187             else:
188                 print "! %s:" % (self.cmd_name)
189             for s in self.cmd_err:
190                 print "> %s" %(string.strip(s))
191         else:
192             print self.cmd_err
193
194 class LconfError (exceptions.Exception):
195     def __init__(self, args):
196         self.args
197
198
199 # ============================================================
200 # handle lctl interface
201 class LCTLInterface:
202     """
203     Manage communication with lctl
204     """
205
206     def __init__(self, cmd):
207         """
208         Initialize close by finding the lctl binary.
209         """
210         self.lctl = find_prog(cmd)
211         if not self.lctl:
212             if config.noexec():
213                 debug('! lctl not found')
214                 self.lctl = 'lctl'
215             else:
216                 raise CommandError('lctl', "unable to find lctl binary.")
217             
218     def run(self, cmds):
219         """
220         run lctl
221         the cmds are written to stdin of lctl
222         lctl doesn't return errors when run in script mode, so
223         stderr is checked
224         should modify command line to accept multiple commands, or
225         create complex command line options
226         """
227         debug("+", self.lctl, cmds)
228         if config.noexec(): return (0, [])
229         p = popen2.Popen3(self.lctl, 1)
230         p.tochild.write(cmds + "\n")
231         p.tochild.close()
232         out = p.fromchild.readlines()
233         err = p.childerr.readlines()
234         ret = p.wait()
235         if ret or len(err):
236             raise CommandError(self.lctl, err, ret)
237         return ret, out
238
239             
240     def network(self, net, nid):
241         """ initialized network and add "self" """
242         # Idea: "mynid" could be used for all network types to add "self," and then
243         # this special case would be gone and the "self" hack would be hidden.
244         if net  == 'tcp':
245             cmds =  """
246   network %s
247   mynid %s
248   add_uuid self %s
249   quit""" % (net, nid, nid)
250         else:
251             cmds =  """
252   network %s
253   add_uuid self %s
254   quit""" % (net, nid)
255             
256         self.run(cmds)
257
258     # create a new connection 
259     def connect(self, net, nid, port, servuuid, send_buf, read_buf):
260         # XXX: buf size params not used yet
261         cmds =  """
262   network %s
263   connect %s %d
264   add_uuid %s %s
265   quit""" % (net, nid, port,  servuuid, nid)
266         self.run(cmds)
267                 
268     # create a new connection 
269     def add_route(self, net, to, via):
270         cmds =  """
271         """ 
272         #self.run(cmds)
273
274     # disconnect one connection
275     def disconnect(self, net, nid, port, servuuid):
276         cmds =  """
277   network %s
278   disconnect %s 
279   del_uuid %s
280   quit""" % (net, nid, servuuid)
281         self.run(cmds)
282
283     # disconnect all connections
284     def disconnectAll(self, net):
285         cmds =  """
286   network %s
287   disconnect
288   del_uuid self
289   quit""" % (net)
290         self.run(cmds)
291
292     # create a new device with lctl
293     def newdev(self, attach, setup = ""):
294         cmds = """
295   newdev
296   attach %s
297   setup %s
298   quit""" % (attach, setup)
299         self.run(cmds)
300
301     # cleanup a device
302     def cleanup(self, name, uuid):
303         cmds = """
304   device $%s
305   cleanup
306   detach
307   quit""" % (name)
308         self.run(cmds)
309
310     # create an lov
311     def lovconfig(self, uuid, mdsuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist):
312         cmds = """
313   device $%s
314   probe
315   lovconfig %s %d %d %d %s %s
316   quit""" % (mdsuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
317         self.run(cmds)
318
319 # ============================================================
320 # Various system-level functions
321 # (ideally moved to their own module)
322
323 # Run a command and return the output and status.
324 # stderr is sent to /dev/null, could use popen3 to
325 # save it if necessary
326 def run(*args):
327     cmd = string.join(map(str,args))
328     debug ("+", cmd)
329     if config.noexec(): return (0, [])
330     f = os.popen(cmd + ' 2>&1')
331     out = f.readlines()
332     ret = f.close()
333     if ret:
334         ret = ret >> 8
335     else:
336         ret = 0
337     return (ret, out)
338
339 # Run a command in the background.
340 def run_daemon(*args):
341     cmd = string.join(map(str,args))
342     debug ("+", cmd)
343     if config.noexec(): return 0
344     f = os.popen(cmd + ' 2>&1')
345     ret = f.close()
346     if ret:
347         ret = ret >> 8
348     else:
349         ret = 0
350     return ret
351
352 # Determine full path to use for an external command
353 # searches dirname(argv[0]) first, then PATH
354 def find_prog(cmd):
355     syspath = string.split(os.environ['PATH'], ':')
356     cmdpath = os.path.dirname(sys.argv[0])
357     syspath.insert(0, cmdpath);
358     syspath.insert(0, os.path.join(cmdpath, '../../portals/linux/utils/'))
359     for d in syspath:
360         prog = os.path.join(d,cmd)
361         if os.access(prog, os.X_OK):
362             return prog
363     return ''
364
365 # Recursively look for file starting at base dir
366 def do_find_file(base, mod):
367     fullname = os.path.join(base, mod)
368     if os.access(fullname, os.R_OK):
369         return fullname
370     for d in os.listdir(base):
371         dir = os.path.join(base,d)
372         if os.path.isdir(dir):
373             module = do_find_file(dir, mod)
374             if module:
375                 return module
376
377 def find_module(src_dir, modname):
378     mod = '%s.o' % (modname)
379     search = (src_dir + "/lustre", src_dir + "/portals/linux")
380     for d in search:
381         try:
382             module = do_find_file(d, mod)
383             if module:
384                 return module
385         except OSError:
386             pass
387     return None
388
389 # is the path a block device?
390 def is_block(path):
391     s = ()
392     try:
393         s =  os.stat(path)
394     except OSError:
395         return 0
396     return stat.S_ISBLK(s[stat.ST_MODE])
397
398 # build fs according to type
399 # fixme: dangerous
400 def mkfs(fstype, dev):
401     if(fstype in ('ext3', 'extN')):
402         mkfs = 'mkfs.ext2 -j -b 4096'
403     else:
404         print 'unsupported fs type: ', fstype
405     if not is_block(dev):
406         force = '-F'
407     else:
408         force = ''
409     (ret, out) = run (mkfs, force, dev)
410     if ret:
411         panic("Unable to build fs:", dev)
412     # enable hash tree indexing on fs
413     if fstype == 'extN':
414         htree = 'echo "feature FEATURE_C5" | debugfs -w'
415         (ret, out) = run (htree, dev)
416         if ret:
417             panic("Unable to enable htree:", dev)
418
419 # some systems use /dev/loopN, some /dev/loop/N
420 def loop_base():
421     import re
422     loop = '/dev/loop'
423     if not os.access(loop + str(0), os.R_OK):
424         loop = loop + '/'
425         if not os.access(loop + str(0), os.R_OK):
426             panic ("can't access loop devices")
427     return loop
428     
429 # find loop device assigned to thefile
430 def find_loop(file):
431     loop = loop_base()
432     for n in xrange(0, MAX_LOOP_DEVICES):
433         dev = loop + str(n)
434         if os.access(dev, os.R_OK):
435             (stat, out) = run('losetup', dev)
436             if (out and stat == 0):
437                 m = re.search(r'\((.*)\)', out[0])
438                 if m and file == m.group(1):
439                     return dev
440         else:
441             break
442     return ''
443
444 # create file if necessary and assign the first free loop device
445 def init_loop(file, size, fstype):
446     dev = find_loop(file)
447     if dev:
448         print 'WARNING file:', file, 'already mapped to', dev
449         return dev
450     if not os.access(file, os.R_OK | os.W_OK):
451         run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size,  file))
452     loop = loop_base()
453     # find next free loop
454     for n in xrange(0, MAX_LOOP_DEVICES):
455         dev = loop + str(n)
456         if os.access(dev, os.R_OK):
457             (stat, out) = run('losetup', dev)
458             if (stat):
459                 run('losetup', dev, file)
460                 return dev
461         else:
462             print "out of loop devices"
463             return ''
464     print "out of loop devices"
465     return ''
466
467 # undo loop assignment
468 def clean_loop(file):
469     dev = find_loop(file)
470     if dev:
471         ret, out = run('losetup -d', dev)
472         if ret:
473             log('unable to clean loop device:', dev, 'for file:', file)
474             logall(out)
475
476 # initialize a block device if needed
477 def block_dev(dev, size, fstype, format):
478     if config.noexec(): return dev
479     if not is_block(dev):
480         dev = init_loop(dev, size, fstype)
481     if (format == 'yes'):
482         mkfs(fstype, dev)
483     return dev
484
485 def get_local_address(net_type):
486     """Return the local address for the network type."""
487     local = ""
488     if net_type == 'tcp':
489         # host `hostname`
490         host = socket.gethostname()
491         local = socket.gethostbyname(host)
492     elif net_type == 'elan':
493         # awk '/NodeId/ { print $2 }' '/proc/elan/device0/position'
494         try:
495             fp = open('/proc/elan/device0/position', 'r')
496             lines = fp.readlines()
497             fp.close()
498             for l in lines:
499                 a = string.split(l)
500                 if a[0] == 'NodeId':
501                     local = a[1]
502                     break
503         except IOError, e:
504             log(e)
505     elif net_type == 'gm':
506         fixme("automatic local address for GM")
507     return local
508         
509         
510
511 # ============================================================
512 # Classes to prepare and cleanup the various objects
513 #
514 class Module:
515     """ Base class for the rest of the modules. The default cleanup method is
516     defined here, as well as some utilitiy funcs.
517     """
518     def __init__(self, tag_name, node):
519         self.dom_node = node
520         self.tag_name = tag_name
521         self.name = node.getAttribute('name')
522         self.uuid = node.getAttribute('uuid')
523         self.kmodule_list = []
524         
525     def info(self, *args):
526         msg = string.join(map(str,args))
527         print self.tag_name + ":", self.name, self.uuid, msg
528
529     def cleanup(self):
530         """ default cleanup, used for most modules """
531         self.info()
532         try:
533             lctl.cleanup(self.name, self.uuid)
534         except CommandError:
535             print "cleanup failed: ", self.name
536
537     def add_module(self, modname):
538         """Append a module to list of modules to load."""
539         self.kmodule_list.append(modname)
540
541     def mod_loaded(self, modname):
542         """Check if a module is already loaded. Look in /proc/modules for it."""
543         fp = open('/proc/modules')
544         lines = fp.readlines()
545         fp.close()
546         # please forgive my tired fingers for this one
547         ret = filter(lambda word, mod=modname: word == mod,
548                      map(lambda line: string.split(line)[0], lines))
549         return ret
550
551     def load_module(self):
552         """Load all the modules in the list in the order they appear."""
553         for mod in self.kmodule_list:
554             #  (rc, out) = run ('/sbin/lsmod | grep -s', mod)
555             if self.mod_loaded(mod) and not config.noexec():
556                 continue
557             log ('loading module:', mod)
558             if config.src_dir():
559                 module = find_module(config.src_dir(), mod)
560                 if not module:
561                     panic('module not found:', mod)
562                 (rc, out)  = run('/sbin/insmod', module)
563                 if rc:
564                     raise CommandError('insmod', out, rc)
565             else:
566                 (rc, out) = run('/sbin/modprobe', mod)
567                 if rc:
568                     raise CommandError('modprobe', out, rc)
569             
570     def cleanup_module(self):
571         """Unload the modules in the list in reverse order."""
572         rev = self.kmodule_list
573         rev.reverse()
574         for mod in rev:
575             log('unloading module:', mod)
576             if config.noexec():
577                 continue
578             (rc, out) = run('/sbin/rmmod', mod)
579             if rc:
580                 log('! unable to unload module:', mod)
581                 logall(out)
582         
583
584 class Network(Module):
585     def __init__(self,node):
586         Module.__init__(self, 'NETWORK', node)
587         self.net_type = node.getAttribute('type')
588         self.nid = getText(node, 'server', '*')
589         self.port = int(getText(node, 'port', 0))
590         self.send_buf = int(getText(node, 'send_buf', 0))
591         self.read_buf = int(getText(node, 'read_buf', 0))
592         if self.nid == '*':
593             self.nid = get_local_address(self.net_type)
594             if not self.nid:
595                 panic("unable to set nid for", self.net_type)
596
597         self.add_module('portals')
598         if self.net_type == 'tcp':
599             self.add_module('ksocknal')
600         if self.net_type == 'elan':
601             self.add_module('kqswnal')
602         if self.net_type == 'gm':
603             self.add_module('kgmnal')
604         self.add_module('obdclass')
605         self.add_module('ptlrpc')
606
607     def prepare(self):
608         self.info(self.net_type, self.nid, self.port)
609         if self.net_type == 'tcp':
610             ret = run_daemon(TCP_ACCEPTOR, self.port)
611             if ret:
612                 raise CommandError(TCP_ACCEPTOR, 'failed', ret)
613         lctl.network(self.net_type, self.nid)
614         lctl.newdev(attach = "ptlrpc RPCDEV")
615
616     def cleanup(self):
617         self.info(self.net_type, self.nid, self.port)
618         try:
619             lctl.cleanup("RPCDEV", "")
620             lctl.disconnectAll(self.net_type)
621         except CommandError:
622             print "cleanup failed: ", self.name
623         if self.net_type == 'tcp':
624             # yikes, this ugly! need to save pid in /var/something
625             run("killall acceptor")
626
627 class LDLM(Module):
628     def __init__(self,node):
629         Module.__init__(self, 'LDLM', node)
630         self.add_module('ldlm')
631     def prepare(self):
632         self.info()
633         lctl.newdev(attach="ldlm %s %s" % (self.name, self.uuid),
634                     setup ="")
635
636 class LOV(Module):
637     def __init__(self,node):
638         Module.__init__(self, 'LOV', node)
639         devs = node.getElementsByTagName('devices')[0]
640         self.stripe_sz = int(devs.getAttribute('stripesize'))
641         self.stripe_off = int(devs.getAttribute('stripeoffset'))
642         self.pattern = int(devs.getAttribute('pattern'))
643         mdsref =  node.getElementsByTagName('mds_ref')[0]
644         self.mdsuuid = mdsref.getAttribute('uuidref')
645         mds= lookup(node.parentNode, self.mdsuuid)
646         self.mdsname = getName(mds)
647         devlist = ""
648         stripe_cnt = 0
649         for child in devs.childNodes:
650             if child.nodeName == 'osc_ref':
651                 devlist = devlist +  child.getAttribute('uuidref') + " "
652                 stripe_cnt = stripe_cnt + 1
653         self.devlist = devlist
654         self.stripe_cnt = stripe_cnt
655         self.add_module('osc')
656         self.add_module('lov')
657
658     def prepare(self):
659         self.info(self.mdsuuid, self.stripe_cnt, self.stripe_sz, self.stripe_off, self.pattern,
660         self.devlist, self.mdsname)
661         lctl.lovconfig(self.uuid, self.mdsname, self.stripe_cnt,
662                        self.stripe_sz, self.stripe_off, self.pattern,
663                        self.devlist)
664
665     def cleanup(self):
666         pass
667
668 class MDS(Module):
669     def __init__(self,node):
670         Module.__init__(self, 'MDS', node)
671         self.devname, self.size = getDevice(node)
672         self.fstype = getText(node, 'fstype')
673         self.format = getText(node, 'autoformat', "no")
674         if self.fstype == 'extN':
675             self.add_module('extN') 
676         self.add_module('mds')
677         self.add_module('mds_%s' % (self.fstype))
678             
679     def prepare(self):
680         self.info(self.devname, self.fstype, self.format)
681         blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
682         lctl.newdev(attach="mds %s %s" % (self.name, self.uuid),
683                     setup ="%s %s" %(blkdev, self.fstype))
684     def cleanup(self):
685         Module.cleanup(self)
686         clean_loop(self.devname)
687
688 class MDC(Module):
689     def __init__(self,node):
690         Module.__init__(self, 'MDC', node)
691         ref = node.getElementsByTagName('mds_ref')[0]
692         self.mds_uuid = ref.getAttribute('uuidref')
693         self.add_module('mdc')
694
695     def prepare(self):
696         self.info(self.mds_uuid)
697         mds = lookup(self.dom_node.parentNode, self.mds_uuid)
698         if mds == None:
699             panic(self.mdsuuid, "not found.")
700         net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
701         srv = Network(net)
702         lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
703         lctl.newdev(attach="mdc %s %s" % (self.name, self.uuid),
704                         setup ="%s %s" %(self.mds_uuid, srv.uuid))
705             
706     def cleanup(self):
707         self.info(self.mds_uuid)
708         net = get_ost_net(self.dom_node.parentNode, self.mds_uuid)
709         srv = Network(net)
710         try:
711             lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
712         except CommandError:
713             print "disconnect failed: ", self.name
714         try:
715             lctl.cleanup(self.name, self.uuid)
716         except CommandError:
717             print "cleanup failed: ", self.name
718
719 class OBD(Module):
720     def __init__(self, node):
721         Module.__init__(self, 'OBD', node)
722         self.obdtype = node.getAttribute('type')
723         self.devname, self.size = getDevice(node)
724         self.fstype = getText(node, 'fstype')
725         self.format = getText(node, 'autoformat', 'yes')
726         if self.fstype == 'extN':
727             self.add_module('extN') 
728         self.add_module(self.obdtype)
729
730     # need to check /proc/mounts and /etc/mtab before
731     # formatting anything.
732     # FIXME: check if device is already formatted.
733     def prepare(self):
734         self.info(self.obdtype, self.devname, self.size, self.fstype, self.format)
735         if self.obdtype == 'obdecho':
736             blkdev = ''
737         else:
738             blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
739         lctl.newdev(attach="%s %s %s" % (self.obdtype, self.name, self.uuid),
740                     setup ="%s %s" %(blkdev, self.fstype))
741     def cleanup(self):
742         Module.cleanup(self)
743         if not self.obdtype == 'obdecho':
744             clean_loop(self.devname)
745
746 class OST(Module):
747     def __init__(self,node):
748         Module.__init__(self, 'OST', node)
749         ref = node.getElementsByTagName('obd_ref')[0]
750         self.obd_uuid = ref.getAttribute('uuidref')
751         self.add_module('ost')
752
753     def prepare(self):
754         self.info(self.obd_uuid)
755         lctl.newdev(attach="ost %s %s" % (self.name, self.uuid),
756                     setup ="%s" % (self.obd_uuid))
757
758 class OSC(Module):
759     def __init__(self,node):
760         Module.__init__(self, 'OSC', node)
761         ref = node.getElementsByTagName('obd_ref')[0]
762         self.obd_uuid = ref.getAttribute('uuidref')
763         ref = node.getElementsByTagName('ost_ref')[0]
764         self.ost_uuid = ref.getAttribute('uuidref')
765         self.add_module('osc')
766
767     def prepare(self):
768         self.info(self.obd_uuid, self.ost_uuid)
769         net = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
770         srv = Network(net)
771         lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_buf, srv.read_buf)
772         lctl.newdev(attach="osc %s %s" % (self.name, self.uuid),
773                     setup ="%s %s" %(self.obd_uuid, srv.uuid))
774
775     def cleanup(self):
776         self.info(self.obd_uuid, self.ost_uuid)
777         net_uuid = get_ost_net(self.dom_node.parentNode, self.ost_uuid)
778         srv = Network(net_uuid)
779         try:
780             lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
781         except CommandError:
782             print "disconnect failed: ", self.name
783         try:
784             lctl.cleanup(self.name, self.uuid)
785         except CommandError:
786             print "cleanup failed: ", self.name
787
788 class Mountpoint(Module):
789     def __init__(self,node):
790         Module.__init__(self, 'MTPT', node)
791         self.path = getText(node, 'path')
792         ref = node.getElementsByTagName('mdc_ref')[0]
793         self.mdc_uuid = ref.getAttribute('uuidref')
794         ref = node.getElementsByTagName('osc_ref')[0]
795         self.lov_uuid = ref.getAttribute('uuidref')
796         self.add_module('osc')
797         self.add_module('llite')
798
799     def prepare(self):
800         l = lookup(self.dom_node.parentNode, self.lov_uuid)
801         if l.nodeName == 'lov':
802             lov = LOV(l)
803             for osc_uuid in string.split(lov.devlist):
804                 osc = lookup(self.dom_node.parentNode, osc_uuid)
805                 if osc:
806                     n = OSC(osc)
807                     n.prepare()
808                 else:
809                     panic('osc not found:', osc_uuid)
810             lctl.newdev(attach="lov %s %s" % (lov.name, lov.uuid),
811                         setup ="%s" % (self.mdc_uuid))
812         else:
813             osc = OSC(l)
814             osc.prepare()
815             
816         self.info(self.path, self.mdc_uuid,self.lov_uuid)
817         cmd = "mount -t lustre_lite -o osc=%s,mdc=%s none %s" % \
818               (self.lov_uuid, self.mdc_uuid, self.path)
819         run("mkdir", self.path)
820         ret, val = run(cmd)
821         if ret:
822             panic("mount failed:", self.path)
823     def cleanup(self):
824         self.info(self.path, self.mdc_uuid,self.lov_uuid)
825         run("umount", self.path)
826         l = lookup(self.dom_node.parentNode, self.lov_uuid)
827         if l.nodeName == 'lov':
828             lov = LOV(l)
829             for osc_uuid in string.split(lov.devlist):
830                 osc = lookup(self.dom_node.parentNode, osc_uuid)
831                 if osc:
832                     n = OSC(osc)
833                     n.cleanup()
834                 else:
835                     panic('osc not found:', osc_uuid)
836             lov.cleanup()
837         else:
838             osc = OSC(l)
839             osc.cleanup()
840             
841 # ============================================================
842 # XML processing and query
843 # TODO: Change query funcs to use XPath, which is muc cleaner
844
845 def getDevice(obd):
846     list = obd.getElementsByTagName('device')
847     if len(list) > 0:
848         dev = list[0]
849         dev.normalize();
850         try:
851             size = int(dev.getAttribute('size'))
852         except ValueError:
853             size = 0
854         return dev.firstChild.data, size
855     return '', 0
856
857 # Get the text content from the first matching child
858 # If there is no content (or it is all whitespace), return
859 # the default
860 def getText(node, tag, default=""):
861     list = node.getElementsByTagName(tag)
862     if len(list) > 0:
863         node = list[0]
864         node.normalize()
865         if node.firstChild:
866             txt = string.strip(node.firstChild.data)
867             if txt:
868                 return txt
869     return default
870
871 def get_ost_net(node, uuid):
872     ost = lookup(node, uuid)
873     list = ost.getElementsByTagName('network_ref')
874     if list:
875         uuid = list[0].getAttribute('uuidref')
876     else:
877         return None
878     return lookup(node, uuid)
879     
880 def lookup(node, uuid):
881     for n in node.childNodes:
882         if n.nodeType == n.ELEMENT_NODE:
883             if getUUID(n) == uuid:
884                 return n
885             else:
886                 n = lookup(n, uuid)
887                 if n: return n
888     return None
889             
890 # Get name attribute of node
891 def getName(node):
892     return node.getAttribute('name')
893
894 def getRef(node):
895     return node.getAttribute('uuidref')
896
897 # Get name attribute of node
898 def getUUID(node):
899     return node.getAttribute('uuid')
900
901 # the tag name is the service type
902 # fixme: this should do some checks to make sure the node is a service
903 def getServiceType(node):
904     return node.nodeName
905
906 #
907 # determine what "level" a particular node is at.
908 # the order of iniitailization is based on level.  objects
909 # are assigned a level based on type:
910 #  net,devices,ldlm:1, obd, mdd:2  mds,ost:3 osc,mdc:4 mounts:5
911 def getServiceLevel(node):
912     type = getServiceType(node)
913     if type in ('network',):
914         return 1
915     if type in ('device', 'ldlm'):
916         return 2
917     elif type in ('obd', 'mdd'):
918         return 3
919     elif type in ('mds','ost'):
920         return 4
921     elif type in ('mdc','osc'):
922         return 5
923     elif type in ('lov',):
924         return 6
925     elif type in ('mountpoint',):
926         return 7
927     return 0
928
929 #
930 # return list of services in a profile. list is a list of tuples
931 # [(level, node),]
932 def getServices(lustreNode, profileNode):
933     list = []
934     for n in profileNode.childNodes:
935         if n.nodeType == n.ELEMENT_NODE:
936             servNode = lookup(lustreNode, getRef(n))
937             if not servNode:
938                 print n
939                 panic('service not found: ' + getRef(n))
940             level = getServiceLevel(servNode)
941             list.append((level, servNode))
942     list.sort()
943     return list
944
945 def getByName(lustreNode, tag, name):
946     ndList = lustreNode.getElementsByTagName(tag)
947     for nd in ndList:
948         if getName(nd) == name:
949             return nd
950     return None
951     
952
953 # ============================================================
954 # lconf level logic
955 # Start a service.
956 def startService(node, clean_flag, module_flag):
957     type = getServiceType(node)
958     debug('Service:', type, getName(node), getUUID(node))
959     # there must be a more dynamic way of doing this...
960     n = None
961     if type == 'ldlm':
962         n = LDLM(node)
963     elif type == 'lov':
964         n = LOV(node)
965     elif type == 'network':
966         n = Network(node)
967     elif type == 'obd':
968         n = OBD(node)
969     elif type == 'ost':
970         n = OST(node)
971     elif type == 'mds':
972         n = MDS(node)
973     elif type == 'osc':
974         n = OSC(node)
975     elif type == 'mdc':
976         n = MDC(node)
977     elif type == 'mountpoint':
978         n = Mountpoint(node)
979     else:
980         panic ("unknown service type:", type)
981
982     if module_flag:
983         if config.nomod():
984             return
985         if clean_flag:
986             n.cleanup_module()
987         else:
988             n.load_module()
989     else:
990         if config.nosetup():
991             return
992         if clean_flag:
993             n.cleanup()
994         else:
995             n.prepare()
996
997 #
998 # Prepare the system to run lustre using a particular profile
999 # in a the configuration. 
1000 #  * load & the modules
1001 #  * setup networking for the current node
1002 #  * make sure partitions are in place and prepared
1003 #  * initialize devices with lctl
1004 # Levels is important, and needs to be enforced.
1005 def startProfile(lustreNode, profileNode, clean_flag, module_flag):
1006     if not profileNode:
1007         panic("profile:", profile, "not found.")
1008     services = getServices(lustreNode, profileNode)
1009     if clean_flag:
1010         services.reverse()
1011     for s in services:
1012         startService(s[1], clean_flag, module_flag)
1013
1014 #
1015 # Load profile for 
1016 def doHost(lustreNode, hosts, clean_flag):
1017     node = None
1018     for h in hosts:
1019         node = getByName(lustreNode, 'node', h)
1020         if node:
1021             break
1022
1023     if not node:
1024         print 'No host entry found.'
1025         return
1026
1027     # Two step process: (1) load modules, (2) setup lustre
1028     # if not cleaning, load modules first.
1029     module_flag = not clean_flag
1030     reflist = node.getElementsByTagName('profile')
1031     for profile in reflist:
1032             startProfile(lustreNode,  profile, clean_flag, module_flag)
1033
1034     if not clean_flag:
1035         setDebugPath()
1036         script = config.gdb_script()
1037         run(lctl.lctl, ' modules >', script)
1038         if config.gdb():
1039             # dump /tmp/ogdb and sleep/pause here
1040             log ("The GDB module script is in", script)
1041             time.sleep(5)
1042             
1043     module_flag = not module_flag
1044     for profile in reflist:
1045             startProfile(lustreNode,  profile, clean_flag, module_flag)
1046
1047 # Command line processing
1048 #
1049 def parse_cmdline(argv):
1050     short_opts = "hdnv"
1051     long_opts = ["ldap", "reformat", "lustre=", "verbose", "gdb",
1052                  "portals=", "makeldiff", "cleanup", "noexec",
1053                  "help", "node=", "get=", "nomod", "nosetup"]
1054     opts = []
1055     args = []
1056     try:
1057         opts, args = getopt.getopt(argv, short_opts, long_opts)
1058     except getopt.error:
1059         print "invalid opt"
1060         usage()
1061
1062     for o, a in opts:
1063         if o in ("-h", "--help"):
1064             usage()
1065         if o in ("-d","--cleanup"):
1066             config.cleanup(1)
1067         if o in ("-v", "--verbose"):
1068             config.verbose(1)
1069         if o in ("-n", "--noexec"):
1070             config.noexec(1)
1071             config.verbose(1)
1072         if o == "--portals":
1073             config.portals =  a
1074         if o == "--lustre":
1075             config.lustre  = a
1076         if o  == "--reformat":
1077             config.reformat(1)
1078         if o  == "--node":
1079             config.node(a)
1080         if o  == "--get":
1081             config.url(a)
1082         if o  == "--gdb":
1083             config.gdb(1)
1084         if o  == "--nomod":
1085             config.nomod(1)
1086         if o  == "--nosetup":
1087             config.nosetup(1)
1088     return args
1089
1090 def fetch(url):
1091     import urllib
1092     data = ""
1093     try:
1094         s = urllib.urlopen(url)
1095         data = s.read()
1096     except:
1097         usage()
1098     return data
1099
1100 def setupModulePath(cmd):
1101     base = os.path.dirname(cmd)
1102     if os.access(base+"/Makefile", os.R_OK):
1103         config.src_dir(base + "/../../")
1104
1105 def setDebugPath():
1106     debug("debug path: ", config.debug_path())
1107     if config.noexec():
1108         return
1109     try:
1110         fp = open('/proc/sys/portals/debug_path', 'w')
1111         fp.write(config.debug_path())
1112         fp.close()
1113     except IOError, e:
1114         print e
1115              
1116     
1117 def makeDevices():
1118     if not os.access('/dev/portals', os.R_OK):
1119         run('mknod /dev/portals c 10 240')
1120     if not os.access('/dev/obd', os.R_OK):
1121         run('mknod /dev/obd c 10 241')
1122
1123 # Initialize or shutdown lustre according to a configuration file
1124 #   * prepare the system for lustre
1125 #   * configure devices with lctl
1126 # Shutdown does steps in reverse
1127 #
1128 def main():
1129     global TCP_ACCEPTOR, lctl
1130     args = parse_cmdline(sys.argv[1:])
1131     if len(args) > 0:
1132         if not os.access(args[0], os.R_OK | os.W_OK):
1133             print 'File not found:', args[0]
1134             sys.exit(1)
1135         dom = xml.dom.minidom.parse(args[0])
1136     elif config.url():
1137         xmldata = fetch(config.url())
1138         dom = xml.dom.minidom.parseString(xmldata)
1139     else:
1140         usage()
1141
1142     node_list = []
1143     if config.node():
1144         node_list.append(config.node())
1145     else:
1146         host = socket.gethostname()
1147         if len(host) > 0:
1148             node_list.append(host)
1149         node_list.append('localhost')
1150     debug("configuring for host: ", node_list)
1151
1152     TCP_ACCEPTOR = find_prog('acceptor')
1153     if not TCP_ACCEPTOR:
1154         if config.noexec():
1155             TCP_ACCEPTOR = 'acceptor'
1156             debug('! acceptor not found')
1157         else:
1158             panic('acceptor not found')
1159
1160     lctl = LCTLInterface('lctl')
1161
1162     setupModulePath(sys.argv[0])
1163     makeDevices()
1164     doHost(dom.documentElement, node_list, config.cleanup())
1165
1166 if __name__ == "__main__":
1167     try:
1168         main()
1169     except LconfError, e:
1170         print e
1171     except CommandError, e:
1172         e.dump()
1173         
1174