2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-02-07 16:22:30 +00:00

Migrate XCC3 discovery to async

This commit is contained in:
Jarrod Johnson
2024-08-08 12:45:39 -04:00
parent 73cd6d52da
commit 67a61c5012
5 changed files with 70 additions and 70 deletions

View File

@@ -31,7 +31,7 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.asynclient as client
async def main():
argparser = optparse.OptionParser(

View File

@@ -297,7 +297,7 @@ async def list_matching_macs(options, session, node=None, checknode=True):
path += 'by-state/{0}/'.format(options.state).lower()
if options.mac:
path += 'by-mac/{0}'.format(options.mac)
result = list(session.read(path))[0]
result = list([x async for x in session.read(path)])[0]
if 'error' in result:
return
yield options.mac.replace(':', '-')
@@ -331,7 +331,7 @@ async def assign_discovery(options, session, needid=True):
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
exitcode = 0
for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
async for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
{'node': options.node}):
if 'assigned' in res:
print('Assigned: {0}'.format(res['assigned']))

View File

@@ -1410,7 +1410,7 @@ async def discover_node(cfg, handler, info, nodename, manual):
elif manual or not util.cert_matches(lastfp, handler.https_cert):
# only 'discover' if it is not the same as last time
try:
handler.config(nodename)
await handler.config(nodename)
except Exception as e:
info['discofailure'] = 'bug'
if manual:
@@ -1448,7 +1448,7 @@ async def discover_node(cfg, handler, info, nodename, manual):
for checkattr in newnodeattribs:
checkval = currattrs.get(nodename, {}).get(checkattr, {}).get('value', None)
if checkval != newnodeattribs[checkattr]:
cfg.set_node_attributes({nodename: newnodeattribs})
await cfg.set_node_attributes({nodename: newnodeattribs})
break
log.log({'info': 'Discovered {0} ({1})'.format(nodename,
handler.devname)})

View File

