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