Whamcloud - gitweb
- config and cleanup is working from obd to lov
[fs/lustre-release.git] / lustre / utils / lconf
1 #!/usr/bin/env python
2 #
3 #  Copyright (C) 2002 Cluster File Systems, Inc.
4 #   Author: Robert Read <rread@clusterfs.com>
5
6 #   This file is part of Lustre, http://www.lustre.org.
7 #
8 #   Lustre is free software; you can redistribute it and/or
9 #   modify it under the terms of version 2 of the GNU General Public
10 #   License as published by the Free Software Foundation.
11 #
12 #   Lustre is distributed in the hope that it will be useful,
13 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #   GNU General Public License for more details.
16 #
17 #   You should have received a copy of the GNU General Public License
18 #   along with Lustre; if not, write to the Free Software
19 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #
21 # lconf - lustre configuration tool
22 #
23 # lconf is the main driver script for starting and stopping
24 # lustre filesystem services.
25 #
26 # Based in part on the XML obdctl modifications done by Brian Behlendorf 
27
28 import sys, getopt
29 import string, os, stat, popen2
30 import re, exceptions
31 import xml.dom.minidom
32
33 # Global parameters
34 TCP_ACCEPTOR = 'acceptor'
35 LCTL = './lctl' # fix this...
36 options = {}
37 #
38 # Maximum number of devices to search for.
39 # (the /dev/loop* nodes need to be created beforehand)
40 MAX_LOOP_DEVICES = 256
41
42
43 def usage():
44     print """usage: lconf --ldap server | config.xml
45
46 config.xml          Lustre configuration in xml format.
47 --ldap server       LDAP server with lustre config database
48
49 Options:
50 -v|--verbose           
51 --debug             Don't send lctl commenads           
52 --reformat          Reformat all devices (will confirm)
53 --lustre="src dir"  Base directory of lustre sources. Used to search
54                     for modules.
55 --portals=src       Portals source 
56 --makeldiff         Translate xml source to LDIFF 
57 --cleanup           Cleans up config. (Shutdown)
58 --iam myname        ??
59
60 (SCRIPT STILL UNDER DEVELOPMENT, MOST FUNCTIONALITY UNIMPLEMENTED)
61 """
62
63 # ============================================================ 
64 # debugging and error funcs
65
66 def fixme(msg = "this feature"):
67     raise RuntimeError, msg + ' not implmemented yet.'
68
69 def panic(*args):
70     msg = string.join(map(str,args))
71     print msg
72     raise RuntimeError, msg
73
74 def log(*args):
75     msg = string.join(map(str,args))
76     print msg
77
78 def logall(msgs):
79     for s in msgs:
80         print string.strip(s)
81
82 def debug(*args):
83     msg = string.join(map(str,args))
84     if isverbose(): print msg
85
86 def isverbose():
87     return options.has_key('verbose') and  options['verbose'] == 1
88
89 def isnotouch():
90     return options.has_key('debug') and options['debug'] == 1
91
92 # ============================================================
93 # locally defined exceptions
94 class CommandError (exceptions.Exception):
95     def __init__(self, args=None):
96         self.args = args
97
98 # ============================================================
99 # Various system-level functions
100 # (ideally moved to their own module)
101
102 # Run a command and return the output and status.
103 # stderr is sent to /dev/null, could use popen3 to
104 # save it if necessary
105 def run(*args):
106     cmd = string.join(map(str,args))
107     debug ("+", cmd)
108     if isnotouch(): return ([], 0)
109     f = os.popen(cmd + ' 2>&1')
110     out = f.readlines()
111     ret = f.close()
112     if ret:
113         ret = ret >> 8
114     else:
115         ret = 0
116     return (ret, out)
117
118 # run lctl
119 # the cmds are written to stdin of lctl
120 # lctl doesn't return errors when run in script mode, so
121 #  stderr is checked
122 # should modify command line to accept multiple commands, or
123 #  create complex command line options
124 def run_lctl(cmds):
125     debug("+", LCTL, cmds)
126     if isnotouch(): return ([], 0)
127     p = popen2.Popen3(LCTL, 1)
128     p.tochild.write(cmds + "\n")
129     p.tochild.close()
130     out = p.fromchild.readlines()
131     ret = p.poll()
132     err = p.childerr.readlines()
133     if ret or len(err):
134         log (LCTL, "error:", ret)
135         logall(err)
136         raise CommandError, err
137     return ret, out
138
139
140 # is the path a block device?
141 def is_block(path):
142     s = ()
143     try:
144         s =  os.stat(path)
145     except OSError:
146         return 0
147     return stat.S_ISBLK(s[stat.ST_MODE])
148
149 # build fs according to type
150 # fixme: dangerous
151 def mkfs(fstype, dev):
152     if(fstype == 'ext3'):
153         mkfs = 'mkfs.ext2 -j'
154     elif (fstype == 'extN'):
155         mkfs = 'mkfs.ext2 -j'
156     else:
157         print 'unsupported fs type: ', fstype
158     if not is_block(dev):
159         force = '-F'
160     else:
161         force = ''
162     run (mkfs, force, dev)
163
164 # some systems use /dev/loopN, some /dev/loop/N
165 def loop_base():
166     import re
167     loop = '/dev/loop'
168     if not os.access(loop + str(0), os.R_OK):
169         loop = loop + '/'
170         if not os.access(loop + str(0), os.R_OK):
171             panic ("can't access loop devices")
172     return loop
173     
174 # find loop device assigned to thefile
175 def find_loop(file):
176     loop = loop_base()
177     for n in xrange(0, MAX_LOOP_DEVICES):
178         dev = loop + str(n)
179         if os.access(dev, os.R_OK):
180             (stat, out) = run('losetup', dev)
181             if (out and stat == 0):
182                 m = re.search(r'\((.*)\)', out[0])
183                 if m and file == m.group(1):
184                     return dev
185         else:
186             break
187     return ''
188
189 # create file if necessary and assign the first free loop device
190 def init_loop(file, size, fstype):
191     dev = find_loop(file)
192     if dev:
193         print 'WARNING file:', file, 'already mapped to', dev
194         return dev
195     if not os.access(file, os.R_OK | os.W_OK):
196         run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size,  file))
197     loop = loop_base()
198     # find next free loop
199     for n in xrange(0, MAX_LOOP_DEVICES):
200         dev = loop + str(n)
201         if os.access(dev, os.R_OK):
202             (stat, out) = run('losetup', dev)
203             if (stat):
204                 run('losetup', dev, file)
205                 return dev
206         else:
207             print "out of loop devices"
208             return ''
209     print "out of loop devices"
210     return ''
211
212 # undo loop assignment
213 def clean_loop(file):
214     dev = find_loop(file)
215     if dev:
216         ret, out = run('losetup -d', dev)
217         if ret:
218             log('unable to clean loop device:', dev, 'for file:', file)
219             logall(out)
220
221 # initialize a block device if needed
222 def block_dev(dev, size, fstype, format):
223     if isnotouch(): return dev
224     if not is_block(dev):
225         dev = init_loop(dev, size, fstype)
226     if (format == 'yes'):
227         mkfs(fstype, dev)
228     return dev
229
230 # create a new device with lctl
231 def lctl_network(net, nid):
232     cmds =  """
233   network %s
234   mynid %s
235   quit""" % (net, nid)
236     run_lctl(cmds)
237
238 # create a new connection 
239 def lctl_connect(net, nid, port, servuuid):
240     cmds =  """
241   network %s
242   connect %s %d
243   add_uuid %s %s
244   quit""" % (net, nid, port,  servuuid, nid)
245     run_lctl(cmds)
246
247 # create a new device with lctl
248 def lctl_disconnect(net, nid, port, servuuid):
249     cmds =  """
250   network %s
251   disconnect %s 
252   quit""" % (net, nid)
253     run_lctl(cmds)
254
255 # create a new device with lctl
256 def lctl_newdev(attach, setup):
257     cmds = """
258   newdev
259   attach %s
260   setup %s
261   quit""" % (attach, setup)
262     run_lctl(cmds)
263
264 # cleanup a device
265 def lctl_cleanup(name, uuid):
266     cmds = """
267   device $%s
268   cleanup
269   detach
270   quit""" % (name)
271     run_lctl(cmds)
272
273 # create an lov
274 def lctl_lovconfig(uuid, mdcuuid, stripe_cnt, stripe_sz, pattern, devlist):
275     cmds = """
276   device $%s
277   probe
278   lovconfig %s %d %d %s %s
279   quit""" % (mdcuuid, uuid, stripe_cnt, stripe_sz, pattern, devlist)
280     run_lctl(cmds)
281
282 # ============================================================
283 # Functions to prepare the various objects
284
285 def prepare_ldlm(node):
286     (name, uuid) = getNodeAttr(node)
287     print 'LDLM:', name, uuid
288     lctl_newdev(attach="ldlm %s %s" % (name, uuid),
289                 setup ="")
290     
291 def prepare_lov(node):
292     (name, uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist) = getLOVInfo(node)
293     print 'LOV:', name, uuid
294     lctl_lovconfig(uuid, mdcuuid, stripe_cnt, strip_sz, pattern, devlist)
295
296 def prepare_network(node):
297     (name, uuid, type, nid, port) = getNetworkInfo(node)
298     print 'NETWORK:', type, nid, port
299     if type == 'tcp':
300         run(TCP_ACCEPTOR, port)
301     lctl_network(type, nid)
302
303
304 # need to check /proc/mounts and /etc/mtab before
305 # formatting anything.
306 # FIXME: check if device is already formatted.
307 def prepare_obd(obd):
308     (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
309     print "OBD: ", name, obdtype, dev, size, fstype, format
310     dev = block_dev(dev, size, fstype, format)
311     lctl_newdev(attach="%s %s %s" % (obdtype, name, uuid),
312                 setup ="%s %s" %(dev, fstype))
313     
314
315 def prepare_ost(ost):
316     name, uuid, obd = getOSTInfo(ost)
317     print "OST: ", name, uuid, obd
318     lctl_newdev(attach="ost %s %s" % (name, uuid),
319                 setup ="$%s" % (obd))
320
321 def prepare_mds(node):
322     (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
323     print "MDS: ", name, dev, size, fstype
324     # setup network for mds, too
325     dev = block_dev(dev, size, fstype, format)
326     lctl_newdev(attach="mds %s %s" % (name, uuid),
327                 setup ="%s %s" %(dev, fstype))
328
329 def prepare_osc(node):
330     (name, uuid, obduuid, srvuuid) = getOSCInfo(node)
331     print 'OSC:', name, uuid, obduuid, srvuuid
332     net = lookup(node.parentNode, srvuuid)
333     srvname, srvuuid, net, server, port = getNetworkInfo(net)
334     lctl_connect(net, server, port, srvuuid)
335     lctl_newdev(attach="osc %s %s" % (name, uuid),
336                 setup ="%s %s" %(obduuid, srvuuid))
337
338 def prepare_mdc(node):
339     (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
340     print 'MDC:', name, uuid, mdsuuid, netuuid
341     lctl_newdev(attach="mdc %s %s" % (name, uuid),
342                 setup ="%s %s" %(mdsuuid, netuuid))
343
344 def prepare_mountpoint(node):
345     print 'MTPT:'
346
347 # ============================================================
348 # Functions to cleanup the various objects
349
350 def cleanup_ldlm(node):
351     (name, uuid) = getNodeAttr(node)
352     print 'LDLM:', name, uuid
353     try:
354         lctl_cleanup(name, uuid)
355     except CommandError:
356         print "cleanup failed: ", name
357
358 def cleanup_lov(node):
359     (name, uuid) = getNodeAttr(node)
360     print 'LOV:', name, uuid
361
362     #lctl_cleanup(name, uuid)
363
364 def cleanup_network(node):
365     (name, uuid, type, nid, port) = getNetworkInfo(node)
366     print 'NETWORK:', type, nid, port
367     #lctl_network(type, nid)
368
369 # need to check /proc/mounts and /etc/mtab before
370 # formatting anything.
371 # FIXME: check if device is already formatted.
372 def cleanup_obd(obd):
373     (name, uuid, obdtype, dev, size, fstype, format) = getOBDInfo(obd)
374     print "OBD: ", name, obdtype, dev, size, fstype, format
375     try:
376         lctl_cleanup(name, uuid)
377     except CommandError:
378         print "cleanup failed: ", name
379     clean_loop(dev)
380
381 def cleanup_ost(ost):
382     name, uuid, obd = getOSTInfo(ost)
383     print "OST: ", name, uuid, obd
384     try:
385         lctl_cleanup(name, uuid)
386     except CommandError:
387         print "cleanup failed: ", name
388
389 def cleanup_mds(node):
390     (name, uuid, dev, size, fstype, format) = getMDSInfo(node)
391     print "MDS: ", name, dev, size, fstype
392     try:
393         lctl_cleanup(name, uuid)
394     except CommandError:
395         print "cleanup failed: ", name
396     clean_loop(dev)
397         
398
399 def cleanup_mdc(node):
400     (name, uuid, mdsuuid, netuuid) = getMDCInfo(node)
401     print 'MDC:', name, uuid, mdsuuid, netuuid
402     try:
403         lctl_cleanup(name, uuid)
404     except CommandError:
405         print "cleanup failed: ", name
406
407
408 def cleanup_osc(node):
409     (name, uuid, obduuid, srvuuid) = getOSCInfo(node)
410     print 'OSC:', name, uuid, obduuid, srvuuid
411     net = lookup(node.parentNode, srvuuid)
412     netname, netuuid, net, server, port = getNetworkInfo(net)
413     try:
414         lctl_disconnect(net, server, port, srvuuid)
415         lctl_cleanup(name, uuid)
416     except CommandError:
417         print "cleanup failed: ", name
418
419 def cleanup_mountpoint(node):
420     print 'MTPT:'
421
422 # ============================================================
423 # XML processing and query
424
425 def getDevice(obd):
426     dev = obd.getElementsByTagName('device')[0]
427     dev.normalize();
428     try:
429         size = int(dev.getAttribute('size'))
430     except ValueError:
431         size = 0
432     return dev.firstChild.data, size
433     
434
435 def getNetworkInfo(node):
436     name, uuid = getNodeAttr(node);
437     type = node.getAttribute('type')
438     nid = getText(node, 'server', "")
439     port = int(getText(node, 'port', 0))
440     return name, uuid, type, nid, port
441     
442 # extract device attributes for an obd
443 def getNodeAttr(node):
444     name = node.getAttribute('name')
445     uuid = node.getAttribute('uuid')
446     return name, uuid
447
448 def getOBDInfo(obd):
449     name, uuid = getNodeAttr(obd);
450     obdtype = obd.getAttribute('type')
451     devname, size = getDevice(obd)
452     fstype = getText(obd, 'fstype')
453     format = getText(obd, 'autoformat')
454     return (name, uuid, obdtype, devname, size, fstype, format)
455     
456 # extract LOV
457 def getLOVInfo(node):
458     name, uuid = getNodeAttr(node)
459     devs = node.getElementsByTagName('devices')[0]
460     stripe_sz = int(devs.getAttribute('stripesize'))
461     pattern = int(devs.getAttribute('pattern'))
462     mdcref =  node.getElementsByTagName('mdc_ref')[0]
463     mdcuuid = mdcref.getAttribute('uuidref')
464     mdc= lookup(node.parentNode, mdcuuid)
465     mdcname = getName(mdc)
466     devlist = ""
467     stripe_cnt = 0
468     for child in devs.childNodes:
469         if child.nodeName == 'obd_ref':
470             devlist = devlist +  child.getAttribute('uuidref') + " "
471             strip_cnt = stripe_cnt + 1
472     return (name, uuid, mdcname, stripe_cnt, stripe_sz, pattern, devlist)
473     
474 # extract device attributes for an obd
475 def getMDSInfo(node):
476     name, uuid = getNodeAttr(node)
477     devname, size = getDevice(node)
478     fstype = getText(node, 'fstype')
479     format = getText(node, 'autoformat', "no")
480     return (name, uuid, devname, size, fstype, format)
481
482 # extract device attributes for an obd
483 def getMDCInfo(node):
484     name, uuid = getNodeAttr(node)
485     ref = node.getElementsByTagName('mds_ref')[0]
486     mdsuuid = ref.getAttribute('uuidref')
487     ref = node.getElementsByTagName('network_ref')[0]
488     netuuid = ref.getAttribute('uuidref')
489     return (name, uuid, mdsuuid, netuuid)
490
491     
492 # extract device attributes for an obd
493 def getOSTInfo(node):
494     name, uuid = getNodeAttr(node)
495     ref = node.getElementsByTagName('obd_ref')[0]
496     uuid = ref.getAttribute('uuidref')
497     obd = lookup(node.parentNode, uuid)
498     if obd:
499          obdname = getOBDInfo(obd)[0]
500     else:
501         obdname = "OBD NOT FOUND"
502     return (name, uuid, obdname)
503
504 # extract device attributes for an obd
505 def getOSCInfo(node):
506     name, uuid = getNodeAttr(node)
507     ref = node.getElementsByTagName('obd_ref')[0]
508     obduuid = ref.getAttribute('uuidref')
509     ref = node.getElementsByTagName('network_ref')[0]
510     ostuuid = ref.getAttribute('uuidref')
511     return (name, uuid, obduuid, ostuuid)
512
513     
514 # Get the text content from the first matching child
515 def getText(node, tag, default=""):
516     list = node.getElementsByTagName(tag)
517     if len(list) > 0:
518         node = list[0]
519         node.normalize()
520         return node.firstChild.data
521     else:
522         return default
523
524 # Recusively search from node for a uuid
525 def lookup(node, uuid):
526     for n in node.childNodes:
527         # this service_id check is ugly. need some other way to
528         # differentiate between definitions and references
529         if n.nodeType == n.ELEMENT_NODE:
530             if getUUID(n) == uuid:
531                 return n
532             else:
533                 n = lookup(n, uuid)
534                 if n: return n
535     return None
536
537 # Get name attribute of node
538 def getName(node):
539     return node.getAttribute('name')
540
541 def getRef(node):
542     return node.getAttribute('uuidref')
543
544 # Get name attribute of node
545 def getUUID(node):
546     return node.getAttribute('uuid')
547
548 # the tag name is the service type
549 # fixme: this should do some checks to make sure the node is a service
550 def getServiceType(node):
551     return node.nodeName
552
553 #
554 # determine what "level" a particular node is at.
555 # the order of iniitailization is based on level.  objects
556 # are assigned a level based on type:
557 #  net,devices:1, obd, mdd:2  mds,ost:3 osc,mdc:4 mounts:5
558 def getServiceLevel(node):
559     type = getServiceType(node)
560     if type in ('network', 'device', 'ldlm'):
561         return 1
562     elif type in ('obd', 'mdd'):
563         return 2
564     elif type in ('mds','ost'):
565         return 3
566     elif type in ('mdc','osc'):
567         return 4
568     elif type in ('lov',):
569         return 5
570     elif type in ('mountpoint',):
571         return 6
572     return 0
573
574 #
575 # return list of services in a profile. list is a list of tuples
576 # [(level, node),]
577 def getServices(lustreNode, profileNode):
578     list = []
579     for n in profileNode.childNodes:
580         if n.nodeType == n.ELEMENT_NODE:
581             servNode = lookup(lustreNode, getRef(n))
582             if not servNode:
583                 panic('service not found: ' + getName(n))
584             level = getServiceLevel(servNode)
585             list.append((level, servNode))
586     list.sort()
587     return list
588
589 def getProfile(lustreNode, profile):
590     profList = lustreNode.getElementsByTagName('profile')
591     for prof in profList:
592         if getName(prof) == profile:
593             return prof
594     return None
595
596 # ============================================================
597 # lconf type level logic
598 #
599
600 #
601 # Start a service.
602 def startService(node):
603     type = getServiceType(node)
604     debug('Starting service:', type, getName(node), getUUID(node))
605     # there must be a more dynamic way of doing this...
606     if type == 'ldlm':
607         prepare_ldlm(node)
608     elif type == 'lov':
609         prepare_lov(node)
610     elif type == 'network':
611         prepare_network(node)
612     elif type == 'obd':
613         prepare_obd(node)
614     elif type == 'ost':
615         prepare_ost(node)
616     elif type == 'mds':
617         prepare_mds(node)
618     elif type == 'osc':
619         prepare_osc(node)
620     elif type == 'mdc':
621         prepare_mdc(node)
622     elif type == 'mountpoint':
623         prepare_mountpoint(node)
624
625 #
626 # Prepare the system to run lustre using a particular profile
627 # in a the configuration. 
628 #  * load & the modules
629 #  * setup networking for the current node
630 #  * make sure partitions are in place and prepared
631 #  * initialize devices with lctl
632 # Levels is important, and needs to be enforced.
633 def startProfile(lustreNode, profile):
634     profileNode = getProfile(lustreNode, profile)
635     if not profileNode:
636         panic("profile:", profile, "not found.")
637     services = getServices(lustreNode, profileNode)
638     for s in services:
639         startService(s[1])
640
641
642 # Stop a service.
643 def stopService(node):
644     type = getServiceType(node)
645     debug('Stopping service:', type, getName(node), getUUID(node))
646     # there must be a more dynamic way of doing this...
647     if type == 'ldlm':
648         cleanup_ldlm(node)
649     elif type == 'lov':
650         cleanup_lov(node)
651     elif type == 'network':
652         cleanup_network(node)
653     elif type == 'obd':
654         cleanup_obd(node)
655     elif type == 'ost':
656         cleanup_ost(node)
657     elif type == 'mds':
658         cleanup_mds(node)
659     elif type == 'osc':
660         cleanup_osc(node)
661     elif type == 'mdc':
662         cleanup_mdc(node)
663     elif type == 'mountpoint':
664         cleanup_mountpoint(node)
665
666 # Shutdown services in reverse order than they were started
667 def cleanupProfile(lustreNode, profile):
668     profileNode = getProfile(lustreNode, profile)
669     if not profileNode:
670         panic("profile:", profile, "not found.")
671     services = getServices(lustreNode, profileNode)
672     services.reverse()
673     for s in services:
674         stopService(s[1])
675
676 #
677 # Command line processing
678 #
679 def parse_cmdline(argv):
680     short_opts = "hv"
681     long_opts = ["ldap", "reformat", "lustre=",
682                  "portals=", "makeldiff", "cleanup", "iam=",
683                  "help", "debug"]
684     opts = []
685     args = []
686     global options
687     try:
688         opts, args = getopt.getopt(argv, short_opts, long_opts)
689     except getopt.GetoptError:
690         print "invalid opt"
691         usage()
692         sys.exit(2)
693
694     for o, a in opts:
695         if o in ("-h", "--help"):
696             usage()
697             sys.exit()
698         if o == "--cleanup":
699             options['cleanup'] = 1
700         if o in ("-v",  "--verbose"):
701             options['verbose'] = 1
702         if o == "--debug":
703             options['debug'] = 1
704         if o == "--portals":
705             options['portals'] = a
706         if o == "--lustre":
707             options['lustre'] = a
708         if o  == "--reformat":
709             options['reformat'] = 1
710     return args
711
712 #
713 # Initialize or shutdown lustre according to a configuration file
714 #   * prepare the system for lustre
715 #   * configure devices with lctl
716 # Shutdown does steps in reverse
717 #
718 def main():
719     global options
720     args = parse_cmdline(sys.argv[1:])
721     if len(args) > 0:
722         dom = xml.dom.minidom.parse(args[0])
723     else:
724         usage()
725         fixme("ldap not implemented yet")
726
727     if options.has_key('cleanup'):
728         cleanupProfile(dom.childNodes[0], 'local-profile')
729     else:
730         startProfile(dom.childNodes[0], 'local-profile')
731     
732
733 # try a different traceback style. (dare ya to try this in java)
734 def my_traceback(file=None):
735     """Print the list of tuples as returned by extract_tb() or
736     extract_stack() as a formatted stack trace to the given file."""
737     import traceback
738     (t,v, tb) = sys.exc_info()
739     list = traceback.extract_tb(tb)
740     if not file:
741         file = sys.stderr
742     for filename, lineno, name, line in list:
743         if line:
744             print '%s:%04d %-14s %s' % (filename,lineno, name, line.strip())
745         else:
746             print '%s:%04d %s' % (filename,lineno, name)
747     print '%s: %s' % (t, v)
748
749 if __name__ == "__main__":
750     try:
751         main()
752     except:
753         my_traceback()
754