Whamcloud - gitweb
- add copyright, license, and attribution
[fs/lustre-release.git] / lustre / utils / lconf
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 # lconf - lustre configuration tool
22 #
23 # lconf is the main driver script for starting and stopping
24 # lustre filesystem services.
25 #
26 # Based in part on the XML obdctl modifications done by Brian Behlendorf 
27
28 import sys, getopt
29 import string, os, stat
30 import re
31 import xml.dom.minidom
32
33 def fixme():
34     raise RuntimeError, 'This feature not implmemented yet.'
35
36 def panic(msg):
37     raise RuntimeError, msg
38
39 #
40 # Maximum number of devices to search for.
41 # (the /dev/loop* nodes need to be created beforehand)
42 MAX_LOOP_DEVICES = 256
43
44
45 def usage():
46     print """usage: lconf --ldap server | config.xml
47
48 config.xml          Lustre configuration in xml format.
49 --ldap server       LDAP server with lustre config database
50
51 Options:
52 --reformat          Reformat all devices (will confirm)
53 --dev="lustre src"  Base directory of lustre sources. Used to search
54                     for modules.
55 --portals=src       Portals source 
56 --makeldiff         Translate xml source to LDIFF 
57 --cleanup           Cleans up config. (Shutdown)
58 --iam myname        ??
59
60 (SCRIPT STILL UNDER DEVELOPMENT, MOST FUNCTIONALITY UNIMPLEMENTED)
61 """
62
63 # ============================================================
64 # Various system-level functions
65 # (ideally moved to their own module)
66
67 # Run a command and return the output and status.
68 # stderr is sent to /dev/null, could use popen3 to
69 # save it if necessary
70 def run(*args):
71     cmd = string.join(map(str,args))
72     print "+", cmd
73     f = os.popen(cmd + ' 2> /dev/null')
74     out = f.readlines()
75     ret = f.close()
76     if ret:
77         ret = ret >> 8
78     else:
79         ret = 0
80     return (out, ret)
81
82 # is the path a block device?
83 def is_block(path):
84     s = ()
85     try:
86         s =  os.stat(path)
87     except OSError:
88         return 0
89     return stat.S_ISBLK(s[stat.ST_MODE])
90
91 # build fs according to type
92 # fixme: dangerous
93 def mkfs(fstype, dev):
94     if(fstype == 'ext3'):
95         mkfs = 'mkfs.ext2 -j'
96     elif (fstype == 'extN'):
97         mkfs = 'mkfs.ext2 -j'
98     else:
99         print 'unsupported fs type: ', fstype
100     if not is_block(dev):
101         force = '-F'
102     else:
103         force = ''
104     run (mkfs, force, dev)
105
106 # some systems use /dev/loopN, some /dev/loop/N
107 def loop_base():
108     import re
109     loop = '/dev/loop'
110     if not os.access(loop + str(0), os.R_OK):
111         loop = loop + '/'
112         if not os.access(loop + str(0), os.R_OK):
113             panic ("can't access loop devices")
114     return loop
115     
116 # find loop device assigned to thefile
117 def find_loop(file):
118     loop = loop_base()
119     for n in xrange(0, MAX_LOOP_DEVICES):
120         dev = loop + str(n)
121         if os.access(dev, os.R_OK):
122             (out, stat) = run('losetup', dev)
123             if (stat == 0 ):
124                 m = re.search(r'\((.*)\)', out[0])
125                 if m and file == m.group(1):
126                     return dev
127         else:
128             break
129     return ''
130
131 # create file if necessary and assign the first free loop device
132 def init_loop(file, size, fstype):
133     dev = find_loop(file)
134     if dev:
135         print 'WARNING file:', file, 'already mapped to', dev
136         return dev
137     if not os.access(file, os.R_OK | os.W_OK):
138         run("dd if=/dev/zero bs=1k count=0 seek=%d of=%s" %(size,  file))
139     loop = loop_base()
140     # find next free loop
141     for n in xrange(0, MAX_LOOP_DEVICES):
142         dev = loop + str(n)
143         if os.access(dev, os.R_OK):
144             (out, stat) = run('losetup', dev)
145             if (stat):
146                 run('losetup', dev, file)
147                 return dev
148         else:
149             print "out of loop devices"
150             return ''
151     print "out of loop devices"
152     return ''
153
154 # undo loop assignment
155 def clean_loop(file):
156     dev = find_loop(file)
157     if dev:
158         run('losetup -d', dev)
159
160 # ============================================================
161 # Functions to prepare the various objects
162
163 def prepare_ldlm(node):
164     print 'prepare ldlm'
165
166 def prepare_network(node):
167     print 'prepare network'
168
169 # need to check /proc/mounts and /etc/mtab before
170 # formatting anything.
171 # FIXME: check if device is already formatted.
172 def prepare_obd(obd):
173     obdname = obd.getAttribute('name')
174     (dev, size, fstype, format) = getDeviceInfo(obd)
175     print "OBD: ", dev, size, fstype, format
176 ##     if not is_block(dev):
177 ##         dev = init_loop(dev, size, fstype)
178 ##     if (format == 'yes'):
179 ##         mkfs(fstype, dev)
180
181 def prepare_ost(node):
182     print 'prepare ost'
183
184 def prepare_mds(node):
185     print 'prepare mds'
186
187 def prepare_osc(node):
188     print 'prepare osc'
189
190 def prepare_mdc(node):
191     print 'prepare mdc'
192
193 def prepare_mountpoint(node):
194     print 'prepare mtpt'
195
196 # ============================================================
197 # XML processing 
198
199 # extract device attributes for an obd
200 def getDeviceInfo(obd):
201     dev = obd.getElementsByTagName('device')[0]
202     dev.normalize();
203     try:
204         size = int(dev.getAttribute('size'))
205     except ValueError:
206         size = 0
207
208     fstype = getText(obd, 'fstype')
209     format = getText(obd, 'autoformat')
210     return (dev.firstChild.data, size, fstype, format)
211     
212 # Get the text content from the first matching child
213 def getText(node, tag):
214     node = node.getElementsByTagName(tag)[0]
215     node.normalize()
216     return node.firstChild.data
217
218 # Recusively search for a particular node by uuid
219 def getByUUID(node, uuid):
220     fixme()
221     for n in node.childNodes:
222         if n.nodeType == n.ELEMENT_NODE:
223             if getUUID(n) == uuid:
224                 return n
225     return None
226
227 # Recusively search for a particular node by name
228 def getByName(node, name):
229     for n in node.childNodes:
230         # this service_id check is ugly. need some other way to
231         # differentiate between definitions and references
232         if n.nodeType == n.ELEMENT_NODE and n.nodeName != 'service_id':
233             if getName(n) == name:
234                 return n
235             else:
236                 n = getByName(n, name)
237                 if n: return n
238                     
239     return None
240
241 # Get name attribute of node
242 def getName(node):
243     return node.getAttribute('name')
244
245 # Get name attribute of node
246 def getUUID(node):
247     return node.getAttribute('uuid')
248
249 # the tag name is the service type
250 # fixme: this should do some checks to make sure the node is a service
251 def getServiceType(node):
252     return node.nodeName
253
254 #
255 # determine what "level" a particular node is at.
256 # the order of iniitailization is based on level.  objects
257 # are assigned a level based on type:
258 #  net,devices:1, obd, mdd:2  mds,ost:3 osc,mdc:4 mounts:5
259 def getServiceLevel(node):
260     type = getServiceType(node)
261     if type in ('network', 'device', 'ldlm'):
262         return 1
263     elif type in ('obd', 'mdd'):
264         return 2
265     elif type in ('mds','ost'):
266         return 3
267     elif type in ('mdc','osc'):
268         return 4
269     elif type in ('mountpoint',):
270         return 5
271     return 0
272
273 #
274 # return list of services in a profile. list is a list of tuples
275 # [(level, node),]
276 def getServices(lustreNode, profileNode):
277     list = []
278     for n in profileNode.childNodes:
279         if n.nodeType == n.ELEMENT_NODE:
280             servNode = getByName(lustreNode, getName(n))
281             if not servNode:
282                 panic('service not found: ' + servNode)
283             level = getServiceLevel(servNode)
284             list.append((level, servNode))
285     list.sort()
286     return list
287
288 def getProfile(lustreNode, profile):
289     profList = lustreNode.getElementsByTagName('profile')
290     for prof in profList:
291         if getName(prof) == profile:
292             return prof
293     return None
294
295 #
296 # Start a service.
297 def startService(node):
298     type = getServiceType(node)
299     print 'Starting service:', type, getName(node), getUUID(node)
300     # there must be a more dynamic way of doing this...
301     if type == 'ldlm':
302         prepare_ldlm(node)
303     elif type == 'network':
304         prepare_network(node)
305     elif type == 'obd':
306         prepare_obd(node)
307     elif type == 'ost':
308         prepare_ost(node)
309     elif type == 'mds':
310         prepare_mds(node)
311     elif type == 'osc':
312         prepare_osc(node)
313     elif type == 'mdc':
314         prepare_mdc(node)
315     elif type == 'mountpoint':
316         prepare_mountpoint(node)
317
318 #
319 # Prepare the system to run lustre using a particular profile
320 # in a the configuration. 
321 #  * load & the modules
322 #  * setup networking for the current node
323 #  * make sure partitions are in place and prepared
324 #  * initialize devices with lctl
325 # Levels is important, and needs to be enforced.
326 def startProfile(lustreNode, profile):
327     profileNode = getProfile(lustreNode, profile)
328     if not profileNode:
329         print "profile:", profile, "not found."
330         sys.exit(1)
331     services = getServices(lustreNode, profileNode)
332     for s in services:
333         startService(s[1])
334
335     #obdlist = lustreNode.getElementsByTagName("obd")
336     #for obd in obdlist:
337     #    prepareDevice(obd)
338
339 #
340 # Initialize or shutdown lustre according to a configuration file
341 #   * prepare the system for lustre
342 #   * configure devices with lctl
343 # Shutdown does steps in reverse
344 #
345 def main():
346     dom = xml.dom.minidom.parse(sys.argv[1])
347     startProfile(dom.childNodes[0], 'local-profile')
348     
349
350 # try a different traceback style. (dare ya to try this in java)
351 def my_traceback(file=None):
352     """Print the list of tuples as returned by extract_tb() or
353     extract_stack() as a formatted stack trace to the given file."""
354     import traceback
355     (t,v, tb) = sys.exc_info()
356     list = traceback.extract_tb(tb)
357     if not file:
358         file = sys.stderr
359     for filename, lineno, name, line in list:
360         if line:
361             print '%s:%04d %-14s %s' % (filename,lineno, name, line.strip())
362         else:
363             print '%s:%04d %s' % (filename,lineno, name)
364     print '%s: %s' % (t, v)
365
366 if __name__ == "__main__":
367     try:
368         main()
369     except:
370         my_traceback()