diff --git a/confluent_client/bin/nodediscover b/confluent_client/bin/nodediscover index 49706451..8bf5e03e 100755 --- a/confluent_client/bin/nodediscover +++ b/confluent_client/bin/nodediscover @@ -173,6 +173,13 @@ def list_discovery(options, session): for mac in list_matching_macs(options, session): print_disco(options, session, mac) +def clear_discovery(options, session): + for mac in list_matching_macs(options, session): + for res in session.delete('/discovery/by-mac/{0}'.format(mac)): + if 'deleted' in res: + print('Cleared info for {0}'.format(res['deleted'])) + else: + print(repr(res)) def list_matching_macs(options, session): path = '/discovery/' @@ -184,6 +191,10 @@ def list_matching_macs(options, session): path += 'by-uuid/{0}/'.format(options.uuid) if options.type: path += 'by-type/{0}/'.format(options.type) + if options.state: + if options.state == 'unknown': + options.state = 'unidentified' + path += 'by-state/{0}/'.format(options.state).lower() if options.mac: path += 'by-mac/{0}'.format(options.mac) result = list(session.read(path))[0] @@ -223,7 +234,7 @@ def assign_discovery(options, session): def main(): parser = optparse.OptionParser( - usage='Usage: %prog [list|assign|rescan] [options]') + usage='Usage: %prog [list|assign|rescan|clear] [options]') # -a for 'address' maybe? # order by # show state (discovered or.. @@ -234,6 +245,9 @@ def main(): parser.add_option('-m', '--model', dest='model', help='Operate with nodes matching the specified model ' 'number', metavar='MODEL') + parser.add_option('-d', '--discoverystate', dest='state', + help='The discovery state of the entries (discovered, ' + 'identified, and unidentified)') parser.add_option('-s', '--serial', dest='serial', help='Operate against the system matching the specified ' 'serial number', metavar='SERIAL') @@ -253,12 +267,14 @@ def main(): help='Import bulk assignment data from given CSV file', metavar='IMPORT.CSV') (options, args) = parser.parse_args() - if len(args) == 0 or args[0] not in ('list', 'assign', 'rescan'): + if len(args) == 0 or args[0] not in ('list', 'assign', 'rescan', 'clear'): parser.print_help() sys.exit(1) session = client.Command() if args[0] == 'list': list_discovery(options, session) + if args[0] == 'clear': + clear_discovery(options, session) if args[0] == 'assign': assign_discovery(options, session) if args[0] == 'rescan': diff --git a/confluent_client/doc/man/nodediscover.ronn b/confluent_client/doc/man/nodediscover.ronn index f0be14dc..a4240178 100644 --- a/confluent_client/doc/man/nodediscover.ronn +++ b/confluent_client/doc/man/nodediscover.ronn @@ -5,7 +5,9 @@ nodediscover(8) -- List or manage confluent node discovery `nodediscover rescan` `nodediscover [options] list` -`nodeattrib [options] assign` +`nodediscover [options] assign` +`nodediscover [options] rescan` +`nodediscover [options] clear` ## DESCRIPTION @@ -22,6 +24,14 @@ data may be filtered by various parameters, as denoted in the options below. identity or, using `-i`, using a csv file to assign nodes all at once. For example, a spreadsheet of serial numbers to desired node names could be used. +**nodediscover rescan** requests the server to do an active sweep for new +devices. Generally every effort is made to passively detect devices as they +become available (as they boot or are plugged in), however sometimes an active +scan is the best approach to catch something that appears to be missing. + +**nodedsicover clear** requests the server forget about a selection of +detected device. It takes the same arguments as **nodediscover list**. + ## CSV FORMAT The CSV format used by nodediscover consists of one header describing the @@ -65,12 +75,26 @@ the nodes. * `-e MAC`, `--ethaddr=MAC`: Operate against the system with the specified MAC address -* `-t TYPE, --type=TYPE`: +* `-t TYPE`, `--type=TYPE`: Operate against the system of the specified type -* `-c, --csv`: +* `-c`, `--csv`: Use CSV formatted output * `-i IMPORT.CSV`, `--import=IMPORT.CSV`: Import bulk assignment data from given CSV file +* `-d STATE`, `--discoverystate=STATE`: + Indicate devices with a particular state. The states are listed below +* discovered: The device has been identified and has also had discovery + activities performed, including any relevant certificate + exchanges and deploying user and network configuration. +* identified: The device has been identified as to what node it is + supposed to be, however no active changes to the attributes + or device configuration has been performed. This is + generally discovery awaiting approval due to + discovery.policy specifying a strict security model. +* unidentified: A device has been sensed, but no node identity has been + established at all. It provides data that can be used + for nodediscover assign, as well as current IP addresses + that can be used for manual efforts as possible. ## EXAMPLES diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 9eb689dc..f25519ae 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -359,15 +359,10 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents): raise exc.InvalidArgumentException() rescan() return (msg.KeyValueData({'rescan': 'started'}),) - elif (operation in ('update', 'create')): + elif operation in ('update', 'create'): if 'node' not in inputdata: raise exc.InvalidArgumentException('Missing node name in input') - _, queryparms, _, _ = _parameterize_path(pathcomponents[1:]) - if 'by-mac' not in queryparms: - raise exc.InvalidArgumentException('Must target using "by-mac"') - mac = queryparms['by-mac'].replace('-', ':') - if mac not in known_info: - raise exc.NotFoundException('{0} not found'.format(mac)) + mac = _get_mac_from_query(pathcomponents) info = known_info[mac] if info['handler'] is None: raise exc.NotImplementedException( @@ -377,10 +372,24 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents): eval_node(configmanager, handler, info, inputdata['node'], manual=True) return [msg.AssignedResource(inputdata['node'])] + elif operation == 'delete': + mac = _get_mac_from_query(pathcomponents) + del known_info[mac] + return [msg.DeletedResource(mac)] raise exc.NotImplementedException( 'Unable to {0} to {1}'.format(operation, '/'.join(pathcomponents))) +def _get_mac_from_query(pathcomponents): + _, queryparms, _, _ = _parameterize_path(pathcomponents[1:]) + if 'by-mac' not in queryparms: + raise exc.InvalidArgumentException('Must target using "by-mac"') + mac = queryparms['by-mac'].replace('-', ':') + if mac not in known_info: + raise exc.NotFoundException('{0} not found'.format(mac)) + return mac + + def handle_read_api_request(pathcomponents): # TODO(jjohnson2): This should be more generalized... # odd indexes into components are 'by-'*, even indexes @@ -812,10 +821,14 @@ def get_nodename_from_enclosures(cfg, info): def eval_node(cfg, handler, info, nodename, manual=False): try: handler.probe() # unicast interrogation as possible to get more data - # for now, we search switch only, ideally we search cmm, smm, and # switch concurrently # do some preconfig, for example, to bring a SMM online if applicable - handler.preconfig() + errorstr = handler.preconfig() + if errorstr: + if manual: + raise exc.InvalidArgumentException(errorstr) + log.log({'error': errorstr}) + return except Exception as e: unknown_info[info['hwaddr']] = info info['discostatus'] = 'unidentified' diff --git a/confluent_server/confluent/discovery/handlers/xcc.py b/confluent_server/confluent/discovery/handlers/xcc.py index ad222489..0e370dbb 100644 --- a/confluent_server/confluent/discovery/handlers/xcc.py +++ b/confluent_server/confluent/discovery/handlers/xcc.py @@ -22,7 +22,24 @@ class NodeHandler(immhandler.NodeHandler): devname = 'XCC' def preconfig(self): - ff = self.info.get('attributes', {}).get('enclosure-form-factor', '') + ff = None + try: + ff = self.info['attributes']['enclosure-form-factor'] + except KeyError: + try: + # an XCC should always have that set, this is sign of + # a bug, try to reset the BMC as a workaround + ipmicmd = self._get_ipmicmd() + ipmicmd.reset_bmc() + return "XCC with address {0} did not have attributes " \ + "declared, attempting to correct with " \ + "XCC reset".format(self.ipaddr) + except pygexc.IpmiException as e: + if (e.ipmicode != 193 and + 'Unauthorized name' not in str(e) and + 'Incorrect password' not in str(e)): + # raise an issue if anything other than to be expected + raise if ff not in ('dense-computing', [u'dense-computing']): return # attempt to enable SMM