Whamcloud - gitweb
* MDC element is no longer created in config. The MDC UUIDS are generated
[fs/lustre-release.git] / lustre / utils / lmc
1 #!/usr/bin/env python
2 # Copyright (C) 2002 Cluster File Systems, Inc.
3 # Author: Robert Read <rread@clusterfs.com>
4
5 #   This file is part of Lustre, http://www.lustre.org.
6 #
7 #   Lustre is free software; you can redistribute it and/or
8 #   modify it under the terms of version 2 of the GNU General Public
9 #   License as published by the Free Software Foundation.
10 #
11 #   Lustre is distributed in the hope that it will be useful,
12 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #   GNU General Public License for more details.
15 #
16 #   You should have received a copy of the GNU General Public License
17 #   along with Lustre; if not, write to the Free Software
18 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #
20
21 """
22 lmc - lustre configurtion data  manager
23  
24  Basic plan for lmc usage:
25 # create nodes
26 ./lmc --output config.xml --node server --net server1 tcp 
27 ./lmc --merge config.xml  --node client --net client1 tcp
28 ./lmc --merge config.xml  --node client --route gw lo [hi]
29 ./lmc --merge config.xml --router --node gw1 --net gw1 tcp
30 ./lmc --merge config.xml --node gw1 --net 1 elan
31
32 ./lmc --merge config.xml --route elan 1 1 100
33 ./lmc --merge config.xml --route tcp gw1 ba1
34
35
36
37 # configure server
38 ./lmc --merge config.xml  --node server --mds mds1 /tmp/mds1 50000
39
40 # create lov
41 ./lmc --merge config.xml  --lov lov1 mds1 65536 0 0
42 ./lmc --merge config.xml  --node server --lov lov1 --ost /tmp/ost1 100000
43 ./lmc --merge config.xml  --node server --lov lov1 --ost /tmp/ost2 100000
44
45 # create client config
46 ./lmc --merge config.xml  --node client --mtpt /mnt/lustre mds1 lov1
47
48 """
49
50 import sys, os, getopt, string
51 import xml.dom.minidom
52 from xml.dom.ext import PrettyPrint
53
54
55 DEFAULT_PORT = 988 # XXX What is the right default acceptor port to use?
56
57 def usage():
58     print """usage: lmc [--node --ost | --mtpt | --lov] args
59 Commands:
60 --node node_name 
61    Node_name by itself it will create a new node. If the --router
62    option is used when creating a new node, then that node will also
63    be configured as a router. When used with other commands it
64    specifies the node to modify.
65
66 --net hostname nettype [port, recv_buf, send_buf]
67    Nettype is either tcp, elan, or gm.
68    Requires --node
69
70 --route net gw lo [hi]
71    This command is used to create  routes.  NET is the
72    network type this route will be used on.  The GW is an address of
73    one of the local interfaces. LO and HI represent a range of
74    addresses that can be reached through the gateway. If HI is not
75    set, then a route to the specific host in LO is created.
76
77 --mds device [size]
78    Create a MDS using the device
79    Requires --node 
80
81 --lov lov_name [mds_name stripe_sz stripe_off pattern]
82    Creates a logical volume
83    When used with other commands, it specifics the lov to modify
84
85 --ost device [size]
86    Creates an OBD/OST/OSC configuration triplet for a new device.
87    When used on "host", the device will be initialized and the OST
88    will be enabled. On client nodes, the OSC will be avaiable.
89    Requires --node
90    If --lov lov_name is used, this device is added to lov. 
91
92 --mtpt /mnt/point mds_name lov_name|osc_name 
93    Creates a client mount point.
94    Requires --node
95
96 Options:
97 --merge="xml file"  Add the new objects to an existing file
98 --format            Format the partitions if unformated
99                     NB: The autoformat option has been disabled until a safe
100                     method is implemented to determine if a block device has a
101                     filesystem.
102 --reformat          Reformat partitions (this should be an lconf arg,
103                     I think)
104 --obdtype="obdtype" Specifiy obdtype: valid ones are obdecho and obdfilter.
105                     This is only useful for the --ost command.
106                     The device parameters are ignored for the obdecho type.
107 """
108     sys.exit(1)
109
110 def error(*args):
111     msg = string.join(map(str,args))
112     print msg
113     sys.exit(1)
114     
115 #
116 # manage names and uuids
117 # need to initialize this by walking tree to ensure
118 # no duplicate names or uuids are created.
119 # this are just place holders for now.
120 # consider changing this to be like OBD-dev-host
121 def new_name(base):
122     ctr = 2
123     ret = base
124     while names.has_key(ret):
125         ret = "%s_%d" % (base, ctr)
126         ctr = 1 + ctr
127     names[ret] = 1
128     return ret
129
130 def new_uuid(name):
131     return "%s_UUID" % (name)
132
133 ldlm_name = 'ldlm'
134 ldlm_uuid = 'ldlm_UUID'
135 def new_lustre(dom):
136     """Create a new empty lustre document"""
137     # adding ldlm here is a bit of a hack, but one is enough.
138     str = """<lustre>
139     <ldlm name="%s" uuid="%s"/>
140     </lustre>""" % (ldlm_name, ldlm_uuid)
141     return dom.parseString(str)
142
143 names = {}
144 uuids = {}
145
146 def init_names(doc):
147     """initialize auto-name generation tables"""
148     global names, uuids
149     # get all elements that contain a name attribute
150     for n in doc.childNodes:
151         if n.nodeType == n.ELEMENT_NODE:
152             if getName(n):
153                 names[getName(n)] = 1
154                 uuids[getUUID(n)] = 1
155             init_names(n)
156
157 def get_format_flag(options):
158     if options.has_key('format'):
159         if options['format']:
160             return 'yes'
161     return 'no'
162
163 ############################################################
164 # Build config objects using DOM
165 #
166 class GenConfig:
167     doc = None
168     dom = None
169     def __init__(self, doc):
170         self.doc = doc
171
172     def ref(self, type, uuid):
173         """ generate <[type]_ref uuidref="[uuid]"/> """
174         tag = "%s_ref" % (type)
175         ref = self.doc.createElement(tag)
176         ref.setAttribute("uuidref", uuid)
177         return ref
178     
179     def newService(self, tag, name, uuid):
180         """ create a new  service elmement, which requires name and uuid attributes """
181         new = self.doc.createElement(tag)
182         new.setAttribute("name", name);
183         new.setAttribute("uuid", uuid);
184         return new
185     
186     def addText(self, node, str):
187         txt = self.doc.createTextNode(str)
188         node.appendChild(txt)
189
190     def addElement(self, node, tag, str=None):
191         """ create a new element and add it as a child to node. If str is passed,
192             a text node is created for the new element"""
193         new = self.doc.createElement(tag)
194         if str:
195             self.addText(new, str)
196         node.appendChild(new)
197         return new
198
199     def network(self, name, uuid, hostname, net, port=0, tcpbuf=0):
200         """create <network> node"""
201         network = self.newService("network", name, uuid)
202         network.setAttribute("type", net);
203         self.addElement(network, "server", hostname)
204         if port:
205             self.addElement(network, "port", "%d" %(port))
206         if tcpbuf:
207             self.addElement(network, "send_mem", "%d" %(tcpbuf))
208             self.addElement(network, "recv_mem", "%d" %(tcpbuf))
209             
210         return network
211
212     def route(self, net_type, gw, lo, hi):
213         """ create one entry for the route table """
214         ref = self.doc.createElement('route')
215         ref.setAttribute("type", net_type)
216         ref.setAttribute("gw", gw)
217         ref.setAttribute("lo", lo)
218         if hi:
219             ref.setAttribute("hi", hi)
220         return ref
221     
222     def node(self, name, uuid):
223         """ create a host """
224         node = self.newService("node", name, uuid)
225         self.addElement(node, 'profile')
226         return node
227
228     def ldlm(self, name, uuid):
229         """ create a ldlm """
230         ldlm = self.newService("ldlm", name, uuid)
231         return ldlm
232
233     def obd(self, name, uuid, fs, obdtype, devname, format, dev_size=0):
234         obd = self.newService("obd", name, uuid)
235         obd.setAttribute('type', obdtype)
236         if fs:
237             self.addElement(obd, "fstype", fs)
238         if devname:
239             dev = self.addElement(obd, "device", devname)
240             if (dev_size):
241                 dev.setAttribute("size", "%s" % (dev_size))
242             self.addElement(obd, "autoformat", format)
243         return obd
244
245     def osc(self, name, uuid, obd_uuid, net_uuid):
246         osc = self.newService("osc", name, uuid)
247         osc.appendChild(self.ref("ost", net_uuid))
248         osc.appendChild(self.ref("obd", obd_uuid))
249         return osc
250
251     def ost(self, name, uuid, obd_uuid, net_uuid):
252         ost = self.newService("ost", name, uuid)
253         ost.appendChild(self.ref("network", net_uuid))
254         ost.appendChild(self.ref("obd", obd_uuid))
255         return ost
256
257     def lov(self, name, uuid, mds_uuid, stripe_sz, stripe_off, pattern):
258         lov = self.newService("lov", name, uuid)
259         lov.appendChild(self.ref("mds", mds_uuid))
260         devs = self.addElement(lov, "devices" )
261         devs.setAttribute("stripesize", stripe_sz)
262         devs.setAttribute("stripeoffset", stripe_off)
263         devs.setAttribute("pattern", pattern)
264         return lov
265
266     def lovconfig(self, name, uuid, lov_uuid):
267         lovconfig = self.newService("lovconfig", name, uuid)
268         lovconfig.appendChild(self.ref("lov", lov_uuid))
269         return lovconfig
270
271     def mds(self, name, uuid, fs, devname, format, net_uuid, node_uuid,
272             failover_uuid = "", dev_size=0 ):
273         mds = self.newService("mds", name, uuid)
274         self.addElement(mds, "fstype", fs)
275         dev = self.addElement(mds, "device", devname)
276         if dev_size:
277             dev.setAttribute("size", "%s" % (dev_size))
278         self.addElement(mds, "autoformat", format)
279         mds.appendChild(self.ref("network", net_uuid))
280         mds.appendChild(self.ref("node", node_uuid))
281         if failover_uuid:
282             mds.appendChild(self.ref("failover", failover_uuid))
283         return mds
284
285     def mountpoint(self, name, uuid, mds_uuid, osc_uuid, path):
286         mtpt = self.newService("mountpoint", name, uuid)
287         mtpt.appendChild(self.ref("mds", mds_uuid))
288         mtpt.appendChild(self.ref("osc", osc_uuid))
289         self.addElement(mtpt, "path", path)
290         return mtpt
291
292 ############################################################
293 # Utilities to query a DOM tree
294 # Using this functions we can treat use config information
295 # directly as a database.
296 def getName(n):
297     return n.getAttribute('name')
298
299 def getUUID(node):
300     return node.getAttribute('uuid')
301
302
303 def findByName(lustre, name, tag = ""):
304     for n in lustre.childNodes:
305         if n.nodeType == n.ELEMENT_NODE:
306             if tag and n.nodeName != tag:
307                 continue
308             if getName(n) == name:
309                 return n
310             else:
311                 n = findByName(n, name)
312                 if n: return n
313     return None
314
315
316 def lookup(node, uuid):
317     for n in node.childNodes:
318         if n.nodeType == n.ELEMENT_NODE:
319             if getUUID(n) == uuid:
320                 return n
321             else:
322                 n = lookup(n, uuid)
323                 if n: return n
324     return None
325             
326
327 def mds2node(lustre, mds_name):
328     """ Find the node a MDS is configured on """
329     mds = findByName(lustre, mds_name, 'mds')
330     ref = mds.getElementsByTagName('node_ref')
331     if not ref:
332         error("no node found for:", mds_name)
333     node_uuid = ref[0].getAttribute('uuidref')
334     node = lookup(lustre, node_uuid)
335     if not node:
336         error("no node found for :", mds_name)
337     return node
338
339
340 def name2uuid(lustre, name, tag="",  fatal=1):
341     ret = findByName(lustre, name, tag)
342     if not ret:
343         if fatal:
344             error('name2uuid:', name, "not found.")
345         else:
346             return ""
347     return getUUID(ret)
348     
349
350 # XXX: assumes only one network element per node. will fix this
351 # as soon as support for routers is added
352 def get_net_uuid(lustre, node_name):
353     """ get a network uuid for a node_name """
354     node = findByName(lustre, node_name, "node")
355     if not node:
356         error ("node not found:", node_name)
357     net = node.getElementsByTagName('network')
358     if net:
359         return getUUID(net[0])
360     return None
361
362
363 def lov_add_osc(gen, lov, osc_uuid):
364     devs = lov.getElementsByTagName('devices')
365     if len(devs) == 1:
366         devs[0].appendChild(gen.ref("osc", osc_uuid))
367     else:
368         error("No devices element found for LOV:", lov)
369
370                             
371 def node_add_profile(gen, node, ref, uuid):
372     ret = node.getElementsByTagName('profile')
373     if not ret:
374         error('node has no profile:', node)
375     ret[0].appendChild(gen.ref(ref, uuid))
376     
377 def get_attr(dom_node, attr, default=""):
378     v = dom_node.getAttribute(attr)
379     if v:
380         return v
381     return default
382
383 ############################################################
384 # Top level commands
385 #
386 def do_add_node(gen, lustre,  options, node_name):
387     uuid = new_uuid(node_name)
388     node = gen.node(node_name, uuid)
389     node_add_profile(gen, node, 'ldlm', ldlm_uuid)
390     if options.has_key('router'):
391         node.setAttribute('router', '1')
392     lustre.appendChild(node)
393     return node
394
395     
396 def add_node(gen, lustre, options, args):
397     """ create a node with a network config """
398     if len(args) > 1:
399         usage()
400
401     node_name = options['node']
402
403     ret = findByName(lustre, node_name, "node")
404     if ret:
405         print "Node:", node_name, "exists."
406         return
407     do_add_node(gen, lustre, options, node_name)
408
409
410 def add_net(gen, lustre, options, args):
411     """ create a node with a network config """
412     if len(args) < 2:
413         usage()
414
415     node_name = options['node']
416     nid = args[0]
417     net_type = args[1]
418     port = 0
419     tcpbuf = 0
420
421     if net_type == 'tcp':
422         if len(args) > 2:
423             port = int(args[2])
424         else:
425             port = DEFAULT_PORT
426         if options.has_key('tcpbuf'):
427             tcpbuf = int(options['tcpbuf'])
428     elif net_type in ('elan', 'gm'):
429         port = 0
430     else:
431         print "Unknown net_type: ", net_type
432         sys.exit(2)
433
434     ret = findByName(lustre, node_name, "node")
435     if not ret:
436         node = do_add_node(gen, lustre, options, node_name)
437     else:
438         node = ret
439     net_name = new_name('NET_'+ node_name +'_'+ net_type)
440     net_uuid = new_uuid(net_name)
441     node.appendChild(gen.network(net_name, net_uuid, nid, net_type, port, tcpbuf))
442     node_add_profile(gen, node, "network", net_uuid)
443
444
445 def add_route(gen, lustre, options, args):
446     """ create a node with a network config """
447     if len(args) < 3:
448         usage()
449
450     node_name = options['node']
451     net_type= args[0]
452     gw = args[1]
453     lo = args[2]
454     hi = ''
455
456     if len(args) > 3:
457         hi = args[3]
458
459     node = findByName(lustre, node_name, "node")
460     if not node:
461         error (node_name, " not found.")
462     
463     netlist = node.getElementsByTagName('network')
464     net = netlist[0]
465     rlist = net.getElementsByTagName('route_tbl')
466     if len(rlist) > 0:
467         rtbl = rlist[0]
468     else:
469         rtbl = gen.addElement(net, 'route_tbl')
470     rtbl.appendChild(gen.route(net_type, gw, lo, hi))
471
472
473 def add_mds(gen, lustre, options, args):
474     if len(args) < 1:
475         usage()
476
477     if options.has_key('node'):
478         node_name = options['node']
479     else:
480         error("--mds requires a --node argument")
481
482     mds_name = new_name(options['mds'])
483     devname = args[0]
484     if len(args) > 1:
485         size = args[1]
486     else:
487         size = 0
488
489     mds_uuid = new_uuid(mds_name)
490
491     node_uuid = name2uuid(lustre, node_name)
492
493     node = findByName(lustre, node_name, "node")
494     node_add_profile(gen, node, "mds", mds_uuid)
495     net_uuid = get_net_uuid(lustre, node_name)
496     if not net_uuid:
497         error("NODE: ", node_name, "not found")
498
499
500     mds = gen.mds(mds_name, mds_uuid, "extN", devname, get_format_flag(options),
501                   net_uuid, node_uuid, dev_size=size)
502     lustre.appendChild(mds)
503                    
504
505 def add_ost(gen, lustre, options, args):
506     lovname = ''
507     obdtype = 'obdfilter'
508     devname = ''
509     size = 0
510     fstype = 'extN'
511     
512     if options.has_key('node'):
513         node_name = options['node']
514     else:
515         error("--ost requires a --node argument")
516
517     if options.has_key('lov'):
518         lovname = options['lov']
519
520     if options.has_key('obdtype'):
521         obdtype = options['obdtype']
522
523     if obdtype == 'obdecho':
524         fstype = ''
525     else:
526         if len(args) < 1:
527             usage()
528         devname = args[0]
529         if len(args) > 1:
530             size = args[1]
531         
532     obdname = new_name('OBD_'+ node_name)
533     oscname = new_name('OSC_'+ node_name)
534     ostname = new_name('OST_'+ node_name)
535     obd_uuid = new_uuid(obdname)
536     ost_uuid = new_uuid(ostname)
537     osc_uuid = new_uuid(oscname)
538
539     net_uuid = get_net_uuid(lustre, node_name)
540     if not net_uuid:
541         error("NODE: ", node_name, "not found")
542     
543     obd = gen.obd(obdname, obd_uuid, fstype, obdtype, devname, get_format_flag(options), size)
544     ost = gen.ost(ostname, ost_uuid, obd_uuid, net_uuid)
545     osc = gen.osc(oscname, osc_uuid, obd_uuid, ost_uuid)
546     
547     if lovname:
548         lov = findByName(lustre, lovname, "lov")
549         if not lov:
550             error("LOV:", lovname, "not found.")
551         lov_add_osc(gen, lov, osc_uuid)
552
553     node = findByName(lustre, node_name, "node")
554     node_add_profile(gen, node, 'obd', obd_uuid)
555     node_add_profile(gen, node, 'ost', ost_uuid)
556
557     lustre.appendChild(obd)
558     lustre.appendChild(osc)
559     lustre.appendChild(ost)
560
561                    
562 # this is generally only used by llecho.sh
563 def add_osc(gen, lustre, options, args):
564     """ add the osc to the profile for this node. """
565     if len(args) < 1:
566         usage()
567     osc_name = args[0]
568     if options.has_key('node'):
569         node_name = options['node']
570     else:
571         error("--osc requires a --node argument")
572     osc_uuid = name2uuid(lustre, osc_name)
573     node = findByName(lustre, node_name, "node")
574     node_add_profile(gen, node, 'osc', osc_uuid)
575
576
577 def add_lov(gen, lustre, options, args):
578     """ create a lov """
579     if len(args) < 4:
580         usage()
581
582     name = options['lov']
583     mds_name = args[0]
584     stripe_sz = args[1]
585     stripe_off = args[2]
586     pattern = args[3]
587     uuid = new_uuid(name)
588
589     ret = findByName(lustre, name, "lov")
590     if ret:
591         error("LOV: ", name, " already exists.")
592
593     mds_uuid = name2uuid(lustre, mds_name)
594     lov = gen.lov(name, uuid, mds_uuid, stripe_sz, stripe_off, pattern)
595     lustre.appendChild(lov)
596     
597     # add an lovconfig entry to the mds profile
598     lovconfig_name = new_name('LVCFG_' + name)
599     lovconfig_uuid = new_uuid(lovconfig_name)
600     node = mds2node(lustre, mds_name)
601     node_add_profile(gen, node, "lovconfig", lovconfig_uuid)
602     lovconfig = gen.lovconfig(lovconfig_name, lovconfig_uuid, uuid)
603     lustre.appendChild(lovconfig)
604
605
606
607 def add_mtpt(gen, lustre, options, args):
608     """ create mtpt on a node """
609     if len(args) < 3:
610         usage()
611
612     if options.has_key('node'):
613         node_name = options['node']
614     else:
615         error("--mtpt requires a --node argument")
616
617     path = args[0]
618     mds_name = args[1]
619     lov_name = args[2]
620
621     name = new_name('MNT_'+ node_name)
622
623     ret = findByName(lustre, name, "mountpoint")
624     if ret:
625         error("MOUNTPOINT: ", name, " already exists.")
626
627     mds_uuid = name2uuid(lustre, mds_name)
628     lov_uuid = name2uuid(lustre, lov_name, tag='lov', fatal=0)
629     if not lov_uuid:
630         lov_uuid = name2uuid(lustre, lov_name, tag='osc', fatal=1)
631
632     uuid = new_uuid(name)
633     mtpt = gen.mountpoint(name, uuid, mds_uuid, lov_uuid, path)
634     node = findByName(lustre, node_name, "node")
635     if not node:
636             error('node:',  node_name, "not found.")
637     node_add_profile(gen, node, "mountpoint", uuid)
638     lustre.appendChild(mtpt)
639
640
641 ############################################################
642 # Command line processing
643 #
644 def parse_cmdline(argv):
645     short_opts = "ho:i:m:"
646     long_opts = ["ost", "osc", "mtpt", "lov=", "node=", "mds=", "net", "tcpbuf=",
647                  "route", "router", "merge=", "format", "reformat", "output=",
648                  "obdtype=", "in=", "help"]
649     opts = []
650     args = []
651     options = {}
652     try:
653         opts, args = getopt.getopt(argv, short_opts, long_opts)
654     except getopt.error:
655         print "invalid opt"
656         usage()
657
658     for o, a in opts:
659         if o in ("-h", "--help"):
660             usage()
661         if o in ("-o", "--output"):
662             options['output'] = a
663         if o == "--ost":
664             options['ost'] = 1
665         if o == "--osc":
666             options['osc'] = 1
667         if o == "--mds":
668             options['mds'] = a
669         if o == "--net":
670             options['net'] = 1
671         if o == "--mtpt":
672             options['mtpt'] = 1
673         if o == "--node":
674             options['node'] = a
675         if o == "--route":
676             options['route'] = 1
677         if o == "--router":
678             options['router'] = 1
679         if o == "--lov":
680             options['lov'] = a
681         if o in ("-m", "--merge"):
682             options['merge'] = a
683         if o == "--obdtype":
684             options['obdtype'] = a
685         if o == "--tcpbuf":
686             options['tcpbuf'] = a
687         if o == "--format":
688             options['format'] = 1
689         if o  == "--reformat":
690             options['reformat'] = 1
691         if o  in ("--in" , "-i"):
692             options['in'] = a
693             
694     return options, args
695
696
697 # simple class for profiling
698 import time
699 class chrono:
700     def __init__(self):
701         self._start = 0
702     def start(self):
703         self._stop = 0
704         self._start = time.time()
705     def stop(self, msg=''):
706         self._stop = time.time()
707         if msg:
708             self.display(msg)
709     def dur(self):
710         return self._stop - self._start
711     def display(self, msg):
712         d = self.dur()
713         str = '%s: %g secs' % (msg, d)
714         print str
715
716 ############################################################
717 # Main
718 #
719 def main():
720     options, args = parse_cmdline(sys.argv[1:])
721     outFile = '-'
722
723     if options.has_key('merge'):
724         outFile = options['merge']
725         if os.access(outFile, os.R_OK):
726             doc = xml.dom.minidom.parse(outFile)
727         else:
728             doc = new_lustre(xml.dom.minidom)
729     elif options.has_key('in'):
730         doc = xml.dom.minidom.parse(options['in'])
731     else:
732         doc = new_lustre(xml.dom.minidom)
733
734     if options.has_key('output'):
735         outFile = options['output']
736
737     lustre = doc.documentElement
738     init_names(lustre)
739     if lustre.tagName != "lustre":
740         print "Existing config not valid."
741         sys.exit(1)
742
743     gen = GenConfig(doc)
744     if options.has_key('ost'):
745         add_ost(gen, lustre, options, args)
746     elif options.has_key('osc'):
747         add_osc(gen, lustre, options, args)
748     elif options.has_key('mtpt'):
749         add_mtpt(gen, lustre, options, args)
750     elif options.has_key('mds'):
751         add_mds(gen, lustre, options, args)
752     elif options.has_key('net'):
753         add_net(gen, lustre, options, args)
754     elif options.has_key('lov'):
755         add_lov(gen, lustre, options, args)
756     elif options.has_key('route'):
757         add_route(gen, lustre, options, args)
758     elif options.has_key('node'):
759         add_node(gen, lustre, options, args)
760     else:
761         print "Missing command"
762         usage()
763
764     if outFile == '-':
765         PrettyPrint(doc)
766     else:
767         PrettyPrint(doc, open(outFile,"w"))
768
769 if __name__ == "__main__":
770     main()
771
772