2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-01-20 22:53:17 +00:00

Begin PDU implementation

This commit is contained in:
Jarrod Johnson 2022-05-10 16:00:08 -04:00
parent 0e879dc3de
commit 6229cb23e8
6 changed files with 212 additions and 1 deletions

View File

@ -534,6 +534,12 @@ node = {
'To support this scenario, the switch should be set up to allow independent operation of member ports123654 (e.g. lacp bypass mode or fallback mode).',
'validvalues': ('lacp', 'loadbalance', 'roundrobin', 'activebackup', 'none')
},
'power.pdu': {
'description': 'Specifies the managed PDU associated with a power input on the node'
},
'power.outlet': {
'description': 'Species the outlet identifier on the PDU associoted with a power input on the node'
},
# 'id.modelnumber': {
# 'description': 'The manufacturer dictated model number for the node',
# },

View File

@ -485,7 +485,7 @@ def attribute_is_invalid(attrname, attrval):
def _get_valid_attrname(attrname):
if attrname.startswith('net.'):
if attrname.startswith('net.') or attrname.startswith('power.'):
# For net.* attribtues, split on the dots and put back together
# longer term we might want a generic approach, but
# right now it's just net. attributes

View File

@ -360,6 +360,10 @@ def _init_core():
{'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi'}),
},
'_pdu': {
'outlets': PluginCollection(
{'pluginattrs': ['hardwaremanagement.method']}),
},
'shell': {
# another special case similar to console
'sessions': PluginCollection({
@ -457,6 +461,7 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'inlets': PluginCollection({'handler': 'pdu'}),
'reseat': PluginRoute({'handler': 'enclosure'}),
},
'sensors': {

View File

@ -21,6 +21,10 @@ try:
import Cookie
except ModuleNotFoundError:
import http.cookies as Cookie
try:
import pywarp
except ImportError:
pywarp = None
import confluent.auth as auth
import confluent.config.attributes as attribs
import confluent.consoleserver as consoleserver

View File

@ -0,0 +1,103 @@
# 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.
import pyghmi.util.webclient as wc
import confluent.util as util
import confluent.messages as msg
class GeistClient(object):
def __init__(self, pdu, configmanager):
self.node = pdu
self.configmanager = configmanager
self._token = None
self._wc = None
self.username = None
@property
def token(self):
if not self._token:
self._token = self.login(self.configmanager)
return self._token
@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
cv = util.TLSCertVerifier(
self.configmanager, self.node,
'pubkeys.tls_hardwaremanager').verify_cert
self._wc = wc.SecureHTTPConnection(target, verifycallback=cv)
return self._wc
def login(self, configmanager):
credcfg = configmanager.get_node_attributes(self.node,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'],
decrypt=True)
username = credcfg.get(
'secret.hardwaremanagementuser', {}).get('value', None)
passwd = credcfg.get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if not username or not passwd:
raise Exception('Missing username or password')
self.username = username
rsp = self.wc.grab_json_response(
'/api/auth/{0]'.format(username),
{'cmd': 'login', 'data': {'password': passwd}})
token = rsp['data']['token']
return token
def logout(self):
if self._token:
self.wc.grab_json_response('/api/auth/{0}'.format(self.username),
{'cmd': 'logout', 'token': self.token})
self._token = None
def get_outlet(self, outlet):
rsp = self.wc.grab_json_response('/api/dev')
rsp = rsp['data']
if len(rsp) != 1:
raise Exception('Multiple PDUs not supported per pdu')
pduname = list(rsp)[0]
outlet = rsp[pduname]['outlet'][str(int(outlet) - 1)]
state = outlet['state']
return state
def set_outlet(self, outlet, state):
rsp = self.wc.grab_json_response('/api/dev')
if len(rsp['data'] != 1):
self.logout()
raise Exception('Multiple PDUs per endpoint not supported')
pdu = list(rsp['data'])[0]
outlet = int(outlet) - 1
rsp = self.wc.grab_json_response(
'/api/dev/{0}/outlet/{1}'.format(pdu, outlet),
{'cmd': 'control', 'token': self.token,
'data': {'action': state, 'delay': False}})
def retrieve(nodes, element, configmanager, inputdata):
for node in nodes:
gc = GeistClient(node, configmanager)
state = gc.get_outlet(element[-1])
yield msg.PowerState(node=node, state=state)

View File

@ -0,0 +1,93 @@
# Copyright 2017 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.
import confluent.core as core
import confluent.messages as msg
import pyghmi.exceptions as pygexc
import confluent.exceptions as exc
def retrieve(nodes, element, configmanager, inputdata):
emebs = configmanager.get_node_attributes(
nodes, (u'power.*pdu', u'power.*outlet'))
if element == ['power', 'inlets']:
outletnames = set([])
for node in nodes:
for attrib in emebs[node]:
attrib = attrib.replace('power.', '').rsplit('.', 1)
if len(attrib) > 1:
outletnames.add('inlet_' + attrib[0])
else:
outletnames.add('default')
if outletnames:
outletnames.add('all')
for inlet in outletnames:
yield msg.ChildCollection(inlet)
elif len(element) == 3:
inletname = element[-1]
outlets = get_outlets(nodes, emebs, inletname)
for node in outlets:
for pgroup in outlets[node]:
pdu = outlets[node][pgroup]['pdu']
outlet = outlets[node][pgroup]['outlet']
for rsp in core.handle_path(
'/nodes/{0}/_pdu/outlets/{1}'.format(pdu, outlet),
'retrieve', configmanager):
yield msg.KeyValueData({pgroup: rsp.kvpairs['state']['value']}, node)
def get_outlets(nodes, emebs, inletname):
outlets = {}
for node in nodes:
if node not in outlets:
outlets[node] = {}
for attrib in emebs[node]:
v = emebs[node][attrib].get('value', None)
if not v:
continue
attrib = attrib.replace('power.', '').rsplit('.', 1)
if len(attrib) > 1:
pgroup = 'inlet_' + attrib[0]
else:
pgroup = 'default'
if inletname == 'all' or pgroup == 'inletname':
if pgroup not in outlets[node]:
outlets[node][pgroup] = {}
outlets[node][pgroup][attrib[-1]] = v
return outlets
def update(nodes, element, configmanager, inputdata):
emebs = configmanager.get_node_attributes(
nodes, (u'power.*pdu', u'power.*outlet'))
for node in nodes:
for attrib in emebs[node]:
print(repr(attrib))
try:
em = emebs[node]['enclosure.manager']['value']
eb = emebs[node]['enclosure.bay']['value']
except KeyError:
em = node
eb = -1
if not em:
em = node
if not eb:
eb = -1
try:
for rsp in core.handle_path(
'/nodes/{0}/_enclosure/reseat_bay'.format(em),
'update', configmanager,
inputdata={'reseat': int(eb)}):
yield rsp
except pygexc.UnsupportedFunctionality as uf:
yield msg.ConfluentNodeError(node, str(uf))
except exc.TargetEndpointUnreachable as uf:
yield msg.ConfluentNodeError(node, str(uf))