mirror of
https://github.com/xcat2/confluent.git
synced 2025-01-17 21:23:18 +00:00
Merge branch 'master' of ssh://git.code.sf.net/p/xcat/confluent
This commit is contained in:
commit
2d9df67272
@ -46,7 +46,6 @@ import math
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
import readline
|
||||
import select
|
||||
import shlex
|
||||
import socket
|
||||
@ -601,9 +600,11 @@ except socket.gaierror:
|
||||
# sys.stdout.write('\x1b[H\x1b[J')
|
||||
# sys.stdout.flush()
|
||||
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.parse_and_bind("set bell-style none")
|
||||
readline.set_completer(completer)
|
||||
if sys.stdout.isatty():
|
||||
import readline
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.parse_and_bind("set bell-style none")
|
||||
readline.set_completer(completer)
|
||||
doexit = False
|
||||
inconsole = False
|
||||
pendingcommand = ""
|
||||
|
59
confluent_server/confluent/alerts.py
Normal file
59
confluent_server/confluent/alerts.py
Normal file
@ -0,0 +1,59 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 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.
|
||||
|
||||
# This handles incoming unsolicited alerts over the network. For the moment
|
||||
# we'll link into ipmi.py to do PET alerts, with the assumption that more
|
||||
# typical .mib based handling will be used for other events and confluent's
|
||||
# event service is to handle the peculiarities of an IPMI PET. In the future
|
||||
# it may make sense to extend this in a more general case, but that rework can
|
||||
# be deferred for now.
|
||||
|
||||
# Phase 1 is to be facilitating some application doing http calls to get help
|
||||
# decoding data.
|
||||
|
||||
# Phase 2 is to be able to bind a port and have snmptrapd just forward the
|
||||
# packet rather than have something block snmptrapd at all for things confluent
|
||||
# can handle.
|
||||
|
||||
__author__ = 'jjohnson2'
|
||||
|
||||
import confluent.exceptions as exc
|
||||
import confluent.lookuptools as lookuptools
|
||||
import confluent.core
|
||||
|
||||
def decode_alert(varbinds, configmanager):
|
||||
"""Decode an SNMP alert for a server
|
||||
|
||||
Given the agentaddr, OID for the trap, and a dict of varbinds,
|
||||
ascertain the node identity and then request a decode
|
||||
|
||||
:param varbinds: A dictionary of OID to value varbinds. Also supported
|
||||
are special keywords 'enterprise' and 'specificTrap' for
|
||||
SNMPv1 traps.
|
||||
|
||||
"""
|
||||
try:
|
||||
agentaddr = varbinds['.1.3.6.1.6.3.18.1.3.0']
|
||||
except KeyError:
|
||||
agentaddr = varbinds['1.3.6.1.6.3.18.1.3.0']
|
||||
node = lookuptools.node_by_manager(agentaddr)
|
||||
if node is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Unable to find a node with specified manager')
|
||||
return confluent.core.handle_path(
|
||||
'/nodes/{0}/events/hardware/decode'.format(node), 'update',
|
||||
configmanager, varbinds, autostrip=False)
|
||||
|
@ -33,6 +33,7 @@
|
||||
# functions. Console is special and just get's passed through
|
||||
# see API.txt
|
||||
|
||||
import confluent.alerts as alerts
|
||||
import confluent.config.attributes as attrscheme
|
||||
import confluent.interface.console as console
|
||||
import confluent.exceptions as exc
|
||||
@ -93,7 +94,7 @@ def load_plugins():
|
||||
pluginmap[plugin] = tmpmod
|
||||
|
||||
|
||||
rootcollections = ['noderange/', 'nodes/', 'nodegroups/', 'users/']
|
||||
rootcollections = ['noderange/', 'nodes/', 'nodegroups/', 'users/', 'events/']
|
||||
|
||||
|
||||
class PluginRoute(object):
|
||||
@ -142,7 +143,11 @@ noderesources = {
|
||||
'log': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
})
|
||||
}),
|
||||
'decode': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
},
|
||||
'health': {
|
||||
@ -386,7 +391,7 @@ def handle_nodegroup_request(configmanager, inputdata,
|
||||
|
||||
|
||||
def handle_node_request(configmanager, inputdata, operation,
|
||||
pathcomponents):
|
||||
pathcomponents, autostrip=True):
|
||||
iscollection = False
|
||||
routespec = None
|
||||
if pathcomponents[0] == 'noderange':
|
||||
@ -491,7 +496,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
nodes=nodesbyhandler[hfunc], element=pathcomponents,
|
||||
configmanager=configmanager,
|
||||
inputdata=inputdata))
|
||||
if isnoderange:
|
||||
if isnoderange or not autostrip:
|
||||
return itertools.chain(*passvalues)
|
||||
elif isinstance(passvalues[0], console.Console):
|
||||
return passvalues[0]
|
||||
@ -499,7 +504,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
return stripnode(passvalues[0], nodes[0])
|
||||
|
||||
|
||||
def handle_path(path, operation, configmanager, inputdata=None):
|
||||
def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
"""Given a full path request, return an object.
|
||||
|
||||
The plugins should generally return some sort of iterator.
|
||||
@ -514,7 +519,7 @@ def handle_path(path, operation, configmanager, inputdata=None):
|
||||
return enumerate_collections(rootcollections)
|
||||
elif pathcomponents[0] == 'noderange':
|
||||
return handle_node_request(configmanager, inputdata, operation,
|
||||
pathcomponents)
|
||||
pathcomponents, autostrip)
|
||||
elif pathcomponents[0] == 'nodegroups':
|
||||
return handle_nodegroup_request(configmanager, inputdata,
|
||||
pathcomponents,
|
||||
@ -522,7 +527,7 @@ def handle_path(path, operation, configmanager, inputdata=None):
|
||||
elif pathcomponents[0] == 'nodes':
|
||||
# single node request of some sort
|
||||
return handle_node_request(configmanager, inputdata,
|
||||
operation, pathcomponents)
|
||||
operation, pathcomponents, autostrip)
|
||||
elif pathcomponents[0] == 'users':
|
||||
# TODO: when non-administrator accounts exist,
|
||||
# they must only be allowed to see their own user
|
||||
@ -546,5 +551,16 @@ def handle_path(path, operation, configmanager, inputdata=None):
|
||||
pathcomponents, operation, inputdata)
|
||||
update_user(user, inputdata.attribs, configmanager)
|
||||
return show_user(user, configmanager)
|
||||
elif pathcomponents[0] == 'events':
|
||||
try:
|
||||
element = pathcomponents[1]
|
||||
except IndexError:
|
||||
if operation != 'retrieve':
|
||||
raise exc.InvalidArgumentException('Target is read-only')
|
||||
return (msg.ChildCollection('decode'),)
|
||||
if element != 'decode':
|
||||
raise exc.NotFoundException()
|
||||
if operation == 'update':
|
||||
return alerts.decode_alert(inputdata, configmanager)
|
||||
else:
|
||||
raise exc.NotFoundException()
|
||||
|
74
confluent_server/confluent/lookuptools.py
Normal file
74
confluent_server/confluent/lookuptools.py
Normal file
@ -0,0 +1,74 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 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.
|
||||
|
||||
# Utility library for interesting lookups of nodes.
|
||||
# Examples:
|
||||
# looking up a node by a hardwaremanagement.manager address
|
||||
# looking up a node by uuid (actually pretty straightforward
|
||||
# looking up a node by mac address
|
||||
# These are generally in the context of coming in from some unstructured
|
||||
# direction (alerts, PXE attempt) and for now will only look at the null
|
||||
# tenant (all baremetal tenants that are expected to receive alert/pxe
|
||||
# service should have a null tenant and a tenant entry that correlates)
|
||||
__author__ = 'jjohnson2'
|
||||
|
||||
import confluent.config.configmanager as configmanager
|
||||
import itertools
|
||||
import socket
|
||||
|
||||
manager_to_nodemap = {}
|
||||
|
||||
def node_by_manager(manager):
|
||||
"""Lookup a node by manager
|
||||
|
||||
Search for a node according to a given network address.
|
||||
Rather than do a simple equality, it uses getaddrinfo
|
||||
to allow name or ip and different forms of ip. For
|
||||
example, 'fe80::0001' will match 'fe80::01' and
|
||||
'127.000.000.001' will match '127.0.0.1'
|
||||
|
||||
:param manager: The ip or resolvable name of the manager
|
||||
|
||||
:returns: The node name (if any)
|
||||
"""
|
||||
|
||||
manageraddresses = []
|
||||
for tmpaddr in socket.getaddrinfo(manager, None):
|
||||
manageraddresses.append(tmpaddr[4][0])
|
||||
cfm = configmanager.ConfigManager(None)
|
||||
if manager in manager_to_nodemap:
|
||||
# We have a stored hint as to the most probably correct answer
|
||||
# put that node at the head of the list in hopes of reducing
|
||||
# iterations for a lookup in a large environment
|
||||
# However we don't trust the answer either, since
|
||||
# reconfiguration could have changed it and this mapping
|
||||
# is not hooked into getting updates
|
||||
check_nodes = itertools.chain(
|
||||
(manager_to_nodemap[manager],), cfm.list_nodes())
|
||||
else:
|
||||
check_nodes = cfm.list_nodes()
|
||||
hmattribs = cfm.get_node_attributes(check_nodes,
|
||||
('hardwaremanagement.manager',))
|
||||
for node in hmattribs:
|
||||
currhm = hmattribs[node]['hardwaremanagement.manager']['value']
|
||||
if currhm in manageraddresses:
|
||||
manager_to_nodemap[manager] = node
|
||||
return node
|
||||
for curraddr in socket.getaddrinfo(currhm, None):
|
||||
curraddr = curraddr[4][0]
|
||||
if curraddr in manageraddresses:
|
||||
manager_to_nodemap[manager] = node
|
||||
return node
|
@ -320,10 +320,34 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False):
|
||||
return InputAlertDestination(path, nodes, inputdata, multinode)
|
||||
elif path == ['identify'] and operation != 'retrieve':
|
||||
return InputIdentifyMessage(path, nodes, inputdata)
|
||||
elif path == ['events', 'hardware', 'decode']:
|
||||
return InputAlertData(path, inputdata, nodes)
|
||||
elif inputdata:
|
||||
raise exc.InvalidArgumentException()
|
||||
|
||||
|
||||
class InputAlertData(ConfluentMessage):
|
||||
|
||||
def __init__(self, path, inputdata, nodes=None):
|
||||
self.alertparams = inputdata
|
||||
# first migrate snmpv1 input to snmpv2 format
|
||||
if 'specifictrap' in self.alertparams:
|
||||
# If we have a 'specifictrap', convert to SNMPv2 per RFC 2576
|
||||
# This way
|
||||
enterprise = self.alertparams['enterprise']
|
||||
specifictrap = self.alertparams['specifictrap']
|
||||
self.alertparams['.1.3.6.1.6.3.1.1.4.1.0'] = enterprise + '.0.' + \
|
||||
str(specifictrap)
|
||||
if '1.3.6.1.6.3.1.1.4.1.0' in self.alertparams:
|
||||
self.alertparams['.1.3.6.1.6.3.1.1.4.1.0'] = \
|
||||
self.alertparams['1.3.6.1.6.3.1.1.4.1.0']
|
||||
if '.1.3.6.1.6.3.1.1.4.1.0' not in self.alertparams:
|
||||
raise exc.InvalidArgumentException('Missing SNMP Trap OID')
|
||||
|
||||
def get_alert(self, node=None):
|
||||
return self.alertparams
|
||||
|
||||
|
||||
class InputAttributes(ConfluentMessage):
|
||||
def __init__(self, path, inputdata, nodes=None):
|
||||
self.nodeattribs = {}
|
||||
|
@ -48,6 +48,14 @@ sensor_categories = {
|
||||
'fans': frozenset(['Fan', 'Cooling Device']),
|
||||
}
|
||||
|
||||
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 xrange(0, len(hexstring), 2)]
|
||||
bytedata = [int(i, 16) for i in hexvals]
|
||||
return bytearray(bytedata)
|
||||
|
||||
def simplify_name(name):
|
||||
return name.lower().replace(' ', '_')
|
||||
@ -355,6 +363,8 @@ class IpmiHandler(object):
|
||||
self.handle_inventory()
|
||||
elif self.element == ['events', 'hardware', 'log']:
|
||||
self.do_eventlog()
|
||||
elif self.element == ['events', 'hardware', 'decode']:
|
||||
self.decode_alert()
|
||||
else:
|
||||
raise Exception('Not Implemented')
|
||||
|
||||
@ -363,6 +373,18 @@ class IpmiHandler(object):
|
||||
return self.handle_alerts()
|
||||
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:
|
||||
@ -399,20 +421,22 @@ class IpmiHandler(object):
|
||||
return
|
||||
raise Exception('Not implemented')
|
||||
|
||||
|
||||
def do_eventlog(self):
|
||||
eventout = []
|
||||
for event in self.ipmicmd.get_event_log():
|
||||
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'])
|
||||
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:
|
||||
@ -637,4 +661,4 @@ def update(nodes, element, configmanager, inputdata):
|
||||
|
||||
def retrieve(nodes, element, configmanager, inputdata):
|
||||
initthread()
|
||||
return perform_requests('read', nodes, element, configmanager, inputdata)
|
||||
return perform_requests('read', nodes, element, configmanager, inputdata)
|
||||
|
Loading…
x
Reference in New Issue
Block a user