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