Whamcloud - gitweb
Branch b1_4_mountconf
[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 configuration data manager
23
24   See the man page, or the Lustre Operations Manual, for documentation on lmc.
25
26 """
27
28 import sys, os, getopt, string, exceptions, re
29 import xml.dom.minidom
30
31 def printDoc(doc, stream=sys.stdout):
32     try:
33         from xml.dom.ext import PrettyPrint
34         PrettyPrint(doc, stream)
35     except ImportError:
36         stream.write(doc.toxml())
37         stream.write("\n")
38     
39
40 PYMOD_DIR = ["/usr/lib/lustre/python", "/usr/lib64/lustre/python"]
41
42 def development_mode():
43     base = os.path.dirname(sys.argv[0])
44     if os.access(base+"/Makefile.am", os.R_OK):
45         return 1
46     return 0
47
48 if not development_mode():
49     sys.path.extend(PYMOD_DIR)
50
51 import Lustre
52
53 DEFAULT_PORT = 988 
54 DEFAULT_STRIPE_SZ = 1048576
55 DEFAULT_STRIPE_CNT = 1
56 DEFAULT_STRIPE_PATTERN = 0
57 UUID_MAX_LENGTH = 31
58
59 def reference():
60     print """usage: lmc --add object [object parameters]
61
62 Object creation command summary:
63
64 --add node
65   --node node_name
66   --timeout num
67   --upcall path
68   --lustre_upcall path
69   --group_upcall path
70   --portals_upcall path
71   --ptldebug debug_level
72   --subsystem subsystem_name
73
74 --add net
75   --node node_name
76   --nid nid
77   --cluster_id 
78   --nettype tcp|elan|gm|openib|iib|vib|ra|ptl|lnet
79   --hostaddr ip[/netmask]
80   --port port
81   --tcpbuf size
82   --irq_affinity 0|1
83   --router
84
85 --add mds
86   --node node_name
87   --mds mds_name
88   --dev path
89   --fstype ldiskfs|ext3
90   --size size
91   --nspath
92   --group_upcall upcall
93   --journal_size size
94   --inode_size size
95   --mdsuuid uuid
96   --mkfsoptions options
97   --mountfsoptions options
98   --quota quotaon=u|g|ug,iunit=,bunit=,itune=,btune= 
99
100 --add lov
101   --lov lov_name
102   --mds mds_name
103   --stripe_sz num
104   --stripe_cnt num
105   --stripe_pattern num
106
107 --add ost
108   --node node_name
109   --ost ost_name 
110   --failout
111   --failover
112   --lov lov_name 
113   --dev path
114   --size size
115   --fstype ldiskfs|ext3
116   --journal_size size
117   --inode_size size
118   --osdtype obdecho|obdfilter
119   --ostuuid uuid
120   --mkfsoptions options
121   --mountfsoptions options
122   --quota quotaon=u|g|ug,iunit=,bunit=,itune=,btune=
123  
124 --add mtpt  - Mountpoint
125   --node node_name
126   --path /mnt/point
127   --mds mds_name
128   --ost ost_name OR --lov lov_name
129   --clientoptions options
130
131 --add route
132   --node nodename
133   --router
134   --gw nid
135   --gateway_cluster_id nid
136   --target_cluster_id nid
137   --lo nid
138   --hi nid
139
140 --add echo_client
141   --node nodename
142 """
143
144 PARAM = Lustre.Options.PARAM
145 PARAMLIST = Lustre.Options.PARAMLIST
146 lmc_options = [
147     # lmc input/output options
148     ('reference', "Print short reference for commands."), 
149     ('verbose,v', "Print system commands as they are run."),
150     ('merge,m', "Append to the specified config file.", PARAM),
151     ('output,o', "Write XML configuration into given output file. Overwrite existing content.", PARAM),
152     ('input,i', "", PARAM),
153     ('batch', "Used to execute lmc commands in batch mode.", PARAM),
154
155     # commands
156     ('add', "", PARAM),
157     
158     # node options
159     ('node', "Add a new node in the cluster configuration.", PARAM),
160     ('timeout', "Set timeout to initiate recovery.", PARAM),
161     ('upcall', "Set both lustre and portals upcall scripts.", PARAM),
162     ('lustre_upcall', "Set location of lustre upcall script.", PARAM),
163     ('group_upcall', "Set location of extended group upcall script.", PARAM),
164     ('portals_upcall', "Set location of portals upcall script.", PARAM),
165     ('ptldebug', "Set the portals debug level",  PARAM),
166     ('subsystem', "Specify which Lustre subsystems have debug output recorded in the log",  PARAM),
167
168     # network 
169     ('nettype', "Specify the network type. This can be tcp/elan/gm/openib/iib/vib/ra/ptl/lnet.", PARAM),
170     ('nid', "Give the network ID, e.g ElanID/IP Address as used by portals.", PARAM),
171     ('port', "Optional argument to specify the TCP port number.", PARAM, DEFAULT_PORT),
172     ('hostaddr', "Optional argument to specify the host address.", PARAMLIST),
173     ('cluster_id', "Specify the cluster ID", PARAM, "0"),
174     ('nonet', "Skip the remote host networking check"),
175
176     # routes
177     ('route', "Add a new route for the cluster.", PARAM),
178     ('router', "Optional flag to mark a node as router."),
179     ('gw', "Specify the nid of the gateway for a route.", PARAM),
180     ('gateway_cluster_id', "", PARAM, "0"),
181     ('target_cluster_id', "", PARAM, "0"),
182     ('lo', "For a range route, this is the low value nid.", PARAM),
183     ('hi', "For a range route, this is a hi value nid.", PARAM,""),
184
185     # servers: mds and ost
186     ('mds', "Specify MDS name.", PARAM),
187     ('ost', "Specify the OST name.", PARAM,""),
188     ('osdtype', "This could obdfilter or obdecho.", PARAM, "obdfilter"),
189     ('failout', "Disable failover support on OST"),
190     ('failover', "Enable failover support on OST"),
191     ('group', "", PARAM),
192     ('dev', "Path of the device on local system.", PARAM,""),
193     ('size', "Specify the size of the device if needed.", PARAM,"0"),
194     ('group_upcall', "Set location of supplementary group upcall.", PARAM,""),
195     ('journal_size', "Specify new journal size for underlying ext3 file system.", PARAM,"0"),
196     ('inode_size', "Specify new inode size for underlying ext3 file system.", PARAM,"0"),
197     ('fstype', "Optional argument to specify the filesystem type.", PARAM, "ext3"),
198     ('mkfsoptions', "Optional argument to mkfs.", PARAM, ""),
199     ('mountfsoptions', "Optional argument to mount fs.", PARAM, ""),
200     ('ostuuid', "Optional argument to specify OST UUID", PARAM,""),
201     ('mdsuuid', "Optional argument to specify MDS UUID", PARAM,""),
202     ('nspath', "Local mount point of server namespace.", PARAM,""),
203     ('format', ""),
204     ('quota', "quotaon:enable quota, only u|g|ug is supported now. \
205                iunit: the unit for slave to acquire/release inode quota from/to masteri.\
206                       Int type (>0), default value in Lustre is 5000 inodes.\
207                bunit: the unit for slave to acquire/release block quota from/to master.\
208                       Mbytes (>0), default value in Lustre is 100(Mbytes).\
209                itune: used to tune the threthold. When inode quota usage reach the threthold,\
210                       slave should acquire/release inode quota from/to master.\
211                       Int type (100 > btune > 0), default value in Lustre is 50 (percentge).\
212                       inode threthold = iunit * itune / 100.\
213                btune: used to tune the threthold. When block quota usage reach the threthold,\
214                       slave should acquire/release block quota from/to master.\
215                       Int type (100 > btune > 0), default value in Lustre is 50 (percentage).\
216                       block threthold = bunit * btune / 100.", PARAM,""),
217     # clients: mountpoint and echo
218     ('echo_client', "", PARAM),
219     ('path', "Specify the mountpoint for Lustre.", PARAM),
220     ('filesystem', "Lustre filesystem name", PARAM,""),
221     ('clientoptions', "Specify the options for Lustre, such as async.", PARAM, ""),
222
223     # lov
224     ('lov', "Specify LOV name.", PARAM,""),
225     ('stripe_sz', "Specify the stripe size in bytes.", PARAM, DEFAULT_STRIPE_SZ),
226     ('stripe_cnt', "Specify the number of OSTs each file should be striped on.", PARAM, DEFAULT_STRIPE_CNT),
227     ('stripe_pattern', "Specify the stripe pattern. RAID 0 is the only one currently supported.", PARAM, 0),
228
229     # cobd
230     ('real_obd', "Specify the real device for the cache obd system.", PARAM),
231     ('cache_obd', "Specify the cache device for the cache obd system.", PARAM),
232     ]
233
234 def error(*args):
235     msg = string.join(map(str,args))
236     raise OptionError("Error: " +  msg)
237
238 def panic(cmd, msg):
239     print "! " + cmd
240     print msg
241     sys.exit(1)
242
243 def warning(*args):
244     msg = string.join(map(str,args))
245     sys.stderr.write("WARNING: %s\n" % (msg))
246
247 def info(*args):
248     msg = string.join(map(str,args))
249     sys.stderr.write("INFO: %s\n" % (msg))
250     
251 #
252 # manage names and uuids
253 # need to initialize this by walking tree to ensure
254 # no duplicate names or uuids are created.
255 # this are just place holders for now.
256 # consider changing this to be like OBD-dev-host
257 def new_name(base):
258     ctr = 2
259     ret = base
260     while names.has_key(ret):
261         ret = "%s_%d" % (base, ctr)
262         ctr = 1 + ctr
263     names[ret] = 1
264     return ret
265
266 def new_uuid(name):
267     ctr = 2
268     ret = "%s_UUID" % (name)
269     if len(ret) > UUID_MAX_LENGTH:
270         ret = ret[-UUID_MAX_LENGTH:]
271     while uuids.has_key(ret):
272         ret = "%s_UUID_%d" % (name, ctr)
273         ctr = 1 + ctr
274         if len(ret) > UUID_MAX_LENGTH:
275             ret = ret[-UUID_MAX_LENGTH:]
276     uuids[ret] = 1
277     return ret
278
279
280 ldlm_name = 'ldlm'
281 ldlm_uuid = 'ldlm_UUID'
282
283 def new_lustre(dom):
284     """Create a new empty lustre document"""
285     # adding ldlm here is a bit of a hack, but one is enough.
286     str = """<lustre version="%s">
287     <ldlm name="%s" uuid="%s"/>
288     </lustre>""" % (Lustre.CONFIG_VERSION, ldlm_name, ldlm_uuid)
289     return dom.parseString(str)
290
291
292 names = {}
293 uuids = {}
294
295 def init_names(doc):
296     """initialize auto-name generation tables"""
297     global names, uuids
298     # get all elements that contain a name attribute
299     for n in doc.childNodes:
300         if n.nodeType == n.ELEMENT_NODE:
301             if getName(n):
302                 names[getName(n)] = 1
303                 uuids[getUUID(n)] = 1
304             init_names(n)
305
306 def get_format_flag(options):
307     if options.format:
308         return 'yes'
309     return 'no'
310
311 ############################################################
312 # Build config objects using DOM
313 #
314 class GenConfig:
315     doc = None
316     dom = None
317     def __init__(self, doc):
318         self.doc = doc
319
320     def ref(self, type, uuid):
321         """ generate <[type]_ref uuidref="[uuid]"/> """
322         tag = "%s_ref" % (type)
323         ref = self.doc.createElement(tag)
324         ref.setAttribute("uuidref", uuid)
325         return ref
326     
327     def newService(self, tag, name, uuid):
328         """ create a new  service elmement, which requires name and uuid attributes """
329         new = self.doc.createElement(tag)
330         new.setAttribute("uuid", uuid);
331         new.setAttribute("name", name);
332         return new
333     
334     def addText(self, node, str):
335         txt = self.doc.createTextNode(str)
336         node.appendChild(txt)
337
338     def addElement(self, node, tag, str=None):
339         """ create a new element and add it as a child to node. If str is passed,
340             a text node is created for the new element"""
341         new = self.doc.createElement(tag)
342         if str:
343             self.addText(new, str)
344         node.appendChild(new)
345         return new
346
347     def recordtime(self, timestr):
348         lustre = self.doc.getElementsByTagName("lustre")
349         lustre[0].setAttribute("mtime", timestr)
350
351     def network(self, name, uuid, nid, cluster_id, net, hostaddr="",
352                 port=0):
353         """create <network> node"""
354         network = self.newService("network", name, uuid)
355         network.setAttribute("nettype", net);
356         self.addElement(network, "nid", nid)
357         self.addElement(network, "clusterid", cluster_id)
358         for host in  hostaddr:
359             self.addElement(network, "hostaddr", host)
360         if port:
361             self.addElement(network, "port", "%d" %(port))
362             
363         return network
364
365     def routetbl(self, name, uuid):
366         """create <routetbl> node"""
367         rtbl = self.newService("routetbl", name, uuid)
368         return rtbl
369         
370     def route(self, gw_net_type, gw, gw_cluster_id, tgt_cluster_id, lo, hi):
371         """ create one entry for the route table """
372         ref = self.doc.createElement('route')
373         ref.setAttribute("type", gw_net_type)
374         ref.setAttribute("gw", gw)
375         ref.setAttribute("gwclusterid", gw_cluster_id)
376         ref.setAttribute("tgtclusterid", tgt_cluster_id)
377         ref.setAttribute("lo", lo)
378         if hi:
379             ref.setAttribute("hi", hi)
380         return ref
381     
382     def profile(self, name, uuid):
383         """ create a host """
384         profile = self.newService("profile", name, uuid)
385         return profile
386
387     def node(self, name, uuid, prof_uuid):
388         """ create a host """
389         node = self.newService("node", name, uuid)
390         node.appendChild(self.ref("profile", prof_uuid))
391         return node
392
393     def ldlm(self, name, uuid):
394         """ create a ldlm """
395         ldlm = self.newService("ldlm", name, uuid)
396         return ldlm
397
398     def osd(self, name, uuid, fstype, osdtype, devname, format, ost_uuid,
399             node_uuid, dev_size=0, journal_size=0, inode_size=0, nspath="", 
400             mkfsoptions="", mountfsoptions="", quota=""):
401         osd = self.newService("osd", name, uuid)
402         osd.setAttribute('osdtype', osdtype)
403         osd.appendChild(self.ref("target", ost_uuid))
404         osd.appendChild(self.ref("node", node_uuid))
405         if fstype:
406             self.addElement(osd, "fstype", fstype)
407         if devname:
408             dev = self.addElement(osd, "devpath", devname)
409             self.addElement(osd, "autoformat", format)
410             if dev_size:
411                 self.addElement(osd, "devsize", "%s" % (dev_size))
412             if journal_size:
413                 self.addElement(osd, "journalsize", "%s" % (journal_size))
414             if inode_size:
415                 self.addElement(osd, "inodesize", "%s" % (inode_size))
416             if mkfsoptions:
417                 self.addElement(osd, "mkfsoptions", mkfsoptions)
418             if mountfsoptions:
419                 self.addElement(osd, "mountfsoptions", mountfsoptions)
420             if quota:
421                 self.addElement(osd, "quota", quota)
422         if nspath:
423             self.addElement(osd, "nspath", nspath)
424         return osd
425
426     def cobd(self, name, uuid, real_uuid, cache_uuid):
427         cobd = self.newService("cobd", name, uuid)
428         cobd.appendChild(self.ref("realobd",real_uuid))
429         cobd.appendChild(self.ref("cacheobd",cache_uuid))
430         return cobd
431
432     def ost(self, name, uuid, osd_uuid, group=""):
433         ost = self.newService("ost", name, uuid)
434         ost.appendChild(self.ref("active", osd_uuid))
435         if group:
436             self.addElement(ost, "group", group)
437         return ost
438
439     def oss(self, name, uuid):
440         oss = self.newService("oss", name, uuid)
441         return oss
442
443     def lov(self, name, uuid, mds_uuid, stripe_sz, stripe_cnt, pattern):
444         lov = self.newService("lov", name, uuid)
445         lov.appendChild(self.ref("mds", mds_uuid))
446         lov.setAttribute("stripesize", str(stripe_sz))
447         lov.setAttribute("stripecount", str(stripe_cnt))
448         lov.setAttribute("stripepattern", str(pattern))
449         return lov
450
451     def lovconfig(self, name, uuid, lov_uuid):
452         lovconfig = self.newService("lovconfig", name, uuid)
453         lovconfig.appendChild(self.ref("lov", lov_uuid))
454         return lovconfig
455
456     def mds(self, name, uuid, mdd_uuid, group=""):
457         mds = self.newService("mds", name, uuid)
458         mds.appendChild(self.ref("active",mdd_uuid))
459         if group:
460             self.addElement(mds, "group", group)
461         return mds
462
463     def mdsdev(self, name, uuid, fstype, devname, format, node_uuid,
464                mds_uuid, dev_size=0, journal_size=0, inode_size=256,
465                nspath="", mkfsoptions="", mountfsoptions="", quota="", group_upcall=""):
466         mdd = self.newService("mdsdev", name, uuid)
467         self.addElement(mdd, "fstype", fstype)
468         dev = self.addElement(mdd, "devpath", devname)
469         self.addElement(mdd, "autoformat", format)
470         if dev_size:
471                 self.addElement(mdd, "devsize", "%s" % (dev_size))
472         if journal_size:
473             self.addElement(mdd, "journalsize", "%s" % (journal_size))
474         if inode_size:
475             self.addElement(mdd, "inodesize", "%s" % (inode_size))
476         if nspath:
477             self.addElement(mdd, "nspath", nspath)
478         if mkfsoptions:
479             self.addElement(mdd, "mkfsoptions", mkfsoptions)
480         if mountfsoptions:
481             self.addElement(mdd, "mountfsoptions", mountfsoptions)
482         if quota:
483             self.addElement(mdd, "quota", quota)
484         if group_upcall:
485             self.addElement(mdd, "group_upcall", group_upcall)
486
487         mdd.appendChild(self.ref("node", node_uuid))
488         mdd.appendChild(self.ref("target", mds_uuid))
489         return mdd
490
491     def mountpoint(self, name, uuid, fs_uuid, path, clientoptions):
492         mtpt = self.newService("mountpoint", name, uuid)
493         mtpt.appendChild(self.ref("filesystem", fs_uuid))
494         self.addElement(mtpt, "path", path)
495         if clientoptions:
496             self.addElement(mtpt, "clientoptions", clientoptions)
497         return mtpt
498
499     def filesystem(self, name, uuid, mds_uuid, obd_uuid):
500         fs = self.newService("filesystem", name, uuid)
501         fs.appendChild(self.ref("mds", mds_uuid))
502         fs.appendChild(self.ref("obd", obd_uuid))
503         return fs
504         
505     def echo_client(self, name, uuid, osc_uuid):
506         ec = self.newService("echoclient", name, uuid)
507         ec.appendChild(self.ref("obd", osc_uuid))
508         return ec
509
510 ############################################################
511 # Utilities to query a DOM tree
512 # Using this functions we can treat use config information
513 # directly as a database.
514 def getName(n):
515     return n.getAttribute('name')
516
517 def getUUID(node):
518     return node.getAttribute('uuid')
519
520
521 def findByName(lustre, name, tag = ""):
522     for n in lustre.childNodes:
523         if n.nodeType == n.ELEMENT_NODE:
524             if tag and n.nodeName != tag:
525                 continue
526             if getName(n) == name:
527                 return n
528             else:
529                 n = findByName(n, name)
530                 if n: return n
531     return None
532
533
534 def lookup(node, uuid):
535     for n in node.childNodes:
536         if n.nodeType == n.ELEMENT_NODE:
537             if getUUID(n) == uuid:
538                 return n
539             else:
540                 n = lookup(n, uuid)
541                 if n: return n
542     return None
543
544
545 def name2uuid(lustre, name, tag="",  fatal=1):
546     ret = findByName(lustre, name, tag)
547     if not ret:
548         if fatal:
549             error('name2uuid:', '"'+name+'"', tag, 'element not found.')
550         else:
551             return ""
552     return getUUID(ret)
553     
554 def lookup_filesystem(lustre, mds_uuid, ost_uuid):
555     for n in lustre.childNodes:
556         if n.nodeType == n.ELEMENT_NODE and n.nodeName == 'filesystem':
557             if ref_exists(n, mds_uuid) and ref_exists(n, ost_uuid):
558                 return getUUID(n)
559     return None
560
561 # XXX: assumes only one network element per node. will fix this
562 # as soon as support for routers is added
563 def get_net_uuid(lustre, node_name):
564     """ get a network uuid for a node_name """
565     node = findByName(lustre, node_name, "node")
566     if not node:
567         error ('get_net_uuid:', '"'+node_name+'"', "node element not found.")
568     net = node.getElementsByTagName('network')
569     if net:
570         return getUUID(net[0])
571     return None
572
573
574 def lov_add_obd(gen, lov, osc_uuid):
575     lov.appendChild(gen.ref("obd", osc_uuid))
576                             
577 def ref_exists(profile, uuid):
578     elist = profile.childNodes
579     for e in elist:
580         if e.nodeType == e.ELEMENT_NODE:
581             ref = e.getAttribute('uuidref')
582             if ref == uuid:
583                 return 1
584     return 0
585         
586 # ensure that uuid is not already in the profile
587 # return true if uuid is added
588 def node_add_profile(gen, node, ref, uuid):
589     refname = "%s_ref" % "profile"
590     ret = node.getElementsByTagName(refname)
591     if not ret:
592         error('node has no profile ref:', node)
593     prof_uuid = ret[0].getAttribute('uuidref')
594     profile = lookup(node.parentNode, prof_uuid)
595     if not profile:
596         error("no profile found:", prof_uuid)
597     if ref_exists(profile, uuid):
598         return 0
599     profile.appendChild(gen.ref(ref, uuid))
600     return 1
601     
602 def get_attr(dom_node, attr, default=""):
603     v = dom_node.getAttribute(attr)
604     if v:
605         return v
606     return default
607
608 ############################################################
609 # Top level commands
610 #
611 def runcmd(cmd):
612     f = os.popen(cmd)
613     ret = f.close()
614     if ret:
615         ret = ret >> 8
616     else:
617         ret = 0
618     return ret
619
620 def set_node_options(gen, node, options):
621     if options.router:
622         node.setAttribute('router', '1')
623     if options.timeout:
624         gen.addElement(node, "timeout", get_option(options, 'timeout'))
625     if options.upcall:
626         default_upcall =  get_option(options, 'upcall')
627     else:
628         default_upcall = ''
629     if default_upcall or options.lustre_upcall:
630         if options.lustre_upcall:
631             gen.addElement(node, 'lustreUpcall', options.lustre_upcall)
632         else: 
633             gen.addElement(node, 'lustreUpcall', default_upcall)
634     if options.group_upcall:
635         gen.addElement(node, 'groupUpcall', options.group_upcall)
636     if default_upcall or options.portals_upcall:
637         if options.portals_upcall:
638             gen.addElement(node, 'portalsUpcall', options.portals_upcall)
639         else:
640             gen.addElement(node, 'portalsUpcall', default_upcall)
641     if options.ptldebug:
642         gen.addElement(node, "ptldebug", get_option(options, 'ptldebug'))
643     if options.subsystem:
644         gen.addElement(node, "subsystem", get_option(options, 'subsystem'))
645     return node
646
647 def do_add_node(gen, lustre,  options, node_name):
648     uuid = new_uuid(node_name)
649     prof_name = new_name("PROFILE_" + node_name)
650     prof_uuid = new_uuid(prof_name)
651     profile = gen.profile(prof_name, prof_uuid)
652     node = gen.node(node_name, uuid, prof_uuid)
653     lustre.appendChild(node)
654     lustre.appendChild(profile)
655
656     node_add_profile(gen, node, 'ldlm', ldlm_uuid)
657     set_node_options(gen, node, options)
658     return node
659
660     
661 def add_node(gen, lustre, options):
662     """ create a node with a network config """
663
664     node_name = get_option(options, 'node')
665     ret = findByName(lustre, node_name, "node")
666     if ret:
667         print "Node:", node_name, "exists."
668         return
669     do_add_node(gen, lustre, options, node_name)
670
671
672 def add_net(gen, lustre, options):
673     """ create a node with a network config """
674
675     node_name = get_option(options, 'node')
676     nid = get_option(options, 'nid')
677     cluster_id = get_option(options, 'cluster_id')
678     hostaddr = get_option(options, 'hostaddr')
679     net_type = get_option(options, 'nettype')
680
681     if net_type in ('lnet','tcp','openib','ra'):
682         port = get_option_int(options, 'port')
683     elif net_type in ('elan','gm','iib','vib','lo','ptl'):
684         port = 0
685     else:
686         print "Unknown net_type: ", net_type
687         sys.exit(2)
688
689     real_net_type = net_type
690     if net_type == 'lnet' and string.find(nid,'@') > 0:
691         real_net_type = string.split(nid,'@')[1]
692
693     # testing network
694     if options.nonet:
695         if options.verbose:
696             print "Skip the remote host networking test."
697     elif (node_name != 'client') and (real_net_type == 'tcp'):
698         if options.verbose:
699             print "Testing network on", node_name
700         target = string.split(nid,'@')[0]
701         out = runcmd("ping -c 1 -w 10 %s" %target)
702         if out != 0:
703             print "Could not connect to", node_name,", Please check network."
704             
705     ret = findByName(lustre, node_name, "node")
706     if not ret:
707         node = do_add_node(gen, lustre, options, node_name)
708     else:
709         node = ret
710         set_node_options(gen, node, options)
711
712     net_name = new_name('NET_'+ node_name +'_'+ net_type)
713     net_uuid = new_uuid(net_name)
714     node.appendChild(gen.network(net_name, net_uuid, nid, cluster_id, net_type,
715                                  hostaddr, port))
716     node_add_profile(gen, node, "network", net_uuid)
717
718
719 def add_route(gen, lustre, options):
720     """ create a node with a network config """
721
722     node_name = get_option(options, 'node')
723     gw_net_type = get_option(options, 'nettype')
724     gw = get_option(options, 'gw')
725     gw_cluster_id = get_option(options, 'gateway_cluster_id')
726     tgt_cluster_id = get_option(options, 'target_cluster_id')
727     lo = get_option(options, 'lo')
728     hi = get_option(options, 'hi')
729     if not hi:
730         hi = lo
731
732     node = findByName(lustre, node_name, "node")
733     if not node:
734         error (node_name, " not found.")
735     
736     rlist = node.getElementsByTagName('routetbl')
737     if len(rlist) > 0:
738         rtbl = rlist[0]
739     else:
740         rtbl_name = new_name("RTBL_" + node_name)
741         rtbl_uuid = new_uuid(rtbl_name)
742         rtbl = gen.routetbl(rtbl_name, rtbl_uuid)
743         node.appendChild(rtbl)
744         node_add_profile(gen, node, "routetbl", rtbl_uuid)
745     rtbl.appendChild(gen.route(gw_net_type, gw, gw_cluster_id, tgt_cluster_id,
746                                lo, hi))
747
748
749 def add_mds(gen, lustre, options):
750     node_name = get_option(options, 'node')
751     mds_name = get_option(options, 'mds')
752     mdd_name = new_name("MDD_" + mds_name +"_" + node_name)
753     mdd_uuid = new_uuid(mdd_name)
754
755     mds_uuid = name2uuid(lustre, mds_name, 'mds', fatal=0)
756     if not mds_uuid:
757         mds_uuid = get_option(options, 'mdsuuid')
758         if mds_uuid:
759             if lookup(lustre, mds_uuid):
760                 error("Duplicate MDS UUID:", mds_uuid)
761         else:
762             mds_uuid = new_uuid(mds_name)
763
764         mds = gen.mds(mds_name, mds_uuid, mdd_uuid, options.group)
765         lustre.appendChild(mds)
766     else:
767         mds = lookup(lustre, mds_uuid)
768     if options.failover:
769         mds.setAttribute('failover', "1")
770     if options.failout:
771         mds.setAttribute('failover,',"0")
772
773     devname = get_option(options, 'dev')
774     size = get_option(options, 'size')
775     fstype = get_option(options, 'fstype')
776     journal_size = get_option(options, 'journal_size')
777     inode_size = get_option(options, 'inode_size')
778     nspath = get_option(options, 'nspath')
779     mkfsoptions = get_option(options, 'mkfsoptions')
780     mountfsoptions = get_option(options, 'mountfsoptions')
781     quota = get_option(options, 'quota')
782     group_upcall = get_option(options, 'group_upcall')
783
784     node_uuid = name2uuid(lustre, node_name, 'node')
785
786     node = findByName(lustre, node_name, "node")
787     node_add_profile(gen, node, "mdsdev", mdd_uuid)
788     net_uuid = get_net_uuid(lustre, node_name)
789     if not net_uuid:
790         error("NODE: ", node_name, "not found")
791
792     mdd = gen.mdsdev(mdd_name, mdd_uuid, fstype, devname,
793                      get_format_flag(options), node_uuid, mds_uuid,
794                      size, journal_size, inode_size, nspath, mkfsoptions,
795                      mountfsoptions, quota, group_upcall)
796     lustre.appendChild(mdd)
797                    
798
799 def add_ost(gen, lustre, options):
800     node_name = get_option(options, 'node')
801     lovname = get_option(options, 'lov')
802     osdtype = get_option(options, 'osdtype')
803
804     node_uuid = name2uuid(lustre, node_name, 'node')
805
806     if osdtype == 'obdecho':
807         fstype = ''
808         devname = ''
809         size = 0
810         fstype = ''
811         journal_size = ''
812         inode_size = ''
813         mkfsoptions = ''
814         mountfsoptions = ''
815         quota = ''
816     else:
817         devname = get_option(options, 'dev') # can be unset for bluearcs
818         size = get_option(options, 'size')
819         fstype = get_option(options, 'fstype')
820         journal_size = get_option(options, 'journal_size')
821         inode_size = get_option(options, 'inode_size')
822         mkfsoptions = get_option(options, 'mkfsoptions')
823         mountfsoptions = get_option(options, 'mountfsoptions')
824         quota = get_option(options, 'quota')
825         
826     nspath = get_option(options, 'nspath')
827
828     ostname = get_option(options, 'ost')
829     if not ostname:
830         ostname = new_name('OST_'+ node_name)
831
832     osdname = new_name("OSD_" + ostname + "_" + node_name)
833     osd_uuid = new_uuid(osdname)
834
835     ost_uuid = name2uuid(lustre, ostname, 'ost', fatal=0)
836     if not ost_uuid:
837         ost_uuid = get_option(options, 'ostuuid')
838         if ost_uuid:
839             if lookup(lustre, ost_uuid):
840                 error("Duplicate OST UUID:", ost_uuid)
841         else:
842             ost_uuid = new_uuid(ostname)
843
844         ost = gen.ost(ostname, ost_uuid, osd_uuid, options.group)
845         lustre.appendChild(ost)
846         if lovname:
847             lov = findByName(lustre, lovname, "lov")
848             if not lov:
849                 error('add_ost:', '"'+lovname+'"', "lov element not found.")
850             lov_add_obd(gen, lov, ost_uuid)
851     else:
852         ost = lookup(lustre, ost_uuid)
853
854     if options.failover:
855         ost.setAttribute('failover', "1")
856     if options.failout:
857         ost.setAttribute('failover', "0")
858     
859
860     osd = gen.osd(osdname, osd_uuid, fstype, osdtype, devname,
861                   get_format_flag(options), ost_uuid, node_uuid, size,
862                   journal_size, inode_size, nspath, mkfsoptions,
863                   mountfsoptions, quota)
864
865     node = findByName(lustre, node_name, "node")
866
867 ##     if node_add_profile(gen, node, 'oss', oss_uuid):
868 ##         ossname = 'OSS'
869 ##         oss_uuid = new_uuid(ossname)
870 ##         oss = gen.oss(ossname, oss_uuid)
871 ##         lustre.appendChild(oss)
872
873     node_add_profile(gen, node, 'osd', osd_uuid)
874     lustre.appendChild(osd)
875
876                    
877 def add_cobd(gen, lustre, options):
878     node_name = get_option(options, 'node')
879     name = new_name('COBD_' + node_name)
880     uuid = new_uuid(name)
881
882     real_name = get_option(options, 'real_obd')
883     cache_name = get_option(options, 'cache_obd')
884     
885     real_uuid = name2uuid(lustre, real_name, tag='obd')
886     cache_uuid = name2uuid(lustre, cache_name, tag='obd')
887
888     node = findByName(lustre, node_name, "node")
889     node_add_profile(gen, node, "cobd", uuid)
890     cobd = gen.cobd(name, uuid, real_uuid, cache_uuid)
891     lustre.appendChild(cobd)
892
893
894 def add_echo_client(gen, lustre, options):
895     """ add an echo client to the profile for this node. """
896     node_name = get_option(options, 'node')
897     lov_name = get_option(options, 'ost')
898
899     node = findByName(lustre, node_name, 'node')
900
901     echoname = new_name('ECHO_'+ node_name)
902     echo_uuid = new_uuid(echoname)
903     node_add_profile(gen, node, 'echoclient', echo_uuid)
904
905     lov_uuid = name2uuid(lustre, lov_name, tag='lov', fatal=0)
906     if not lov_uuid:
907         lov_uuid = name2uuid(lustre, lov_name, tag='ost', fatal=1)
908
909     echo = gen.echo_client(echoname, echo_uuid, lov_uuid)
910     lustre.appendChild(echo)
911
912
913 def add_lov(gen, lustre, options):
914     """ create a lov """
915
916     lov_orig = get_option(options, 'lov')
917     name = new_name(lov_orig)
918     if name != lov_orig:
919         warning("name:", lov_orig, "already used. using:", name)
920
921     mds_name = get_option(options, 'mds')
922     stripe_sz = get_option_int(options, 'stripe_sz')
923     stripe_cnt = get_option_int(options, 'stripe_cnt')
924     if stripe_cnt == 0:
925         info("default stripe count (0) - will use %d stripe(s) per file" \
926                 % DEFAULT_STRIPE_CNT)
927     pattern = get_option_int(options, 'stripe_pattern')
928     uuid = new_uuid(name)
929
930     ret = findByName(lustre, name, "lov")
931     if ret:
932         error("LOV: ", name, " already exists.")
933
934     mds_uuid = name2uuid(lustre, mds_name, 'mds')
935     lov = gen.lov(name, uuid, mds_uuid, stripe_sz, stripe_cnt, pattern)
936     lustre.appendChild(lov)
937     
938     # add an lovconfig entry to the active mdsdev profile
939     lovconfig_name = new_name('LVCFG_' + name)
940     lovconfig_uuid = new_uuid(lovconfig_name)
941     mds = findByName(lustre, mds_name, "mds")
942     mds.appendChild(gen.ref("lovconfig", lovconfig_uuid))
943     lovconfig = gen.lovconfig(lovconfig_name, lovconfig_uuid, uuid)
944     lustre.appendChild(lovconfig)
945
946 def add_default_lov(gen, lustre, mds_name, lov_name):
947     """ create a default lov """
948                                                                                                                                                
949     stripe_sz = DEFAULT_STRIPE_SZ
950     stripe_cnt = DEFAULT_STRIPE_CNT
951     pattern = DEFAULT_STRIPE_PATTERN
952     uuid = new_uuid(lov_name)
953                                                                                                                                                
954     ret = findByName(lustre, lov_name, "lov")
955     if ret:
956         error("LOV: ", lov_name, " already exists.")
957                                                                                                                                                
958     mds_uuid = name2uuid(lustre, mds_name, 'mds')
959     lov = gen.lov(lov_name, uuid, mds_uuid, stripe_sz, stripe_cnt, pattern)
960     lustre.appendChild(lov)
961                                                                                                                                                
962     # add an lovconfig entry to the active mdsdev profile
963     lovconfig_name = new_name('LVCFG_' + lov_name)
964     lovconfig_uuid = new_uuid(lovconfig_name)
965     mds = findByName(lustre, mds_name)
966     mds.appendChild(gen.ref("lovconfig", lovconfig_uuid))
967     lovconfig = gen.lovconfig(lovconfig_name, lovconfig_uuid, uuid)
968     lustre.appendChild(lovconfig)
969
970 def new_filesystem(gen, lustre, mds_uuid, obd_uuid):
971     fs_name = new_name("FS_fsname")
972     fs_uuid = new_uuid(fs_name)
973     mds = lookup(lustre, mds_uuid)
974     mds.appendChild(gen.ref("filesystem", fs_uuid))
975     fs = gen.filesystem(fs_name, fs_uuid, mds_uuid, obd_uuid)
976     lustre.appendChild(fs)
977     return fs_uuid
978
979 def get_fs_uuid(gen, lustre, mds_name, obd_name):
980     mds_uuid = name2uuid(lustre, mds_name, tag='mds')
981     obd_uuid = name2uuid(lustre, obd_name, tag='lov', fatal=0)
982     fs_uuid = lookup_filesystem(lustre, mds_uuid, obd_uuid)
983     if not fs_uuid:
984         fs_uuid = new_filesystem(gen, lustre, mds_uuid, obd_uuid)
985     return fs_uuid
986     
987 def add_mtpt(gen, lustre, options):
988     """ create mtpt on a node """
989     node_name = get_option(options, 'node')
990
991     path = get_option(options, 'path')
992     clientoptions = get_option(options, "clientoptions")
993     fs_name = get_option(options, 'filesystem')
994
995     lov_name = get_option(options, 'lov')
996     ost_name = get_option(options, 'ost')
997     mds_name = get_option(options, 'mds')
998     if lov_name == '':
999         if ost_name == '':
1000             error("--add mtpt requires --lov lov_name or --ost ost_name")
1001         else:
1002             warning("use default value for lov, due no --lov lov_name provided")
1003             lov_name = new_name("lov_default")
1004             add_default_lov(gen, lustre, mds_name, lov_name)
1005             ost_uuid = name2uuid(lustre, ost_name, 'ost', fatal=0)
1006             if not ost_uuid:
1007                 error('add_mtpt:', '"'+ost_name+'"', "ost element not found.")
1008             lov = findByName(lustre, lov_name, "lov")
1009             lov_add_obd(gen, lov, ost_uuid)
1010
1011     if fs_name == '':
1012         fs_uuid = get_fs_uuid(gen, lustre, mds_name, lov_name)
1013     else:
1014         fs_uuid = name2uuid(lustre, fs_name, tag='filesystem')
1015
1016     name = new_name('MNT_'+ node_name)
1017
1018     ret = findByName(lustre, name, "mountpoint")
1019     if ret:
1020         # this can't happen, because new_name creates unique names
1021         error("MOUNTPOINT: ", name, " already exists.")
1022
1023     uuid = new_uuid(name)
1024     mtpt = gen.mountpoint(name, uuid, fs_uuid, path, clientoptions)
1025     node = findByName(lustre, node_name, "node")
1026     if not node:
1027         error('node:',  node_name, "not found.")
1028     node_add_profile(gen, node, "mountpoint", uuid)
1029     lustre.appendChild(mtpt)
1030
1031 ############################################################
1032 # Command line processing
1033 #
1034 class OptionError (exceptions.Exception):
1035     def __init__(self, args):
1036         self.args = args
1037
1038 def get_option(options, tag):
1039     """Look for tag in options hash and return the value if set. If not
1040     set, then if return default it is set, otherwise exception."""
1041     if options.__getattr__(tag) != None:
1042         return options.__getattr__(tag)
1043     else:
1044         raise OptionError("--add %s requires --%s <value>" % (options.add, tag))
1045
1046 def get_option_int(options, tag):
1047     """Return an integer option.  Raise exception if the value is not an int"""
1048     val = get_option(options, tag)
1049     try:
1050         n = int(val)
1051     except ValueError:
1052         raise OptionError("--%s <num> (value must be integer)" % (tag))        
1053     return n
1054
1055 # simple class for profiling
1056 import time
1057 class chrono:
1058     def __init__(self):
1059         self._start = 0
1060     def start(self):
1061         self._stop = 0
1062         self._start = time.time()
1063     def stop(self, msg=''):
1064         self._stop = time.time()
1065         if msg:
1066             self.display(msg)
1067     def dur(self):
1068         return self._stop - self._start
1069     def display(self, msg):
1070         d = self.dur()
1071         str = '%s: %g secs' % (msg, d)
1072         print str
1073
1074 #################################################################
1075 # function cmdlinesplit used to split cmd line from batch file
1076 #
1077 def cmdlinesplit(cmdline):
1078                                                                                                                                                
1079     double_quote  = re.compile(r'"(([^"\\]|\\.)*)"')
1080     single_quote  = re.compile(r"'(.*?)'")
1081     escaped = re.compile(r'\\(.)')
1082     esc_quote = re.compile(r'\\([\\"])')
1083     outside = re.compile(r"""([^\s\\'"]+)""")
1084                                                                                                                                                
1085     arg_list = []
1086     i = 0; arg = None
1087     while i < len(cmdline):
1088         c = cmdline[i]
1089         if c == '"':
1090             match = double_quote.match(cmdline, i)
1091             if not match:
1092                 print "Unmatched double quote:", cmdline
1093                 sys.exit(1)
1094             i = match.end()
1095             if arg is None: arg = esc_quote.sub(r'\1', match.group(1))
1096             else:           arg = arg + esc_quote.sub(r'\1', match.group(1))
1097                                                                                                                                                
1098         elif c == "'":
1099             match = single_quote.match(cmdline, i)
1100             if not match:
1101                 print "Unmatched single quote:", cmdline
1102                 sys.exit(1)
1103             i = match.end()
1104             if arg is None: arg = match.group(1)
1105             else:           arg = arg + match.group(1)
1106                                                                                                                                                
1107         elif c == "\\":
1108             match = escaped.match(cmdline, i)
1109             if not match:
1110                 print "Unmatched backslash", cmdline
1111                 sys.exit(1)
1112             i = match.end()
1113             if arg is None: arg = match.group(1)
1114             else:           arg = arg + match.group(1)
1115                                                                                                                                                
1116         elif c in string.whitespace:
1117             if arg != None:
1118                 arg_list.append(str(arg))
1119             arg = None
1120             while i < len(cmdline) and cmdline[i] in string.whitespace:
1121                 i = i + 1
1122         else:
1123             match = outside.match(cmdline, i)
1124             assert match
1125             i = match.end()
1126             if arg is None: arg = match.group()
1127             else:           arg = arg + match.group()
1128                                                                                                                                                
1129     if arg != None: arg_list.append(str(arg))
1130                                                                                                                                                
1131     return arg_list
1132
1133 ############################################################
1134 # Main
1135 #
1136
1137 def add(devtype, gen, lustre, options):
1138     if devtype == 'net':
1139         add_net(gen, lustre, options)
1140     elif devtype == 'mtpt':
1141         add_mtpt(gen, lustre, options)
1142     elif devtype == 'mds':
1143         add_mds(gen, lustre, options)
1144     elif devtype == 'ost':
1145         add_ost(gen, lustre, options)
1146     elif devtype == 'lov':
1147         add_lov(gen, lustre, options)
1148     elif devtype == 'route':
1149         add_route(gen, lustre, options)
1150     elif devtype == 'node':
1151         add_node(gen, lustre, options)
1152     elif devtype == 'echo_client':
1153         add_echo_client(gen, lustre, options)
1154     elif devtype == 'cobd':
1155         add_cobd(gen, lustre, options)
1156     else:
1157         error("unknown device type:", devtype)
1158     
1159 def do_command(gen, lustre, options, args):
1160     if options.add:
1161         add(options.add, gen, lustre, options)
1162     else:
1163         error("Missing command")
1164
1165 def main():
1166     cl = Lustre.Options("lmc", "", lmc_options)
1167     try:
1168         options, args = cl.parse(sys.argv[1:])
1169     except Lustre.OptionError, e:
1170         panic("lmc", e)
1171
1172     if len(args) > 0:
1173         panic(string.join(sys.argv), "Unexpected extra arguments on command line: " + string.join(args))
1174
1175     if options.reference:
1176         reference()
1177         sys.exit(0)
1178
1179     outFile = '-'
1180
1181     if options.merge:
1182         outFile = options.merge
1183         if os.access(outFile, os.R_OK):
1184             doc = xml.dom.minidom.parse(outFile)
1185         else:
1186             doc = new_lustre(xml.dom.minidom)
1187     elif options.input:
1188         doc = xml.dom.minidom.parse(options.input)
1189     else:
1190         doc = new_lustre(xml.dom.minidom)
1191
1192     if options.output:
1193         outFile = options.output
1194
1195     lustre = doc.documentElement
1196     init_names(lustre)
1197     if lustre.tagName != "lustre":
1198         print "Existing config not valid."
1199         sys.exit(1)
1200
1201     gen = GenConfig(doc)
1202
1203     if options.batch:
1204         fp = open(options.batch)
1205         batchCommands = fp.readlines()
1206         fp.close()
1207         for cmd in batchCommands:
1208             try:
1209                 options, args = cl.parse(cmdlinesplit(cmd))
1210                 if options.merge or options.input or options.output:
1211                         print "The batchfile should not contain --merge, --input or --output."
1212                         sys.exit(1)
1213                 do_command(gen, lustre, options, args)
1214             except OptionError, e:
1215                 panic(cmd, e)
1216             except Lustre.OptionError, e:
1217                 panic(cmd, e)
1218     else:
1219         try:
1220             do_command(gen, lustre, options, args)
1221         except OptionError, e:
1222             panic(string.join(sys.argv),e)
1223         except Lustre.OptionError, e:
1224             panic("lmc", e)
1225
1226     #record timestamp
1227     timestr = string.split(str(time.time()), '.')
1228     gen.recordtime(timestr[0])
1229
1230     if outFile == '-':
1231         printDoc(doc)
1232     else:
1233         printDoc(doc, open(outFile,"w"))
1234
1235 if __name__ == "__main__":
1236     main()