Whamcloud - gitweb
land b_ost_amd onto HEAD.
[fs/lustre-release.git] / lustre / utils / lconf
index 787321f..6c2ea4d 100755 (executable)
@@ -23,7 +23,7 @@
 # lconf is the main driver script for starting and stopping
 # lustre filesystem services.
 #
-# Based in part on the XML obdctl modifications done by Brian Behlendorf 
+# Based in part on the XML obdctl modifications done by Brian Behlendorf
 
 import sys, getopt, types
 import string, os, stat, popen2, socket, time, random, fcntl, select
@@ -61,10 +61,10 @@ MAX_LOOP_DEVICES = 256
 PORTALS_DIR = 'portals'
 
 # Needed to call lconf --record
-CONFIG_FILE = "" 
+CONFIG_FILE = ""
 
 # Please keep these in sync with the values in portals/kp30.h
-ptldebug_names = { 
+ptldebug_names = {
     "trace" :     (1 << 0),
     "inode" :     (1 << 1),
     "super" :     (1 << 2),
@@ -88,6 +88,7 @@ ptldebug_names = {
     "rpctrace" :  (1 << 20),
     "vfstrace" :  (1 << 21),
     "reada" :     (1 << 22),
+    "config" :    (1 << 23),
     }
 
 subsystem_names = {
@@ -123,11 +124,11 @@ def cleanup_error(rc):
     if not first_cleanup_error:
         first_cleanup_error = rc
 
-# ============================================================ 
+# ============================================================
 # debugging and error funcs
 
 def fixme(msg = "this feature"):
-    raise Lustre.LconfError, msg + ' not implmemented yet.'
+    raise Lustre.LconfError, msg + ' not implemented yet.'
 
 def panic(*args):
     msg = string.join(map(str,args))
@@ -240,7 +241,7 @@ class DaemonHandler:
             return pid
         except IOError:
             return 0
-        
+
     def clean_pidfile(self):
         """ Remove a stale pidfile """
         log("removing stale pidfile:", self.pidfile())
@@ -248,7 +249,7 @@ class DaemonHandler:
             os.unlink(self.pidfile())
         except OSError, e:
             log(self.pidfile(), e)
-            
+
 class AcceptorHandler(DaemonHandler):
     def __init__(self, port, net_type, send_mem, recv_mem, irq_aff):
         DaemonHandler.__init__(self, "acceptor")
@@ -265,7 +266,7 @@ class AcceptorHandler(DaemonHandler):
 
     def command_line(self):
         return string.join(map(str,('-s', self.send_mem, '-r', self.recv_mem, self.flags, self.port)))
-    
+
 acceptors = {}
 
 # start the acceptors
@@ -285,14 +286,14 @@ def run_one_acceptor(port):
         if not daemon.running():
             daemon.start()
     else:
-         panic("run_one_acceptor: No acceptor defined for port:", port)   
-        
+         panic("run_one_acceptor: No acceptor defined for port:", port)
+
 def stop_acceptor(port):
     if acceptors.has_key(port):
         daemon = acceptors[port]
         if daemon.running():
             daemon.stop()
-        
+
 
 # ============================================================
 # handle lctl interface
@@ -317,7 +318,7 @@ class LCTLInterface:
 
     def use_save_file(self, file):
         self.save_file = file
-        
+
     def record(self, dev_name, logname):
         log("Recording log", logname, "on", dev_name)
         self.record_device = dev_name
@@ -349,7 +350,7 @@ class LCTLInterface:
     device $%s
     record %s
     %s""" % (self.record_device, self.record_log, cmds)
-            
+
         debug("+", cmd_line, cmds)
         if config.noexec: return (0, [])
 
@@ -401,7 +402,7 @@ class LCTLInterface:
             raise CommandError(self.lctl, out, rc)
         return rc, out
 
-            
+
     def clear_log(self, dev, log):
         """ clear an existing log """
         cmds =  """
@@ -437,7 +438,7 @@ class LCTLInterface:
              recv_mem,
              nid, hostaddr, port, flags )
             self.run(cmds)
-    
+
     def connect(self, srv):
         self.add_uuid(srv.net_type, srv.nid_uuid, srv.nid)
         if srv.net_type  in ('tcp',) and not config.lctl_dump:
@@ -453,7 +454,7 @@ class LCTLInterface:
     device $%s
     recover %s""" %(dev_name, new_conn)
         self.run(cmds)
-                
+
     # add a route to a range
     def add_route(self, net, gw, lo, hi):
         cmds =  """
@@ -466,7 +467,7 @@ class LCTLInterface:
         except CommandError, e:
             log ("ignore: ")
             e.dump()
-                
+
     def del_route(self, net, gw, lo, hi):
         cmds =  """
   ignore_errors
@@ -509,7 +510,7 @@ class LCTLInterface:
   quit""" % (net_type,
              nid, hostaddr)
                 self.run(cmds)
-        
+
     # disconnect one connection
     def disconnect(self, srv):
         self.del_uuid(srv.nid_uuid)
@@ -544,7 +545,7 @@ class LCTLInterface:
   setup %s
   quit""" % (name, setup)
         self.run(cmds)
-        
+
 
     # create a new device with lctl
     def newdev(self, type, name, uuid, setup = ""):
@@ -554,7 +555,7 @@ class LCTLInterface:
         except CommandError, e:
             self.cleanup(name, uuid, 0)
             raise e
-        
+
 
     # cleanup a device
     def cleanup(self, name, uuid, force, failover = 0):
@@ -570,14 +571,20 @@ class LCTLInterface:
 
     # create an lov
     def lov_setup(self, name, uuid, desc_uuid, mdsuuid, stripe_cnt,
-                  stripe_sz, stripe_off,
-                      pattern, devlist):
+                  stripe_sz, stripe_off, pattern):
         cmds = """
   attach lov %s %s
-  lov_setup %s %d %d %d %s %s
-  quit""" % (name, uuid, desc_uuid, stripe_cnt, stripe_sz, stripe_off,
-             pattern, devlist)
+  lov_setup %s %d %d %d %s
+  quit""" % (name, uuid, desc_uuid, stripe_cnt, stripe_sz, stripe_off, pattern)
         self.run(cmds)
+
+    # add an OBD to a LOV
+    def lov_add_obd(self, name, uuid, obd_uuid, index, gen):
+        cmds = """
+  lov_modify_tgts add %s %s %s %s
+  quit""" % (name, obd_uuid, index, gen)
+        self.run(cmds)
+
     # create an lmv
     def lmv_setup(self, name, uuid, desc_uuid, devlist):
         cmds = """
@@ -585,13 +592,20 @@ class LCTLInterface:
   lmv_setup %s %s
   quit""" % (name, uuid, desc_uuid, devlist)
         self.run(cmds)
-    # create an lov
-    def lov_setconfig(self, uuid, mdsuuid, stripe_cnt, stripe_sz, stripe_off,
-                      pattern, devlist):
+
+    # delete an OBD from a LOV
+    def lov_del_obd(self, name, uuid, obd_uuid, index, gen):
         cmds = """
-  cfg_device $%s
-  lov_setconfig %s %d %d %d %s %s
-  quit""" % (mdsuuid, uuid, stripe_cnt, stripe_sz, stripe_off, pattern, devlist)
+  lov_modify_tgts del %s %s %s %s
+  quit""" % (name, obd_uuid, index, gen)
+        self.run(cmds)
+
+    # deactivate an OBD
+    def deactivate(self, name):
+        cmds = """
+  device $%s
+  deactivate
+  quit""" % (name)
         self.run(cmds)
 
     # dump the log file
@@ -639,7 +653,6 @@ class LCTLInterface:
   quit""" % (timeout,)
         self.run(cmds)
 
-    # delete mount options
     def set_lustre_upcall(self, upcall):
         cmds = """
   set_lustre_upcall %s
@@ -711,7 +724,7 @@ def find_module(src_dir, dev_dir, modname):
     modbase = src_dir +'/'+ dev_dir +'/'+ modname
     for modext in '.ko', '.o':
         module = modbase + modext
-        try: 
+        try:
             if os.access(module, os.R_OK):
                 return module
         except OSError:
@@ -753,7 +766,7 @@ def mkfs(dev, devsize, fstype, jsize, isize, mkfsoptions, isblock=1):
             if devsize > 1024 * 1024:
                 jsize = ((devsize / 102400) * 4)
             if jsize > 400:
-                jsize = 400        
+                jsize = 400
         if jsize:  jopt = "-J size=%d" %(jsize,)
         if isize:  iopt = "-I %d" %(isize,)
         mkfs = 'mkfs.ext2 -j -b 4096 '
@@ -860,7 +873,7 @@ def clean_loop(file):
 
 # determine if dev is formatted as a <fstype> filesystem
 def need_format(fstype, dev):
-    # FIXME don't know how to implement this    
+    # FIXME don't know how to implement this
     return 0
 
 # initialize a block device if needed
@@ -879,7 +892,7 @@ def block_dev(dev, size, fstype, reformat, autoformat, journal_size,
 #        panic("device:", dev,
 #              "not prepared, and autoformat is not set.\n",
 #              "Rerun with --reformat option to format ALL filesystems")
-        
+
     return dev
 
 def if2addr(iface):
@@ -917,7 +930,7 @@ def sys_get_local_nid(net_type, wildcard, cluster_id):
     else:
         local = sys_get_local_address(net_type, wildcard, cluster_id)
     return local
-        
+
 def sys_get_local_address(net_type, wildcard, cluster_id):
     """Return the local address for the network type."""
     local = ""
@@ -945,7 +958,7 @@ def sys_get_local_address(net_type, wildcard, cluster_id):
                     elan_id = a[1]
                     break
             try:
-                nid = my_int(cluster_id) + my_int(elan_id) 
+                nid = my_int(cluster_id) + my_int(elan_id)
                 local = "%d" % (nid)
             except ValueError, e:
                 local = elan_id
@@ -1022,7 +1035,7 @@ def fs_is_mounted(path):
     except IOError, e:
         log(e)
     return 0
-        
+
 
 class kmod:
     """Manage kernel modules"""
@@ -1088,7 +1101,7 @@ class Module:
         self._server = None
         self._connected = 0
         self.kmod = kmod(config.lustre, config.portals)
-        
+
     def info(self, *args):
         msg = string.join(map(str,args))
         print self.module_name + ":", self.name, self.uuid, msg
@@ -1102,7 +1115,7 @@ class Module:
             log(self.module_name, "cleanup failed: ", self.name)
             e.dump()
             cleanup_error(e.rc)
-            
+
     def add_portals_module(self, dev_dir, modname):
         """Append a module to list of modules to load."""
         self.kmod.add_portals_module(dev_dir, modname)
@@ -1114,7 +1127,7 @@ class Module:
     def load_module(self):
         """Load all the modules in the list in the order they appear."""
         self.kmod.load_module()
-            
+
     def cleanup_module(self):
         """Unload the modules in the list in reverse order."""
         if self.safe_to_clean():
@@ -1122,10 +1135,10 @@ class Module:
 
     def safe_to_clean(self):
         return 1
-        
+
     def safe_to_clean_modules(self):
         return self.safe_to_clean()
-        
+
 class Network(Module):
     def __init__(self,db):
         Module.__init__(self, 'NETWORK', db)
@@ -1170,7 +1183,7 @@ class Network(Module):
         return "NID_%s_UUID" %(nid,)
 
     def prepare(self):
-        if is_network_prepared():
+        if not config.record and is_network_prepared():
             return
         self.info(self.net_type, self.nid, self.port)
         if not (config.record and self.generic_nid):
@@ -1251,9 +1264,9 @@ class RouteTable(Module):
             return None
 
         return Network(srvdb)
-        
+
     def prepare(self):
-        if is_network_prepared():
+        if not config.record and is_network_prepared():
             return
         self.info()
         for net_type, gw, gw_cluster_id, tgt_cluster_id, lo, hi in self.db.get_route_tbl():
@@ -1295,7 +1308,7 @@ class Management(Module):
         self.add_lustre_module('mgmt', 'mgmt_svc')
 
     def prepare(self):
-        if is_prepared(self.name):
+        if not config.record and is_prepared(self.name):
             return
         self.info()
         lctl.newdev("mgmt", self.name, self.uuid)
@@ -1339,7 +1352,7 @@ class LOV(Module):
         self.stripe_sz = self.db.get_val_int('stripesize', 1048576)
         self.stripe_off = self.db.get_val_int('stripeoffset', 0)
         self.pattern = self.db.get_val_int('stripepattern', 0)
-        self.devlist = self.db.get_refs('obd')
+        self.devlist = self.db.get_lov_tgts('lov_tgt')
         self.stripe_cnt = self.db.get_val_int('stripecount', len(self.devlist))
         self.osclist = []
         self.desc_uuid = self.uuid
@@ -1349,13 +1362,15 @@ class LOV(Module):
             self.config_only = 1
             return
         self.config_only = None
-        mds= self.db.lookup(self.mds_uuid)
+        mds = self.db.lookup(self.mds_uuid)
         self.mds_name = mds.getName()
-        for obd_uuid in self.devlist:
+        for (obd_uuid, index, gen, active) in self.devlist:
+            if obd_uuid == '':
+                continue
             obd = self.db.lookup(obd_uuid)
             osc = get_osc(obd, self.uuid, fs_name)
             if osc:
-                self.osclist.append(osc)
+                self.osclist.append((osc, index, gen, active))
             else:
                 panic('osc not found:', obd_uuid)
     def get_uuid(self):
@@ -1363,38 +1378,39 @@ class LOV(Module):
     def get_name(self):
         return self.name
     def prepare(self):
-        if is_prepared(self.name):
+        if not config.record and is_prepared(self.name):
             return
-        if self.config_only:
-            panic("Can't prepare config_only LOV ", self.name)
-            
-        for osc in self.osclist:
+        self.info(self.mds_uuid, self.stripe_cnt, self.stripe_sz,
+                  self.stripe_off, self.pattern, self.devlist,
+                  self.mds_name)
+        lctl.lov_setup(self.name, self.uuid,
+                       self.desc_uuid, self.mds_name, self.stripe_cnt,
+                       self.stripe_sz, self.stripe_off, self.pattern)
+        for (osc, index, gen, active) in self.osclist:
+            target_uuid = osc.target_uuid
             try:
                 # Only ignore connect failures with --force, which
                 # isn't implemented here yet.
+                osc.active = active
                 osc.prepare(ignore_connect_failure=0)
             except CommandError, e:
                 print "Error preparing OSC %s\n" % osc.uuid
                 raise e
-        self.info(self.mds_uuid, self.stripe_cnt, self.stripe_sz,
-                  self.stripe_off, self.pattern, self.devlist, self.mds_name)
-        lctl.lov_setup(self.name, self.uuid,
-                       self.desc_uuid, self.mds_name, self.stripe_cnt,
-                       self.stripe_sz, self.stripe_off, self.pattern,
-                       string.join(self.devlist))
+            lctl.lov_add_obd(self.name, self.uuid, target_uuid, index, gen)
 
     def cleanup(self):
+        for (osc, index, gen, active) in self.osclist:
+            target_uuid = osc.target_uuid
+            osc.cleanup()
         if is_prepared(self.name):
             Module.cleanup(self)
         if self.config_only:
             panic("Can't clean up config_only LOV ", self.name)
-        for osc in self.osclist:
-            osc.cleanup()
 
     def load_module(self):
         if self.config_only:
             panic("Can't load modules for config_only LOV ", self.name)
-        for osc in self.osclist:
+        for (osc, index, gen, active) in self.osclist:
             osc.load_module()
             break
         Module.load_module(self)
@@ -1403,8 +1419,9 @@ class LOV(Module):
         if self.config_only:
             panic("Can't cleanup modules for config_only LOV ", self.name)
         Module.cleanup_module(self)
-        for osc in self.osclist:
-            osc.cleanup_module()
+        for (osc, index, gen, active) in self.osclist:
+            if active:
+                osc.cleanup_module()
             break
 
     def correct_level(self, level, op=None):
@@ -1588,9 +1605,9 @@ class MDSDEV(Module):
     def load_module(self):
         if self.active:
             Module.load_module(self)
-            
+
     def prepare(self):
-        if is_prepared(self.name):
+        if not config.record and is_prepared(self.name):
             return
         if not self.active:
             debug(self.uuid, "not active")
@@ -1658,59 +1675,61 @@ class MDSDEV(Module):
                 raise e
 
     def write_conf(self):
-        if is_prepared(self.name):
-            return
-        self.info(self.devpath, self.fstype, self.format)
+        do_cleanup = 0
+        if not is_prepared(self.name):
+            self.info(self.devpath, self.fstype, self.format)
 
-        blkdev = block_dev(self.devpath, self.size, self.fstype,
-                           config.reformat, self.format, self.journal_size,
-                           self.inode_size, self.mkfsoptions, self.backfstype,
-                           self.backdevpath)
+            blkdev = block_dev(self.devpath, self.size, self.fstype,
+                               config.reformat, self.format, self.journal_size,
+                               self.inode_size, self.mkfsoptions,
+                               self.backfstype, self.backdevpath)
 
-       # Even for writing logs we mount mds with supplied mount options
-       # because it will not mount smfs (if used) otherwise.
-       
-        mountfsoptions = def_mount_options(self.fstype, 'mds')
-            
-        if config.mountfsoptions:
-            if mountfsoptions:
-                mountfsoptions = mountfsoptions + ',' + config.mountfsoptions
-            else:
-                mountfsoptions = config.mountfsoptions
-            if self.mountfsoptions:
-                mountfsoptions = mountfsoptions + ',' + self.mountfsoptions
-        else:
-            if self.mountfsoptions:
+            # Even for writing logs we mount mds with supplied mount options
+            # because it will not mount smfs (if used) otherwise.
+
+            mountfsoptions = def_mount_options(self.fstype, 'mds')
+
+            if config.mountfsoptions:
                 if mountfsoptions:
-                    mountfsoptions = mountfsoptions + ',' + self.mountfsoptions
+                    mountfsoptions = mountfsoptions + ',' + config.mountfsoptions
                 else:
-                    mountfsoptions = self.mountfsoptions
-            
-        if self.fstype == 'smfs':
-            realdev = self.fstype
+                    mountfsoptions = config.mountfsoptions
+                if self.mountfsoptions:
+                    mountfsoptions = mountfsoptions + ',' + self.mountfsoptions
+            else:
+                if self.mountfsoptions:
+                    if mountfsoptions:
+                        mountfsoptions = mountfsoptions + ',' + self.mountfsoptions
+                    else:
+                        mountfsoptions = self.mountfsoptions
+
+            if self.fstype == 'smfs':
+                realdev = self.fstype
                 
-            if mountfsoptions:
-                mountfsoptions = "%s,type=%s,dev=%s" % (mountfsoptions, 
-                                                        self.backfstype, 
-                                                        blkdev)
+                if mountfsoptions:
+                    mountfsoptions = "%s,type=%s,dev=%s" % (mountfsoptions, 
+                                                            self.backfstype, 
+                                                            blkdev)
+                else:
+                    mountfsoptions = "type=%s,dev=%s" % (self.backfstype, 
+                                                         blkdev)
             else:
-                mountfsoptions = "type=%s,dev=%s" % (self.backfstype, 
-                                                     blkdev)
-        else:
-            realdev = blkdev
+                realdev = blkdev
        
-        print 'MDS mount options: ' + mountfsoptions
-        
-       # As mount options are passed by 4th param to config tool, we need 
-       # to pass something in 3rd param. But we do not want this 3rd param
-       # be counted as a profile name for reading log on MDS setup, thus,
-       # we pass there some predefined sign like 'dumb', which will be 
-        # checked in MDS code and skipped. Probably there is more nice way
-        # like pass empty string and check it in config tool and pass null
-        # as 4th param.
-        lctl.newdev("mds", self.name, self.uuid,
-                    setup ="%s %s %s %s" %(realdev, self.fstype, 
-                                           'dumb', mountfsoptions))
+                print 'MDS mount options: ' + mountfsoptions
+
+            # As mount options are passed by 4th param to config tool, we need 
+            # to pass something in 3rd param. But we do not want this 3rd param
+            # be counted as a profile name for reading log on MDS setup, thus,
+            # we pass there some predefined sign like 'dumb', which will be 
+            # checked in MDS code and skipped. Probably there is more nice way
+            # like pass empty string and check it in config tool and pass null
+            # as 4th param.
+            lctl.newdev("mds", self.name, self.uuid,
+                        setup ="%s %s %s %s" %(realdev, self.fstype, 
+                                               'dumb', mountfsoptions))
+            do_cleanup = 1
+
         # record logs for the MDS lov
         for uuid in self.filesystem_uuids:
             log("recording clients for filesystem:", uuid)
@@ -1736,6 +1755,7 @@ class MDSDEV(Module):
             client.prepare()
             lctl.mount_option(self.name, client.get_name(), "")
             lctl.end_record()
+            process_updates(self.db, self.name, self.name, client)
 
             config.cleanup = 1
             lctl.clear_log(self.name, self.name + '-clean')
@@ -1743,10 +1763,15 @@ class MDSDEV(Module):
             client.cleanup()
             lctl.del_mount_option(self.name)
             lctl.end_record()
+            process_updates(self.db, self.name, self.name + '-clean', client)
             config.cleanup = 0
             config.record = 0
 
         # record logs for each client
+        if config.noexec:
+            noexec_opt = '-n'
+        else:
+            noexec_opt = ''
         if config.ldapurl:
             config_options = "--ldapurl " + config.ldapurl + " --config " + config.config
         else:
@@ -1763,9 +1788,7 @@ class MDSDEV(Module):
                         debug("recording", client_name)
                         old_noexec = config.noexec
                         config.noexec = 0
-                        noexec_opt = ('', '-n')
-                        ret, out = run (sys.argv[0],
-                                        noexec_opt[old_noexec == 1],
+                        ret, out = run (sys.argv[0], noexec_opt,
                                         " -v --record --nomod",
                                         "--record_log", client_name,
                                         "--record_device", self.name,
@@ -1773,8 +1796,7 @@ class MDSDEV(Module):
                                         config_options)
                         if config.verbose:
                             for s in out: log("record> ", string.strip(s))
-                        ret, out = run (sys.argv[0],
-                                        noexec_opt[old_noexec == 1],
+                        ret, out = run (sys.argv[0], noexec_opt,
                                         "--cleanup -v --record --nomod",
                                         "--record_log", client_name + "-clean",
                                         "--record_device", self.name,
@@ -1783,18 +1805,19 @@ class MDSDEV(Module):
                         if config.verbose:
                             for s in out: log("record> ", string.strip(s))
                         config.noexec = old_noexec
-        try:
-            lctl.cleanup(self.name, self.uuid, 0, 0)
-        except CommandError, e:
-            log(self.module_name, "cleanup failed: ", self.name)
-            e.dump()
-            cleanup_error(e.rc)
-            Module.cleanup(self)
+        if do_cleanup:
+            try:
+                lctl.cleanup(self.name, self.uuid, 0, 0)
+            except CommandError, e:
+                log(self.module_name, "cleanup failed: ", self.name)
+                e.dump()
+                cleanup_error(e.rc)
+                Module.cleanup(self)
         
-        if self.fstype == 'smfs':
-            clean_loop(self.backdevpath)
-        else:
-            clean_loop(self.devpath)
+            if self.fstype == 'smfs':
+                clean_loop(self.backdevpath)
+            else:
+                clean_loop(self.devpath)
 
     def msd_remaining(self):
         out = lctl.device_list()
@@ -1876,7 +1899,7 @@ class OSD(Module):
             self.active = 0
         if self.active and config.group and config.group != ost.get_val('group'):
             self.active = 0
-            
+
         self.target_dev_uuid = self.uuid
         self.uuid = target_uuid
         # modules
@@ -2014,6 +2037,7 @@ class Client(Module):
         self.target_name = tgtdb.getName()
         self.target_uuid = tgtdb.getUUID()
         self.db = tgtdb
+        self.active = 1
 
         self.tgt_dev_uuid = get_active_target(tgtdb)
         if not self.tgt_dev_uuid:
@@ -2054,7 +2078,7 @@ class Client(Module):
 
     def prepare(self, ignore_connect_failure = 0):
         self.info(self.target_uuid)
-        if is_prepared(self.name):
+        if not config.record and is_prepared(self.name):
             self.cleanup()
         try:
             srv = choose_local_server(self.get_servers())
@@ -2070,7 +2094,7 @@ class Client(Module):
             if not ignore_connect_failure:
                 raise e
         if srv:
-            if self.target_uuid in config.inactive and self.permits_inactive():
+            if self.permits_inactive() and (self.target_uuid in config.inactive or self.active == 0):
                 debug("%s inactive" % self.target_uuid)
                 inactive_p = "inactive"
             else:
@@ -2098,6 +2122,13 @@ class Client(Module):
     def correct_level(self, level, op=None):
         return level
 
+    def deactivate(self):
+        try:
+            lctl.deactivate(self.name)
+        except CommandError, e:
+            log(self.module_name, "deactivate failed: ", self.name)
+            e.dump()
+            cleanup_error(e.rc)
 
 class MDC(Client):
     def __init__(self, db, uuid, fs_name):
@@ -2121,7 +2152,7 @@ class ManagementClient(Client):
         Client.__init__(self, db, uuid, 'mgmt_cli', '',
                         self_name = mgmtcli_name_for_uuid(db.getUUID()),
                         module_dir = 'mgmt')
-            
+
 class COBD(Module):
     def __init__(self, db, uuid, name, type, name_override = None):
         Module.__init__(self, 'COBD', db)
@@ -2158,7 +2189,7 @@ class COBD(Module):
     def prepare(self):
         self.real.prepare()
         self.cache.prepare()
-        if is_prepared(self.name):
+        if not config.record and is_prepared(self.name):
             return
         self.info(self.real_uuid, self.cache_uuid)
         lctl.newdev("cobd", self.name, self.uuid,
@@ -2242,7 +2273,7 @@ class ECHO_CLIENT(Module):
         self.osc = VOSC(obd, self.uuid, self.name)
 
     def prepare(self):
-        if is_prepared(self.name):
+        if not config.record and is_prepared(self.name):
             return
         run_acceptors()
         self.osc.prepare() # XXX This is so cheating. -p
@@ -2309,7 +2340,7 @@ class Mountpoint(Module):
             self.mgmtcli = None
 
     def prepare(self):
-        if fs_is_mounted(self.path):
+        if not config.record and fs_is_mounted(self.path):
             log(self.path, "already mounted.")
             return
         run_acceptors()
@@ -2388,7 +2419,7 @@ def get_ost_net(self, osd_uuid):
     return srv_list
 
 
-# the order of iniitailization is based on level. 
+# the order of iniitailization is based on level.
 def getServiceLevel(self):
     type = self.get_class()
     ret=0;
@@ -2412,7 +2443,7 @@ def getServiceLevel(self):
         panic("Unknown type: ", type)
 
     if ret < config.minlevel or ret > config.maxlevel:
-        ret = 0 
+        ret = 0
     return ret
 
 #
@@ -2434,7 +2465,7 @@ def getServices(self):
 
 
 ############################################################
-# MDC UUID hack - 
+# MDC UUID hack -
 # FIXME: clean this mess up!
 #
 # OSC is no longer in the xml, so we have to fake it.
@@ -2543,7 +2574,7 @@ def find_route(srv_list):
             if  (r[3] <= to and to <= r[4]) and cluster_id == r[2]:
                 result.append((srv, r))
     return result
-           
+
 def get_active_target(db):
     target_uuid = db.getUUID()
     target_name = db.getName()
@@ -2559,7 +2590,7 @@ def get_server_by_nid_uuid(db,  nid_uuid):
         net = Network(n)
         if net.nid_uuid == nid_uuid:
             return net
-        
+
 
 ############################################################
 # lconf level logic
@@ -2596,7 +2627,7 @@ def newService(db):
 
 #
 # Prepare the system to run lustre using a particular profile
-# in a the configuration. 
+# in a the configuration.
 #  * load & the modules
 #  * setup networking for the current node
 #  * make sure partitions are in place and prepared
@@ -2609,10 +2640,144 @@ def for_each_profile(db, prof_list, operation):
             panic("profile:", profile, "not found.")
         services = getServices(prof_db)
         operation(services)
-        
+
+def magic_get_osc(db, rec, lov):
+    if lov:
+        lov_uuid = lov.get_uuid()
+        lov_name = lov.osc.fs_name
+    else:
+        lov_uuid = rec.getAttribute('lov_uuidref')
+        # FIXME: better way to find the mountpoint?
+        filesystems = db.root_node.getElementsByTagName('filesystem')
+        fsuuid = None
+        for fs in filesystems:
+            ref = fs.getElementsByTagName('obd_ref')
+            if ref[0].getAttribute('uuidref') == lov_uuid:
+                fsuuid = fs.getAttribute('uuid')
+                break
+
+        if not fsuuid:
+            panic("malformed xml: lov uuid '" + lov_uuid + "' referenced in 'add' record is not used by any filesystems.")
+
+        mtpts = db.root_node.getElementsByTagName('mountpoint')
+        lov_name = None
+        for fs in mtpts:
+            ref = fs.getElementsByTagName('filesystem_ref')
+            if ref[0].getAttribute('uuidref') == fsuuid:
+                lov_name = fs.getAttribute('name')
+                break
+
+        if not lov_name:
+            panic("malformed xml: 'add' record references lov uuid '" + lov_uuid + "', which references filesystem uuid '" + fsuuid + "', which does not reference a mountpoint.")
+
+    print "lov_uuid: " + lov_uuid + "; lov_name: " + lov_name
+
+    ost_uuid = rec.getAttribute('ost_uuidref')
+    obd = db.lookup(ost_uuid)
+
+    if not obd:
+        panic("malformed xml: 'add' record references ost uuid '" + ost_uuid + "' which cannot be found.")
+
+    osc = get_osc(obd, lov_uuid, lov_name)
+    if not osc:
+        panic('osc not found:', obd_uuid)
+    return osc
+
+# write logs for update records.  sadly, logs of all types -- and updates in
+# particular -- are something of an afterthought.  lconf needs rewritten with
+# these as core concepts.  so this is a pretty big hack.
+def process_update_record(db, update, lov):
+    for rec in update.childNodes:
+        if rec.nodeType != rec.ELEMENT_NODE:
+            continue
+
+        log("found "+rec.nodeName+" record in update version " +
+            str(update.getAttribute('version')))
+
+        lov_uuid = rec.getAttribute('lov_uuidref')
+        ost_uuid = rec.getAttribute('ost_uuidref')
+        index = rec.getAttribute('index')
+        gen = rec.getAttribute('generation')
+
+        if not lov_uuid or not ost_uuid or not index or not gen:
+            panic("malformed xml: 'update' record requires lov_uuid, ost_uuid, index, and generation.")
+
+        if not lov:
+            tmplov = db.lookup(lov_uuid)
+            if not tmplov:
+                panic("malformed xml: 'delete' record contains lov UUID '" + lov_uuid + "', which cannot be located.")
+            lov_name = tmplov.getName()
+        else:
+            lov_name = lov.osc.name
+
+        # ------------------------------------------------------------- add
+        if rec.nodeName == 'add':
+            if config.cleanup:
+                lctl.lov_del_obd(lov_name, lov_uuid, ost_uuid, index, gen)
+                continue
+
+            osc = magic_get_osc(db, rec, lov)
+
+            try:
+                # Only ignore connect failures with --force, which
+                # isn't implemented here yet.
+                osc.prepare(ignore_connect_failure=0)
+            except CommandError, e:
+                print "Error preparing OSC %s\n" % osc.uuid
+                raise e
+
+            lctl.lov_add_obd(lov_name, lov_uuid, ost_uuid, index, gen)
+
+        # ------------------------------------------------------ deactivate
+        elif rec.nodeName == 'deactivate':
+            if config.cleanup:
+                continue
+
+            osc = magic_get_osc(db, rec, lov)
+
+            try:
+                osc.deactivate()
+            except CommandError, e:
+                print "Error deactivating OSC %s\n" % osc.uuid
+                raise e
+
+        # ---------------------------------------------------------- delete
+        elif rec.nodeName == 'delete':
+            if config.cleanup:
+                continue
+
+            osc = magic_get_osc(db, rec, lov)
+
+            try:
+                config.cleanup = 1
+                osc.cleanup()
+                config.cleanup = 0
+            except CommandError, e:
+                print "Error cleaning up OSC %s\n" % osc.uuid
+                raise e
+
+            lctl.lov_del_obd(lov_name, lov_uuid, ost_uuid, index, gen)
+
+def process_updates(db, log_device, log_name, lov = None):
+    updates = db.root_node.getElementsByTagName('update')
+    for u in updates:
+        if not u.childNodes:
+            log("ignoring empty update record (version " +
+                str(u.getAttribute('version')) + ")")
+            continue
+
+        version = u.getAttribute('version')
+        real_name = "%s-%s" % (log_name, version)
+        lctl.clear_log(log_device, real_name)
+        lctl.record(log_device, real_name)
+
+        process_update_record(db, u, lov)
+
+        lctl.end_record()
+
 def doWriteconf(services):
-    if config.nosetup:
-        return
+    #if config.nosetup:
+    #    return
     for s in services:
         if s[1].get_class() == 'mdsdev':
             n = newService(s[1])
@@ -2633,7 +2798,7 @@ def doSetup(services):
     nlist.sort()
     for n in nlist:
         n[1].prepare()
-    
+
 def doModules(services):
     if config.nomod:
         return
@@ -2669,7 +2834,7 @@ def doUnloadModules(services):
             n.cleanup_module()
 
 #
-# Load profile for 
+# Load profile for
 def doHost(lustreDB, hosts):
     global is_router, local_node_name
     node_db = None
@@ -2687,7 +2852,7 @@ def doHost(lustreDB, hosts):
     timeout = node_db.get_val_int('timeout', 0)
     ptldebug = node_db.get_val('ptldebug', '')
     subsystem = node_db.get_val('subsystem', '')
-    
+
     find_local_clusters(node_db)
     if not is_router:
         find_local_routes(lustreDB)
@@ -2794,7 +2959,7 @@ def setupModulePath(cmd, portals_dir = PORTALS_DIR):
     base = os.path.dirname(cmd)
     if development_mode():
         if not config.lustre:
-            debug('using objdir module paths')            
+            debug('using objdir module paths')
             config.lustre = (os.path.join(base, ".."))
         # normalize the portals dir, using command line arg if set
         if config.portals:
@@ -2804,7 +2969,7 @@ def setupModulePath(cmd, portals_dir = PORTALS_DIR):
         debug('config.portals', config.portals)
     elif config.lustre and config.portals:
         # production mode
-        # if --lustre and --portals, normalize portals 
+        # if --lustre and --portals, normalize portals
         # can ignore POTRALS_DIR here, since it is probly useless here
         config.portals = os.path.join(config.lustre, config.portals)
         debug('config.portals B', config.portals)
@@ -2895,8 +3060,8 @@ def sys_set_netmem_max(path, max):
         fp = open(path, 'w')
         fp.write('%d\n' %(max))
         fp.close()
-    
-    
+
+
 def sys_make_devices():
     if not os.access('/dev/portals', os.R_OK):
         run('mknod /dev/portals c 10 240')
@@ -2910,7 +3075,7 @@ def add_to_path(new_dir):
     if new_dir in syspath:
         return
     os.environ['PATH'] = os.environ['PATH'] + ':' + new_dir
-    
+
 def default_debug_path():
     path = '/tmp/lustre-log'
     if os.path.isdir('/r'):
@@ -2988,7 +3153,7 @@ lconf_options = [
               PARAM),
     ('minlevel', "Minimum level of services to configure/cleanup",
                  INTPARAM, 0),
-    ('maxlevel', """Maximum level of services to configure/cleanup 
+    ('maxlevel', """Maximum level of services to configure/cleanup
                     Levels are aproximatly like:
                             10 - netwrk
                             20 - device, ldlm
@@ -3019,14 +3184,14 @@ lconf_options = [
     ('inactive', """The name of an inactive service, to be ignored during
                     mounting (currently OST-only). Can be repeated.""",
                 PARAMLIST),
-    ]      
+    ]
 
 def main():
     global lctl, config, toplustreDB, CONFIG_FILE
 
     # in the upcall this is set to SIG_IGN
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-    
+
     cl = Lustre.Options("lconf", "config.xml", lconf_options)
     try:
         config, args = cl.parse(sys.argv[1:])
@@ -3049,7 +3214,7 @@ def main():
     random.seed(seed)
 
     sanitise_path()
-    
+
     init_select(config.select)
 
     if len(args) > 0:
@@ -3131,8 +3296,12 @@ def main():
 
     doHost(lustreDB, node_list)
 
-    if config.record:
-        lctl.end_record()
+    if not config.record:
+        return
+
+    lctl.end_record()
+
+    process_updates(db, config.record_device, config.record_log)
 
 if __name__ == "__main__":
     try: