#!/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: lmc --output config.xml --node nodename nid nettype [port=2346] lmc --merge config.xml --lov lovname stripsize stripeoffset lmc --merge config.xml --ost /dev/name nodename lovname [size=9999] lmc --merge config.xml --mtpt /mnt/lustre lovname nodename """ import sys, getopt, string import xml.dom.minidom from xml.dom.ext import PrettyPrint from xml.xpath import Evaluate DEFAULT_PORT = 888 # XXX What is the right default acceptor port to use? def usage(): print """usage: lmc [--node --ost | --mtpt | --lov] args Commands: --node node_name hostname nettype [port] Node_name is used to refer to this node during the configure process. Nettype is either tcp, elan, or gm. --lov lov_name mdc_name stripe_sz stripe_off pattern Creates a logical volume --mds node_name device [size] --ost device node_name lov_name [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. If lov_name is used, this device is added to lov. --mtpt node_name mds_name lov_name /mnt/point Creates a client mount point. 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) """ 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 get_uuid(name): return "%s_UUID" % (name) def new_lustre(dom): """Create a new empty lustre document""" str = """ """ return dom.parseString(str) names = {} uuids = {} def init_names(lustre): """initialize auto-name generation tables""" global names, uuids # get all elements that contain a name attribute for n in Evaluate("//@name/..", lustre): names[n.getAttribute("name")] = 1 uuids[n.getAttribute("uuid")] = 1 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): """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)) return network def node(self, name, uuid): """ create a host """ node = self.newService("node", name, uuid) return node def obd(self, name, uuid, fs, devname, format, dev_size=0): obd = self.newService("obd", name, uuid) obd.setAttribute('type', 'obdfilter') self.addElement(obd, "fstype", fs) 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, stripe_sz, stripe_off, pattern): lov = self.newService("lov", name, 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, 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)) 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("mtpt", name, uuid) mtpt.appendChild(self.ref("mdc", mdc_uuid)) mtpt.appendChild(self.ref("osc", osc_uuid)) self.addElement(mtpt, "path", path) return mtpt def findByName(lustre, name, tag = "*"): path = '//%s[@name="%s"]' % (tag, name) ret = Evaluate(path, lustre) if ret: return ret[0] return None # 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 = Evaluate("./network", node) if net: return net[0].getAttribute("uuid") return None def lov_add_osc(gen, lov, osc_uuid): devs = Evaluate("devices", lov) if len(devs) == 1: devs[0].appendChild(gen.ref("osc", osc_uuid)) else: error("No devices element found for LOV:", lov) # # Create a new obd, osc, and ost. Add them to the DOM. # def add_ost(gen, lustre, options, args): if len(args) < 3: usage() devname = args[0] node_name = args[1] lovname = args[2] if len(args) > 3: size = args[3] else: size = 0 obdname = new_name(node_name+"_" + "obd") oscname = new_name(node_name+"_"+"osc") ostname = new_name(node_name+"_"+"ost") obd_uuid = get_uuid(obdname) ost_uuid = get_uuid(ostname) osc_uuid = get_uuid(oscname) net_uuid = get_net_uuid(lustre, node_name) if not net_uuid: error("NODE: ", node_name, "not found") lov = findByName(lustre, lovname, "lov") if not lov: error("LOV:", lovname, "not found.") obd = gen.obd(obdname, obd_uuid, "extN", devname, "no", size) ost = gen.ost(ostname, ost_uuid, obd_uuid, net_uuid) osc = gen.osc(oscname, osc_uuid, obd_uuid, ost_uuid) lov_add_osc(gen, lov, osc_uuid) lustre.appendChild(obd) lustre.appendChild(osc) lustre.appendChild(ost) def add_node(gen, lustre, options, args): """ create a node with a network config """ if len(args) < 3: usage() name = args[0] nid = args[1] nettype = args[2] if nettype == 'tcp': if len(args) > 3: port = int(args[3]) else: port = DEFAULT_PORT elif nettype in ('elan', 'gm'): port = 0 else: print "Unknown nettype: ", nettype sys.exit(2) uuid = get_uuid(name) node = gen.node(name, uuid) net_name = name+"_net" net_uuid = get_uuid(net_name) node.appendChild(gen.network(net_name, net_uuid, nid, nettype, port)) lustre.appendChild(node) def add_lov(gen, lustre, options, args): """ create a lov """ if len(args) < 4: usage() name = args[0] stripe_sz = args[1] stripe_off = args[2] pattern = args[3] ret = findByName(lustre, name, "lov") if ret: error("LOV: ", name, " already exists.") uuid = get_uuid(name) lov = gen.lov(name,uuid,stripe_sz, stripe_off, pattern) lustre.appendChild(lov) def add_mtpt(gen, lustre, options, args): """ create mtpt on a node """ if len(args) < 4: usage() node_name = args[0] mdc_name = args[1] lov_name = args[2] path = args[3] name = new_name(node_name + "_mtpt") ret = findByName(lustre, name, "mountpoint") if ret: error("MOUNTPOINT: ", name, " already exists.") ret = findByName(lustre, lov_name, "lov") if not ret: error("LOV: ", lov_name, " not found.") lov_uuid = ret.getAttribute("uuid") ret = findByName(lustre, mdc_name, "mdc") if not ret: error("MDC: ", mdc_name, " not found.") mdc_uuid = ret.getAttribute("uuid") uuid = get_uuid(name) mtpt = gen.mountpoint(name, uuid, mdc_uuid, lov_uuid, path) lustre.appendChild(mtpt) def add_mds(gen, lustre, options, args): if len(args) < 3: usage() node_name = args[0] devname = args[1] if len(args) > 3: size = args[3] else: size = 0 ret = findByName(lustre, node_name, "node") mds_name = new_name(node_name+"_" + "mds") mdc_name = new_name(node_name+"_"+"mdc") mds_uuid = get_uuid(mds_name) mdc_uuid = get_uuid(mdc_name) 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, "no", net_uuid) mdc = gen.mdc(mdc_name, mdc_uuid, mds_uuid) lustre.appendChild(mds) lustre.appendChild(mdc) # # Command line processing # def parse_cmdline(argv): short_opts = "ho:i:" long_opts = ["ost", "mtpt", "lov", "node", "mds", "merge=", "format", "reformat", "output=", "in=", "help"] opts = [] args = [] options = {} try: opts, args = getopt.getopt(argv, short_opts, long_opts) except getopt.GetoptError: 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 == "--mds": options['mds'] = 1 if o == "--node": options['node'] = 1 if o == "--lov": options['lov'] = 1 if o == "--mtpt": options['mtpt'] = 1 if o == "--merge": options['merge'] = a if o == "--format": options['format'] = 1 if o == "--reformat": options['reformat'] = 1 if o in ("--in" , "-i"): options['in'] = a return options, args def main(): options, args = parse_cmdline(sys.argv[1:]) outFile = '-' if options.has_key('merge'): outFile = options['merge'] doc = xml.dom.minidom.parse(outFile) 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) lustre = doc.documentElement 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('node'): add_node(gen, lustre, options, args) elif options.has_key('mtpt'): add_mtpt(gen, lustre, options, args) elif options.has_key('lov'): add_lov(gen, lustre, options, args) elif options.has_key('mds'): add_mds(gen, lustre, options, args) else: print "Missing command" usage() if outFile == '-': PrettyPrint(doc) else: PrettyPrint(doc, open(outFile,"w")) if __name__ == "__main__": main()