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