Whamcloud - gitweb
- fix route config and clean up errors
[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 MAXTCPBUF = 1048576
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     if not config.noexec():
153         raise LconfError(msg)
154     else:
155         print "! " + msg
156
157 def log(*args):
158     msg = string.join(map(str,args))
159     print msg
160
161 def logall(msgs):
162     for s in msgs:
163         print string.strip(s)
164
165 def debug(*args):
166     if config.verbose():
167         msg = string.join(map(str,args))
168         print msg
169
170 # ============================================================
171 # locally defined exceptions
172 class CommandError (exceptions.Exception):
173     def __init__(self, cmd_name, cmd_err, rc=None):
174         self.cmd_name = cmd_name
175         self.cmd_err = cmd_err
176         self.rc = rc
177
178     def dump(self):
179         import types
180         if type(self.cmd_err) == types.StringType:
181             if self.rc:
182                 print "! %s (%d): %s" % (self.cmd_name, self.rc, self.cmd_err)
183             else:
184                 print "! %s: %s" % (self.cmd_name, self.cmd_err)
185         elif type(self.cmd_err) == types.ListType:
186             if self.rc:
187                 print "! %s (error %d):" % (self.cmd_name, self.rc)
188             else:
189                 print "! %s:" % (self.cmd_name)
190             for s in self.cmd_err:
191                 print "> %s" %(string.strip(s))
192         else:
193             print self.cmd_err
194
195 class LconfError (exceptions.Exception):
196     def __init__(self, args):
197         self.args = args
198
199
200 # ============================================================
201 # handle lctl interface
202 class LCTLInterface:
203     """
204     Manage communication with lctl
205     """
206
207     def __init__(self, cmd):
208         """
209         Initialize close by finding the lctl binary.
210         """
211         self.lctl = find_prog(cmd)
212         if not self.lctl:
213             if config.noexec():
214                 debug('! lctl not found')
215                 self.lctl = 'lctl'
216             else:
217                 raise CommandError('lctl', "unable to find lctl binary.")
218             
219     def run(self, cmds):
220         """
221         run lctl
222         the cmds are written to stdin of lctl
223         lctl doesn't return errors when run in script mode, so
224         stderr is checked
225         should modify command line to accept multiple commands, or
226         create complex command line options
227         """
228         debug("+", self.lctl, cmds)
229         if config.noexec(): return (0, [])
230         p = popen2.Popen3(self.lctl, 1)
231         p.tochild.write(cmds + "\n")
232         p.tochild.close()
233         out = p.fromchild.readlines()
234         err = p.childerr.readlines()
235         ret = p.wait()
236         if ret or len(err):
237             raise CommandError(self.lctl, err, ret)
238         return ret, out
239
240             
241     def network(self, net, nid):
242         """ initialized network and add "self" """
243         # Idea: "mynid" could be used for all network types to add "self," and then
244         # this special case would be gone and the "self" hack would be hidden.
245         if net  == 'tcp':
246             cmds =  """
247   network %s
248   mynid %s
249   add_uuid self %s
250   quit""" % (net, nid, nid)
251         else:
252             cmds =  """
253   network %s
254   add_uuid self %s
255   quit""" % (net, nid)
256             
257         self.run(cmds)
258
259     # create a new connection
260     def connect(self, net, nid, port, servuuid, send_mem, recv_mem):
261         if net == 'tcp':
262             cmds =  """
263   network %s
264   add_uuid %s %s
265   send_mem %d
266   recv_mem %d
267   connect %s %d
268   quit""" % (net, servuuid, nid, send_mem, recv_mem, nid, port,  )
269         else:
270             cmds =  """
271   network %s
272   add_uuid %s %s
273   connect %s %d
274   quit""" % (net, servuuid, nid, nid, port,  )
275             
276         self.run(cmds)
277                 
278     # add a route to a range
279     def add_route(self, net, gw, lo, hi):
280         cmds =  """
281   network %s
282   add_route %s %s %s
283   quit  """ % (net, gw, lo, hi)
284         self.run(cmds)
285
286                 
287     # add a route to a range
288     def del_route(self, net, gw, lo, hi):
289         cmds =  """
290   network %s
291   del_route %s
292   quit  """ % (net, lo)
293         self.run(cmds)
294
295     # add a route to a host
296     def add_route_host(self, net, uuid, gw, tgt):
297         cmds =  """
298   network %s
299   add_uuid %s %s
300   add_route %s %s
301   quit """ % (net, uuid, tgt, gw, tgt)
302         self.run(cmds)
303
304     # disconnect one connection
305     def disconnect(self, net, nid, port, servuuid):
306         cmds =  """
307   network %s
308   disconnect %s 
309   del_uuid %s
310   quit""" % (net, nid, servuuid)
311         self.run(cmds)
312
313     # disconnect all connections
314     def disconnectAll(self, net):
315         cmds =  """
316   network %s
317   disconnect
318   del_uuid self
319   quit""" % (net)
320         self.run(cmds)
321
322     # create a new device with lctl
323     def newdev(self, attach, setup = ""):
324         cmds = """
325   newdev
326   attach %s
327   setup %s
328   quit""" % (attach, setup)
329         self.run(cmds)
330
331     # cleanup a device
332     def cleanup(self, name, uuid):
333         cmds = """
334   device $%s
335   cleanup
336   detach
337   quit""" % (name)
338         self.run(cmds)
339
340     # create an lov
341     def lovconfig(self, uuid, mdsuuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist):
342         cmds = """
343   device $%s
344   probe
345   lovconfig %s %d %d %d %s %s
346   quit""" % (mdsuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
347         self.run(cmds)
348
349 # ============================================================
350 # Various system-level functions
351 # (ideally moved to their own module)
352
353 # Run a command and return the output and status.
354 # stderr is sent to /dev/null, could use popen3 to
355 # save it if necessary
356 def run(*args):
357     cmd = string.join(map(str,args))
358     debug ("+", cmd)
359     if config.noexec(): return (0, [])
360     f = os.popen(cmd + ' 2>&1')
361     out = f.readlines()
362     ret = f.close()
363     if ret:
364         ret = ret >> 8
365     else:
366         ret = 0
367     return (ret, out)
368
369 # Run a command in the background.
370 def run_daemon(*args):
371     cmd = string.join(map(str,args))
372     debug ("+", cmd)
373     if config.noexec(): return 0
374     f = os.popen(cmd + ' 2>&1')
375     ret = f.close()
376     if ret:
377         ret = ret >> 8
378     else:
379         ret = 0
380     return ret
381
382 # Determine full path to use for an external command
383 # searches dirname(argv[0]) first, then PATH
384 def find_prog(cmd):
385     syspath = string.split(os.environ['PATH'], ':')
386     cmdpath = os.path.dirname(sys.argv[0])
387     syspath.insert(0, cmdpath);
388     syspath.insert(0, os.path.join(cmdpath, '../../portals/linux/utils/'))
389     for d in syspath:
390         prog = os.path.join(d,cmd)
391         if os.access(prog, os.X_OK):
392             return prog
393     return ''
394
395 # Recursively look for file starting at base dir
396 def do_find_file(base, mod):
397     fullname = os.path.join(base, mod)
398     if os.access(fullname, os.R_OK):
399         return fullname
400     for d in os.listdir(base):
401         dir = os.path.join(base,d)
402         if os.path.isdir(dir):
403             module = do_find_file(dir, mod)
404             if module:
405                 return module
406
407 def find_module(src_dir, modname):
408     mod = '%s.o' % (modname)
409     search = (src_dir + "/lustre", src_dir + "/portals/linux")
410     for d in search:
411         try:
412             module = do_find_file(d, mod)
413             if module:
414                 return module
415         except OSError:
416             pass
417     return None
418
419 # is the path a block device?
420 def is_block(path):
421     s = ()
422     try:
423         s =  os.stat(path)
424     except OSError:
425         return 0
426     return stat.S_ISBLK(s[stat.ST_MODE])
427
428 # build fs according to type
429 # fixme: dangerous
430 def mkfs(fstype, dev):
431     if(fstype in ('ext3', 'extN')):
432         mkfs = 'mkfs.ext2 -j -b 4096'
433     else:
434         print 'unsupported fs type: ', fstype
435     if not is_block(dev):
436         force = '-F'
437     else:
438         force = ''
439     (ret, out) = run (mkfs, force, dev)
440     if ret:
441         panic("Unable to build fs:", dev)
442     # enable hash tree indexing on fs
443     if fstype == 'extN':
444         htree = 'echo "feature FEATURE_C5" | debugfs -w'
445         (ret, out) = run (htree, dev)
446         if ret:
447             panic("Unable to enable htree:", dev)
448
449 # some systems use /dev/loopN, some /dev/loop/N
450 def loop_base():
451     import re
452     loop = '/dev/loop'
453     if not os.access(loop + str(0), os.R_OK):
454         loop = loop + '/'
455         if not os.access(loop + str(0), os.R_OK):
456             panic ("can't access loop devices")
457     return loop
458     
459 # find loop device assigned to thefile
460 def find_loop(file):
461     loop = loop_base()
462     for n in xrange(0, MAX_LOOP_DEVICES):
463         dev = loop + str(n)
464         if os.access(dev, os.R_OK):
465             (stat, out) = run('losetup', dev)
466             if (out and stat == 0):
467                 m = re.search(r'\((.*)\)', out[0])
468                 if m and file == m.group(1):
469                     return dev
470         else:
471             break
472     return ''
473
474 # create file if necessary and assign the first free loop device
475 def init_loop(file, size, fstype):
476     dev = find_loop(file)
477     if dev:
478         print 'WARNING file:', file, 'already mapped to', dev
479         return dev
480     if not os.access(file, os.R_OK | os.W_OK):
481         run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size,  file))
482     loop = loop_base()
483     # find next free loop
484     for n in xrange(0, MAX_LOOP_DEVICES):
485         dev = loop + str(n)
486         if os.access(dev, os.R_OK):
487             (stat, out) = run('losetup', dev)
488             if (stat):
489                 run('losetup', dev, file)
490                 return dev
491         else:
492             print "out of loop devices"
493             return ''
494     print "out of loop devices"
495     return ''
496
497 # undo loop assignment
498 def clean_loop(file):
499     dev = find_loop(file)
500     if dev:
501         ret, out = run('losetup -d', dev)
502         if ret:
503             log('unable to clean loop device:', dev, 'for file:', file)
504             logall(out)
505
506 # determine if dev is formatted as a <fstype> filesystem
507 def need_format(fstype, dev):
508     # FIXME don't know how to implement this    
509     return 0
510
511 # initialize a block device if needed
512 def block_dev(dev, size, fstype, format):
513     if config.noexec(): return dev
514     if not is_block(dev):
515         dev = init_loop(dev, size, fstype)
516     if config.reformat() or (need_format(fstype, dev) and format == 'yes'):
517         mkfs(fstype, dev)
518
519 #    else:
520 #        panic("device:", dev,
521 #              "not prepared, and autoformat is not set.\n",
522 #              "Rerun with --reformat option to format ALL filesystems")
523         
524     return dev
525
526 def get_local_address(net_type):
527     """Return the local address for the network type."""
528     local = ""
529     if net_type == 'tcp':
530         # host `hostname`
531         host = socket.gethostname()
532         local = socket.gethostbyname(host)
533     elif net_type == 'elan':
534         # awk '/NodeId/ { print $2 }' '/proc/elan/device0/position'
535         try:
536             fp = open('/proc/elan/device0/position', 'r')
537             lines = fp.readlines()
538             fp.close()
539             for l in lines:
540                 a = string.split(l)
541                 if a[0] == 'NodeId':
542                     local = a[1]
543                     break
544         except IOError, e:
545             log(e)
546     elif net_type == 'gm':
547         fixme("automatic local address for GM")
548     return local
549         
550         
551
552 # ============================================================
553 # Classes to prepare and cleanup the various objects
554 #
555 class Module:
556     """ Base class for the rest of the modules. The default cleanup method is
557     defined here, as well as some utilitiy funcs.
558     """
559     def __init__(self, module_name, dom_node):
560         self.dom_node = dom_node
561         self.module_name = module_name
562         self.name = get_attr(dom_node, 'name')
563         self.uuid = get_attr(dom_node, 'uuid')
564         self.kmodule_list = []
565         self._server = None
566         self._connected = 0
567         
568     def info(self, *args):
569         msg = string.join(map(str,args))
570         print self.module_name + ":", self.name, self.uuid, msg
571
572
573     def lookup_server(self, srv_uuid):
574         """ Lookup a server's network information """
575         net = get_ost_net(self.dom_node.parentNode, srv_uuid)
576         self._server = Network(net)
577
578     def get_server(self):
579         return self._server
580
581     def cleanup(self):
582         """ default cleanup, used for most modules """
583         self.info()
584         srv = self.get_server()
585         if srv and local_net(srv):
586             try:
587                 lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
588             except CommandError, e:
589                 log(self.module_name, "disconnect failed: ", self.name)
590                 e.dump()
591         try:
592             lctl.cleanup(self.name, self.uuid)
593         except CommandError, e:
594             log(self.module_name, "cleanup failed: ", self.name)
595             e.dump()
596
597     def add_module(self, modname):
598         """Append a module to list of modules to load."""
599         self.kmodule_list.append(modname)
600
601     def mod_loaded(self, modname):
602         """Check if a module is already loaded. Look in /proc/modules for it."""
603         fp = open('/proc/modules')
604         lines = fp.readlines()
605         fp.close()
606         # please forgive my tired fingers for this one
607         ret = filter(lambda word, mod=modname: word == mod,
608                      map(lambda line: string.split(line)[0], lines))
609         return ret
610
611     def load_module(self):
612         """Load all the modules in the list in the order they appear."""
613         for mod in self.kmodule_list:
614             #  (rc, out) = run ('/sbin/lsmod | grep -s', mod)
615             if self.mod_loaded(mod) and not config.noexec():
616                 continue
617             log ('loading module:', mod)
618             if config.src_dir():
619                 module = find_module(config.src_dir(), mod)
620                 if not module:
621                     panic('module not found:', mod)
622                 (rc, out)  = run('/sbin/insmod', module)
623                 if rc:
624                     raise CommandError('insmod', out, rc)
625             else:
626                 (rc, out) = run('/sbin/modprobe', mod)
627                 if rc:
628                     raise CommandError('modprobe', out, rc)
629             
630     def cleanup_module(self):
631         """Unload the modules in the list in reverse order."""
632         rev = self.kmodule_list
633         rev.reverse()
634         for mod in rev:
635             if not self.mod_loaded(mod):
636                 continue
637             log('unloading module:', mod)
638             if config.noexec():
639                 continue
640             (rc, out) = run('/sbin/rmmod', mod)
641             if rc:
642                 log('! unable to unload module:', mod)
643                 logall(out)
644         
645
646 class Network(Module):
647     def __init__(self,dom_node):
648         Module.__init__(self, 'NETWORK', dom_node)
649         self.net_type = get_attr(dom_node,'type')
650         self.nid = get_text(dom_node, 'server', '*')
651         self.port = get_text_int(dom_node, 'port', 0)
652         self.send_mem = get_text_int(dom_node, 'send_mem', 65536)
653         self.recv_mem = get_text_int(dom_node, 'recv_mem', 65536)
654         if self.nid == '*':
655             self.nid = get_local_address(self.net_type)
656             if not self.nid:
657                 panic("unable to set nid for", self.net_type)
658
659         self.add_module('portals')
660         if node_needs_router():
661             self.add_module('kptlrouter')
662         if self.net_type == 'tcp':
663             self.add_module('ksocknal')
664         if self.net_type == 'elan':
665             self.add_module('kqswnal')
666         if self.net_type == 'gm':
667             self.add_module('kgmnal')
668         self.add_module('obdclass')
669         self.add_module('ptlrpc')
670
671     def prepare(self):
672         self.info(self.net_type, self.nid, self.port)
673         if self.net_type == 'tcp':
674             ret = run_daemon(TCP_ACCEPTOR, '-s', self.send_mem, '-r', self.recv_mem, self.port)
675             if ret:
676                 raise CommandError(TCP_ACCEPTOR, 'failed', ret)
677         ret = self.dom_node.getElementsByTagName('route_tbl')
678         for a in ret:
679             for r in a.getElementsByTagName('route'):
680                 net_type = get_attr(r, 'type')
681                 gw = get_attr(r, 'gw')
682                 lo = get_attr(r, 'lo')
683                 hi = get_attr(r,'hi', '')
684                 lctl.add_route(net_type, gw, lo, hi)
685                 if self.net_type == 'tcp' and hi == '':
686                     srv = nid2server(self.dom_node.parentNode.parentNode, lo)
687                     if not srv:
688                         panic("no server for nid", lo)
689                     else:
690                         lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_mem, srv.recv_mem)
691
692             
693         lctl.network(self.net_type, self.nid)
694         lctl.newdev(attach = "ptlrpc RPCDEV")
695
696     def cleanup(self):
697         self.info(self.net_type, self.nid, self.port)
698         ret = self.dom_node.getElementsByTagName('route_tbl')
699         for a in ret:
700             for r in a.getElementsByTagName('route'):
701                 lo = get_attr(r, 'lo')
702                 hi = get_attr(r,'hi', '')
703                 if self.net_type == 'tcp' and hi == '':
704                     srv = nid2server(self.dom_node.parentNode.parentNode, lo)
705                     if not srv:
706                         panic("no server for nid", lo)
707                     else:
708                         try:
709                             lctl.disconnect(srv.net_type, srv.nid, srv.port, srv.uuid)
710                         except CommandError, e:
711                                 print "disconnect failed: ", self.name
712                                 e.dump()
713                 try:
714                     lctl.del_route(self.net_type, self.nid, lo, hi)
715                 except CommandError, e:
716                     print "del_route failed: ", self.name
717                     e.dump()
718               
719         try:
720             lctl.cleanup("RPCDEV", "")
721         except CommandError, e:
722             print "cleanup failed: ", self.name
723             e.dump()
724         try:
725             lctl.disconnectAll(self.net_type)
726         except CommandError, e:
727             print "disconnectAll failed: ", self.name
728             e.dump()
729         if self.net_type == 'tcp':
730             # yikes, this ugly! need to save pid in /var/something
731             run("killall acceptor")
732
733 class LDLM(Module):
734     def __init__(self,dom_node):
735         Module.__init__(self, 'LDLM', dom_node)
736         self.add_module('ldlm')
737     def prepare(self):
738         self.info()
739         lctl.newdev(attach="ldlm %s %s" % (self.name, self.uuid),
740                     setup ="")
741
742 class LOV(Module):
743     def __init__(self,dom_node):
744         Module.__init__(self, 'LOV', dom_node)
745         self.stripe_sz = get_attr_int(dom_node, 'stripesize', 65536)
746         self.stripe_off = get_attr_int(dom_node, 'stripeoffset', 0)
747         self.pattern = get_attr_int(dom_node, 'pattern', 0)
748         self.mdsuuid = get_first_ref(dom_node, 'mds')
749         mds= lookup(dom_node.parentNode, self.mdsuuid)
750         self.mdsname = getName(mds)
751         self.devlist = get_all_refs(dom_node, 'osc')
752         self.stripe_cnt = len(self.devlist)
753
754     def prepare(self):
755         self.info(self.mdsuuid, self.stripe_cnt, self.stripe_sz, self.stripe_off, self.pattern,
756         self.devlist, self.mdsname)
757         lctl.lovconfig(self.uuid, self.mdsname, self.stripe_cnt,
758                        self.stripe_sz, self.stripe_off, self.pattern,
759                        string.join(self.devlist))
760
761
762 class MDS(Module):
763     def __init__(self,dom_node):
764         Module.__init__(self, 'MDS', dom_node)
765         self.devname, self.size = get_device(dom_node)
766         self.fstype = get_text(dom_node, 'fstype')
767         self.format = get_text(dom_node, 'autoformat', "no")
768         if self.fstype == 'extN':
769             self.add_module('extN') 
770         self.add_module('mds')
771         self.add_module('mds_%s' % (self.fstype))
772             
773     def prepare(self):
774         self.info(self.devname, self.fstype, self.format)
775         blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
776         lctl.newdev(attach="mds %s %s" % (self.name, self.uuid),
777                     setup ="%s %s" %(blkdev, self.fstype))
778     def cleanup(self):
779         Module.cleanup(self)
780         clean_loop(self.devname)
781
782 class MDC(Module):
783     def __init__(self,dom_node):
784         Module.__init__(self, 'MDC', dom_node)
785         self.mds_uuid = get_first_ref(dom_node, 'mds')
786         self.lookup_server(self.mds_uuid)
787         self.add_module('mdc')
788
789     def prepare(self):
790         self.info(self.mds_uuid)
791         srv = self.get_server()
792         lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_mem, srv.recv_mem)
793         lctl.newdev(attach="mdc %s %s" % (self.name, self.uuid),
794                         setup ="%s %s" %(self.mds_uuid, srv.uuid))
795             
796 class OBD(Module):
797     def __init__(self, dom_node):
798         Module.__init__(self, 'OBD', dom_node)
799         self.obdtype = get_attr(dom_node, 'type')
800         self.devname, self.size = get_device(dom_node)
801         self.fstype = get_text(dom_node, 'fstype')
802         self.format = get_text(dom_node, 'autoformat', 'yes')
803         if self.fstype == 'extN':
804             self.add_module('extN') 
805         self.add_module(self.obdtype)
806
807     # need to check /proc/mounts and /etc/mtab before
808     # formatting anything.
809     # FIXME: check if device is already formatted.
810     def prepare(self):
811         self.info(self.obdtype, self.devname, self.size, self.fstype, self.format)
812         if self.obdtype == 'obdecho':
813             blkdev = ''
814         else:
815             blkdev = block_dev(self.devname, self.size, self.fstype, self.format)
816         lctl.newdev(attach="%s %s %s" % (self.obdtype, self.name, self.uuid),
817                     setup ="%s %s" %(blkdev, self.fstype))
818     def cleanup(self):
819         Module.cleanup(self)
820         if not self.obdtype == 'obdecho':
821             clean_loop(self.devname)
822
823 class OST(Module):
824     def __init__(self,dom_node):
825         Module.__init__(self, 'OST', dom_node)
826         self.obd_uuid = get_first_ref(dom_node, 'obd')
827         self.add_module('ost')
828
829     def prepare(self):
830         self.info(self.obd_uuid)
831         lctl.newdev(attach="ost %s %s" % (self.name, self.uuid),
832                     setup ="%s" % (self.obd_uuid))
833
834 class OSC(Module):
835     def __init__(self,dom_node):
836         Module.__init__(self, 'OSC', dom_node)
837         self.obd_uuid = get_first_ref(dom_node, 'obd')
838         self.ost_uuid = get_first_ref(dom_node, 'ost')
839         self.lookup_server(self.ost_uuid)
840         self.add_module('osc')
841
842     def prepare(self):
843         self.info(self.obd_uuid, self.ost_uuid)
844         srv = self.get_server()
845         if local_net(srv):
846             lctl.connect(srv.net_type, srv.nid, srv.port, srv.uuid, srv.send_mem, srv.recv_mem)
847         else:
848             r =  find_route(srv)
849             if r:
850                 lctl.add_route_host(r[0], srv.uuid, r[1], r[2])
851             else:
852                 panic ("no route to",  srv.nid)
853             
854         lctl.newdev(attach="osc %s %s" % (self.name, self.uuid),
855                     setup ="%s %s" %(self.obd_uuid, srv.uuid))
856
857
858 class Mountpoint(Module):
859     def __init__(self,dom_node):
860         Module.__init__(self, 'MTPT', dom_node)
861         self.path = get_text(dom_node, 'path')
862         self.mdc_uuid = get_first_ref(dom_node, 'mdc')
863         self.lov_uuid = get_first_ref(dom_node, 'osc')
864         self.add_module('osc')
865         # should add lov only if needed
866         self.add_module('lov')
867         self.add_module('llite')
868
869     def prepare(self):
870         l = lookup(self.dom_node.parentNode, self.lov_uuid)
871         if l.nodeName == 'lov':
872             lov = LOV(l)
873             for osc_uuid in lov.devlist:
874                 osc = lookup(self.dom_node.parentNode, osc_uuid)
875                 if osc:
876                     n = OSC(osc)
877                     n.prepare()
878                 else:
879                     panic('osc not found:', osc_uuid)
880             lctl.newdev(attach="lov %s %s" % (lov.name, lov.uuid),
881                         setup ="%s" % (self.mdc_uuid))
882         else:
883             osc = OSC(l)
884             osc.prepare()
885             
886         self.info(self.path, self.mdc_uuid,self.lov_uuid)
887         cmd = "mount -t lustre_lite -o osc=%s,mdc=%s none %s" % \
888               (self.lov_uuid, self.mdc_uuid, self.path)
889         run("mkdir", self.path)
890         ret, val = run(cmd)
891         if ret:
892             panic("mount failed:", self.path)
893     def cleanup(self):
894         self.info(self.path, self.mdc_uuid,self.lov_uuid)
895         (rc, out) = run("umount", self.path)
896         if rc:
897             log("umount failed, cleanup will most likely not work.")
898         l = lookup(self.dom_node.parentNode, self.lov_uuid)
899         if l.nodeName == 'lov':
900             lov = LOV(l)
901             for osc_uuid in lov.devlist:
902                 osc = lookup(self.dom_node.parentNode, osc_uuid)
903                 if osc:
904                     n = OSC(osc)
905                     n.cleanup()
906                 else:
907                     panic('osc not found:', osc_uuid)
908         else:
909             osc = OSC(l)
910             osc.cleanup()
911             
912
913 # ============================================================
914 # XML processing and query
915 # TODO: Change query funcs to use XPath, which is muc cleaner
916
917 def get_device(obd):
918     list = obd.getElementsByTagName('device')
919     if len(list) > 0:
920         dev = list[0]
921         dev.normalize();
922         size = get_attr_int(dev, 'size', 0)
923         return dev.firstChild.data, size
924     return '', 0
925
926 # Get the text content from the first matching child
927 # If there is no content (or it is all whitespace), return
928 # the default
929 def get_text(dom_node, tag, default=""):
930     list = dom_node.getElementsByTagName(tag)
931     if len(list) > 0:
932         dom_node = list[0]
933         dom_node.normalize()
934         if dom_node.firstChild:
935             txt = string.strip(dom_node.firstChild.data)
936             if txt:
937                 return txt
938     return default
939
940 def get_text_int(dom_node, tag, default=0):
941     list = dom_node.getElementsByTagName(tag)
942     n = default
943     if len(list) > 0:
944         dom_node = list[0]
945         dom_node.normalize()
946         if dom_node.firstChild:
947             txt = string.strip(dom_node.firstChild.data)
948             if txt:
949                 try:
950                     n = int(txt)
951                 except ValueError:
952                     panic("text value is not integer:", txt)
953     return n
954
955 def get_attr(dom_node, attr, default=""):
956     v = dom_node.getAttribute(attr)
957     if v:
958         return v
959     return default
960
961 def get_attr_int(dom_node, attr, default=0):
962     n = default
963     v = dom_node.getAttribute(attr)
964     if v:
965         try:
966             n = int(v)
967         except ValueError:
968             panic("attr value is not integer", v)
969     return n
970
971 def get_first_ref(dom_node, tag):
972     """ Get the first uuidref of the type TAG. Used one only
973     one is expected.  Returns the uuid."""
974     uuid = None
975     refname = '%s_ref' % tag
976     list = dom_node.getElementsByTagName(refname)
977     if len(list) > 0:
978         uuid = getRef(list[0])
979     return uuid
980     
981 def get_all_refs(dom_node, tag):
982     """ Get all the refs of type TAG.  Returns list of uuids. """
983     uuids = []
984     refname = '%s_ref' % tag
985     list = dom_node.getElementsByTagName(refname)
986     if len(list) > 0:
987         for i in list:
988             uuids.append(getRef(i))
989     return uuids
990
991 def get_ost_net(dom_node, uuid):
992     ost = lookup(dom_node, uuid)
993     uuid = get_first_ref(ost, 'network')
994     if not uuid:
995         return None
996     return lookup(dom_node, uuid)
997
998 def nid2server(dom_node, nid):
999     netlist = dom_node.getElementsByTagName('network')
1000     for net_node in netlist:
1001         if get_text(net_node, 'server') == nid:
1002             return Network(net_node)
1003     return None
1004     
1005 def lookup(dom_node, uuid):
1006     for n in dom_node.childNodes:
1007         if n.nodeType == n.ELEMENT_NODE:
1008             if getUUID(n) == uuid:
1009                 return n
1010             else:
1011                 n = lookup(n, uuid)
1012                 if n: return n
1013     return None
1014             
1015 # Get name attribute of dom_node
1016 def getName(dom_node):
1017     return dom_node.getAttribute('name')
1018
1019 def getRef(dom_node):
1020     return dom_node.getAttribute('uuidref')
1021
1022 # Get name attribute of dom_node
1023 def getUUID(dom_node):
1024     return dom_node.getAttribute('uuid')
1025
1026 # the tag name is the service type
1027 # fixme: this should do some checks to make sure the dom_node is a service
1028 def getServiceType(dom_node):
1029     return dom_node.nodeName
1030
1031 #
1032 # determine what "level" a particular node is at.
1033 # the order of iniitailization is based on level. 
1034 def getServiceLevel(dom_node):
1035     type = getServiceType(dom_node)
1036     if type in ('network',):
1037         return 10
1038     elif type in ('device', 'ldlm'):
1039         return 20
1040     elif type in ('obd', 'mdd'):
1041         return 30
1042     elif type in ('mds','ost'):
1043         return 40
1044     elif type in ('mdc','osc'):
1045         return 50
1046     elif type in ('lov',):
1047         return 60
1048     elif type in ('mountpoint',):
1049         return 70
1050     return 0
1051
1052 #
1053 # return list of services in a profile. list is a list of tuples
1054 # [(level, dom_node),]
1055 def getServices(lustreNode, profileNode):
1056     list = []
1057     for n in profileNode.childNodes:
1058         if n.nodeType == n.ELEMENT_NODE:
1059             servNode = lookup(lustreNode, getRef(n))
1060             if not servNode:
1061                 print n
1062                 panic('service not found: ' + getRef(n))
1063             level = getServiceLevel(servNode)
1064             list.append((level, servNode))
1065     list.sort()
1066     return list
1067
1068 def getByName(lustreNode, name, tag):
1069     ndList = lustreNode.getElementsByTagName(tag)
1070     for nd in ndList:
1071         if getName(nd) == name:
1072             return nd
1073     return None
1074     
1075
1076
1077
1078 ############################################################
1079 # routing ("rooting")
1080 #
1081 routes = []
1082 local_node = []
1083 router_flag = 0
1084
1085 def init_node(dom_node):
1086     global local_node, router_flag
1087     netlist = dom_node.getElementsByTagName('network')
1088     for dom_net in netlist:
1089         type = get_attr(dom_net, 'type')
1090         gw = get_text(dom_net, 'server')
1091         local_node.append((type, gw))
1092
1093 def node_needs_router():
1094     return router_flag
1095
1096 def get_routes(type, gw, dom_net):
1097     """ Return the routes as a list of tuples of the form:
1098         [(type, gw, lo, hi),]"""
1099     res = []
1100     tbl = dom_net.getElementsByTagName('route_tbl')
1101     for t in tbl:
1102         routes = t.getElementsByTagName('route')
1103         for r in routes:
1104             lo = get_attr(r, 'lo')
1105             hi = get_attr(r, 'hi', '')
1106             res.append((type, gw, lo, hi))
1107     return res
1108     
1109
1110 def init_route_config(lustre):
1111     """ Scan the lustre config looking for routers.  Build list of
1112     routes. """
1113     global routes, router_flag
1114     routes = []
1115     list = lustre.getElementsByTagName('node')
1116     for node in list:
1117         if get_attr(node, 'router'):
1118             router_flag = 1
1119             for (local_type, local_nid) in local_node:
1120                 gw = None
1121                 netlist = node.getElementsByTagName('network')
1122                 for dom_net in netlist:
1123                     if local_type == get_attr(dom_net, 'type'):
1124                         gw = get_text(dom_net, 'server')
1125                         break
1126                 if not gw:
1127                     continue
1128                 for dom_net in netlist:
1129                     if local_type != get_attr(dom_net, 'type'):
1130                         for route in get_routes(local_type, gw, dom_net):
1131                             routes.append(route)
1132     
1133
1134 def local_net(net):
1135     global local_node
1136     for iface in local_node:
1137         if net.net_type == iface[0]:
1138             return 1
1139     return 0
1140
1141 def find_route(net):
1142     global local_node, routes
1143     frm_type = local_node[0][0]
1144     to_type = net.net_type
1145     to = net.nid
1146     debug ('looking for route to', to_type,to)
1147     for r in routes:
1148         if  r[2] == to:
1149             return r
1150     return None
1151            
1152     
1153         
1154
1155 ############################################################
1156 # lconf level logic
1157 # Start a service.
1158 def startService(dom_node, module_flag):
1159     type = getServiceType(dom_node)
1160     debug('Service:', type, getName(dom_node), getUUID(dom_node))
1161     # there must be a more dynamic way of doing this...
1162     n = None
1163     if type == 'ldlm':
1164         n = LDLM(dom_node)
1165     elif type == 'lov':
1166         n = LOV(dom_node)
1167     elif type == 'network':
1168         n = Network(dom_node)
1169     elif type == 'obd':
1170         n = OBD(dom_node)
1171     elif type == 'ost':
1172         n = OST(dom_node)
1173     elif type == 'mds':
1174         n = MDS(dom_node)
1175     elif type == 'osc':
1176         n = OSC(dom_node)
1177     elif type == 'mdc':
1178         n = MDC(dom_node)
1179     elif type == 'mountpoint':
1180         n = Mountpoint(dom_node)
1181     else:
1182         panic ("unknown service type:", type)
1183
1184     if module_flag:
1185         if config.nomod():
1186             return
1187         if config.cleanup():
1188             n.cleanup_module()
1189         else:
1190             n.load_module()
1191     else:
1192         if config.nosetup():
1193             return
1194         if config.cleanup():
1195             n.cleanup()
1196         else:
1197             n.prepare()
1198
1199 #
1200 # Prepare the system to run lustre using a particular profile
1201 # in a the configuration. 
1202 #  * load & the modules
1203 #  * setup networking for the current node
1204 #  * make sure partitions are in place and prepared
1205 #  * initialize devices with lctl
1206 # Levels is important, and needs to be enforced.
1207 def startProfile(lustreNode, profileNode, module_flag):
1208     if not profileNode:
1209         panic("profile:", profile, "not found.")
1210     services = getServices(lustreNode, profileNode)
1211     if config.cleanup():
1212         services.reverse()
1213     for s in services:
1214         startService(s[1], module_flag)
1215
1216
1217 #
1218 # Load profile for 
1219 def doHost(lustreNode, hosts):
1220     global routes
1221     dom_node = None
1222     for h in hosts:
1223         dom_node = getByName(lustreNode, h, 'node')
1224         if dom_node:
1225             break
1226
1227     if not dom_node:
1228         print 'No host entry found.'
1229         return
1230
1231     if not get_attr(dom_node, 'router'):
1232         init_node(dom_node)
1233         init_route_config(lustreNode)
1234     else:
1235         global router_flag 
1236         router_flag = 1
1237
1238     # Two step process: (1) load modules, (2) setup lustre
1239     # if not cleaning, load modules first.
1240     module_flag = not config.cleanup()
1241     reflist = dom_node.getElementsByTagName('profile')
1242     for profile in reflist:
1243             startProfile(lustreNode,  profile, module_flag)
1244
1245     if not config.cleanup():
1246         sys_set_debug_path()
1247         script = config.gdb_script()
1248         run(lctl.lctl, ' modules >', script)
1249         if config.gdb():
1250             # dump /tmp/ogdb and sleep/pause here
1251             log ("The GDB module script is in", script)
1252             time.sleep(5)
1253             
1254     module_flag = not module_flag
1255     for profile in reflist:
1256             startProfile(lustreNode,  profile, module_flag)
1257
1258 ############################################################
1259 # Command line processing
1260 #
1261 def parse_cmdline(argv):
1262     short_opts = "hdnv"
1263     long_opts = ["ldap", "reformat", "lustre=", "verbose", "gdb",
1264                  "portals=", "makeldiff", "cleanup", "noexec",
1265                  "help", "node=", "get=", "nomod", "nosetup"]
1266     opts = []
1267     args = []
1268     try:
1269         opts, args = getopt.getopt(argv, short_opts, long_opts)
1270     except getopt.error:
1271         print "invalid opt"
1272         usage()
1273
1274     for o, a in opts:
1275         if o in ("-h", "--help"):
1276             usage()
1277         if o in ("-d","--cleanup"):
1278             config.cleanup(1)
1279         if o in ("-v", "--verbose"):
1280             config.verbose(1)
1281         if o in ("-n", "--noexec"):
1282             config.noexec(1)
1283             config.verbose(1)
1284         if o == "--portals":
1285             config.portals =  a
1286         if o == "--lustre":
1287             config.lustre  = a
1288         if o  == "--reformat":
1289             config.reformat(1)
1290         if o  == "--node":
1291             config.node(a)
1292         if o  == "--get":
1293             config.url(a)
1294         if o  == "--gdb":
1295             config.gdb(1)
1296         if o  == "--nomod":
1297             config.nomod(1)
1298         if o  == "--nosetup":
1299             config.nosetup(1)
1300     return args
1301
1302 def fetch(url):
1303     import urllib
1304     data = ""
1305     try:
1306         s = urllib.urlopen(url)
1307         data = s.read()
1308     except:
1309         usage()
1310     return data
1311
1312 def setupModulePath(cmd):
1313     base = os.path.dirname(cmd)
1314     if os.access(base+"/Makefile", os.R_OK):
1315         config.src_dir(base + "/../../")
1316
1317 def sys_set_debug_path():
1318     debug("debug path: ", config.debug_path())
1319     if config.noexec():
1320         return
1321     try:
1322         fp = open('/proc/sys/portals/debug_path', 'w')
1323         fp.write(config.debug_path())
1324         fp.close()
1325     except IOError, e:
1326         print e
1327              
1328 #/proc/sys/net/core/rmem_max
1329 #/proc/sys/net/core/wmem_max
1330 def sys_set_netmem_max(path, max):
1331     debug("setting", path, "to at least", max)
1332     if config.noexec():
1333         return
1334     fp = open(path)
1335     str = fp.readline()
1336     fp.close
1337     cur = int(str)
1338     if max > cur:
1339         fp = open(path, 'w')
1340         fp.write('%d\n' %(max))
1341         fp.close()
1342     
1343     
1344 def sys_make_devices():
1345     if not os.access('/dev/portals', os.R_OK):
1346         run('mknod /dev/portals c 10 240')
1347     if not os.access('/dev/obd', os.R_OK):
1348         run('mknod /dev/obd c 10 241')
1349
1350 # Initialize or shutdown lustre according to a configuration file
1351 #   * prepare the system for lustre
1352 #   * configure devices with lctl
1353 # Shutdown does steps in reverse
1354 #
1355 def main():
1356     global TCP_ACCEPTOR, lctl, MAXTCPBUF
1357     args = parse_cmdline(sys.argv[1:])
1358     if len(args) > 0:
1359         if not os.access(args[0], os.R_OK | os.W_OK):
1360             print 'File not found:', args[0]
1361             sys.exit(1)
1362         dom = xml.dom.minidom.parse(args[0])
1363     elif config.url():
1364         xmldata = fetch(config.url())
1365         dom = xml.dom.minidom.parseString(xmldata)
1366     else:
1367         usage()
1368
1369     node_list = []
1370     if config.node():
1371         node_list.append(config.node())
1372     else:
1373         host = socket.gethostname()
1374         if len(host) > 0:
1375             node_list.append(host)
1376         node_list.append('localhost')
1377     debug("configuring for host: ", node_list)
1378
1379     TCP_ACCEPTOR = find_prog('acceptor')
1380     if not TCP_ACCEPTOR:
1381         if config.noexec():
1382             TCP_ACCEPTOR = 'acceptor'
1383             debug('! acceptor not found')
1384         else:
1385             panic('acceptor not found')
1386
1387     lctl = LCTLInterface('lctl')
1388
1389     setupModulePath(sys.argv[0])
1390     sys_make_devices()
1391     sys_set_netmem_max('/proc/sys/net/core/rmem_max', MAXTCPBUF)
1392     sys_set_netmem_max('/proc/sys/net/core/wmem_max', MAXTCPBUF)
1393     doHost(dom.documentElement, node_list)
1394
1395 if __name__ == "__main__":
1396     try:
1397         main()
1398     except LconfError, e:
1399         print e
1400     except CommandError, e:
1401         e.dump()