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