Whamcloud - gitweb
Land b_smallfix onto HEAD (20040416_1638) (more 2.6 build fixes)
[fs/lustre-release.git] / lustre / utils / lconf
1 #!/usr/bin/env python
2 #
3 #  Copyright (C) 2002-2003 Cluster File Systems, Inc.
4 #   Authors: Robert Read <rread@clusterfs.com>
5 #            Mike Shaver <shaver@clusterfs.com>
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, types
29 import string, os, stat, popen2, socket, time, random, fcntl, select
30 import re, exceptions, signal, traceback
31 import xml.dom.minidom
32
33 if sys.version[0] == '1':
34     from FCNTL import F_GETFL, F_SETFL
35 else:
36     from fcntl import F_GETFL, F_SETFL
37
38 PYMOD_DIR = "/usr/lib/lustre/python"
39
40 def development_mode():
41     base = os.path.dirname(sys.argv[0])
42     if os.access(base+"/Makefile", os.R_OK):
43         return 1
44     return 0
45
46 if development_mode():
47     sys.path.append('../utils')
48 else:
49     sys.path.append(PYMOD_DIR)
50
51 import Lustre
52
53 # Global parameters
54 MAXTCPBUF = 16777216
55 DEFAULT_TCPBUF = 8388608
56 DEFAULT_PORT = 988
57 #
58 # Maximum number of devices to search for.
59 # (the /dev/loop* nodes need to be created beforehand)
60 MAX_LOOP_DEVICES = 256
61 PORTALS_DIR = 'portals'
62
63 # Needed to call lconf --record
64 CONFIG_FILE = "" 
65
66 # Please keep these in sync with the values in portals/kp30.h
67 ptldebug_names = { 
68     "trace" :     (1 << 0),
69     "inode" :     (1 << 1),
70     "super" :     (1 << 2),
71     "ext2" :      (1 << 3),
72     "malloc" :    (1 << 4),
73     "cache" :     (1 << 5),
74     "info" :      (1 << 6),
75     "ioctl" :     (1 << 7),
76     "blocks" :    (1 << 8),
77     "net" :       (1 << 9),
78     "warning" :   (1 << 10),
79     "buffs" :     (1 << 11),
80     "other" :     (1 << 12),
81     "dentry" :    (1 << 13),
82     "portals" :   (1 << 14),
83     "page" :      (1 << 15),
84     "dlmtrace" :  (1 << 16),
85     "error" :     (1 << 17),
86     "emerg" :     (1 << 18),
87     "ha" :        (1 << 19),
88     "rpctrace" :  (1 << 20),
89     "vfstrace" :  (1 << 21),
90     "reada" :     (1 << 22),
91     }
92
93 subsystem_names = {
94     "undefined" :    (1 << 0),
95     "mdc" :          (1 << 1),
96     "mds" :          (1 << 2),
97     "osc" :          (1 << 3),
98     "ost" :          (1 << 4),
99     "class" :        (1 << 5),
100     "log" :          (1 << 6),
101     "llite" :        (1 << 7),
102     "rpc" :          (1 << 8),
103     "mgmt" :         (1 << 9),
104     "portals" :      (1 << 10),
105     "socknal" :      (1 << 11),
106     "qswnal" :       (1 << 12),
107     "pinger" :       (1 << 13),
108     "filter" :       (1 << 14),
109     "ptlbd" :        (1 << 15),
110     "echo" :         (1 << 16),
111     "ldlm" :         (1 << 17),
112     "lov" :          (1 << 18),
113     "gmnal" :        (1 << 19),
114     "ptlrouter" :    (1 << 20),
115     "cobd" :         (1 << 21),
116     "ibnal" :        (1 << 22),
117     }
118
119
120 first_cleanup_error = 0
121 def cleanup_error(rc):
122     global first_cleanup_error
123     if not first_cleanup_error:
124         first_cleanup_error = rc
125
126 # ============================================================ 
127 # debugging and error funcs
128
129 def fixme(msg = "this feature"):
130     raise Lustre.LconfError, msg + ' not implmemented yet.'
131
132 def panic(*args):
133     msg = string.join(map(str,args))
134     if not config.noexec:
135         raise Lustre.LconfError(msg)
136     else:
137         print "! " + msg
138
139 def log(*args):
140     msg = string.join(map(str,args))
141     print msg
142
143 def logall(msgs):
144     for s in msgs:
145         print string.strip(s)
146
147 def debug(*args):
148     if config.verbose:
149         msg = string.join(map(str,args))
150         print msg
151
152 # ack, python's builtin int() does not support '0x123' syntax.
153 # eval can do it, although what a hack!
154 def my_int(s):
155     try:
156         if s[0:2] == '0x':
157             return eval(s, {}, {})
158         else:
159             return int(s)
160     except SyntaxError, e:
161         raise ValueError("not a number")
162     except NameError, e:
163         raise ValueError("not a number")
164
165 # ============================================================
166 # locally defined exceptions
167 class CommandError (exceptions.Exception):
168     def __init__(self, cmd_name, cmd_err, rc=None):
169         self.cmd_name = cmd_name
170         self.cmd_err = cmd_err
171         self.rc = rc
172
173     def dump(self):
174         import types
175         if type(self.cmd_err) == types.StringType:
176             if self.rc:
177                 print "! %s (%d): %s" % (self.cmd_name, self.rc, self.cmd_err)
178             else:
179                 print "! %s: %s" % (self.cmd_name, self.cmd_err)
180         elif type(self.cmd_err) == types.ListType:
181             if self.rc:
182                 print "! %s (error %d):" % (self.cmd_name, self.rc)
183             else:
184                 print "! %s:" % (self.cmd_name)
185             for s in self.cmd_err:
186                 print "> %s" %(string.strip(s))
187         else:
188             print self.cmd_err
189
190
191 # ============================================================
192 # handle daemons, like the acceptor
193 class DaemonHandler:
194     """ Manage starting and stopping a daemon. Assumes daemon manages
195     it's own pid file. """
196
197     def __init__(self, cmd):
198         self.command = cmd
199         self.path =""
200
201     def start(self):
202         if self.running():
203             log(self.command, "already running.")
204         if not self.path:
205             self.path = find_prog(self.command)
206             if not self.path:
207                 panic(self.command, "not found.")
208         ret, out = runcmd(self.path +' '+ self.command_line())
209         if ret:
210             raise CommandError(self.path, out, ret)
211
212     def stop(self):
213         if self.running():
214             pid = self.read_pidfile()
215             try:
216                 log ("killing process", pid)
217                 os.kill(pid, 15)
218                 #time.sleep(1) # let daemon die
219             except OSError, e:
220                 log("unable to kill", self.command, e)
221             if self.running():
222                 log("unable to kill", self.command)
223
224     def running(self):
225         pid = self.read_pidfile()
226         if pid:
227             try:
228                 os.kill(pid, 0)
229             except OSError:
230                 self.clean_pidfile()
231             else:
232                 return 1
233         return 0
234
235     def read_pidfile(self):
236         try:
237             fp = open(self.pidfile(), 'r')
238             pid = int(fp.read())
239             fp.close()
240             return pid
241         except IOError:
242             return 0
243         
244     def clean_pidfile(self):
245         """ Remove a stale pidfile """
246         log("removing stale pidfile:", self.pidfile())
247         try:
248             os.unlink(self.pidfile())
249         except OSError, e:
250             log(self.pidfile(), e)
251             
252 class AcceptorHandler(DaemonHandler):
253     def __init__(self, port, net_type, send_mem, recv_mem, irq_aff):
254         DaemonHandler.__init__(self, "acceptor")
255         self.port = port
256         self.flags = ''
257         self.send_mem = send_mem
258         self.recv_mem = recv_mem
259
260         if irq_aff:
261             self.flags = self.flags + ' -i'
262
263     def pidfile(self):
264         return "/var/run/%s-%d.pid" % (self.command, self.port)
265
266     def command_line(self):
267         return string.join(map(str,('-s', self.send_mem, '-r', self.recv_mem, self.flags, self.port)))
268     
269 acceptors = {}
270
271 # start the acceptors
272 def run_acceptors():
273     if config.lctl_dump or config.record:
274         return
275     for port in acceptors.keys():
276         daemon = acceptors[port]
277         if not daemon.running():
278             daemon.start()
279
280 def run_one_acceptor(port):
281     if config.lctl_dump or config.record:
282         return
283     if acceptors.has_key(port):
284         daemon = acceptors[port]
285         if not daemon.running():
286             daemon.start()
287     else:
288          panic("run_one_acceptor: No acceptor defined for port:", port)   
289         
290 def stop_acceptor(port):
291     if acceptors.has_key(port):
292         daemon = acceptors[port]
293         if daemon.running():
294             daemon.stop()
295         
296
297 # ============================================================
298 # handle lctl interface
299 class LCTLInterface:
300     """
301     Manage communication with lctl
302     """
303
304     def __init__(self, cmd):
305         """
306         Initialize close by finding the lctl binary.
307         """
308         self.lctl = find_prog(cmd)
309         self.save_file = ''
310         self.record_device = ''
311         if not self.lctl:
312             if config.noexec:
313                 debug('! lctl not found')
314                 self.lctl = 'lctl'
315             else:
316                 raise CommandError('lctl', "unable to find lctl binary.")
317
318     def use_save_file(self, file):
319         self.save_file = file
320         
321     def record(self, dev_name, logname):
322         log("Recording log", logname, "on", dev_name)
323         self.record_device = dev_name
324         self.record_log = logname
325
326     def end_record(self):
327         log("End recording log", self.record_log, "on", self.record_device)
328         self.record_device = None
329         self.record_log = None
330
331     def set_nonblock(self, fd):
332         fl = fcntl.fcntl(fd, F_GETFL)
333         fcntl.fcntl(fd, F_SETFL, fl | os.O_NDELAY)
334
335     def run(self, cmds):
336         """
337         run lctl
338         the cmds are written to stdin of lctl
339         lctl doesn't return errors when run in script mode, so
340         stderr is checked
341         should modify command line to accept multiple commands, or
342         create complex command line options
343         """
344         cmd_line = self.lctl
345         if self.save_file:
346             cmds = '\n  dump ' + self.save_file + '\n' + cmds
347         elif self.record_device:
348             cmds = """
349     device $%s
350     record %s
351     %s""" % (self.record_device, self.record_log, cmds)
352             
353         debug("+", cmd_line, cmds)
354         if config.noexec: return (0, [])
355
356         child = popen2.Popen3(cmd_line, 1) # Capture stdout and stderr from command
357         child.tochild.write(cmds + "\n")
358         child.tochild.close()
359
360         # From "Python Cookbook" from O'Reilly
361         outfile = child.fromchild
362         outfd = outfile.fileno()
363         self.set_nonblock(outfd)
364         errfile = child.childerr
365         errfd = errfile.fileno()
366         self.set_nonblock(errfd)
367
368         outdata = errdata = ''
369         outeof = erreof = 0
370         while 1:
371             ready = select.select([outfd,errfd],[],[]) # Wait for input
372             if outfd in ready[0]:
373                 outchunk = outfile.read()
374                 if outchunk == '': outeof = 1
375                 outdata = outdata + outchunk
376             if errfd in ready[0]:
377                 errchunk = errfile.read()
378                 if errchunk == '': erreof = 1
379                 errdata = errdata + errchunk
380             if outeof and erreof: break
381         # end of "borrowed" code
382
383         ret = child.wait()
384         if os.WIFEXITED(ret):
385             rc = os.WEXITSTATUS(ret)
386         else:
387             rc = 0
388         if rc or len(errdata):
389             raise CommandError(self.lctl, errdata, rc)
390         return rc, outdata
391
392     def runcmd(self, *args):
393         """
394         run lctl using the command line
395         """
396         cmd = string.join(map(str,args))
397         debug("+", self.lctl, cmd)
398         rc, out = run(self.lctl, cmd)
399         if rc:
400             raise CommandError(self.lctl, out, rc)
401         return rc, out
402
403             
404     def clear_log(self, dev, log):
405         """ clear an existing log """
406         cmds =  """
407   device $%s
408   probe
409   clear_log %s
410   quit """ % (dev, log)
411         self.run(cmds)
412
413     def network(self, net, nid):
414         """ set mynid """
415         cmds =  """
416   network %s
417   mynid %s
418   quit """ % (net, nid)
419         self.run(cmds)
420
421     # create a new connection
422     def add_uuid(self, net_type, uuid, nid):
423         cmds = "\n  add_uuid %s %s %s" %(uuid, nid, net_type)
424         self.run(cmds)
425
426     def add_autoconn(self, net_type, send_mem, recv_mem, nid, hostaddr,
427                      port, flags):
428         if net_type  in ('tcp',) and not config.lctl_dump:
429             cmds =  """
430   network %s
431   send_mem %d
432   recv_mem %d
433   add_autoconn %s %s %d %s
434   quit""" % (net_type,
435              send_mem,
436              recv_mem,
437              nid, hostaddr, port, flags )
438             self.run(cmds)
439     
440     def connect(self, srv):
441         self.add_uuid(srv.net_type, srv.nid_uuid, srv.nid)
442         if srv.net_type  in ('tcp',) and not config.lctl_dump:
443             flags = 's'
444             if srv.irq_affinity:
445                 flags = flags + 'i'
446             self.add_autoconn(srv.net_type, srv.send_mem, srv.recv_mem,
447                  srv.nid, srv.hostaddr, srv.port, flags)
448
449     # Recover a device
450     def recover(self, dev_name, new_conn):
451         cmds = """
452     device $%s
453     recover %s""" %(dev_name, new_conn)
454         self.run(cmds)
455                 
456     # add a route to a range
457     def add_route(self, net, gw, lo, hi):
458         cmds =  """
459   network %s
460   add_route %s %s %s
461   quit  """ % (net,
462                gw, lo, hi)
463         try:
464             self.run(cmds)
465         except CommandError, e:
466             log ("ignore: ")
467             e.dump()
468                 
469     def del_route(self, net, gw, lo, hi):
470         cmds =  """
471   ignore_errors
472   network %s
473   del_route %s %s %s
474   quit  """ % (net, gw, lo, hi)
475         self.run(cmds)
476
477     # add a route to a host
478     def add_route_host(self, net, uuid, gw, tgt):
479         self.add_uuid(net, uuid, tgt)
480         cmds =  """
481   network %s
482   add_route %s %s
483   quit """ % (net,
484               gw, tgt)
485         try:
486             self.run(cmds)
487         except CommandError, e:
488             log ("ignore: ")
489             e.dump()
490
491     # add a route to a range
492     def del_route_host(self, net, uuid, gw, tgt):
493         self.del_uuid(uuid)
494         cmds =  """
495   ignore_errors
496   network %s
497   del_route %s %s
498   quit  """ % (net, gw, tgt)
499         self.run(cmds)
500
501
502     def del_autoconn(self, net_type, nid, hostaddr):
503         if net_type  in ('tcp',) and not config.lctl_dump:
504                 cmds =  """
505   ignore_errors
506   network %s
507   del_autoconn %s %s s
508   quit""" % (net_type,
509              nid, hostaddr)
510                 self.run(cmds)
511         
512     # disconnect one connection
513     def disconnect(self, srv):
514         self.del_uuid(srv.nid_uuid)
515         if srv.net_type  in ('tcp',) and not config.lctl_dump:
516             self.del_autoconn(srv.net_type, srv.nid, srv.hostaddr)
517
518     def del_uuid(self, uuid):
519         cmds =  """
520   ignore_errors
521   del_uuid %s
522   quit""" % (uuid,)
523         self.run(cmds)
524
525     # disconnect all
526     def disconnectAll(self, net):
527         cmds =  """
528   ignore_errors
529   network %s
530   disconnect
531   quit""" % (net)
532         self.run(cmds)
533
534     def attach(self, type, name, uuid):
535         cmds = """
536   attach %s %s %s
537   quit""" % (type, name, uuid)
538         self.run(cmds)
539         
540     def setup(self,  name, setup = ""):
541         cmds = """
542   cfg_device %s
543   setup %s
544   quit""" % (name, setup)
545         self.run(cmds)
546         
547
548     # create a new device with lctl
549     def newdev(self, type, name, uuid, setup = ""):
550         self.attach(type, name, uuid);
551         try:
552             self.setup(name, setup)
553         except CommandError, e:
554             self.cleanup(name, uuid, 0)
555             raise e
556         
557
558     # cleanup a device
559     def cleanup(self, name, uuid, force, failover = 0):
560         if failover: force = 1
561         cmds = """
562   ignore_errors
563   cfg_device $%s
564   cleanup %s %s
565   detach
566   quit""" % (name, ('', 'force')[force],
567              ('', 'failover')[failover])
568         self.run(cmds)
569
570     # create an lov
571     def lov_setup(self, name, uuid, desc_uuid, mdsuuid, stripe_cnt,
572                   stripe_sz, stripe_off,
573                       pattern, devlist):
574         cmds = """
575   attach lov %s %s
576   lov_setup %s %d %d %d %s %s
577   quit""" % (name, uuid, desc_uuid, stripe_cnt, stripe_sz, stripe_off,
578              pattern, devlist)
579         self.run(cmds)
580
581     # create an lov
582     def lov_setconfig(self, uuid, mdsuuid, stripe_cnt, stripe_sz, stripe_off,
583                       pattern, devlist):
584         cmds = """
585   cfg_device $%s
586   lov_setconfig %s %d %d %d %s %s
587   quit""" % (mdsuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
588         self.run(cmds)
589
590     # dump the log file
591     def dump(self, dump_file):
592         cmds = """
593   debug_kernel %s 1
594   quit""" % (dump_file)
595         self.run(cmds)
596
597     # get list of devices
598     def device_list(self):
599         devices = '/proc/fs/lustre/devices'
600         ret = []
601         if os.access(devices, os.R_OK):
602             try:
603                 fp = open(devices, 'r')
604                 ret =  fp.readlines()
605                 fp.close()
606             except IOError, e:
607                 log(e)
608         return ret
609
610     # get lustre version
611     def lustre_version(self):
612         rc, out = self.runcmd('version')
613         return out
614
615     # dump mount options
616     def mount_option(self, profile, osc, mdc):
617         cmds = """
618   mount_option %s %s %s
619   quit""" % (profile, osc, mdc)
620         self.run(cmds)
621
622     # delete mount options
623     def del_mount_option(self, profile):
624         cmds = """
625   del_mount_option %s
626   quit""" % (profile,)
627         self.run(cmds)
628
629     def set_timeout(self, timeout):
630         cmds = """
631   set_timeout %s
632   quit""" % (timeout,)
633         self.run(cmds)
634
635     # delete mount options
636     def set_lustre_upcall(self, upcall):
637         cmds = """
638   set_lustre_upcall %s
639   quit""" % (upcall,)
640         self.run(cmds)
641 # ============================================================
642 # Various system-level functions
643 # (ideally moved to their own module)
644
645 # Run a command and return the output and status.
646 # stderr is sent to /dev/null, could use popen3 to
647 # save it if necessary
648 def runcmd(cmd):
649     debug ("+", cmd)
650     if config.noexec: return (0, [])
651     f = os.popen(cmd + ' 2>&1')
652     out = f.readlines()
653     ret = f.close()
654     if ret:
655         ret = ret >> 8
656     else:
657         ret = 0
658     return (ret, out)
659
660 def run(*args):
661     cmd = string.join(map(str,args))
662     return runcmd(cmd)
663
664 # Run a command in the background.
665 def run_daemon(*args):
666     cmd = string.join(map(str,args))
667     debug ("+", cmd)
668     if config.noexec: return 0
669     f = os.popen(cmd + ' 2>&1')
670     ret = f.close()
671     if ret:
672         ret = ret >> 8
673     else:
674         ret = 0
675     return ret
676
677 # Determine full path to use for an external command
678 # searches dirname(argv[0]) first, then PATH
679 def find_prog(cmd):
680     syspath = string.split(os.environ['PATH'], ':')
681     cmdpath = os.path.dirname(sys.argv[0])
682     syspath.insert(0, cmdpath);
683     if config.portals:
684         syspath.insert(0, os.path.join(config.portals, 'utils/'))
685     for d in syspath:
686         prog = os.path.join(d,cmd)
687         if os.access(prog, os.X_OK):
688             return prog
689     return ''
690
691 # Recursively look for file starting at base dir
692 def do_find_file(base, mod):
693     fullname = os.path.join(base, mod)
694     if os.access(fullname, os.R_OK):
695         return fullname
696     for d in os.listdir(base):
697         dir = os.path.join(base,d)
698         if os.path.isdir(dir):
699             module = do_find_file(dir, mod)
700             if module:
701                 return module
702
703 def find_module(src_dir, dev_dir, modname):
704     modbase = src_dir +'/'+ dev_dir +'/'+ modname
705     for modext in '.ko', '.o':
706         module = modbase + modext
707         try: 
708             if os.access(module, os.R_OK):
709                 return module
710         except OSError:
711             pass
712     return None
713
714 # is the path a block device?
715 def is_block(path):
716     s = ()
717     try:
718         s =  os.stat(path)
719     except OSError:
720         return 0
721     return stat.S_ISBLK(s[stat.ST_MODE])
722
723 # build fs according to type
724 # fixme: dangerous
725 def mkfs(dev, devsize, fstype, jsize, isize, mkfsoptions, isblock=1):
726     block_cnt = ''
727     jopt = ''
728     iopt = ''
729     if devsize:
730         if devsize < 8000:
731             panic("size of filesystem on '%s' must be larger than 8MB, but is set to %s"%
732                   (dev, devsize))
733         # devsize is in 1k, and fs block count is in 4k
734         block_cnt = devsize/4
735
736     if fstype in ('ext3', 'extN', 'ldiskfs'):
737         # ext3 journal size is in megabytes
738         if jsize == 0:
739             if devsize == 0:
740                 if not is_block(dev):
741                     ret, out = runcmd("ls -l %s" %dev)
742                     devsize = int(string.split(out[0])[4]) / 1024
743                 else:
744                     ret, out = runcmd("sfdisk -s %s" %dev)
745                     devsize = int(out[0])
746             if devsize > 1024 * 1024:
747                 jsize = ((devsize / 102400) * 4)
748             if jsize > 400:
749                 jsize = 400        
750         if jsize:  jopt = "-J size=%d" %(jsize,)
751         if isize:  iopt = "-I %d" %(isize,)
752         mkfs = 'mkfs.ext2 -j -b 4096 '
753         if not isblock or config.force:
754             mkfs = mkfs + ' -F '
755     elif fstype == 'reiserfs':
756         # reiserfs journal size is in blocks
757         if jsize:  jopt = "--journal_size %d" %(jsize,)
758         mkfs = 'mkreiserfs -ff'
759     else:
760         panic('unsupported fs type: ', fstype)
761
762     if config.mkfsoptions != None:
763         mkfs = mkfs + ' ' + config.mkfsoptions
764     if mkfsoptions != None:
765         mkfs = mkfs + ' ' + mkfsoptions
766     (ret, out) = run (mkfs, jopt, iopt, dev, block_cnt)
767     if ret:
768         panic("Unable to build fs:", dev, string.join(out))
769     # enable hash tree indexing on fsswe
770     if fstype in ('ext3', 'extN', 'ldiskfs'):
771         htree = 'echo "feature FEATURE_C5" | debugfs -w'
772         (ret, out) = run (htree, dev)
773         if ret:
774             panic("Unable to enable htree:", dev)
775
776 # some systems use /dev/loopN, some /dev/loop/N
777 def loop_base():
778     import re
779     loop = '/dev/loop'
780     if not os.access(loop + str(0), os.R_OK):
781         loop = loop + '/'
782         if not os.access(loop + str(0), os.R_OK):
783             panic ("can't access loop devices")
784     return loop
785     
786 # find loop device assigned to thefile
787 def find_loop(file):
788     loop = loop_base()
789     for n in xrange(0, MAX_LOOP_DEVICES):
790         dev = loop + str(n)
791         if os.access(dev, os.R_OK):
792             (stat, out) = run('losetup', dev)
793             if out and stat == 0:
794                 m = re.search(r'\((.*)\)', out[0])
795                 if m and file == m.group(1):
796                     return dev
797         else:
798             break
799     return ''
800
801 # create file if necessary and assign the first free loop device
802 def init_loop(file, size, fstype, journal_size, inode_size, mkfsoptions, reformat):
803     dev = find_loop(file)
804     if dev:
805         print 'WARNING file:', file, 'already mapped to', dev
806         return dev
807     if reformat or not os.access(file, os.R_OK | os.W_OK):
808         if size < 8000:
809             panic("size of loopback file '%s' must be larger than 8MB, but is set to %s" % (file,size))
810         (ret, out) = run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size,
811                                                                          file))
812         if ret:
813             panic("Unable to create backing store:", file)
814         mkfs(file, size, fstype, journal_size, inode_size, mkfsoptions, isblock=0)
815
816     loop = loop_base()
817     # find next free loop
818     for n in xrange(0, MAX_LOOP_DEVICES):
819         dev = loop + str(n)
820         if os.access(dev, os.R_OK):
821             (stat, out) = run('losetup', dev)
822             if stat:
823                 run('losetup', dev, file)
824                 return dev
825         else:
826             print "out of loop devices"
827             return ''
828     print "out of loop devices"
829     return ''
830
831 # undo loop assignment
832 def clean_loop(file):
833     dev = find_loop(file)
834     if dev:
835         ret, out = run('losetup -d', dev)
836         if ret:
837             log('unable to clean loop device:', dev, 'for file:', file)
838             logall(out)
839
840 # determine if dev is formatted as a <fstype> filesystem
841 def need_format(fstype, dev):
842     # FIXME don't know how to implement this    
843     return 0
844
845 # initialize a block device if needed
846 def block_dev(dev, size, fstype, reformat, autoformat, journal_size,
847               inode_size, mkfsoptions):
848     if config.noexec: return dev
849     if not is_block(dev):
850         dev = init_loop(dev, size, fstype, journal_size, inode_size,
851                         mkfsoptions, reformat)
852     elif reformat or (need_format(fstype, dev) and autoformat == 'yes'):
853         mkfs(dev, size, fstype, journal_size, inode_size, mkfsoptions,
854              isblock=0)
855 #    else:
856 #        panic("device:", dev,
857 #              "not prepared, and autoformat is not set.\n",
858 #              "Rerun with --reformat option to format ALL filesystems")
859         
860     return dev
861
862 def if2addr(iface):
863     """lookup IP address for an interface"""
864     rc, out = run("/sbin/ifconfig", iface)
865     if rc or not out:
866        return None
867     addr = string.split(out[1])[1]
868     ip = string.split(addr, ':')[1]
869     return ip
870
871 def sys_get_elan_position_file():
872     procfiles = ["/proc/elan/device0/position",
873                  "/proc/qsnet/elan4/device0/position",
874                  "/proc/qsnet/elan3/device0/position"]
875     for p in procfiles:
876         if os.access(p, os.R_OK):
877             return p
878     return ""
879
880 def sys_get_local_nid(net_type, wildcard, cluster_id):
881     """Return the local nid."""
882     local = ""
883     if sys_get_elan_position_file():
884         local = sys_get_local_address('elan', '*', cluster_id)
885     else:
886         local = sys_get_local_address(net_type, wildcard, cluster_id)
887     return local
888         
889 def sys_get_local_address(net_type, wildcard, cluster_id):
890     """Return the local address for the network type."""
891     local = ""
892     if net_type in ('tcp',):
893         if  ':' in wildcard:
894             iface, star = string.split(wildcard, ':')
895             local = if2addr(iface)
896             if not local:
897                 panic ("unable to determine ip for:", wildcard)
898         else:
899             host = socket.gethostname()
900             local = socket.gethostbyname(host)
901     elif net_type == 'elan':
902         # awk '/NodeId/ { print $2 }' 'sys_get_elan_position_file()'
903         f = sys_get_elan_position_file()
904         if not f:
905             panic ("unable to determine local Elan ID")
906         try:
907             fp = open(f, 'r')
908             lines = fp.readlines()
909             fp.close()
910             for l in lines:
911                 a = string.split(l)
912                 if a[0] == 'NodeId':
913                     elan_id = a[1]
914                     break
915             try:
916                 nid = my_int(cluster_id) + my_int(elan_id) 
917                 local = "%d" % (nid)
918             except ValueError, e:
919                 local = elan_id
920         except IOError, e:
921             log(e)
922     elif net_type == 'gm':
923         fixme("automatic local address for GM")
924     elif net_type == 'scimac':
925         scinode="/opt/scali/sbin/scinode"
926         if os.path.exists(scinode):
927             (rc,local) = run(scinode)
928         else:
929             panic (scinode, " not found on node with scimac networking")
930         if rc:
931             panic (scinode, " failed")
932         local=string.rstrip(local[0])
933
934     return local
935
936 def mod_loaded(modname):
937     """Check if a module is already loaded. Look in /proc/modules for it."""
938     try:
939         fp = open('/proc/modules')
940         lines = fp.readlines()
941         fp.close()
942         # please forgive my tired fingers for this one
943         ret = filter(lambda word, mod=modname: word == mod,
944                      map(lambda line: string.split(line)[0], lines))
945         return ret
946     except Exception, e:
947         return 0
948
949 # XXX: instead of device_list, ask for $name and see what we get
950 def is_prepared(name):
951     """Return true if a device exists for the name"""
952     if config.lctl_dump:
953         return 0
954     if (config.noexec or config.record) and config.cleanup:
955         return 1
956     try:
957         # expect this format:
958         # 1 UP ldlm ldlm ldlm_UUID 2
959         out = lctl.device_list()
960         for s in out:
961             if name == string.split(s)[3]:
962                 return 1
963     except CommandError, e:
964         e.dump()
965     return 0
966
967 def is_network_prepared():
968     """If the any device exists, then assume that all networking
969        has been configured"""
970     out = lctl.device_list()
971     return len(out) > 0
972
973 def fs_is_mounted(path):
974     """Return true if path is a mounted lustre filesystem"""
975     try:
976         fp = open('/proc/mounts')
977         lines = fp.readlines()
978         fp.close()
979         for l in lines:
980             a = string.split(l)
981             if a[1] == path and a[2] == 'lustre_lite':
982                 return 1
983     except IOError, e:
984         log(e)
985     return 0
986         
987
988 class kmod:
989     """Manage kernel modules"""
990     def __init__(self, lustre_dir, portals_dir):
991         self.lustre_dir = lustre_dir
992         self.portals_dir = portals_dir
993         self.kmodule_list = []
994
995     def add_portals_module(self, dev_dir, modname):
996         """Append a module to list of modules to load."""
997         self.kmodule_list.append((self.portals_dir, dev_dir, modname))
998
999     def add_lustre_module(self, dev_dir, modname):
1000         """Append a module to list of modules to load."""
1001         self.kmodule_list.append((self.lustre_dir, dev_dir, modname))
1002
1003     def load_module(self):
1004         """Load all the modules in the list in the order they appear."""
1005         for src_dir, dev_dir, mod in self.kmodule_list:
1006             if mod_loaded(mod) and not config.noexec:
1007                 continue
1008             log ('loading module:', mod, 'srcdir', src_dir, 'devdir', dev_dir)
1009             if src_dir:
1010                 module = find_module(src_dir, dev_dir,  mod)
1011                 if not module:
1012                     panic('module not found:', mod)
1013                 (rc, out)  = run('/sbin/insmod', module)
1014                 if rc:
1015                     raise CommandError('insmod', out, rc)
1016             else:
1017                 (rc, out) = run('/sbin/modprobe', mod)
1018                 if rc:
1019                     raise CommandError('modprobe', out, rc)
1020
1021     def cleanup_module(self):
1022         """Unload the modules in the list in reverse order."""
1023         rev = self.kmodule_list
1024         rev.reverse()
1025         for src_dir, dev_dir, mod in rev:
1026             if not mod_loaded(mod) and not config.noexec:
1027                 continue
1028             # debug hack
1029             if mod == 'portals' and config.dump:
1030                 lctl.dump(config.dump)
1031             log('unloading module:', mod)
1032             (rc, out) = run('/sbin/rmmod', mod)
1033             if rc:
1034                 log('! unable to unload module:', mod)
1035                 logall(out)
1036
1037 # ============================================================
1038 # Classes to prepare and cleanup the various objects
1039 #
1040 class Module:
1041     """ Base class for the rest of the modules. The default cleanup method is
1042     defined here, as well as some utilitiy funcs.
1043     """
1044     def __init__(self, module_name, db):
1045         self.db = db
1046         self.module_name = module_name
1047         self.name = self.db.getName()
1048         self.uuid = self.db.getUUID()
1049         self._server = None
1050         self._connected = 0
1051         self.kmod = kmod(config.lustre, config.portals)
1052         
1053     def info(self, *args):
1054         msg = string.join(map(str,args))
1055         print self.module_name + ":", self.name, self.uuid, msg
1056
1057     def cleanup(self):
1058         """ default cleanup, used for most modules """
1059         self.info()
1060         try:
1061             lctl.cleanup(self.name, self.uuid, config.force)
1062         except CommandError, e:
1063             log(self.module_name, "cleanup failed: ", self.name)
1064             e.dump()
1065             cleanup_error(e.rc)
1066             
1067     def add_portals_module(self, dev_dir, modname):
1068         """Append a module to list of modules to load."""
1069         self.kmod.add_portals_module(dev_dir, modname)
1070
1071     def add_lustre_module(self, dev_dir, modname):
1072         """Append a module to list of modules to load."""
1073         self.kmod.add_lustre_module(dev_dir, modname)
1074
1075     def load_module(self):
1076         """Load all the modules in the list in the order they appear."""
1077         self.kmod.load_module()
1078             
1079     def cleanup_module(self):
1080         """Unload the modules in the list in reverse order."""
1081         if self.safe_to_clean():
1082             self.kmod.cleanup_module()
1083
1084     def safe_to_clean(self):
1085         return 1
1086         
1087     def safe_to_clean_modules(self):
1088         return self.safe_to_clean()
1089         
1090 class Network(Module):
1091     def __init__(self,db):
1092         Module.__init__(self, 'NETWORK', db)
1093         self.net_type = self.db.get_val('nettype')
1094         self.nid = self.db.get_val('nid', '*')
1095         self.cluster_id = self.db.get_val('clusterid', "0")
1096         self.port = self.db.get_val_int('port', 0)
1097         self.send_mem = self.db.get_val_int('sendmem', DEFAULT_TCPBUF)
1098         self.recv_mem = self.db.get_val_int('recvmem', DEFAULT_TCPBUF)
1099         self.irq_affinity = self.db.get_val_int('irqaffinity', 0)
1100
1101         if '*' in self.nid:
1102             self.nid = sys_get_local_nid(self.net_type, self.nid, self.cluster_id)
1103             if not self.nid:
1104                 panic("unable to set nid for", self.net_type, self.nid, cluster_id)
1105             self.generic_nid = 1
1106             debug("nid:", self.nid)
1107         else:
1108             self.generic_nid = 0
1109
1110         self.nid_uuid = self.nid_to_uuid(self.nid)
1111
1112         self.hostaddr = self.db.get_val('hostaddr', self.nid)
1113         if '*' in self.hostaddr:
1114             self.hostaddr = sys_get_local_address(self.net_type, self.hostaddr, self.cluster_id)
1115             if not self.hostaddr:
1116                 panic("unable to set hostaddr for", self.net_type, self.hostaddr, self.cluster_id)
1117             debug("hostaddr:", self.hostaddr)
1118
1119         self.add_portals_module("libcfs", 'libcfs')
1120         self.add_portals_module("portals", 'portals')
1121         if node_needs_router():
1122             self.add_portals_module("router", 'kptlrouter')
1123         if self.net_type == 'tcp':
1124             self.add_portals_module("knals/socknal", 'ksocknal')
1125         if self.net_type == 'elan':
1126             self.add_portals_module("knals/qswnal", 'kqswnal')
1127         if self.net_type == 'gm':
1128             self.add_portals_module("knals/gmnal", 'kgmnal')
1129         if self.net_type == 'scimac':
1130             self.add_portals_module("knals/scimacnal", 'kscimacnal')
1131
1132     def nid_to_uuid(self, nid):
1133         return "NID_%s_UUID" %(nid,)
1134
1135     def prepare(self):
1136         if is_network_prepared():
1137             return
1138         self.info(self.net_type, self.nid, self.port)
1139         if not (config.record and self.generic_nid):
1140             lctl.network(self.net_type, self.nid)
1141         if self.net_type == 'tcp':
1142             sys_tweak_socknal()
1143         if self.net_type == 'elan':
1144             sys_optimize_elan()
1145         if self.port and  node_is_router():
1146             run_one_acceptor(self.port)
1147             self.connect_peer_gateways()
1148
1149     def connect_peer_gateways(self):
1150         for router in self.db.lookup_class('node'):
1151             if router.get_val_int('router', 0):
1152                 for netuuid in router.get_networks():
1153                     net = self.db.lookup(netuuid)
1154                     gw = Network(net)
1155                     if (gw.cluster_id == self.cluster_id and
1156                         gw.net_type == self.net_type):
1157                         if gw.nid != self.nid:
1158                             lctl.connect(gw)
1159
1160     def disconnect_peer_gateways(self):
1161         for router in self.db.lookup_class('node'):
1162             if router.get_val_int('router', 0):
1163                 for netuuid in router.get_networks():
1164                     net = self.db.lookup(netuuid)
1165                     gw = Network(net)
1166                     if (gw.cluster_id == self.cluster_id and
1167                         gw.net_type == self.net_type):
1168                         if gw.nid != self.nid:
1169                             try:
1170                                 lctl.disconnect(gw)
1171                             except CommandError, e:
1172                                 print "disconnect failed: ", self.name
1173                                 e.dump()
1174                                 cleanup_error(e.rc)
1175
1176     def safe_to_clean(self):
1177         return not is_network_prepared()
1178
1179     def cleanup(self):
1180         self.info(self.net_type, self.nid, self.port)
1181         if self.port:
1182             stop_acceptor(self.port)
1183         if  node_is_router():
1184             self.disconnect_peer_gateways()
1185
1186 class RouteTable(Module):
1187     def __init__(self,db):
1188         Module.__init__(self, 'ROUTES', db)
1189
1190     def server_for_route(self, net_type, gw, gw_cluster_id, tgt_cluster_id,
1191                          lo, hi):
1192         # only setup connections for tcp NALs
1193         srvdb = None
1194         if not net_type in ('tcp',):
1195             return None
1196
1197         # connect to target if route is to single node and this node is the gw
1198         if lo == hi and local_interface(net_type, gw_cluster_id, gw):
1199             if not local_cluster(net_type, tgt_cluster_id):
1200                 panic("target", lo, " not on the local cluster")
1201             srvdb = self.db.nid2server(lo, net_type, gw_cluster_id)
1202         # connect to gateway if this node is not the gw
1203         elif (local_cluster(net_type, gw_cluster_id)
1204               and not local_interface(net_type, gw_cluster_id, gw)):
1205             srvdb = self.db.nid2server(gw, net_type, gw_cluster_id)
1206         else:
1207             return None
1208
1209         if not srvdb:
1210             panic("no server for nid", lo)
1211             return None
1212
1213         return Network(srvdb)
1214         
1215     def prepare(self):
1216         if is_network_prepared():
1217             return
1218         self.info()
1219         for net_type, gw, gw_cluster_id, tgt_cluster_id, lo, hi in self.db.get_route_tbl():
1220             lctl.add_route(net_type, gw, lo, hi)
1221             srv = self.server_for_route(net_type, gw, gw_cluster_id, tgt_cluster_id, lo, hi)
1222             if srv:
1223                 lctl.connect(srv)
1224
1225     def safe_to_clean(self):
1226         return not is_network_prepared()
1227
1228     def cleanup(self):
1229         if is_network_prepared():
1230             # the network is still being used, don't clean it up
1231             return
1232         for net_type, gw, gw_cluster_id, tgt_cluster_id, lo, hi in self.db.get_route_tbl():
1233             srv = self.server_for_route(net_type, gw, gw_cluster_id, tgt_cluster_id, lo, hi)
1234             if srv:
1235                 try:
1236                     lctl.disconnect(srv)
1237                 except CommandError, e:
1238                     print "disconnect failed: ", self.name
1239                     e.dump()
1240                     cleanup_error(e.rc)
1241
1242             try:
1243                 lctl.del_route(net_type, gw, lo, hi)
1244             except CommandError, e:
1245                 print "del_route failed: ", self.name
1246                 e.dump()
1247                 cleanup_error(e.rc)
1248
1249 class Management(Module):
1250     def __init__(self, db):
1251         Module.__init__(self, 'MGMT', db)
1252         self.add_lustre_module('lvfs', 'lvfs')
1253         self.add_lustre_module('obdclass', 'obdclass')
1254         self.add_lustre_module('ptlrpc', 'ptlrpc')
1255         self.add_lustre_module('mgmt', 'mgmt_svc')
1256
1257     def prepare(self):
1258         if is_prepared(self.name):
1259             return
1260         self.info()
1261         lctl.newdev("mgmt", self.name, self.uuid)
1262
1263     def safe_to_clean(self):
1264         return 1
1265
1266     def cleanup(self):
1267         if is_prepared(self.name):
1268             Module.cleanup(self)
1269
1270 # This is only needed to load the modules; the LDLM device
1271 # is now created automatically.
1272 class LDLM(Module):
1273     def __init__(self,db):
1274         Module.__init__(self, 'LDLM', db)
1275         self.add_lustre_module('lvfs', 'lvfs')
1276         self.add_lustre_module('obdclass', 'obdclass')
1277         self.add_lustre_module('ptlrpc', 'ptlrpc')
1278
1279     def prepare(self):
1280         return
1281
1282     def cleanup(self):
1283         return
1284
1285 class LOV(Module):
1286     def __init__(self, db, uuid, fs_name, name_override = None, config_only = None):
1287         Module.__init__(self, 'LOV', db)
1288         if name_override != None:
1289             self.name = "lov_%s" % name_override
1290         self.add_lustre_module('lov', 'lov')
1291         self.mds_uuid = self.db.get_first_ref('mds')
1292         self.stripe_sz = self.db.get_val_int('stripesize', 65536)
1293         self.stripe_off = self.db.get_val_int('stripeoffset', 0)
1294         self.pattern = self.db.get_val_int('stripepattern', 0)
1295         self.devlist = self.db.get_refs('obd')
1296         self.stripe_cnt = self.db.get_val_int('stripecount', len(self.devlist))
1297         self.osclist = []
1298         self.desc_uuid = self.uuid
1299         self.uuid = generate_client_uuid(self.name)
1300         self.fs_name = fs_name
1301         if config_only:
1302             self.config_only = 1
1303             return
1304         self.config_only = None
1305         mds= self.db.lookup(self.mds_uuid)
1306         self.mds_name = mds.getName()
1307         for obd_uuid in self.devlist:
1308             obd = self.db.lookup(obd_uuid)
1309             osc = get_osc(obd, self.uuid, fs_name)
1310             if osc:
1311                 self.osclist.append(osc)
1312             else:
1313                 panic('osc not found:', obd_uuid)
1314             
1315     def prepare(self):
1316         if is_prepared(self.name):
1317             return
1318         if self.config_only:
1319             panic("Can't prepare config_only LOV ", self.name)
1320             
1321         for osc in self.osclist:
1322             try:
1323                 # Only ignore connect failures with --force, which
1324                 # isn't implemented here yet.
1325                 osc.prepare(ignore_connect_failure=0)
1326             except CommandError, e:
1327                 print "Error preparing OSC %s\n" % osc.uuid
1328                 raise e
1329         self.info(self.mds_uuid, self.stripe_cnt, self.stripe_sz,
1330                   self.stripe_off, self.pattern, self.devlist, self.mds_name)
1331         lctl.lov_setup(self.name, self.uuid,
1332                        self.desc_uuid, self.mds_name, self.stripe_cnt,
1333                        self.stripe_sz, self.stripe_off, self.pattern,
1334                        string.join(self.devlist))
1335
1336     def cleanup(self):
1337         if is_prepared(self.name):
1338             Module.cleanup(self)
1339         if self.config_only:
1340             panic("Can't clean up config_only LOV ", self.name)
1341         for osc in self.osclist:
1342             osc.cleanup()
1343
1344     def load_module(self):
1345         if self.config_only:
1346             panic("Can't load modules for config_only LOV ", self.name)
1347         for osc in self.osclist:
1348             osc.load_module()
1349             break
1350         Module.load_module(self)
1351
1352     def cleanup_module(self):
1353         if self.config_only:
1354             panic("Can't cleanup modules for config_only LOV ", self.name)
1355         Module.cleanup_module(self)
1356         for osc in self.osclist:
1357             osc.cleanup_module()
1358             break
1359
1360 class MDSDEV(Module):
1361     def __init__(self,db):
1362         Module.__init__(self, 'MDSDEV', db)
1363         self.devpath = self.db.get_val('devpath','')
1364         self.size = self.db.get_val_int('devsize', 0)
1365         self.journal_size = self.db.get_val_int('journalsize', 0)
1366         self.fstype = self.db.get_val('fstype', '')
1367         self.nspath = self.db.get_val('nspath', '')
1368         self.mkfsoptions = self.db.get_val('mkfsoptions', '')
1369         # overwrite the orignal MDSDEV name and uuid with the MDS name and uuid
1370         target_uuid = self.db.get_first_ref('target')
1371         mds = self.db.lookup(target_uuid)
1372         self.name = mds.getName()
1373         self.filesystem_uuids = mds.get_refs('filesystem')
1374         # FIXME: if fstype not set, then determine based on kernel version
1375         self.format = self.db.get_val('autoformat', "no")
1376         if mds.get_val('failover', 0):
1377             self.failover_mds = 'f'
1378         else:
1379             self.failover_mds = 'n'
1380         active_uuid = get_active_target(mds)
1381         if not active_uuid:
1382             panic("No target device found:", target_uuid)
1383         if active_uuid == self.uuid:
1384             self.active = 1
1385         else:
1386             self.active = 0
1387         if self.active and config.group and config.group != mds.get_val('group'):
1388             self.active = 0
1389
1390         self.inode_size = self.db.get_val_int('inodesize', 0)
1391         if self.inode_size == 0:
1392             # find the LOV for this MDS
1393             lovconfig_uuid = mds.get_first_ref('lovconfig')
1394             if not lovconfig_uuid:
1395                 panic("No LOV config found for MDS ", mds.name)
1396             lovconfig = mds.lookup(lovconfig_uuid)
1397             lov_uuid = lovconfig.get_first_ref('lov')
1398             if not lov_uuid:
1399                 panic("No LOV found for lovconfig ", lovconfig.name)
1400             lov = LOV(self.db.lookup(lov_uuid), lov_uuid, 'FS_name', config_only = 1)
1401
1402             # default stripe count controls default inode_size
1403             stripe_count = lov.stripe_cnt
1404             if stripe_count > 77:
1405                 self.inode_size = 4096
1406             elif stripe_count > 35:
1407                 self.inode_size = 2048
1408             elif stripe_count > 13:
1409                 self.inode_size = 1024
1410             elif stripe_count > 3:
1411                 self.inode_size = 512
1412             else:
1413                 self.inode_size = 256
1414
1415         self.target_dev_uuid = self.uuid
1416         self.uuid = target_uuid
1417         # modules
1418         self.add_lustre_module('mdc', 'mdc')
1419         self.add_lustre_module('osc', 'osc')
1420         self.add_lustre_module('lov', 'lov')
1421         self.add_lustre_module('mds', 'mds')
1422         if self.fstype == 'ldiskfs':
1423             self.add_lustre_module('ldiskfs', 'ldiskfs')
1424         if self.fstype:
1425             self.add_lustre_module('lvfs', 'fsfilt_%s' % (self.fstype))
1426
1427     def load_module(self):
1428         if self.active:
1429             Module.load_module(self)
1430             
1431     def prepare(self):
1432         if is_prepared(self.name):
1433             return
1434         if not self.active:
1435             debug(self.uuid, "not active")
1436             return
1437         if config.reformat:
1438             # run write_conf automatically, if --reformat used
1439             self.write_conf()
1440         self.info(self.devpath, self.fstype, self.size, self.format)
1441         run_acceptors()
1442         # never reformat here
1443         blkdev = block_dev(self.devpath, self.size, self.fstype, 0,
1444                            self.format, self.journal_size, self.inode_size,
1445                            self.mkfsoptions)
1446         if not is_prepared('MDT'):
1447             lctl.newdev("mdt", 'MDT', 'MDT_UUID', setup ="")
1448         try: 
1449             lctl.newdev("mds", self.name, self.uuid,
1450                         setup ="%s %s %s" %(blkdev, self.fstype, self.name))
1451         except CommandError, e:
1452             if e.rc == 2:
1453                 panic("MDS is missing the config log. Need to run " +
1454                        "lconf --write_conf.")
1455             else:
1456                 raise e
1457
1458     def write_conf(self):
1459         if is_prepared(self.name):
1460             return
1461         self.info(self.devpath, self.fstype, self.format)
1462         blkdev = block_dev(self.devpath, self.size, self.fstype,
1463                            config.reformat, self.format, self.journal_size,
1464                            self.inode_size, self.mkfsoptions)
1465         lctl.newdev("mds", self.name, self.uuid,
1466                     setup ="%s %s" %(blkdev, self.fstype))
1467                 
1468         # record logs for the MDS lov
1469         for uuid in self.filesystem_uuids:
1470             log("recording clients for filesystem:", uuid)
1471             fs = self.db.lookup(uuid)
1472             obd_uuid = fs.get_first_ref('obd')
1473             client_uuid = generate_client_uuid(self.name)
1474             client = VOSC(self.db.lookup(obd_uuid), client_uuid, self.name,
1475                           self.name)
1476             config.record = 1
1477             lctl.clear_log(self.name, self.name)
1478             lctl.record(self.name, self.name)
1479             client.prepare()
1480             lctl.mount_option(self.name, client.get_name(), "")
1481             lctl.end_record()
1482
1483             config.cleanup = 1
1484             lctl.clear_log(self.name, self.name + '-clean')
1485             lctl.record(self.name, self.name + '-clean')
1486             client.cleanup()
1487             lctl.del_mount_option(self.name)
1488             lctl.end_record()
1489             config.cleanup = 0
1490             config.record = 0
1491
1492         # record logs for each client
1493         if config.ldapurl:
1494             config_options = "--ldapurl " + config.ldapurl + " --config " + config.config
1495         else:
1496             config_options = CONFIG_FILE
1497
1498         for node_db in self.db.lookup_class('node'):
1499             client_name = node_db.getName()
1500             for prof_uuid in node_db.get_refs('profile'):
1501                 prof_db = node_db.lookup(prof_uuid)
1502                 # refactor this into a funtion to test "clientness"
1503                 # of a node.
1504                 for ref_class, ref_uuid in prof_db.get_all_refs():
1505                     if ref_class in ('mountpoint','echoclient'):
1506                         debug("recording", client_name)
1507                         old_noexec = config.noexec
1508                         config.noexec = 0
1509                         noexec_opt = ('', '-n')
1510                         ret, out = run (sys.argv[0],
1511                                         noexec_opt[old_noexec == 1],
1512                                         " -v --record --nomod",
1513                                         "--record_log", client_name,
1514                                         "--record_device", self.name,
1515                                         "--node", client_name,
1516                                         config_options)
1517                         if config.verbose:
1518                             for s in out: log("record> ", string.strip(s))
1519                         ret, out = run (sys.argv[0],
1520                                         noexec_opt[old_noexec == 1],
1521                                         "--cleanup -v --record --nomod",
1522                                         "--record_log", client_name + "-clean",
1523                                         "--record_device", self.name,
1524                                         "--node", client_name,
1525                                         config_options)
1526                         if config.verbose:
1527                             for s in out: log("record> ", string.strip(s))
1528                         config.noexec = old_noexec
1529         try:
1530             lctl.cleanup(self.name, self.uuid, 0, 0)
1531         except CommandError, e:
1532             log(self.module_name, "cleanup failed: ", self.name)
1533             e.dump()
1534             cleanup_error(e.rc)
1535             Module.cleanup(self)
1536         clean_loop(self.devpath)
1537  
1538     def msd_remaining(self):
1539         out = lctl.device_list()
1540         for s in out:
1541             if string.split(s)[2] in ('mds',):
1542                 return 1
1543
1544     def safe_to_clean(self):
1545         return self.active
1546
1547     def safe_to_clean_modules(self):
1548         return not self.msd_remaining()
1549
1550     def cleanup(self):
1551         if not self.active:
1552             debug(self.uuid, "not active")
1553             return
1554         self.info()
1555         if is_prepared(self.name):
1556             try:
1557                 lctl.cleanup(self.name, self.uuid, config.force,
1558                              config.failover)
1559             except CommandError, e:
1560                 log(self.module_name, "cleanup failed: ", self.name)
1561                 e.dump()
1562                 cleanup_error(e.rc)
1563                 Module.cleanup(self)
1564         if not self.msd_remaining() and is_prepared('MDT'):
1565             try:
1566                 lctl.cleanup("MDT", "MDT_UUID", config.force,
1567                              config.failover)
1568             except CommandError, e:
1569                 print "cleanup failed: ", self.name
1570                 e.dump()
1571                 cleanup_error(e.rc)
1572         clean_loop(self.devpath)
1573
1574 class OSD(Module):
1575     def __init__(self, db):
1576         Module.__init__(self, 'OSD', db)
1577         self.osdtype = self.db.get_val('osdtype')
1578         self.devpath = self.db.get_val('devpath', '')
1579         self.size = self.db.get_val_int('devsize', 0)
1580         self.journal_size = self.db.get_val_int('journalsize', 0)
1581         self.inode_size = self.db.get_val_int('inodesize', 0)
1582         self.mkfsoptions = self.db.get_val('mkfsoptions', '')
1583         self.fstype = self.db.get_val('fstype', '')
1584         self.nspath = self.db.get_val('nspath', '')
1585         target_uuid = self.db.get_first_ref('target')
1586         ost = self.db.lookup(target_uuid)
1587         self.name = ost.getName()
1588         self.format = self.db.get_val('autoformat', 'yes')
1589         if ost.get_val('failover', 0):
1590             self.failover_ost = 'f'
1591         else:
1592             self.failover_ost = 'n'
1593
1594         active_uuid = get_active_target(ost)
1595         if not active_uuid:
1596             panic("No target device found:", target_uuid)
1597         if active_uuid == self.uuid:
1598             self.active = 1
1599         else:
1600             self.active = 0
1601         if self.active and config.group and config.group != ost.get_val('group'):
1602             self.active = 0
1603             
1604         self.target_dev_uuid = self.uuid
1605         self.uuid = target_uuid
1606         # modules
1607         self.add_lustre_module('ost', 'ost')
1608         # FIXME: should we default to ext3 here?
1609         if self.fstype == 'ldiskfs':
1610             self.add_lustre_module('ldiskfs', 'ldiskfs')
1611         if self.fstype:
1612             self.add_lustre_module('lvfs' , 'fsfilt_%s' % (self.fstype))
1613         self.add_lustre_module(self.osdtype, self.osdtype)
1614
1615     def load_module(self):
1616         if self.active:
1617             Module.load_module(self)
1618
1619     # need to check /proc/mounts and /etc/mtab before
1620     # formatting anything.
1621     # FIXME: check if device is already formatted.
1622     def prepare(self):
1623         if is_prepared(self.name):
1624             return
1625         if not self.active:
1626             debug(self.uuid, "not active")
1627             return
1628         self.info(self.osdtype, self.devpath, self.size, self.fstype,
1629                   self.format, self.journal_size, self.inode_size)
1630         run_acceptors()
1631         if self.osdtype == 'obdecho':
1632             blkdev = ''
1633         else:
1634             blkdev = block_dev(self.devpath, self.size, self.fstype,
1635                                config.reformat, self.format, self.journal_size,
1636                                self.inode_size, self.mkfsoptions)
1637         lctl.newdev(self.osdtype, self.name, self.uuid,
1638                     setup ="%s %s %s" %(blkdev, self.fstype,
1639                                            self.failover_ost))
1640         if not is_prepared('OSS'):
1641             lctl.newdev("ost", 'OSS', 'OSS_UUID', setup ="")
1642
1643     def osd_remaining(self):
1644         out = lctl.device_list()
1645         for s in out:
1646             if string.split(s)[2] in ('obdfilter', 'obdecho'):
1647                 return 1
1648
1649     def safe_to_clean(self):
1650         return self.active
1651
1652     def safe_to_clean_modules(self):
1653         return not self.osd_remaining()
1654
1655     def cleanup(self):
1656         if not self.active:
1657             debug(self.uuid, "not active")
1658             return
1659         if is_prepared(self.name):
1660             self.info()
1661             try:
1662                 lctl.cleanup(self.name, self.uuid, config.force,
1663                              config.failover)
1664             except CommandError, e:
1665                 log(self.module_name, "cleanup failed: ", self.name)
1666                 e.dump()
1667                 cleanup_error(e.rc)
1668         if not self.osd_remaining() and is_prepared('OSS'):
1669             try:
1670                 lctl.cleanup("OSS", "OSS_UUID", config.force,
1671                              config.failover)
1672             except CommandError, e:
1673                 print "cleanup failed: ", self.name
1674                 e.dump()
1675                 cleanup_error(e.rc)
1676         if not self.osdtype == 'obdecho':
1677             clean_loop(self.devpath)
1678
1679 def mgmt_uuid_for_fs(mtpt_name):
1680     if not mtpt_name:
1681         return ''
1682     mtpt_db = toplevel.lookup_name(mtpt_name)
1683     fs_uuid = mtpt_db.get_first_ref('filesystem')
1684     fs = toplevel.lookup(fs_uuid)
1685     if not fs:
1686         return ''
1687     return fs.get_first_ref('mgmt')
1688
1689 # Generic client module, used by OSC and MDC
1690 class Client(Module):
1691     def __init__(self, tgtdb, uuid, module, fs_name, self_name=None,
1692                  module_dir=None):
1693         self.target_name = tgtdb.getName()
1694         self.target_uuid = tgtdb.getUUID()
1695         self.db = tgtdb
1696
1697         self.tgt_dev_uuid = get_active_target(tgtdb)
1698         if not self.tgt_dev_uuid:
1699             panic("No target device found for target:", self.target_name)
1700             
1701         self.kmod = kmod(config.lustre, config.portals)
1702         self._server = None
1703         self._connected = 0
1704
1705         self.module = module
1706         self.module_name = string.upper(module)
1707         if not self_name:
1708             self.name = '%s_%s_%s_%s' % (self.module_name, socket.gethostname(),
1709                                          self.target_name, fs_name)
1710         else:
1711             self.name = self_name
1712         self.uuid = uuid
1713         self.lookup_server(self.tgt_dev_uuid)
1714         mgmt_uuid = mgmt_uuid_for_fs(fs_name)
1715         if mgmt_uuid:
1716             self.mgmt_name = mgmtcli_name_for_uuid(mgmt_uuid)
1717         else:
1718             self.mgmt_name = ''
1719         self.fs_name = fs_name
1720         if not module_dir:
1721             module_dir = module
1722         self.add_lustre_module(module_dir, module)
1723
1724     def lookup_server(self, srv_uuid):
1725         """ Lookup a server's network information """
1726         self._server_nets = get_ost_net(self.db, srv_uuid)
1727         if len(self._server_nets) == 0:
1728             panic ("Unable to find a server for:", srv_uuid)
1729
1730     def get_servers(self):
1731         return self._server_nets
1732
1733     def prepare(self, ignore_connect_failure = 0):
1734         self.info(self.target_uuid)
1735         if is_prepared(self.name):
1736             self.cleanup()
1737         try:
1738             srv = choose_local_server(self.get_servers())
1739             if srv:
1740                 lctl.connect(srv)
1741             else:
1742                 routes = find_route(self.get_servers())
1743                 if len(routes) == 0:
1744                     panic ("no route to",  self.target_uuid)
1745                 for (srv, r) in routes:
1746                     lctl.add_route_host(r[0], srv.nid_uuid, r[1], r[3])
1747         except CommandError, e:
1748             if not ignore_connect_failure:
1749                 raise e
1750         if srv:
1751             if self.target_uuid in config.inactive and self.permits_inactive():
1752                 debug("%s inactive" % self.target_uuid)
1753                 inactive_p = "inactive"
1754             else:
1755                 debug("%s active" % self.target_uuid)
1756                 inactive_p = ""
1757             lctl.newdev(self.module, self.name, self.uuid,
1758                         setup ="%s %s %s %s" % (self.target_uuid, srv.nid_uuid,
1759                                                 inactive_p, self.mgmt_name))
1760
1761     def cleanup(self):
1762         if is_prepared(self.name):
1763             Module.cleanup(self)
1764             try:
1765                 srv = choose_local_server(self.get_servers())
1766                 if srv:
1767                     lctl.disconnect(srv)
1768                 else:
1769                     for (srv, r) in find_route(self.get_servers()):
1770                         lctl.del_route_host(r[0], srv.nid_uuid, r[1], r[3])
1771             except CommandError, e:
1772                 log(self.module_name, "cleanup failed: ", self.name)
1773                 e.dump()
1774                 cleanup_error(e.rc)
1775
1776
1777 class MDC(Client):
1778     def __init__(self, db, uuid, fs_name):
1779          Client.__init__(self, db, uuid, 'mdc', fs_name)
1780
1781     def permits_inactive(self):
1782         return 0
1783
1784 class OSC(Client):
1785     def __init__(self, db, uuid, fs_name):
1786          Client.__init__(self, db, uuid, 'osc', fs_name)
1787
1788     def permits_inactive(self):
1789         return 1
1790
1791 def mgmtcli_name_for_uuid(uuid):
1792     return 'MGMTCLI_%s' % uuid
1793
1794 class ManagementClient(Client):
1795     def __init__(self, db, uuid):
1796         Client.__init__(self, db, uuid, 'mgmt_cli', '',
1797                         self_name = mgmtcli_name_for_uuid(db.getUUID()),
1798                         module_dir = 'mgmt')
1799             
1800 class COBD(Module):
1801     def __init__(self, db):
1802         Module.__init__(self, 'COBD', db)
1803         self.real_uuid = self.db.get_first_ref('realobd')
1804         self.cache_uuid = self.db.get_first_ref('cacheobd')
1805         self.add_lustre_module('cobd' , 'cobd')
1806
1807     # need to check /proc/mounts and /etc/mtab before
1808     # formatting anything.
1809     # FIXME: check if device is already formatted.
1810     def prepare(self):
1811         if is_prepared(self.name):
1812             return
1813         self.info(self.real_uuid, self.cache_uuid)
1814         lctl.newdev("cobd", self.name, self.uuid,
1815                     setup ="%s %s" %(self.real_uuid, self.cache_uuid))
1816
1817
1818 # virtual interface for  OSC and LOV
1819 class VOSC(Module):
1820     def __init__(self, db, uuid, fs_name, name_override = None):
1821         Module.__init__(self, 'VOSC', db)
1822         if db.get_class() == 'lov':
1823             self.osc = LOV(db, uuid, fs_name, name_override)
1824         else:
1825             self.osc = get_osc(db, uuid, fs_name)
1826     def get_uuid(self):
1827         return self.osc.uuid
1828     def get_name(self):
1829         return self.osc.name
1830     def prepare(self):
1831         self.osc.prepare()
1832     def cleanup(self):
1833         self.osc.cleanup()
1834     def load_module(self):
1835         self.osc.load_module()
1836     def cleanup_module(self):
1837         self.osc.cleanup_module()
1838
1839
1840 class ECHO_CLIENT(Module):
1841     def __init__(self,db):
1842         Module.__init__(self, 'ECHO_CLIENT', db)
1843         self.add_lustre_module('obdecho', 'obdecho')
1844         self.obd_uuid = self.db.get_first_ref('obd')
1845         obd = self.db.lookup(self.obd_uuid)
1846         self.uuid = generate_client_uuid(self.name)
1847         self.osc = VOSC(obd, self.uuid, self.name)
1848
1849     def prepare(self):
1850         if is_prepared(self.name):
1851             return
1852         run_acceptors()
1853         self.osc.prepare() # XXX This is so cheating. -p
1854         self.info(self.obd_uuid)
1855
1856         lctl.newdev("echo_client", self.name, self.uuid,
1857                     setup = self.osc.get_name())
1858
1859     def cleanup(self):
1860         if is_prepared(self.name):
1861             Module.cleanup(self)
1862         self.osc.cleanup()
1863
1864     def load_module(self):
1865         self.osc.load_module()
1866         Module.load_module(self)
1867
1868     def cleanup_module(self):
1869         Module.cleanup_module(self)
1870         self.osc.cleanup_module()
1871
1872
1873 def generate_client_uuid(name):
1874         client_uuid = '%05x_%.19s_%05x%05x' % (int(random.random() * 1048576),
1875                                                name,
1876                                                int(random.random() * 1048576),
1877                                                int(random.random() * 1048576))
1878         return client_uuid[:36]
1879
1880
1881 class Mountpoint(Module):
1882     def __init__(self,db):
1883         Module.__init__(self, 'MTPT', db)
1884         self.path = self.db.get_val('path')
1885         self.fs_uuid = self.db.get_first_ref('filesystem')
1886         fs = self.db.lookup(self.fs_uuid)
1887         self.mds_uuid = fs.get_first_ref('mds')
1888         self.obd_uuid = fs.get_first_ref('obd')
1889         self.mgmt_uuid = fs.get_first_ref('mgmt')
1890         obd = self.db.lookup(self.obd_uuid)
1891         client_uuid = generate_client_uuid(self.name)
1892         self.vosc = VOSC(obd, client_uuid, self.name)
1893         self.mdc = get_mdc(db, client_uuid, self.name, self.mds_uuid)
1894
1895         self.add_lustre_module('mdc', 'mdc')
1896         self.add_lustre_module('llite', 'llite')
1897         if self.mgmt_uuid:
1898             self.mgmtcli = ManagementClient(db.lookup(self.mgmt_uuid),
1899                                             client_uuid)
1900         else:
1901             self.mgmtcli = None
1902
1903     def prepare(self):
1904         if fs_is_mounted(self.path):
1905             log(self.path, "already mounted.")
1906             return
1907         run_acceptors()
1908         if self.mgmtcli:
1909             self.mgmtcli.prepare()
1910         self.vosc.prepare()
1911         self.mdc.prepare()
1912         mdc_name = self.mdc.name
1913
1914         self.info(self.path, self.mds_uuid, self.obd_uuid)
1915         if config.record or config.lctl_dump:
1916             lctl.mount_option(local_node_name, self.vosc.get_name(), mdc_name)
1917             return
1918         cmd = "mount -t lustre_lite -o osc=%s,mdc=%s %s %s" % \
1919               (self.vosc.get_name(), mdc_name, config.config, self.path)
1920         run("mkdir", self.path)
1921         ret, val = run(cmd)
1922         if ret:
1923             self.mdc.cleanup()            
1924             self.vosc.cleanup()
1925             panic("mount failed:", self.path, ":", string.join(val))
1926
1927     def cleanup(self):
1928         self.info(self.path, self.mds_uuid,self.obd_uuid)
1929
1930         if config.record or config.lctl_dump:
1931             lctl.del_mount_option(local_node_name)
1932         else:
1933             if fs_is_mounted(self.path):
1934                 if config.force:
1935                     (rc, out) = run("umount", "-f", self.path)
1936                 else:
1937                     (rc, out) = run("umount", self.path)
1938                 if rc:
1939                     raise CommandError('umount', out, rc)
1940
1941             if fs_is_mounted(self.path):
1942                 panic("fs is still mounted:", self.path)
1943
1944         self.mdc.cleanup()
1945         self.vosc.cleanup()
1946         if self.mgmtcli:
1947             self.mgmtcli.cleanup()
1948
1949     def load_module(self):
1950         if self.mgmtcli:
1951             self.mgmtcli.load_module()
1952         self.vosc.load_module()
1953         Module.load_module(self)
1954
1955     def cleanup_module(self):
1956         Module.cleanup_module(self)
1957         self.vosc.cleanup_module()
1958         if self.mgmtcli:
1959             self.mgmtcli.cleanup_module()
1960
1961
1962 # ============================================================
1963 # misc query functions
1964
1965 def get_ost_net(self, osd_uuid):
1966     srv_list = []
1967     if not osd_uuid:
1968         return srv_list
1969     osd = self.lookup(osd_uuid)
1970     node_uuid = osd.get_first_ref('node')
1971     node = self.lookup(node_uuid)
1972     if not node:
1973         panic("unable to find node for osd_uuid:", osd_uuid,
1974               " node_ref:", node_uuid)
1975     for net_uuid in node.get_networks():
1976         db = node.lookup(net_uuid)
1977         srv_list.append(Network(db))
1978     return srv_list
1979
1980
1981 # the order of iniitailization is based on level. 
1982 def getServiceLevel(self):
1983     type = self.get_class()
1984     ret=0;
1985     if type in ('network',):
1986         ret = 5
1987     elif type in ('routetbl',):
1988         ret = 6
1989     elif type in ('ldlm',):
1990         ret = 20
1991     elif type in ('mgmt',):
1992         ret = 25
1993     elif type in ('osd', 'cobd'):
1994         ret = 30
1995     elif type in ('mdsdev',):
1996         ret = 40
1997     elif type in ('mountpoint', 'echoclient'):
1998         ret = 70
1999     else:
2000         panic("Unknown type: ", type)
2001
2002     if ret < config.minlevel or ret > config.maxlevel:
2003         ret = 0 
2004     return ret
2005
2006 #
2007 # return list of services in a profile. list is a list of tuples
2008 # [(level, db_object),]
2009 def getServices(self):
2010     list = []
2011     for ref_class, ref_uuid in self.get_all_refs(): 
2012             servdb = self.lookup(ref_uuid)
2013             if  servdb:
2014                 level = getServiceLevel(servdb)
2015                 if level > 0:
2016                     list.append((level, servdb))
2017             else:
2018                 panic('service not found: ' + ref_uuid)
2019
2020     list.sort()
2021     return list
2022
2023
2024 ############################################################
2025 # MDC UUID hack - 
2026 # FIXME: clean this mess up!
2027 #
2028 # OSC is no longer in the xml, so we have to fake it.
2029 # this is getting ugly and begging for another refactoring
2030 def get_osc(ost_db, uuid, fs_name):
2031     osc = OSC(ost_db, uuid, fs_name)
2032     return osc
2033
2034 def get_mdc(db, uuid, fs_name, mds_uuid):
2035     mds_db = db.lookup(mds_uuid);
2036     if not mds_db:
2037         panic("no mds:", mds_uuid)
2038     mdc = MDC(mds_db, uuid, fs_name)
2039     return mdc
2040
2041 ############################################################
2042 # routing ("rooting")
2043
2044 # list of (nettype, cluster_id, nid)
2045 local_clusters = []
2046
2047 def find_local_clusters(node_db):
2048     global local_clusters
2049     for netuuid in node_db.get_networks():
2050         net = node_db.lookup(netuuid)
2051         srv = Network(net)
2052         debug("add_local", netuuid)
2053         local_clusters.append((srv.net_type, srv.cluster_id, srv.nid))
2054         if srv.port > 0:
2055             if acceptors.has_key(srv.port):
2056                 panic("duplicate port:", srv.port)
2057             acceptors[srv.port] = AcceptorHandler(srv.port, srv.net_type,
2058                                                   srv.send_mem, srv.recv_mem,
2059                                                   srv.irq_affinity)
2060
2061 # This node is a gateway.
2062 is_router = 0
2063 def node_is_router():
2064     return is_router
2065
2066 # If there are any routers found in the config, then this will be true
2067 # and all nodes will load kptlrouter.
2068 needs_router = 0
2069 def node_needs_router():
2070     return needs_router or is_router
2071
2072 # list of (nettype, gw, tgt_cluster_id, lo, hi)
2073 # Currently, these local routes are only added to kptlrouter route
2074 # table if they are needed to connect to a specific server.  This
2075 # should be changed so all available routes are loaded, and the
2076 # ptlrouter can make all the decisions.
2077 local_routes = []
2078
2079 def find_local_routes(lustre):
2080     """ Scan the lustre config looking for routers .  Build list of
2081     routes. """
2082     global local_routes, needs_router
2083     local_routes = []
2084     list = lustre.lookup_class('node')
2085     for router in list:
2086         if router.get_val_int('router', 0):
2087             needs_router = 1
2088             for (local_type, local_cluster_id, local_nid) in local_clusters:
2089                 gw = None
2090                 for netuuid in router.get_networks():
2091                     db = router.lookup(netuuid)
2092                     if (local_type == db.get_val('nettype') and
2093                        local_cluster_id == db.get_val('clusterid')):
2094                         gw = db.get_val('nid')
2095                         break
2096                 if gw:
2097                     debug("find_local_routes: gw is", gw)
2098                     for route in router.get_local_routes(local_type, gw):
2099                         local_routes.append(route)
2100     debug("find_local_routes:", local_routes)
2101
2102
2103 def choose_local_server(srv_list):
2104     for srv in srv_list:
2105         if local_cluster(srv.net_type, srv.cluster_id):
2106             return srv
2107
2108 def local_cluster(net_type, cluster_id):
2109     for cluster in local_clusters:
2110         if net_type == cluster[0] and cluster_id == cluster[1]:
2111             return 1
2112     return 0
2113
2114 def local_interface(net_type, cluster_id, nid):
2115     for cluster in local_clusters:
2116         if (net_type == cluster[0] and cluster_id == cluster[1]
2117             and nid == cluster[2]):
2118             return 1
2119     return 0
2120
2121 def find_route(srv_list):
2122     result = []
2123     frm_type = local_clusters[0][0]
2124     for srv in srv_list:
2125         debug("find_route: srv:", srv.nid, "type: ", srv.net_type)
2126         to_type = srv.net_type
2127         to = srv.nid
2128         cluster_id = srv.cluster_id
2129         debug ('looking for route to', to_type, to)
2130         for r in local_routes:
2131             debug("find_route: ", r)
2132             if  (r[3] <= to and to <= r[4]) and cluster_id == r[2]:
2133                 result.append((srv, r))
2134     return result
2135            
2136 def get_active_target(db):
2137     target_uuid = db.getUUID()
2138     target_name = db.getName()
2139     node_name = get_select(target_name)
2140     if node_name:
2141         tgt_dev_uuid = db.get_node_tgt_dev(node_name, target_uuid)
2142     else:
2143         tgt_dev_uuid = db.get_first_ref('active')
2144     return tgt_dev_uuid
2145
2146 def get_server_by_nid_uuid(db,  nid_uuid):
2147     for n in db.lookup_class("network"):
2148         net = Network(n)
2149         if net.nid_uuid == nid_uuid:
2150             return net
2151         
2152
2153 ############################################################
2154 # lconf level logic
2155 # Start a service.
2156 def newService(db):
2157     type = db.get_class()
2158     debug('Service:', type, db.getName(), db.getUUID())
2159     n = None
2160     if type == 'ldlm':
2161         n = LDLM(db)
2162     elif type == 'lov':
2163         n = LOV(db, "YOU_SHOULD_NEVER_SEE_THIS_UUID")
2164     elif type == 'network':
2165         n = Network(db)
2166     elif type == 'routetbl':
2167         n = RouteTable(db)
2168     elif type == 'osd':
2169         n = OSD(db)
2170     elif type == 'cobd':
2171         n = COBD(db)
2172     elif type == 'mdsdev':
2173         n = MDSDEV(db)
2174     elif type == 'mountpoint':
2175         n = Mountpoint(db)
2176     elif type == 'echoclient':
2177         n = ECHO_CLIENT(db)
2178     elif type == 'mgmt':
2179         n = Management(db)
2180     else:
2181         panic ("unknown service type:", type)
2182     return n
2183
2184 #
2185 # Prepare the system to run lustre using a particular profile
2186 # in a the configuration. 
2187 #  * load & the modules
2188 #  * setup networking for the current node
2189 #  * make sure partitions are in place and prepared
2190 #  * initialize devices with lctl
2191 # Levels is important, and needs to be enforced.
2192 def for_each_profile(db, prof_list, operation):
2193     for prof_uuid in prof_list:
2194         prof_db = db.lookup(prof_uuid)
2195         if not prof_db:
2196             panic("profile:", profile, "not found.")
2197         services = getServices(prof_db)
2198         operation(services)
2199         
2200 def doWriteconf(services):
2201     if config.nosetup:
2202         return
2203     for s in services:
2204         if s[1].get_class() == 'mdsdev':
2205             n = newService(s[1])
2206             n.write_conf()
2207
2208 def doSetup(services):
2209     if config.nosetup:
2210         return
2211     for s in services:
2212         n = newService(s[1])
2213         n.prepare()
2214     
2215 def doModules(services):
2216     if config.nomod:
2217         return
2218     for s in services:
2219         n = newService(s[1])
2220         n.load_module()
2221
2222 def doCleanup(services):
2223     if config.nosetup:
2224         return
2225     services.reverse()
2226     for s in services:
2227         n = newService(s[1])
2228         if n.safe_to_clean():
2229             n.cleanup()
2230
2231 def doUnloadModules(services):
2232     if config.nomod:
2233         return
2234     services.reverse()
2235     for s in services:
2236         n = newService(s[1])
2237         if n.safe_to_clean_modules():
2238             n.cleanup_module()
2239
2240 #
2241 # Load profile for 
2242 def doHost(lustreDB, hosts):
2243     global is_router, local_node_name
2244     node_db = None
2245     for h in hosts:
2246         node_db = lustreDB.lookup_name(h, 'node')
2247         if node_db:
2248             break
2249     if not node_db:
2250         panic('No host entry found.')
2251
2252     local_node_name = node_db.get_val('name', 0)
2253     is_router = node_db.get_val_int('router', 0)
2254     lustre_upcall = node_db.get_val('lustreUpcall', '')
2255     portals_upcall = node_db.get_val('portalsUpcall', '')
2256     timeout = node_db.get_val_int('timeout', 0)
2257     ptldebug = node_db.get_val('ptldebug', '')
2258     subsystem = node_db.get_val('subsystem', '')
2259     
2260     find_local_clusters(node_db)
2261     if not is_router:
2262         find_local_routes(lustreDB)
2263
2264     # Two step process: (1) load modules, (2) setup lustre
2265     # if not cleaning, load modules first.
2266     prof_list = node_db.get_refs('profile')
2267
2268     if config.write_conf:
2269         for_each_profile(node_db, prof_list, doModules)
2270         sys_make_devices()
2271         for_each_profile(node_db, prof_list, doWriteconf)
2272         for_each_profile(node_db, prof_list, doUnloadModules)
2273
2274     elif config.recover:
2275         if not (config.tgt_uuid and config.client_uuid and config.conn_uuid):
2276             raise Lustre.LconfError( "--recovery requires --tgt_uuid <UUID> " +
2277                                      "--client_uuid <UUID> --conn_uuid <UUID>")
2278         doRecovery(lustreDB, lctl, config.tgt_uuid, config.client_uuid,
2279                    config.conn_uuid)
2280     elif config.cleanup:
2281         if config.force:
2282             # the command line can override this value
2283             timeout = 5
2284         # ugly hack, only need to run lctl commands for --dump
2285         if config.lctl_dump or config.record:
2286             for_each_profile(node_db, prof_list, doCleanup)
2287             return
2288
2289         sys_set_timeout(timeout)
2290         sys_set_ptldebug(ptldebug)
2291         sys_set_subsystem(subsystem)
2292         sys_set_lustre_upcall(lustre_upcall)
2293         sys_set_portals_upcall(portals_upcall)
2294
2295         for_each_profile(node_db, prof_list, doCleanup)
2296         for_each_profile(node_db, prof_list, doUnloadModules)
2297
2298     else:
2299         # ugly hack, only need to run lctl commands for --dump
2300         if config.lctl_dump or config.record:
2301             sys_set_timeout(timeout)
2302             sys_set_lustre_upcall(lustre_upcall)
2303             for_each_profile(node_db, prof_list, doSetup)
2304             return
2305
2306         sys_make_devices()
2307         sys_set_netmem_max('/proc/sys/net/core/rmem_max', MAXTCPBUF)
2308         sys_set_netmem_max('/proc/sys/net/core/wmem_max', MAXTCPBUF)
2309
2310         for_each_profile(node_db, prof_list, doModules)
2311
2312         sys_set_debug_path()
2313         sys_set_ptldebug(ptldebug)
2314         sys_set_subsystem(subsystem)
2315         script = config.gdb_script
2316         run(lctl.lctl, ' modules >', script)
2317         if config.gdb:
2318             log ("The GDB module script is in", script)
2319             # pause, so user has time to break and
2320             # load the script
2321             time.sleep(5)
2322         sys_set_timeout(timeout)
2323         sys_set_lustre_upcall(lustre_upcall)
2324         sys_set_portals_upcall(portals_upcall)
2325
2326         for_each_profile(node_db, prof_list, doSetup)
2327
2328 def doRecovery(db, lctl, tgt_uuid, client_uuid, nid_uuid):
2329     tgt = db.lookup(tgt_uuid)
2330     if not tgt:
2331         raise Lustre.LconfError("doRecovery: "+ tgt_uuid +" not found.")
2332     new_uuid = get_active_target(tgt)
2333     if not new_uuid:
2334         raise Lustre.LconfError("doRecovery: no active target found for: " +
2335                                 tgt_uuid)
2336     net = choose_local_server(get_ost_net(db, new_uuid))
2337     if not net:
2338         raise Lustre.LconfError("Unable to find a connection to:" + new_uuid)
2339
2340     log("Reconnecting", tgt_uuid, " to ",  net.nid_uuid);
2341     try:
2342         oldnet = get_server_by_nid_uuid(db, nid_uuid)
2343         if oldnet:
2344             lctl.disconnect(oldnet)
2345     except CommandError, e:
2346         log("recover: disconnect", nid_uuid, "failed: ")
2347         e.dump()
2348
2349     try:
2350         lctl.connect(net)
2351     except CommandError, e:
2352         log("recover: connect failed")
2353         e.dump()
2354
2355     lctl.recover(client_uuid, net.nid_uuid)
2356
2357
2358 def setupModulePath(cmd, portals_dir = PORTALS_DIR):
2359     base = os.path.dirname(cmd)
2360     if development_mode():
2361         if not config.lustre:
2362             debug('using objdir module paths')            
2363             config.lustre = (os.path.join(base, ".."))
2364         # normalize the portals dir, using command line arg if set
2365         if config.portals:
2366             portals_dir = config.portals
2367         dir = os.path.join(config.lustre, portals_dir)
2368         config.portals = dir
2369         debug('config.portals', config.portals)
2370     elif config.lustre and config.portals:
2371         # production mode
2372         # if --lustre and --portals, normalize portals 
2373         # can ignore POTRALS_DIR here, since it is probly useless here
2374         config.portals = os.path.join(config.lustre, config.portals)
2375         debug('config.portals B', config.portals)
2376
2377 def sysctl(path, val):
2378     debug("+ sysctl", path, val)
2379     if config.noexec:
2380         return
2381     try:
2382         fp = open(os.path.join('/proc/sys', path), 'w')
2383         fp.write(str(val))
2384         fp.close()
2385     except IOError, e:
2386         panic(str(e))
2387
2388
2389 def sys_set_debug_path():
2390     sysctl('portals/debug_path', config.debug_path)
2391
2392 def sys_set_lustre_upcall(upcall):
2393     # the command overrides the value in the node config
2394     if config.lustre_upcall:
2395         upcall = config.lustre_upcall
2396     elif config.upcall:
2397         upcall = config.upcall
2398     if upcall:
2399         lctl.set_lustre_upcall(upcall)
2400
2401 def sys_set_portals_upcall(upcall):
2402     # the command overrides the value in the node config
2403     if config.portals_upcall:
2404         upcall = config.portals_upcall
2405     elif config.upcall:
2406         upcall = config.upcall
2407     if upcall:
2408         sysctl('portals/upcall', upcall)
2409
2410 def sys_set_timeout(timeout):
2411     # the command overrides the value in the node config
2412     if config.timeout and config.timeout > 0:
2413         timeout = config.timeout
2414     if timeout != None and timeout > 0:
2415         lctl.set_timeout(timeout)
2416
2417 def sys_tweak_socknal ():
2418     if config.single_socket:
2419         sysctl("socknal/typed", 0)
2420
2421 def sys_optimize_elan ():
2422     procfiles = ["/proc/elan/config/eventint_punt_loops",
2423                  "/proc/qsnet/elan3/config/eventint_punt_loops",
2424                  "/proc/qsnet/elan4/config/elan4_mainint_punt_loops"]
2425     for p in procfiles:
2426         if os.access(p, os.R_OK):
2427             run ("echo 0 > " + p)
2428
2429 def sys_set_ptldebug(ptldebug):
2430     if config.ptldebug:
2431         ptldebug = config.ptldebug
2432     if ptldebug:
2433         try:
2434             val = eval(ptldebug, ptldebug_names)
2435             val = "0x%x" % (val)
2436             sysctl('portals/debug', val)
2437         except NameError, e:
2438             panic(str(e))
2439
2440 def sys_set_subsystem(subsystem):
2441     if config.subsystem:
2442         subsystem = config.subsystem
2443     if subsystem:
2444         try:
2445             val = eval(subsystem, subsystem_names)
2446             val = "0x%x" % (val)
2447             sysctl('portals/subsystem_debug', val)
2448         except NameError, e:
2449             panic(str(e))
2450
2451 def sys_set_netmem_max(path, max):
2452     debug("setting", path, "to at least", max)
2453     if config.noexec:
2454         return
2455     fp = open(path)
2456     str = fp.readline()
2457     fp.close()
2458     cur = int(str)
2459     if max > cur:
2460         fp = open(path, 'w')
2461         fp.write('%d\n' %(max))
2462         fp.close()
2463     
2464     
2465 def sys_make_devices():
2466     if not os.access('/dev/portals', os.R_OK):
2467         run('mknod /dev/portals c 10 240')
2468     if not os.access('/dev/obd', os.R_OK):
2469         run('mknod /dev/obd c 10 241')
2470
2471
2472 # Add dir to the global PATH, if not already there.
2473 def add_to_path(new_dir):
2474     syspath = string.split(os.environ['PATH'], ':')
2475     if new_dir in syspath:
2476         return
2477     os.environ['PATH'] = os.environ['PATH'] + ':' + new_dir
2478     
2479 def default_debug_path():
2480     path = '/tmp/lustre-log'
2481     if os.path.isdir('/r'):
2482         return '/r' + path
2483     else:
2484         return path
2485
2486 def default_gdb_script():
2487     script = '/tmp/ogdb'
2488     if os.path.isdir('/r'):
2489         return '/r' + script
2490     else:
2491         return script
2492
2493
2494 DEFAULT_PATH = ('/sbin', '/usr/sbin', '/bin', '/usr/bin')
2495 # ensure basic elements are in the system path
2496 def sanitise_path():
2497     for dir in DEFAULT_PATH:
2498         add_to_path(dir)
2499
2500 # global hack for the --select handling
2501 tgt_select = {}
2502 def init_select(args):
2503     # args = [service=nodeA,service2=nodeB service3=nodeC]
2504     global tgt_select
2505     for arg in args:
2506         list = string.split(arg, ',')
2507         for entry in list:
2508             srv, node = string.split(entry, '=')
2509             tgt_select[srv] = node
2510
2511 def get_select(srv):
2512     if tgt_select.has_key(srv):
2513         return tgt_select[srv]
2514     return None
2515
2516
2517 FLAG = Lustre.Options.FLAG
2518 PARAM = Lustre.Options.PARAM
2519 INTPARAM = Lustre.Options.INTPARAM
2520 PARAMLIST = Lustre.Options.PARAMLIST
2521 lconf_options = [
2522     ('verbose,v', "Print system commands as they are run"),
2523     ('ldapurl',"LDAP server URL, eg. ldap://localhost", PARAM),
2524     ('config', "Cluster config name used for LDAP query", PARAM),
2525     ('select', "service=nodeA,service2=nodeB ", PARAMLIST),
2526     ('node',   "Load config for <nodename>", PARAM),
2527     ('cleanup,d', "Cleans up config. (Shutdown)"),
2528     ('force,f', "Forced unmounting and/or obd detach during cleanup",
2529                FLAG, 0),
2530     ('single_socket', "socknal option: only use one socket instead of bundle",
2531                FLAG, 0),
2532     ('failover',"""Used to shut down without saving state.
2533                    This will allow this node to "give up" a service to a
2534                    another node for failover purposes. This will not
2535                    be a clean shutdown.""",
2536                FLAG, 0),
2537     ('gdb', """Prints message after creating gdb module script
2538                     and sleeps for 5 seconds."""),
2539     ('noexec,n', """Prints the commands and steps that will be run for a
2540                     config without executing them. This can used to check if a
2541                     config file is doing what it should be doing"""),
2542     ('nomod', "Skip load/unload module step."),
2543     ('nosetup', "Skip device setup/cleanup step."),
2544     ('reformat', "Reformat all devices (without question)"),
2545     ('mkfsoptions', "Additional options for the mk*fs command line", PARAM),
2546     ('dump',  "Dump the kernel debug log to file before portals is unloaded",
2547                PARAM),
2548     ('write_conf', "Save all the client config information on mds."),
2549     ('record', "Write config information on mds."),
2550     ('record_log', "Name of config record log.", PARAM),
2551     ('record_device', "MDS device name that will record the config commands",
2552               PARAM),
2553     ('minlevel', "Minimum level of services to configure/cleanup",
2554                  INTPARAM, 0),
2555     ('maxlevel', """Maximum level of services to configure/cleanup 
2556                     Levels are aproximatly like:
2557                             10 - netwrk
2558                             20 - device, ldlm
2559                             30 - osd, mdd
2560                             40 - mds, ost
2561                             70 - mountpoint, echo_client, osc, mdc, lov""",
2562                INTPARAM, 100),
2563     ('lustre', """Base directory of lustre sources. This parameter will
2564                   cause lconf to load modules from a source tree.""", PARAM),
2565     ('portals', """Portals source directory.  If this is a relative path,
2566                    then it is assumed to be relative to lustre. """, PARAM),
2567     ('timeout', "Set recovery timeout", INTPARAM),
2568     ('upcall',  "Set both portals and lustre upcall script", PARAM),
2569     ('lustre_upcall', "Set lustre upcall script", PARAM),
2570     ('portals_upcall', "Set portals upcall script", PARAM),
2571     ('lctl_dump', "Save lctl ioctls to the dumpfile argument", PARAM),
2572     ('ptldebug', "Set the portals debug level",  PARAM),
2573     ('subsystem', "Set the portals debug subsystem",  PARAM),
2574     ('gdb_script', "Fullname of gdb debug script", PARAM, default_gdb_script()),
2575     ('debug_path', "Path to save debug dumps", PARAM, default_debug_path()),
2576 # Client recovery options
2577     ('recover', "Recover a device"),
2578     ('group', "The group of devices to configure or cleanup", PARAM),
2579     ('tgt_uuid', "The failed target (required for recovery)", PARAM),
2580     ('client_uuid', "The failed client (required for recovery)", PARAM),
2581     ('conn_uuid', "The failed connection (required for recovery)", PARAM),
2582
2583     ('inactive', """The name of an inactive service, to be ignored during
2584                     mounting (currently OST-only). Can be repeated.""",
2585                 PARAMLIST),
2586     ]      
2587
2588 def main():
2589     global lctl, config, toplevel, CONFIG_FILE
2590
2591     # in the upcall this is set to SIG_IGN
2592     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
2593     
2594     cl = Lustre.Options("lconf", "config.xml", lconf_options)
2595     try:
2596         config, args = cl.parse(sys.argv[1:])
2597     except Lustre.OptionError, e:
2598         print e
2599         sys.exit(1)
2600
2601     setupModulePath(sys.argv[0])
2602
2603     host = socket.gethostname()
2604
2605     # the PRNG is normally seeded with time(), which is not so good for starting
2606     # time-synchronized clusters
2607     input = open('/dev/urandom', 'r')
2608     if not input:
2609         print 'Unable to open /dev/urandom!'
2610         sys.exit(1)
2611     seed = input.read(32)
2612     input.close()
2613     random.seed(seed)
2614
2615     sanitise_path()
2616     
2617     init_select(config.select)
2618
2619     if len(args) > 0:
2620         # allow config to be fetched via HTTP, but only with python2
2621         if sys.version[0] != '1' and args[0].startswith('http://'):
2622             import urllib2
2623             try:
2624                 config_file = urllib2.urlopen(args[0])
2625             except (urllib2.URLError, socket.error), err:
2626                 if hasattr(err, 'args'):
2627                     err = err.args[1]
2628                 print "Could not access '%s': %s" %(args[0], err)
2629                 sys.exit(1)
2630         elif not os.access(args[0], os.R_OK):
2631             print 'File not found or readable:', args[0]
2632             sys.exit(1)
2633         else:
2634             # regular file
2635             config_file = open(args[0], 'r')
2636         try:
2637             dom = xml.dom.minidom.parse(config_file)
2638         except Exception:
2639             panic("%s does not appear to be a config file." % (args[0]))
2640             sys.exit(1) # make sure to die here, even in debug mode.
2641         CONFIG_FILE = args[0]
2642         db = Lustre.LustreDB_XML(dom.documentElement, dom.documentElement)
2643         if not config.config:
2644             config.config = os.path.basename(args[0])# use full path?
2645             if config.config[-4:] == '.xml':
2646                 config.config = config.config[:-4]
2647     elif config.ldapurl:
2648         if not config.config:
2649             panic("--ldapurl requires --config name")
2650         dn = "config=%s,fs=lustre" % (config.config)
2651         db = Lustre.LustreDB_LDAP('', {}, base=dn, url = config.ldapurl)
2652     elif config.ptldebug or config.subsystem:
2653         sys_set_ptldebug(None)
2654         sys_set_subsystem(None)
2655         sys.exit(0)
2656     else:
2657         print 'Missing config file or ldap URL.'
2658         print 'see lconf --help for command summary'
2659         sys.exit(1)
2660
2661     toplevel = db
2662
2663     ver = db.get_version()
2664     if not ver:
2665         panic("No version found in config data, please recreate.")
2666     if ver != Lustre.CONFIG_VERSION:
2667         panic("Config version", ver, "does not match lconf version",
2668               Lustre.CONFIG_VERSION)
2669
2670     node_list = []
2671     if config.node:
2672         node_list.append(config.node)
2673     else:
2674         if len(host) > 0:
2675             node_list.append(host)
2676         node_list.append('localhost')
2677
2678     debug("configuring for host: ", node_list)
2679
2680     if len(host) > 0:
2681         config.debug_path = config.debug_path + '-' + host
2682         config.gdb_script = config.gdb_script + '-' + host
2683
2684     lctl = LCTLInterface('lctl')
2685
2686     if config.lctl_dump:
2687         lctl.use_save_file(config.lctl_dump)
2688
2689     if config.record:
2690         if not (config.record_device and config.record_log):
2691             panic("When recording, both --record_log and --record_device must be specified.")
2692         lctl.clear_log(config.record_device, config.record_log)
2693         lctl.record(config.record_device, config.record_log)
2694
2695     doHost(db, node_list)
2696
2697     if config.record:
2698         lctl.end_record()
2699
2700 if __name__ == "__main__":
2701     try:
2702         main()
2703     except Lustre.LconfError, e:
2704         print e
2705 #        traceback.print_exc(file=sys.stdout)
2706         sys.exit(1)
2707     except CommandError, e:
2708         e.dump()
2709         sys.exit(e.rc)
2710
2711     if first_cleanup_error:
2712         sys.exit(first_cleanup_error)