@@ -12,32 +12,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import confluent.discovery.handlers.generic as generic
import confluent.exceptions as exc
import confluent.netutil as netutil
import confluent.util as util
import eventlet
import eventlet.support.greendns
import json
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
getaddrinfo = eventlet.support.greendns.getaddrinfo
from socket import getaddrinfo
webclient = eventlet.import_patched('pyghmi.util.webclient')
import aiohmi.util.webclient as webclient
def get_host_interface_urls(wc, mginfo):
async def get_host_interface_urls(wc, mginfo):
returls = []
hifurl = mginfo.get('HostInterfaces', {}).get('@odata.id', None)
if not hifurl:
return None
hifinfo = wc.grab_json_response(hifurl)
hifinfo = await wc.grab_json_response(hifurl)
hifurls = hifinfo.get('Members', [])
for hifurl in hifurls:
hifurl = hifurl['@odata.id']
hifinfo = wc.grab_json_response(hifurl)
hifinfo = await wc.grab_json_response(hifurl)
acturl = hifinfo.get('ManagerEthernetInterface', {}).get('@odata.id', None)
if acturl:
returls.append(acturl)
@@ -61,30 +60,32 @@ class NodeHandler(generic.NodeHandler):
self._mgrinfo = None
super(NodeHandler, self).__init__(info, configmanager)
def srvroot(self, wc):
async def srvroot(self, wc):
if not self._srvroot:
srvroot, status = wc.grab_json_response_with_status('/redfish/v1/')
srvroot, status = await wc.grab_json_response_with_status('/redfish/v1/')
if status == 200:
self._srvroot = srvroot
return self._srvroot
def mgrinfo(self, wc):
async def mgrinfo(self, wc):
if not self._mgrinfo:
mgrs = self.srvroot(wc)['Managers']['@odata.id']
rsp = wc.grab_json_response(mgrs)
svroot = await self.srvroot(wc)
mgrs = svroot['Managers']['@odata.id']
rsp = await wc.grab_json_response(mgrs)
if len(rsp['Members']) != 1:
raise Exception("Can not handle multiple Managers")
mgrurl = rsp['Members'][0]['@odata.id']
self._mgrinfo = wc.grab_json_response(mgrurl)
self._mgrinfo = await wc.grab_json_response(mgrurl)
return self._mgrinfo
def get_firmware_default_account_info(self):
raise Exception('This must be subclassed')
def scan(self):
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
i = c.grab_json_response('/redfish/v1/')
async def scan(self):
await self.get_https_cert()
c = webclient.WebConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
i = await c.grab_json_response('/redfish/v1/')
uuid = i.get('UUID', None)
if uuid:
self.info['uuid'] = uuid.lower()
@@ -95,18 +96,19 @@ class NodeHandler(generic.NodeHandler):
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def enable_ipmi(self, wc):
npu = self.mgrinfo(wc).get(
async def enable_ipmi(self, wc):
mgrinfo = await self.mgrinfo(wc)
npu =mgrinfo.get(
'NetworkProtocol', {}).get('@odata.id', None)
if not npu:
raise Exception('Cannot enable IPMI, no NetworkProtocol on BMC')
npi = wc.grab_json_response(npu)
npi = await wc.grab_json_response(npu)
if not npi.get('IPMI', {}).get('ProtocolEnabled'):
wc.set_header('If-Match', '*')
wc.grab_json_response_with_status(
await wc.grab_json_response_with_status(
npu, {'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
acctinfo = wc.grab_json_response_with_status(
self.target_account_url(wc))
acctinfo = await wc.grab_json_response_with_status(
await self.target_account_url(wc))
acctinfo = acctinfo[0]
actypes = acctinfo['AccountTypes']
candidates = acctinfo['AccountTypes@Redfish.AllowableValues']
@@ -116,18 +118,19 @@ class NodeHandler(generic.NodeHandler):
'AccountTypes': actypes,
'Password': self.currpass,
}
rsp = wc.grab_json_response_with_status(
self.target_account_url(wc), acctupd, method='PATCH')
rsp = await wc.grab_json_response_with_status(
await self.target_account_url(wc), acctupd, method='PATCH')
def _get_wc(self):
async def _get_wc(self):
await self.get_https_cert()
defuser, defpass = self.get_firmware_default_account_info()
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
wc = webclient.WebConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
wc.set_basic_credentials(defuser, defpass)
wc.set_header('Content-Type', 'application/json')
wc.set_header('Accept', 'application/json')
authmode = 0
if not self.trieddefault:
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
if status == 403:
self.trieddefault = True
chgurl = None
@@ -144,30 +147,30 @@ class NodeHandler(generic.NodeHandler):
if self.targpass == defpass:
raise Exception("Must specify a non-default password to onboard this BMC")
wc.set_header('If-Match', '*')
cpr = wc.grab_json_response_with_status(chgurl, {'Password': self.targpass}, method='PATCH')
cpr = await wc.grab_json_response_with_status(chgurl, {'Password': self.targpass}, method='PATCH')
if cpr[1] >= 200 and cpr[1] < 300:
self.curruser = defuser
self.currpass = self.targpass
wc.set_basic_credentials(self.curruser, self.currpass)
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
_, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
tries = 10
while status >= 300 and tries:
eventlet.sleep(1)
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
await asyncio.sleep(1)
_, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
return wc
if status > 400:
self.trieddefault = True
if status == 401:
wc.set_basic_credentials(defuser, self.targpass)
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
if status == 200: # Default user still, but targpass
self.currpass = self.targpass
self.curruser = defuser
return wc
elif self.targuser != defuser:
wc.set_basic_credentials(self.targuser, self.targpass)
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
if status != 200:
raise Exception("Target BMC does not recognize firmware default credentials nor the confluent stored credential")
else:
@@ -176,28 +179,29 @@ class NodeHandler(generic.NodeHandler):
return wc
if self.curruser:
wc.set_basic_credentials(self.curruser, self.currpass)
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
if status != 200:
return None
return wc
wc.set_basic_credentials(self.targuser, self.targpass)
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
if status != 200:
return None
self.curruser = self.targuser
self.currpass = self.targpass
return wc
def target_account_url(self, wc):
asrv = self.srvroot(wc).get('AccountService', {}).get('@odata.id')
rsp, status = wc.grab_json_response_with_status(asrv)
async def target_account_url(self, wc):
srvroot = await self.srvroot(wc)
asrv = srvroot.get('AccountService', {}).get('@odata.id')
rsp, status = await wc.grab_json_response_with_status(asrv)
accts = rsp.get('Accounts', {}).get('@odata.id')
rsp, status = wc.grab_json_response_with_status(accts)
rsp, status = await wc.grab_json_response_with_status(accts)
accts = rsp.get('Members', [])
for accturl in accts:
accturl = accturl.get('@odata.id', '')
if accturl:
rsp, status = wc.grab_json_response_with_status(accturl)
rsp, status = await wc.grab_json_response_with_status(accturl)
if rsp.get('UserName', None) == self.curruser:
targaccturl = accturl
break
@@ -205,7 +209,7 @@ class NodeHandler(generic.NodeHandler):
raise Exception("Unable to identify Account URL to modify on this BMC")
return targaccturl
def config(self, nodename):
async def config(self, nodename):
mgrs = None
self.nodename = nodename
creds = self.configmanager.get_node_attributes(
@@ -223,7 +227,7 @@ class NodeHandler(generic.NodeHandler):
passwd = util.stringify(passwd)
self.targuser = user
self.targpass = passwd
wc = self._get_wc()
wc = await self._get_wc()
curruserinfo = {}
authupdate = {}
wc.set_header('Content-Type', 'application/json')
@@ -232,23 +236,23 @@ class NodeHandler(generic.NodeHandler):
if passwd != self.currpass:
authupdate['Password'] = passwd
if authupdate:
targaccturl = self.target_account_url(wc)
rsp, status = wc.grab_json_response_with_status(targaccturl, authupdate, method='PATCH')
targaccturl = await self.target_account_url(wc)
rsp, status = await wc.grab_json_response_with_status(targaccturl, authupdate, method='PATCH')
if status >= 300:
raise Exception("Failed attempting to update credentials on BMC")
self.curruser = user
self.currpass = passwd
wc.set_basic_credentials(user, passwd)
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
_, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
tries = 10
while tries and status >= 300:
tries -= 1
eventlet.sleep(1.0)
_, status = wc.grab_json_response_with_status(
await asyncio.sleep(1.0)
_, status = await wc.grab_json_response_with_status(
'/redfish/v1/Managers')
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
self.enable_ipmi(wc)
await self.enable_ipmi(wc)
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
@@ -259,9 +263,9 @@ class NodeHandler(generic.NodeHandler):
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
hifurls = get_host_interface_urls(wc, self.mgrinfo(wc))
hifurls = await get_host_interface_urls(wc, self.mgrinfo(wc))
mgtnicinfo = self.mgrinfo(wc)['EthernetInterfaces']['@odata.id']
mgtnicinfo = wc.grab_json_response(mgtnicinfo)
mgtnicinfo = await wc.grab_json_response(mgtnicinfo)
mgtnics = [x['@odata.id'] for x in mgtnicinfo.get('Members', [])]
actualnics = []
for candnic in mgtnics:
@@ -270,7 +274,7 @@ class NodeHandler(generic.NodeHandler):
actualnics.append(candnic)
if len(actualnics) != 1:
raise Exception("Multi-interface BMCs are not supported currently")
currnet = wc.grab_json_response(actualnics[0])
currnet = await wc.grab_json_response(actualnics[0])
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
newconfig = {
"Address": newip,
@@ -286,11 +290,11 @@ class NodeHandler(generic.NodeHandler):
break
else:
wc.set_header('If-Match', '*')
rsp, status = wc.grab_json_response_with_status(actualnics[0], {
rsp, status = await wc.grab_json_response_with_status(actualnics[0], {
'DHCPv4': {'DHCPEnabled': False},
'IPv4StaticAddresses': [newconfig]}, method='PATCH')
elif self.ipaddr.startswith('fe80::'):
self.configmanager.set_node_attributes(
await self.configmanager.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(

View File

@@ -13,14 +13,9 @@
# limitations under the License.
import confluent.discovery.handlers.redfishbmc as redfishbmc
import eventlet.support.greendns
import confluent.util as util
webclient = eventlet.import_patched('pyghmi.util.webclient')
getaddrinfo = eventlet.support.greendns.getaddrinfo
import socket
import aiohmi.util.webclient as webclient
class NodeHandler(redfishbmc.NodeHandler):
@@ -29,12 +24,13 @@ class NodeHandler(redfishbmc.NodeHandler):
def get_firmware_default_account_info(self):
return ('USERID', 'PASSW0RD')
def scan(self):
ip, port = self.get_web_port_and_ip()
c = webclient.SecureHTTPConnection(ip, port,
async def scan(self):
ip, port = await self.get_web_port_and_ip()
await self.get_https_cert()
c = webclient.WebConnection(ip, port,
verifycallback=self.validate_cert)
c.set_header('Accept', 'application/json')
i = c.grab_json_response('/api/providers/logoninfo')
i = await c.grab_json_response('/api/providers/logoninfo')
modelname = i.get('items', [{}])[0].get('machine_name', None)
if modelname:
self.info['modelname'] = modelname
@@ -84,7 +80,7 @@ def remote_nodecfg(nodename, cfm):
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
'value', None)
ipaddr = ipaddr.split('/', 1)[0]
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
ipaddr = socket.getaddrinfo(ipaddr, 0)[0][-1]
if not ipaddr:
raise Exception('Cannot remote configure a system without known '
'address')