modprobe.py:
[nodemanager.git] / net.py
1 #
2 # $Id$
3 #
4
5 """network configuration"""
6
7 # system provided modules
8 import os, string, time, socket, modprobe
9
10 # local modules
11 import sioc, bwlimit, logger, iptables
12
13 def GetSlivers(plc, data):
14     InitInterfaces(plc, data)
15     InitNodeLimit(data)
16     InitI2(plc, data)
17     InitNAT(plc, data)
18
19 def InitNodeLimit(data):
20     # query running network interfaces
21     devs = sioc.gifconf()
22     ips = dict(zip(devs.values(), devs.keys()))
23     macs = {}
24     for dev in devs:
25         macs[sioc.gifhwaddr(dev).lower()] = dev
26
27     # XXX Exempt Internet2 destinations from node bwlimits
28     # bwlimit.exempt_init('Internet2', internet2_ips)
29     for network in data['networks']:
30         # Get interface name preferably from MAC address, falling
31         # back on IP address.
32         hwaddr=network['mac']
33         if hwaddr <> None: hwaddr=hwaddr.lower()
34         if hwaddr in macs:
35             dev = macs[network['mac']]
36         elif network['ip'] in ips:
37             dev = ips[network['ip']]
38         else:
39             logger.log('%s: no such interface with address %s/%s' % (network['hostname'], network['ip'], network['mac']))
40             continue
41
42         # Get current node cap
43         try:
44             old_bwlimit = bwlimit.get_bwcap(dev)
45         except:
46             old_bwlimit = None
47
48         # Get desired node cap
49         if network['bwlimit'] is None or network['bwlimit'] < 0:
50             new_bwlimit = bwlimit.bwmax
51         else:
52             new_bwlimit = network['bwlimit']
53
54         if old_bwlimit != new_bwlimit:
55             # Reinitialize bandwidth limits
56             bwlimit.init(dev, new_bwlimit)
57
58             # XXX This should trigger an rspec refresh in case
59             # some previously invalid sliver bwlimit is now valid
60             # again, or vice-versa.
61
62 def InitI2(plc, data):
63     if "Internet2" in data['groups']:
64         logger.log("This is an Internet2 node.  Setting rules.")
65         i2nodes = []
66         i2nodeids = plc.GetNodeGroups(["Internet2"])[0]['node_ids']
67         for node in plc.GetNodeNetworks({"node_id": i2nodeids}, ["ip"]):
68             # Get the IPs
69             i2nodes.append(node['ip'])
70         # this will create the set if it doesn't already exist
71         # and add IPs that don't exist in the set rather than
72         # just recreateing the set.
73         bwlimit.exempt_init('Internet2', i2nodes)
74         
75         # set the iptables classification rule if it doesnt exist.
76         cmd = '-A POSTROUTING -m set --set Internet2 dst -j CLASSIFY --set-class 0001:2000 --add-mark'
77         rules = []
78         ipt = os.popen("/sbin/iptables-save")
79         for line in ipt.readlines(): rules.append(line.strip(" \n"))
80         ipt.close()
81         if cmd not in rules:
82             logger.verbose("net:  Adding iptables rule for Internet2")
83             os.popen("/sbin/iptables -t mangle " + cmd)
84
85 def InitNAT(plc, data):
86     # query running network interfaces
87     devs = sioc.gifconf()
88     ips = dict(zip(devs.values(), devs.keys()))
89     macs = {}
90     for dev in devs:
91         macs[sioc.gifhwaddr(dev).lower()] = dev
92
93     ipt = iptables.IPTables()
94     for network in data['networks']:
95         # Get interface name preferably from MAC address, falling
96         # back on IP address.
97         hwaddr=network['mac']
98         if hwaddr <> None: hwaddr=hwaddr.lower()
99         if hwaddr in macs:
100             dev = macs[network['mac']]
101         elif network['ip'] in ips:
102             dev = ips[network['ip']]
103         else:
104             logger.log('%s: no such interface with address %s/%s' % (network['hostname'], network['ip'], network['mac']))
105             continue
106
107         try:
108             settings = plc.GetNodeNetworkSettings({'nodenetwork_setting_id': network['nodenetwork_setting_ids']})
109         except:
110             continue
111
112         for setting in settings:
113             if setting['category'].upper() != 'FIREWALL':
114                 continue
115             if setting['name'].upper() == 'EXTERNAL':
116                 # Enable NAT for this interface
117                 ipt.add_ext(dev)
118             elif setting['name'].upper() == 'INTERNAL':
119                 ipt.add_int(dev)
120             elif setting['name'].upper() == 'PF': # XXX Uglier code is hard to find...
121                 for pf in setting['value'].split("\n"):
122                     fields = {}
123                     for field in pf.split(","):
124                         (key, val) = field.split("=", 2)
125                         fields[key] = val
126                     if 'new_dport' not in fields:
127                         fields['new_dport'] = fields['dport']
128                     if 'source' not in fields:
129                         fields['source'] = "0.0.0.0/0"
130                     ipt.add_pf(fields)
131     ipt.commit()
132
133 def InitInterfaces(plc, data):
134     sysconfig = "/etc/sysconfig/network-scripts"
135
136     # query running network interfaces
137     devs = sioc.gifconf()
138     ips = dict(zip(devs.values(), devs.keys()))
139     macs = {}
140     for dev in devs:
141         macs[sioc.gifhwaddr(dev).lower()] = dev
142
143     # assume data['networks'] contains this node's NodeNetworks
144     interfaces = {}
145     interface = 1
146     hostname = data.get('hostname',socket.gethostname())
147     networks = data.get('networks',())
148     failedToGetSettings = False
149     for network in networks:
150         logger.log('interface %d: %s'%(interface,network))
151         logger.log('macs = %s' % macs)
152         logger.log('ips = %s' % ips)
153         # Get interface name preferably from MAC address, falling back
154         # on IP address.
155         hwaddr=network['mac']
156         if hwaddr <> None: hwaddr=hwaddr.lower()
157         if hwaddr in macs:
158             orig_ifname = macs[hwaddr]
159         elif network['ip'] in ips:
160             orig_ifname = ips[network['ip']]
161         else:
162             orig_ifname = None
163
164         if orig_ifname:
165                 logger.log('orig_ifname = %s' % orig_ifname)
166         
167         inter = {}
168         inter['ONBOOT']='yes'
169         inter['USERCTL']='no'
170         if network['mac']:
171             inter['HWADDR'] = network['mac']
172
173         if network['method'] == "static":
174             inter['BOOTPROTO'] = "static"
175             inter['IPADDR'] = network['ip']
176             inter['NETMASK'] = network['netmask']
177
178         elif network['method'] == "dhcp":
179             inter['BOOTPROTO'] = "dhcp"
180             if network['hostname']:
181                 inter['DHCP_HOSTNAME'] = network['hostname']
182             else:
183                 inter['DHCP_HOSTNAME'] = hostname 
184             if not network['is_primary']:
185                 inter['DHCLIENTARGS'] = "-R subnet-mask"
186
187         if len(network['nodenetwork_setting_ids']) > 0:
188             try:
189                 settings = plc.GetNodeNetworkSettings({'nodenetwork_setting_id':
190                                                        network['nodenetwork_setting_ids']})
191             except:
192                 logger.log("FATAL: failed call GetNodeNetworkSettings({'nodenetwork_setting_id':{%s})"% \
193                            network['nodenetwork_setting_ids'])
194                 failedToGetSettings = True
195                 continue # on to the next network
196
197             for setting in settings:
198                 # to explicitly set interface name
199                 settingname = setting['name'].upper()
200                 if settingname in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER'):
201                     inter[settingname]=setting['value']
202                 else:
203                     logger.log("WARNING: ignored setting named %s"%setting['name'])
204
205         # support aliases to interfaces either by name or HWADDR
206         if 'ALIAS' in inter:
207             if 'HWADDR' in inter:
208                 hwaddr = inter['HWADDR'].lower()
209                 del inter['HWADDR']
210                 if hwaddr in macs:
211                     hwifname = macs[hwaddr]
212                     if ('IFNAME' in inter) and inter['IFNAME'] <> hwifname:
213                         logger.log("WARNING: alias ifname (%s) and hwaddr ifname (%s) do not match"%\
214                                        (inter['IFNAME'],hwifname))
215                         inter['IFNAME'] = hwifname
216                 else:
217                     logger.log('WARNING: mac addr %s for alias not found' %(hwaddr,alias))
218
219             if 'IFNAME' in inter:
220                 # stupid RH /etc/sysconfig/network-scripts/ifup-aliases:new_interface()
221                 # checks if the "$DEVNUM" only consists of '^[0-9A-Za-z_]*$'. Need to make
222                 # our aliases compliant.
223                 parts = inter['ALIAS'].split('_')
224                 isValid=True
225                 for part in parts:
226                     isValid=isValid and part.isalnum()
227
228                 if isValid:
229                     interfaces["%s:%s" % (inter['IFNAME'],inter['ALIAS'])] = inter 
230                 else:
231                     logger.log("WARNING: interface alias (%s) not a valid string for RH ifup-aliases"% inter['ALIAS'])
232             else:
233                 logger.log("WARNING: interface alias (%s) not matched to an interface"% inter['ALIAS'])
234             interface -= 1
235         else:
236             if ('IFNAME' not in inter) and not orig_ifname:
237                 ifname="eth%d" % (interface-1)
238                 # should check if $ifname is an eth already defines
239                 if os.path.exists("%s/ifcfg-%s"%(sysconfig,ifname)):
240                     logger.log("WARNING: possibly blowing away %s configuration"%ifname)
241             else:
242                 if ('IFNAME' not in inter) and orig_ifname:
243                     ifname = orig_ifname
244                 else:
245                     ifname = inter['IFNAME']
246                 interface -= 1
247             interfaces[ifname] = inter
248                 
249     m = modprobe.Modprobe()
250     m.input("/etc/modprobe.conf")
251     for (dev, inter) in interfaces.iteritems():
252         # get the driver string "moduleName option1=a option2=b"
253         driver=inter.get('DRIVER','')
254         if driver <> '':
255             driver=driver.split()
256             kernelmodule=driver[0]
257             m.aliasset(dev,kernelmodule)
258             options=" ".join(driver[1:])
259             if options <> '':
260                 m.optionsset(dev,options)
261     m.output("/etc/modprobe.conf")
262
263     # clean up after any ifcfg-$dev script that's no longer listed as
264     # part of the NodeNetworks associated with this node
265
266     # list all network-scripts
267     files = os.listdir(sysconfig)
268
269     # filter out the ifcfg-* files
270     ifcfgs=[]
271     for f in files:
272         if f.find("ifcfg-") == 0:
273             ifcfgs.append(f)
274
275     # remove loopback (lo) from ifcfgs list
276     lo = "ifcfg-lo"
277     if lo in ifcfgs: ifcfgs.remove(lo)
278
279     # remove known devices from icfgs list
280     for (dev, inter) in interfaces.iteritems():
281         ifcfg = 'ifcfg-'+dev
282         if ifcfg in ifcfgs: ifcfgs.remove(ifcfg)
283
284     # delete the remaining ifcfgs from 
285     deletedSomething = False
286
287     if not failedToGetSettings:
288         for ifcfg in ifcfgs:
289             dev = ifcfg[len('ifcfg-'):]
290             path = "%s/ifcfg-%s" % (sysconfig,dev)
291             logger.log("removing %s %s"%(dev,path))
292             ifdown = os.popen("/sbin/ifdown %s" % dev)
293             ifdown.close()
294             deletedSomething=True
295             os.unlink(path)
296
297     # wait a bit for the one or more ifdowns to have taken effect
298     if deletedSomething:
299         time.sleep(2)
300
301     # Process ifcg-$dev changes / additions
302     newdevs = []
303     for (dev, inter) in interfaces.iteritems():
304         tmpnam = os.tmpnam()
305         f = file(tmpnam, "w")
306         f.write("# Autogenerated by NodeManager/net.py.... do not edit!\n")
307         if 'DRIVER' in inter:
308             f.write("# using %s driver for device %s\n" % (inter['DRIVER'],dev))
309         f.write('DEVICE="%s"\n' % dev)
310         
311         # print the configuration values
312         for (key, val) in inter.iteritems():
313             if key not in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER'):
314                 f.write('%s="%s"\n' % (key, val))
315
316         # print the configuration specific option values (if any)
317         if 'CFGOPTIONS' in inter:
318             cfgoptions = inter['CFGOPTIONS']
319             f.write('#CFGOPTIONS are %s\n' % cfgoptions)
320             for cfgoption in cfgoptions.split():
321                 key,val = cfgoption.split('=')
322                 key=key.strip()
323                 key=key.upper()
324                 val=val.strip()
325                 f.write('%s="%s"\n' % (key,val))
326         f.close()
327
328         # compare whether two files are the same
329         def comparefiles(a,b):
330             try:
331                 logger.log("comparing %s with %s" % (a,b))
332                 if not os.path.exists(a): return False
333                 fb = open(a)
334                 buf_a = fb.read()
335                 fb.close()
336
337                 if not os.path.exists(b): return False
338                 fb = open(b)
339                 buf_b = fb.read()
340                 fb.close()
341
342                 return buf_a == buf_b
343             except IOError, e:
344                 return False
345
346         path = "%s/ifcfg-%s" % (sysconfig,dev)
347         if not os.path.exists(path):
348             logger.log('adding configuration for %s' % dev)
349             # add ifcfg-$dev configuration file
350             os.rename(tmpnam,path)
351             os.chmod(path,0644)
352             newdevs.append(dev)
353             
354         elif not comparefiles(tmpnam,path):
355             logger.log('Configuration change for %s' % dev)
356             logger.log('ifdown %s' % dev)
357             # invoke ifdown for the old configuration
358             p = os.popen("/sbin/ifdown %s" % dev)
359             p.close()
360             # wait a few secs for ifdown to complete
361             time.sleep(2)
362
363             logger.log('replacing configuration for %s' % dev)
364             # replace ifcfg-$dev configuration file
365             os.rename(tmpnam,path)
366             os.chmod(path,0644)
367             newdevs.append(dev)
368         else:
369             # tmpnam & path are identical
370             os.unlink(tmpnam)
371
372     for dev in newdevs:
373         cfgvariables = {}
374         fb = file("%s/ifcfg-%s"%(sysconfig,dev),"r")
375         for line in fb.readlines():
376             parts = line.split()
377             if parts[0][0]=="#":continue
378             if parts[0].find('='):
379                 name,value = parts[0].split('=')
380                 # clean up name & value
381                 name = name.strip()
382                 value = value.strip()
383                 value = value.strip("'")
384                 value = value.strip('"')
385                 cfgvariables[name]=value
386         fb.close()
387
388         def getvar(name):
389             if name in cfgvariables:
390                 value=cfgvariables[name]
391                 value = value.lower()
392                 return value
393             return ''
394
395         # skip over device configs with ONBOOT=no
396         if getvar("ONBOOT") == 'no': continue
397
398         # don't bring up slave devices, the network scripts will
399         # handle those correctly
400         if getvar("SLAVE") == 'yes': continue
401
402         logger.log('bringing up %s' % dev)
403         p = os.popen("/sbin/ifup %s" % dev)
404         # check for failure?
405         p.close()
406
407 def start(options, config):
408     pass