1 #!/usr/bin/python3 /usr/bin/plcsh
14 def ovs_check(logger):
15 """ Return True if openvswitch is running, False otherwise. Try restarting
18 rc = os.system("service openvswitch status")
21 logger.log("net: restarting openvswitch")
22 rc = os.system("service openvswitch restart")
23 rc = os.system("service openvswitch status")
26 logger.log("net: failed to restart openvswitch")
29 def InitInterfaces(logger, plc, data, root="",
30 files_only=False, program="NodeManager"):
32 sysconfig = "{}/etc/sysconfig/network-scripts".format(root)
34 os.makedirs(sysconfig)
36 if e.errno != errno.EEXIST:
39 # query running network interfaces
41 ips = {ip: interface for (ip, interface) in devs.items()}
44 macs[sioc.gifhwaddr(dev).lower()] = dev
48 hostname = data.get('hostname', socket.gethostname())
50 # assume data['interfaces'] contains this node's Interfaces
51 interfaces = data['interfaces']
52 failedToGetSettings = False
54 # NOTE: GetInterfaces does not necessarily order the interfaces returned.
55 # Because 'interface' is decremented as each interface is processed,
56 # by the time is_primary=True (primary) interface is reached, the device
57 # "eth<interface>" is not eth0. But, something like eth-4, or eth-12.
58 # This code sorts the interfaces, placing is_primary=True interfaces first.
59 # There is a lot of room for improvement to how this
60 # script handles interfaces and how it chooses the primary interface.
61 # NOTE: by sorting on 'is_primary' and then reversing (since False is sorted
62 # before True) all 'is_primary' interfaces are at the beginning of the list.
63 interfaces.sort(key=lambda d: d['is_primary'], reverse=True)
65 # The names of the bridge devices
68 for interface in interfaces:
69 logger.verbose('net:InitInterfaces interface {}: {}'.format(device_id, interface))
70 logger.verbose('net:InitInterfaces macs = {}'.format(macs))
71 logger.verbose('net:InitInterfaces ips = {}'.format(ips))
72 # Get interface name preferably from MAC address, falling back
74 hwaddr = interface['mac']
76 hwaddr = hwaddr.lower()
78 orig_ifname = macs[hwaddr]
79 elif interface['ip'] in ips:
80 orig_ifname = ips[interface['ip']]
85 logger.verbose('net:InitInterfaces orig_ifname = {}'.format(orig_ifname))
87 details = prepDetails(interface, hostname)
89 if interface['is_primary']:
90 gateway = interface['gateway']
92 if 'interface_tag_ids' in interface:
93 interface_tag_ids = "interface_tag_ids"
94 interface_tag_id = "interface_tag_id"
97 if interface[interface_tag_ids]:
99 filter = {interface_tag_id : interface[interface_tag_ids]}
100 settings = plc.GetInterfaceTags(filter)
102 logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({})"
104 failedToGetSettings = True
105 continue # on to the next interface
107 for setting in settings:
108 settingname = setting[name_key].upper()
109 if ((settingname in ('IFNAME', 'ALIAS', 'CFGOPTIONS', 'DRIVER',
110 'VLAN','TYPE','DEVICETYPE')) or
111 (re.search('^IPADDR[0-9]+$|^NETMASK[0-9]+$', settingname))):
112 # TD: Added match for secondary IPv4 configuration.
113 details[settingname] = setting['value']
114 # IPv6 support on IPv4 interface
115 elif settingname in ('IPV6ADDR', 'IPV6_DEFAULTGW',
116 'IPV6ADDR_SECONDARIES', 'IPV6_AUTOCONF'):
117 # TD: Added IPV6_AUTOCONF.
118 details[settingname] = setting['value']
119 details['IPV6INIT'] = 'yes'
121 elif settingname in \
122 ("MODE", "ESSID", "NW", "FREQ", "CHANNEL", "SENS",
123 "RATE", "KEY", "KEY1", "KEY2", "KEY3", "KEY4",
124 "SECURITYMODE", "IWCONFIG", "IWPRIV") :
125 details [settingname] = setting['value']
126 details ['TYPE'] = 'Wireless'
128 elif settingname in ('BRIDGE',):
129 details['BRIDGE'] = setting['value']
130 elif settingname in ('OVS_BRIDGE',):
131 # If openvswitch isn't running, then we'll lose network
132 # connectivity when we reconfigure eth0.
133 if ovs_check(logger):
134 details['OVS_BRIDGE'] = setting['value']
135 details['TYPE'] = "OVSPort"
136 details['DEVICETYPE'] = "ovs"
138 logger.log("net:InitInterfaces ERROR: OVS_BRIDGE specified, "
139 "yet ovs is not running")
141 logger.log("net:InitInterfaces WARNING: ignored setting named {}"
142 .format(setting[name_key]))
144 # support aliases to interfaces either by name or HWADDR
145 if 'ALIAS' in details:
146 if 'HWADDR' in details:
147 hwaddr = details['HWADDR'].lower()
148 del details['HWADDR']
150 hwifname = macs[hwaddr]
151 if ('IFNAME' in details) and details['IFNAME'] != hwifname:
152 logger.log("net:InitInterfaces WARNING: alias ifname ({}) and hwaddr ifname ({}) do not match"
153 .format(details['IFNAME'], hwifname))
154 details['IFNAME'] = hwifname
156 logger.log('net:InitInterfaces WARNING: mac addr {} for alias not found'.format(hwaddr))
158 if 'IFNAME' in details:
159 # stupid RH /etc/sysconfig/network-scripts/ifup-aliases:new_interface()
160 # checks if the "$DEVNUM" only consists of '^[0-9A-Za-z_]*$'. Need to make
161 # our aliases compliant.
162 parts = details['ALIAS'].split('_')
165 isValid=isValid and part.isalnum()
168 devices_map["{}:{}".format(details['IFNAME'], details['ALIAS'])] = details
170 logger.log("net:InitInterfaces WARNING: interface alias ({}) "
171 "is not a valid string for RH ifup-aliases"
172 .format(details['ALIAS']))
174 logger.log("net:InitInterfaces WARNING: interface alias ({}) "
175 " not matched to an interface"
176 .format(details['ALIAS']))
178 elif ('BRIDGE' in details or 'OVS_BRIDGE' in details) and 'IFNAME' in details:
179 # The bridge inherits the mac of the first attached interface.
180 ifname = details['IFNAME']
182 if 'BRIDGE' in details:
183 bridgeName = details['BRIDGE']
184 bridgeType = 'Bridge'
186 bridgeName = details['OVS_BRIDGE']
187 bridgeType = 'OVSBridge'
189 logger.log('net:InitInterfaces: {} detected. Adding {} to devices_map'
190 .format(bridgeType, ifname))
191 devices_map[ifname] = removeBridgedIfaceDetails(details)
193 logger.log('net:InitInterfaces: Adding {} {}'.format(bridgeType, bridgeName))
194 bridgeDetails = prepDetails(interface)
196 # TD: Add configuration for secondary IPv4 and IPv6 addresses to the bridge.
197 if interface[interface_tag_ids]:
198 filter = {interface_tag_id : interface[interface_tag_ids]}
200 settings = plc.GetInterfaceTags(filter)
202 logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({})"
204 failedToGetSettings = True
205 continue # on to the next interface
207 for setting in settings:
208 settingname = setting[name_key].upper()
209 if (re.search('^IPADDR[0-9]+$|^NETMASK[0-9]+$', settingname)):
210 # TD: Added match for secondary IPv4 configuration.
211 bridgeDetails[settingname]=setting['value']
212 # IPv6 support on IPv4 interface
213 elif settingname in ('IPV6ADDR', 'IPV6_DEFAULTGW',
214 'IPV6ADDR_SECONDARIES', 'IPV6_AUTOCONF'):
215 # TD: Added IPV6_AUTOCONF.
216 bridgeDetails[settingname] = setting['value']
217 bridgeDetails['IPV6INIT'] = 'yes'
219 bridgeDevices.append(bridgeName)
220 bridgeDetails['TYPE'] = bridgeType
221 if bridgeType == 'OVSBridge':
222 bridgeDetails['DEVICETYPE'] = 'ovs'
223 if bridgeDetails['BOOTPROTO'] == 'dhcp':
224 del bridgeDetails['BOOTPROTO']
225 bridgeDetails['OVSBOOTPROTO'] = 'dhcp'
226 bridgeDetails['OVSDHCPINTERFACES'] = ifname
227 devices_map[bridgeName] = bridgeDetails
229 if 'IFNAME' in details:
230 ifname = details['IFNAME']
237 ifname = "eth{}".format(device_id - 1)
238 if ifname not in devices_map:
241 if os.path.exists("{}/ifcfg-{}".format(sysconfig, ifname)):
242 logger.log("net:InitInterfaces WARNING: possibly blowing away {} configuration"
244 devices_map[ifname] = details
246 logger.log('net:InitInterfaces: Device map: {}'.format(devices_map))
247 m = modprobe.Modprobe()
249 m.input("{}/etc/modprobe.conf".format(root))
252 for (dev, details) in devices_map.items():
253 # get the driver string "moduleName option1=a option2=b"
254 driver = details.get('DRIVER', '')
256 driver = driver.split()
257 kernelmodule = driver[0]
258 m.aliasset(dev, kernelmodule)
259 options = " ".join(driver[1:])
261 m.optionsset(dev, options)
262 m.output("{}/etc/modprobe.conf".format(root), program)
264 # clean up after any ifcfg-$dev script that's no longer listed as
265 # part of the Interfaces associated with this node
267 # list all network-scripts
268 files = os.listdir(sysconfig)
270 # filter out the ifcfg-* files
273 if f.find("ifcfg-") == 0:
276 # remove loopback (lo) from ifcfgs list
281 # remove known devices from ifcfgs list
282 for (dev, details) in devices_map.items():
287 # delete the remaining ifcfgs from
288 deletedSomething = False
290 if not failedToGetSettings:
292 dev = ifcfg[len('ifcfg-'):]
293 path = "{}/ifcfg-{}".format(sysconfig, dev)
295 logger.verbose("net:InitInterfaces removing {} {}".format(dev, path))
296 os.system("/sbin/ifdown {}".format(dev))
297 deletedSomething=True
300 # wait a bit for the one or more ifdowns to have taken effect
304 # Write network configuration file
305 with open("{}/etc/sysconfig/network".format(root), "w") as networkconf:
306 networkconf.write("NETWORKING=yes\nHOSTNAME={}\n".format(hostname))
307 if gateway is not None:
308 networkconf.write("GATEWAY={}\n".format(gateway))
310 # Process ifcfg-$dev changes / additions
313 for (dev, details) in devices_map.items():
314 (fd, tmpnam) = tempfile.mkstemp(dir=sysconfig)
315 f = os.fdopen(fd, "w")
316 f.write("# Autogenerated by pyplnet... do not edit!\n")
317 if 'DRIVER' in details:
318 f.write("# using {} driver for device {}\n".format(details['DRIVER'], dev))
319 f.write('DEVICE={}\n'.format(dev))
321 # print the configuration values
322 for (key, val) in details.items():
323 if key not in ('IFNAME', 'ALIAS', 'CFGOPTIONS', 'DRIVER', 'GATEWAY'):
324 f.write('{}="{}"\n'.format(key, val))
326 # print the configuration specific option values (if any)
327 if 'CFGOPTIONS' in details:
328 cfgoptions = details['CFGOPTIONS']
329 f.write('#CFGOPTIONS are {}\n'.format(cfgoptions))
330 for cfgoption in cfgoptions.split():
331 key,val = cfgoption.split('=')
335 f.write('{}="{}"\n'.format(key, val))
338 # compare whether two files are the same
339 def comparefiles(a,b):
341 logger.verbose("net:InitInterfaces comparing {} with {}".format(a, b))
342 if not os.path.exists(a) or not os.path.exists(b):
350 return buf_a == buf_b
354 src_route_changed = False
355 if ('PRIMARY' not in details and 'GATEWAY' in details and
356 details['GATEWAY'] != ''):
358 (fd, rule_tmpnam) = tempfile.mkstemp(dir=sysconfig)
359 os.write(fd, "from {} lookup {}\n".format(details['IPADDR'], table))
361 rule_dest = "{}/rule-{}".format(sysconfig, dev)
362 if not comparefiles(rule_tmpnam, rule_dest):
363 os.rename(rule_tmpnam, rule_dest)
364 os.chmod(rule_dest, 0o644)
365 src_route_changed = True
367 os.unlink(rule_tmpnam)
368 (fd, route_tmpnam) = tempfile.mkstemp(dir=sysconfig)
369 netmask = struct.unpack("I", socket.inet_aton(details['NETMASK']))[0]
370 ip = struct.unpack("I", socket.inet_aton(details['IPADDR']))[0]
371 network = socket.inet_ntoa(struct.pack("I", (ip & netmask)))
372 netmask = socket.ntohl(netmask)
374 while (netmask & (1 << i)) == 0:
377 os.write(fd, "{}/{} dev {} table {}\n".format(network, prefix, dev, table))
378 os.write(fd, "default via {} dev {} table {}\n".format(details['GATEWAY'], dev, table))
380 route_dest = "{}/route-{}".format(sysconfig, dev)
381 if not comparefiles(route_tmpnam, route_dest):
382 os.rename(route_tmpnam, route_dest)
383 os.chmod(route_dest, 0o644)
384 src_route_changed = True
386 os.unlink(route_tmpnam)
388 path = "{}/ifcfg-{}".format(sysconfig,dev)
389 if not os.path.exists(path):
390 logger.verbose('net:InitInterfaces adding configuration for {}'.format(dev))
391 # add ifcfg-$dev configuration file
392 os.rename(tmpnam,path)
396 elif not comparefiles(tmpnam,path) or src_route_changed:
397 logger.verbose('net:InitInterfaces Configuration change for {}'.format(dev))
399 logger.verbose('net:InitInterfaces ifdown {}'.format(dev))
400 # invoke ifdown for the old configuration
401 os.system("/sbin/ifdown {}".format(dev))
402 # wait a few secs for ifdown to complete
405 logger.log('replacing configuration for {}'.format(dev))
406 # replace ifcfg-$dev configuration file
407 os.rename(tmpnam, path)
408 os.chmod(path, 0o644)
411 # tmpnam & path are identical
416 with open("{}/ifcfg-{}".format(sysconfig, dev), "r") as fb:
417 for line in fb.readlines():
419 if parts[0][0] == "#":
421 if parts[0].find('='):
422 name, value = parts[0].split('=')
423 # clean up name & value
425 value = value.strip()
426 value = value.strip("'")
427 value = value.strip('"')
428 cfgvariables[name] = value
431 if name in cfgvariables:
432 value = cfgvariables[name]
433 value = value.lower()
437 # skip over device configs with ONBOOT=no
438 if getvar("ONBOOT") == 'no': continue
440 # don't bring up slave devices, the network scripts will
441 # handle those correctly
442 if getvar("SLAVE") == 'yes': continue
444 # Delay bringing up any bridge devices
445 if dev in bridgeDevices: continue
448 logger.verbose('net:InitInterfaces bringing up {}'.format(dev))
449 os.system("/sbin/ifup {}".format(dev))
451 # Bring up the bridge devices
452 for bridge in bridgeDevices:
453 if not files_only and bridge in newdevs:
454 logger.verbose('net:InitInterfaces bringing up bridge {}'.format(bridge))
455 os.system("/sbin/ifup {}".format(bridge))
458 # Prepare the interface details.
460 def prepDetails(interface, hostname=''):
462 details['ONBOOT'] = 'yes'
463 details['USERCTL'] = 'no'
464 # starting with f27, it's OK to use NetworkManager
465 # attempt to work around issues seen starting with f23
466 # details['NM_CONTROLLED'] = 'no'
468 details['HWADDR'] = interface['mac']
469 if interface['is_primary']:
470 details['PRIMARY'] = 'yes'
472 if interface['method'] == "static":
473 details['BOOTPROTO'] = "static"
474 details['IPADDR'] = interface['ip']
475 details['NETMASK'] = interface['netmask']
476 details['GATEWAY'] = interface['gateway']
477 if interface['is_primary']:
478 if interface['dns1']:
479 details['DNS1'] = interface['dns1']
480 if interface['dns2']:
481 details['DNS2'] = interface['dns2']
483 elif interface['method'] == "dhcp":
484 details['BOOTPROTO'] = "dhcp"
485 details['PERSISTENT_DHCLIENT'] = "yes"
486 if interface['hostname']:
487 details['DHCP_HOSTNAME'] = interface['hostname']
489 details['DHCP_HOSTNAME'] = hostname
490 if not interface['is_primary']:
491 details['DHCLIENTARGS'] = "-R subnet-mask"
496 # Remove duplicate entry from the bridged interface's configuration file.
498 def removeBridgedIfaceDetails(details):
499 # TD: Also added secondary IPv4 keys and IPv6 keys to the keys to be removed.
500 allKeys = [ 'PRIMARY', 'PERSISTENT_DHCLIENT', 'DHCLIENTARGS', 'DHCP_HOSTNAME',
501 'BOOTPROTO', 'IPADDR', 'NETMASK', 'GATEWAY', 'DNS1', 'DNS2',
502 'IPV6ADDR', 'IPV6_DEFAULTGW', 'IPV6ADDR_SECONDARIES',
503 'IPV6_AUTOCONF', 'IPV6INIT' ]
504 for i in range(1, 256):
505 allKeys.append('IPADDR' + str(i))
506 allKeys.append('NETMASK' + str(i))
512 # TD: Also turn off IPv6
513 details['IPV6INIT'] = 'no'
514 details['IPV6_AUTOCONF'] = 'no'
518 if __name__ == "__main__":
522 parser = optparse.OptionParser(usage="plnet [-v] [-f] [-p <program>] -r root node_id")
523 parser.add_option("-v", "--verbose", action="store_true", dest="verbose")
524 parser.add_option("-r", "--root", action="store", type="string",
525 dest="root", default=None)
526 parser.add_option("-f", "--files-only", action="store_true",
528 parser.add_option("-p", "--program", action="store", type="string",
529 dest="program", default="plnet")
530 (options, args) = parser.parse_args()
531 if len(args) != 1 or options.root is None:
535 node = shell.GetNodes({'node_id': [int(args[0])]})
536 interfaces = shell.GetInterfaces({'interface_id': node[0]['interface_ids']})
539 data = {'hostname': node[0]['hostname'], 'interfaces': interfaces}
541 def __init__(self, verbose):
542 self.verbosity = verbose
543 def log(self, msg, loglevel=2):
546 def verbose(self, msg):
548 l = logger(options.verbose)
549 InitInterfaces(l, shell, data, options.root, options.files_only)