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