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:
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user