2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-26 11:30:23 +00:00
confluent/confluent_osdeploy/common/profile/scripts/confignet
Jarrod Johnson e03f010eac Fix Ubuntu confignet without IPv6
If confignet did not have ipv6 to work with, it
would fail to work at all.

Also handle when the configuration has a blank DNS server in it.
2023-11-09 09:03:59 -05:00

497 lines
19 KiB
Python

#!/usr/bin/python
import glob
import json
import os
import socket
import sys
import time
import shlex
import subprocess
try:
import yaml
except ImportError:
pass
try:
from importlib.machinery import SourceFileLoader
def load_source(mod, path):
return SourceFileLoader(mod, path).load_module()
except ImportError:
from imp import load_source
try:
apiclient = load_source('apiclient', '/opt/confluent/bin/apiclient')
except IOError:
apiclient = load_source('apiclient', '/etc/confluent/apiclient')
def add_lla(iface, mac):
pieces = mac.split(':')
initbyte = int(pieces[0], 16) ^ 2
lla = 'fe80::{0:x}{1}:{2}ff:fe{3}:{4}{5}/64'.format(initbyte, pieces[1], pieces[2], pieces[3], pieces[4], pieces[5])
try:
with open('/proc/sys/net/ipv6/conf/{0}/disable_ipv6'.format(iface), 'w') as setin:
setin.write('0')
subprocess.check_call(['ip', 'addr', 'add', 'dev', iface, lla, 'scope', 'link'])
except Exception:
return None
return lla
#cli = apiclient.HTTPSClient(json=True)
#c = cli.grab_url_with_status('/confluent-api/self/netcfg')
def add_missing_llas():
#NetworkManager goes out of its way to suppress ipv6 lla, so will just add some
added = {}
linkinfo = subprocess.check_output(['ip', '-br', 'l']).decode('utf8')
ifaces = {}
for line in linkinfo.split('\n'):
line = line.strip().split()
if not line or 'LOOPBACK' in line[-1] or 'NO-CARRIER' in line[-1]:
continue
if 'UP' not in line[-1]:
subprocess.call(['ip', 'link', 'set', line[0], 'up'])
ifaces[line[0]] = line[2]
ips = {}
ipinfo = subprocess.check_output(['ip', '-br', '-6', 'a']).decode('utf8')
for line in ipinfo.split('\n'):
line = line.strip().split(None, 2)
if not line:
continue
ips[line[0]] = line[2]
for iface in ifaces:
for addr in ips.get(iface, '').split():
if addr.startswith('fe80::'):
break
else:
newlla = add_lla(iface, ifaces[iface])
if newlla:
added[iface] = newlla
return added
def rm_tmp_llas(tmpllas):
for iface in tmpllas:
subprocess.check_call(['ip', 'addr', 'del', 'dev', iface, tmpllas[iface]])
def await_tentative():
maxwait = 10
while b'tentative' in subprocess.check_output(['ip', 'a']):
if maxwait == 0:
break
maxwait -= 1
time.sleep(1)
def map_idx_to_name():
map = {}
devtype = {}
prevdev = None
for line in subprocess.check_output(['ip', 'l']).decode('utf8').splitlines():
if line.startswith(' ') and 'link/' in line:
typ = line.split()[0].split('/')[1]
devtype[prevdev] = typ if type != 'ether' else 'ethernet'
if line.startswith(' '):
continue
idx, iface, rst = line.split(':', 2)
prevdev = iface.strip()
rst = rst.split()
try:
midx = rst.index('master')
continue
except ValueError:
pass
idx = int(idx)
iface = iface.strip()
map[idx] = iface
return map, devtype
def get_interface_name(iname, settings):
explicitname = settings.get('interface_names', None)
if explicitname:
return explicitname
if settings.get('current_nic', False):
return iname
return None
class NetplanManager(object):
def __init__(self, deploycfg):
self.cfgbydev = {}
self.read_connections()
self.deploycfg = deploycfg
def read_connections(self):
for plan in glob.glob('/etc/netplan/*.y*ml'):
with open(plan) as planfile:
planinfo = yaml.safe_load(planfile)
if not planinfo:
continue
nicinfo = planinfo.get('network', {}).get('ethernets', {})
for devname in nicinfo:
if devname == 'lo':
continue
if 'gateway4' in nicinfo[devname]:
# normalize deprecated syntax on read in
gw4 = nicinfo[devname]['gateway4']
del nicinfo[devname]['gateway4']
routeinfo = nicinfo[devname].get('routes', [])
for ri in routeinfo:
if ri.get('via', None) == gw4 and ri.get('to', None) in ('default', '0.0.0.0/0', '0/0'):
break
else:
routeinfo.append({
'to': 'default',
'via': gw4
})
nicinfo[devname]['routes'] = routeinfo
self.cfgbydev[devname] = nicinfo[devname]
def apply_configuration(self, cfg):
devnames = cfg['interfaces']
if len(devnames) != 1:
raise Exception('Multi-nic team/bonds not yet supported')
stgs = cfg['settings']
needcfgapply = False
for devname in devnames:
needcfgwrite = False
# ipv6_method missing at uconn...
if stgs.get('ipv6_method', None) == 'static':
curraddr = stgs'ipv6_address']
currips = self.getcfgarrpath([devname, 'addresses'])
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('ipv4_method', None) == 'static':
curraddr = stgs['ipv4_address']
currips = self.getcfgarrpath([devname, 'addresses'])
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
for gwaddr in gws:
if gwaddr:
cfgroutes = self.getcfgarrpath([devname, 'routes'])
for rinfo in cfgroutes:
if rinfo.get('via', None) == gwaddr:
break
else:
needcfgwrite = True
cfgroutes.append({'via': gwaddr, 'to': 'default'})
dnsips = self.deploycfg.get('nameservers', [])
dnsdomain = self.deploycfg.get('dnsdomain', '')
if dnsips:
currdnsips = self.getcfgarrpath([devname, 'nameservers', 'addresses'])
for dnsip in dnsips:
if dnsip and dnsip not in currdnsips:
needcfgwrite = True
currdnsips.append(dnsip)
if dnsdomain:
currdnsdomain = self.getcfgarrpath([devname, 'nameservers', 'search'])
if dnsdomain not in currdnsdomain:
needcfgwrite = True
currdnsdomain.append(dnsdomain)
if needcfgwrite:
needcfgapply = True
newcfg = {'network': {'version': 2, 'ethernets': {devname: self.cfgbydev[devname]}}}
with open('/etc/netplan/{0}-confluentcfg.yaml'.format(devname), 'w') as planout:
planout.write(yaml.dump(newcfg))
if needcfgapply:
subprocess.call(['netplan', 'apply'])
def getcfgarrpath(self, devpath):
currptr = self.cfgbydev
for k in devpath[:-1]:
if k not in currptr:
currptr[k] = {}
currptr = currptr[k]
if devpath[-1] not in currptr:
currptr[devpath[-1]] = []
return currptr[devpath[-1]]
class WickedManager(object):
def __init__(self):
self.teamidx = 0
self.read_connections()
def read_connections(self):
self.cfgbydev = {}
for ifcfg in glob.glob('/etc/sysconfig/network/ifcfg-*'):
devname = ifcfg.replace('/etc/sysconfig/network/ifcfg-', '')
if devname == 'lo':
continue
currcfg = {}
self.cfgbydev[devname] = currcfg
for cfg in open(ifcfg).read().splitlines():
cfg = cfg.split('#', 1)[0]
kv = ' '.join(shlex.split(cfg)).split('=', 1)
if len(kv) != 2:
continue
k, v = kv
k = k.strip()
v = v.strip()
currcfg[k] = v
def apply_configuration(self, cfg):
stgs = cfg['settings']
ipcfg = 'STARTMODE=auto\n'
routecfg = ''
bootproto4 = stgs.get('ipv4_method', 'none')
bootproto6 = stgs.get('ipv6_method', 'none')
if bootproto4 == 'dhcp' and bootproto6 == 'dhcp':
ipcfg += 'BOOTPROTO=dhcp\n'
elif bootproto4 == 'dhcp':
ipcfg += 'BOOTPROTO=dhcp4\n'
elif bootproto6 == 'dhcp':
ipcfg += 'BOOTPROTO=dhcp6\n'
else:
ipcfg += 'BOOTPROTO=static\n'
if stgs.get('ipv4_address', None):
ipcfg += 'IPADDR=' + stgs['ipv4_address'] + '\n'
v4gw = stgs.get('ipv4_gateway', None)
if stgs.get('ipv6_address', None):
ipcfg += 'IPADDR_V6=' + stgs['ipv6_address'] + '\n'
v6gw = stgs.get('ipv6_gateway', None)
cname = None
if len(cfg['interfaces']) > 1: # creating new team
if not stgs.get('team_mode', None):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
return
if not stgs.get('connection_name', None):
stgs['connection_name'] = 'bond{0}'.format(self.teamidx)
self.teamidx += 1
cname = stgs['connection_name']
with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as teamout:
teamout.write(ipcfg)
if stgs['team_mode'] == 'lacp':
stgs['team_mode'] = '802.3ad'
teamout.write("BONDING_MODULE_OPTS='mode={0} miimon=100'\nBONDING_MASTER=yes\n".format(stgs['team_mode']))
idx = 1
for iface in cfg['interfaces']:
subprocess.call(['wicked', 'ifdown', iface])
try:
os.remove('/etc/sysconfig/network/ifcfg-{0}'.format(iface))
os.remove('/etc/sysconfig/network/ifroute-{0}'.format(iface))
except OSError:
pass
teamout.write('BONDING_SLAVE{0}={1}\n'.format(idx, iface))
idx += 1
else:
cname = list(cfg['interfaces'])[0]
priorcfg = self.cfgbydev.get(cname, {})
for cf in priorcfg:
if cf.startswith('TEAM_'):
ipcfg += '{0}={1}\n'.format(cf, priorcfg[cf])
with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as iout:
iout.write(ipcfg)
if v4gw:
routecfg += 'default {0} - {1}\n'.format(v4gw, cname)
if v6gw:
routecfg += 'default {0} - {1}\n'.format(v6gw, cname)
if routecfg:
with open('/etc/sysconfig/network/ifroute-{0}'.format(cname), 'w') as routeout:
routeout.write(routecfg)
subprocess.call(['wicked', 'ifup', cname])
class NetworkManager(object):
def __init__(self, devtypes):
self.connections = {}
self.uuidbyname = {}
self.uuidbydev = {}
self.connectiondetail = {}
self.read_connections()
self.teamidx = 0
self.devtypes = devtypes
def read_connections(self):
self.connections = {}
self.uuidbyname = {}
self.uuidbydev = {}
self.connectiondetail = {}
ci = subprocess.check_output(['nmcli', '-t', 'c']).decode('utf8')
for inf in ci.splitlines():
n, u, t, dev = inf.split(':')
if n == 'NAME':
continue
if dev == '--':
dev = None
self.uuidbyname[n] = u
if dev:
self.uuidbydev[dev] = u
self.connections[u] = {'name': n, 'uuid': u, 'type': t, 'dev': dev}
deats = {}
for deat in subprocess.check_output(['nmcli', 'c', 's', u]).decode('utf8').splitlines():
k, v = deat.split(':', 1)
v = v.strip()
if v == '--':
continue
if '(default)' in v:
continue
deats[k] = v
self.connectiondetail[u] = deats
def add_team_member(self, team, member):
bondcfg = {}
if member in self.uuidbydev:
myuuid = self.uuidbydev[member]
deats = self.connectiondetail[myuuid]
currteam = deats.get('connection.master', None)
if currteam == team:
return
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname'):
if deats.get(stg, None):
bondcfg[stg] = deats[stg]
if member in self.uuidbyname:
subprocess.check_call(['nmcli', 'c', 'del', self.uuidbyname[member]])
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond-slave', 'master', team, 'con-name', member, 'connection.interface-name', member])
if bondcfg:
args = []
for parm in bondcfg:
args.append(parm)
args.append(bondcfg[parm])
subprocess.check_call(['nmcli', 'c', 'm', team] + args)
def apply_configuration(self, cfg):
cmdargs = {}
cmdargs['connection.autoconnect'] = 'yes'
stgs = cfg['settings']
cmdargs['ipv6.method'] = stgs.get('ipv6_method', 'link-local')
if stgs.get('ipv6_address', None):
cmdargs['ipv6.addresses'] = stgs['ipv6_address']
cmdargs['ipv4.method'] = stgs.get('ipv4_method', 'disabled')
if stgs.get('ipv4_address', None):
cmdargs['ipv4.addresses'] = stgs['ipv4_address']
if stgs.get('ipv4_gateway', None):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if len(cfg['interfaces']) > 1: # team time.. should be..
if not cfg['settings'].get('team_mode', None):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
return
if not cfg['settings'].get('connection_name', None):
cfg['settings']['connection_name'] = 'team{0}'.format(self.teamidx)
self.teamidx += 1
cname = cfg['settings']['connection_name']
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'mode={}'.format(stgs['team_mode'])] + cargs)
for iface in cfg['interfaces']:
self.add_team_member(cname, iface)
subprocess.check_call(['nmcli', 'c', 'u', cname])
else:
cname = stgs.get('connection_name', None)
iname = list(cfg['interfaces'])[0]
if not cname:
cname = iname
u = self.uuidbyname.get(cname, None)
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
if u:
cmdargs['connection.interface-name'] = iname
subprocess.check_call(['nmcli', 'c', 'm', u] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
else:
subprocess.check_call(['nmcli', 'c', 'add', 'type', self.devtypes[iname], 'con-name', cname, 'connection.interface-name', iname] + cargs)
self.read_connections()
u = self.uuidbyname.get(cname, None)
if u:
subprocess.check_call(['nmcli', 'c', 'u', u])
if __name__ == '__main__':
havefirewall = subprocess.call(['systemctl', 'status', 'firewalld'])
havefirewall = havefirewall == 0
if havefirewall:
subprocess.check_call(['systemctl', 'stop', 'firewalld'])
tmpllas = add_missing_llas()
await_tentative()
idxmap, devtypes = map_idx_to_name()
netname_to_interfaces = {}
myaddrs = apiclient.get_my_addresses()
srvs, _ = apiclient.scan_confluents()
doneidxs = set([])
dc = None
for srv in srvs:
try:
s = socket.create_connection((srv, 443))
except socket.error:
continue
myname = s.getsockname()
s.close()
if len(myname) == 4:
curridx = myname[-1]
else:
myname = myname[0]
myname = socket.inet_pton(socket.AF_INET, myname)
for addr in myaddrs:
if myname == addr[1].tobytes():
curridx = addr[-1]
if curridx in doneidxs:
continue
for tries in (1, 2, 3):
try:
status, nc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/netcfg')
break
except Exception:
if tries == 3:
raise
time.sleep(1)
continue
nc = json.loads(nc)
if not dc:
for tries in (1, 2, 3):
try:
status, dc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/deploycfg2')
break
except Exception:
if tries == 3:
raise
time.sleep(1)
continue
dc = json.loads(dc)
iname = get_interface_name(idxmap[curridx], nc.get('default', {}))
if iname:
for iname in iname.split(','):
if 'default' in netname_to_interfaces:
netname_to_interfaces['default']['interfaces'].add(iname)
else:
netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']}
for netname in nc.get('extranets', {}):
uname = '_' + netname
iname = get_interface_name(idxmap[curridx], nc['extranets'][netname])
if iname:
for iname in iname.split(','):
if uname in netname_to_interfaces:
netname_to_interfaces[uname]['interfaces'].add(iname)
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
doneidxs.add(curridx)
if 'default' in netname_to_interfaces:
for netn in netname_to_interfaces:
if netn == 'default':
continue
netname_to_interfaces['default']['interfaces'] -= netname_to_interfaces[netn]['interfaces']
if not netname_to_interfaces['default']['interfaces']:
del netname_to_interfaces['default']
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
if os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
for netn in netname_to_interfaces:
nm.apply_configuration(netname_to_interfaces[netn])
if havefirewall:
subprocess.check_call(['systemctl', 'start', 'firewalld'])
await_tentative()