From 67a61c50125db2bbe36f9c82e166d1888fead080 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 8 Aug 2024 12:45:39 -0400 Subject: [PATCH] Migrate XCC3 discovery to async --- confluent_client/bin/nodedefine | 2 +- confluent_client/bin/nodediscover | 4 +- confluent_server/confluent/discovery/core.py | 4 +- .../discovery/handlers/redfishbmc.py | 110 +++++++++--------- .../confluent/discovery/handlers/xcc3.py | 20 ++-- 5 files changed, 70 insertions(+), 70 deletions(-) diff --git a/confluent_client/bin/nodedefine b/confluent_client/bin/nodedefine index ce347bec..c398bb3d 100755 --- a/confluent_client/bin/nodedefine +++ b/confluent_client/bin/nodedefine @@ -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( diff --git a/confluent_client/bin/nodediscover b/confluent_client/bin/nodediscover index 4efdff99..2a504ab7 100755 --- a/confluent_client/bin/nodediscover +++ b/confluent_client/bin/nodediscover @@ -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'])) diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index e876e0bf..6d675976 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -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)}) diff --git a/confluent_server/confluent/discovery/handlers/redfishbmc.py b/confluent_server/confluent/discovery/handlers/redfishbmc.py index 7cf3f3d1..f0099aa5 100644 --- a/confluent_server/confluent/discovery/handlers/redfishbmc.py +++ b/confluent_server/confluent/discovery/handlers/redfishbmc.py @@ -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( diff --git a/confluent_server/confluent/discovery/handlers/xcc3.py b/confluent_server/confluent/discovery/handlers/xcc3.py index 050186e9..faafd63f 100644 --- a/confluent_server/confluent/discovery/handlers/xcc3.py +++ b/confluent_server/confluent/discovery/handlers/xcc3.py @@ -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')