2
0
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:
Jarrod Johnson 2015-07-01 15:14:28 -04:00
commit 2d9df67272
6 changed files with 218 additions and 20 deletions

View File

@ -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 = ""

View 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)

View File

@ -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()

View 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

View File

@ -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 = {}

View File

@ -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)