#!/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, toe, 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 [mds_name stripe_sz sub_stripe_count pattern] Creates a logical volume When used with other commands, it specifics the lov to modify --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 Optional --obduuid Specifies the UUID used for the obd. 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 NB: The autoformat option has been disabled until a safe method is implemented to determine if a block device has a filesystem. --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 "Error: ", msg sys.exit(1) def warning(*args): msg = string.join(map(str,args)) print "Warning: ", msg # # 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_count, 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("stripecount", stripe_count) devs.setAttribute("pattern", pattern) return lov def lovconfig(self, name, uuid, lov_uuid): lovconfig = self.newService("lovconfig", name, uuid) lovconfig.appendChild(self.ref("lov", lov_uuid)) return lovconfig 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 mountpoint(self, name, uuid, mds_uuid, osc_uuid, path): mtpt = self.newService("mountpoint", name, uuid) mtpt.appendChild(self.ref("mds", mds_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("mds2node:", "no node_ref found for", '"'+mds_name+'"') node_uuid = ref[0].getAttribute('uuidref') node = lookup(lustre, node_uuid) if not node: error('mds2node:', "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+'"', tag, 'element 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 ('get_net_uuid:', '"'+node_name+'"', "node element not found.") 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 in ('tcp', 'toe'): 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): fstype = 'extN' if len(args) < 1: usage() if options.has_key('node'): node_name = options['node'] else: error("--mds requires a --node argument") if options.has_key('fstype'): fstype = options['fstype'] mds_name = new_name(options['mds']) if mds_name != options['mds']: warning("name:", options['mds'], "already used. using:", mds_name) devname = args[0] if len(args) > 1: size = args[1] else: size = 0 mds_uuid = new_uuid(mds_name) node_uuid = name2uuid(lustre, node_name, 'node') 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, fstype, devname, get_format_flag(options), net_uuid, node_uuid, dev_size=size) lustre.appendChild(mds) 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 options.has_key('fstype'): fstype = options['fstype'] 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) if options.has_key('obduuid'): obd_uuid = options['obduuid'] obd = lookup(lustre, obd_uuid) if obd: error("Duplicate OBD UUID:", obd_uuid) else: 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('add_ost:', '"'+lovname+'"', "lov element 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) # either 'osc' or 'lov' 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 = new_name(options['lov']) if name != options['lov']: warning("name:", options['lov'], "already used. using:", name) mds_name = args[0] stripe_sz = args[1] stripe_count = 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, 'mds') lov = gen.lov(name, uuid, mds_uuid, stripe_sz, stripe_count, pattern) lustre.appendChild(lov) # add an lovconfig entry to the mds profile lovconfig_name = new_name('LVCFG_' + name) lovconfig_uuid = new_uuid(lovconfig_name) node = mds2node(lustre, mds_name) node_add_profile(gen, node, "lovconfig", lovconfig_uuid) lovconfig = gen.lovconfig(lovconfig_name, lovconfig_uuid, uuid) lustre.appendChild(lovconfig) 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] name = new_name('MNT_'+ node_name) ret = findByName(lustre, name, "mountpoint") if ret: error("MOUNTPOINT: ", name, " already exists.") mds_uuid = name2uuid(lustre, mds_name, tag='mds') 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, mds_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) 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=", "route", "router", "merge=", "format", "reformat", "output=", "obdtype=", "fstype=", "obduuid=", "in=", "help", "batch="] 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: # Commands to create new devices if o == "--ost": options['ost'] = 1 if o == "--osc": options['osc'] = 1 if o == "--mds": options['mds'] = a 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 # Options for commands if o == "--obdtype": options['obdtype'] = a if o == "--fstype": options['fstype'] = a if o == "--obduuid": options['obduuid'] = a if o == "--tcpbuf": options['tcpbuf'] = a # lmc options if o in ("-h", "--help"): usage() if o in ("-o", "--output"): options['output'] = a if o in ("-m", "--merge"): options['merge'] = a if o == "--format": options['format'] = 1 if o == "--reformat": options['reformat'] = 1 if o == "--batch": options['batch'] = a 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 do_command(gen, lustre, options, args): 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('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() 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('batch'): fp = open(options['batch']) batchCommands = fp.readlines() fp.close() for cmd in batchCommands: options, args = parse_cmdline(string.split(cmd)) do_command(gen, lustre, options, args) else: do_command(gen, lustre, options, args) if outFile == '-': PrettyPrint(doc) else: PrettyPrint(doc, open(outFile,"w")) if __name__ == "__main__": main()