#!/usr/bin/env python # Copyright (C) 2002 Cluster File Systems, Inc. # Author: Robert Read # This file is part of Lustre, http://www.lustre.org. # # Lustre is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # Lustre is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lustre; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # """ lmc - lustre configurtion data manager Basic plan for lmc usage: # create nodes ./lmc --output config.xml --node server --net server1 tcp ./lmc --merge config.xml --node client --net client1 tcp ./lmc --merge config.xml --node client --route gw lo [hi] ./lmc --merge config.xml --router --node gw1 --net gw1 tcp ./lmc --merge config.xml --node gw1 --net 1 elan ./lmc --merge config.xml --route elan 1 1 100 ./lmc --merge config.xml --route tcp gw1 ba1 # configure server ./lmc --merge config.xml --node server --mds mds1 /tmp/mds1 50000 # create lov ./lmc --merge config.xml --lov lov1 mds1 65536 0 0 ./lmc --merge config.xml --node server --lov lov1 --ost /tmp/ost1 100000 ./lmc --merge config.xml --node server --lov lov1 --ost /tmp/ost2 100000 # create client config ./lmc --merge config.xml --node client --mtpt /mnt/lustre mds1 lov1 """ import sys, os, getopt, string import xml.dom.minidom from xml.dom.ext import PrettyPrint DEFAULT_PORT = 988 # XXX What is the right default acceptor port to use? def usage(): print """usage: lmc [--node --ost | --mtpt | --lov] args Commands: --node node_name Node_name by itself it will create a new node. If the --router option is used when creating a new node, then that node will also be configured as a router. When used with other commands it specifies the node to modify. --net hostname nettype [port, recv_buf, send_buf] Nettype is either tcp, elan, or gm. Requires --node --route net gw lo [hi] This command is used to create routes. NET is the network type this route will be used on. The GW is an address of one of the local interfaces. LO and HI represent a range of addresses that can be reached through the gateway. If HI is not set, then a route to the specific host in LO is created. --mds device [size] Create a MDS using the device Requires --node --lov lov_name [mdc_name stripe_sz stripe_off pattern] Creates a logical volume When used with other commands, it specifics the lov to modify --mdc mdc_name Configures a MDC for a node. Requires --node --ost device [size] Creates an OBD/OST/OSC configuration triplet for a new device. When used on "host", the device will be initialized and the OST will be enabled. On client nodes, the OSC will be avaiable. Requires --node If --lov lov_name is used, this device is added to lov. --mtpt /mnt/point mds_name lov_name|osc_name Creates a client mount point. Requires --node Options: --merge="xml file" Add the new objects to an existing file --format Format the partitions if unformated --reformat Reformat partitions (this should be an lconf arg, I think) --obdtype="obdtype" Specifiy obdtype: valid ones are obdecho and obdfilter. This is only useful for the --ost command. The device parameters are ignored for the obdecho type. """ sys.exit(1) def error(*args): msg = string.join(map(str,args)) print msg sys.exit(1) # # manage names and uuids # need to initialize this by walking tree to ensure # no duplicate names or uuids are created. # this are just place holders for now. # consider changing this to be like OBD-dev-host def new_name(base): ctr = 2 ret = base while names.has_key(ret): ret = "%s_%d" % (base, ctr) ctr = 1 + ctr names[ret] = 1 return ret def new_uuid(name): return "%s_UUID" % (name) ldlm_name = 'ldlm' ldlm_uuid = 'ldlm_UUID' def new_lustre(dom): """Create a new empty lustre document""" # adding ldlm here is a bit of a hack, but one is enough. str = """ """ % (ldlm_name, ldlm_uuid) return dom.parseString(str) names = {} uuids = {} def init_names(doc): """initialize auto-name generation tables""" global names, uuids # get all elements that contain a name attribute for n in doc.childNodes: if n.nodeType == n.ELEMENT_NODE: if getName(n): names[getName(n)] = 1 uuids[getUUID(n)] = 1 init_names(n) def get_format_flag(options): if options.has_key('format'): if options['format']: return 'yes' return 'no' ############################################################ # Build config objects using DOM # class GenConfig: doc = None dom = None def __init__(self, doc): self.doc = doc def ref(self, type, uuid): """ generate <[type]_ref uuidref="[uuid]"/> """ tag = "%s_ref" % (type) ref = self.doc.createElement(tag) ref.setAttribute("uuidref", uuid) return ref def newService(self, tag, name, uuid): """ create a new service elmement, which requires name and uuid attributes """ new = self.doc.createElement(tag) new.setAttribute("name", name); new.setAttribute("uuid", uuid); return new def addText(self, node, str): txt = self.doc.createTextNode(str) node.appendChild(txt) def addElement(self, node, tag, str=None): """ create a new element and add it as a child to node. If str is passed, a text node is created for the new element""" new = self.doc.createElement(tag) if str: self.addText(new, str) node.appendChild(new) return new def network(self, name, uuid, hostname, net, port=0, tcpbuf=0): """create node""" network = self.newService("network", name, uuid) network.setAttribute("type", net); self.addElement(network, "server", hostname) if port: self.addElement(network, "port", "%d" %(port)) if tcpbuf: self.addElement(network, "send_mem", "%d" %(tcpbuf)) self.addElement(network, "recv_mem", "%d" %(tcpbuf)) return network def route(self, net_type, gw, lo, hi): """ create one entry for the route table """ ref = self.doc.createElement('route') ref.setAttribute("type", net_type) ref.setAttribute("gw", gw) ref.setAttribute("lo", lo) if hi: ref.setAttribute("hi", hi) return ref def node(self, name, uuid): """ create a host """ node = self.newService("node", name, uuid) self.addElement(node, 'profile') return node def ldlm(self, name, uuid): """ create a ldlm """ ldlm = self.newService("ldlm", name, uuid) return ldlm def obd(self, name, uuid, fs, obdtype, devname, format, dev_size=0): obd = self.newService("obd", name, uuid) obd.setAttribute('type', obdtype) if fs: self.addElement(obd, "fstype", fs) if devname: dev = self.addElement(obd, "device", devname) if (dev_size): dev.setAttribute("size", "%s" % (dev_size)) self.addElement(obd, "autoformat", format) return obd def osc(self, name, uuid, obd_uuid, net_uuid): osc = self.newService("osc", name, uuid) osc.appendChild(self.ref("ost", net_uuid)) osc.appendChild(self.ref("obd", obd_uuid)) return osc def ost(self, name, uuid, obd_uuid, net_uuid): ost = self.newService("ost", name, uuid) ost.appendChild(self.ref("network", net_uuid)) ost.appendChild(self.ref("obd", obd_uuid)) return ost def lov(self, name, uuid, mds_uuid, stripe_sz, stripe_off, pattern): lov = self.newService("lov", name, uuid) lov.appendChild(self.ref("mds", mds_uuid)) devs = self.addElement(lov, "devices" ) devs.setAttribute("stripesize", stripe_sz) devs.setAttribute("stripeoffset", stripe_off) devs.setAttribute("pattern", pattern) return lov def mds(self, name, uuid, fs, devname, format, net_uuid, node_uuid, failover_uuid = "", dev_size=0 ): mds = self.newService("mds", name, uuid) self.addElement(mds, "fstype", fs) dev = self.addElement(mds, "device", devname) if dev_size: dev.setAttribute("size", "%s" % (dev_size)) self.addElement(mds, "autoformat", format) mds.appendChild(self.ref("network", net_uuid)) mds.appendChild(self.ref("node", node_uuid)) if failover_uuid: mds.appendChild(self.ref("failover", failover_uuid)) return mds def mdc(self, name, uuid, mds_uuid): mdc = self.newService("mdc", name, uuid) mdc.appendChild(self.ref("mds", mds_uuid)) return mdc def mountpoint(self, name, uuid, mdc_uuid, osc_uuid, path): mtpt = self.newService("mountpoint", name, uuid) mtpt.appendChild(self.ref("mdc", mdc_uuid)) mtpt.appendChild(self.ref("osc", osc_uuid)) self.addElement(mtpt, "path", path) return mtpt ############################################################ # Utilities to query a DOM tree # Using this functions we can treat use config information # directly as a database. def getName(n): return n.getAttribute('name') def getUUID(node): return node.getAttribute('uuid') def findByName(lustre, name, tag = ""): for n in lustre.childNodes: if n.nodeType == n.ELEMENT_NODE: if tag and n.nodeName != tag: continue if getName(n) == name: return n else: n = findByName(n, name) if n: return n return None def lookup(node, uuid): for n in node.childNodes: if n.nodeType == n.ELEMENT_NODE: if getUUID(n) == uuid: return n else: n = lookup(n, uuid) if n: return n return None def mds2node(lustre, mds_name): """ Find the node a MDS is configured on """ mds = findByName(lustre, mds_name, 'mds') ref = mds.getElementsByTagName('node_ref') if not ref: error("no node found for:", mds_name) node_uuid = ref[0].getAttribute('uuidref') node = lookup(lustre, node_uuid) if not node: error("no node found for :", mds_name) return node def name2uuid(lustre, name, tag="", fatal=1): ret = findByName(lustre, name, tag) if not ret: if fatal: error('name2uuid:', name, "not found.") else: return "" return getUUID(ret) # XXX: assumes only one network element per node. will fix this # as soon as support for routers is added def get_net_uuid(lustre, node_name): """ get a network uuid for a node_name """ node = findByName(lustre, node_name, "node") if not node: error ("node not found:", node_name) net = node.getElementsByTagName('network') if net: return getUUID(net[0]) return None def lov_add_osc(gen, lov, osc_uuid): devs = lov.getElementsByTagName('devices') if len(devs) == 1: devs[0].appendChild(gen.ref("osc", osc_uuid)) else: error("No devices element found for LOV:", lov) def node_add_profile(gen, node, ref, uuid): ret = node.getElementsByTagName('profile') if not ret: error('node has no profile:', node) ret[0].appendChild(gen.ref(ref, uuid)) def get_attr(dom_node, attr, default=""): v = dom_node.getAttribute(attr) if v: return v return default ############################################################ # Top level commands # def do_add_node(gen, lustre, options, node_name): uuid = new_uuid(node_name) node = gen.node(node_name, uuid) node_add_profile(gen, node, 'ldlm', ldlm_uuid) if options.has_key('router'): node.setAttribute('router', '1') lustre.appendChild(node) return node def add_node(gen, lustre, options, args): """ create a node with a network config """ if len(args) > 1: usage() node_name = options['node'] ret = findByName(lustre, node_name, "node") if ret: print "Node:", node_name, "exists." return do_add_node(gen, lustre, options, node_name) def add_net(gen, lustre, options, args): """ create a node with a network config """ if len(args) < 2: usage() node_name = options['node'] nid = args[0] net_type = args[1] port = 0 tcpbuf = 0 if net_type == 'tcp': if len(args) > 2: port = int(args[2]) else: port = DEFAULT_PORT if options.has_key('tcpbuf'): tcpbuf = int(options['tcpbuf']) elif net_type in ('elan', 'gm'): port = 0 else: print "Unknown net_type: ", net_type sys.exit(2) ret = findByName(lustre, node_name, "node") if not ret: node = do_add_node(gen, lustre, options, node_name) else: node = ret net_name = new_name('NET_'+ node_name +'_'+ net_type) net_uuid = new_uuid(net_name) node.appendChild(gen.network(net_name, net_uuid, nid, net_type, port, tcpbuf)) node_add_profile(gen, node, "network", net_uuid) def add_route(gen, lustre, options, args): """ create a node with a network config """ if len(args) < 3: usage() node_name = options['node'] net_type= args[0] gw = args[1] lo = args[2] hi = '' if len(args) > 3: hi = args[3] node = findByName(lustre, node_name, "node") if not node: error (node_name, " not found.") netlist = node.getElementsByTagName('network') net = netlist[0] rlist = net.getElementsByTagName('route_tbl') if len(rlist) > 0: rtbl = rlist[0] else: rtbl = gen.addElement(net, 'route_tbl') rtbl.appendChild(gen.route(net_type, gw, lo, hi)) def add_mds(gen, lustre, options, args): if len(args) < 1: usage() if options.has_key('node'): node_name = options['node'] else: error("--mds requires a --node argument") mds_name = new_name(options['mds']) devname = args[0] if len(args) > 1: size = args[1] else: size = 0 mdc_name = 'MDC_' + mds_name mds_uuid = new_uuid(mds_name) mdc_uuid = new_uuid(mdc_name) node_uuid = name2uuid(lustre, node_name) node = findByName(lustre, node_name, "node") node_add_profile(gen, node, "mds", mds_uuid) net_uuid = get_net_uuid(lustre, node_name) if not net_uuid: error("NODE: ", node_name, "not found") mds = gen.mds(mds_name, mds_uuid, "extN", devname, get_format_flag(options), net_uuid, node_uuid, dev_size=size) mdc = gen.mdc(mdc_name, mdc_uuid, mds_uuid) lustre.appendChild(mds) lustre.appendChild(mdc) def add_mdc(gen, lustre, options, args): """ create mtpt on a node """ if len(args) < 1: usage() if options.has_key('node'): node_name = options['node'] else: error("--mdc requires a --node argument") mdc_name = args[0] mdc_uuid = name2uuid(lustre, mdc_name) node = findByName(lustre, node_name, "node") if not node: error('node:', node_name, "not found.") node_add_profile(gen, node, "mdc", mdc_uuid) def add_ost(gen, lustre, options, args): lovname = '' obdtype = 'obdfilter' devname = '' size = 0 fstype = 'extN' if options.has_key('node'): node_name = options['node'] else: error("--ost requires a --node argument") if options.has_key('lov'): lovname = options['lov'] if options.has_key('obdtype'): obdtype = options['obdtype'] if obdtype == 'obdecho': fstype = '' else: if len(args) < 1: usage() devname = args[0] if len(args) > 1: size = args[1] obdname = new_name('OBD_'+ node_name) oscname = new_name('OSC_'+ node_name) ostname = new_name('OST_'+ node_name) obd_uuid = new_uuid(obdname) ost_uuid = new_uuid(ostname) osc_uuid = new_uuid(oscname) net_uuid = get_net_uuid(lustre, node_name) if not net_uuid: error("NODE: ", node_name, "not found") obd = gen.obd(obdname, obd_uuid, fstype, obdtype, devname, get_format_flag(options), size) ost = gen.ost(ostname, ost_uuid, obd_uuid, net_uuid) osc = gen.osc(oscname, osc_uuid, obd_uuid, ost_uuid) if lovname: lov = findByName(lustre, lovname, "lov") if not lov: error("LOV:", lovname, "not found.") lov_add_osc(gen, lov, osc_uuid) node = findByName(lustre, node_name, "node") node_add_profile(gen, node, 'obd', obd_uuid) node_add_profile(gen, node, 'ost', ost_uuid) lustre.appendChild(obd) lustre.appendChild(osc) lustre.appendChild(ost) # this is generally only used by llecho.sh def add_osc(gen, lustre, options, args): """ add the osc to the profile for this node. """ if len(args) < 1: usage() osc_name = args[0] if options.has_key('node'): node_name = options['node'] else: error("--osc requires a --node argument") osc_uuid = name2uuid(lustre, osc_name) node = findByName(lustre, node_name, "node") node_add_profile(gen, node, 'osc', osc_uuid) def add_lov(gen, lustre, options, args): """ create a lov """ if len(args) < 4: usage() name = options['lov'] mds_name = args[0] stripe_sz = args[1] stripe_off = args[2] pattern = args[3] uuid = new_uuid(name) ret = findByName(lustre, name, "lov") if ret: error("LOV: ", name, " already exists.") mds_uuid = name2uuid(lustre, mds_name) node = mds2node(lustre, mds_name) node_add_profile(gen, node, "lov", uuid) lov = gen.lov(name, uuid, mds_uuid, stripe_sz, stripe_off, pattern) lustre.appendChild(lov) def add_mtpt(gen, lustre, options, args): """ create mtpt on a node """ if len(args) < 3: usage() if options.has_key('node'): node_name = options['node'] else: error("--mtpt requires a --node argument") path = args[0] mds_name = args[1] lov_name = args[2] mdc_name = 'MDC_' + mds_name name = new_name('MNT_'+ node_name) ret = findByName(lustre, name, "mountpoint") if ret: error("MOUNTPOINT: ", name, " already exists.") mdc_uuid = name2uuid(lustre, mdc_name) lov_uuid = name2uuid(lustre, lov_name, tag='lov', fatal=0) if not lov_uuid: lov_uuid = name2uuid(lustre, lov_name, tag='osc', fatal=1) uuid = new_uuid(name) mtpt = gen.mountpoint(name, uuid, mdc_uuid, lov_uuid, path) node = findByName(lustre, node_name, "node") if not node: error('node:', node_name, "not found.") node_add_profile(gen, node, "mountpoint", uuid) node_add_profile(gen, node, "mdc", mdc_uuid) lustre.appendChild(mtpt) ############################################################ # Command line processing # def parse_cmdline(argv): short_opts = "ho:i:m:" long_opts = ["ost", "osc", "mtpt", "lov=", "node=", "mds=", "net", "tcpbuf=", "mdc", "route", "router", "merge=", "format", "reformat", "output=", "obdtype=", "in=", "help"] opts = [] args = [] options = {} try: opts, args = getopt.getopt(argv, short_opts, long_opts) except getopt.error: print "invalid opt" usage() for o, a in opts: if o in ("-h", "--help"): usage() if o in ("-o", "--output"): options['output'] = a if o == "--ost": options['ost'] = 1 if o == "--osc": options['osc'] = 1 if o == "--mds": options['mds'] = a if o == "--mdc": options['mdc'] = 1 if o == "--net": options['net'] = 1 if o == "--mtpt": options['mtpt'] = 1 if o == "--node": options['node'] = a if o == "--route": options['route'] = 1 if o == "--router": options['router'] = 1 if o == "--lov": options['lov'] = a if o in ("-m", "--merge"): options['merge'] = a if o == "--obdtype": options['obdtype'] = a if o == "--tcpbuf": options['tcpbuf'] = a if o == "--format": options['format'] = 1 if o == "--reformat": options['reformat'] = 1 if o in ("--in" , "-i"): options['in'] = a return options, args # simple class for profiling import time class chrono: def __init__(self): self._start = 0 def start(self): self._stop = 0 self._start = time.time() def stop(self, msg=''): self._stop = time.time() if msg: self.display(msg) def dur(self): return self._stop - self._start def display(self, msg): d = self.dur() str = '%s: %g secs' % (msg, d) print str ############################################################ # Main # def main(): options, args = parse_cmdline(sys.argv[1:]) outFile = '-' if options.has_key('merge'): outFile = options['merge'] if os.access(outFile, os.R_OK): doc = xml.dom.minidom.parse(outFile) else: doc = new_lustre(xml.dom.minidom) elif options.has_key('in'): doc = xml.dom.minidom.parse(options['in']) else: doc = new_lustre(xml.dom.minidom) if options.has_key('output'): outFile = options['output'] lustre = doc.documentElement init_names(lustre) if lustre.tagName != "lustre": print "Existing config not valid." sys.exit(1) gen = GenConfig(doc) if options.has_key('ost'): add_ost(gen, lustre, options, args) elif options.has_key('osc'): add_osc(gen, lustre, options, args) elif options.has_key('mtpt'): add_mtpt(gen, lustre, options, args) elif options.has_key('mds'): add_mds(gen, lustre, options, args) elif options.has_key('mdc'): add_mdc(gen, lustre, options, args) elif options.has_key('net'): add_net(gen, lustre, options, args) elif options.has_key('lov'): add_lov(gen, lustre, options, args) elif options.has_key('route'): add_route(gen, lustre, options, args) elif options.has_key('node'): add_node(gen, lustre, options, args) else: print "Missing command" usage() if outFile == '-': PrettyPrint(doc) else: PrettyPrint(doc, open(outFile,"w")) if __name__ == "__main__": main()