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