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