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:
@@ -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(
|
||||
|
||||
@@ -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']))
|
||||
|
||||
@@ -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)})
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user