Whamcloud - gitweb
- auto-naming is now smarter
[fs/lustre-release.git] / lustre / utils / lmc
1 #!/usr/bin/env python
2 #
3 #  Copyright (C) 2002 Cluster File Systems, Inc.
4 #   Author: Robert Read <rread@clusterfs.com>
5
6 #   This file is part of Lustre, http://www.lustre.org.
7 #
8 #   Lustre is free software; you can redistribute it and/or
9 #   modify it under the terms of version 2 of the GNU General Public
10 #   License as published by the Free Software Foundation.
11 #
12 #   Lustre is distributed in the hope that it will be useful,
13 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #   GNU General Public License for more details.
16 #
17 #   You should have received a copy of the GNU General Public License
18 #   along with Lustre; if not, write to the Free Software
19 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #
21
22 """
23 lmc - lustre configurtion data  manager
24
25  Basic plan for lmc usage:
26   lmc --output config.xml --node nodename nid nettype [port=2346]
27   lmc --merge config.xml  --lov lovname stripsize stripeoffset
28   lmc --merge config.xml  --ost /dev/name nodename lovname [size=9999]
29   lmc --merge config.xml  --mtpt /mnt/lustre lovname nodename
30 """
31
32 import sys, getopt, string
33 import xml.dom.minidom
34 from xml.dom.ext import PrettyPrint
35 from xml.xpath import Evaluate
36
37 DEFAULT_PORT = 888 # XXX What is the right default acceptor port to use?
38
39 def usage():
40     print """usage: lmc [--node --ost | --mtpt | --lov] args
41 Commands:
42 --node node_name hostname nettype [port]
43    Node_name is used to refer to this node during the configure process.
44    Nettype is either tcp, elan, or gm.
45
46 --lov lov_name mdc_name stripe_sz stripe_off pattern
47    Creates a logical volume 
48
49 --mds node_name device [size]
50
51 --ost device node_name lov_name [size]
52    Creates an OBD/OST/OSC configuration triplet for a new device.
53    When used on "host", the device will be initialized and the OST
54    will be enabled. On client nodes, the OSC will be avaiable.
55    If lov_name is used, this device is added to lov. 
56
57 --mtpt node_name mds_name lov_name /mnt/point
58    Creates a client mount point.
59
60 Options:
61 --merge="xml file"  Add the new objects to an existing file
62 --format            Format the partitions if unformated
63 --reformat          Reformat partitions (this should be an lconf arg,
64                     I think)
65 """
66     sys.exit(1)
67
68 def error(*args):
69     msg = string.join(map(str,args))
70     print msg
71     sys.exit(1)
72     
73 #
74 # manage names and uuids
75 # need to initialize this by walking tree to ensure
76 # no duplicate names or uuids are created.
77 # this are just place holders for now.
78 # consider changing this to be like OBD-dev-host
79 def new_name(base):
80     ctr = 2
81     ret = base
82     while names.has_key(ret):
83         ret = "%s_%d" % (base, ctr)
84         ctr = 1 + ctr
85     names[ret] = 1
86     return ret
87
88 def get_uuid(name):
89     return "%s_UUID" % (name)
90
91 def new_lustre(dom):
92     """Create a new empty lustre document"""
93     str = """<lustre> </lustre>"""
94     return dom.parseString(str)
95
96 names = {}
97 uuids = {}
98 def init_names(lustre):
99     """initialize auto-name generation tables"""
100     global names, uuids
101     # get all elements that contain a name attribute
102     for n in Evaluate("//@name/..", lustre):
103         names[n.getAttribute("name")] = 1
104         uuids[n.getAttribute("uuid")] = 1
105
106 class GenConfig:
107     doc = None
108     dom = None
109     def __init__(self, doc):
110         self.doc = doc
111
112     def ref(self, type, uuid):
113         """ generate <[type]_ref uuidref="[uuid]"/> """
114         tag = "%s_ref" % (type)
115         ref = self.doc.createElement(tag)
116         ref.setAttribute("uuidref", uuid)
117         return ref
118     
119     def newService(self, tag, name, uuid):
120         """ create a new  service elmement, which requires name and uuid attributes """
121         new = self.doc.createElement(tag)
122         new.setAttribute("name", name);
123         new.setAttribute("uuid", uuid);
124         return new
125     
126     def addText(self, node, str):
127         txt = self.doc.createTextNode(str)
128         node.appendChild(txt)
129
130     def addElement(self, node, tag, str=None):
131         """ create a new element and add it as a child to node. If str is passed,
132             a text node is created for the new element"""
133         new = self.doc.createElement(tag)
134         if str:
135             self.addText(new, str)
136         node.appendChild(new)
137         return new
138
139     def network(self, name, uuid, hostname, net, port=0):
140         """create <network> node"""
141         network = self.newService("network", name, uuid)
142         network.setAttribute("type", net);
143         self.addElement(network, "server", hostname)
144         if port:
145             self.addElement(network, "port", "%d" %(port))
146         return network
147
148     def node(self, name, uuid):
149         """ create a host """
150         node = self.newService("node", name, uuid)
151         return node
152
153     def obd(self, name, uuid, fs, devname, format, dev_size=0):
154         obd = self.newService("obd", name, uuid)
155         obd.setAttribute('type', 'obdfilter')
156         self.addElement(obd, "fstype", fs)
157         dev = self.addElement(obd, "device", devname)
158         if (dev_size):
159             dev.setAttribute("size", "%s" % (dev_size))
160         self.addElement(obd, "autoformat", format)
161         return obd
162
163     def osc(self, name, uuid, obd_uuid, net_uuid):
164         osc = self.newService("osc", name, uuid)
165         osc.appendChild(self.ref("ost", net_uuid))
166         osc.appendChild(self.ref("obd", obd_uuid))
167         return osc
168
169     def ost(self, name, uuid, obd_uuid, net_uuid):
170         ost = self.newService("ost", name, uuid)
171         ost.appendChild(self.ref("network", net_uuid))
172         ost.appendChild(self.ref("obd", obd_uuid))
173         return ost
174
175     def lov(self, name, uuid, stripe_sz, stripe_off, pattern):
176         lov = self.newService("lov", name, uuid)
177         devs = self.addElement(lov, "devices" )
178         devs.setAttribute("stripesize", stripe_sz)
179         devs.setAttribute("stripeoffset", stripe_off)
180         devs.setAttribute("pattern", pattern)
181         return lov
182
183     def mds(self, name, uuid, fs, devname, format, net_uuid, failover_uuid = "", dev_size=0 ):
184         mds = self.newService("mds", name, uuid)
185         self.addElement(mds, "fstype", fs)
186         dev = self.addElement(mds, "device", devname)
187         if dev_size:
188             dev.setAttribute("size", "%s" % (dev_size))
189         self.addElement(mds, "autoformat", format)
190         mds.appendChild(self.ref("network", net_uuid))
191         if failover_uuid:
192             mds.appendChild(self.ref("failover", failover_uuid))
193         return mds
194
195     def mdc(self, name, uuid, mds_uuid):
196         mdc = self.newService("mdc", name, uuid)
197         mdc.appendChild(self.ref("mds", mds_uuid))
198         return mdc
199
200     def mountpoint(self, name, uuid, mdc_uuid, osc_uuid, path):
201         mtpt = self.newService("mtpt", name, uuid)
202         mtpt.appendChild(self.ref("mdc", mdc_uuid))
203         mtpt.appendChild(self.ref("osc", osc_uuid))
204         self.addElement(mtpt, "path", path)
205         return mtpt
206
207 def findByName(lustre, name, tag = "*"):
208     path = '//%s[@name="%s"]' % (tag, name)
209     ret = Evaluate(path, lustre)
210     if ret:
211         return ret[0]
212     return None
213     
214 # XXX: assumes only one network element per node. will fix this
215 # as soon as support for routers is added
216 def get_net_uuid(lustre, node_name):
217     """ get a network uuid for a node_name """
218     node = findByName(lustre, node_name, "node")
219     if not node:
220         error ("node not found:", node_name)
221     net = Evaluate("./network", node)
222     if net:
223         return net[0].getAttribute("uuid")
224     return None
225
226 def lov_add_osc(gen, lov, osc_uuid):
227     devs = Evaluate("devices", lov)
228     if len(devs) == 1:
229         devs[0].appendChild(gen.ref("osc", osc_uuid))
230     else:
231         error("No devices element found for LOV:", lov)
232                             
233     
234 #
235 # Create a new obd, osc, and ost. Add them to the DOM.
236 #
237 def add_ost(gen, lustre, options, args):
238     if len(args) < 3:
239         usage()
240
241     devname = args[0]
242     node_name = args[1]
243     lovname = args[2]
244     if len(args) > 3:
245         size = args[3]
246     else:
247         size = 0
248
249     obdname = new_name(node_name+"_" + "obd")
250     oscname = new_name(node_name+"_"+"osc")
251     ostname = new_name(node_name+"_"+"ost")
252     obd_uuid = get_uuid(obdname)
253     ost_uuid = get_uuid(ostname)
254     osc_uuid = get_uuid(oscname)
255
256     net_uuid = get_net_uuid(lustre, node_name)
257     if not net_uuid:
258         error("NODE: ", node_name, "not found")
259     
260     lov = findByName(lustre, lovname, "lov")
261     if not lov:
262         error("LOV:", lovname, "not found.")
263
264     obd = gen.obd(obdname, obd_uuid,  "extN", devname, "no", size)
265     ost = gen.ost(ostname, ost_uuid, obd_uuid, net_uuid)
266     osc = gen.osc(oscname, osc_uuid, obd_uuid, ost_uuid)
267     
268     lov_add_osc(gen, lov, osc_uuid)
269     lustre.appendChild(obd)
270     lustre.appendChild(osc)
271     lustre.appendChild(ost)
272                    
273 def add_node(gen, lustre, options, args):
274     """ create a node with a network config """
275     if len(args) < 3:
276         usage()
277
278     name = args[0]
279     nid = args[1]
280     nettype = args[2]
281
282     if nettype == 'tcp':
283         if len(args) > 3:
284             port = int(args[3])
285         else:
286             port = DEFAULT_PORT
287         
288     elif nettype in ('elan', 'gm'):
289         port = 0
290     else:
291         print "Unknown nettype: ", nettype
292         sys.exit(2)
293
294     uuid = get_uuid(name)
295     node = gen.node(name, uuid)
296     net_name = name+"_net"
297     net_uuid = get_uuid(net_name)
298     node.appendChild(gen.network(net_name, net_uuid, nid, nettype, port))
299     lustre.appendChild(node)
300
301
302 def add_lov(gen, lustre, options, args):
303     """ create a lov """
304     if len(args) < 4:
305         usage()
306
307     name = args[0]
308     stripe_sz = args[1]
309     stripe_off = args[2]
310     pattern = args[3]
311
312     ret = findByName(lustre, name, "lov")
313     if ret:
314         error("LOV: ", name, " already exists.")
315
316     uuid = get_uuid(name)
317     lov = gen.lov(name,uuid,stripe_sz, stripe_off, pattern)
318     lustre.appendChild(lov)
319
320 def add_mtpt(gen, lustre, options, args):
321     """ create mtpt on a node """
322     if len(args) < 4:
323         usage()
324
325     node_name = args[0]
326     mdc_name = args[1]
327     lov_name = args[2]
328     path = args[3]
329
330     name = new_name(node_name + "_mtpt")
331
332     ret = findByName(lustre, name, "mountpoint")
333     if ret:
334         error("MOUNTPOINT: ", name, " already exists.")
335
336     ret = findByName(lustre, lov_name, "lov")
337     if not ret:
338         error("LOV: ", lov_name, " not found.")
339     lov_uuid = ret.getAttribute("uuid")
340
341     ret = findByName(lustre, mdc_name, "mdc")
342     if not ret:
343         error("MDC: ", mdc_name, " not found.")
344     mdc_uuid = ret.getAttribute("uuid")
345
346     uuid = get_uuid(name)
347     mtpt = gen.mountpoint(name, uuid, mdc_uuid, lov_uuid, path)
348     lustre.appendChild(mtpt)
349
350
351 def add_mds(gen, lustre, options, args):
352     if len(args) < 3:
353         usage()
354
355     node_name = args[0]
356     devname = args[1]
357     if len(args) > 3:
358         size = args[3]
359     else:
360         size = 0
361
362     ret = findByName(lustre, node_name, "node")
363
364     mds_name = new_name(node_name+"_" + "mds")
365     mdc_name = new_name(node_name+"_"+"mdc")
366     mds_uuid = get_uuid(mds_name)
367     mdc_uuid = get_uuid(mdc_name)
368
369     net_uuid = get_net_uuid(lustre, node_name)
370     if not net_uuid:
371         error("NODE: ", node_name, "not found")
372
373
374     mds = gen.mds(mds_name, mds_uuid, "extN", devname, "no", net_uuid)
375     mdc = gen.mdc(mdc_name, mdc_uuid, mds_uuid)
376     lustre.appendChild(mds)
377     lustre.appendChild(mdc)
378                    
379
380 #
381 # Command line processing
382 #
383
384 def parse_cmdline(argv):
385     short_opts = "ho:i:"
386     long_opts = ["ost", "mtpt", "lov", "node", "mds",
387                  "merge=", "format", "reformat", "output=",
388                  "in=", "help"]
389     opts = []
390     args = []
391     options = {}
392     try:
393         opts, args = getopt.getopt(argv, short_opts, long_opts)
394     except getopt.GetoptError:
395         print "invalid opt"
396         usage()
397
398     for o, a in opts:
399         if o in ("-h", "--help"):
400             usage()
401         if o in ("-o", "--output"):
402             options['output'] = a
403         if o == "--ost":
404             options['ost'] = 1
405         if o == "--mds":
406             options['mds'] = 1
407         if o == "--node":
408             options['node'] = 1
409         if o == "--lov":
410             options['lov'] = 1
411         if o == "--mtpt":
412             options['mtpt'] = 1
413         if o == "--merge":
414             options['merge'] = a
415         if o == "--format":
416             options['format'] = 1
417         if o  == "--reformat":
418             options['reformat'] = 1
419         if o  in ("--in" , "-i"):
420             options['in'] = a
421             
422     return options, args
423
424 def main():
425     options, args = parse_cmdline(sys.argv[1:])
426     outFile = '-'
427
428     if options.has_key('merge'):
429         outFile = options['merge']
430         doc = xml.dom.minidom.parse(outFile)
431     elif options.has_key('in'):
432         doc = xml.dom.minidom.parse(options['in'])
433     else:
434         doc = new_lustre(xml.dom.minidom)
435
436     if options.has_key('output'):
437         outFile = options['output']
438
439     lustre = doc.documentElement
440     init_names(lustre)
441     lustre = doc.documentElement
442     if lustre.tagName != "lustre":
443         print "Existing config not valid."
444         sys.exit(1)
445
446     gen = GenConfig(doc)
447     if options.has_key('ost'):
448         add_ost(gen, lustre, options, args)
449     elif options.has_key('node'):
450         add_node(gen, lustre, options, args)
451     elif options.has_key('mtpt'):
452         add_mtpt(gen, lustre, options, args)
453     elif options.has_key('lov'):
454         add_lov(gen, lustre, options, args)
455     elif options.has_key('mds'):
456         add_mds(gen, lustre, options, args)
457     else:
458         print "Missing command"
459         usage()
460
461     if outFile == '-':
462         PrettyPrint(doc)
463     else:
464         PrettyPrint(doc, open(outFile,"w"))
465 if __name__ == "__main__":
466     main()
467
468