1410a0eb1f08b492472834046123fd1b770ff9d6
[pyplnet.git] / plnet.py
1 #!/usr/bin/python /usr/bin/plcsh
2 # $Id$
3
4 import os
5 import socket
6 import time
7 import tempfile
8 import errno
9
10 import sioc
11 import modprobe
12
13 def InitInterfaces(logger, plc, data, root="", files_only=False, program="NodeManager"):
14     sysconfig = "%s/etc/sysconfig/network-scripts" % root
15     try:
16         os.makedirs(sysconfig)
17     except OSError, e:
18         if e.errno != errno.EEXIST:
19             raise e
20
21     # query running network interfaces
22     devs = sioc.gifconf()
23     ips = dict(zip(devs.values(), devs.keys()))
24     macs = {}
25     for dev in devs:
26         macs[sioc.gifhwaddr(dev).lower()] = dev
27
28     # assume data['networks'] contains this node's NodeNetworks
29     interfaces = {}
30     interface = 1
31     hostname = data.get('hostname',socket.gethostname())
32     gateway = None
33     networks = data['networks']
34     failedToGetSettings = False
35     for network in networks:
36         logger.verbose('net:InitInterfaces interface %d: %s'%(interface,network))
37         logger.verbose('net:InitInterfaces macs = %s' % macs)
38         logger.verbose('net:InitInterfaces ips = %s' % ips)
39         # Get interface name preferably from MAC address, falling back
40         # on IP address.
41         hwaddr=network['mac']
42         if hwaddr <> None: hwaddr=hwaddr.lower()
43         if hwaddr in macs:
44             orig_ifname = macs[hwaddr]
45         elif network['ip'] in ips:
46             orig_ifname = ips[network['ip']]
47         else:
48             orig_ifname = None
49
50         if orig_ifname:
51                 logger.verbose('net:InitInterfaces orig_ifname = %s' % orig_ifname)
52         
53         inter = {}
54         inter['ONBOOT']='yes'
55         inter['USERCTL']='no'
56         if network['mac']:
57             inter['HWADDR'] = network['mac']
58         if network['is_primary']:
59             inter['PRIMARY']='yes'
60
61         if network['method'] == "static":
62             inter['BOOTPROTO'] = "static"
63             inter['IPADDR'] = network['ip']
64             inter['NETMASK'] = network['netmask']
65             if network['is_primary']:
66                 gateway = network['gateway']
67
68         elif network['method'] == "dhcp":
69             inter['BOOTPROTO'] = "dhcp"
70             inter['PERSISTENT_DHCLIENT'] = "yes"
71             if network['hostname']:
72                 inter['DHCP_HOSTNAME'] = network['hostname']
73             else:
74                 inter['DHCP_HOSTNAME'] = hostname 
75             if not network['is_primary']:
76                 inter['DHCLIENTARGS'] = "-R subnet-mask"
77
78         if len(network['interface_tag_ids']) > 0:
79             try:
80                 settings = plc.GetInterfaceTags({'interface_tag_id':
81                                                  network['interface_tag_ids']})
82             except:
83                 logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({'interface_tag_id':{%s})"% \
84                            network['interface_tag_ids'])
85                 failedToGetSettings = True
86                 continue # on to the next network
87
88             for setting in settings:
89                 # to explicitly set interface name
90                 settingname = setting['name'].upper()
91                 if settingname in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER'):
92                     inter[settingname]=setting['value']
93                 else:
94                     logger.log("net:InitInterfaces WARNING: ignored setting named %s"%setting['name'])
95
96         # support aliases to interfaces either by name or HWADDR
97         if 'ALIAS' in inter:
98             if 'HWADDR' in inter:
99                 hwaddr = inter['HWADDR'].lower()
100                 del inter['HWADDR']
101                 if hwaddr in macs:
102                     hwifname = macs[hwaddr]
103                     if ('IFNAME' in inter) and inter['IFNAME'] <> hwifname:
104                         logger.log("net:InitInterfaces WARNING: alias ifname (%s) and hwaddr ifname (%s) do not match"%\
105                                        (inter['IFNAME'],hwifname))
106                         inter['IFNAME'] = hwifname
107                 else:
108                     logger.log('net:InitInterfaces WARNING: mac addr %s for alias not found' %(hwaddr,alias))
109
110             if 'IFNAME' in inter:
111                 # stupid RH /etc/sysconfig/network-scripts/ifup-aliases:new_interface()
112                 # checks if the "$DEVNUM" only consists of '^[0-9A-Za-z_]*$'. Need to make
113                 # our aliases compliant.
114                 parts = inter['ALIAS'].split('_')
115                 isValid=True
116                 for part in parts:
117                     isValid=isValid and part.isalnum()
118
119                 if isValid:
120                     interfaces["%s:%s" % (inter['IFNAME'],inter['ALIAS'])] = inter 
121                 else:
122                     logger.log("net:InitInterfaces WARNING: interface alias (%s) not a valid string for RH ifup-aliases"% inter['ALIAS'])
123             else:
124                 logger.log("net:InitInterfaces WARNING: interface alias (%s) not matched to an interface"% inter['ALIAS'])
125             interface -= 1
126         else:
127             if ('IFNAME' not in inter) and not orig_ifname:
128                 ifname="eth%d" % (interface-1)
129                 # should check if $ifname is an eth already defines
130                 if os.path.exists("%s/ifcfg-%s"%(sysconfig,ifname)):
131                     logger.log("net:InitInterfaces WARNING: possibly blowing away %s configuration"%ifname)
132             else:
133                 if ('IFNAME' not in inter) and orig_ifname:
134                     ifname = orig_ifname
135                 else:
136                     ifname = inter['IFNAME']
137                 interface -= 1
138             interfaces[ifname] = inter
139                 
140     m = modprobe.Modprobe()
141     try:
142         m.input("%s/etc/modprobe.conf" % root, program)
143     except:
144         pass
145     for (dev, inter) in interfaces.iteritems():
146         # get the driver string "moduleName option1=a option2=b"
147         driver=inter.get('DRIVER','')
148         if driver <> '':
149             driver=driver.split()
150             kernelmodule=driver[0]
151             m.aliasset(dev,kernelmodule)
152             options=" ".join(driver[1:])
153             if options <> '':
154                 m.optionsset(dev,options)
155     m.output("%s/etc/modprobe.conf" % root)
156
157     # clean up after any ifcfg-$dev script that's no longer listed as
158     # part of the NodeNetworks associated with this node
159
160     # list all network-scripts
161     files = os.listdir(sysconfig)
162
163     # filter out the ifcfg-* files
164     ifcfgs=[]
165     for f in files:
166         if f.find("ifcfg-") == 0:
167             ifcfgs.append(f)
168
169     # remove loopback (lo) from ifcfgs list
170     lo = "ifcfg-lo"
171     if lo in ifcfgs: ifcfgs.remove(lo)
172
173     # remove known devices from icfgs list
174     for (dev, inter) in interfaces.iteritems():
175         ifcfg = 'ifcfg-'+dev
176         if ifcfg in ifcfgs: ifcfgs.remove(ifcfg)
177
178     # delete the remaining ifcfgs from 
179     deletedSomething = False
180
181     if not failedToGetSettings:
182         for ifcfg in ifcfgs:
183             dev = ifcfg[len('ifcfg-'):]
184             path = "%s/ifcfg-%s" % (sysconfig,dev)
185             if not files_only:
186                 logger.verbose("net:InitInterfaces removing %s %s"%(dev,path))
187                 os.system("/sbin/ifdown %s" % dev)
188             deletedSomething=True
189             os.unlink(path)
190
191     # wait a bit for the one or more ifdowns to have taken effect
192     if deletedSomething:
193         time.sleep(2)
194
195     # Write network configuration file
196     networkconf = file("%s/etc/sysconfig/network" % root, "w")
197     networkconf.write("NETWORKING=yes\nHOSTNAME=%s\n" % hostname)
198     if gateway is not None:
199         networkconf.write("GATEWAY=%s\n" % gateway)
200     networkconf.close()
201
202     # Process ifcfg-$dev changes / additions
203     newdevs = []
204     for (dev, inter) in interfaces.iteritems():
205         (fd, tmpnam) = tempfile.mkstemp(dir=sysconfig)
206         f = os.fdopen(fd, "w")
207         f.write("# Autogenerated by pyplnet... do not edit!\n")
208         if 'DRIVER' in inter:
209             f.write("# using %s driver for device %s\n" % (inter['DRIVER'],dev))
210         f.write('DEVICE=%s\n' % dev)
211         
212         # print the configuration values
213         for (key, val) in inter.iteritems():
214             if key not in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER'):
215                 f.write('%s=%s\n' % (key, val))
216
217         # print the configuration specific option values (if any)
218         if 'CFGOPTIONS' in inter:
219             cfgoptions = inter['CFGOPTIONS']
220             f.write('#CFGOPTIONS are %s\n' % cfgoptions)
221             for cfgoption in cfgoptions.split():
222                 key,val = cfgoption.split('=')
223                 key=key.strip()
224                 key=key.upper()
225                 val=val.strip()
226                 f.write('%s="%s"\n' % (key,val))
227         f.close()
228
229         # compare whether two files are the same
230         def comparefiles(a,b):
231             try:
232                 logger.verbose("net:InitInterfaces comparing %s with %s" % (a,b))
233                 if not os.path.exists(a): return False
234                 fb = open(a)
235                 buf_a = fb.read()
236                 fb.close()
237
238                 if not os.path.exists(b): return False
239                 fb = open(b)
240                 buf_b = fb.read()
241                 fb.close()
242
243                 return buf_a == buf_b
244             except IOError, e:
245                 return False
246
247         path = "%s/ifcfg-%s" % (sysconfig,dev)
248         if not os.path.exists(path):
249             logger.verbose('net:InitInterfaces adding configuration for %s' % dev)
250             # add ifcfg-$dev configuration file
251             os.rename(tmpnam,path)
252             os.chmod(path,0644)
253             newdevs.append(dev)
254             
255         elif not comparefiles(tmpnam,path):
256             logger.verbose('net:InitInterfaces Configuration change for %s' % dev)
257             if not files_only:
258                 logger.verbose('net:InitInterfaces ifdown %s' % dev)
259                 # invoke ifdown for the old configuration
260                 os.system("/sbin/ifdown %s" % dev)
261                 # wait a few secs for ifdown to complete
262                 time.sleep(2)
263
264             logger.log('replacing configuration for %s' % dev)
265             # replace ifcfg-$dev configuration file
266             os.rename(tmpnam,path)
267             os.chmod(path,0644)
268             newdevs.append(dev)
269         else:
270             # tmpnam & path are identical
271             os.unlink(tmpnam)
272
273     for dev in newdevs:
274         cfgvariables = {}
275         fb = file("%s/ifcfg-%s"%(sysconfig,dev),"r")
276         for line in fb.readlines():
277             parts = line.split()
278             if parts[0][0]=="#":continue
279             if parts[0].find('='):
280                 name,value = parts[0].split('=')
281                 # clean up name & value
282                 name = name.strip()
283                 value = value.strip()
284                 value = value.strip("'")
285                 value = value.strip('"')
286                 cfgvariables[name]=value
287         fb.close()
288
289         def getvar(name):
290             if name in cfgvariables:
291                 value=cfgvariables[name]
292                 value = value.lower()
293                 return value
294             return ''
295
296         # skip over device configs with ONBOOT=no
297         if getvar("ONBOOT") == 'no': continue
298
299         # don't bring up slave devices, the network scripts will
300         # handle those correctly
301         if getvar("SLAVE") == 'yes': continue
302
303         if not files_only:
304             logger.verbose('net:InitInterfaces bringing up %s' % dev)
305             os.system("/sbin/ifup %s" % dev)
306
307 if __name__ == "__main__":
308     import optparse
309     import sys
310
311     parser = optparse.OptionParser(usage="plnet [-v] [-f] [-p <program>] -r root node_id")
312     parser.add_option("-v", "--verbose", action="store_true", dest="verbose")
313     parser.add_option("-r", "--root", action="store", type="string",
314                       dest="root", default=None)
315     parser.add_option("-f", "--files-only", action="store_true",
316                       dest="files_only")
317     parser.add_option("-p", "--program", action="store", type="string",
318                       dest="program", default="plnet")
319     (options, args) = parser.parse_args()
320     if len(args) != 1 or options.root is None:
321         print >>sys.stderr, "Missing root or node_id"
322         parser.print_help()
323         sys.exit(1)
324
325     node = shell.GetNodes({'node_id': [int(args[0])]})
326     networks = shell.GetInterfaces({'interface_id': node[0]['interface_ids']})
327
328     data = {'hostname': node[0]['hostname'], 'networks': networks}
329     class logger:
330         def __init__(self, verbose):
331             self.verbosity = verbose
332         def log(self, msg, loglevel=2):
333             if self.verbosity:
334                 print msg
335         def verbose(self, msg):
336             self.log(msg, 1)
337     l = logger(options.verbose)
338     InitInterfaces(l, shell, data, options.root, options.files_only)