2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-22 00:49:32 +00:00

Change to allow non-mac ids in discovery

Inventing mac addresses for routed discovery is misleading.

Change to a more accurate indication.
This commit is contained in:
Jarrod Johnson
2026-09-16 12:30:53 -04:00
parent 069d010944
commit 5803dc8ac5
2 changed files with 75 additions and 59 deletions
+18 -18
View File
@@ -81,16 +81,16 @@ async def subscribe_discovery(options, session, subscribe, targ):
if 'status' in rsp:
print(rsp['status'])
async def print_disco(options, session, currmac, outhandler, columns):
async def print_disco(options, session, currid, outhandler, columns):
procinfo = {}
async for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
async for tmpinfo in session.read('/discovery/by-id/{0}'.format(currid)):
procinfo.update(tmpinfo)
if 'Switch' in columns or 'Port' in columns:
if 'switch' in procinfo:
procinfo['port'] = procinfo['switchport']
else:
async for tmpinfo in session.read(
'/networking/macs/by-mac/{0}'.format(currmac)):
'/networking/macs/by-id/{0}'.format(currid)):
if 'ports' in tmpinfo:
# The api sorts so that the most specific available value
# is last
@@ -164,7 +164,7 @@ async def search_record(datum, options, session):
options.__dict__[searchkey] = None
for searchkey in searchkeys & set(datum):
options.__dict__[searchkey] = datum[searchkey]
return [x async for x in list_matching_macs(options, session)]
return [x async for x in list_matching_ents(options, session)]
def datum_to_attrib(datum):
@@ -259,7 +259,7 @@ async def assign_macs(maclist, nodename, assignlimit):
# time and the caller's is busy defining the remaining nodes
mysess = client.Command()
for mac in maclist:
async for res in mysess.update('/discovery/by-mac/{0}'.format(mac),
async for res in mysess.update('/discovery/by-id/{0}'.format(mac),
{'node': nodename}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
@@ -288,8 +288,8 @@ async def list_discovery(options, session):
if options.order.lower() == field.lower():
orderby = field
outhandler = client.Tabulator(columns)
for mac in [x async for x in list_matching_macs(options, session)]:
await print_disco(options, session, mac, outhandler, columns)
for infoid in [x async for x in list_matching_ents(options, session)]:
await print_disco(options, session, infoid, outhandler, columns)
if options.csv:
outhandler.write_csv(sys.stdout, orderby)
else:
@@ -297,15 +297,15 @@ async def list_discovery(options, session):
print(row)
async def clear_discovery(options, session):
allmacs = [x async for x in list_matching_macs(options, session)]
for mac in allmacs:
async for res in session.delete('/discovery/by-mac/{0}'.format(mac)):
allidentifiers = [x async for x in list_matching_ents(options, session)]
for infoid in allidentifiers:
async for res in session.delete('/discovery/by-id/{0}'.format(infoid)):
if 'deleted' in res:
print('Cleared info for {0}'.format(res['deleted']))
else:
print(repr(res))
async def list_matching_macs(options, session, node=None, checknode=True):
async def list_matching_ents(options, session, node=None, checknode=True):
path = '/discovery/'
if node:
path += 'by-node/{0}/'.format(node)
@@ -324,14 +324,14 @@ async def list_matching_macs(options, session, node=None, checknode=True):
options.state = 'unidentified'
path += 'by-state/{0}/'.format(options.state).lower()
if options.mac:
path += 'by-mac/{0}'.format(options.mac)
path += 'by-id/{0}'.format(options.mac)
result = list([x async for x in session.read(path)])[0]
if 'error' in result:
return
yield options.mac.replace(':', '-')
return
else:
path += 'by-mac/'
path += 'by-id/'
async for x in session.read(path):
if 'item' in x and 'href' in x['item']:
yield x['item']['href']
@@ -350,16 +350,16 @@ async def assign_discovery(options, session, needid=True):
abort = True
if abort:
sys.exit(1)
matches = [x async for x in list_matching_macs(options, session, None if needid else options.node, False)]
matches = [x async for x in list_matching_ents(options, session, None if needid else options.node, False)]
if not matches:
# Do a rescan to catch missing requested data
await blocking_scan(session)
matches = [x async for x in list_matching_macs(options, session, None if needid else options.node, False)]
matches = [x async for x in list_matching_ents(options, session, None if needid else options.node, False)]
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
exitcode = 0
async for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
async for res in session.update('/discovery/by-id/{0}'.format(matches[0]),
{'node': options.node}):
if 'assigned' in res:
print('Assigned: {0}'.format(res['assigned']))
@@ -405,8 +405,8 @@ async def main():
'UUID', metavar='UUID')
parser.add_option('-n', '--node', help='Operate with the given nodename')
parser.add_option('-e', '--ethaddr', dest='mac',
help='Operate against the system with the specified MAC '
'address', metavar='MAC')
help='Operate against the system with the specified MAC or IP '
'address if mac not available', metavar='MAC')
parser.add_option('-t', '--type', dest='type',
help='Operate against the system of the specified type',
metavar='TYPE')
+57 -41
View File
@@ -368,9 +368,19 @@ def list_matching_states(criteria):
def list_matching_macs(criteria):
for mac in sorted(list(known_info)):
info = known_info[mac]
if 'hwaddr' not in info:
continue
if _info_matches(info, criteria):
yield msg.ChildCollection(mac.replace(':', '-'))
def list_matching_ids(criteria):
for info_id in sorted(list(known_info)):
info = known_info[info_id]
if 'info_identifier' not in info:
continue
if _info_matches(info, criteria):
yield msg.ChildCollection(info_id)
def list_matching_types(criteria):
rettypes = []
@@ -414,6 +424,7 @@ list_info = {
'by-mac': list_matching_macs,
'by-state': list_matching_states,
'by-uuid': list_matching_uuids,
'by-id': list_matching_ids,
}
multi_selectors = set([
@@ -432,6 +443,7 @@ node_selectors = set([
single_selectors = set([
'by-mac',
'by-id',
])
@@ -554,8 +566,7 @@ async def register_remote_addrs(addresses, configmanager):
try:
sd = await ssdp.check_fish(('/DeviceDescription.json', nd))
if not sd:
hwaddr = ':'.join(['{:02x}'.format(x) for x in hashlib.sha256(addr.encode('utf-8')).digest()[:6]])
return addr, await generic_eval(addr, hwaddr)
return addr, await generic_eval(addr)
if 'macaddress' in sd['attributes']:
sd['hwaddr'] = sd['attributes']['macaddress']
else:
@@ -676,7 +687,7 @@ def handle_read_api_request(pathcomponents):
dirlist.append(msg.ChildCollection('subscriptions/'))
return dirlist
if not coll:
return show_info(queryparms['by-mac'])
return show_info(queryparms.get('by-id', queryparms.get('by-mac')))
if not indexof:
return [msg.ChildCollection(x + '/') for x in sorted(list(subcats))]
if indexof not in list_info:
@@ -765,23 +776,21 @@ async def _recheck_single_unknown_info(configmanager, info):
handler = info['handler'].NodeHandler(info, configmanager)
if handler.https_supported and not await handler.get_https_cert():
if handler.cert_fail_reason == 'unreachable':
log.log(
{
'info': '{0} with hwaddr {1} is not reachable at {2}'
''.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
if info.get('hwaddr'):
infotxt = f'{handler.devname} with hwaddr {info["hwaddr"]} is not reachable at {handler.ipaddr}'
else:
infotxt = f'{handler.devname} is not reachable at {handler.ipaddr}'
log.log({'info': infotxt})
# addresses data is bad, delete the offending ip
info['addresses'] = [x for x in info.get('addresses', []) if x != handler.ipaddr]
# TODO(jjohnson2): rescan due to bad peer addr data?
# not just wait around for the next announce
return
log.log(
{
'info': '{0} with hwaddr {1} at address {2} is not yet running '
'https, will examine later'.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
if info.get('hwaddr'):
infotxt = f'{handler.devname} with hwaddr {info["hwaddr"]} at address {handler.ipaddr} is not yet running https, will examine later'
else:
infotxt = f'{handler.devname} at address {handler.ipaddr} is not yet running https, will examine later'
log.log({'info': infotxt})
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
rechecker.cancel()
# if cancel did not result in dead, then we are in progress
@@ -806,12 +815,14 @@ async def _recheck_single_unknown_info(configmanager, info):
def safe_detected(info):
if 'hwaddr' not in info or not info['hwaddr']:
infoid = info.get('hwaddr', info.get('addresses', [[None]])[0][0])
if not infoid:
return
if info['hwaddr'] in runningevals:
if infoid in runningevals:
# Do not evaluate the same mac multiple times at once
return
runningevals[info['hwaddr']] = tasks.spawn_task(eval_detected(info))
info['info_identifier'] = infoid
runningevals[info['info_identifier']] = tasks.spawn_task(eval_detected(info))
async def eval_detected(info):
@@ -819,7 +830,7 @@ async def eval_detected(info):
await detected(info)
except Exception:
traceback.print_exc()
del runningevals[info['hwaddr']]
del runningevals[info['info_identifier']]
async def detected(info):
@@ -843,7 +854,7 @@ async def detected(info):
tasks.spawn_after(10, info['protocol'].fix_info, info,
safe_detected)
return
if info['hwaddr'] in known_info and 'addresses' in info:
if info['info_identifier'] in known_info and 'addresses' in info:
# we should tee these up for parsing when an enclosure comes up
# also when switch config parameters change, should discard
# and there's also if wiring is fixed...
@@ -854,7 +865,7 @@ async def detected(info):
# bz 93219, fix submitted, but not in builds yet
# strictly speaking, going ipv4 only legitimately is mistreated here,
# but that should be an edge case
oldaddr = known_info[info['hwaddr']].get('addresses', [])
oldaddr = known_info[info['info_identifier']].get('addresses', [])
for addr in info['addresses']:
if addr[0].startswith('fe80::'):
break
@@ -862,14 +873,14 @@ async def detected(info):
for addr in oldaddr:
if addr[0].startswith('fe80::'):
info['addresses'].append(addr)
if known_info[info['hwaddr']].get(
if known_info[info['info_identifier']].get(
'addresses', []) == info['addresses']:
# if the ip addresses match, then assume no changes
# now something resetting to defaults could, in theory
# have the same address, but need to be reset
# in that case, however, a user can clear pubkeys to force a check
return
known_info[info['hwaddr']] = info
known_info[info['info_identifier']] = info
cfg = cfm.ConfigManager(None)
if handler:
handler = handler.NodeHandler(info, cfg)
@@ -894,7 +905,7 @@ async def detected(info):
known_serials[info['serialnumber']] = info
uuid = info.get('uuid', None)
if uuid_is_valid(uuid):
known_uuids[uuid][info['hwaddr']] = info
known_uuids[uuid][info['info_identifier']] = info
info['otheraddresses'] = set([])
for i4addr in info.get('attributes', {}).get('ipv4-address', []):
info['otheraddresses'].add(i4addr)
@@ -920,7 +931,7 @@ async def detected(info):
if rechecker is None or rechecker.done():
rechecktime = util.monotonic_time() + 300
rechecker = tasks.spawn_task_after(300, _periodic_recheck, cfg)
unknown_info[info['hwaddr']] = info
unknown_info[info['info_identifier']] = info
info['discostatus'] = 'unidentified'
#TODO, spawn after to recheck sooner, or somehow else
# influence periodic recheck to shorten delay?
@@ -934,7 +945,7 @@ async def detected(info):
{}).get('value', None)
if util.cert_matches(lastfp, await handler.get_https_cert()):
info['nodename'] = nodename
known_nodes[nodename][info['hwaddr']] = info
known_nodes[nodename][info['info_identifier']] = info
info['discostatus'] = 'discovered'
uuid = info.get('uuid', None)
if uuid:
@@ -960,7 +971,7 @@ async def detected(info):
# handler.devname, info['hwaddr'], handler.ipaddr
# )})
info['discostatus'] = 'unidentified'
unknown_info[info['hwaddr']] = info
unknown_info[info['info_identifier']] = info
@@ -1143,8 +1154,11 @@ async def get_nodename(cfg, handler, info):
if not nodename: # as a last resort, search switches for info
# This is the slowest potential operation, so we hope for the
# best to occur prior to this
nodename, macinfo = await macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
maccount = macinfo['maccount']
if 'hwaddr' in info:
nodename, macinfo = await macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
maccount = macinfo['maccount']
else:
maccount = None
if nodename:
if handler.devname in ('SMM', 'SMM3'):
nl = list(cfg.filter_node_attributes(
@@ -1282,12 +1296,12 @@ async def eval_node(cfg, handler, info, nodename, manual=False):
# do some preconfig, for example, to bring a SMM online if applicable
await handler.preconfig(nodename)
except Exception:
unknown_info[info['hwaddr']] = info
unknown_info[info['info_identifier']] = info
info['discostatus'] = 'unidentified'
errorstr = 'An error occurred during discovery, check the ' \
'trace and stderr logs, mac was {0} and ip was {1}' \
', the node or the containing enclosure was {2}' \
''.format(info['hwaddr'], handler.ipaddr, nodename)
''.format(info['info_identifier'], handler.ipaddr, nodename)
traceback.print_exc()
if manual:
raise exc.InvalidArgumentException(errorstr)
@@ -1297,7 +1311,7 @@ async def eval_node(cfg, handler, info, nodename, manual=False):
# switch, it is probably the enclosure manager and not
# the node directly. switch is ambiguous and we should leave it alone
if 'enclosure.bay' in info and handler.is_enclosure:
unknown_info[info['hwaddr']] = info
unknown_info[info['info_identifier']] = info
info['discostatus'] = 'unidentified'
log.log({'error': 'Something that is an enclosure reported a bay, '
'not possible'})
@@ -1318,10 +1332,10 @@ async def eval_node(cfg, handler, info, nodename, manual=False):
pending_nodes[match[2]] = info
return
if 'enclosure.bay' not in info:
unknown_info[info['hwaddr']] = info
unknown_info[info['info_identifier']] = info
info['discostatus'] = 'unidentified'
errorstr = '{2} with mac {0} is in {1}, but unable to ' \
'determine bay number'.format(info['hwaddr'],
errorstr = '{2} with info identifier {0} is in {1}, but unable to ' \
'determine bay number'.format(info['info_identifier'],
nodename,
handler.ipaddr)
if manual:
@@ -1384,7 +1398,7 @@ async def eval_node(cfg, handler, info, nodename, manual=False):
if manual:
raise exc.InvalidArgumentException(errorstr)
log.log({'error': errorstr})
unknown_info[info['hwaddr']] = info
unknown_info[info['info_identifier']] = info
info['discostatus'] = 'unidentified'
return
nodename = nl[0]
@@ -1440,9 +1454,9 @@ async def discover_node(cfg, handler, info, nodename, manual):
raise exc.InvalidArgumentException(
'Attempt to assign {0} conflicts with existing node {1} '
'based on TLS certificate.'.format(nodename, prevnode))
known_nodes[nodename][info['hwaddr']] = info
if info['hwaddr'] in unknown_info:
del unknown_info[info['hwaddr']]
known_nodes[nodename][info['info_identifier']] = info
if info['info_identifier'] in unknown_info:
del unknown_info[info['info_identifier']]
info['discostatus'] = 'identified'
dp = cfg.get_node_attributes(
[nodename], ('discovery.policy', 'id.uuid',
@@ -1738,7 +1752,7 @@ async def blocking_scan(aggressive=False):
scanner = None
async def generic_eval(address, hwaddr):
async def generic_eval(address, hwaddr=None):
ports = await netutil.peer_reachable(address)
if not ports:
return None
@@ -1749,7 +1763,9 @@ async def generic_eval(address, hwaddr):
if not addrinfo:
return None
sockaddr = addrinfo[0][4]
peerdata = {'addresses': [sockaddr], 'hwaddr': hwaddr, 'openports': ports}
peerdata = {'addresses': [sockaddr], 'openports': ports}
if hwaddr:
peerdata['hwaddr'] = hwaddr
if 443 in ports:
resdata = await ssdp.check_fish(('/redfish/v1/', peerdata))
if resdata: