mirror of
https://github.com/xcat2/confluent.git
synced 2025-04-18 19:18:59 +00:00
This opens the door for normalized common sensors for clients that care about the semantics but cannot keep track of inconsistent sensor names from implementation to implementation.
1684 lines
69 KiB
Python
1684 lines
69 KiB
Python
# Copyright 2014 IBM Corporation
|
|
# Copyright 2015-2018 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 atexit
|
|
import confluent.exceptions as exc
|
|
import confluent.firmwaremanager as firmwaremanager
|
|
import confluent.interface.console as conapi
|
|
import confluent.messages as msg
|
|
import confluent.util as util
|
|
import copy
|
|
import errno
|
|
import eventlet
|
|
import eventlet.event
|
|
import eventlet.green.threading as threading
|
|
import eventlet.greenpool as greenpool
|
|
import eventlet.queue as queue
|
|
import eventlet.support.greendns
|
|
from fnmatch import fnmatch
|
|
import os
|
|
import pwd
|
|
import pyghmi.constants as pygconstants
|
|
import pyghmi.exceptions as pygexc
|
|
import pyghmi.storage as storage
|
|
console = eventlet.import_patched('pyghmi.ipmi.console')
|
|
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
|
|
import socket
|
|
import ssl
|
|
import traceback
|
|
|
|
|
|
if not hasattr(ssl, 'SSLEOFError'):
|
|
ssl.SSLEOFError = None
|
|
|
|
try:
|
|
range = xrange
|
|
except NameError:
|
|
pass
|
|
|
|
pci_cache = {}
|
|
|
|
def get_dns_txt(qstring):
|
|
return eventlet.support.greendns.resolver.query(
|
|
qstring, 'TXT')[0].strings[0].replace('i=', '')
|
|
|
|
def get_pci_text_from_ids(subdevice, subvendor, device, vendor):
|
|
fqpi = '{0}.{1}.{2}.{3}'.format(subdevice, subvendor, device, vendor)
|
|
if fqpi in pci_cache:
|
|
return pci_cache[fqpi]
|
|
vendorstr = None
|
|
try:
|
|
vendorstr = get_dns_txt('{0}.pci.id.ucw.cz'.format(subvendor))
|
|
except Exception:
|
|
try:
|
|
vendorstr = get_dns_txt('{0}.pci.id.ucw.cz'.format(vendor))
|
|
except Exception:
|
|
pass
|
|
devstr = None
|
|
try:
|
|
devstr = get_dns_txt(fqpi + '.pci.id.ucw.cz')
|
|
except Exception:
|
|
try:
|
|
devstr = get_dns_txt('{0}.{1}.pci.id.ucw.cz'.format(
|
|
device, vendor))
|
|
except Exception:
|
|
pass
|
|
if vendorstr and devstr:
|
|
pci_cache[fqpi] = vendorstr, devstr
|
|
return vendorstr, devstr
|
|
|
|
|
|
# There is something not right with the RLocks used in pyghmi when
|
|
# eventlet comes into play. It seems like sometimes on acquire,
|
|
# it calls _get_ident and it isn't the id(greenlet) and so
|
|
# a thread deadlocks itself due to identity crisis?
|
|
# However, since we are not really threaded, the operations being protected
|
|
# are not actually dangerously multiplexed... so we can replace with
|
|
# a null context manager for now
|
|
class NullLock(object):
|
|
|
|
def donothing(self, *args, **kwargs):
|
|
return 1
|
|
|
|
__enter__ = donothing
|
|
__exit__ = donothing
|
|
acquire = donothing
|
|
release = donothing
|
|
|
|
console.session.select = eventlet.green.select
|
|
console.session.threading = eventlet.green.threading
|
|
console.session.WAITING_SESSIONS = NullLock()
|
|
console.session.KEEPALIVE_SESSIONS = NullLock()
|
|
console.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
|
|
|
|
|
|
def exithandler():
|
|
if console.session.iothread is not None:
|
|
console.session.iothread.join()
|
|
|
|
atexit.register(exithandler)
|
|
|
|
_ipmiworkers = greenpool.GreenPool(512)
|
|
|
|
_ipmithread = None
|
|
_ipmiwaiters = []
|
|
|
|
sensor_categories = {
|
|
'temperature': frozenset(['Temperature']),
|
|
'energy': frozenset(['Energy']),
|
|
'power': frozenset(['Power', 'Current']),
|
|
'fans': frozenset(['Fan', 'Cooling Device']),
|
|
}
|
|
|
|
|
|
class EmptySensor(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.value = None
|
|
self.states = ['Unavailable']
|
|
self.units = None
|
|
self.health = 'ok'
|
|
|
|
|
|
def hex2bin(hexstring):
|
|
hexvals = hexstring.split(':')
|
|
if len(hexvals) < 2:
|
|
hexvals = hexstring.split(' ')
|
|
if len(hexvals) < 2:
|
|
hexvals = [hexstring[i:i+2] for i in range(0, len(hexstring), 2)]
|
|
bytedata = [int(i, 16) for i in hexvals]
|
|
return bytearray(bytedata)
|
|
|
|
|
|
def simplify_name(name):
|
|
return name.lower().replace(' ', '_').replace('/', '-').replace(
|
|
'_-_', '-')
|
|
|
|
|
|
def sanitize_invdata(indata):
|
|
"""Sanitize pyghmi data
|
|
|
|
pyghmi will return bytearrays when it has no idea what to do. In our
|
|
case, we will change those to hex strings. Additionally, ignore 'extra'
|
|
fields if the oem_parser is set
|
|
"""
|
|
if 'oem_parser' in indata and indata['oem_parser'] is not None:
|
|
if 'board_extra' in indata:
|
|
del indata['board_extra']
|
|
if 'chassis_extra' in indata:
|
|
del indata['chassis_extra']
|
|
if 'product_extra' in indata:
|
|
del indata['product_extra']
|
|
for k in indata:
|
|
if isinstance(indata[k], bytearray):
|
|
indata[k] = '0x' + ''.join(format(x, '02x') for x in indata[k])
|
|
elif isinstance(indata[k], dict):
|
|
sanitize_invdata(indata[k])
|
|
elif isinstance(indata[k], list):
|
|
for idx, value in enumerate(indata[k]):
|
|
if isinstance(value, bytearray):
|
|
indata[k][idx] = '0x' + ''.join(
|
|
format(x, '02x') for x in indata[k][idx])
|
|
|
|
|
|
class IpmiCommandWrapper(ipmicommand.Command):
|
|
def __init__(self, node, cfm, **kwargs):
|
|
self.cfm = cfm
|
|
self.node = node
|
|
self.sensormap = {}
|
|
self._inhealth = False
|
|
self._lasthealth = None
|
|
kwargs['keepalive'] = False
|
|
self._attribwatcher = cfm.watch_attributes(
|
|
(node,), ('secret.hardwaremanagementuser', 'collective.manager',
|
|
'secret.hardwaremanagementpassword', 'secret.ipmikg',
|
|
'hardwaremanagement.manager'), self._attribschanged)
|
|
super(self.__class__, self).__init__(**kwargs)
|
|
self.setup_confluent_keyhandler()
|
|
try:
|
|
os.makedirs('/var/cache/confluent/ipmi/')
|
|
except OSError as e:
|
|
if e.errno != errno.EEXIST or not os.path.isdir(
|
|
'/var/cache/confluent/ipmi/'):
|
|
raise
|
|
try:
|
|
self.set_sdr_cachedir('/var/cache/confluent/ipmi/')
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def setup_confluent_keyhandler(self):
|
|
self.register_key_handler(util.TLSCertVerifier(
|
|
self.cfm, self.node, 'pubkeys.tls_hardwaremanager').verify_cert)
|
|
|
|
def close_confluent(self):
|
|
if self._attribwatcher:
|
|
self.cfm.remove_watcher(self._attribwatcher)
|
|
self._attribwatcher = None
|
|
|
|
def _attribschanged(self, nodeattribs, configmanager, **kwargs):
|
|
try:
|
|
self.ipmi_session._mark_broken()
|
|
except AttributeError:
|
|
# if ipmi_session doesn't already exist,
|
|
# then do nothing
|
|
pass
|
|
|
|
def get_health(self):
|
|
if self._inhealth:
|
|
while self._inhealth:
|
|
eventlet.sleep(0.1)
|
|
return self._lasthealth
|
|
self._inhealth = True
|
|
try:
|
|
self._lasthealth = super(IpmiCommandWrapper, self).get_health()
|
|
except Exception:
|
|
self._inhealth = False
|
|
raise
|
|
self._inhealth = False
|
|
return self._lasthealth
|
|
|
|
|
|
def _ipmi_evtloop():
|
|
while True:
|
|
try:
|
|
console.session.Session.wait_for_rsp(timeout=600)
|
|
while _ipmiwaiters:
|
|
waiter = _ipmiwaiters.pop()
|
|
waiter.send()
|
|
except: # TODO(jbjohnso): log the trace into the log
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
def get_conn_params(node, configdata):
|
|
if 'secret.hardwaremanagementuser' in configdata:
|
|
username = configdata['secret.hardwaremanagementuser']['value']
|
|
else:
|
|
username = 'USERID'
|
|
if 'secret.hardwaremanagementpassword' in configdata:
|
|
passphrase = configdata['secret.hardwaremanagementpassword']['value']
|
|
else:
|
|
passphrase = 'PASSW0RD' # for lack of a better guess
|
|
if 'hardwaremanagement.manager' in configdata:
|
|
bmc = configdata['hardwaremanagement.manager']['value']
|
|
else:
|
|
bmc = node
|
|
bmc = bmc.split('/', 1)[0]
|
|
if 'secret.ipmikg' in configdata:
|
|
kg = configdata['secret.ipmikg']['value']
|
|
else:
|
|
kg = passphrase
|
|
# TODO(jbjohnso): check if the end has some number after a : without []
|
|
# for non default port
|
|
return {
|
|
'username': username,
|
|
'passphrase': passphrase,
|
|
'kg': kg,
|
|
'bmc': bmc,
|
|
'port': 623,
|
|
}
|
|
|
|
|
|
_configattributes = ('secret.hardwaremanagementuser',
|
|
'secret.hardwaremanagementpassword',
|
|
'secret.ipmikg', 'hardwaremanagement.manager')
|
|
|
|
|
|
def _donothing(data):
|
|
# a dummy function to avoid some awkward exceptions from
|
|
# zombie pyghmi console objects
|
|
pass
|
|
|
|
|
|
class IpmiConsole(conapi.Console):
|
|
configattributes = frozenset(_configattributes)
|
|
bmctonodemapping = {}
|
|
|
|
def __init__(self, node, config):
|
|
self.error = None
|
|
self.datacallback = None
|
|
crypt = config.decrypt
|
|
self.solconnection = None
|
|
config.decrypt = True
|
|
self.broken = False
|
|
configdata = config.get_node_attributes([node], _configattributes)
|
|
connparams = get_conn_params(node, configdata[node])
|
|
config.decrypt = crypt
|
|
self.username = connparams['username']
|
|
self.password = connparams['passphrase']
|
|
self.kg = connparams['kg']
|
|
self.bmc = connparams['bmc']
|
|
self.port = connparams['port']
|
|
self.connected = False
|
|
# ok, is self.bmc unique among nodes already
|
|
# Cannot actually create console until 'connect', when we get callback
|
|
if (self.bmc in self.bmctonodemapping and
|
|
self.bmctonodemapping[self.bmc] != node):
|
|
raise Exception(
|
|
"Duplicate hardwaremanagement.manager attribute for {0} and {1}".format(
|
|
node, self.bmctonodemapping[self.bmc]))
|
|
self.bmctonodemapping[self.bmc] = node
|
|
|
|
def __del__(self):
|
|
self.solconnection = None
|
|
try:
|
|
del self.bmctonodemapping[self.bmc]
|
|
except KeyError:
|
|
pass
|
|
|
|
def handle_data(self, data):
|
|
if type(data) == dict:
|
|
if 'error' in data:
|
|
self.solconnection = None
|
|
self.broken = True
|
|
self.error = data['error']
|
|
if self.connected:
|
|
self.connected = False
|
|
self.datacallback(conapi.ConsoleEvent.Disconnect)
|
|
else:
|
|
self.datacallback(data)
|
|
|
|
def connect(self, callback):
|
|
self.datacallback = callback
|
|
# we provide a weak reference to pyghmi as otherwise we'd
|
|
# have a circular reference and reference counting would never get
|
|
# out...
|
|
try:
|
|
self.solconnection = console.Console(bmc=self.bmc, port=self.port,
|
|
userid=self.username,
|
|
password=self.password,
|
|
kg=self.kg, force=True,
|
|
iohandler=self.handle_data)
|
|
self.solconnection.outputlock = NullLock()
|
|
while (self.solconnection and not self.solconnection.connected and
|
|
not (self.broken or self.solconnection.broken or
|
|
self.solconnection.ipmi_session.broken)):
|
|
w = eventlet.event.Event()
|
|
_ipmiwaiters.append(w)
|
|
w.wait(15)
|
|
if (self.broken or not self.solconnection or
|
|
self.solconnection.broken or
|
|
self.solconnection.ipmi_session.broken):
|
|
if not self.error:
|
|
self.error = 'Unknown error'
|
|
if (self.error.startswith('Incorrect password') or
|
|
self.error.startswith('Unauthorized name')):
|
|
raise exc.TargetEndpointBadCredentials
|
|
else:
|
|
raise exc.TargetEndpointUnreachable(self.error)
|
|
self.connected = True
|
|
except socket.gaierror as err:
|
|
raise exc.TargetEndpointUnreachable(str(err))
|
|
|
|
def close(self):
|
|
if self.solconnection is not None:
|
|
# break the circular reference here
|
|
self.solconnection.out_handler = _donothing
|
|
self.solconnection.close()
|
|
self.solconnection = None
|
|
self.datacallback = None
|
|
self.broken = True
|
|
self.error = "closed"
|
|
|
|
def write(self, data):
|
|
self.solconnection.send_data(data)
|
|
|
|
def send_break(self):
|
|
self.solconnection.send_break()
|
|
|
|
|
|
def perform_requests(operator, nodes, element, cfg, inputdata, realop):
|
|
cryptit = cfg.decrypt
|
|
cfg.decrypt = True
|
|
configdata = cfg.get_node_attributes(nodes, _configattributes)
|
|
cfg.decrypt = cryptit
|
|
resultdata = queue.LightQueue()
|
|
livingthreads = set([])
|
|
numnodes = len(nodes)
|
|
for node in nodes:
|
|
livingthreads.add(_ipmiworkers.spawn(
|
|
perform_request, operator, node, element, configdata, inputdata,
|
|
cfg, resultdata, realop))
|
|
while livingthreads:
|
|
try:
|
|
bundle = []
|
|
datum = resultdata.get(timeout=10)
|
|
while datum:
|
|
if datum != 'Done':
|
|
if isinstance(datum, Exception):
|
|
raise datum
|
|
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
|
|
len(datum.kvpairs) == 1):
|
|
bundle.append((list(datum.kvpairs)[0], datum))
|
|
numnodes -= 1
|
|
else:
|
|
yield datum
|
|
timeout = 0.1 if numnodes else 0.001
|
|
datum = resultdata.get(timeout=timeout)
|
|
except queue.Empty:
|
|
pass
|
|
finally:
|
|
for datum in sorted(
|
|
bundle, key=lambda x: util.naturalize_string(x[0])):
|
|
yield datum[1]
|
|
for t in list(livingthreads):
|
|
if t.dead:
|
|
livingthreads.discard(t)
|
|
try:
|
|
# drain queue if a thread put something on the queue and died
|
|
while True:
|
|
datum = resultdata.get_nowait()
|
|
if datum != 'Done':
|
|
yield datum
|
|
except queue.Empty:
|
|
pass
|
|
|
|
|
|
def perform_request(operator, node, element,
|
|
configdata, inputdata, cfg, results, realop):
|
|
try:
|
|
return IpmiHandler(operator, node, element, configdata, inputdata,
|
|
cfg, results, realop).handle_request()
|
|
except pygexc.IpmiException as ipmiexc:
|
|
excmsg = str(ipmiexc)
|
|
if excmsg in ('Session no longer connected', 'timeout'):
|
|
results.put(msg.ConfluentTargetTimeout(node))
|
|
else:
|
|
results.put(msg.ConfluentNodeError(node, excmsg))
|
|
raise
|
|
except exc.TargetEndpointUnreachable as tu:
|
|
results.put(msg.ConfluentTargetTimeout(node, str(tu)))
|
|
except ssl.SSLEOFError:
|
|
results.put(msg.ConfluentNodeError(
|
|
node, 'Unable to communicate with the https server on '
|
|
'the target BMC'))
|
|
except exc.PubkeyInvalid:
|
|
results.put(msg.ConfluentNodeError(
|
|
node,
|
|
'Mismatch detected between target certificate fingerprint '
|
|
'and pubkeys.tls_hardwaremanager attribute'))
|
|
except pygexc.InvalidParameterValue as e:
|
|
results.put(msg.ConfluentNodeError(node, str(e)))
|
|
except Exception as e:
|
|
results.put(msg.ConfluentNodeError(node, 'Unexpected Error: {0}'.format(str(e))))
|
|
traceback.print_exc()
|
|
finally:
|
|
results.put('Done')
|
|
|
|
persistent_ipmicmds = {}
|
|
|
|
class IpmiHandler(object):
|
|
def __init__(self, operation, node, element, cfd, inputdata, cfg, output,
|
|
realop):
|
|
self.cfm = cfg
|
|
self.invmap = {}
|
|
self.output = output
|
|
self.sensorcategory = None
|
|
self.broken = False
|
|
self.error = None
|
|
eventlet.sleep(0)
|
|
self.cfg = cfd[node]
|
|
self.current_user = cfg.current_user
|
|
self.loggedin = False
|
|
self.node = node
|
|
self.element = element
|
|
self.op = operation
|
|
self.realop = realop
|
|
connparams = get_conn_params(node, self.cfg)
|
|
self.ipmicmd = None
|
|
self.inputdata = inputdata
|
|
self.tenant = cfg.tenant
|
|
tenant = cfg.tenant
|
|
while ((node, tenant) not in persistent_ipmicmds or
|
|
not (persistent_ipmicmds[(node, tenant)].ipmi_session.logged or persistent_ipmicmds[(node, tenant)].ipmi_session.logging) or
|
|
persistent_ipmicmds[(node, tenant)].ipmi_session.broken):
|
|
try:
|
|
persistent_ipmicmds[(node, tenant)].close_confluent()
|
|
persistent_ipmicmds[(node, tenant)].ipmi_session._mark_broken()
|
|
persistent_ipmicmds[(node, tenant)].ipmi_session.logonwaiters = []
|
|
except KeyError: # was no previous session
|
|
pass
|
|
try:
|
|
persistent_ipmicmds[(node, tenant)] = IpmiCommandWrapper(
|
|
node, cfg, bmc=connparams['bmc'],
|
|
userid=connparams['username'],
|
|
password=connparams['passphrase'], kg=connparams['kg'],
|
|
port=connparams['port'], onlogon=self.logged)
|
|
ipmisess = persistent_ipmicmds[(node, tenant)].ipmi_session
|
|
begin = util.monotonic_time()
|
|
while ((not (ipmisess.broken or self.loggedin)) and
|
|
(util.monotonic_time() - begin) < 30):
|
|
ipmisess.wait_for_rsp(31 - (util.monotonic_time() - begin))
|
|
if self.broken or self.loggedin:
|
|
break
|
|
cfd = cfg.get_node_attributes(node, _configattributes, decrypt=True)
|
|
self.cfg = cfd[node]
|
|
connparams = get_conn_params(node, self.cfg)
|
|
ipmisess._mark_broken()
|
|
# raise exc.TargetEndpointUnreachable(
|
|
# "Login process to " + connparams['bmc'] + " died")
|
|
except socket.gaierror as ge:
|
|
if ge.errno == -2:
|
|
raise exc.TargetEndpointUnreachable(ge.strerror)
|
|
raise
|
|
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
|
|
giveup = util.monotonic_time() + 60
|
|
while not self.ipmicmd.ipmi_session.broken and not self.ipmicmd.ipmi_session.logged and self.ipmicmd.ipmi_session.logging:
|
|
self.ipmicmd.ipmi_session.wait_for_rsp(3)
|
|
if util.monotonic_time() > giveup:
|
|
self.ipmicmd.ipmi_session.broken = True
|
|
|
|
bootdevices = {
|
|
'optical': 'cd'
|
|
}
|
|
|
|
def logged(self, response, ipmicmd):
|
|
if 'error' in response:
|
|
self.broken = True
|
|
self.error = response['error']
|
|
else:
|
|
self.ipmicmd = ipmicmd
|
|
self.loggedin = True
|
|
|
|
def handle_request(self):
|
|
if self.broken:
|
|
if (self.error == 'timeout' or
|
|
'Insufficient resources' in self.error):
|
|
self.error = self.error.replace(' reported in RAKP4', '')
|
|
self.output.put(msg.ConfluentTargetTimeout(
|
|
self.node, self.error))
|
|
return
|
|
elif 'Invalid Session ID' in self.error:
|
|
self.output.put(msg.ConfluentTargetTimeout(
|
|
self.node, 'Temporary Login Error'))
|
|
return
|
|
elif ('Unauthorized' in self.error or
|
|
'Incorrect password' in self.error):
|
|
self.output.put(
|
|
msg.ConfluentTargetInvalidCredentials(self.node))
|
|
return
|
|
else:
|
|
raise Exception(self.error)
|
|
if self.element == ['power', 'state']:
|
|
self.power()
|
|
elif self.element == ['_enclosure', 'reseat_bay']:
|
|
self.reseat_bay()
|
|
elif self.element == ['boot', 'nextdevice']:
|
|
self.bootdevice()
|
|
elif self.element == ['health', 'hardware']:
|
|
self.health()
|
|
elif self.element == ['identify']:
|
|
self.identify()
|
|
elif self.element[0] == 'sensors':
|
|
self.handle_sensors()
|
|
elif self.element[:2] == ['configuration', 'storage']:
|
|
self.handle_storage()
|
|
elif self.element[0] == 'configuration':
|
|
self.handle_configuration()
|
|
elif self.element[:3] == ['inventory', 'firmware', 'updates']:
|
|
self.handle_update()
|
|
elif self.element[0] == 'inventory':
|
|
self.handle_inventory()
|
|
elif self.element == ['media', 'attach']:
|
|
self.handle_attach_media()
|
|
elif self.element == ['media', 'detach']:
|
|
self.handle_detach_media()
|
|
elif self.element == ['media', 'uploads']:
|
|
self.handle_media_upload()
|
|
elif self.element == ['media', 'current']:
|
|
self.handle_list_media()
|
|
elif self.element == ['events', 'hardware', 'log']:
|
|
self.do_eventlog()
|
|
elif self.element == ['events', 'hardware', 'decode']:
|
|
self.decode_alert()
|
|
elif self.element == ['console', 'license']:
|
|
self.handle_license()
|
|
elif self.element == ['console', 'graphical']:
|
|
self.handle_graphical_console()
|
|
elif self.element == ['support', 'servicedata']:
|
|
self.handle_servicedata_fetch()
|
|
elif self.element == ['description']:
|
|
self.handle_description()
|
|
else:
|
|
raise Exception('Not Implemented')
|
|
|
|
def handle_update(self):
|
|
u = firmwaremanager.Updater(self.node, self.ipmicmd.update_firmware,
|
|
self.inputdata.nodefile(self.node), self.tenant,
|
|
bank=self.inputdata.bank, configmanager=self.cfm)
|
|
self.output.put(
|
|
msg.CreatedResource(
|
|
'nodes/{0}/inventory/firmware/updates/active/{1}'.format(
|
|
self.node, u.name)))
|
|
|
|
def handle_media_upload(self):
|
|
u = firmwaremanager.Updater(self.node, self.ipmicmd.upload_media,
|
|
self.inputdata.nodefile(self.node), self.tenant,
|
|
type='mediaupload', configmanager=self.cfm)
|
|
self.output.put(msg.CreatedResource(
|
|
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
|
|
|
def get_diags(self, savefile, progress, data=None):
|
|
return self.ipmicmd.get_diagnostic_data(
|
|
savefile, progress=progress, autosuffix=True)
|
|
|
|
def handle_servicedata_fetch(self):
|
|
u = firmwaremanager.Updater(
|
|
self.node, self.get_diags,
|
|
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
|
|
owner=self.current_user)
|
|
self.output.put(msg.CreatedResource(
|
|
'nodes/{0}/support/servicedata/{1}'.format(self.node, u.name)))
|
|
|
|
def handle_attach_media(self):
|
|
try:
|
|
self.ipmicmd.attach_remote_media(self.inputdata.nodefile(
|
|
self.node))
|
|
except pygexc.UnsupportedFunctionality as uf:
|
|
self.output.put(msg.ConfluentNodeError(self.node, str(uf)))
|
|
|
|
def handle_detach_media(self):
|
|
self.ipmicmd.detach_remote_media()
|
|
|
|
def handle_list_media(self):
|
|
for media in self.ipmicmd.list_media():
|
|
self.output.put(msg.Media(self.node, media))
|
|
|
|
def handle_configuration(self):
|
|
if self.element[1:3] == ['management_controller', 'alerts']:
|
|
return self.handle_alerts()
|
|
elif self.element[1:3] == ['management_controller', 'users']:
|
|
return self.handle_users()
|
|
elif self.element[1:3] == ['management_controller', 'net_interfaces']:
|
|
return self.handle_nets()
|
|
elif self.element[1:3] == ['management_controller', 'reset']:
|
|
return self.handle_reset()
|
|
elif self.element[1:3] == ['management_controller', 'identifier']:
|
|
return self.handle_identifier()
|
|
elif self.element[1:3] == ['management_controller', 'hostname']:
|
|
return self.handle_hostname()
|
|
elif self.element[1:3] == ['management_controller', 'domain_name']:
|
|
return self.handle_domain_name()
|
|
elif self.element[1:3] == ['management_controller', 'ntp']:
|
|
return self.handle_ntp()
|
|
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
|
|
return self.handle_bmcconfig()
|
|
elif self.element[1:4] == ['management_controller', 'extended', 'advanced']:
|
|
return self.handle_bmcconfig(True)
|
|
elif self.element[1:4] == ['management_controller', 'extended', 'extra']:
|
|
return self.handle_bmcconfig(True, extended=True)
|
|
elif self.element[1:3] == ['system', 'all']:
|
|
return self.handle_sysconfig()
|
|
elif self.element[1:3] == ['system', 'advanced']:
|
|
return self.handle_sysconfig(True)
|
|
elif self.element[1:3] == ['system', 'clear']:
|
|
return self.handle_sysconfigclear()
|
|
elif self.element[1:3] == ['management_controller', 'clear']:
|
|
return self.handle_bmcconfigclear()
|
|
elif self.element[1:3] == ['management_controller', 'licenses']:
|
|
return self.handle_licenses()
|
|
elif self.element[1:3] == ['management_controller', 'save_licenses']:
|
|
return self.save_licenses()
|
|
raise Exception('Not implemented')
|
|
|
|
def decode_alert(self):
|
|
inputdata = self.inputdata.get_alert(self.node)
|
|
specifictrap = int(inputdata['.1.3.6.1.6.3.1.1.4.1.0'].rpartition(
|
|
'.')[-1])
|
|
for tmpvarbind in inputdata:
|
|
if tmpvarbind.endswith('3183.1.1'):
|
|
varbinddata = inputdata[tmpvarbind]
|
|
varbinddata = hex2bin(varbinddata)
|
|
event = self.ipmicmd.decode_pet(specifictrap, varbinddata)
|
|
self.pyghmi_event_to_confluent(event)
|
|
self.output.put(msg.EventCollection((event,), name=self.node))
|
|
|
|
def handle_alerts(self):
|
|
if self.element[3] == 'destinations':
|
|
if len(self.element) == 4:
|
|
# A list of destinations
|
|
maxdest = self.ipmicmd.get_alert_destination_count()
|
|
for alertidx in range(0, maxdest + 1):
|
|
self.output.put(msg.ChildCollection(alertidx))
|
|
return
|
|
elif len(self.element) == 5:
|
|
alertidx = int(self.element[-1])
|
|
if self.op == 'read':
|
|
destdata = self.ipmicmd.get_alert_destination(alertidx)
|
|
self.output.put(msg.AlertDestination(
|
|
ip=destdata['address'],
|
|
acknowledge=destdata['acknowledge_required'],
|
|
acknowledge_timeout=destdata.get('acknowledge_timeout', None),
|
|
retries=destdata['retries'],
|
|
name=self.node))
|
|
return
|
|
elif self.op == 'update':
|
|
alertparms = self.inputdata.alert_params_by_node(
|
|
self.node)
|
|
alertargs = {}
|
|
if 'acknowledge' in alertparms:
|
|
alertargs['acknowledge_required'] = alertparms['acknowledge']
|
|
if 'acknowledge_timeout' in alertparms:
|
|
alertargs['acknowledge_timeout'] = alertparms['acknowledge_timeout']
|
|
if 'ip' in alertparms:
|
|
alertargs['ip'] = alertparms['ip']
|
|
if 'retries' in alertparms:
|
|
alertargs['retries'] = alertparms['retries']
|
|
self.ipmicmd.set_alert_destination(destination=alertidx,
|
|
**alertargs)
|
|
return
|
|
elif self.op == 'delete':
|
|
self.ipmicmd.clear_alert_destination(alertidx)
|
|
return
|
|
raise Exception('Not implemented')
|
|
|
|
def handle_nets(self):
|
|
if len(self.element) == 3:
|
|
if self.op != 'read':
|
|
self.output.put(
|
|
msg.ConfluentNodeError(self.node, 'Unsupported operation'))
|
|
return
|
|
self.output.put(msg.ChildCollection('management'))
|
|
elif len(self.element) == 4 and self.element[-1] == 'management':
|
|
if self.op == 'read':
|
|
lancfg = self.ipmicmd.get_net_configuration()
|
|
v6cfg = self.ipmicmd.get_net6_configuration()
|
|
self.output.put(msg.NetworkConfiguration(
|
|
self.node, ipv4addr=lancfg['ipv4_address'],
|
|
ipv4gateway=lancfg['ipv4_gateway'],
|
|
ipv4cfgmethod=lancfg['ipv4_configuration'],
|
|
hwaddr=lancfg['mac_address'],
|
|
staticv6addrs=v6cfg.get('static_addrs', ''),
|
|
staticv6gateway=v6cfg.get('static_gateway', ''),
|
|
))
|
|
elif self.op == 'update':
|
|
config = self.inputdata.netconfig(self.node)
|
|
try:
|
|
self.ipmicmd.set_net_configuration(
|
|
ipv4_address=config['ipv4_address'],
|
|
ipv4_configuration=config['ipv4_configuration'],
|
|
ipv4_gateway=config['ipv4_gateway'])
|
|
v6addrs = config.get('static_v6_addresses', None)
|
|
if v6addrs is not None:
|
|
v6addrs = v6addrs.split(',')
|
|
v6gw = config.get('static_v6_gateway', None)
|
|
self.ipmicmd.set_net6_configuration(static_addresses=v6addrs, static_gateway=v6gw)
|
|
except socket.error as se:
|
|
self.output.put(msg.ConfluentNodeError(self.node,
|
|
se.message))
|
|
except ValueError as e:
|
|
if e.message == 'negative shift count':
|
|
self.output.put(msg.ConfluentNodeError(
|
|
self.node, 'Invalid prefix length given'))
|
|
else:
|
|
raise
|
|
elif len(self.element) == 4 and self.element[-1] != 'management':
|
|
self.output.put(
|
|
msg.ConfluentTargetNotFound(self.node,
|
|
'Interface not found'))
|
|
|
|
def handle_users(self):
|
|
# Create user
|
|
if len(self.element) == 3:
|
|
if self.op == 'update':
|
|
user = self.inputdata.credentials[self.node]
|
|
self.ipmicmd.create_user(uid=user['uid'], name=user['username'],
|
|
password=user['password'],
|
|
callback=True,link_auth=True, ipmi_msg=True,
|
|
privilege_level=user['privilege_level'])
|
|
# A list of users
|
|
self.output.put(msg.ChildCollection('all'))
|
|
for user in self.ipmicmd.get_users():
|
|
self.output.put(msg.ChildCollection(user, candelete=True))
|
|
return
|
|
# List all users
|
|
elif len(self.element) == 4 and self.element[-1] == 'all':
|
|
users = []
|
|
for user in self.ipmicmd.get_users():
|
|
users.append(self.ipmicmd.get_user(uid=user))
|
|
self.output.put(msg.UserCollection(users=users, name=self.node))
|
|
return
|
|
# Update user
|
|
elif len(self.element) == 4:
|
|
user = int(self.element[-1])
|
|
if self.op == 'read':
|
|
data = self.ipmicmd.get_user(uid=user)
|
|
self.output.put(msg.User(
|
|
uid=data['uid'],
|
|
username=data['name'],
|
|
privilege_level=data['access']['privilege_level'],
|
|
expiration=data['expiration'],
|
|
name=self.node))
|
|
return
|
|
elif self.op == 'update':
|
|
user = self.inputdata.credentials[self.node]
|
|
|
|
if 'username' in user:
|
|
self.ipmicmd.set_user_name(uid=user['uid'],
|
|
name=user['username'])
|
|
|
|
if 'password' in user:
|
|
self.ipmicmd.set_user_password(uid=user['uid'],
|
|
password=user['password'])
|
|
self.ipmicmd.set_user_password(uid=user['uid'],
|
|
mode='enable', password=user['password'])
|
|
if 'privilege_level' in user:
|
|
self.ipmicmd.set_user_access(uid=user['uid'],
|
|
privilege_level=user[
|
|
'privilege_level'])
|
|
if 'enabled' in user:
|
|
if user['enabled'] == 'yes':
|
|
mode = 'enable'
|
|
else:
|
|
mode = 'disable'
|
|
self.ipmicmd.disable_user(user['uid'], mode)
|
|
return
|
|
elif self.op == 'delete':
|
|
self.ipmicmd.user_delete(uid=user)
|
|
return
|
|
|
|
def do_eventlog(self):
|
|
eventout = []
|
|
clear = False
|
|
if self.op == 'delete':
|
|
clear = True
|
|
for event in self.ipmicmd.get_event_log(clear):
|
|
self.pyghmi_event_to_confluent(event)
|
|
eventout.append(event)
|
|
self.output.put(msg.EventCollection(eventout, name=self.node))
|
|
|
|
def pyghmi_event_to_confluent(self, event):
|
|
event['severity'] = _str_health(event.get('severity', 'unknown'))
|
|
if 'event_data' in event:
|
|
event['event'] = '{0} - {1}'.format(
|
|
event['event'], event['event_data'])
|
|
if 'event_id' in event:
|
|
event['id'] = '{0}.{1}'.format(event['event_id'],
|
|
event['component_type_id'])
|
|
|
|
def make_inventory_map(self):
|
|
invnames = self.ipmicmd.get_inventory_descriptions()
|
|
for name in invnames:
|
|
self.invmap[simplify_name(name)] = name
|
|
|
|
def make_sensor_map(self, sensors=None):
|
|
if sensors is None:
|
|
sensors = self.ipmicmd.get_sensor_descriptions()
|
|
for sensor in sensors:
|
|
resourcename = sensor['name']
|
|
self.ipmicmd.sensormap[simplify_name(resourcename)] = resourcename
|
|
|
|
def read_normalized(self, sensorname):
|
|
readings = None
|
|
if sensorname == 'average_cpu_temp':
|
|
cputemp = self.ipmicmd.get_average_processor_temperature()
|
|
readings = [cputemp]
|
|
elif sensorname == 'inlet_temp':
|
|
inltemp = self.ipmicmd.get_inlet_temperature()
|
|
readings = [inltemp]
|
|
elif sensorname == 'total_power':
|
|
sensor = EmptySensor('Total Power')
|
|
sensor.states = []
|
|
sensor.units = 'W'
|
|
sensor.value = self.ipmicmd.get_system_power_watts()
|
|
readings = [sensor]
|
|
if readings:
|
|
self.output.put(msg.SensorReadings(readings, name=self.node))
|
|
|
|
def read_sensors(self, sensorname):
|
|
if sensorname == 'all':
|
|
sensors = self.ipmicmd.get_sensor_descriptions()
|
|
readings = []
|
|
for sensor in filter(self.match_sensor, sensors):
|
|
try:
|
|
reading = self.ipmicmd.get_sensor_reading(
|
|
sensor['name'])
|
|
except pygexc.IpmiException as ie:
|
|
if ie.ipmicode == 203:
|
|
self.output.put(msg.SensorReadings([EmptySensor(
|
|
sensor['name'])], name=self.node))
|
|
continue
|
|
raise
|
|
if hasattr(reading, 'health'):
|
|
reading.health = _str_health(reading.health)
|
|
if hasattr(reading, 'unavailable') and reading.unavailable:
|
|
self.output.put(msg.SensorReadings([EmptySensor(
|
|
reading.name)], name=self.node))
|
|
continue
|
|
readings.append(reading)
|
|
self.output.put(msg.SensorReadings(readings, name=self.node))
|
|
else:
|
|
if sensorname not in self.ipmicmd.sensormap:
|
|
self.make_sensor_map()
|
|
if sensorname not in self.ipmicmd.sensormap:
|
|
self.output.put(
|
|
msg.ConfluentTargetNotFound(self.node,
|
|
'Sensor not found'))
|
|
return
|
|
try:
|
|
reading = self.ipmicmd.get_sensor_reading(
|
|
self.ipmicmd.sensormap[sensorname])
|
|
if hasattr(reading, 'health'):
|
|
reading.health = _str_health(reading.health)
|
|
if hasattr(reading, 'unavailable') and reading.unavailable:
|
|
self.output.put(msg.SensorReadings([EmptySensor(
|
|
reading.name)], name=self.node))
|
|
else:
|
|
self.output.put(
|
|
msg.SensorReadings([reading],
|
|
name=self.node))
|
|
except pygexc.IpmiException as ie:
|
|
if ie.ipmicode == 203:
|
|
self.output.put(msg.ConfluentResourceUnavailable(
|
|
self.node, 'Unavailable'
|
|
))
|
|
else:
|
|
self.output.put(msg.ConfluentTargetTimeout(self.node))
|
|
|
|
def list_inventory(self):
|
|
try:
|
|
components = self.ipmicmd.get_inventory_descriptions()
|
|
except pygexc.IpmiException:
|
|
self.output.put(msg.ConfluentTargetTimeout(self.node))
|
|
return
|
|
self.output.put(msg.ChildCollection('all'))
|
|
for component in components:
|
|
self.output.put(msg.ChildCollection(simplify_name(component)))
|
|
|
|
def list_firmware(self):
|
|
self.output.put(msg.ChildCollection('all'))
|
|
for id, data in self.ipmicmd.get_firmware():
|
|
self.output.put(msg.ChildCollection(simplify_name(id)))
|
|
|
|
def read_firmware(self, component):
|
|
items = []
|
|
errorneeded = False
|
|
try:
|
|
complist = () if component == 'all' else (component,)
|
|
for id, data in self.ipmicmd.get_firmware(complist):
|
|
if (component in ('core', 'all') or
|
|
component == simplify_name(id)):
|
|
items.append({id: data})
|
|
except ssl.SSLEOFError:
|
|
errorneeded = msg.ConfluentNodeError(
|
|
self.node, 'Unable to communicate with the https server on '
|
|
'the target BMC while trying to read extended '
|
|
'information')
|
|
except exc.PubkeyInvalid:
|
|
errorneeded = msg.ConfluentNodeError(
|
|
self.node,
|
|
'Extended information unavailable, mismatch detected between '
|
|
'target certificate fingerprint and '
|
|
'pubkeys.tls_hardwaremanager attribute')
|
|
except pygexc.TemporaryError as e:
|
|
errorneeded = msg.ConfluentNodeError(
|
|
self.node, str(e))
|
|
self.output.put(msg.Firmware(items, self.node))
|
|
if errorneeded:
|
|
self.output.put(errorneeded)
|
|
|
|
def handle_inventory(self):
|
|
if self.element[1] == 'firmware':
|
|
if len(self.element) == 3:
|
|
return self.list_firmware()
|
|
elif len(self.element) == 4:
|
|
return self.read_firmware(self.element[-1])
|
|
elif self.element[1] == 'hardware':
|
|
if len(self.element) == 3: # list things in inventory
|
|
return self.list_inventory()
|
|
elif len(self.element) == 4: # actually read inventory data
|
|
return self.read_inventory(self.element[-1])
|
|
raise Exception('Unsupported scenario...')
|
|
|
|
def list_leds(self):
|
|
self.output.put(msg.ChildCollection('all'))
|
|
for category, info in self.ipmicmd.get_leds():
|
|
self.output.put(msg.ChildCollection(simplify_name(category)))
|
|
|
|
def read_leds(self, component):
|
|
led_categories = []
|
|
for category, info in self.ipmicmd.get_leds():
|
|
if component == 'all' or component == simplify_name(category):
|
|
led_categories.append({category: info})
|
|
self.output.put(msg.LEDStatus(led_categories, self.node))
|
|
|
|
def read_inventory(self, component):
|
|
errorneeded = False
|
|
try:
|
|
invitems = []
|
|
if component == 'all':
|
|
for invdata in self.ipmicmd.get_inventory():
|
|
if invdata[1] is None:
|
|
newinf = {'present': False, 'information': None,
|
|
'name': invdata[0]}
|
|
|
|
else:
|
|
sanitize_invdata(invdata[1])
|
|
newinf = {'present': True, 'information': invdata[1]}
|
|
newinf['name'] = invdata[1].get('name', invdata[0])
|
|
self.add_invitem(invitems, newinf)
|
|
else:
|
|
self.make_inventory_map()
|
|
compname = self.invmap.get(component, None)
|
|
if compname is None:
|
|
self.output.put(msg.ConfluentTargetNotFound())
|
|
return
|
|
invdata = self.ipmicmd.get_inventory_of_component(compname)
|
|
if invdata is None:
|
|
newinf = {'present': False, 'information': None,
|
|
'name': compname}
|
|
else:
|
|
sanitize_invdata(invdata)
|
|
newinf = {'present': True, 'information': invdata,
|
|
'name': invdata.get('name', compname)}
|
|
self.add_invitem(invitems, newinf)
|
|
except ssl.SSLEOFError:
|
|
errorneeded = msg.ConfluentNodeError(
|
|
self.node, 'Unable to communicate with the https server on '
|
|
'the target BMC while trying to read extended '
|
|
'information')
|
|
except exc.PubkeyInvalid:
|
|
errorneeded = msg.ConfluentNodeError(
|
|
self.node,
|
|
'Extended information unavailable, mismatch detected between '
|
|
'target certificate fingerprint and '
|
|
'pubkeys.tls_hardwaremanager attribute')
|
|
newinvdata = {'inventory': invitems}
|
|
self.output.put(msg.KeyValueData(newinvdata, self.node))
|
|
if errorneeded:
|
|
self.output.put(errorneeded)
|
|
|
|
def add_invitem(self, invitems, newinf):
|
|
if newinf.get('information', None) and 'name' in newinf['information']:
|
|
newinf = copy.deepcopy(newinf)
|
|
del newinf['information']['name']
|
|
if (fnmatch(newinf['name'], 'Adapter ??:??:??') or fnmatch(
|
|
newinf['name'], 'PCIeGen? x*') or not newinf['name']):
|
|
myinf = newinf.get('information', {})
|
|
sdid = myinf.get('PCI Subsystem Device ID', None)
|
|
svid = myinf.get('PCI Subsystem Vendor ID', None)
|
|
did = myinf.get('PCI Device ID', None)
|
|
vid = myinf.get('PCI Vendor ID', None)
|
|
vstr, dstr = get_pci_text_from_ids(sdid, svid, did, vid)
|
|
if vstr:
|
|
newinf['information']['PCI Vendor'] = vstr
|
|
if dstr:
|
|
newinf['name'] = dstr
|
|
invitems.append(newinf)
|
|
|
|
def handle_storage(self):
|
|
if self.element[-1] == '':
|
|
self.element = self.element[:-1]
|
|
storelem = self.element[2:]
|
|
if 'read' == self.op:
|
|
return self._show_storage(storelem)
|
|
elif 'update' == self.realop:
|
|
return self._update_storage(storelem)
|
|
elif 'delete' == self.op:
|
|
return self._delete_storage(storelem)
|
|
elif 'create' == self.realop:
|
|
return self._create_storage(storelem)
|
|
|
|
def _delete_storage(self, storelem):
|
|
if len(storelem) < 2:
|
|
storelem.append('')
|
|
if len(storelem) < 2 or storelem[0] != 'volumes':
|
|
raise exc.InvalidArgumentException('Must target a specific volume')
|
|
volname = storelem[-1]
|
|
curr = self.ipmicmd.get_storage_configuration()
|
|
volumes = []
|
|
volsfound = False
|
|
toremove = storage.ConfigSpec(arrays=[storage.Array(volumes=volumes)])
|
|
for pool in curr.arrays:
|
|
for vol in pool.volumes:
|
|
if simplify_name(vol.name) == volname:
|
|
volsfound = True
|
|
volumes.append(vol)
|
|
if not volsfound:
|
|
self.output.put(msg.ConfluentTargetNotFound(
|
|
self.node, "No volume named '{0}' found".format(volname)))
|
|
return
|
|
self.ipmicmd.remove_storage_configuration(toremove)
|
|
self.output.put(msg.DeletedResource(volname))
|
|
|
|
def _create_storage(self, storelem):
|
|
if 'volumes' not in storelem:
|
|
raise exc.InvalidArgumentException('Can only create volumes')
|
|
vols = []
|
|
thedisks = None
|
|
currcfg = self.ipmicmd.get_storage_configuration()
|
|
currnames = []
|
|
for arr in currcfg.arrays:
|
|
arrname = '{0}-{1}'.format(*arr.id)
|
|
for vol in arr.volumes:
|
|
currnames.append(vol.name)
|
|
disks = []
|
|
vols = []
|
|
vol = self.inputdata.inputbynode[self.node][0]
|
|
raidlvl = vol['raidlevel']
|
|
for disk in currcfg.disks:
|
|
if simplify_name(disk.name) in vol['disks']:
|
|
disks.append(disk)
|
|
elif (disk.status == 'Unconfigured Good' and
|
|
vol['disks'][0] in ('remainder', 'rest')):
|
|
disks.append(disk)
|
|
elif vol['disks'][0] == 'all':
|
|
disks.append(disk)
|
|
for vol in self.inputdata.inputbynode[self.node]:
|
|
if thedisks and thedisks != vol['disks']:
|
|
raise exc.InvalidArgumentException(
|
|
'Not currently supported to create multiple arrays '
|
|
'in a single request')
|
|
if raidlvl and vol['raidlevel'] != raidlvl:
|
|
raise exc.InvalidArgumentException('Cannot mix raid levels in '
|
|
'a single array')
|
|
vols.append(storage.Volume(name=vol['name'], size=vol['size'], stripsize=vol['stripsize']))
|
|
newcfg = storage.ConfigSpec(
|
|
arrays=(storage.Array(raid=raidlvl, disks=disks, volumes=vols),))
|
|
self.ipmicmd.apply_storage_configuration(newcfg)
|
|
for vol in self.inputdata.inputbynode[self.node]:
|
|
if vol['name'] is None:
|
|
newcfg = self.ipmicmd.get_storage_configuration()
|
|
for arr in newcfg.arrays:
|
|
arrname = '{0}-{1}'.format(*arr.id)
|
|
for vol in arr.volumes:
|
|
if vol.name not in currnames:
|
|
self.output.put(
|
|
msg.Volume(self.node, vol.name, vol.size,
|
|
vol.status, arrname))
|
|
return
|
|
else:
|
|
self._show_storage(storelem[:1] + [vol['name']])
|
|
|
|
def _update_storage(self, storelem):
|
|
if storelem[0] == 'disks':
|
|
if len(storelem) == 1:
|
|
raise exc.InvalidArgumentException('Must target a disk')
|
|
self.set_disk(storelem[-1],
|
|
self.inputdata.inputbynode[self.node])
|
|
self._show_storage(storelem)
|
|
|
|
def _show_storage(self, storelem):
|
|
if storelem[0] == 'disks':
|
|
if len(storelem) == 1:
|
|
return self.list_disks()
|
|
return self.show_disk(storelem[1])
|
|
elif storelem[0] == 'arrays':
|
|
if len(storelem) == 1:
|
|
return self.list_arrays()
|
|
return self.show_array(storelem[1])
|
|
elif storelem[0] == 'volumes':
|
|
if len(storelem) == 1:
|
|
return self.list_volumes()
|
|
return self.show_volume(storelem[1])
|
|
elif storelem[0] == 'all':
|
|
return self._show_all_storage()
|
|
|
|
|
|
def handle_sensors(self):
|
|
if self.element[-1] == '':
|
|
self.element = self.element[:-1]
|
|
if len(self.element) < 3:
|
|
return
|
|
self.sensorcategory = self.element[2]
|
|
if self.sensorcategory == 'normalized':
|
|
return self.read_normalized(self.element[-1])
|
|
# list sensors per category
|
|
if len(self.element) == 3 and self.element[-2] == 'hardware':
|
|
if self.sensorcategory == 'leds':
|
|
return self.list_leds()
|
|
return self.list_sensors()
|
|
elif len(self.element) == 4: # resource requested
|
|
if self.sensorcategory == 'leds':
|
|
return self.read_leds(self.element[-1])
|
|
return self.read_sensors(self.element[-1])
|
|
|
|
def match_sensor(self, sensor):
|
|
if self.sensorcategory == 'all':
|
|
return True
|
|
if sensor['type'] in sensor_categories[self.sensorcategory]:
|
|
return True
|
|
return False
|
|
|
|
def set_disk(self, name, state):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for disk in scfg.disks:
|
|
if (name == 'all' or simplify_name(disk.name) == name or
|
|
disk == name):
|
|
disk.status = state
|
|
self.ipmicmd.apply_storage_configuration(
|
|
storage.ConfigSpec(disks=scfg.disks))
|
|
|
|
def _show_all_storage(self):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for disk in scfg.disks:
|
|
self.output.put(
|
|
msg.Disk(self.node, disk.name, disk.description,
|
|
disk.id, disk.status, disk.serial,
|
|
disk.fru))
|
|
for arr in scfg.arrays:
|
|
for disk in arr.disks:
|
|
self.output.put(
|
|
msg.Disk(self.node, disk.name, disk.description,
|
|
disk.id, disk.status, disk.serial,
|
|
disk.fru, array='{0}-{1}'.format(*arr.id)))
|
|
for disk in arr.hotspares:
|
|
self.output.put(
|
|
msg.Disk(self.node, disk.name, disk.description,
|
|
disk.id, disk.status, disk.serial,
|
|
disk.fru, array='{0}-{1}'.format(*arr.id)))
|
|
for arr in scfg.arrays:
|
|
arrname = '{0}-{1}'.format(*arr.id)
|
|
self._detail_array(arr, arrname, True)
|
|
|
|
def show_disk(self, name):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for disk in scfg.disks:
|
|
if simplify_name(disk.name) == name or disk == name:
|
|
self.output.put(
|
|
msg.Disk(self.node, disk.name, disk.description,
|
|
disk.id, disk.status, disk.serial,
|
|
disk.fru))
|
|
for arr in scfg.arrays:
|
|
arrname = '{0}-{1}'.format(*arr.id)
|
|
for disk in arr.disks:
|
|
if (name == 'all' or simplify_name(disk.name) == name or
|
|
disk == name):
|
|
self.output.put(
|
|
msg.Disk(self.node, disk.name, disk.description,
|
|
disk.id, disk.status, disk.serial,
|
|
disk.fru, arrname))
|
|
for disk in arr.hotspares:
|
|
if (name == 'all' or simplify_name(disk.name) == name or
|
|
disk == name):
|
|
self.output.put(
|
|
msg.Disk(self.node, disk.name, disk.description,
|
|
disk.id, disk.status, disk.serial,
|
|
disk.fru, arrname))
|
|
|
|
def list_disks(self):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for disk in scfg.disks:
|
|
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
|
|
for arr in scfg.arrays:
|
|
for disk in arr.disks:
|
|
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
|
|
for disk in arr.hotspares:
|
|
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
|
|
|
|
def list_arrays(self):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for arr in scfg.arrays:
|
|
self.output.put(msg.ChildCollection('{0}-{1}'.format(*arr.id)))
|
|
|
|
def show_array(self, name):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for arr in scfg.arrays:
|
|
arrname = '{0}-{1}'.format(*arr.id)
|
|
if arrname == name:
|
|
self._detail_array(arr, arrname)
|
|
|
|
def _detail_array(self, arr, arrname, detailvol=False):
|
|
vols = []
|
|
for vol in arr.volumes:
|
|
vols.append(simplify_name(vol.name))
|
|
disks = []
|
|
for disk in arr.disks:
|
|
disks.append(simplify_name(disk.name))
|
|
for disk in arr.hotspares:
|
|
disks.append(simplify_name(disk.name))
|
|
self.output.put(msg.Array(self.node, disks, arr.raid,
|
|
vols, arrname, arr.capacity,
|
|
arr.available_capacity))
|
|
if detailvol:
|
|
for vol in arr.volumes:
|
|
self.output.put(msg.Volume(self.node, vol.name, vol.size,
|
|
vol.status, arrname))
|
|
|
|
def show_volume(self, name):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for arr in scfg.arrays:
|
|
arrname = '{0}-{1}'.format(*arr.id)
|
|
for vol in arr.volumes:
|
|
if name == simplify_name(vol.name):
|
|
self.output.put(msg.Volume(self.node, vol.name, vol.size,
|
|
vol.status, arrname))
|
|
|
|
def list_volumes(self):
|
|
scfg = self.ipmicmd.get_storage_configuration()
|
|
for arr in scfg.arrays:
|
|
for vol in arr.volumes:
|
|
self.output.put(msg.ChildCollection(simplify_name(vol.name)))
|
|
|
|
def list_sensors(self):
|
|
try:
|
|
sensors = self.ipmicmd.get_sensor_descriptions()
|
|
except pygexc.IpmiException:
|
|
self.output.put(msg.ConfluentTargetTimeout(self.node))
|
|
return
|
|
self.output.put(msg.ChildCollection('all'))
|
|
for sensor in filter(self.match_sensor, sensors):
|
|
self.output.put(msg.ChildCollection(simplify_name(sensor['name'])))
|
|
|
|
def health(self):
|
|
if 'read' == self.op:
|
|
try:
|
|
response = self.ipmicmd.get_health()
|
|
except pygexc.IpmiException:
|
|
self.output.put(msg.ConfluentTargetTimeout(self.node))
|
|
return
|
|
health = response['health']
|
|
health = _str_health(health)
|
|
self.output.put(msg.HealthSummary(health, self.node))
|
|
if 'badreadings' in response:
|
|
badsensors = []
|
|
for reading in response['badreadings']:
|
|
if hasattr(reading, 'health'):
|
|
reading.health = _str_health(reading.health)
|
|
badsensors.append(reading)
|
|
self.output.put(msg.SensorReadings(badsensors, name=self.node))
|
|
else:
|
|
raise exc.InvalidArgumentException('health is read-only')
|
|
|
|
def reseat_bay(self):
|
|
bay = self.inputdata.inputbynode[self.node]
|
|
try:
|
|
self.ipmicmd.reseat_bay(bay)
|
|
self.output.put(msg.ReseatResult(self.node, 'success'))
|
|
except pygexc.UnsupportedFunctionality as uf:
|
|
self.output.put(uf)
|
|
|
|
def bootdevice(self):
|
|
if 'read' == self.op:
|
|
bootdev = self.ipmicmd.get_bootdev()
|
|
if bootdev['bootdev'] in self.bootdevices:
|
|
bootdev['bootdev'] = self.bootdevices[bootdev['bootdev']]
|
|
bootmode = 'unspecified'
|
|
if 'uefimode' in bootdev:
|
|
if bootdev['uefimode']:
|
|
bootmode = 'uefi'
|
|
else:
|
|
bootmode = 'bios'
|
|
persistent = False
|
|
if 'persistent' in bootdev:
|
|
persistent = bootdev['persistent']
|
|
self.output.put(msg.BootDevice(node=self.node,
|
|
device=bootdev['bootdev'],
|
|
bootmode=bootmode,
|
|
persistent=persistent))
|
|
return
|
|
elif 'update' == self.op:
|
|
bootdev = self.inputdata.bootdevice(self.node)
|
|
douefi = False
|
|
if self.inputdata.bootmode(self.node) == 'uefi':
|
|
douefi = True
|
|
persistent = self.inputdata.persistent(self.node)
|
|
bootdev = self.ipmicmd.set_bootdev(bootdev, uefiboot=douefi,
|
|
persist=persistent)
|
|
if bootdev['bootdev'] in self.bootdevices:
|
|
bootdev['bootdev'] = self.bootdevices[bootdev['bootdev']]
|
|
self.output.put(msg.BootDevice(node=self.node,
|
|
device=bootdev['bootdev']))
|
|
|
|
def identify(self):
|
|
if 'update' == self.op:
|
|
identifystate = self.inputdata.inputbynode[self.node] == 'on'
|
|
if self.inputdata.inputbynode[self.node] == 'blink':
|
|
raise exc.InvalidArgumentException(
|
|
'"blink" is not supported with ipmi')
|
|
self.ipmicmd.set_identify(on=identifystate)
|
|
self.output.put(msg.IdentifyState(
|
|
node=self.node, state=self.inputdata.inputbynode[self.node]))
|
|
return
|
|
elif 'read' == self.op:
|
|
# ipmi has identify as read-only for now
|
|
self.output.put(msg.IdentifyState(node=self.node, state=''))
|
|
return
|
|
|
|
def power(self):
|
|
if 'read' == self.op:
|
|
power = self.ipmicmd.get_power()
|
|
self.output.put(msg.PowerState(node=self.node,
|
|
state=power['powerstate']))
|
|
return
|
|
elif 'update' == self.op:
|
|
powerstate = self.inputdata.powerstate(self.node)
|
|
oldpower = None
|
|
waitamount = 30
|
|
if powerstate == 'boot':
|
|
oldpower = self.ipmicmd.get_power()
|
|
if 'powerstate' in oldpower:
|
|
oldpower = oldpower['powerstate']
|
|
elif powerstate == 'shutdown':
|
|
waitamount = True
|
|
self.ipmicmd.set_power(powerstate, wait=waitamount)
|
|
if powerstate == 'boot' and oldpower == 'on':
|
|
power = {'powerstate': 'reset'}
|
|
else:
|
|
power = self.ipmicmd.get_power()
|
|
if powerstate == 'reset' and power['powerstate'] == 'on':
|
|
power['powerstate'] = 'reset'
|
|
|
|
self.output.put(msg.PowerState(node=self.node,
|
|
state=power['powerstate'],
|
|
oldstate=oldpower))
|
|
return
|
|
|
|
def handle_reset(self):
|
|
if 'read' == self.op:
|
|
self.output.put(msg.BMCReset(node=self.node,
|
|
state='reset'))
|
|
return
|
|
elif 'update' == self.op:
|
|
self.ipmicmd.reset_bmc()
|
|
return
|
|
|
|
def handle_identifier(self):
|
|
if 'read' == self.op:
|
|
mci = self.ipmicmd.get_mci()
|
|
self.output.put(msg.MCI(self.node, mci))
|
|
return
|
|
elif 'update' == self.op:
|
|
mci = self.inputdata.mci(self.node)
|
|
self.ipmicmd.set_mci(mci)
|
|
return
|
|
|
|
def handle_hostname(self):
|
|
if 'read' == self.op:
|
|
hostname = self.ipmicmd.get_hostname()
|
|
self.output.put(msg.Hostname(self.node, hostname))
|
|
return
|
|
elif 'update' == self.op:
|
|
hostname = self.inputdata.hostname(self.node)
|
|
self.ipmicmd.set_hostname(hostname)
|
|
return
|
|
|
|
def handle_domain_name(self):
|
|
if 'read' == self.op:
|
|
dn = self.ipmicmd.get_domain_name()
|
|
self.output.put(msg.DomainName(self.node, dn))
|
|
return
|
|
elif 'update' == self.op:
|
|
dn = self.inputdata.domain_name(self.node)
|
|
self.ipmicmd.set_domain_name(dn)
|
|
return
|
|
|
|
def handle_bmcconfigclear(self):
|
|
if 'read' == self.op:
|
|
raise exc.InvalidArgumentException(
|
|
'Cannot read the "clear" resource')
|
|
self.ipmicmd.clear_bmc_configuration()
|
|
|
|
def handle_sysconfigclear(self):
|
|
if 'read' == self.op:
|
|
raise exc.InvalidArgumentException(
|
|
'Cannot read the "clear" resource')
|
|
self.ipmicmd.clear_system_configuration()
|
|
|
|
def handle_bmcconfig(self, advanced=False, extended=False):
|
|
if 'read' == self.op:
|
|
try:
|
|
if extended:
|
|
bmccfg = self.ipmicmd.get_extended_bmc_configuration()
|
|
else:
|
|
bmccfg = self.ipmicmd.get_bmc_configuration()
|
|
self.output.put(msg.ConfigSet(self.node, bmccfg))
|
|
except Exception as e:
|
|
self.output.put(
|
|
msg.ConfluentNodeError(self.node, str(e)))
|
|
elif 'update' == self.op:
|
|
self.ipmicmd.set_bmc_configuration(
|
|
self.inputdata.get_attributes(self.node))
|
|
|
|
def handle_sysconfig(self, advanced=False):
|
|
if 'read' == self.op:
|
|
try:
|
|
self.output.put(msg.ConfigSet(
|
|
self.node, self.ipmicmd.get_system_configuration(
|
|
hideadvanced=not advanced)))
|
|
except Exception as e:
|
|
self.output.put(
|
|
msg.ConfluentNodeError(self.node, str(e)))
|
|
elif 'update' == self.op:
|
|
self.ipmicmd.set_system_configuration(
|
|
self.inputdata.get_attributes(self.node))
|
|
|
|
def handle_ntp(self):
|
|
if self.element[3] == 'enabled':
|
|
if 'read' == self.op:
|
|
enabled = self.ipmicmd.get_ntp_enabled()
|
|
self.output.put(msg.NTPEnabled(self.node, enabled))
|
|
return
|
|
elif 'update' == self.op:
|
|
enabled = self.inputdata.ntp_enabled(self.node)
|
|
self.ipmicmd.set_ntp_enabled(enabled == 'True')
|
|
return
|
|
elif self.element[3] == 'servers':
|
|
if len(self.element) == 4:
|
|
self.output.put(msg.ChildCollection('all'))
|
|
size = len(self.ipmicmd.get_ntp_servers())
|
|
for idx in range(1, size + 1):
|
|
self.output.put(msg.ChildCollection(idx))
|
|
else:
|
|
if 'read' == self.op:
|
|
if self.element[-1] == 'all':
|
|
servers = self.ipmicmd.get_ntp_servers()
|
|
self.output.put(msg.NTPServers(self.node, servers))
|
|
return
|
|
else:
|
|
idx = int(self.element[-1]) - 1
|
|
servers = self.ipmicmd.get_ntp_servers()
|
|
if len(servers) > idx:
|
|
self.output.put(msg.NTPServer(self.node, servers[idx]))
|
|
else:
|
|
self.output.put(
|
|
msg.ConfluentTargetNotFound(
|
|
self.node, 'Requested NTP configuration not found'))
|
|
return
|
|
elif self.op in ('update', 'create'):
|
|
if self.element[-1] == 'all':
|
|
servers = self.inputdata.ntp_servers(self.node)
|
|
for idx in servers:
|
|
self.ipmicmd.set_ntp_server(servers[idx],
|
|
int(idx[-1])-1)
|
|
return
|
|
else:
|
|
idx = int(self.element[-1]) - 1
|
|
server = self.inputdata.ntp_server(self.node)
|
|
self.ipmicmd.set_ntp_server(server, idx)
|
|
return
|
|
|
|
def handle_license(self):
|
|
available = self.ipmicmd.get_remote_kvm_available()
|
|
self.output.put(msg.License(self.node, available))
|
|
return
|
|
|
|
def save_licenses(self):
|
|
directory = self.inputdata.nodefile(self.node)
|
|
checkdir = directory
|
|
if not os.access(directory, os.W_OK):
|
|
raise exc.InvalidArgumentException(
|
|
'The confluent system user/group is unable to write to '
|
|
'directory {0}, check ownership and permissions'.format(
|
|
checkdir))
|
|
for saved in self.ipmicmd.save_licenses(directory):
|
|
if self.current_user:
|
|
try:
|
|
pwent = pwd.getpwnam(self.current_user)
|
|
os.chown(saved, pwent.pw_uid, pwent.pw_gid)
|
|
except KeyError:
|
|
pass
|
|
self.output.put(msg.SavedFile(self.node, saved))
|
|
|
|
def handle_licenses(self):
|
|
if self.element[-1] == '':
|
|
self.element = self.element[:-1]
|
|
if self.op in ('create', 'update'):
|
|
filename = self.inputdata.nodefile(self.node)
|
|
datfile = None
|
|
if filename in self.cfm.clientfiles:
|
|
cf = self.cfm.clientfiles[filename]
|
|
datfile = os.fdopen(os.dup(cf.fileno()), cf.mode)
|
|
if datfile is None and not os.access(filename, os.R_OK):
|
|
errstr = ('{0} is not readable by confluent on {1} '
|
|
'(ensure confluent user or group can access file '
|
|
'and parent directories)').format(
|
|
filename, socket.gethostname())
|
|
self.output.put(msg.ConfluentNodeError(self.node, errstr))
|
|
return
|
|
try:
|
|
self.ipmicmd.apply_license(filename, data=datfile)
|
|
finally:
|
|
if datfile is not None:
|
|
datfile.close()
|
|
if len(self.element) == 3:
|
|
self.output.put(msg.ChildCollection('all'))
|
|
i = 1
|
|
for lic in self.ipmicmd.get_licenses():
|
|
self.output.put(msg.ChildCollection(str(i)))
|
|
i += 1
|
|
return
|
|
licname = self.element[3]
|
|
if licname == 'all':
|
|
for lic in self.ipmicmd.get_licenses():
|
|
if self.op == 'delete':
|
|
self.ipmicmd.delete_license(lic['name'])
|
|
else:
|
|
self.output.put(msg.License(self.node, feature=lic['name'], state=lic.get('state', 'Active')))
|
|
else:
|
|
index = int(licname)
|
|
lic = list(self.ipmicmd.get_licenses())[index - 1]
|
|
if self.op == 'delete':
|
|
self.ipmicmd.delete_license(lic['name'])
|
|
else:
|
|
self.output.put(msg.License(self.node, feature=lic['name'], state=lic.get('state', 'Active')))
|
|
def handle_description(self):
|
|
dsc = self.ipmicmd.get_description()
|
|
self.output.put(msg.KeyValueData(dsc, self.node))
|
|
|
|
def handle_graphical_console(self):
|
|
args = self.ipmicmd.get_graphical_console()
|
|
m = msg.GraphicalConsole(self.node, *args)
|
|
self.output.put(m)
|
|
return
|
|
|
|
|
|
def _str_health(health):
|
|
if isinstance(health, str):
|
|
return health
|
|
if pygconstants.Health.Failed & health:
|
|
health = 'failed'
|
|
elif pygconstants.Health.Critical & health:
|
|
health = 'critical'
|
|
elif pygconstants.Health.Warning & health:
|
|
health = 'warning'
|
|
else:
|
|
health = 'ok'
|
|
return health
|
|
|
|
|
|
def initthread():
|
|
global _ipmithread
|
|
if _ipmithread is None:
|
|
_ipmithread = eventlet.spawn(_ipmi_evtloop)
|
|
|
|
|
|
def create(nodes, element, configmanager, inputdata, realop='create'):
|
|
initthread()
|
|
if element == ['_console', 'session']:
|
|
if len(nodes) > 1:
|
|
raise Exception("_console/session does not support multiple nodes")
|
|
return IpmiConsole(nodes[0], configmanager)
|
|
else:
|
|
return perform_requests(
|
|
'update', nodes, element, configmanager, inputdata, realop)
|
|
|
|
|
|
def update(nodes, element, configmanager, inputdata):
|
|
initthread()
|
|
return create(nodes, element, configmanager, inputdata, 'update')
|
|
|
|
|
|
def retrieve(nodes, element, configmanager, inputdata):
|
|
initthread()
|
|
if '/'.join(element).startswith('inventory/firmware/updates/active'):
|
|
return firmwaremanager.list_updates(nodes, configmanager.tenant,
|
|
element)
|
|
elif '/'.join(element).startswith('media/uploads'):
|
|
return firmwaremanager.list_updates(nodes, configmanager.tenant,
|
|
element, 'mediaupload')
|
|
elif '/'.join(element).startswith('support/servicedata'):
|
|
return firmwaremanager.list_updates(nodes, configmanager.tenant,
|
|
element, 'ffdc')
|
|
else:
|
|
return perform_requests('read', nodes, element, configmanager,
|
|
inputdata, 'read')
|
|
|
|
def delete(nodes, element, configmanager, inputdata):
|
|
initthread()
|
|
if '/'.join(element).startswith('inventory/firmware/updates/active'):
|
|
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
|
|
element)
|
|
elif '/'.join(element).startswith('media/uploads'):
|
|
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
|
|
element, type='mediaupload')
|
|
elif '/'.join(element).startswith('support/servicedata'):
|
|
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
|
|
element, type='ffdc')
|
|
return perform_requests(
|
|
'delete', nodes, element, configmanager, inputdata, 'delete')
|
|
|