#!/usr/bin/python3 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017-2021 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 script demonstrates a strategy for redfish bmcs that # dhcp to leverage the confluent switch scanning to help # bootstrap such devices. Be aware of the uuid reformatting # code, and determine if it is relevant for the target system # in question. The normal thing would be to leave UUID as-is, # but some implementations mangle it in a misunderstanding # of 'wire format' UUID. Also, here xCAT is used as the # 'dhcp helper', so that may need to be replaced with dnsmasq # or direct isc dhcp code. # Unfortunately, this is particular about the dhcp server, # the user must know if the bmc in question mangles the uuid # or not, and other such limitation make this difficult to blindly # recommend, but hopefully can be useful reference material import sys sys.path.append('/opt/confluent/lib/python') import confluent.client as cli import eventlet.greenpool import gzip import io import json import os import struct import subprocess import time webclient = eventlet.import_patched('pyghmi.util.webclient') bmcsbyuuid = {} def checkfish(addr, mac): wc = webclient.SecureHTTPConnection(addr, 443, verifycallback=lambda x: True) wc.connect() wc.request('GET', '/redfish/v1') rsp = wc.getresponse() body = rsp.read() if body[:2] == b'\x1f\x8b': body = gzip.GzipFile(fileobj=io.BytesIO(body)).read() try: body = json.loads(body) except json.decoder.JSONDecodeError: return uuid = body.get('UUID', None) if not uuid: return #This part is needed if a bmc sticks 'wire format' uuid in the json body #Should be skipped for bmcs that present it sanely uuidparts = uuid.split('-') uuidparts[0] = '{:08x}'.format(struct.unpack('!I', struct.pack('<I', int(uuidparts[0], 16)))[0]) uuidparts[1] = '{:04x}'.format(struct.unpack('!H', struct.pack('<H', int(uuidparts[1], 16)))[0]) uuidparts[2] = '{:04x}'.format(struct.unpack('!H', struct.pack('<H', int(uuidparts[2], 16)))[0]) uuid = '-'.join(uuidparts) if uuid in bmcsbyuuid: bmcsbyuuid[uuid]['bmcs'][mac] = addr else: bmcsbyuuid[uuid] = {'bmcs': {mac: addr}} if __name__ == '__main__': gpool = eventlet.greenpool.GreenPool() with open('/var/lib/dhcpd/dhcpd.leases', 'r') as leasefile: leases = leasefile.read() inlease = False currip = None mactoips = {} for line in leases.split('\n'): if line.startswith('lease '): currip = line.split()[1] inlease = True continue if not inlease: continue if 'hardware ethernet' in line: currmac = line.split()[-1].replace(';', '') mactoips[currmac] = currip currmac = None currip = None inlease = False # warm up arp tables and fdb pings = {} for mac in mactoips: pings[mac] = subprocess.Popen(['ping', '-c', '1', mactoips[mac]], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) for mac in pings: ret = pings[mac].wait() if ret != 0: del mactoips[mac] c = cli.Command() list(c.update('/networking/macs/rescan', {'rescan': 'start'})) scanning = True mactonode = {} while scanning: for rsp in c.read('/networking/macs/rescan'): scanning = rsp.get('scanning', True) time.sleep(0.1) for mac in mactoips: macinfo = list(c.read('/networking/macs/by-mac/{}'.format(mac))) for inf in macinfo: if inf.get('possiblenode', None): mactonode[mac] = inf['possiblenode'] for mac in sorted(mactonode): gpool.spawn(checkfish, mactoips[mac], mac) gpool.waitall() for uuid in sorted(bmcsbyuuid): macd = bmcsbyuuid[uuid]['bmcs'] macs = sorted(macd) currnode = None for mac in macs: currnode = mactonode.get(mac, None) if currnode: break print('Performing: nodeattrib {} id.uuid={} custom.bmcmac={} bmc={}'.format(currnode, uuid, macs[0], macd[macs[0]])) list(c.update('/nodes/{}/attributes/current'.format(currnode), {'id.uuid': uuid, 'custom.bmcmac': macs[0], 'bmc': macd[macs[0]]})) subprocess.check_call(['nodeadd', currnode + '-bmc', 'mac.mac=' + macs[0]]) subprocess.check_call(['makedhcp', currnode + '-bmc']) subprocess.check_call(['nodeboot', currnode]) subprocess.check_call(['nodebmcreset', currnode]) list(c.update('/nodes/{}/attributes/current'.format(currnode), {'bmc': currnode + '-bmc'}))