2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-01-28 11:57:37 +00:00

Add module for cooltera nodesensors

This commit is contained in:
Jarrod Johnson 2023-05-10 13:24:20 -04:00
parent 2237e0e173
commit a167d19441

View File

@ -0,0 +1,266 @@
# Copyright 2022 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.
from xml.etree.ElementTree import fromstring as rfromstring
import pyghmi.util.webclient as wc
import confluent.util as util
import confluent.messages as msg
import confluent.exceptions as exc
import eventlet.green.time as time
import eventlet.green.socket as socket
import eventlet.greenpool as greenpool
import eventlet
try:
import Cookie
httplib = eventlet.import_patched('httplib')
except ImportError:
httplib = eventlet.import_patched('http.client')
import http.cookies as Cookie
sensorsbymodel = {
'FS1350': ['alarms', 'dt', 'duty', 'dw', 'mode', 'p3state', 'primflow', 'ps1', 'ps1a', 'ps1b', 'ps2', 'ps3', 'ps4', 'ps5a', 'ps5b', 'ps5c', 'pumpspeed1', 'pumpspeed2', 'pumpspeed3', 'rh', 'sdp', 'secflow', 'setpoint', 't1', 't2', 't2a', 't2b', 't2c', 't3', 't3', 't4', 't5', 'valve', 'valve2'],
'FS600': ['alarms', 'dt', 'duty', 'dw', 'mode', 'p3state', 'pdp', 'primflow', 'ps1', 'ps1a', 'ps1b', 'ps2', 'ps3', 'ps4', 'ps5a', 'ps5b', 'pumpspeed1', 'pumpspeed2', 'rh', 'sdp', 'secflow', 'setpoint', 't1', 't2', 't2a', 't2b', 't2c', 't3', 't3', 't4', 't5', 'valve'],
'RM100': ['alarms', 'dt', 'duty', 'dw', 'mode', 'p3state', 'primflow', 'ps1', 'ps1a', 'ps2', 'ps3', 'pumpspeed1', 'pumpspeed2', 'rh', 'sdp', 'secflow', 'setpoint', 't1', 't2', 't2a', 't2b', 't2c', 't3', 't3', 't4', 't5', 'valve'],
}
_thesensors = {
'RM100': {
't1': ('Primary loop supply temperature', 'degC'),
't2': ('Secondary loop supply temperature', 'degC'),
't4': ('Secondary loop return temperature', 'degC'),
't3': ('Ambient air temperature', 'degC'),
't5': ('Primary loop return temperature', 'degC'),
'rh': ('Relative Humidity', '%'),
'dw': ('Dewpoint', 'degC'),
'pumpspeed1': ('Pump 1 Speed', '%'),
'pumpspeed2': ('Pump 2 Speed', '%'),
'alarms': ('Number of active alarms', ''),
'primflow': ('Input flow rate', 'l/m'),
'secflow': ('Output flow rate', 'l/m'),
'ps1': ('Secondary loop return pressure', 'bar'),
'ps3': ('Secondary loop supply pressure', 'bar'),
},
'FS600': {
't1': ('Primary loop supply temperature', 'degC'),
't2': ('Secondary loop supply temperature', 'degC'),
't4': ('Secondary loop return temperature', 'degC'),
't5': ('Primary loop return temperature', 'degC'),
't3': ('Ambient air temperature', 'degC'),
'rh': ('Relative Humidity', '%'),
'dw': ('Dewpoint', 'degC'),
'pumpspeed1': ('Pump 1 Speed', '%'),
'pumpspeed2': ('Pump 2 Speed', '%'),
'alarms': ('Number of active alarms', ''),
'primflow': ('Input flow rate', 'l/m'),
'secflow': ('Output flow rate', 'l/m'),
'ps1': ('Secondary loop return pressure', 'bar'),
'ps3': ('Primary loop supply pressure', 'bar'),
'ps2': ('Secondary loop supply pressure', 'bar'),
},
'FS1350': {
't1': ('Primary loop supply temperature', 'degC'),
't2': ('Secondary loop supply temperature', 'degC'),
't4': ('Secondary loop return temperature', 'degC'),
't5': ('Primary loop return temperature', 'degC'),
't3': ('Ambient air temperature', 'degC'),
'rh': ('Relative Humidity', '%'),
'dw': ('Dewpoint', 'degC'),
'pumpspeed1': ('Pump 1 Speed', '%'),
'pumpspeed2': ('Pump 2 Speed', '%'),
'pumpspeed3': ('Pump 2 Speed', '%'),
'alarms': ('Number of active alarms', ''),
'primflow': ('Input flow rate', 'l/m'),
'secflow': ('Output flow rate', 'l/m'),
'ps1': ('Secondary loop return pressure', 'bar'),
'ps3': ('Primary loop supply pressure', 'bar'),
'ps2': ('Secondary loop supply pressure', 'bar'),
'ps4': ('Primary loop return pressure', 'bar'),'
},
}
def fromstring(inputdata):
if isinstance(inputdata, bytes):
cmpstr = b'!entity'
else:
cmpstr = '!entity'
if cmpstr in inputdata.lower():
raise Exception('!ENTITY not supported in this interface')
# The measures above should filter out the risky facets of xml
# We don't need sophisticated feature support
return rfromstring(inputdata) # nosec
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
pdupool = greenpool.GreenPool(128)
class WebResponse(httplib.HTTPResponse):
def _check_close(self):
return True
class WebConnection(wc.SecureHTTPConnection):
response_class = WebResponse
def __init__(self, host, secure, verifycallback):
if secure:
port = 443
else:
port = 80
wc.SecureHTTPConnection.__init__(self, host, port, verifycallback=verifycallback)
self.secure = secure
self.cookies = {}
def connect(self):
if self.secure:
return super(WebConnection, self).connect()
addrinfo = socket.getaddrinfo(self.host, self.port)[0]
# workaround problems of too large mtu, moderately frequent occurance
# in this space
plainsock = socket.socket(addrinfo[0])
plainsock.settimeout(self.mytimeout)
try:
plainsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
except socket.error:
pass
plainsock.connect(addrinfo[4])
self.sock = plainsock
def getresponse(self):
try:
rsp = super(WebConnection, self).getresponse()
try:
hdrs = [x.split(':', 1) for x in rsp.msg.headers]
except AttributeError:
hdrs = rsp.msg.items()
for hdr in hdrs:
if hdr[0] == 'Set-Cookie':
c = Cookie.BaseCookie(hdr[1])
for k in c:
self.cookies[k] = c[k].value
except httplib.BadStatusLine:
self.broken = True
raise
return rsp
def request(self, method, url, body=None):
headers = {}
if body:
headers['Content-Length'] = len(body)
cookies = []
for cookie in self.cookies:
cookies.append('{0}={1}'.format(cookie, self.cookies[cookie]))
headers['Cookie'] = ';'.join(cookies)
headers['Host'] = 'pdu.cluster.net'
headers['Accept'] = '*/*'
headers['Accept-Language'] = 'en-US,en;q=0.9'
headers['Connection'] = 'close'
headers['Referer'] = 'http://pdu.cluster.net/setting_admin.htm'
return super(WebConnection, self).request(method, url, body, headers)
def grab_response(self, url, body=None, method=None):
if method is None:
method = 'GET' if body is None else 'POST'
if body:
self.request(method, url, body)
else:
self.request(method, url)
rsp = self.getresponse()
body = rsp.read()
return body, rsp.status
class CoolteraClient(object):
def __init__(self, cdu, configmanager):
self.node = cdu
self.configmanager = configmanager
self._wc = None
@property
def wc(self):
if self._wc:
return self._wc
targcfg = self.configmanager.get_node_attributes(self.node,
['hardwaremanagement.manager'],
decrypt=True)
targcfg = targcfg.get(self.node, {})
target = targcfg.get(
'hardwaremanagement.manager', {}).get('value', None)
if not target:
target = self.node
target = target.split('/', 1)[0]
cv = util.TLSCertVerifier(
self.configmanager, self.node,
'pubkeys.tls_hardwaremanager').verify_cert
self._wc = WebConnection(target, secure=True, verifycallback=cv)
return self._wc
def xml2stateinfo(statdata):
statdata = fromstring(statdata)
stateinfo = []
sensornames = sorted([x.tag for x in statdata])
themodel = None
for model in sensorsbymodel:
if sensorsbymodel[model] == sensornames:
themodel = model
break
thesensors = _thesensors[themodel]
#['mode', 't1', 't2a', 't2b', 't2c', 't2', 't5', 't3', 't4', 'dw', 't3', 'rh', 'setpoint', 'secflow', 'primflow', 'ps1', 'ps1a', 'ps1b', 'ps2', 'ps3', 'ps4', 'ps5a', 'ps5b', 'ps5c', 'sdp', 'valve', 'valve2', 'pumpspeed1', 'pumpspeed2', 'pumpspeed3', 'alarms', 'dt', 'p3state', 'duty']
for tagname in thesensors:
label, units = thesensors[tagname]
val = statdata.find(tagname).text.replace(units, '').strip()
stateinfo.append({
'name': label,
'value': val,
'units': units.replace('degC', '°C'),
'type': 'Temperature',
})
return stateinfo
_sensors_by_node = {}
def read_sensors(element, node, configmanager):
category, name = element[-2:]
justnames = False
if len(element) == 3:
# just get names
category = name
name = 'all'
justnames = True
for sensor in sensors:
yield msg.ChildCollection(simplify_name(sensors[sensor][0]))
return
if category in ('leds, fans'):
return
sn = _sensors_by_node.get(node, None)
if not sn or sn[1] < time.time():
cc = CoolteraClient(node, configmanager)
cc.wc.request('GET', '/status.xml')
rsp = cc.wc.getresponse()
statdata = rsp.read()
statinfo = xml2stateinfo(statdata)
_sensors_by_node[node] = (statinfo, time.time() + 1)
sn = _sensors_by_node.get(node, None)
if sn:
yield msg.SensorReadings(sn[0], name=node)
def retrieve(nodes, element, configmanager, inputdata):
if element[0] == 'sensors':
gp = greenpool.GreenPile(pdupool)
for node in nodes:
gp.spawn(read_sensors, element, node, configmanager)
for rsp in gp:
for datum in rsp:
yield datum
return