From 5c5a0269d7cc048d1db77c8060e8f3e7870e17ca Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 27 Mar 2018 15:13:46 -0400 Subject: [PATCH] Add hostname for FPC and XCC Provide access to hostname and dns domain on FPC. Also, make the webclient a bit more robust. Change-Id: I7176adf821a01c5cf7f20cc8227118a6b8dbd4a3 --- pyghmi/ipmi/command.py | 31 +++++++++++++++ pyghmi/ipmi/oem/generic.py | 12 ++++++ pyghmi/ipmi/oem/lenovo/handler.py | 18 +++++++++ pyghmi/ipmi/oem/lenovo/imm.py | 10 +++++ pyghmi/ipmi/oem/lenovo/nextscale.py | 58 ++++++++++++++++++++++++++++- pyghmi/util/webclient.py | 27 ++++++++++---- 6 files changed, 147 insertions(+), 9 deletions(-) diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index ffc25c72..3aa81570 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -1142,6 +1142,23 @@ class Command(object): if not ip == '0.0.0.0': self._assure_alert_policy(channel, destination) + def get_hostname(self): + """Get the hostname used by the BMC in various contexts + + This can vary somewhat in interpretation, but generally speaking + this should be the name that shows up on UIs and in DHCP requests and + DNS registration requests, as applicable. + + :return: current hostname + """ + self.oem_init() + try: + return self._oem.get_hostname() + except exc.UnsupportedFunctionality: + # Use the DCMI MCI field as a fallback, since it's the closest + # thing in the IPMI Spec for this + return self.get_mci() + def get_mci(self): """Get the Management Controller Identifier, per DCMI specification @@ -1149,6 +1166,20 @@ class Command(object): """ return self._chunkwise_dcmi_fetch(9) + def set_hostname(self, hostname): + """Set the hostname to be used by the BMC in various contexts. + + See get_hostname for details + + :param hostname: The hostname to set + :return: Nothing + """ + self.oem_init() + try: + return self._oem.set_hostname(hostname) + except exc.UnsupportedFunctionality: + return self.set_mci(hostname) + def set_mci(self, mci): """Set the management controller identifier, per DCMI specification diff --git a/pyghmi/ipmi/oem/generic.py b/pyghmi/ipmi/oem/generic.py index b0fea2fc..618852df 100644 --- a/pyghmi/ipmi/oem/generic.py +++ b/pyghmi/ipmi/oem/generic.py @@ -278,6 +278,18 @@ class OEMHandler(object): """ return + def set_hostname(self, hostname): + """OEM specific hook to specify name information + + """ + raise exc.UnsupportedFunctionality() + + def get_hostname(self): + """OEM specific hook to specify name information + + """ + raise exc.UnsupportedFunctionality() + def set_alert_ipv6_destination(self, ip, destination, channel): """Set an IPv6 alert destination diff --git a/pyghmi/ipmi/oem/lenovo/handler.py b/pyghmi/ipmi/oem/lenovo/handler.py index 6e853215..7ca2305a 100755 --- a/pyghmi/ipmi/oem/lenovo/handler.py +++ b/pyghmi/ipmi/oem/lenovo/handler.py @@ -652,6 +652,8 @@ class OEMHandler(generic.OEMHandler): data=(4, i)) name += rsp['data'][:] return name.rstrip('\x00') + elif self.is_fpc: + return self.smmhandler.get_domain() def set_oem_domain_name(self, name): if self.has_tsm: @@ -669,6 +671,22 @@ class OEMHandler(generic.OEMHandler): self._restart_dns() return + elif self.is_fpc: + self.smmhandler.set_domain(name) + + def set_hostname(self, hostname): + if self.is_fpc: + return self.smmhandler.set_hostname(hostname) + elif self.has_xcc: + return self.immhandler.set_hostname(hostname) + return super(OEMHandler, self).set_hostname(hostname) + + def get_hostname(self): + if self.is_fpc: + return self.smmhandler.get_hostname() + elif self.has_xcc: + return self.immhandler.get_hostname() + return super(OEMHandler, self).get_hostname() """ Gets a remote console launcher for a Lenovo ThinkServer. diff --git a/pyghmi/ipmi/oem/lenovo/imm.py b/pyghmi/ipmi/oem/lenovo/imm.py index a654b367..42745fca 100644 --- a/pyghmi/ipmi/oem/lenovo/imm.py +++ b/pyghmi/ipmi/oem/lenovo/imm.py @@ -1123,6 +1123,16 @@ class XCCClient(IMMClient): if '_csrf_token' in wc.cookies: wc.set_header('X-XSRF-TOKEN', self.wc.cookies['_csrf_token']) + def set_hostname(self, hostname): + self.wc.grab_json_response('/api/dataset', {'IMM_HostName': hostname}) + self.wc.grab_json_response('/api/dataset', {'IMM_DescName': hostname}) + self.weblogout() + + def get_hostname(self): + rsp = self.wc.grab_json_response('/api/dataset/sys_info') + self.weblogout() + return rsp['items'][0]['system_name'] + def update_firmware_backend(self, filename, data=None, progress=None, bank=None): self.weblogout() diff --git a/pyghmi/ipmi/oem/lenovo/nextscale.py b/pyghmi/ipmi/oem/lenovo/nextscale.py index 7bc06348..ad4953fa 100644 --- a/pyghmi/ipmi/oem/lenovo/nextscale.py +++ b/pyghmi/ipmi/oem/lenovo/nextscale.py @@ -233,6 +233,7 @@ def get_sensor_reading(name, ipmicmd, sz): class SMMClient(object): + def __init__(self, ipmicmd): self.ipmicmd = weakref.proxy(ipmicmd) self.smm = ipmicmd.bmc @@ -275,9 +276,64 @@ class SMMClient(object): self.st1 = data.text for data in authdata.findall('st2'): self.st2 = data.text + if not self.st2: + # This firmware puts tokens in the html file, parse that + wc.request('GET', '/index.html') + rsp = wc.getresponse() + if rsp.status != 200: + raise Exception(rsp.read()) + indexhtml = rsp.read() + for line in indexhtml.split('\n'): + if '"ST1"' in line: + self.st1 = line.split()[-1].replace( + '"', '').replace(',', '') + if '"ST2"' in line: + self.st2 = line.split()[-1].replace( + '"', '').replace(',', '') wc.set_header('ST2', self.st2) return wc + def set_hostname(self, hostname): + self.wc.request('POST', '/data', 'set=hostname:' + hostname) + rsp = self.wc.getresponse() + if rsp.status != 200: + raise Exception(rsp.read()) + rsp.read() + self.logout() + + def get_hostname(self): + currinfo = self.get_netinfo() + self.logout() + for data in currinfo.find('netConfig').findall('hostname'): + return data.text + + def get_netinfo(self): + self.wc.request('POST', '/data', 'get=hostname') + rsp = self.wc.getresponse() + data = rsp.read() + if rsp.status == 400: + self.wc.request('POST', '/data?get=hostname', '') + rsp = self.wc.getresponse() + data = rsp.read() + if rsp.status != 200: + raise Exception(data) + currinfo = fromstring(data) + return currinfo + + def set_domain(self, domain): + self.wc.request('POST', '/data', 'set=dnsDomain:' + domain) + rsp = self.wc.getresponse() + if rsp.status != 200: + raise Exception(rsp.read()) + rsp.read() + self.logout() + + def get_domain(self): + currinfo = self.get_netinfo() + self.logout() + for data in currinfo.find('netConfig').findall('dnsDomain'): + return data.text + def get_ntp_enabled(self, variant): self.wc.request('POST', '/data', 'get=ntpOpMode') rsp = self.wc.getresponse() @@ -370,6 +426,6 @@ class SMMClient(object): @property def wc(self): - if not self._wc: + if not self._wc or self._wc.broken: self._wc = self.get_webclient() return self._wc diff --git a/pyghmi/util/webclient.py b/pyghmi/util/webclient.py index c7c237d7..81c0030f 100644 --- a/pyghmi/util/webclient.py +++ b/pyghmi/util/webclient.py @@ -69,6 +69,7 @@ class SecureHTTPConnection(httplib.HTTPConnection, object): **kwargs): if 'timeout' not in kwargs: kwargs['timeout'] = 60 + self.broken = False self.thehost = host self.theport = port httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs) @@ -107,12 +108,16 @@ class SecureHTTPConnection(httplib.HTTPConnection, object): bincert) def getresponse(self): - rsp = super(SecureHTTPConnection, self).getresponse() - for hdr in rsp.msg.headers: - if hdr.startswith('Set-Cookie:'): - c = Cookie.BaseCookie(hdr[11:]) - for k in c: - self.cookies[k] = c[k].value + try: + rsp = super(SecureHTTPConnection, self).getresponse() + for hdr in rsp.msg.headers: + if hdr.startswith('Set-Cookie:'): + c = Cookie.BaseCookie(hdr[11:]) + for k in c: + self.cookies[k] = c[k].value + except httplib.BadStatusLine: + self.broken = True + raise return rsp def grab_json_response(self, url, data=None, referer=None): @@ -164,6 +169,8 @@ class SecureHTTPConnection(httplib.HTTPConnection, object): headers = self.stdheaders.copy() if method == 'GET' and 'Content-Type' in headers: del headers['Content-Type'] + if method == 'POST' and body and 'Content-Type' not in headers: + headers['Content-Type'] = 'application/x-www-form-urlencoded' if self.cookies: cookies = [] for ckey in self.cookies: @@ -175,5 +182,9 @@ class SecureHTTPConnection(httplib.HTTPConnection, object): headers['Cookie'] += '; ' + '; '.join(cookies) if referer: headers['Referer'] = referer - return super(SecureHTTPConnection, self).request(method, url, body, - headers) + try: + return super(SecureHTTPConnection, self).request(method, url, body, + headers) + except httplib.CannotSendRequest: + self.broken = True + raise