From feb12b95cb6b8db5f0383ef9852043d6ef5214b0 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 27 Oct 2021 11:52:09 -0400 Subject: [PATCH] Add facility to enable confignet Provide function to populate a map of all networking, relative to the interface facing thte client. --- .../confluent/config/attributes.py | 20 +++ confluent_server/confluent/netutil.py | 135 ++++++++++++++++-- 2 files changed, 143 insertions(+), 12 deletions(-) diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index eb93fbd4..3394cf13 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -441,6 +441,19 @@ node = { 'description': 'Whether or not the indicated network interface is to be used for booting. This is used by ' 'the discovery process to decide where to place the mac address of a detected PXE nic.', }, + 'net.connection_name': { + 'description': 'Name to use when specifiying a name for connection and/or interface name for a team. This may be the name of a team interface, ' + 'the connection name in network manager for the interface, or may be installed as an altname ' + 'as supported by the respective OS deployment profiles. Default is to accept default name for ' + 'a team consistent with the respective OS, or to use the matching original port name as connection name.' + }, + 'net.interface_names': { + 'description': 'Interface name or comma delimited list of names to match for this interface. It is generally recommended ' + 'to leave this blank unless needing to set up interfaces that are not on a common subnet with a confluent server, ' + 'as confluent servers provide autodetection for matching the correct network definition to an interface.' + 'This would be the default name per the deployed OS and can be a comma delimited list to denote members of ' + 'a team'. + }, 'net.ipv4_address': { 'description': 'When configuring static, use this address. If ' 'unspecified, it will check if the node name resolves ' @@ -513,6 +526,13 @@ node = { 'depending on default configuration of the respective ' 'operating system', }, + 'net.team_mode': { + 'description': 'Indicates that this interface should be a team and what mode or runner to use when teamed. ' + 'If this covers a deployment interface, one of the member interfaces may be brought up as ' + 'a standalone interface until deployment is complete, as supported by the OS deployment profile. ' + 'To support this scenario, the switch should be set up to allow independent operation of member ports123654 (e.g. lacp bypass mode or fallback mode).', + 'validvalues': ('lacp', 'loadbalance', 'roundrobin', 'activebackup', 'none') + }, # 'id.modelnumber': { # 'description': 'The manufacturer dictated model number for the node', # }, diff --git a/confluent_server/confluent/netutil.py b/confluent_server/confluent/netutil.py index c5f3f6a2..473d6ef1 100644 --- a/confluent_server/confluent/netutil.py +++ b/confluent_server/confluent/netutil.py @@ -144,6 +144,114 @@ def inametonets(iname): net = socket.inet_ntoa(struct.pack('!I', net)) yield (net, mask_to_cidr(addr['netmask']), addr['addr']) + +class NetManager(object): + def __init__(self, myaddrs, node, configmanager): + self.myaddrs = myaddrs + self.cfm = configmanager + self.node = node + self.myattribs = {} + self.consumednames4 = set([]) + self.consumednames6 = set([]) + + def process_attribs(self, netname, attribs): + self.myattribs[netname] = {} + ipv4addr = None + ipv6addr = None + myattribs = self.myattribs[netname] + hwaddr = attribs.get('hwaddr', None) + if hwaddr: + myattribs['hwaddr'] = hwaddr + conname = attribs.get('connection_name', None) + if conname: + myattribs['connection_name'] = conname + iname = attribs.get('interface_names', None) + if iname: + myattribs['interface_names'] = iname + teammod = attribs.get('team_mode', None) + if teammod: + myattribs['team_mode'] = teammod + method = attribs.get('ipv4_method', None) + if method != 'dhcp': + ipv4addr = attribs.get('ipv4_address', None) + if not ipv4addr: + currname = attribs.get('hostname', self.node).split()[0] + if currname and currname not in self.consumednames4: + for ai in socket.getaddrinfo(currname, 0, socket.AF_INET, socket.SOCK_STREAM): + ipv4addr = ai[-1][0] + self.consumednames4.add(currname) + if ipv4addr: + myattribs['ipv4_method'] = 'static' + myattribs['ipv4_address'] = ipv4addr + else: + myattribs['ipv4_method'] = 'dhcp' + if attribs.get('ipv4_gateway', None) and 'ipv4_method' in self.myattribs[netname]: + myattribs['ipv4_gateway'] = attribs['ipv4_gateway'] + method = attribs.get('ipv6_method', None) + if method != 'dhcp': + ipv6addr = attribs.get('ipv6_address', None) + if not ipv6addr: + currname = attribs.get('hostname', self.node).split()[0] + if currname and currname not in self.consumednames6: + for ai in socket.getaddrinfo(currname, 0, socket.AF_INET6, socket.SOCK_STREAM): + ipv6addr = ai[-1][0] + self.consumednames6.add(currname) + if ipv6addr: + myattribs['ipv6_methad'] = 'static' + myattribs['ipv6_address'] = ipv6addr + if attribs.get('ipv6_gateway', None) and 'ipv6_method' in myattribs: + myattribs['ipv6_gateway'] = attribs['ipv6_gateway'] + if 'ipv4_method' not in myattribs and 'ipv6_method' not in myattribs: + del self.myattribs[netname] + return + if ipv4addr: + ipv4bytes = socket.inet_pton(socket.AF_INET, ipv4addr) + for addr in self.myaddrs: + if addr[0] != socket.AF_INET: + continue + if ipn_on_same_subnet(addr[0], addr[1], ipv4bytes, addr[2]): + myattribs['current_nic'] = True + if not myattribs.get('current_nic', False) and ipv6addr: + ipv6bytes = socket.inet_pton(socket.AF_INET6, ipv6addr) + for addr in self.myaddrs: + if addr[0] != socket.AF_INET6: + continue + if ipn_on_same_subnet(addr[0], addr[1], ipv6bytes, addr[2]): + myattribs['current_nic'] = True + if 'current_nic' not in myattribs: + myattribs['current_nic'] = False + + +def get_full_net_config(configmanager, node, serverip=None): + cfd = configmanager.get_node_attributes(node, ['net.*']) + cfd = cfd.get(node, {}) + attribs = {} + for attrib in cfd: + val = cfd[attrib].get('value', None) + if val is None: + continue + if attrib.startswith('net.'): + attrib = attrib.replace('net.', '').rsplit('.', 1) + if len(attrib) == 1: + iface = None + attrib = attrib[0] + else: + iface, attrib = attrib + if attrib in ('switch', 'switchport', 'bootable'): + continue + if iface not in attribs: + attribs[iface] = {} + attribs[iface][attrib] = val + myaddrs = None + if serverip: + myaddrs = get_addresses_by_serverip(serverip) + nm = NetManager(myaddrs, node, configmanager) + for netname in attribs: + nm.process_attribs(netname, attribs[netname]) + return nm.myattribs + + + # TODO(jjohnson2): have a method to arbitrate setting methods, to aid # in correct matching of net.* based on parameters, mainly for pxe # The scheme for pxe: @@ -213,19 +321,8 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, if v6broken: cfgdata['ipv6_broken'] = True if serverip is not None: - if '.' in serverip: - fam = socket.AF_INET - elif ':' in serverip: - fam = socket.AF_INET6 - else: - raise ValueError('"{0}" is not a valid ip argument') - ipbytes = socket.inet_pton(fam, serverip) dhcprequested = False - matchlla = None - if ipbytes[:8] == b'\xfe\x80\x00\x00\x00\x00\x00\x00': - myaddrs = get_my_addresses(matchlla=ipbytes) - else: - myaddrs = [x for x in get_my_addresses() if x[1] == ipbytes] + myaddrs = get_addresses_by_serverip(serverip) genericmethod = 'static' ipbynodename = None ip6bynodename = None @@ -364,6 +461,20 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, break return cfgdata +def get_addresses_by_serverip(serverip): + if '.' in serverip: + fam = socket.AF_INET + elif ':' in serverip: + fam = socket.AF_INET6 + else: + raise ValueError('"{0}" is not a valid ip argument') + ipbytes = socket.inet_pton(fam, serverip) + if ipbytes[:8] == b'\xfe\x80\x00\x00\x00\x00\x00\x00': + myaddrs = get_my_addresses(matchlla=ipbytes) + else: + myaddrs = [x for x in get_my_addresses() if x[1] == ipbytes] + return myaddrs + nlhdrsz = struct.calcsize('IHHII') ifaddrsz = struct.calcsize('BBBBI')