From 9837ad7932ad2b2da46c797c6a7dba838d9f3b21 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 18 Nov 2022 14:14:31 -0500 Subject: [PATCH] Add multi-address registration This enables scanning a range or subnet --- confluent_client/bin/nodediscover | 31 +++++-- confluent_server/confluent/discovery/core.py | 90 +++++++++++++++++--- confluent_server/confluent/messages.py | 23 +++++ 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/confluent_client/bin/nodediscover b/confluent_client/bin/nodediscover index 995e6f3a..ead5b691 100755 --- a/confluent_client/bin/nodediscover +++ b/confluent_client/bin/nodediscover @@ -51,16 +51,27 @@ columnmapping = { #TODO: add chassis uuid def register_endpoint(options, session, addr): + neednewline = False + current = 0 for rsp in session.update('/discovery/register', {'addresses': addr}): + if 'count' in rsp: + total = rsp['count'] + elif total > 1: + current += 1 + sys.stdout.write('\x1b[2K\r{0:.1f}% Scanned'.format(100 * current / total)) + sys.stdout.flush() + neednewline = True if 'created' in rsp: + if neednewline: + print('') + neednewline = False print('Registered: {0}'.format(rsp['created'])) - else: - sys.stderr.write(repr(rsp)) - sys.stderr.write('\n') + if neednewline: + print('') -def subscribe_discovery(options, session, subscribe): +def subscribe_discovery(options, session, subscribe, targ): keyn = 'subscribe' if subscribe else 'unsubscribe' - payload = {keyn: options.node} + payload = {keyn: targ} if subscribe: for rsp in session.update('/discovery/subscriptions/{0}'.format(options.node), payload): if 'status' in rsp: @@ -383,6 +394,12 @@ def main(): if len(args) == 0 or args[0] not in ('list', 'assign', 'reassign', 'rescan', 'clear', 'subscribe', 'unsubscribe', 'register'): parser.print_help() sys.exit(1) + if args[0] == 'register' and len(args) != 2: + sys.stderr.write('Register requires target address or range (e.g. 192.168.2.30, 192.168.2.0/24, or 192.168.2.1-192.168.2.30)') + sys.exit(1) + if 'subscribe' in args[0] and len(args) != 2: + sys.stderr.write('subscribe/unsubscribe subcommands require switch name as argument') + sys.exit(1) session = client.Command() if args[0] == 'list': list_discovery(options, session) @@ -395,9 +412,9 @@ def main(): if args[0] == 'register': register_endpoint(options, session, args[1]) if args[0] == 'subscribe': - subscribe_discovery(options, session, True) + subscribe_discovery(options, session, True, args[1]) if args[0] == 'unsubscribe': - subscribe_discovery(options, session, False) + subscribe_discovery(options, session, False, args[1]) if args[0] == 'rescan': blocking_scan(session) print("Rescan complete") diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 7be7b838..89c97bb6 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -83,6 +83,7 @@ import json import eventlet import traceback import shlex +import struct import eventlet.green.socket as socket import socket as nsocket import eventlet.green.subprocess as subprocess @@ -385,6 +386,59 @@ single_selectors = set([ ]) +def addr_to_number(addr): + addr = socket.inet_pton(socket.AF_INET, addr) + num = struct.unpack('!I', addr)[0] + return num + +def number_to_addr(number): + addr = struct.pack('!I', number) + addr = socket.inet_ntop(socket.AF_INET, addr) + return addr + +def iterate_addrs(addrs, countonly=False): + if '.' not in addrs: + raise exc.InvalidArgumentException('IPv4 only supported') + if '-' in addrs: + first, last = addrs.split('-', 1) + currn = addr_to_number(first) + last = addr_to_number(last) + if last < currn: + tm = currn + currn = last + currn = tm + if (last - currn) > 65538: + raise exc.InvalidArgumentException("Too many ip addresses") + if countonly: + yield last - currn + 1 + return + while currn <= last: + yield number_to_addr(currn) + currn += 1 + elif '/' in addrs: + first, plen = addrs.split('/', 1) + plen = int(plen) + if plen > 32: + raise exc.InvalidArgumentException("Invalid prefix length") + mask = (2**32 - 1) ^ (2**(32 - plen) - 1) + currn = addr_to_number(first) + currn = currn & mask + numips = 2**(32 - plen) + if numips > 65538: + raise exc.InvalidArgumentException("Too many ip addresses") + if countonly: + yield numips + return + while numips > 0: + yield number_to_addr(currn) + currn += 1 + numips -= 1 + else: + if countonly: + yield 1 + return + yield addrs + def _parameterize_path(pathcomponents): listrequested = False childcoll = True @@ -442,6 +496,30 @@ def save_subscriptions(subs): with open('/etc/confluent/discovery_subscriptions.json', 'w') as dso: dso.write(json.dumps(subs)) + +def register_remote_addrs(addresses, configmanager): + def register_remote_addr(addr): + nd = { + 'addresses': [(addr, 443)] + } + sd = ssdp.check_fish(('/DeviceDescription.json', nd)) + if not sd: + return addr, False + sd['hwaddr'] = sd['attributes']['mac-address'] + nh = xcc.NodeHandler(sd, configmanager) + nh.scan() + detected(nh.info) + return addr, True + rpool = eventlet.greenpool.GreenPool(512) + for count in iterate_addrs(addresses, True): + yield msg.ConfluentResourceCount(count) + for result in rpool.imap(register_remote_addr, iterate_addrs(addresses)): + if result[1]: + yield msg.CreatedResource(result[0]) + else: + yield msg.ConfluentResourceNotFound(result[0]) + + def handle_api_request(configmanager, inputdata, operation, pathcomponents): if pathcomponents == ['discovery', 'autosense']: return handle_autosense_config(operation, inputdata) @@ -477,17 +555,7 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents): if pathcomponents == ['discovery', 'register']: if 'addresses' not in inputdata: raise exc.InvalidArgumentException('Missing address in input') - nd = { - 'addresses': [(inputdata['addresses'], 443)] - } - sd = ssdp.check_fish(('/DeviceDescription.json', nd)) - if not sd: - raise exc.InvalidArgumentException('Target address is not detected or is not a supported device for remote discovery registration') - sd['hwaddr'] = sd['attributes']['mac-address'] - nh = xcc.NodeHandler(sd, configmanager) - nh.scan() - detected(nh.info) - return [msg.CreatedResource(inputdata['address'])] + return register_remote_addrs(inputdata['addresses'], configmanager) if 'node' not in inputdata: raise exc.InvalidArgumentException('Missing node name in input') mac = _get_mac_from_query(pathcomponents) diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 7c6651cd..48ee3126 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -309,6 +309,29 @@ class DeletedResource(ConfluentMessage): pass +class ConfluentResourceNotFound(ConfluentMessage): + notnode = True + apicode = 404 + + def __init__(self, resource): + self.myargs = [resource] + self.desc = 'Not Found' + self.kvpairs = {'missing': resource} + + def strip_node(self, node): + pass + +class ConfluentResourceCount(ConfluentMessage): + notnode = True + + def __init__(self, count): + self.myargs = [count] + self.desc = 'Resource Count' + self.kvpairs = {'count': count} + + def strip_node(self, node): + pass + class CreatedResource(ConfluentMessage): notnode = True readonly = True