#!/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 See lustre book for documentation for lmc. """ import sys, os, getopt, string, exceptions import xml.dom.minidom from xml.dom.ext import PrettyPrint DEFAULT_PORT = 988 def usage(): print """usage: lmc --add object [object parameters] Object creation command summary: --add node --node node_name --timeout num --recovery_upcall path --add net --node node_name --nid addr --nettype tcp|elan|toe|gm --port port --tcpbuf size --router --add mds --node node_name --mds mds_name --dev path --size size --add lov --lov lov_name --mds mds_name --stripe_sz num --stripe_cnt num --stripe_pattern num -add ost --node node_name --obd obd_name --lov lov_name --dev path --size size --obduuid uuid --add mtpt - Mountpoint --node node_name --path /mnt/point --mds mds_name --obd obd_name OR --lov lovname """ sys.exit(1) def error(*args): msg = string.join(map(str,args)) raise OptionError("Error: " + msg) def panic(cmd, msg): print "! " + cmd print 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("uuid", uuid); new.setAttribute("name", name); 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, ost_uuid, dev_size=0): obd = self.newService("obd", name, uuid) obd.setAttribute('type', obdtype) self.addElement(obd, 'active_target', ost_uuid) 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 cobd(self, name, uuid, real_uuid, cache_uuid): cobd = self.newService("cobd", name, uuid) cobd.appendChild(self.ref("real_obd",real_uuid)) cobd.appendChild(self.ref("cache_obd",cache_uuid)) return cobd 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_cnt, 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_cnt) 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("obd", osc_uuid)) self.addElement(mtpt, "path", path) return mtpt def echo_client(self, name, uuid, osc_uuid): ec = self.newService("echo_client", name, uuid) ec.appendChild(self.ref("obd", osc_uuid)) return ec ############################################################ # 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_obd(gen, lov, osc_uuid): devs = lov.getElementsByTagName('devices') if len(devs) == 1: devs[0].appendChild(gen.ref("obd", 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 has_option(options, 'router'): node.setAttribute('router', '1') if has_option(options, 'timeout'): node.setAttribute('timeout', get_option(options, 'timeout')) if has_option(options, 'recovery_upcall'): node.setAttribute('recovery_upcall', get_option(options, 'recovery_upcall')) lustre.appendChild(node) return node def add_node(gen, lustre, options): """ create a node with a network config """ node_name = get_option(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): """ create a node with a network config """ node_name = get_option(options, 'node') nid = get_option(options, 'nid') net_type = get_option(options, 'nettype') if net_type == 'tcp': port = get_option_int(options, 'port', DEFAULT_PORT) tcpbuf = get_option_int(options, 'tcpbuf', 0) elif net_type in ('elan', 'gm'): port = 0 tcpbuf = 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): """ create a node with a network config """ node_name = get_option(options, 'node') net_type = get_option(options, 'nettype') gw = get_option(options, 'gw') lo = get_option(options, 'lo') hi = get_option(options, 'hi', '') 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): node_name = get_option(options, 'node') mds_orig = get_option(options, 'mds') mds_name = new_name(mds_orig) if mds_name != mds_orig: warning("name:", mds_orig, "already used. using:", mds_name) devname = get_option(options, 'dev') size = get_option(options, 'size', 0) fstype = get_option(options, 'fstype', 'extN') 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): node_name = get_option(options, 'node') lovname = get_option(options, 'lov', '') obdtype = get_option(options, 'obdtype', 'obdfilter') if obdtype == 'obdecho': fstype = '' devname = '' size = 0 fstype = '' else: devname = get_option(options, 'dev', '') # can be unset for bluearcs size = get_option(options, 'size', 0) fstype = get_option(options, 'fstype', 'extN') obdname = get_option(options, 'obd', 'OBD_'+ node_name) obdname = new_name(obdname) ostname = new_name('OST_'+ obdname) 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) 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), ost_uuid, size) ost = gen.ost(ostname, ost_uuid, obd_uuid, net_uuid) if lovname: lov = findByName(lustre, lovname, "lov") if not lov: error('add_ost:', '"'+lovname+'"', "lov element not found.") lov_add_obd(gen, lov, obd_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(ost) def add_cobd(gen, lustre, options): node_name = get_option(options, 'node') name = new_name('COBD_' + node_name) uuid = new_uuid(name) real_name = get_option(options, 'real_obd') cache_name = get_option(options, 'cache_obd') real_uuid = name2uuid(lustre, real_name, tag='obd') cache_uuid = name2uuid(lustre, cache_name, tag='obd') node = findByName(lustre, node_name, "node") node_add_profile(gen, node, "cobd", uuid) cobd = gen.cobd(name, uuid, real_uuid, cache_uuid) lustre.appendChild(cobd) def add_echo_client(gen, lustre, options): """ add an echo client to the profile for this node. """ node_name = get_option(options, 'node') lov_name = get_option(options, 'obd') node = findByName(lustre, node_name, 'node') echoname = new_name('ECHO_'+ node_name) echo_uuid = new_uuid(echoname) node_add_profile(gen, node, 'echo_client', echo_uuid) lov_uuid = name2uuid(lustre, lov_name, tag='lov', fatal=0) if not lov_uuid: lov_uuid = name2uuid(lustre, lov_name, tag='obd', fatal=1) echo = gen.echo_client(echoname, echo_uuid, lov_uuid) lustre.appendChild(echo) def add_lov(gen, lustre, options): """ create a lov """ lov_orig = get_option(options, 'lov') name = new_name(lov_orig) if name != lov_orig: warning("name:", lov_orig, "already used. using:", name) mds_name = get_option(options, 'mds') stripe_sz = get_option(options, 'stripe_sz') stripe_cnt = get_option(options, 'stripe_cnt', 0) pattern = get_option(options, 'stripe_pattern', 0) 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_cnt, 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): """ create mtpt on a node """ node_name = get_option(options, 'node') path = get_option(options, 'path') mds_name = get_option(options, 'mds') lov_name = get_option(options, 'lov', '') if lov_name == '': lov_name = get_option(options, 'obd', '') if lov_name == '': error("--add mtpt requires either --lov lov_name or --obd obd_name") 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='obd', 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) def add_oscref(gen, lustre, options): """ create mtpt on a node """ node_name = get_option(options, 'node') osc_name = get_option(options, 'osc') osc_uuid = name2uuid(lustre, osc_name, tag='osc') node = findByName(lustre, node_name, "node") if not node: error('node:', node_name, "not found") node_add_profile(gen, node, "osc",osc_uuid) ############################################################ # Command line processing # class OptionError (exceptions.Exception): def __init__(self, args): self.args = args def has_option(options, tag): """Look for tag in options hash and return the true if set""" if options.has_key(tag): return 1 return 0 def get_option(options, tag, default = None): """Look for tag in options hash and return the value if set. If not set, then if return default it is set, otherwise exception.""" if options.has_key(tag): return options[tag] elif default != None: return default else: raise OptionError("--add %s requires --%s value" % (options['add'], tag)) # this exception should print an error like '--add blah requires -- value' def get_option_int(options, tag, default = None): """Return an integer option. Raise exception if the value is not an int""" val = get_option(options, tag, default) return int(val) def parse_cmdline(argv): short_opts = "ho:i:m:" long_opts = ["add=", "node=", "nettype=", "nid=", "tcpbuf=", "port=", "echo_client=", "stripe_sz=", "stripe_cnt=", "stripe_pattern=", "mds=", "route", "router", "merge=", "format", "reformat", "output=", "dev=", "size=", "obd=", "obdtype=", "obduuid=", "in=", "path=", "help", "batch=", "lov=", "gw=", "lo=", "hi=", "oscref", "osc=", "real_obd=", "cache_obd=", "fstype=", "timeout=", "recovery_upcall="] opts = [] args = [] options = {} try: opts, args = getopt.getopt(argv, short_opts, long_opts) except getopt.error, e: panic(string.join(sys.argv), e) for o, a in opts: # Commands to create new devices if o == "--add": options['add'] = a if o == "--node": options['node'] = a # devices names if o == "--lov": options['lov'] = a if o == "--mds": options['mds'] = a if o == "--obd": options['obd'] = a # node options if o == "--timeout": options['timeout'] = a if o == "--recovery_upcall": options['recovery_upcall'] = a if o == "--router": options['router'] = 1 # network options if o == "--nid": options['nid'] = a if o == "--nettype": options['nettype'] = a if o == "--net": options[''] = a if o == "--tcpbuf": options['tcpbuf'] = a if o == "--port": options['port'] = a if o == "--mtpt": options['mtpt'] = 1 if o == "--route": options['route'] = 1 # ost options if o == "--dev": options['dev'] = a if o == "--size": options['size'] = a if o == "--path": options['path'] = a if o == "--osc": options['osc'] = a if o == "--obdtype": options['obdtype'] = a if o == "--fstype": options['fstype'] = a if o == "--obduuid": options['obduuid'] = a # lov options if o == "--stripe_sz": options['stripe_sz'] = a if o == "--stripe_cnt": options['stripe_cnt'] = a if o == "--stripe_pattern": options['stripe_pattern'] = a if o == "--gw": options['gw'] = a if o == "--lo": options['lo'] = a if o == "--hi": options['hi'] = a # cobd if o == "--cache_obd": options['cache_obd'] = a if o == "--real_obd": options['real_obd'] = 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 add(devtype, gen, lustre, options): if devtype == 'net': add_net(gen, lustre, options) elif devtype =='osc': add_osc(gen, lustre, options) elif devtype == 'mtpt': add_mtpt(gen, lustre, options) elif devtype == 'mds': add_mds(gen, lustre, options) elif devtype == 'ost': add_ost(gen, lustre, options) elif devtype == 'lov': add_lov(gen, lustre, options) elif devtype == 'route': add_route(gen, lustre, options) elif devtype == 'node': add_node(gen, lustre, options) elif devtype == 'echo_client': add_echo_client(gen, lustre, options) elif devtype == 'oscref': add_oscref(gen, lustre, options) elif devtype == 'cobd': add_cobd(gen, lustre, options) else: error("unknown device type:", devtype) def do_command(gen, lustre, options, args): if options.has_key('add'): add(options['add'], gen, lustre, options) else: error("Missing command") 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)) try: do_command(gen, lustre, options, args) except OptionError, e: panic(cmd, e) else: try: do_command(gen, lustre, options, args) except OptionError, e: panic(string.join(sys.argv),e) if outFile == '-': PrettyPrint(doc) else: PrettyPrint(doc, open(outFile,"w")) if __name__ == "__main__": main()