1 #!/usr/bin/python3 /usr/bin/plcsh
17 def ovs_check(logger):
18 """ Return True if openvswitch is running, False otherwise. Try restarting
21 rc = os.system("service openvswitch status")
24 logger.log("net: restarting openvswitch")
25 rc = os.system("service openvswitch restart")
26 rc = os.system("service openvswitch status")
29 logger.log("net: failed to restart openvswitch")
32 def InitInterfaces(logger, plc, data, root="",
33 files_only=False, program="NodeManager"):
36 sysconfig = "{}/etc/sysconfig/network-scripts".format(root)
38 os.makedirs(sysconfig)
40 if e.errno != errno.EEXIST:
43 # query running network interfaces
45 ips = {ip: interface for (ip, interface) in devs.items()}
48 macs[sioc.gifhwaddr(dev).lower()] = dev
52 hostname = data.get('hostname', socket.gethostname())
54 # assume data['interfaces'] contains this node's Interfaces
55 # can cope with 4.3 ('networks') or 5.0 ('interfaces')
57 interfaces = data['interfaces']
59 interfaces = data['networks']
60 failedToGetSettings = False
62 # NOTE: GetInterfaces/NodeNetworks does not necessarily order the interfaces
63 # returned. Because 'interface' is decremented as each interface is processed,
64 # by the time is_primary=True (primary) interface is reached, the device
65 # "eth<interface>" is not eth0. But, something like eth-4, or eth-12.
66 # This code sorts the interfaces, placing is_primary=True interfaces first.
67 # There is a lot of room for improvement to how this
68 # script handles interfaces and how it chooses the primary interface.
69 # NOTE: by sorting on 'is_primary' and then reversing (since False is sorted
70 # before True) all 'is_primary' interfaces are at the beginning of the list.
71 interfaces.sort(key=lambda d: d['is_primary'], reverse=True)
73 # The names of the bridge devices
76 for interface in interfaces:
77 logger.verbose('net:InitInterfaces interface {}: {}'.format(device_id, interface))
78 logger.verbose('net:InitInterfaces macs = {}'.format(macs))
79 logger.verbose('net:InitInterfaces ips = {}'.format(ips))
80 # Get interface name preferably from MAC address, falling back
82 hwaddr=interface['mac']
83 if hwaddr != None: hwaddr=hwaddr.lower()
85 orig_ifname = macs[hwaddr]
86 elif interface['ip'] in ips:
87 orig_ifname = ips[interface['ip']]
92 logger.verbose('net:InitInterfaces orig_ifname = {}'.format(orig_ifname))
94 details = prepDetails(interface, hostname)
96 if interface['is_primary']:
97 gateway = interface['gateway']
99 if 'interface_tag_ids' in interface:
101 interface_tag_ids = "interface_tag_ids"
102 interface_tag_id = "interface_tag_id"
106 interface_tag_ids = "nodenetwork_setting_ids"
107 interface_tag_id = "nodenetwork_setting_id"
110 if len(interface[interface_tag_ids]) > 0:
112 filter = { interface_tag_id : interface[interface_tag_ids] }
114 settings = plc.GetInterfaceTags(filter)
116 settings = plc.GetNodeNetworkSettings(filter)
118 logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({})"
120 failedToGetSettings = True
121 continue # on to the next interface
123 for setting in settings:
124 settingname = setting[name_key].upper()
125 if ((settingname in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER','VLAN','TYPE','DEVICETYPE')) or \
126 (re.search('^IPADDR[0-9]+$|^NETMASK[0-9]+$', settingname))):
127 # TD: Added match for secondary IPv4 configuration.
128 details[settingname]=setting['value']
129 # IPv6 support on IPv4 interface
130 elif settingname in ('IPV6ADDR','IPV6_DEFAULTGW','IPV6ADDR_SECONDARIES', 'IPV6_AUTOCONF'):
131 # TD: Added IPV6_AUTOCONF.
132 details[settingname]=setting['value']
133 details['IPV6INIT']='yes'
135 elif settingname in \
136 [ "MODE", "ESSID", "NW", "FREQ", "CHANNEL", "SENS", "RATE",
137 "KEY", "KEY1", "KEY2", "KEY3", "KEY4", "SECURITYMODE",
138 "IWCONFIG", "IWPRIV" ] :
139 details [settingname] = setting['value']
140 details ['TYPE']='Wireless'
142 elif settingname in [ 'BRIDGE' ]:
143 details['BRIDGE'] = setting['value']
144 elif settingname in [ 'OVS_BRIDGE' ]:
145 # If openvswitch isn't running, then we'll lose network
146 # connectivity when we reconfigure eth0.
147 if ovs_check(logger):
148 details['OVS_BRIDGE'] = setting['value']
149 details['TYPE'] = "OVSPort"
150 details['DEVICETYPE'] = "ovs"
152 logger.log("net:InitInterfaces ERROR: OVS_BRIDGE specified, yet ovs is not running")
154 logger.log("net:InitInterfaces WARNING: ignored setting named {}"
155 .format(setting[name_key]))
157 # support aliases to interfaces either by name or HWADDR
158 if 'ALIAS' in details:
159 if 'HWADDR' in details:
160 hwaddr = details['HWADDR'].lower()
161 del details['HWADDR']
163 hwifname = macs[hwaddr]
164 if ('IFNAME' in details) and details['IFNAME'] != hwifname:
165 logger.log("net:InitInterfaces WARNING: alias ifname ({}) and hwaddr ifname ({}) do not match"
166 .format(details['IFNAME'], hwifname))
167 details['IFNAME'] = hwifname
169 logger.log('net:InitInterfaces WARNING: mac addr {} for alias not found'.format(hwaddr))
171 if 'IFNAME' in details:
172 # stupid RH /etc/sysconfig/network-scripts/ifup-aliases:new_interface()
173 # checks if the "$DEVNUM" only consists of '^[0-9A-Za-z_]*$'. Need to make
174 # our aliases compliant.
175 parts = details['ALIAS'].split('_')
178 isValid=isValid and part.isalnum()
181 devices_map["{}:{}".format(details['IFNAME'], details['ALIAS'])] = details
183 logger.log("net:InitInterfaces WARNING: interface alias ({}) not a valid string for RH ifup-aliases"
184 .format(details['ALIAS']))
186 logger.log("net:InitInterfaces WARNING: interface alias ({}) not matched to an interface"
187 .format(details['ALIAS']))
189 elif ('BRIDGE' in details or 'OVS_BRIDGE' in details) and 'IFNAME' in details:
190 # The bridge inherits the mac of the first attached interface.
191 ifname = details['IFNAME']
193 if 'BRIDGE' in details:
194 bridgeName = details['BRIDGE']
195 bridgeType = 'Bridge'
197 bridgeName = details['OVS_BRIDGE']
198 bridgeType = 'OVSBridge'
200 logger.log('net:InitInterfaces: {} detected. Adding {} to devices_map'
201 .format(bridgeType, ifname))
202 devices_map[ifname] = removeBridgedIfaceDetails(details)
204 logger.log('net:InitInterfaces: Adding {} {}'.format(bridgeType, bridgeName))
205 bridgeDetails = prepDetails(interface)
207 # TD: Add configuration for secondary IPv4 and IPv6 addresses to the bridge.
208 if len(interface[interface_tag_ids]) > 0:
209 filter = { interface_tag_id : interface[interface_tag_ids] }
212 settings = plc.GetInterfaceTags(filter)
214 settings = plc.GetNodeNetworkSettings(filter)
216 logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({})"
218 failedToGetSettings = True
219 continue # on to the next interface
221 for setting in settings:
222 settingname = setting[name_key].upper()
223 if (re.search('^IPADDR[0-9]+$|^NETMASK[0-9]+$', settingname)):
224 # TD: Added match for secondary IPv4 configuration.
225 bridgeDetails[settingname]=setting['value']
226 # IPv6 support on IPv4 interface
227 elif settingname in ('IPV6ADDR','IPV6_DEFAULTGW','IPV6ADDR_SECONDARIES', 'IPV6_AUTOCONF'):
228 # TD: Added IPV6_AUTOCONF.
229 bridgeDetails[settingname]=setting['value']
230 bridgeDetails['IPV6INIT']='yes'
232 bridgeDevices.append(bridgeName)
233 bridgeDetails['TYPE'] = bridgeType
234 if bridgeType == 'OVSBridge':
235 bridgeDetails['DEVICETYPE'] = 'ovs'
236 if bridgeDetails['BOOTPROTO'] == 'dhcp':
237 del bridgeDetails['BOOTPROTO']
238 bridgeDetails['OVSBOOTPROTO'] = 'dhcp'
239 bridgeDetails['OVSDHCPINTERFACES'] = ifname
240 devices_map[bridgeName] = bridgeDetails
242 if 'IFNAME' in details:
243 ifname = details['IFNAME']
250 ifname="eth{}".format(device_id - 1)
251 if ifname not in devices_map:
254 if os.path.exists("{}/ifcfg-{}".format(sysconfig, ifname)):
255 logger.log("net:InitInterfaces WARNING: possibly blowing away {} configuration"
257 devices_map[ifname] = details
259 logger.log('net:InitInterfaces: Device map: {}'.format(devices_map))
260 m = modprobe.Modprobe()
262 m.input("{}/etc/modprobe.conf".format(root))
265 for (dev, details) in devices_map.items():
266 # get the driver string "moduleName option1=a option2=b"
267 driver=details.get('DRIVER','')
269 driver=driver.split()
270 kernelmodule=driver[0]
271 m.aliasset(dev,kernelmodule)
272 options=" ".join(driver[1:])
274 m.optionsset(dev,options)
275 m.output("{}/etc/modprobe.conf".format(root), program)
277 # clean up after any ifcfg-$dev script that's no longer listed as
278 # part of the Interfaces associated with this node
280 # list all network-scripts
281 files = os.listdir(sysconfig)
283 # filter out the ifcfg-* files
286 if f.find("ifcfg-") == 0:
289 # remove loopback (lo) from ifcfgs list
291 if lo in ifcfgs: ifcfgs.remove(lo)
293 # remove known devices from ifcfgs list
294 for (dev, details) in devices_map.items():
296 if ifcfg in ifcfgs: ifcfgs.remove(ifcfg)
298 # delete the remaining ifcfgs from
299 deletedSomething = False
301 if not failedToGetSettings:
303 dev = ifcfg[len('ifcfg-'):]
304 path = "{}/ifcfg-{}".format(sysconfig, dev)
306 logger.verbose("net:InitInterfaces removing {} {}".format(dev, path))
307 os.system("/sbin/ifdown {}".format(dev))
308 deletedSomething=True
311 # wait a bit for the one or more ifdowns to have taken effect
315 # Write network configuration file
316 with open("{}/etc/sysconfig/network".format(root), "w") as networkconf:
317 networkconf.write("NETWORKING=yes\nHOSTNAME={}\n".format(hostname))
318 if gateway is not None:
319 networkconf.write("GATEWAY={}\n".format(gateway))
321 # Process ifcfg-$dev changes / additions
324 for (dev, details) in devices_map.items():
325 (fd, tmpnam) = tempfile.mkstemp(dir=sysconfig)
326 f = os.fdopen(fd, "w")
327 f.write("# Autogenerated by pyplnet... do not edit!\n")
328 if 'DRIVER' in details:
329 f.write("# using {} driver for device {}\n".format(details['DRIVER'], dev))
330 f.write('DEVICE={}\n'.format(dev))
332 # print the configuration values
333 for (key, val) in details.items():
334 if key not in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER','GATEWAY'):
335 f.write('{}="{}"\n'.format(key, val))
337 # print the configuration specific option values (if any)
338 if 'CFGOPTIONS' in details:
339 cfgoptions = details['CFGOPTIONS']
340 f.write('#CFGOPTIONS are {}\n'.format(cfgoptions))
341 for cfgoption in cfgoptions.split():
342 key,val = cfgoption.split('=')
346 f.write('{}="{}"\n'.format(key, val))
349 # compare whether two files are the same
350 def comparefiles(a,b):
352 logger.verbose("net:InitInterfaces comparing {} with {}".format(a, b))
353 if not os.path.exists(a) or not os.path.exists(b):
361 return buf_a == buf_b
365 src_route_changed = False
366 if ('PRIMARY' not in details and 'GATEWAY' in details and
367 details['GATEWAY'] != ''):
369 (fd, rule_tmpnam) = tempfile.mkstemp(dir=sysconfig)
370 os.write(fd, "from {} lookup {}\n".format(details['IPADDR'], table))
372 rule_dest = "{}/rule-{}".format(sysconfig, dev)
373 if not comparefiles(rule_tmpnam, rule_dest):
374 os.rename(rule_tmpnam, rule_dest)
375 os.chmod(rule_dest, 0o644)
376 src_route_changed = True
378 os.unlink(rule_tmpnam)
379 (fd, route_tmpnam) = tempfile.mkstemp(dir=sysconfig)
380 netmask = struct.unpack("I", socket.inet_aton(details['NETMASK']))[0]
381 ip = struct.unpack("I", socket.inet_aton(details['IPADDR']))[0]
382 network = socket.inet_ntoa(struct.pack("I", (ip & netmask)))
383 netmask = socket.ntohl(netmask)
385 while (netmask & (1 << i)) == 0:
388 os.write(fd, "{}/{} dev {} table {}\n".format(network, prefix, dev, table))
389 os.write(fd, "default via {} dev {} table {}\n".format(details['GATEWAY'], dev, table))
391 route_dest = "{}/route-{}".format(sysconfig, dev)
392 if not comparefiles(route_tmpnam, route_dest):
393 os.rename(route_tmpnam, route_dest)
394 os.chmod(route_dest, 0o644)
395 src_route_changed = True
397 os.unlink(route_tmpnam)
399 path = "{}/ifcfg-{}".format(sysconfig,dev)
400 if not os.path.exists(path):
401 logger.verbose('net:InitInterfaces adding configuration for {}'.format(dev))
402 # add ifcfg-$dev configuration file
403 os.rename(tmpnam,path)
407 elif not comparefiles(tmpnam,path) or src_route_changed:
408 logger.verbose('net:InitInterfaces Configuration change for {}'.format(dev))
410 logger.verbose('net:InitInterfaces ifdown {}'.format(dev))
411 # invoke ifdown for the old configuration
412 os.system("/sbin/ifdown {}".format(dev))
413 # wait a few secs for ifdown to complete
416 logger.log('replacing configuration for {}'.format(dev))
417 # replace ifcfg-$dev configuration file
418 os.rename(tmpnam,path)
422 # tmpnam & path are identical
427 with open("{}/ifcfg-{}".format(sysconfig, dev), "r") as fb:
428 for line in fb.readlines():
430 if parts[0][0]=="#":continue
431 if parts[0].find('='):
432 name,value = parts[0].split('=')
433 # clean up name & value
435 value = value.strip()
436 value = value.strip("'")
437 value = value.strip('"')
438 cfgvariables[name]=value
441 if name in cfgvariables:
442 value=cfgvariables[name]
443 value = value.lower()
447 # skip over device configs with ONBOOT=no
448 if getvar("ONBOOT") == 'no': continue
450 # don't bring up slave devices, the network scripts will
451 # handle those correctly
452 if getvar("SLAVE") == 'yes': continue
454 # Delay bringing up any bridge devices
455 if dev in bridgeDevices: continue
458 logger.verbose('net:InitInterfaces bringing up {}'.format(dev))
459 os.system("/sbin/ifup {}".format(dev))
461 # Bring up the bridge devices
462 for bridge in bridgeDevices:
463 if not files_only and bridge in newdevs:
464 logger.verbose('net:InitInterfaces bringing up bridge {}'.format(bridge))
465 os.system("/sbin/ifup {}".format(bridge))
468 # Prepare the interface details.
470 def prepDetails(interface, hostname=''):
472 details['ONBOOT'] = 'yes'
473 details['USERCTL'] = 'no'
474 # starting with f27, it's OK to use NetworkManager
475 # attempt to work around issues seen starting with f23
476 # details['NM_CONTROLLED'] = 'no'
478 details['HWADDR'] = interface['mac']
479 if interface['is_primary']:
480 details['PRIMARY'] = 'yes'
482 if interface['method'] == "static":
483 details['BOOTPROTO'] = "static"
484 details['IPADDR'] = interface['ip']
485 details['NETMASK'] = interface['netmask']
486 details['GATEWAY'] = interface['gateway']
487 if interface['is_primary']:
488 if interface['dns1']:
489 details['DNS1'] = interface['dns1']
490 if interface['dns2']:
491 details['DNS2'] = interface['dns2']
493 elif interface['method'] == "dhcp":
494 details['BOOTPROTO'] = "dhcp"
495 details['PERSISTENT_DHCLIENT'] = "yes"
496 if interface['hostname']:
497 details['DHCP_HOSTNAME'] = interface['hostname']
499 details['DHCP_HOSTNAME'] = hostname
500 if not interface['is_primary']:
501 details['DHCLIENTARGS'] = "-R subnet-mask"
506 # Remove duplicate entry from the bridged interface's configuration file.
508 def removeBridgedIfaceDetails(details):
509 # TD: Also added secondary IPv4 keys and IPv6 keys to the keys to be removed.
510 allKeys = [ 'PRIMARY', 'PERSISTENT_DHCLIENT', 'DHCLIENTARGS', 'DHCP_HOSTNAME',
511 'BOOTPROTO', 'IPADDR', 'NETMASK', 'GATEWAY', 'DNS1', 'DNS2',
512 'IPV6ADDR', 'IPV6_DEFAULTGW', 'IPV6ADDR_SECONDARIES',
513 'IPV6_AUTOCONF', 'IPV6INIT' ]
514 for i in range(1, 256):
515 allKeys.append('IPADDR' + str(i))
516 allKeys.append('NETMASK' + str(i))
522 # TD: Also turn off IPv6
523 details['IPV6INIT'] = 'no'
524 details['IPV6_AUTOCONF'] = 'no'
528 if __name__ == "__main__":
532 parser = optparse.OptionParser(usage="plnet [-v] [-f] [-p <program>] -r root node_id")
533 parser.add_option("-v", "--verbose", action="store_true", dest="verbose")
534 parser.add_option("-r", "--root", action="store", type="string",
535 dest="root", default=None)
536 parser.add_option("-f", "--files-only", action="store_true",
538 parser.add_option("-p", "--program", action="store", type="string",
539 dest="program", default="plnet")
540 (options, args) = parser.parse_args()
541 if len(args) != 1 or options.root is None:
543 print("Missing root or node_id", file=sys.stderr)
547 node = shell.GetNodes({'node_id': [int(args[0])]})
549 interfaces = shell.GetInterfaces({'interface_id': node[0]['interface_ids']})
550 except AttributeError:
551 interfaces = shell.GetNodeNetworks({'nodenetwork_id':node[0]['nodenetwork_ids']})
555 data = {'hostname': node[0]['hostname'], 'interfaces': interfaces}
557 def __init__(self, verbose):
558 self.verbosity = verbose
559 def log(self, msg, loglevel=2):
562 def verbose(self, msg):
564 l = logger(options.verbose)
565 InitInterfaces(l, shell, data, options.root, options.files_only)