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