1 #!/usr/bin/python3 /usr/bin/plcsh
3 # pylint: disable=c0111, c0103, r0912, r0913, r0914, r0915
16 def ovs_check(logger):
17 """ Return True if openvswitch is running, False otherwise. Try restarting
20 retcod = os.system("service openvswitch status")
23 logger.log("net: restarting openvswitch")
24 retcod = os.system("service openvswitch restart")
25 retcod = os.system("service openvswitch status")
28 logger.log("net: failed to restart openvswitch")
31 def InitInterfaces(logger, plc, data, root="",
32 files_only=False, program="NodeManager"):
34 sysconfig = "{}/etc/sysconfig/network-scripts".format(root)
36 os.makedirs(sysconfig)
38 if e.errno != errno.EEXIST:
41 # query running network interfaces
43 ips = {ip: interface for (ip, interface) in devs.items()}
46 macs[sioc.gifhwaddr(dev).lower()] = dev
50 hostname = data.get('hostname', socket.gethostname())
52 # assume data['interfaces'] contains this node's Interfaces
53 interfaces = data['interfaces']
54 failedToGetSettings = False
56 # NOTE: GetInterfaces does not necessarily order the interfaces returned.
57 # Because 'interface' is decremented as each interface is processed,
58 # by the time is_primary=True (primary) interface is reached, the device
59 # "eth<interface>" is not eth0. But, something like eth-4, or eth-12.
60 # This code sorts the interfaces, placing is_primary=True interfaces first.
61 # There is a lot of room for improvement to how this
62 # script handles interfaces and how it chooses the primary interface.
63 # NOTE: by sorting on 'is_primary' and then reversing (since False is sorted
64 # before True) all 'is_primary' interfaces are at the beginning of the list.
65 interfaces.sort(key=lambda d: d['is_primary'], reverse=True)
67 # The names of the bridge devices
70 for interface in interfaces:
71 logger.verbose('net:InitInterfaces interface {}: {}'.format(device_id, interface))
72 logger.verbose('net:InitInterfaces macs = {}'.format(macs))
73 logger.verbose('net:InitInterfaces ips = {}'.format(ips))
74 # Get interface name preferably from MAC address, falling back
76 hwaddr = interface['mac']
77 if hwaddr is not None:
78 hwaddr = hwaddr.lower()
80 orig_ifname = macs[hwaddr]
81 elif interface['ip'] in ips:
82 orig_ifname = ips[interface['ip']]
87 logger.verbose('net:InitInterfaces orig_ifname = {}'.format(orig_ifname))
89 details = prepDetails(interface, hostname)
91 if interface['is_primary']:
92 gateway = interface['gateway']
94 if 'interface_tag_ids' in interface:
95 interface_tag_ids = "interface_tag_ids"
96 interface_tag_id = "interface_tag_id"
99 if interface[interface_tag_ids]:
100 filter = {interface_tag_id : interface[interface_tag_ids]}
102 settings = plc.GetInterfaceTags(filter)
104 logger.log("net:InitInterfaces FATAL: failed call "
105 "GetInterfaceTags({})".format(filter))
106 failedToGetSettings = True
107 continue # on to the next interface
109 for setting in settings:
110 settingname = setting[name_key].upper()
111 if (settingname in ('IFNAME', 'ALIAS', 'CFGOPTIONS', 'DRIVER',
112 'VLAN', 'TYPE', 'DEVICETYPE') or
113 re.search('^IPADDR[0-9]+$|^NETMASK[0-9]+$', settingname)):
114 # TD: Added match for secondary IPv4 configuration.
115 details[settingname] = setting['value']
116 # IPv6 support on IPv4 interface
117 elif settingname in ('IPV6ADDR', 'IPV6_DEFAULTGW',
118 'IPV6ADDR_SECONDARIES', 'IPV6_AUTOCONF'):
119 # TD: Added IPV6_AUTOCONF.
120 details[settingname] = setting['value']
121 details['IPV6INIT'] = 'yes'
123 elif settingname in \
124 ("MODE", "ESSID", "NW", "FREQ", "CHANNEL", "SENS",
125 "RATE", "KEY", "KEY1", "KEY2", "KEY3", "KEY4",
126 "SECURITYMODE", "IWCONFIG", "IWPRIV"):
127 details[settingname] = setting['value']
128 details['TYPE'] = 'Wireless'
130 elif settingname in ('BRIDGE',):
131 details['BRIDGE'] = setting['value']
132 elif settingname in ('OVS_BRIDGE',):
133 # If openvswitch isn't running, then we'll lose network
134 # connectivity when we reconfigure eth0.
135 if ovs_check(logger):
136 details['OVS_BRIDGE'] = setting['value']
137 details['TYPE'] = "OVSPort"
138 details['DEVICETYPE'] = "ovs"
140 logger.log("net:InitInterfaces ERROR: OVS_BRIDGE specified, "
141 "yet ovs is not running")
143 logger.log("net:InitInterfaces WARNING: ignored setting named {}"
144 .format(setting[name_key]))
146 # support aliases to interfaces either by name or HWADDR
147 if 'ALIAS' in details:
148 if 'HWADDR' in details:
149 hwaddr = details['HWADDR'].lower()
150 del details['HWADDR']
152 hwifname = macs[hwaddr]
153 if ('IFNAME' in details) and details['IFNAME'] != hwifname:
154 logger.log("net:InitInterfaces WARNING: alias ifname ({}) and hwaddr ifname ({}) do not match"
155 .format(details['IFNAME'], hwifname))
156 details['IFNAME'] = hwifname
158 logger.log('net:InitInterfaces WARNING: mac addr {} for alias not found'.format(hwaddr))
160 if 'IFNAME' in details:
161 # stupid RH /etc/sysconfig/network-scripts/ifup-aliases:new_interface()
162 # checks if the "$DEVNUM" only consists of '^[0-9A-Za-z_]*$'. Need to make
163 # our aliases compliant.
164 parts = details['ALIAS'].split('_')
167 isValid=isValid and part.isalnum()
170 devices_map["{}:{}".format(details['IFNAME'], details['ALIAS'])] = details
172 logger.log("net:InitInterfaces WARNING: interface alias ({}) "
173 "is not a valid string for RH ifup-aliases"
174 .format(details['ALIAS']))
176 logger.log("net:InitInterfaces WARNING: interface alias ({}) "
177 " not matched to an interface"
178 .format(details['ALIAS']))
180 elif ('BRIDGE' in details or 'OVS_BRIDGE' in details) and 'IFNAME' in details:
181 # The bridge inherits the mac of the first attached interface.
182 ifname = details['IFNAME']
184 if 'BRIDGE' in details:
185 bridgeName = details['BRIDGE']
186 bridgeType = 'Bridge'
188 bridgeName = details['OVS_BRIDGE']
189 bridgeType = 'OVSBridge'
191 logger.log('net:InitInterfaces: {} detected. Adding {} to devices_map'
192 .format(bridgeType, ifname))
193 devices_map[ifname] = removeBridgedIfaceDetails(details)
195 logger.log('net:InitInterfaces: Adding {} {}'.format(bridgeType, bridgeName))
196 bridgeDetails = prepDetails(interface)
198 # TD: Add configuration for secondary IPv4 and IPv6 addresses to the bridge.
199 if interface[interface_tag_ids]:
200 filter = {interface_tag_id : interface[interface_tag_ids]}
202 settings = plc.GetInterfaceTags(filter)
204 logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({})"
206 failedToGetSettings = True
207 continue # on to the next interface
209 for setting in settings:
210 settingname = setting[name_key].upper()
211 if (re.search('^IPADDR[0-9]+$|^NETMASK[0-9]+$', settingname)):
212 # TD: Added match for secondary IPv4 configuration.
213 bridgeDetails[settingname]=setting['value']
214 # IPv6 support on IPv4 interface
215 elif settingname in ('IPV6ADDR', 'IPV6_DEFAULTGW',
216 'IPV6ADDR_SECONDARIES', 'IPV6_AUTOCONF'):
217 # TD: Added IPV6_AUTOCONF.
218 bridgeDetails[settingname] = setting['value']
219 bridgeDetails['IPV6INIT'] = 'yes'
221 bridgeDevices.append(bridgeName)
222 bridgeDetails['TYPE'] = bridgeType
223 if bridgeType == 'OVSBridge':
224 bridgeDetails['DEVICETYPE'] = 'ovs'
225 if bridgeDetails['BOOTPROTO'] == 'dhcp':
226 del bridgeDetails['BOOTPROTO']
227 bridgeDetails['OVSBOOTPROTO'] = 'dhcp'
228 bridgeDetails['OVSDHCPINTERFACES'] = ifname
229 devices_map[bridgeName] = bridgeDetails
231 if 'IFNAME' in details:
232 ifname = details['IFNAME']
239 ifname = "eth{}".format(device_id - 1)
240 if ifname not in devices_map:
243 if os.path.exists("{}/ifcfg-{}".format(sysconfig, ifname)):
244 logger.log("net:InitInterfaces WARNING: possibly blowing away {} configuration"
246 devices_map[ifname] = details
248 logger.log('net:InitInterfaces: Device map: {}'.format(devices_map))
249 m = modprobe.Modprobe()
251 m.input("{}/etc/modprobe.conf".format(root))
254 for (dev, details) in devices_map.items():
255 # get the driver string "moduleName option1=a option2=b"
256 driver = details.get('DRIVER', '')
258 driver = driver.split()
259 kernelmodule = driver[0]
260 m.aliasset(dev, kernelmodule)
261 options = " ".join(driver[1:])
263 m.optionsset(dev, options)
264 m.output("{}/etc/modprobe.conf".format(root), program)
266 # clean up after any ifcfg-$dev script that's no longer listed as
267 # part of the Interfaces associated with this node
269 # list all network-scripts
270 files = os.listdir(sysconfig)
272 # filter out the ifcfg-* files
275 if f.find("ifcfg-") == 0:
278 # remove loopback (lo) from ifcfgs list
283 # remove known devices from ifcfgs list
284 for (dev, details) in devices_map.items():
289 # delete the remaining ifcfgs from
290 deletedSomething = False
292 if not failedToGetSettings:
294 dev = ifcfg[len('ifcfg-'):]
295 path = "{}/ifcfg-{}".format(sysconfig, dev)
297 logger.verbose("net:InitInterfaces removing {} {}".format(dev, path))
298 os.system("/sbin/ifdown {}".format(dev))
299 deletedSomething=True
302 # wait a bit for the one or more ifdowns to have taken effect
306 # Write network configuration file
307 with open("{}/etc/sysconfig/network".format(root), "w") as networkconf:
308 networkconf.write("NETWORKING=yes\nHOSTNAME={}\n".format(hostname))
309 if gateway is not None:
310 networkconf.write("GATEWAY={}\n".format(gateway))
312 # Process ifcfg-$dev changes / additions
315 for (dev, details) in devices_map.items():
316 (fd, tmpnam) = tempfile.mkstemp(dir=sysconfig)
317 f = os.fdopen(fd, "w")
318 f.write("# Autogenerated by pyplnet... do not edit!\n")
319 if 'DRIVER' in details:
320 f.write("# using {} driver for device {}\n".format(details['DRIVER'], dev))
321 f.write('DEVICE={}\n'.format(dev))
323 # print the configuration values
324 for (key, val) in details.items():
325 if key not in ('IFNAME', 'ALIAS', 'CFGOPTIONS', 'DRIVER', 'GATEWAY'):
326 f.write('{}="{}"\n'.format(key, val))
328 # print the configuration specific option values (if any)
329 if 'CFGOPTIONS' in details:
330 cfgoptions = details['CFGOPTIONS']
331 f.write('#CFGOPTIONS are {}\n'.format(cfgoptions))
332 for cfgoption in cfgoptions.split():
333 key,val = cfgoption.split('=')
337 f.write('{}="{}"\n'.format(key, val))
340 # compare whether two files are the same
341 def comparefiles(a,b):
343 logger.verbose("net:InitInterfaces comparing {} with {}".format(a, b))
344 if not os.path.exists(a) or not os.path.exists(b):
352 return buf_a == buf_b
356 src_route_changed = False
357 if ('PRIMARY' not in details and 'GATEWAY' in details and
358 details['GATEWAY'] != ''):
360 (fd, rule_tmpnam) = tempfile.mkstemp(dir=sysconfig)
361 os.write(fd, "from {} lookup {}\n".format(details['IPADDR'], table))
363 rule_dest = "{}/rule-{}".format(sysconfig, dev)
364 if not comparefiles(rule_tmpnam, rule_dest):
365 os.rename(rule_tmpnam, rule_dest)
366 os.chmod(rule_dest, 0o644)
367 src_route_changed = True
369 os.unlink(rule_tmpnam)
370 (fd, route_tmpnam) = tempfile.mkstemp(dir=sysconfig)
371 netmask = struct.unpack("I", socket.inet_aton(details['NETMASK']))[0]
372 ip = struct.unpack("I", socket.inet_aton(details['IPADDR']))[0]
373 network = socket.inet_ntoa(struct.pack("I", (ip & netmask)))
374 netmask = socket.ntohl(netmask)
376 while (netmask & (1 << i)) == 0:
379 os.write(fd, "{}/{} dev {} table {}\n".format(network, prefix, dev, table))
380 os.write(fd, "default via {} dev {} table {}\n".format(details['GATEWAY'], dev, table))
382 route_dest = "{}/route-{}".format(sysconfig, dev)
383 if not comparefiles(route_tmpnam, route_dest):
384 os.rename(route_tmpnam, route_dest)
385 os.chmod(route_dest, 0o644)
386 src_route_changed = True
388 os.unlink(route_tmpnam)
390 path = "{}/ifcfg-{}".format(sysconfig,dev)
391 if not os.path.exists(path):
392 logger.verbose('net:InitInterfaces adding configuration for {}'.format(dev))
393 # add ifcfg-$dev configuration file
394 os.rename(tmpnam,path)
398 elif not comparefiles(tmpnam,path) or src_route_changed:
399 logger.verbose('net:InitInterfaces Configuration change for {}'.format(dev))
401 logger.verbose('net:InitInterfaces ifdown {}'.format(dev))
402 # invoke ifdown for the old configuration
403 os.system("/sbin/ifdown {}".format(dev))
404 # wait a few secs for ifdown to complete
407 logger.log('replacing configuration for {}'.format(dev))
408 # replace ifcfg-$dev configuration file
409 os.rename(tmpnam, path)
410 os.chmod(path, 0o644)
413 # tmpnam & path are identical
418 with open("{}/ifcfg-{}".format(sysconfig, dev), "r") as fb:
419 for line in fb.readlines():
421 if parts[0][0] == "#":
423 if parts[0].find('='):
424 name, value = parts[0].split('=')
425 # clean up name & value
427 value = value.strip()
428 value = value.strip("'")
429 value = value.strip('"')
430 cfgvariables[name] = value
433 if name in cfgvariables:
434 value = cfgvariables[name]
435 value = value.lower()
439 # skip over device configs with ONBOOT=no
440 if getvar("ONBOOT") == 'no': continue
442 # don't bring up slave devices, the network scripts will
443 # handle those correctly
444 if getvar("SLAVE") == 'yes': continue
446 # Delay bringing up any bridge devices
447 if dev in bridgeDevices: continue
450 logger.verbose('net:InitInterfaces bringing up {}'.format(dev))
451 os.system("/sbin/ifup {}".format(dev))
453 # Bring up the bridge devices
454 for bridge in bridgeDevices:
455 if not files_only and bridge in newdevs:
456 logger.verbose('net:InitInterfaces bringing up bridge {}'.format(bridge))
457 os.system("/sbin/ifup {}".format(bridge))
460 # Prepare the interface details.
462 def prepDetails(interface, hostname=''):
464 details['ONBOOT'] = 'yes'
465 details['USERCTL'] = 'no'
466 # starting with f27, it's OK to use NetworkManager
467 # attempt to work around issues seen starting with f23
468 # details['NM_CONTROLLED'] = 'no'
470 details['HWADDR'] = interface['mac']
471 if interface['is_primary']:
472 details['PRIMARY'] = 'yes'
474 if interface['method'] == "static":
475 details['BOOTPROTO'] = "static"
476 details['IPADDR'] = interface['ip']
477 details['NETMASK'] = interface['netmask']
478 details['GATEWAY'] = interface['gateway']
479 if interface['is_primary']:
480 if interface['dns1']:
481 details['DNS1'] = interface['dns1']
482 if interface['dns2']:
483 details['DNS2'] = interface['dns2']
485 elif interface['method'] == "dhcp":
486 details['BOOTPROTO'] = "dhcp"
487 details['PERSISTENT_DHCLIENT'] = "yes"
488 if interface['hostname']:
489 details['DHCP_HOSTNAME'] = interface['hostname']
491 details['DHCP_HOSTNAME'] = hostname
492 if not interface['is_primary']:
493 details['DHCLIENTARGS'] = "-R subnet-mask"
498 # Remove duplicate entry from the bridged interface's configuration file.
500 def removeBridgedIfaceDetails(details):
501 # TD: Also added secondary IPv4 keys and IPv6 keys to the keys to be removed.
502 allKeys = [ 'PRIMARY', 'PERSISTENT_DHCLIENT', 'DHCLIENTARGS', 'DHCP_HOSTNAME',
503 'BOOTPROTO', 'IPADDR', 'NETMASK', 'GATEWAY', 'DNS1', 'DNS2',
504 'IPV6ADDR', 'IPV6_DEFAULTGW', 'IPV6ADDR_SECONDARIES',
505 'IPV6_AUTOCONF', 'IPV6INIT' ]
506 for i in range(1, 256):
507 allKeys.append('IPADDR' + str(i))
508 allKeys.append('NETMASK' + str(i))
514 # TD: Also turn off IPv6
515 details['IPV6INIT'] = 'no'
516 details['IPV6_AUTOCONF'] = 'no'
524 usage = "plnet [-v] [-f] [-p <program>] -r root node_id"
526 parser = optparse.OptionParser(usage=usage)
528 "-r", "--root", action="store", type="string",
529 dest="root", default=None)
531 "-v", "--verbose", action="store_true", dest="verbose")
533 "-f", "--files-only", action="store_true",
536 "-p", "--program", action="store", type="string",
537 dest="program", default="plnet")
538 (options, args) = parser.parse_args()
539 if len(args) != 1 or options.root is None:
543 node = shell.GetNodes({'node_id': [int(args[0])]})
544 interfaces = shell.GetInterfaces({'interface_id': node[0]['interface_ids']})
547 data = {'hostname': node[0]['hostname'], 'interfaces': interfaces}
549 def __init__(self, verbose):
550 self.verbosity = verbose
551 def log(self, msg, loglevel=2):
554 def verbose(self, msg):
556 l = logger(options.verbose)
557 InitInterfaces(l, shell, data, options.root, options.files_only)
559 if __name__ == "__main__":