mirror of
https://github.com/xcat2/confluent.git
synced 2025-02-22 05:21:09 +00:00
The assumption that /sys/class/net is interfaces is incorrect, when encountering entries that are not interfaces, do not mess up the call.
326 lines
12 KiB
Python
326 lines
12 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2017 Lenovo
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# this will implement noderange grammar
|
|
|
|
|
|
import confluent.exceptions as exc
|
|
import codecs
|
|
import netifaces
|
|
import struct
|
|
import eventlet.green.socket as socket
|
|
import eventlet.support.greendns
|
|
import os
|
|
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
|
|
|
|
|
def mask_to_cidr(mask):
|
|
maskn = socket.inet_pton(socket.AF_INET, mask)
|
|
maskn = struct.unpack('!I', maskn)[0]
|
|
cidr = 32
|
|
while maskn & 0b1 == 0 and cidr > 0:
|
|
cidr -= 1
|
|
maskn >>= 1
|
|
return cidr
|
|
|
|
def cidr_to_mask(cidr):
|
|
return socket.inet_ntop(
|
|
socket.AF_INET, struct.pack('!I', (2**32 - 1) ^ (2**(32 - cidr) - 1)))
|
|
|
|
def ip_on_same_subnet(first, second, prefix):
|
|
if first.startswith('::ffff:') and '.' in first:
|
|
first = first.replace('::ffff:', '')
|
|
if second.startswith('::ffff:') and '.' in second:
|
|
second = second.replace('::ffff:', '')
|
|
addrinf = socket.getaddrinfo(first, None, 0, socket.SOCK_STREAM)[0]
|
|
fam = addrinf[0]
|
|
ip = socket.inet_pton(fam, addrinf[-1][0])
|
|
ip = int(codecs.encode(bytes(ip), 'hex'), 16)
|
|
addrinf = socket.getaddrinfo(second, None, 0, socket.SOCK_STREAM)[0]
|
|
if fam != addrinf[0]:
|
|
return False
|
|
txtaddr = addrinf[-1][0].split('%')[0]
|
|
oip = socket.inet_pton(fam, txtaddr)
|
|
oip = int(codecs.encode(bytes(oip), 'hex'), 16)
|
|
if fam == socket.AF_INET:
|
|
addrlen = 32
|
|
elif fam == socket.AF_INET6:
|
|
addrlen = 128
|
|
else:
|
|
raise Exception("Unknown address family {0}".format(fam))
|
|
mask = 2 ** prefix - 1 << (addrlen - prefix)
|
|
return ip & mask == oip & mask
|
|
|
|
|
|
def address_is_local(address):
|
|
for iface in netifaces.interfaces():
|
|
for i4 in netifaces.ifaddresses(iface).get(2, []):
|
|
cidr = mask_to_cidr(i4['netmask'])
|
|
if ip_on_same_subnet(i4['addr'], address, cidr):
|
|
return True
|
|
for i6 in netifaces.ifaddresses(iface).get(10, []):
|
|
cidr = int(i6['netmask'].split('/')[1])
|
|
laddr = i6['addr'].split('%')[0]
|
|
if ip_on_same_subnet(laddr, address, cidr):
|
|
return True
|
|
return False
|
|
|
|
|
|
_idxtoifnamemap = {}
|
|
def _rebuildidxmap():
|
|
_idxtoifnamemap.clear()
|
|
for iname in os.listdir('/sys/class/net'):
|
|
try:
|
|
ci = int(open('/sys/class/net/{0}/ifindex'.format(iname)).read())
|
|
_idxtoifnamemap[ci] = iname
|
|
except Exception: # there may be non interface in /sys/class/net
|
|
pass
|
|
|
|
|
|
def myiptonets(svrip):
|
|
fam = netifaces.AF_INET
|
|
if ':' in svrip:
|
|
fam = netifaces.AF_INET6
|
|
relevantnic = None
|
|
for iface in netifaces.interfaces():
|
|
for addr in netifaces.ifaddresses(iface).get(fam, []):
|
|
addr = addr.get('addr', '')
|
|
addr = addr.split('%')[0]
|
|
if addresses_match(addr, svrip):
|
|
relevantnic = iface
|
|
break
|
|
else:
|
|
continue
|
|
break
|
|
return inametonets(relevantnic)
|
|
|
|
|
|
def _iftonets(ifidx):
|
|
if isinstance(ifidx, int):
|
|
_rebuildidxmap()
|
|
ifidx = _idxtoifnamemap.get(ifidx, None)
|
|
return inametonets(ifidx)
|
|
|
|
def inametonets(iname):
|
|
addrs = netifaces.ifaddresses(iname)
|
|
try:
|
|
addrs = addrs[netifaces.AF_INET]
|
|
except KeyError:
|
|
return
|
|
for addr in addrs:
|
|
ip = struct.unpack('!I', socket.inet_aton(addr['addr']))[0]
|
|
mask = struct.unpack('!I', socket.inet_aton(addr['netmask']))[0]
|
|
net = ip & mask
|
|
net = socket.inet_ntoa(struct.pack('!I', net))
|
|
yield (net, mask_to_cidr(addr['netmask']), addr['addr'])
|
|
|
|
# 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:
|
|
# For one: the candidate net.* should have pxe set to true, to help
|
|
# disambiguate from interfaces meant for bmc access
|
|
# bmc relies upon hardwaremanagement.manager, plus we don't collect
|
|
# that mac address
|
|
# the ip as reported by recvmsg to match the subnet of that net.* interface
|
|
# if switch and port available, that should match.
|
|
def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
|
serverip=None):
|
|
"""Fetch network configuration parameters for a nic
|
|
|
|
For a given node and interface, find and retrieve the pertinent network
|
|
configuration data. The desired configuration can be searched
|
|
either by ip or by mac.
|
|
|
|
:param configmanager: The relevant confluent.config.ConfigManager
|
|
instance.
|
|
:param node: The name of the node
|
|
:param ip: An IP address on the intended subnet
|
|
:param mac: The mac address of the interface
|
|
:param ifidx: The local index relevant to the network.
|
|
|
|
:returns: A dict of parameters, 'ipv4_gateway', ....
|
|
"""
|
|
# ip parameter *could* be the result of recvmsg with cmsg to tell
|
|
# pxe *our* ip address, or it could be the desired ip address
|
|
#TODO(jjohnson2): ip address, prefix length, mac address,
|
|
# join a bond/bridge, vlan configs, etc.
|
|
# also other nic criteria, physical location, driver and index...
|
|
nodenetattribs = configmanager.get_node_attributes(
|
|
node, 'net*').get(node, {})
|
|
cfgbyname = {}
|
|
for attrib in nodenetattribs:
|
|
segs = attrib.split('.')
|
|
if len(segs) == 2:
|
|
name = None
|
|
else:
|
|
name = segs[1]
|
|
if name not in cfgbyname:
|
|
cfgbyname[name] = {}
|
|
cfgbyname[name][segs[-1]] = nodenetattribs[attrib].get('value',
|
|
None)
|
|
cfgdata = {
|
|
'ipv4_gateway': None,
|
|
'ipv4_address': None,
|
|
'ipv4_method': None,
|
|
'prefix': None,
|
|
}
|
|
nets = None
|
|
needsvrip = False
|
|
if ifidx is not None:
|
|
dhcprequested = False
|
|
nets = list(_iftonets(ifidx))
|
|
if not nets:
|
|
cfgdata['ipv4_broken'] = True
|
|
if serverip is not None:
|
|
needsvrip = True
|
|
dhcprequested = False
|
|
nets = list(myiptonets(serverip))
|
|
genericmethod = 'static'
|
|
ipbynodename = None
|
|
try:
|
|
ipbynodename = socket.getaddrinfo(
|
|
node, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
|
|
except Exception:
|
|
ipbynodename = None
|
|
if nets is not None:
|
|
candgws = []
|
|
candsrvs = []
|
|
for net in nets:
|
|
net, prefix, svrip = net
|
|
candsrvs.append(svrip)
|
|
cfgdata['deploy_server'] = svrip
|
|
for candidate in cfgbyname:
|
|
ipmethod = cfgbyname[candidate].get('ipv4_method', 'static')
|
|
if ipmethod == 'dhcp':
|
|
dhcprequested = True
|
|
continue
|
|
if ipmethod == 'firmwaredhcp':
|
|
genericmethod = ipmethod
|
|
candip = cfgbyname[candidate].get('ipv4_address', None)
|
|
if candip and '/' in candip:
|
|
candip, candprefix = candip.split('/')
|
|
if int(candprefix) != prefix:
|
|
continue
|
|
candgw = cfgbyname[candidate].get('ipv4_gateway', None)
|
|
if candip:
|
|
try:
|
|
if ip_on_same_subnet(net, candip, prefix):
|
|
cfgdata['ipv4_address'] = candip
|
|
cfgdata['ipv4_method'] = ipmethod
|
|
cfgdata['ipv4_gateway'] = cfgbyname[candidate].get(
|
|
'ipv4_gateway', None)
|
|
cfgdata['prefix'] = prefix
|
|
if ipbynodename and ipbynodename == candip:
|
|
cfgdata['matchesnodename'] = True
|
|
return cfgdata
|
|
except Exception as e:
|
|
cfgdata['error_msg'] = "Error trying to evaluate net.*ipv4_address attribute value '{0}' on {1}: {2}".format(candip, node, str(e))
|
|
elif candgw:
|
|
if ip_on_same_subnet(net, candgw, prefix):
|
|
candgws.append(candgw)
|
|
if dhcprequested:
|
|
if not cfgdata.get('ipv4_method', None):
|
|
cfgdata['ipv4_method'] = 'dhcp'
|
|
return cfgdata
|
|
if ipbynodename == None:
|
|
return cfgdata
|
|
for net in nets:
|
|
net, prefix, svrip = net
|
|
if ip_on_same_subnet(net, ipbynodename, prefix):
|
|
cfgdata['matchesnodename'] = True
|
|
cfgdata['ipv4_address'] = ipbynodename
|
|
cfgdata['ipv4_method'] = genericmethod
|
|
cfgdata['prefix'] = prefix
|
|
break
|
|
for svr in candsrvs:
|
|
if ip_on_same_subnet(svr, ipbynodename, prefix):
|
|
cfgdata['deploy_server'] = svr
|
|
break
|
|
for gw in candgws:
|
|
if ip_on_same_subnet(gw, ipbynodename, prefix):
|
|
cfgdata['ipv4_gateway'] = gw
|
|
break
|
|
return cfgdata
|
|
if ip is not None:
|
|
prefixlen = get_prefix_len_for_ip(ip)
|
|
cfgdata['prefix'] = prefixlen
|
|
for setting in nodenetattribs:
|
|
if 'ipv4_gateway' not in setting:
|
|
continue
|
|
gw = nodenetattribs[setting].get('value', None)
|
|
if gw is None or not gw:
|
|
continue
|
|
if ip_on_same_subnet(ip, gw, prefixlen):
|
|
cfgdata['ipv4_gateway'] = gw
|
|
break
|
|
return cfgdata
|
|
|
|
|
|
def get_prefix_len_for_ip(ip):
|
|
# for now, we'll use the system route table
|
|
# later may provide for configuration lookup to override the route
|
|
# table
|
|
ip = getaddrinfo(ip, 0, socket.AF_INET)[0][-1][0]
|
|
try:
|
|
ipn = socket.inet_aton(ip)
|
|
except socket.error: # For now, assume 64 for ipv6
|
|
return 64
|
|
# It comes out big endian, regardless of host arch
|
|
ipn = struct.unpack('>I', ipn)[0]
|
|
rf = open('/proc/net/route')
|
|
ri = rf.read()
|
|
rf.close()
|
|
ri = ri.split('\n')[1:]
|
|
for rl in ri:
|
|
if not rl:
|
|
continue
|
|
rd = rl.split('\t')
|
|
if rd[1] == '00000000': # default gateway, not useful for this
|
|
continue
|
|
# don't have big endian to look at, assume that it is host endian
|
|
maskn = struct.unpack('I', struct.pack('>I', int(rd[7], 16)))[0]
|
|
netn = struct.unpack('I', struct.pack('>I', int(rd[1], 16)))[0]
|
|
if ipn & maskn == netn:
|
|
nbits = 0
|
|
while maskn:
|
|
nbits += 1
|
|
maskn = maskn << 1 & 0xffffffff
|
|
return nbits
|
|
raise exc.NotImplementedException("Non local addresses not supported")
|
|
|
|
def addresses_match(addr1, addr2):
|
|
"""Check two network addresses for similarity
|
|
|
|
Is it zero padded in one place, not zero padded in another? Is one place by name and another by IP??
|
|
Is one context getting a normal IPv4 address and another getting IPv4 in IPv6 notation?
|
|
This function examines the two given names, performing the required changes to compare them for equivalency
|
|
|
|
:param addr1:
|
|
:param addr2:
|
|
:return: True if the given addresses refer to the same thing
|
|
"""
|
|
for addrinfo in socket.getaddrinfo(addr1, 0, 0, socket.SOCK_STREAM):
|
|
rootaddr1 = socket.inet_pton(addrinfo[0], addrinfo[4][0])
|
|
if addrinfo[0] == socket.AF_INET6 and rootaddr1[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
|
|
# normalize to standard IPv4
|
|
rootaddr1 = rootaddr1[-4:]
|
|
for otherinfo in socket.getaddrinfo(addr2, 0, 0, socket.SOCK_STREAM):
|
|
otheraddr = socket.inet_pton(otherinfo[0], otherinfo[4][0])
|
|
if otherinfo[0] == socket.AF_INET6 and otheraddr[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
|
|
otheraddr = otheraddr[-4:]
|
|
if otheraddr == rootaddr1:
|
|
return True
|
|
return False
|