2 # Copyright (C) 2002 Cluster File Systems, Inc.
3 # Author: Robert Read <rread@clusterfs.com>
5 # This file is part of Lustre, http://www.lustre.org.
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.
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.
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.
22 lmc - lustre configurtion data manager
24 Basic plan for lmc usage:
26 ./lmc --output config.xml --node server --net server1 tcp
27 ./lmc --merge config.xml --node client --net client1 tcp
28 ./lmc --merge config.xml --node client --route gw lo [hi]
29 ./lmc --merge config.xml --router --node gw1 --net gw1 tcp
30 ./lmc --merge config.xml --node gw1 --net 1 elan
32 ./lmc --merge config.xml --route elan 1 1 100
33 ./lmc --merge config.xml --route tcp gw1 ba1
38 ./lmc --merge config.xml --node server --mds mds1 /tmp/mds1 50000
41 ./lmc --merge config.xml --lov lov1 mds1 65536 0 0
42 ./lmc --merge config.xml --node server --lov lov1 --ost /tmp/ost1 100000
43 ./lmc --merge config.xml --node server --lov lov1 --ost /tmp/ost2 100000
45 # create client config
46 ./lmc --merge config.xml --node client --mtpt /mnt/lustre mds1 lov1
50 import sys, os, getopt, string
51 import xml.dom.minidom
52 from xml.dom.ext import PrettyPrint
55 DEFAULT_PORT = 988 # XXX What is the right default acceptor port to use?
58 print """usage: lmc [--node --ost | --mtpt | --lov] args
61 Node_name by itself it will create a new node. If the --router
62 option is used when creating a new node, then that node will also
63 be configured as a router. When used with other commands it
64 specifies the node to modify.
66 --net hostname nettype [port, recv_buf, send_buf]
67 Nettype is either tcp, elan, or gm.
70 --route net gw lo [hi]
71 This command is used to create routes. NET is the
72 network type this route will be used on. The GW is an address of
73 one of the local interfaces. LO and HI represent a range of
74 addresses that can be reached through the gateway. If HI is not
75 set, then a route to the specific host in LO is created.
78 Create a MDS using the device
81 --lov lov_name [mds_name stripe_sz sub_stripe_count pattern]
82 Creates a logical volume
83 When used with other commands, it specifics the lov to modify
86 Creates an OBD/OST/OSC configuration triplet for a new device.
87 When used on "host", the device will be initialized and the OST
88 will be enabled. On client nodes, the OSC will be avaiable.
90 Optional --obduuid Specifies the UUID used for the obd.
91 If --lov lov_name is used, this device is added to lov.
93 --mtpt /mnt/point mds_name lov_name|osc_name
94 Creates a client mount point.
98 --merge="xml file" Add the new objects to an existing file
99 --format Format the partitions if unformated
100 NB: The autoformat option has been disabled until a safe
101 method is implemented to determine if a block device has a
103 --reformat Reformat partitions (this should be an lconf arg,
105 --obdtype="obdtype" Specifiy obdtype: valid ones are obdecho and obdfilter.
106 This is only useful for the --ost command.
107 The device parameters are ignored for the obdecho type.
112 msg = string.join(map(str,args))
117 msg = string.join(map(str,args))
118 print "Warning: ", msg
121 # manage names and uuids
122 # need to initialize this by walking tree to ensure
123 # no duplicate names or uuids are created.
124 # this are just place holders for now.
125 # consider changing this to be like OBD-dev-host
129 while names.has_key(ret):
130 ret = "%s_%d" % (base, ctr)
136 return "%s_UUID" % (name)
139 ldlm_uuid = 'ldlm_UUID'
141 """Create a new empty lustre document"""
142 # adding ldlm here is a bit of a hack, but one is enough.
144 <ldlm name="%s" uuid="%s"/>
145 </lustre>""" % (ldlm_name, ldlm_uuid)
146 return dom.parseString(str)
152 """initialize auto-name generation tables"""
154 # get all elements that contain a name attribute
155 for n in doc.childNodes:
156 if n.nodeType == n.ELEMENT_NODE:
158 names[getName(n)] = 1
159 uuids[getUUID(n)] = 1
162 def get_format_flag(options):
163 if options.has_key('format'):
164 if options['format']:
168 ############################################################
169 # Build config objects using DOM
174 def __init__(self, doc):
177 def ref(self, type, uuid):
178 """ generate <[type]_ref uuidref="[uuid]"/> """
179 tag = "%s_ref" % (type)
180 ref = self.doc.createElement(tag)
181 ref.setAttribute("uuidref", uuid)
184 def newService(self, tag, name, uuid):
185 """ create a new service elmement, which requires name and uuid attributes """
186 new = self.doc.createElement(tag)
187 new.setAttribute("name", name);
188 new.setAttribute("uuid", uuid);
191 def addText(self, node, str):
192 txt = self.doc.createTextNode(str)
193 node.appendChild(txt)
195 def addElement(self, node, tag, str=None):
196 """ create a new element and add it as a child to node. If str is passed,
197 a text node is created for the new element"""
198 new = self.doc.createElement(tag)
200 self.addText(new, str)
201 node.appendChild(new)
204 def network(self, name, uuid, hostname, net, port=0, tcpbuf=0):
205 """create <network> node"""
206 network = self.newService("network", name, uuid)
207 network.setAttribute("type", net);
208 self.addElement(network, "server", hostname)
210 self.addElement(network, "port", "%d" %(port))
212 self.addElement(network, "send_mem", "%d" %(tcpbuf))
213 self.addElement(network, "recv_mem", "%d" %(tcpbuf))
217 def route(self, net_type, gw, lo, hi):
218 """ create one entry for the route table """
219 ref = self.doc.createElement('route')
220 ref.setAttribute("type", net_type)
221 ref.setAttribute("gw", gw)
222 ref.setAttribute("lo", lo)
224 ref.setAttribute("hi", hi)
227 def node(self, name, uuid):
228 """ create a host """
229 node = self.newService("node", name, uuid)
230 self.addElement(node, 'profile')
233 def ldlm(self, name, uuid):
234 """ create a ldlm """
235 ldlm = self.newService("ldlm", name, uuid)
238 def obd(self, name, uuid, fs, obdtype, devname, format, dev_size=0):
239 obd = self.newService("obd", name, uuid)
240 obd.setAttribute('type', obdtype)
242 self.addElement(obd, "fstype", fs)
244 dev = self.addElement(obd, "device", devname)
246 dev.setAttribute("size", "%s" % (dev_size))
247 self.addElement(obd, "autoformat", format)
250 def osc(self, name, uuid, obd_uuid, net_uuid):
251 osc = self.newService("osc", name, uuid)
252 osc.appendChild(self.ref("ost", net_uuid))
253 osc.appendChild(self.ref("obd", obd_uuid))
256 def ost(self, name, uuid, obd_uuid, net_uuid):
257 ost = self.newService("ost", name, uuid)
258 ost.appendChild(self.ref("network", net_uuid))
259 ost.appendChild(self.ref("obd", obd_uuid))
262 def lov(self, name, uuid, mds_uuid, stripe_sz, stripe_count, pattern):
263 lov = self.newService("lov", name, uuid)
264 lov.appendChild(self.ref("mds", mds_uuid))
265 devs = self.addElement(lov, "devices" )
266 devs.setAttribute("stripesize", stripe_sz)
267 devs.setAttribute("stripecount", stripe_count)
268 devs.setAttribute("pattern", pattern)
271 def lovconfig(self, name, uuid, lov_uuid):
272 lovconfig = self.newService("lovconfig", name, uuid)
273 lovconfig.appendChild(self.ref("lov", lov_uuid))
276 def mds(self, name, uuid, fs, devname, format, net_uuid, node_uuid,
277 failover_uuid = "", dev_size=0 ):
278 mds = self.newService("mds", name, uuid)
279 self.addElement(mds, "fstype", fs)
280 dev = self.addElement(mds, "device", devname)
282 dev.setAttribute("size", "%s" % (dev_size))
283 self.addElement(mds, "autoformat", format)
284 mds.appendChild(self.ref("network", net_uuid))
285 mds.appendChild(self.ref("node", node_uuid))
287 mds.appendChild(self.ref("failover", failover_uuid))
290 def mountpoint(self, name, uuid, mds_uuid, osc_uuid, path):
291 mtpt = self.newService("mountpoint", name, uuid)
292 mtpt.appendChild(self.ref("mds", mds_uuid))
293 mtpt.appendChild(self.ref("osc", osc_uuid))
294 self.addElement(mtpt, "path", path)
297 ############################################################
298 # Utilities to query a DOM tree
299 # Using this functions we can treat use config information
300 # directly as a database.
302 return n.getAttribute('name')
305 return node.getAttribute('uuid')
308 def findByName(lustre, name, tag = ""):
309 for n in lustre.childNodes:
310 if n.nodeType == n.ELEMENT_NODE:
311 if tag and n.nodeName != tag:
313 if getName(n) == name:
316 n = findByName(n, name)
321 def lookup(node, uuid):
322 for n in node.childNodes:
323 if n.nodeType == n.ELEMENT_NODE:
324 if getUUID(n) == uuid:
332 def mds2node(lustre, mds_name):
333 """ Find the node a MDS is configured on """
334 mds = findByName(lustre, mds_name, 'mds')
335 ref = mds.getElementsByTagName('node_ref')
337 error("mds2node:", "no node_ref found for", '"'+mds_name+'"')
338 node_uuid = ref[0].getAttribute('uuidref')
339 node = lookup(lustre, node_uuid)
341 error('mds2node:', "no node found for :", '"'+mds_name+'"')
345 def name2uuid(lustre, name, tag="", fatal=1):
346 ret = findByName(lustre, name, tag)
349 error('name2uuid:', '"'+name+'"', tag, 'element not found.')
355 # XXX: assumes only one network element per node. will fix this
356 # as soon as support for routers is added
357 def get_net_uuid(lustre, node_name):
358 """ get a network uuid for a node_name """
359 node = findByName(lustre, node_name, "node")
361 error ('get_net_uuid:', '"'+node_name+'"', "node element not found.")
362 net = node.getElementsByTagName('network')
364 return getUUID(net[0])
368 def lov_add_osc(gen, lov, osc_uuid):
369 devs = lov.getElementsByTagName('devices')
371 devs[0].appendChild(gen.ref("osc", osc_uuid))
373 error("No devices element found for LOV:", lov)
376 def node_add_profile(gen, node, ref, uuid):
377 ret = node.getElementsByTagName('profile')
379 error('node has no profile:', node)
380 ret[0].appendChild(gen.ref(ref, uuid))
382 def get_attr(dom_node, attr, default=""):
383 v = dom_node.getAttribute(attr)
388 ############################################################
391 def do_add_node(gen, lustre, options, node_name):
392 uuid = new_uuid(node_name)
393 node = gen.node(node_name, uuid)
394 node_add_profile(gen, node, 'ldlm', ldlm_uuid)
395 if options.has_key('router'):
396 node.setAttribute('router', '1')
397 lustre.appendChild(node)
401 def add_node(gen, lustre, options, args):
402 """ create a node with a network config """
406 node_name = options['node']
408 ret = findByName(lustre, node_name, "node")
410 print "Node:", node_name, "exists."
412 do_add_node(gen, lustre, options, node_name)
415 def add_net(gen, lustre, options, args):
416 """ create a node with a network config """
420 node_name = options['node']
426 if net_type == 'tcp':
431 if options.has_key('tcpbuf'):
432 tcpbuf = int(options['tcpbuf'])
433 elif net_type in ('elan', 'gm'):
436 print "Unknown net_type: ", net_type
439 ret = findByName(lustre, node_name, "node")
441 node = do_add_node(gen, lustre, options, node_name)
444 net_name = new_name('NET_'+ node_name +'_'+ net_type)
445 net_uuid = new_uuid(net_name)
446 node.appendChild(gen.network(net_name, net_uuid, nid, net_type, port, tcpbuf))
447 node_add_profile(gen, node, "network", net_uuid)
450 def add_route(gen, lustre, options, args):
451 """ create a node with a network config """
455 node_name = options['node']
464 node = findByName(lustre, node_name, "node")
466 error (node_name, " not found.")
468 netlist = node.getElementsByTagName('network')
470 rlist = net.getElementsByTagName('route_tbl')
474 rtbl = gen.addElement(net, 'route_tbl')
475 rtbl.appendChild(gen.route(net_type, gw, lo, hi))
478 def add_mds(gen, lustre, options, args):
482 if options.has_key('node'):
483 node_name = options['node']
485 error("--mds requires a --node argument")
487 mds_name = new_name(options['mds'])
488 if mds_name != options['mds']:
489 warning("name:", options['mds'], "already used. using:", mds_name)
496 mds_uuid = new_uuid(mds_name)
498 node_uuid = name2uuid(lustre, node_name, 'node')
500 node = findByName(lustre, node_name, "node")
501 node_add_profile(gen, node, "mds", mds_uuid)
502 net_uuid = get_net_uuid(lustre, node_name)
504 error("NODE: ", node_name, "not found")
507 mds = gen.mds(mds_name, mds_uuid, "extN", devname, get_format_flag(options),
508 net_uuid, node_uuid, dev_size=size)
509 lustre.appendChild(mds)
512 def add_ost(gen, lustre, options, args):
514 obdtype = 'obdfilter'
519 if options.has_key('node'):
520 node_name = options['node']
522 error("--ost requires a --node argument")
524 if options.has_key('lov'):
525 lovname = options['lov']
527 if options.has_key('obdtype'):
528 obdtype = options['obdtype']
529 if obdtype == 'obdecho':
538 obdname = new_name('OBD_'+ node_name)
539 oscname = new_name('OSC_'+ node_name)
540 ostname = new_name('OST_'+ node_name)
541 if options.has_key('obduuid'):
542 obd_uuid = options['obduuid']
543 obd = lookup(lustre, obd_uuid)
545 error("Duplicate OBD UUID:", obd_uuid)
547 obd_uuid = new_uuid(obdname)
548 ost_uuid = new_uuid(ostname)
549 osc_uuid = new_uuid(oscname)
551 net_uuid = get_net_uuid(lustre, node_name)
553 error("NODE: ", node_name, "not found")
555 obd = gen.obd(obdname, obd_uuid, fstype, obdtype, devname, get_format_flag(options), size)
556 ost = gen.ost(ostname, ost_uuid, obd_uuid, net_uuid)
557 osc = gen.osc(oscname, osc_uuid, obd_uuid, ost_uuid)
560 lov = findByName(lustre, lovname, "lov")
562 error('add_ost:', '"'+lovname+'"', "lov element not found.")
563 lov_add_osc(gen, lov, osc_uuid)
565 node = findByName(lustre, node_name, "node")
566 node_add_profile(gen, node, 'obd', obd_uuid)
567 node_add_profile(gen, node, 'ost', ost_uuid)
569 lustre.appendChild(obd)
570 lustre.appendChild(osc)
571 lustre.appendChild(ost)
574 # this is generally only used by llecho.sh
575 def add_osc(gen, lustre, options, args):
576 """ add the osc to the profile for this node. """
580 if options.has_key('node'):
581 node_name = options['node']
583 error("--osc requires a --node argument")
584 osc_uuid = name2uuid(lustre, osc_name) # either 'osc' or 'lov'
585 node = findByName(lustre, node_name, "node")
586 node_add_profile(gen, node, 'osc', osc_uuid)
589 def add_lov(gen, lustre, options, args):
594 name = new_name(options['lov'])
595 if name != options['lov']:
596 warning("name:", options['lov'], "already used. using:", name)
600 stripe_count = args[2]
602 uuid = new_uuid(name)
604 ret = findByName(lustre, name, "lov")
606 error("LOV: ", name, " already exists.")
608 mds_uuid = name2uuid(lustre, mds_name, 'mds')
609 lov = gen.lov(name, uuid, mds_uuid, stripe_sz, stripe_count, pattern)
610 lustre.appendChild(lov)
612 # add an lovconfig entry to the mds profile
613 lovconfig_name = new_name('LVCFG_' + name)
614 lovconfig_uuid = new_uuid(lovconfig_name)
615 node = mds2node(lustre, mds_name)
616 node_add_profile(gen, node, "lovconfig", lovconfig_uuid)
617 lovconfig = gen.lovconfig(lovconfig_name, lovconfig_uuid, uuid)
618 lustre.appendChild(lovconfig)
622 def add_mtpt(gen, lustre, options, args):
623 """ create mtpt on a node """
627 if options.has_key('node'):
628 node_name = options['node']
630 error("--mtpt requires a --node argument")
636 name = new_name('MNT_'+ node_name)
638 ret = findByName(lustre, name, "mountpoint")
640 error("MOUNTPOINT: ", name, " already exists.")
642 mds_uuid = name2uuid(lustre, mds_name, tag='mds')
643 lov_uuid = name2uuid(lustre, lov_name, tag='lov', fatal=0)
645 lov_uuid = name2uuid(lustre, lov_name, tag='osc', fatal=1)
647 uuid = new_uuid(name)
648 mtpt = gen.mountpoint(name, uuid, mds_uuid, lov_uuid, path)
649 node = findByName(lustre, node_name, "node")
651 error('node:', node_name, "not found.")
652 node_add_profile(gen, node, "mountpoint", uuid)
653 lustre.appendChild(mtpt)
656 ############################################################
657 # Command line processing
659 def parse_cmdline(argv):
660 short_opts = "ho:i:m:"
661 long_opts = ["ost", "osc", "mtpt", "lov=", "node=", "mds=", "net", "tcpbuf=",
662 "route", "router", "merge=", "format", "reformat", "output=",
663 "obdtype=", "obduuid=", "in=", "help", "batch="]
668 opts, args = getopt.getopt(argv, short_opts, long_opts)
674 # Commands to create new devices
690 options['router'] = 1
694 # Options for commands
696 options['obdtype'] = a
698 options['obduuid'] = a
700 options['tcpbuf'] = a
703 if o in ("-h", "--help"):
705 if o in ("-o", "--output"):
706 options['output'] = a
707 if o in ("-m", "--merge"):
710 options['format'] = 1
711 if o == "--reformat":
712 options['reformat'] = 1
715 if o in ("--in" , "-i"):
721 # simple class for profiling
728 self._start = time.time()
729 def stop(self, msg=''):
730 self._stop = time.time()
734 return self._stop - self._start
735 def display(self, msg):
737 str = '%s: %g secs' % (msg, d)
740 ############################################################
743 def do_command(gen, lustre, options, args):
744 if options.has_key('ost'):
745 add_ost(gen, lustre, options, args)
746 elif options.has_key('osc'):
747 add_osc(gen, lustre, options, args)
748 elif options.has_key('mtpt'):
749 add_mtpt(gen, lustre, options, args)
750 elif options.has_key('mds'):
751 add_mds(gen, lustre, options, args)
752 elif options.has_key('net'):
753 add_net(gen, lustre, options, args)
754 elif options.has_key('lov'):
755 add_lov(gen, lustre, options, args)
756 elif options.has_key('route'):
757 add_route(gen, lustre, options, args)
758 elif options.has_key('node'):
759 add_node(gen, lustre, options, args)
761 print "Missing command"
765 options, args = parse_cmdline(sys.argv[1:])
768 if options.has_key('merge'):
769 outFile = options['merge']
770 if os.access(outFile, os.R_OK):
771 doc = xml.dom.minidom.parse(outFile)
773 doc = new_lustre(xml.dom.minidom)
774 elif options.has_key('in'):
775 doc = xml.dom.minidom.parse(options['in'])
777 doc = new_lustre(xml.dom.minidom)
779 if options.has_key('output'):
780 outFile = options['output']
782 lustre = doc.documentElement
784 if lustre.tagName != "lustre":
785 print "Existing config not valid."
790 if options.has_key('batch'):
791 fp = open(options['batch'])
792 batchCommands = fp.readlines()
794 for cmd in batchCommands:
795 options, args = parse_cmdline(string.split(cmd))
796 do_command(gen, lustre, options, args)
798 do_command(gen, lustre, options, args)
803 PrettyPrint(doc, open(outFile,"w"))
805 if __name__ == "__main__":