mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 00:31:09 +00:00
Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba9d62b4e5 | |||
| 4442ce1c71 | |||
| 1d64792cb9 | |||
| e721f8836e | |||
| 6770fe9342 | |||
| 804d4c2d95 | |||
| f946fab3c5 | |||
| 5a0ac899b9 | |||
| 310bd11669 | |||
| 5ab1d6ea59 | |||
| d8c3b2f267 | |||
| f02c74cce0 | |||
| 5485ef36bf | |||
| 1cf238708f | |||
| 80aa2c477a | |||
| 9868e55958 | |||
| 7a8fbe7d62 | |||
| 52aaeef506 | |||
| be8d82c6c1 | |||
| a83583a56d | |||
| f906cebca3 | |||
| e153e8acd0 | |||
| dec0543ce3 | |||
| 204f6de7e9 | |||
| 76906c191b | |||
| 61a0a66486 | |||
| 597bc9413f | |||
| e53a9f83f4 | |||
| 31a191cb10 | |||
| 8d566c1795 | |||
| 2c2884f80f | |||
| fe4797857d | |||
| e90204cc4d | |||
| d4828b2115 | |||
| 7b26d2edfb | |||
| 0bb0368d07 | |||
| 1467e1f172 | |||
| 06f22e7acb | |||
| 8779f1c27f | |||
| 2245e53ff1 | |||
| 99d1edcdef | |||
| fbca02e262 | |||
| d533321d55 | |||
| 0b7b713f3f | |||
| 865545b8d1 | |||
| 258e257939 | |||
| 127fc59195 | |||
| 8c4d33db92 | |||
| b1240e327f | |||
| 477418a56e | |||
| 9b52e276cc | |||
| fa545eeaee | |||
| 5590b4138e | |||
| 098c78558b | |||
| 57a3c6d287 | |||
| 3790984555 | |||
| b88a135602 | |||
| 2115fb3f78 | |||
| 1f8bc635a8 | |||
| f78a4d6074 | |||
| d9ed98d58e | |||
| db3320e2ec | |||
| a02f96710b | |||
| 03de545e09 | |||
| fcd0e523a0 | |||
| 0ae2b7acc6 | |||
| c50be7c3f2 | |||
| 45f62b5c05 | |||
| a0e6e0b5c6 | |||
| 9975d57d5e | |||
| c1bd46b22a | |||
| ff213916b6 | |||
| 5128a80c79 | |||
| dc436fda74 | |||
| ad20193309 | |||
| 0bd9b08be2 | |||
| e9a31f52ae | |||
| a3dcf88749 | |||
| 765c15ed5b | |||
| 34960bff22 | |||
| 29417d935c | |||
| b48cd8b685 | |||
| a1a61dfdbd | |||
| 11d4628458 | |||
| 4dcfaf7363 | |||
| 655a0b4d17 | |||
| 668f1c98b6 | |||
| b1b4ee4634 | |||
| e207940e50 | |||
| e02dc74eb7 | |||
| 8114c3dc6a | |||
| fde68e1320 | |||
| 32184d463d | |||
| e7ff4fdd93 | |||
| 044def59ba | |||
| 419c41e2cc | |||
| c20f0dc2ae | |||
| 455feb867d | |||
| dc0c7270a4 | |||
| ceee6173da | |||
| 2fb63a34ba | |||
| 55c333b198 | |||
| 56455fe497 | |||
| 87552b9f03 | |||
| 6ba7072aed | |||
| f7b383b692 | |||
| a0e3dca856 | |||
| 90ad5829fc | |||
| 669661b530 | |||
| e17b9e98a8 | |||
| aef26abd56 | |||
| 57bacd69c1 | |||
| 88aa696d29 | |||
| 548b71cd6c | |||
| c263c2eebb | |||
| 7a084bf538 | |||
| cbf595df26 | |||
| 680f60114e | |||
| ff52ad4740 | |||
| 9626491c41 | |||
| 3f1d49c30b | |||
| 5f1ddc7f84 | |||
| 6e5a7e15d9 | |||
| 5388f497d4 | |||
| cff997bd0b | |||
| e55f55677b | |||
| 905c41b021 | |||
| 19dd0539a9 | |||
| 4d1d016325 | |||
| aa4783672d | |||
| 859aad793d | |||
| f52eec6c19 | |||
| 24eb872090 | |||
| d27df8fffc | |||
| 4aef8524e9 | |||
| 14a9220acb | |||
| 49bff93eed | |||
| 97c928350c | |||
| 2d9df67272 | |||
| e11a749fa4 | |||
| 186929d217 | |||
| 86f66e9795 | |||
| 5a610f23ca | |||
| 3d5fa74f4f | |||
| fefe1dc9e8 | |||
| 7b02954da8 | |||
| 6ab1ae0c38 | |||
| 6a1da9e84f | |||
| c6d0d87ca9 | |||
| bfc7ea2b51 | |||
| 7baec5a69f | |||
| 7cda6f7d6e | |||
| 2ab2fbda27 | |||
| 6204628f43 | |||
| b97cd79c3a | |||
| d2f400d982 | |||
| 46b7550a41 | |||
| a28b67e9aa | |||
| 4090dac50c | |||
| c98d2e32d3 | |||
| 2fe0425191 | |||
| 27437048a6 | |||
| 17b5d5a816 | |||
| 26a969c41a | |||
| 093e9faec4 |
@@ -0,0 +1 @@
|
||||
include confluent_env.sh
|
||||
@@ -1 +0,0 @@
|
||||
1.1
|
||||
@@ -41,19 +41,21 @@
|
||||
# esc-( would interfere with normal esc use too much
|
||||
# ~ I will not use for now...
|
||||
|
||||
import fcntl
|
||||
import math
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
import readline
|
||||
import select
|
||||
import shlex
|
||||
import socket
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
import tty
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
import tty
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
exitcode = 0
|
||||
consoleonly = False
|
||||
@@ -73,8 +75,11 @@ conserversequence = '\x05c' # ctrl-e, c
|
||||
|
||||
oldtcattr = None
|
||||
fd = sys.stdin
|
||||
if fd.isatty():
|
||||
oldtcattr = termios.tcgetattr(fd.fileno())
|
||||
try:
|
||||
if fd.isatty():
|
||||
oldtcattr = termios.tcgetattr(fd.fileno())
|
||||
except NameError:
|
||||
pass
|
||||
netserver = None
|
||||
laststate = {}
|
||||
|
||||
@@ -248,6 +253,9 @@ def print_result(res):
|
||||
attrstr = '%s=""' % key
|
||||
elif type(res[key]) == list:
|
||||
attrstr = '%s=%s' % (key, recurse_format(res[key]))
|
||||
elif not isinstance(res[key], dict):
|
||||
print '{0}: {1}'.format(key, res[key])
|
||||
continue
|
||||
elif 'value' in res[key] and res[key]['value'] is not None:
|
||||
attrstr = '%s="%s"' % (key, res[key]['value'])
|
||||
elif 'value' in res[key] and res[key]['value'] is None:
|
||||
@@ -258,7 +266,10 @@ def print_result(res):
|
||||
attrstr = '%s=""' % key
|
||||
else:
|
||||
sys.stdout.write('{0}: '.format(key))
|
||||
print_result(res[key])
|
||||
if isinstance(res[key], str) or isinstance(res[key], unicode):
|
||||
print res[key]
|
||||
else:
|
||||
print_result(res[key])
|
||||
continue
|
||||
if res[key] is not None and 'inheritedfrom' in res[key]:
|
||||
notes.append(
|
||||
@@ -355,9 +366,11 @@ def do_command(command, server):
|
||||
else:
|
||||
sys.stderr.write("%s: command not found...\n" % argv[0])
|
||||
|
||||
|
||||
def shutdown():
|
||||
tlvdata.send(session.connection, {'operation': 'shutdown', 'path': '/'})
|
||||
|
||||
|
||||
def createresource(args):
|
||||
resname = args[0]
|
||||
attribs = args[1:]
|
||||
@@ -489,6 +502,18 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
|
||||
inconsole = False
|
||||
|
||||
|
||||
def get_session_node(shellargs):
|
||||
# straight to node console
|
||||
if len(shellargs) == 1 and ' ' not in shellargs[0]:
|
||||
return shellargs[0]
|
||||
if len(shellargs) == 2 and shellargs[0] == 'start':
|
||||
args = [s for s in shellargs[1].split('/') if s]
|
||||
if len(args) == 4 and args[0] == 'nodes' and args[2] == 'console' and \
|
||||
args[3] == 'session':
|
||||
return args[1]
|
||||
return None
|
||||
|
||||
|
||||
def conserver_command(filehandle, command):
|
||||
# x - conserver has that as 'show baud', I am inclined to replace that with
|
||||
# 'request exclusive'
|
||||
@@ -576,7 +601,6 @@ def server_connect():
|
||||
session.authenticate(username, passphrase)
|
||||
while not session.authenticated:
|
||||
username = raw_input("Name: ")
|
||||
readline.clear_history()
|
||||
passphrase = getpass.getpass("Passphrase: ")
|
||||
session.authenticate(username, passphrase)
|
||||
|
||||
@@ -593,15 +617,20 @@ 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 = ""
|
||||
if len(shellargs) == 1 and ' ' not in shellargs[0]: # straight to node console
|
||||
session_node = get_session_node(shellargs)
|
||||
if session_node is not None:
|
||||
consoleonly = True
|
||||
do_command("start /nodes/%s/console/session" % shellargs[0], netserver)
|
||||
do_command("start /nodes/%s/console/session" % session_node, netserver)
|
||||
doexit = True
|
||||
elif shellargs:
|
||||
command = " ".join(shellargs)
|
||||
@@ -642,7 +671,7 @@ while inconsole or not doexit:
|
||||
try:
|
||||
server_connect()
|
||||
connected = True
|
||||
except socket.gaierror, socket.error:
|
||||
except (socket.gaierror, socket.error):
|
||||
pass
|
||||
if not connected:
|
||||
time.sleep(1)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# 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.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] node",
|
||||
epilog="Command sequences are available while connected to a console, hit "
|
||||
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
|
||||
"For example, ctrl-'e', then 'c', then '.' will exit the current "
|
||||
"console")
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
os.execl(confettypath, confettypath, 'start',
|
||||
'/nodes/{0}/console/session'.format(args[0]))
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/python
|
||||
# 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.
|
||||
|
||||
from datetime import datetime as dt
|
||||
import os
|
||||
import sys
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange> [clear]\n'.format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
|
||||
deletemode = False
|
||||
if len(sys.argv) == 3:
|
||||
if sys.argv[2] == 'clear':
|
||||
deletemode = True
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
def format_event(evt):
|
||||
retparts = []
|
||||
if 'timestamp' in evt:
|
||||
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
||||
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
|
||||
dscparts = []
|
||||
if 'component_type' in evt:
|
||||
dscparts.append(evt['component_type'])
|
||||
if 'component' in evt and evt['component'] is not None:
|
||||
dscparts.append(evt['component'])
|
||||
if 'event' in evt:
|
||||
evttext = evt['event']
|
||||
try:
|
||||
if evttext.startswith(evt['component'] + ' - '):
|
||||
evttext = evt['event'].replace(evt['component'] + ' - ', '')
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
dscparts.append(evttext)
|
||||
retparts.append(' - '.join(dscparts))
|
||||
return ' '.join(retparts)
|
||||
|
||||
|
||||
if deletemode:
|
||||
func = session.delete
|
||||
else:
|
||||
func = session.read
|
||||
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
|
||||
if 'error' in rsp:
|
||||
sys.stderr.write(rsp['error'] + '\n')
|
||||
exitcode |= rsp['errorcode']
|
||||
if 'databynode' in rsp:
|
||||
nodedata = rsp['databynode']
|
||||
for node in nodedata:
|
||||
thisdata = nodedata[node]
|
||||
if 'error' in thisdata:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, thisdata['error']))
|
||||
exitcode |= 1
|
||||
if 'events' in thisdata:
|
||||
evtdata = thisdata['events']
|
||||
for evt in evtdata:
|
||||
print '{0}: {1}'.format(node, format_event(evt))
|
||||
@@ -28,42 +28,62 @@ import confluent.client as client
|
||||
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
noderange = sys.argv[1]
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.stderr.write('Usage: {0} <noderange>\n'.format(sys.argv[0]))
|
||||
sys.exit(1)
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
healthbynode = {}
|
||||
healthexplanations = {}
|
||||
for health in session.read('/noderange/{0}/health/hardware'.format(noderange)):
|
||||
if 'error' in health:
|
||||
sys.stderr.write(health['error'] + '\n')
|
||||
if 'errorcode' in health:
|
||||
exitcode |= health['errorcode']
|
||||
else:
|
||||
exitcode |= 1
|
||||
continue
|
||||
if 'databynode' not in health:
|
||||
continue
|
||||
health = health['databynode']
|
||||
for node in health:
|
||||
if 'health' in health[node]:
|
||||
healthbynode[node] = health[node]['health']['value']
|
||||
if 'sensors' in health[node]:
|
||||
healthexplanations[node] = []
|
||||
for sensor in health[node]['sensors']:
|
||||
explanation = sensor['name'] + ':'
|
||||
if sensor['value'] is not None:
|
||||
explanation += sensor['value']
|
||||
if sensor['units'] is not None:
|
||||
explanation += sensor['units']
|
||||
if sensor['states']:
|
||||
explanation += ','.join(sensor['states'])
|
||||
healthexplanations[node].append(explanation)
|
||||
if node in healthbynode and node in healthexplanations:
|
||||
if healthexplanations[node]:
|
||||
print('{0}: {1} ({2})'.format(
|
||||
node, healthbynode[node],
|
||||
','.join(healthexplanations[node])))
|
||||
|
||||
def main():
|
||||
global session, exitcode, healthbynode, healthexplanations, health, node, sensor, explanation
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
healthbynode = {}
|
||||
healthexplanations = {}
|
||||
for health in session.read(
|
||||
'/noderange/{0}/health/hardware'.format(noderange)):
|
||||
if 'error' in health:
|
||||
sys.stderr.write(health['error'] + '\n')
|
||||
if 'errorcode' in health:
|
||||
exitcode |= health['errorcode']
|
||||
else:
|
||||
print('{0}: {1}'.format(node, healthbynode[node]))
|
||||
exitcode |= 1
|
||||
continue
|
||||
if 'databynode' not in health:
|
||||
continue
|
||||
health = health['databynode']
|
||||
for node in health:
|
||||
if 'error' in health[node]:
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(
|
||||
node, health[node]['error']))
|
||||
exitcode |= 1
|
||||
if 'health' in health[node]:
|
||||
healthbynode[node] = health[node]['health']['value']
|
||||
if 'sensors' in health[node]:
|
||||
healthexplanations[node] = []
|
||||
for sensor in health[node]['sensors']:
|
||||
explanation = sensor['name'] + ':'
|
||||
if sensor['value'] is not None:
|
||||
explanation += str(sensor['value'])
|
||||
if sensor['units'] is not None:
|
||||
explanation += sensor['units']
|
||||
if sensor['states']:
|
||||
explanation += ','
|
||||
if sensor['states']:
|
||||
explanation += ','.join(sensor['states'])
|
||||
healthexplanations[node].append(explanation)
|
||||
if node in healthbynode and node in healthexplanations:
|
||||
if healthexplanations[node]:
|
||||
print(u'{0}: {1} ({2})'.format(
|
||||
node, healthbynode[node],
|
||||
','.join(healthexplanations[node])))
|
||||
else:
|
||||
print('{0}: {1}'.format(node, healthbynode[node]))
|
||||
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -25,8 +25,12 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.stderr.write('Usage: {0} <noderange> [on|off]\n'.format(sys.argv[0]))
|
||||
sys.exit(1)
|
||||
|
||||
noderange = sys.argv[1]
|
||||
identifystate = None
|
||||
if len(sys.argv) > 2:
|
||||
identifystate = sys.argv[2]
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python
|
||||
# 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.
|
||||
|
||||
__author__ = 'jjohnson2'
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange [list of attributes")
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
nodelist = '/noderange/{0}/nodes/'.format(noderange)
|
||||
except IndexError:
|
||||
nodelist = '/nodes/'
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
if len(args) > 1:
|
||||
seenattributes = set([])
|
||||
for res in session.read('/noderange/{0}/attributes/all'.format(noderange)):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
for attr in res['databynode'][node]:
|
||||
seenattributes.add(attr)
|
||||
currattr = res['databynode'][node][attr]
|
||||
if attr in args[1:]:
|
||||
if 'value' in currattr:
|
||||
if currattr['value'] is not None:
|
||||
attrout = '{0}: {1}: {2}'.format(
|
||||
node, attr, currattr['value'])
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
elif 'isset' in currattr:
|
||||
if currattr['isset']:
|
||||
attrout = '{0}: {1}: ********'.format(node, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
if options.blame:
|
||||
blamedata = []
|
||||
if 'inheritedfrom' in currattr:
|
||||
blamedata.append('inherited from group {0}'.format(
|
||||
currattr['inheritedfrom']
|
||||
))
|
||||
if 'expression' in currattr:
|
||||
blamedata.append(
|
||||
'derived from expression "{0}"'.format(
|
||||
currattr['expression']))
|
||||
if blamedata:
|
||||
attrout += ' (' + ', '.join(blamedata) + ')'
|
||||
print attrout
|
||||
if not exitcode:
|
||||
for attr in args[1:]:
|
||||
if attr not in seenattributes:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
exitcode = 1
|
||||
else:
|
||||
for res in session.read(nodelist):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print res['item']['href'].replace('/', '')
|
||||
sys.exit(exitcode)
|
||||
@@ -26,7 +26,14 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
noderange = sys.argv[1]
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange> ([status|on|off|shutdown|boot|reset]\n'.format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
|
||||
setstate = None
|
||||
if len(sys.argv) > 2:
|
||||
if setstate == 'softoff':
|
||||
|
||||
@@ -54,8 +54,11 @@ if options.numreadings:
|
||||
repeatmode = options.numreadings
|
||||
if options.interval is None:
|
||||
options.interval = 1
|
||||
|
||||
noderange = args[0]
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_usage()
|
||||
sys.exit(1)
|
||||
sensors = []
|
||||
for sensorgroup in args[1:]:
|
||||
for sensor in sensorgroup.split(','):
|
||||
@@ -205,4 +208,8 @@ def main():
|
||||
sensorpass(True)
|
||||
|
||||
|
||||
main()
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(0)
|
||||
|
||||
@@ -26,10 +26,18 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
noderange = sys.argv[1]
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange> [default|cd|network|setup|hd]\n'.format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
bootdev = None
|
||||
if len(sys.argv) > 2:
|
||||
bootdev = sys.argv[2]
|
||||
if bootdev in ('net', 'pxe'):
|
||||
bootdev = 'network'
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sys.exit(
|
||||
|
||||
@@ -43,7 +43,7 @@ def _parseserver(string):
|
||||
|
||||
class Command(object):
|
||||
|
||||
def __init__(self, server="/var/run/confluent/api.sock"):
|
||||
def __init__(self, server=None):
|
||||
self.connection = None
|
||||
if server is None:
|
||||
if 'CONFLUENT_HOST' in os.environ:
|
||||
@@ -62,6 +62,10 @@ class Command(object):
|
||||
self.authenticated = True
|
||||
else:
|
||||
self.authenticated = False
|
||||
if not self.authenticated and 'CONFLUENT_USER' in os.environ:
|
||||
username = os.environ['CONFLUENT_USER']
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
self.authenticate(username, passphrase)
|
||||
|
||||
def authenticate(self, username, password):
|
||||
tlvdata.send(self.connection,
|
||||
@@ -94,23 +98,27 @@ class Command(object):
|
||||
|
||||
def simple_noderange_command(self, noderange, resource, input=None,
|
||||
key=None):
|
||||
rc = 0
|
||||
if resource[0] == '/':
|
||||
resource = resource[1:]
|
||||
# The implicit key is the resource basename
|
||||
if key is None:
|
||||
ikey = resource.rpartition('/')[-1]
|
||||
else:
|
||||
ikey = key
|
||||
if input is None:
|
||||
for res in self.read('/noderange/{0}/{1}'.format(
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
else:
|
||||
for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), {ikey: input}):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
try:
|
||||
rc = 0
|
||||
if resource[0] == '/':
|
||||
resource = resource[1:]
|
||||
# The implicit key is the resource basename
|
||||
if key is None:
|
||||
ikey = resource.rpartition('/')[-1]
|
||||
else:
|
||||
ikey = key
|
||||
if input is None:
|
||||
for res in self.read('/noderange/{0}/{1}'.format(
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
else:
|
||||
for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), {ikey: input}):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -146,6 +154,7 @@ class Command(object):
|
||||
try:
|
||||
self.connection.settimeout(5)
|
||||
self.connection.connect(sa)
|
||||
self.connection.settimeout(None)
|
||||
except:
|
||||
raise
|
||||
self.connection.close()
|
||||
|
||||
@@ -38,6 +38,9 @@ def send(handle, data):
|
||||
if isinstance(data, str):
|
||||
# plain text, e.g. console data
|
||||
tl = len(data)
|
||||
if tl == 0:
|
||||
# if you don't have anything to say, don't say anything at all
|
||||
return
|
||||
if tl < 16777216:
|
||||
# type for string is '0', so we don't need
|
||||
# to xor anything in
|
||||
@@ -76,6 +79,8 @@ def recv(handle):
|
||||
# 4 byte tlv
|
||||
dlen = tl & 16777215 # grab lower 24 bits
|
||||
datatype = (tl & 2130706432) >> 24 # grab 7 bits from near beginning
|
||||
if dlen == 0:
|
||||
return None
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
PATH=/opt/confluent/bin:$PATH
|
||||
export PATH
|
||||
@@ -7,5 +7,6 @@ setup(
|
||||
author_email='jjohnson2@lenovo.com',
|
||||
url='http://xcat.sf.net/',
|
||||
packages=['confluent'],
|
||||
scripts=['bin/confetty', 'bin/nodehealth', 'bin/nodeidentify', 'bin/nodepower', 'bin/nodesensors', 'bin/nodesetboot'],
|
||||
scripts=['bin/confetty', 'bin/nodeconsole', 'bin/nodeeventlog', 'bin/nodehealth', 'bin/nodeidentify', 'bin/nodelist', 'bin/nodepower', 'bin/nodesensors', 'bin/nodesetboot'],
|
||||
data_files=[('/etc/profile.d', ['confluent_env.sh'])],
|
||||
)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
include sysvinit/*
|
||||
include systemd/*
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
1.1
|
||||
@@ -22,14 +22,17 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
# if installed into system path, do not muck with things
|
||||
sys.path.append(path)
|
||||
from confluent import main
|
||||
import confluent.main
|
||||
|
||||
#import cProfile
|
||||
#import time
|
||||
#p = cProfile.Profile(time.clock)
|
||||
#p.enable()
|
||||
#try:
|
||||
main.run()
|
||||
import multiprocessing
|
||||
if __name__ == '__main__':
|
||||
multiprocessing.freeze_support()
|
||||
confluent.main.run()
|
||||
#except:
|
||||
# pass
|
||||
#p.disable()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
#
|
||||
# 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 sys
|
||||
import os
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
# if installed into system path, do not muck with things
|
||||
sys.path.append(path)
|
||||
import confluent.main
|
||||
|
||||
#import cProfile
|
||||
#import time
|
||||
#p = cProfile.Profile(time.clock)
|
||||
#p.enable()
|
||||
#try:
|
||||
import multiprocessing
|
||||
if __name__ == '__main__':
|
||||
multiprocessing.freeze_support()
|
||||
confluent.main.run()
|
||||
#except:
|
||||
# pass
|
||||
#p.disable()
|
||||
#p.print_stats(sort='cumulative')
|
||||
#p.print_stats(sort='time')
|
||||
@@ -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)
|
||||
|
||||
@@ -26,7 +26,10 @@ import Crypto.Protocol.KDF as KDF
|
||||
import hashlib
|
||||
import hmac
|
||||
import multiprocessing
|
||||
import PAM
|
||||
try:
|
||||
import PAM
|
||||
except ImportError:
|
||||
pass
|
||||
import time
|
||||
|
||||
_pamservice = 'confluent'
|
||||
@@ -161,6 +164,8 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
pammy.acct_mgmt()
|
||||
del pammy
|
||||
return authorize(user, element, tenant, skipuserobj=False)
|
||||
except NameError:
|
||||
pass
|
||||
except PAM.error:
|
||||
if credobj.haspam:
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
#
|
||||
# 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 defines config variable to store the global configuration for confluent
|
||||
import ConfigParser
|
||||
import os
|
||||
|
||||
_config = None
|
||||
|
||||
|
||||
def init_config():
|
||||
global _config
|
||||
configfile = "/etc/confluent/service.cfg"
|
||||
if os.name == 'nt':
|
||||
configfile = os.path.join(os.getenv('SystemDrive'), '\\ProgramData',
|
||||
'confluent', 'cfg', 'service.cfg')
|
||||
_config = ConfigParser.ConfigParser()
|
||||
_config.read(configfile)
|
||||
|
||||
|
||||
def get_config():
|
||||
if _config is None:
|
||||
init_config()
|
||||
return _config
|
||||
|
||||
|
||||
def get_int_option(section, option):
|
||||
if _config is None:
|
||||
init_config()
|
||||
try:
|
||||
return _config.getint(section, option)
|
||||
except (
|
||||
ConfigParser.NoSectionError, ConfigParser.NoOptionError,
|
||||
ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def get_boolean_option(section, option):
|
||||
if _config is None:
|
||||
init_config()
|
||||
try:
|
||||
return _config.getboolean(section, option)
|
||||
except (
|
||||
ConfigParser.NoSectionError, ConfigParser.NoOptionError,
|
||||
ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def get_option(section, option):
|
||||
if _config is None:
|
||||
init_config()
|
||||
try:
|
||||
return _config.get(section, option)
|
||||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
return None
|
||||
@@ -67,6 +67,7 @@ import base64
|
||||
import confluent.config.attributes as allattributes
|
||||
import confluent.log
|
||||
import confluent.util
|
||||
import confluent.exceptions as exc
|
||||
import copy
|
||||
import cPickle
|
||||
import errno
|
||||
@@ -78,6 +79,7 @@ import re
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
|
||||
_masterkey = None
|
||||
@@ -104,22 +106,26 @@ def _derive_keys(password, salt):
|
||||
lambda p, s: HMAC.new(p, s, SHA256).digest())
|
||||
finalkey = KDF.PBKDF2(tmpkey, salt, 32, 50000,
|
||||
lambda p, s: HMAC.new(p, s, SHA256).digest())
|
||||
return finalkey[:32], finalkey[32:]
|
||||
return finalkey[:16], finalkey[16:]
|
||||
|
||||
|
||||
def _get_protected_key(keydict, password):
|
||||
if keydict['unencryptedvalue']:
|
||||
def _get_protected_key(keydict, password, paramname):
|
||||
if password and 'unencryptedvalue' in keydict:
|
||||
set_global(paramname, _format_key(
|
||||
keydict['unencryptedvalue'],
|
||||
password=password))
|
||||
if 'unencryptedvalue' in keydict:
|
||||
return keydict['unencryptedvalue']
|
||||
# TODO(jbjohnso): check for TPM sealing
|
||||
if 'passphraseprotected' in keydict:
|
||||
if password is None:
|
||||
raise Exception("Passphrase protected secret requires password")
|
||||
for pp in keydict['passphraseprotected']:
|
||||
salt = pp[0]
|
||||
privkey, integkey = _derive_keys(password, salt)
|
||||
return decrypt_value(pp[1:], key=privkey, integritykey=integkey)
|
||||
raise exc.LockedCredentials("Passphrase protected secret requires password")
|
||||
pp = keydict['passphraseprotected']
|
||||
salt = pp[0]
|
||||
privkey, integkey = _derive_keys(password, salt)
|
||||
return decrypt_value(pp[1:], key=privkey, integritykey=integkey)
|
||||
else:
|
||||
raise Exception("No available decryption key")
|
||||
raise exc.LockedCredentials("No available decryption key")
|
||||
|
||||
|
||||
def _format_key(key, password=None):
|
||||
@@ -127,7 +133,7 @@ def _format_key(key, password=None):
|
||||
salt = os.urandom(32)
|
||||
privkey, integkey = _derive_keys(password, salt)
|
||||
cval = crypt_value(key, key=privkey, integritykey=integkey)
|
||||
return {"passphraseprotected": cval}
|
||||
return {"passphraseprotected": (salt,) + cval}
|
||||
else:
|
||||
return {"unencryptedvalue": key}
|
||||
|
||||
@@ -138,7 +144,7 @@ def init_masterkey(password=None):
|
||||
cfgn = get_global('master_privacy_key')
|
||||
|
||||
if cfgn:
|
||||
_masterkey = _get_protected_key(cfgn, password=password)
|
||||
_masterkey = _get_protected_key(cfgn, password, 'master_privacy_key')
|
||||
else:
|
||||
_masterkey = os.urandom(32)
|
||||
set_global('master_privacy_key', _format_key(
|
||||
@@ -146,7 +152,8 @@ def init_masterkey(password=None):
|
||||
password=password))
|
||||
cfgn = get_global('master_integrity_key')
|
||||
if cfgn:
|
||||
_masterintegritykey = _get_protected_key(cfgn, password=password)
|
||||
_masterintegritykey = _get_protected_key(cfgn, password,
|
||||
'master_integrity_key')
|
||||
else:
|
||||
_masterintegritykey = os.urandom(64)
|
||||
set_global('master_integrity_key', _format_key(
|
||||
@@ -155,15 +162,18 @@ def init_masterkey(password=None):
|
||||
|
||||
|
||||
def decrypt_value(cryptvalue,
|
||||
key=_masterkey,
|
||||
integritykey=_masterintegritykey):
|
||||
key=None,
|
||||
integritykey=None):
|
||||
iv, cipherdata, hmac = cryptvalue
|
||||
if _masterkey is None or _masterintegritykey is None:
|
||||
init_masterkey()
|
||||
check_hmac = HMAC.new(_masterintegritykey, cipherdata, SHA256).digest()
|
||||
if key is None and integritykey is None:
|
||||
if _masterkey is None or _masterintegritykey is None:
|
||||
init_masterkey()
|
||||
key = _masterkey
|
||||
integritykey = _masterintegritykey
|
||||
check_hmac = HMAC.new(integritykey, cipherdata, SHA256).digest()
|
||||
if hmac != check_hmac:
|
||||
raise Exception("bad HMAC value on crypted value")
|
||||
decrypter = AES.new(_masterkey, AES.MODE_CBC, iv)
|
||||
decrypter = AES.new(key, AES.MODE_CBC, iv)
|
||||
value = decrypter.decrypt(cipherdata)
|
||||
padsize = ord(value[-1])
|
||||
pad = value[-padsize:]
|
||||
@@ -176,13 +186,14 @@ def decrypt_value(cryptvalue,
|
||||
|
||||
|
||||
def crypt_value(value,
|
||||
key=_masterkey,
|
||||
integritykey=_masterintegritykey):
|
||||
key=None,
|
||||
integritykey=None):
|
||||
# encrypt given value
|
||||
# PKCS7 is the padding scheme to employ, if no padded needed, pad with 16
|
||||
# check HMAC prior to attempting decrypt
|
||||
if key is None or integritykey is None:
|
||||
init_masterkey()
|
||||
if _masterkey is None or _masterintegritykey is None:
|
||||
init_masterkey()
|
||||
key = _masterkey
|
||||
integritykey = _masterintegritykey
|
||||
iv = os.urandom(16)
|
||||
@@ -405,7 +416,11 @@ def hook_new_configmanagers(callback):
|
||||
|
||||
|
||||
class ConfigManager(object):
|
||||
_cfgdir = "/etc/confluent/cfg/"
|
||||
if os.name == 'nt':
|
||||
_cfgdir = os.path.join(
|
||||
os.getenv('SystemDrive'), '\\ProgramData', 'confluent', 'cfg')
|
||||
else:
|
||||
_cfgdir = "/etc/confluent/cfg"
|
||||
_cfgwriter = None
|
||||
_writepending = False
|
||||
_syncrunning = False
|
||||
@@ -864,6 +879,9 @@ class ConfigManager(object):
|
||||
def set_group_attributes(self, attribmap, autocreate=False):
|
||||
changeset = {}
|
||||
for group in attribmap.iterkeys():
|
||||
if group == '':
|
||||
raise ValueError('"{0}" is not a valid group name'.format(
|
||||
group))
|
||||
if not autocreate and group not in self._cfgstore['nodegroups']:
|
||||
raise ValueError("{0} group does not exist".format(group))
|
||||
for attr in attribmap[group].iterkeys():
|
||||
@@ -973,7 +991,16 @@ class ConfigManager(object):
|
||||
if node not in attribwatchers:
|
||||
continue
|
||||
attribwatcher = attribwatchers[node]
|
||||
for attrname in nodeattrs[node].iterkeys():
|
||||
# usually, we will only look at the specific attribute keys that
|
||||
# have had change flagged, so set up to iterate through only those
|
||||
checkattrs = nodeattrs[node]
|
||||
if '_nodedeleted' in nodeattrs[node]:
|
||||
# in the case of a deleted node, we want to iterate through
|
||||
# *all* attributes that the node might have had set prior
|
||||
# to deletion, to make all watchers aware of the removed
|
||||
# node and take appropriate action
|
||||
checkattrs = attribwatcher
|
||||
for attrname in checkattrs:
|
||||
if attrname not in attribwatcher:
|
||||
continue
|
||||
for notifierid in attribwatcher[attrname].iterkeys():
|
||||
@@ -1006,6 +1033,9 @@ class ConfigManager(object):
|
||||
watcher(added=[], deleting=nodes, configmanager=self)
|
||||
changeset = {}
|
||||
for node in nodes:
|
||||
# set a reserved attribute for the sake of the change notification
|
||||
# framework to trigger on
|
||||
changeset[node] = {'_nodedeleted': 1}
|
||||
node = node.encode('utf-8')
|
||||
if node in self._cfgstore['nodes']:
|
||||
self._sync_groups_to_node(node=node, groups=[],
|
||||
@@ -1070,6 +1100,8 @@ class ConfigManager(object):
|
||||
# this mitigates risk of arguments being partially applied
|
||||
for node in attribmap.iterkeys():
|
||||
node = node.encode('utf-8')
|
||||
if node == '':
|
||||
raise ValueError('"{0}" is not a valid node name'.format(node))
|
||||
if autocreate is False and node not in self._cfgstore['nodes']:
|
||||
raise ValueError("node {0} does not exist".format(node))
|
||||
for attrname in attribmap[node].iterkeys():
|
||||
@@ -1192,15 +1224,15 @@ class ConfigManager(object):
|
||||
global _cfgstore
|
||||
_cfgstore = {}
|
||||
rootpath = cls._cfgdir
|
||||
_load_dict_from_dbm(['globals'], rootpath + "/globals")
|
||||
_load_dict_from_dbm(['globals'], os.path.join(rootpath, "globals"))
|
||||
for confarea in _config_areas:
|
||||
_load_dict_from_dbm(['main', confarea], rootpath + "/" + confarea)
|
||||
_load_dict_from_dbm(['main', confarea], os.path.join(rootpath, confarea))
|
||||
try:
|
||||
for tenant in os.listdir(rootpath + '/tenants/'):
|
||||
for tenant in os.listdir(os.path.join(rootpath, 'tenants')):
|
||||
for confarea in _config_areas:
|
||||
_load_dict_from_dbm(
|
||||
['main', tenant, confarea],
|
||||
"%s/%s/%s" % (rootpath, tenant, confarea))
|
||||
os.path.join(rootpath, tenant, confarea))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -1231,7 +1263,7 @@ class ConfigManager(object):
|
||||
dirtyglobals = copy.deepcopy(_cfgstore['dirtyglobals'])
|
||||
del _cfgstore['dirtyglobals']
|
||||
_mkpath(cls._cfgdir)
|
||||
globalf = dbm.open(cls._cfgdir + "/globals", 'c', 384) # 0600
|
||||
globalf = dbm.open(os.path.join(cls._cfgdir, "globals"), 'c', 384) # 0600
|
||||
try:
|
||||
for globalkey in dirtyglobals:
|
||||
if globalkey in _cfgstore['globals']:
|
||||
@@ -1252,11 +1284,11 @@ class ConfigManager(object):
|
||||
pathname = cls._cfgdir
|
||||
currdict = _cfgstore['main']
|
||||
else:
|
||||
pathname = cls._cfgdir + '/tenants/' + tenant + '/'
|
||||
pathname = os.path.join(cls._cfgdir, 'tenants', tenant)
|
||||
currdict = _cfgstore['tenant'][tenant]
|
||||
for category in dkdict.iterkeys():
|
||||
_mkpath(pathname)
|
||||
dbf = dbm.open(pathname + category, 'c', 384) # 0600
|
||||
dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600
|
||||
try:
|
||||
for ck in dkdict[category]:
|
||||
if ck not in currdict[category]:
|
||||
@@ -1310,7 +1342,8 @@ def dump_db_to_directory(location, password, redact=None):
|
||||
cfgfile.write(ConfigManager(tenant=None)._dump_to_json(redact=redact))
|
||||
cfgfile.write('\n')
|
||||
try:
|
||||
for tenant in os.listdir(ConfigManager._cfgdir + '/tenants/'):
|
||||
for tenant in os.listdir(
|
||||
os.path.join(ConfigManager._cfgdir, '/tenants/')):
|
||||
with open(os.path.join(location, tenant + '.json'), 'w') as cfgfile:
|
||||
cfgfile.write(ConfigManager(tenant=tenant)._dump_to_json(
|
||||
redact=redact))
|
||||
|
||||
@@ -347,6 +347,9 @@ class _ConsoleHandler(object):
|
||||
if data == conapi.ConsoleEvent.Disconnect:
|
||||
self._got_disconnected()
|
||||
return
|
||||
elif data == '':
|
||||
# ignore empty strings from a cconsole provider
|
||||
return
|
||||
if '\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
self.appmodedetected = False
|
||||
if '\x1b[?1h' in data: # remember the session wants the client to use
|
||||
@@ -362,7 +365,10 @@ class _ConsoleHandler(object):
|
||||
eventdata |= 2
|
||||
self.log(data, eventdata=eventdata)
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.buffer += data
|
||||
if isinstance(data, bytearray) or isinstance(data, bytes):
|
||||
self.buffer += data
|
||||
else:
|
||||
self.buffer += data.encode('utf-8')
|
||||
#TODO: analyze buffer for registered events, examples:
|
||||
# panics
|
||||
# certificate signing request
|
||||
|
||||
@@ -33,18 +33,23 @@
|
||||
# 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
|
||||
import confluent.messages as msg
|
||||
import confluent.noderange as noderange
|
||||
import confluent.shellmodule as shellmodule
|
||||
try:
|
||||
import confluent.shellmodule as shellmodule
|
||||
except ImportError:
|
||||
pass
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
|
||||
pluginmap = {}
|
||||
|
||||
|
||||
def seek_element(currplace, currkey):
|
||||
try:
|
||||
return currplace[currkey]
|
||||
@@ -55,10 +60,11 @@ def seek_element(currplace, currkey):
|
||||
return currplace
|
||||
raise
|
||||
|
||||
|
||||
def nested_lookup(nestdict, key):
|
||||
try:
|
||||
return reduce(seek_element, key, nestdict)
|
||||
except TypeError as e:
|
||||
except TypeError:
|
||||
raise exc.NotFoundException("Invalid element requested")
|
||||
|
||||
|
||||
@@ -72,7 +78,7 @@ def load_plugins():
|
||||
if not os.path.isdir(plugindir):
|
||||
continue
|
||||
sys.path.append(plugindir)
|
||||
#two passes, to avoid adding both py and pyc files
|
||||
# two passes, to avoid adding both py and pyc files
|
||||
for plugin in os.listdir(plugindir):
|
||||
if plugin.startswith('.'):
|
||||
continue
|
||||
@@ -80,7 +86,7 @@ def load_plugins():
|
||||
if plugtype == '.sh':
|
||||
pluginmap[plugin] = shellmodule.Plugin(
|
||||
os.path.join(plugindir, plugin + '.sh'))
|
||||
else:
|
||||
elif "__init__" not in plugin:
|
||||
plugins.add(plugin)
|
||||
for plugin in plugins:
|
||||
tmpmod = __import__(plugin)
|
||||
@@ -91,13 +97,14 @@ def load_plugins():
|
||||
pluginmap[plugin] = tmpmod
|
||||
|
||||
|
||||
rootcollections = ['noderange/', 'nodes/', 'nodegroups/', 'users/']
|
||||
rootcollections = ['noderange/', 'nodes/', 'nodegroups/', 'users/', 'events/']
|
||||
|
||||
|
||||
class PluginRoute(object):
|
||||
def __init__(self, routedict):
|
||||
self.routeinfo = routedict
|
||||
|
||||
|
||||
class PluginCollection(object):
|
||||
def __init__(self, routedict):
|
||||
self.routeinfo = routedict
|
||||
@@ -105,21 +112,81 @@ class PluginCollection(object):
|
||||
# _ prefix indicates internal use (e.g. special console scheme) and should not
|
||||
# be enumerated in any collection
|
||||
noderesources = {
|
||||
'attributes': {
|
||||
'all': PluginRoute({'handler': 'attributes'}),
|
||||
'current': PluginRoute({'handler': 'attributes'}),
|
||||
},
|
||||
'boot': {
|
||||
'nextdevice': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'configuration': {
|
||||
'management_controller': {
|
||||
'alerts': {
|
||||
'destinations': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'users': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'net_interfaces': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'reset': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'identifier': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'domain_name': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'ntp': {
|
||||
'enabled': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'servers': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
'_console': {
|
||||
'session': PluginRoute({
|
||||
'pluginattrs': ['console.method'],
|
||||
}),
|
||||
},
|
||||
'console': {
|
||||
#this is a dummy value, http or socket must handle special
|
||||
# this is a dummy value, http or socket must handle special
|
||||
'session': PluginRoute({}),
|
||||
},
|
||||
'power': {
|
||||
'state': PluginRoute({
|
||||
'license': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'events': {
|
||||
'hardware': {
|
||||
'log': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'decode': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
},
|
||||
'health': {
|
||||
'hardware': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
@@ -130,16 +197,26 @@ noderesources = {
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'boot': {
|
||||
'nextdevice': PluginRoute({
|
||||
'inventory': {
|
||||
'hardware': {
|
||||
'all': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'firmware': {
|
||||
'all': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
},
|
||||
'power': {
|
||||
'state': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'attributes': {
|
||||
'all': PluginRoute({'handler': 'attributes'}),
|
||||
'current': PluginRoute({'handler': 'attributes'}),
|
||||
},
|
||||
'sensors': {
|
||||
'hardware': {
|
||||
'all': PluginCollection({
|
||||
@@ -158,7 +235,12 @@ noderesources = {
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'leds': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
@@ -313,6 +395,7 @@ def enumerate_collections(collections):
|
||||
def handle_nodegroup_request(configmanager, inputdata,
|
||||
pathcomponents, operation):
|
||||
iscollection = False
|
||||
routespec = None
|
||||
if len(pathcomponents) < 2:
|
||||
if operation == "create":
|
||||
inputdata = msg.InputAttributes(pathcomponents, inputdata)
|
||||
@@ -337,10 +420,10 @@ def handle_nodegroup_request(configmanager, inputdata,
|
||||
if iscollection:
|
||||
if operation == "delete":
|
||||
return delete_nodegroup_collection(pathcomponents,
|
||||
configmanager)
|
||||
configmanager)
|
||||
elif operation == "retrieve":
|
||||
return enumerate_nodegroup_collection(pathcomponents,
|
||||
configmanager)
|
||||
configmanager)
|
||||
else:
|
||||
raise Exception("TODO")
|
||||
plugroute = routespec.routeinfo
|
||||
@@ -356,7 +439,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':
|
||||
@@ -397,7 +480,8 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
except TypeError:
|
||||
allnodes.sort()
|
||||
return iterate_collections(allnodes)
|
||||
if isnoderange and len(pathcomponents) == 3 and pathcomponents[2] == 'nodes':
|
||||
if (isnoderange and len(pathcomponents) == 3 and
|
||||
pathcomponents[2] == 'nodes'):
|
||||
# this means that it's a list of relevant nodes
|
||||
nodes = list(nodes)
|
||||
try:
|
||||
@@ -427,10 +511,10 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
passvalues = []
|
||||
plugroute = routespec.routeinfo
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata, nodes)
|
||||
pathcomponents, operation, inputdata, nodes, isnoderange)
|
||||
if 'handler' in plugroute: # fixed handler definition, easy enough
|
||||
hfunc = getattr(pluginmap[plugroute['handler']], operation)
|
||||
passvalue =hfunc(
|
||||
passvalue = hfunc(
|
||||
nodes=nodes, element=pathcomponents,
|
||||
configmanager=configmanager,
|
||||
inputdata=inputdata)
|
||||
@@ -455,19 +539,20 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
nodesbyhandler[hfunc].append(node)
|
||||
else:
|
||||
nodesbyhandler[hfunc] = [node]
|
||||
for hfunc in nodesbyhandler.iterkeys():
|
||||
for hfunc in nodesbyhandler:
|
||||
passvalues.append(hfunc(
|
||||
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]
|
||||
else:
|
||||
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.
|
||||
@@ -482,17 +567,17 @@ 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,
|
||||
operation)
|
||||
pathcomponents,
|
||||
operation)
|
||||
elif pathcomponents[0] == 'nodes':
|
||||
#single node request of some sort
|
||||
# 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,
|
||||
# TODO: when non-administrator accounts exist,
|
||||
# they must only be allowed to see their own user
|
||||
try:
|
||||
user = pathcomponents[1]
|
||||
@@ -514,5 +599,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()
|
||||
|
||||
@@ -42,6 +42,11 @@ class TargetEndpointBadCredentials(ConfluentException):
|
||||
# failed
|
||||
pass
|
||||
|
||||
class LockedCredentials(ConfluentException):
|
||||
# A request was performed that required a credential, but the credential
|
||||
# store is locked
|
||||
pass
|
||||
|
||||
|
||||
class ForbiddenRequest(ConfluentException):
|
||||
# The client request is not allowed by authorization engine
|
||||
@@ -51,4 +56,8 @@ class ForbiddenRequest(ConfluentException):
|
||||
class NotImplementedException(ConfluentException):
|
||||
# The current configuration/plugin is unable to perform
|
||||
# the requested task. http code 501
|
||||
pass
|
||||
pass
|
||||
|
||||
class GlobalConfigError(ConfluentException):
|
||||
# The configuration in the global config file is not right
|
||||
pass
|
||||
|
||||
@@ -25,7 +25,7 @@ import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages
|
||||
import confluent.core as pluginapi
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.tlvdata
|
||||
import confluent.util as util
|
||||
import copy
|
||||
import eventlet
|
||||
@@ -36,6 +36,7 @@ import time
|
||||
import urlparse
|
||||
import eventlet.wsgi
|
||||
#scgi = eventlet.import_patched('flup.server.scgi')
|
||||
tlvdata = confluent.tlvdata
|
||||
|
||||
|
||||
auditlog = None
|
||||
@@ -103,9 +104,36 @@ def node_creation_resources():
|
||||
desc=attribs.node[attr]['description']).html() + '<br>\n'
|
||||
|
||||
|
||||
def user_creation_resources():
|
||||
credential = {
|
||||
'uid': {
|
||||
'description': (''),
|
||||
},
|
||||
'username': {
|
||||
'description': (''),
|
||||
},
|
||||
'password': {
|
||||
'description': (''),
|
||||
},
|
||||
'privilege_level': {
|
||||
'description': (''),
|
||||
},
|
||||
}
|
||||
for attr in sorted(credential.iterkeys()):
|
||||
if attr == "password":
|
||||
yield confluent.messages.CryptedAttributes(
|
||||
kv={attr: None},
|
||||
desc=credential[attr]['description']).html() + '<br>\n'
|
||||
else:
|
||||
yield confluent.messages.Attributes(
|
||||
kv={attr: None},
|
||||
desc=credential[attr]['description']).html() + '<br>\n'
|
||||
|
||||
|
||||
create_resource_functions = {
|
||||
'/nodes/': node_creation_resources,
|
||||
'/groups/': group_creation_resources,
|
||||
'nodes': node_creation_resources,
|
||||
'groups': group_creation_resources,
|
||||
'users': user_creation_resources,
|
||||
}
|
||||
|
||||
|
||||
@@ -399,12 +427,15 @@ def resourcehandler_backend(env, start_response):
|
||||
except exc.InvalidArgumentException as e:
|
||||
start_response('400 Bad Request - ' + str(e), headers)
|
||||
yield '400 - Bad Request - ' + str(e)
|
||||
except exc.TargetEndpointUnreachable:
|
||||
except exc.TargetEndpointUnreachable as tu:
|
||||
start_response('504 Unreachable Target', headers)
|
||||
yield '504 - Unreachable Target'
|
||||
yield '504 - Unreachable Target - ' + str(tu)
|
||||
except exc.TargetEndpointBadCredentials:
|
||||
start_response('502 Bad Credentials', headers)
|
||||
yield '502 - Bad Credentials'
|
||||
except exc.LockedCredentials:
|
||||
start_response('500 Locked credential store', headers)
|
||||
yield '500 - Credential store locked'
|
||||
except exc.NotImplementedException:
|
||||
start_response('501 Not Implemented', headers)
|
||||
yield '501 Not Implemented'
|
||||
@@ -429,7 +460,6 @@ def _assemble_html(responses, resource, querydict, url, extension):
|
||||
iscollection = True
|
||||
yield '<a rel="collection" href="../{0}">../{0}</a><br>'.format(
|
||||
extension)
|
||||
|
||||
else:
|
||||
iscollection = False
|
||||
yield '<a rel="collection" href="./{0}">./{0}</a><br>'.format(
|
||||
@@ -445,11 +475,15 @@ def _assemble_html(responses, resource, querydict, url, extension):
|
||||
if iscollection:
|
||||
# localpath = url[:-2] (why was this here??)
|
||||
try:
|
||||
if url == '/users/':
|
||||
return
|
||||
firstpass = True
|
||||
for y in create_resource_functions[url]():
|
||||
module = url.split('/')
|
||||
if not module:
|
||||
return
|
||||
for y in create_resource_functions[module[-2]]():
|
||||
if firstpass:
|
||||
yield "<hr>Define new resource in %s:<BR>" % \
|
||||
url.split("/")[-2]
|
||||
yield "<hr>Define new resource in %s:<BR>" % module[-2]
|
||||
firstpass = False
|
||||
yield y
|
||||
yield ('<input value="create" name="restexplorerop" type="submit">'
|
||||
@@ -482,12 +516,16 @@ def _assemble_json(responses, resource, url, extension):
|
||||
haldata = rsp.raw()
|
||||
for hk in haldata.iterkeys():
|
||||
if 'href' in haldata[hk]:
|
||||
if isinstance(haldata[hk]['href'], int):
|
||||
haldata[hk]['href'] = str(haldata[hk]['href'])
|
||||
haldata[hk]['href'] += extension
|
||||
if hk in links:
|
||||
if isinstance(links[hk], list):
|
||||
links[hk].append(haldata[hk])
|
||||
else:
|
||||
links[hk] = [links[hk], haldata[hk]]
|
||||
elif hk == 'item':
|
||||
links[hk] = [haldata[hk],]
|
||||
else:
|
||||
links[hk] = haldata[hk]
|
||||
else:
|
||||
@@ -499,14 +537,20 @@ def _assemble_json(responses, resource, url, extension):
|
||||
else:
|
||||
rspdata[dk] = [rspdata[dk], rsp[dk]]
|
||||
else:
|
||||
rspdata[dk] = rsp[dk]
|
||||
if dk == 'databynode':
|
||||
# a quirk, databynode suggests noderange
|
||||
# multi response. This should *always* be a list,
|
||||
# even if it will be length 1
|
||||
rspdata[dk] = [rsp[dk]]
|
||||
else:
|
||||
rspdata[dk] = rsp[dk]
|
||||
rspdata["_links"] = links
|
||||
tlvdata.unicode_dictvalues(rspdata)
|
||||
yield json.dumps(
|
||||
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
|
||||
|
||||
|
||||
def serve():
|
||||
def serve(bind_host, bind_port):
|
||||
# TODO(jbjohnso): move to unix socket and explore
|
||||
# either making apache deal with it
|
||||
# or just supporting nginx or lighthttpd
|
||||
@@ -518,20 +562,22 @@ def serve():
|
||||
#also, the potential for direct http can be handy
|
||||
#todo remains unix domain socket for even http
|
||||
eventlet.wsgi.server(
|
||||
eventlet.listen(('::', 4005, 0, 0), family=socket.AF_INET6),
|
||||
eventlet.listen((bind_host, bind_port, 0, 0), family=socket.AF_INET6),
|
||||
resourcehandler, log=False, log_output=False, debug=False)
|
||||
|
||||
|
||||
class HttpApi(object):
|
||||
def __init__(self):
|
||||
def __init__(self, bind_host=None, bind_port=None):
|
||||
self.server = None
|
||||
self.bind_host = bind_host or '::'
|
||||
self.bind_port = bind_port or 4005
|
||||
|
||||
def start(self):
|
||||
global auditlog
|
||||
global tracelog
|
||||
tracelog = log.Logger('trace')
|
||||
auditlog = log.Logger('audit')
|
||||
self.server = eventlet.spawn(serve)
|
||||
self.server = eventlet.spawn(serve, self.bind_host, self.bind_port)
|
||||
|
||||
|
||||
_cleaner = eventlet.spawn(_sessioncleaner)
|
||||
|
||||
@@ -28,7 +28,7 @@ class Console(object):
|
||||
"""This is the class defining the interface a console plugin must return
|
||||
for the _console/session element"""
|
||||
def __init__(self, node, config):
|
||||
raise NotImplementedError("Subclassing required")
|
||||
return
|
||||
|
||||
def connect(self, callback):
|
||||
raise NotImplementedError("Subclassing required")
|
||||
@@ -36,13 +36,10 @@ class Console(object):
|
||||
def write(self, data):
|
||||
raise NotImplementedError("Subclassing required")
|
||||
|
||||
def wait_for_data(self, timeout=600):
|
||||
raise NotImplementedError("Subclassing required")
|
||||
|
||||
def send_break(self):
|
||||
"""This function is how a plugin should implement sending a break to
|
||||
the remote console"""
|
||||
pass
|
||||
return
|
||||
|
||||
def ping(self):
|
||||
"""This function is a hint to the console plugin that now would be a
|
||||
@@ -54,4 +51,4 @@ class Console(object):
|
||||
as well, so consoles can schedule a health check to run at this time.
|
||||
No return is expected, any error condition can be reported by sending
|
||||
ConsoleEvent.Disconnect, just like normal."""
|
||||
pass
|
||||
return
|
||||
|
||||
@@ -61,14 +61,34 @@
|
||||
|
||||
import collections
|
||||
import confluent.config.configmanager
|
||||
import confluent.config.conf as conf
|
||||
import confluent.exceptions as exc
|
||||
import eventlet
|
||||
import fcntl
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_SH
|
||||
except ImportError:
|
||||
if os.name == 'nt':
|
||||
import msvcrt
|
||||
LOCK_SH = msvcrt.LK_LOCK # no shared, degrade to exclusive
|
||||
LOCK_EX = msvcrt.LK_LOCK
|
||||
LOCK_UN = msvcrt.LK_UNLCK
|
||||
def flock(file, flag):
|
||||
oldoffset = file.tell()
|
||||
file.seek(0)
|
||||
msvcrt.locking(file.fileno(), flag, 1)
|
||||
file.seek(oldoffset)
|
||||
else:
|
||||
raise
|
||||
|
||||
# on conserving filehandles:
|
||||
# upon write, if file not open, open it for append
|
||||
# upon write, schedule/reschedule closing filehandle in 15 seconds
|
||||
@@ -80,14 +100,14 @@ import traceback
|
||||
# if that happens, warn to have user increase ulimit for optimal
|
||||
# performance
|
||||
|
||||
MIDNIGHT = 24 * 60 * 60
|
||||
_loggers = {}
|
||||
|
||||
|
||||
class Events(object):
|
||||
(
|
||||
undefined, clearscreen, clientconnect, clientdisconnect,
|
||||
consoledisconnect, consoleconnect, stacktrace
|
||||
) = range(7)
|
||||
consoledisconnect, consoleconnect, stacktrace, logrollover
|
||||
) = range(8)
|
||||
logstr = {
|
||||
2: 'connection by ',
|
||||
3: 'disconnection by ',
|
||||
@@ -98,6 +118,371 @@ class DataTypes(object):
|
||||
text, dictionary, console, event = range(4)
|
||||
|
||||
|
||||
class RollingTypes(object):
|
||||
no_rolling, size_rolling, time_rolling = range(3)
|
||||
|
||||
|
||||
class BaseRotatingHandler(object):
|
||||
|
||||
def __init__(self, filepath, logname):
|
||||
"""
|
||||
Use the specified filename for streamed logging
|
||||
"""
|
||||
self.filepath = filepath
|
||||
self.textpath = os.path.join(self.filepath, logname)
|
||||
self.binpath = os.path.join(self.filepath, logname + ".cbl")
|
||||
self.textfile = None
|
||||
self.binfile = None
|
||||
|
||||
def open(self):
|
||||
if self.textfile is None:
|
||||
self.textfile = open(self.textpath, mode='ab')
|
||||
self.textfile.seek(0, 2)
|
||||
if self.binfile is None:
|
||||
self.binfile = open(self.binpath, mode='ab')
|
||||
self.binfile.seek(0, 2)
|
||||
return self.textfile, self.binfile
|
||||
|
||||
def try_emit(self, binrecord, textrecord):
|
||||
"""
|
||||
Emit a record.
|
||||
|
||||
Output the record to the file, catering for rollover as described
|
||||
in doRollover().
|
||||
"""
|
||||
rolling_type = self.shouldRollover(binrecord, textrecord)
|
||||
if rolling_type:
|
||||
flock(self.textfile, LOCK_UN)
|
||||
return self.doRollover(rolling_type)
|
||||
return None
|
||||
|
||||
def emit(self, binrecord, textrecord):
|
||||
if self.textfile is None:
|
||||
self.textfile = open(self.textpath, mode='ab')
|
||||
if self.binfile is None:
|
||||
self.binfile = open(self.binpath, mode='ab')
|
||||
self.textfile.write(textrecord)
|
||||
self.binfile.write(binrecord)
|
||||
self.textfile.flush()
|
||||
self.binfile.flush()
|
||||
|
||||
def get_textfile_offset(self, data_len):
|
||||
if self.textfile is None:
|
||||
self.textfile = open(self.textpath, mode='ab')
|
||||
return self.textfile.tell() + data_len
|
||||
|
||||
def close(self):
|
||||
if self.textfile:
|
||||
if not self.textfile.closed:
|
||||
self.textfile.close()
|
||||
self.textfile = None
|
||||
if self.binfile:
|
||||
if not self.binfile.closed:
|
||||
self.binfile.close()
|
||||
self.binfile = None
|
||||
|
||||
|
||||
class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
|
||||
"""
|
||||
Handler for logging to a file, rotating the log file at certain timed
|
||||
intervals.
|
||||
|
||||
If backupCount is > 0, when rollover is done, no more than backupCount
|
||||
files are kept - the oldest ones are deleted.
|
||||
"""
|
||||
|
||||
def __init__(self, filepath, logname, interval=1):
|
||||
BaseRotatingHandler.__init__(self, filepath, logname)
|
||||
try:
|
||||
self.when = conf.get_option('log', 'when').upper()
|
||||
except (AttributeError):
|
||||
self.when = 'D'
|
||||
self.backupCount = conf.get_int_option('log', 'backup_count') or 3
|
||||
self.maxBytes = conf.get_int_option(
|
||||
'log','max_bytes') or 4 * 1024 * 1024 * 1024
|
||||
if self.maxBytes < 8192:
|
||||
raise exc.GlobalConfigError("The minimum value of max_bytes "
|
||||
"of log rolling size in the log "
|
||||
"section should larger than 8192.")
|
||||
self.utc = conf.get_boolean_option('log', 'utc') or False
|
||||
|
||||
# Calculate the real rollover interval, which is just the number of
|
||||
# seconds between rollovers. Also set the filename suffix used when
|
||||
# a rollover occurs. Current 'when' events supported:
|
||||
# S - Seconds
|
||||
# M - Minutes
|
||||
# H - Hours
|
||||
# D - Days
|
||||
# midnight - roll over at midnight
|
||||
# W{0-6} - roll over on a certain day; 0 - Monday
|
||||
#
|
||||
# Case of the 'when' specifier is not important; lower or upper case
|
||||
# will work.
|
||||
if self.when == 'S':
|
||||
self.interval = 1 # one second
|
||||
self.suffix = "%Y-%m-%d_%H-%M-%S"
|
||||
self.extMatch = r"^(cbl\.){0,1}\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.{0,1}\d*$"
|
||||
elif self.when == 'M':
|
||||
self.interval = 60 # one minute
|
||||
self.suffix = "%Y-%m-%d_%H-%M"
|
||||
self.extMatch = r"^(cbl\.){0,1}\d{4}-\d{2}-\d{2}_\d{2}-\d{2}\.{0,1}\d*$"
|
||||
elif self.when == 'H':
|
||||
self.interval = 60 * 60 # one hour
|
||||
self.suffix = "%Y-%m-%d_%H"
|
||||
self.extMatch = r"^(cbl\.){0,1}\d{4}-\d{2}-\d{2}_\d{2}$"
|
||||
elif self.when == 'D' or self.when == 'MIDNIGHT':
|
||||
self.interval = 60 * 60 * 24 # one day
|
||||
self.suffix = "%Y-%m-%d"
|
||||
self.extMatch = r"^(cbl\.){0,1}\d{4}-\d{2}-\d{2}\.{0,1}\d*$"
|
||||
elif self.when.startswith('W'):
|
||||
self.interval = 60 * 60 * 24 * 7 # one week
|
||||
if len(self.when) != 2:
|
||||
raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
|
||||
if self.when[1] < '0' or self.when[1] > '6':
|
||||
raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
|
||||
self.dayOfWeek = int(self.when[1])
|
||||
self.suffix = "%Y-%m-%d"
|
||||
self.extMatch = r"^(cbl\.){0,1}\d{4}-\d{2}-\d{2}\.{0,1}\d*$"
|
||||
else:
|
||||
raise ValueError("Invalid rollover interval specified: %s" % self.when)
|
||||
|
||||
self.extMatch = re.compile(self.extMatch)
|
||||
self.interval = self.interval * interval # multiply by units requested
|
||||
# Note: Get the modify time of text log file to calculate the
|
||||
# rollover time
|
||||
if os.path.exists(self.textpath):
|
||||
t = os.stat(self.textpath)[stat.ST_MTIME]
|
||||
else:
|
||||
t = int(time.time())
|
||||
self.rolloverAt = self.computeRollover(t)
|
||||
self.sizeRollingCount = 0
|
||||
self.initSizeRollingCount()
|
||||
|
||||
def computeRollover(self, currentTime):
|
||||
"""
|
||||
Work out the rollover time based on the specified time.
|
||||
"""
|
||||
result = currentTime + self.interval
|
||||
# If we are rolling over at midnight or weekly, then the interval is already known.
|
||||
# What we need to figure out is WHEN the next interval is. In other words,
|
||||
# if you are rolling over at midnight, then your base interval is 1 day,
|
||||
# but you want to start that one day clock at midnight, not now. So, we
|
||||
# have to fudge the rolloverAt value in order to trigger the first rollover
|
||||
# at the right time. After that, the regular interval will take care of
|
||||
# the rest. Note that this code doesn't care about leap seconds. :)
|
||||
if self.when == 'MIDNIGHT' or self.when.startswith('W'):
|
||||
# This could be done with less code, but I wanted it to be clear
|
||||
if self.utc:
|
||||
t = time.gmtime(currentTime)
|
||||
else:
|
||||
t = time.localtime(currentTime)
|
||||
currentHour = t[3]
|
||||
currentMinute = t[4]
|
||||
currentSecond = t[5]
|
||||
# r is the number of seconds left between now and midnight
|
||||
r = MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
|
||||
currentSecond)
|
||||
result = currentTime + r
|
||||
# If we are rolling over on a certain day, add in the number of days until
|
||||
# the next rollover, but offset by 1 since we just calculated the time
|
||||
# until the next day starts. There are three cases:
|
||||
# Case 1) The day to rollover is today; in this case, do nothing
|
||||
# Case 2) The day to rollover is further in the interval (i.e., today is
|
||||
# day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
|
||||
# next rollover is simply 6 - 2 - 1, or 3.
|
||||
# Case 3) The day to rollover is behind us in the interval (i.e., today
|
||||
# is day 5 (Saturday) and rollover is on day 3 (Thursday).
|
||||
# Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
|
||||
# number of days left in the current week (1) plus the number
|
||||
# of days in the next week until the rollover day (3).
|
||||
# The calculations described in 2) and 3) above need to have a day added.
|
||||
# This is because the above time calculation takes us to midnight on this
|
||||
# day, i.e. the start of the next day.
|
||||
if self.when.startswith('W'):
|
||||
day = t[6] # 0 is Monday
|
||||
if day != self.dayOfWeek:
|
||||
if day < self.dayOfWeek:
|
||||
daysToWait = self.dayOfWeek - day
|
||||
else:
|
||||
daysToWait = 6 - day + self.dayOfWeek + 1
|
||||
newRolloverAt = result + (daysToWait * (60 * 60 * 24))
|
||||
if not self.utc:
|
||||
dstNow = t[-1]
|
||||
dstAtRollover = time.localtime(newRolloverAt)[-1]
|
||||
if dstNow != dstAtRollover:
|
||||
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
|
||||
addend = -3600
|
||||
else: # DST bows out before next rollover, so we need to add an hour
|
||||
addend = 3600
|
||||
newRolloverAt += addend
|
||||
result = newRolloverAt
|
||||
return result
|
||||
|
||||
def shouldRollover(self, binrecord, textrecord):
|
||||
"""
|
||||
Determine if rollover should occur.
|
||||
Just compare times.
|
||||
"""
|
||||
# time rolling first
|
||||
t = int(time.time())
|
||||
if t >= self.rolloverAt:
|
||||
return RollingTypes.time_rolling
|
||||
self.open()
|
||||
if self.maxBytes > 0: # are we rolling over?
|
||||
if self.textfile.tell() + len(textrecord) >= self.maxBytes:
|
||||
return RollingTypes.size_rolling
|
||||
if self.binfile.tell() + len(binrecord) >= self.maxBytes:
|
||||
return RollingTypes.size_rolling
|
||||
return RollingTypes.no_rolling
|
||||
|
||||
def getFilesToDelete(self):
|
||||
"""
|
||||
Determine the files to delete when rolling over.
|
||||
"""
|
||||
dirName, baseName = os.path.split(self.textpath)
|
||||
files = []
|
||||
prefix = baseName + "."
|
||||
filePaths = glob.glob(os.path.join(dirName, "%s*" % prefix))
|
||||
fileNames = [os.path.split(f)[1] for f in filePaths]
|
||||
plen = len(prefix)
|
||||
t_set = set()
|
||||
for fileName in fileNames:
|
||||
suffix = fileName[plen:]
|
||||
if self.extMatch.match(suffix):
|
||||
s = suffix.split(".")
|
||||
t = s[1] if suffix.startswith("cbl") else s[0]
|
||||
t_set.add(t)
|
||||
files.append({'time': t, 'file': os.path.join(dirName,
|
||||
fileName)})
|
||||
|
||||
t_list = list(t_set)
|
||||
t_list.sort()
|
||||
result = [f['file'] for f in files if
|
||||
f['time'] in t_list[:-(self.backupCount - 1)]]
|
||||
return result
|
||||
|
||||
def initSizeRollingCount(self):
|
||||
"""
|
||||
Init the max number of log files for current time.
|
||||
"""
|
||||
dirName, baseName = os.path.split(self.textpath)
|
||||
prefix = baseName + "."
|
||||
filePaths = glob.glob(os.path.join(dirName, "%s*" % prefix))
|
||||
fileNames = [os.path.split(f)[1] for f in filePaths]
|
||||
plen = len(prefix)
|
||||
for fileName in fileNames:
|
||||
suffix = fileName[plen:]
|
||||
try:
|
||||
self.sizeRollingCount = max(self.sizeRollingCount, int(suffix))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def _sizeRoll(self):
|
||||
self.close()
|
||||
for i in range(self.sizeRollingCount, 0, -1):
|
||||
sbfn = "%s.%d" % (self.binpath, i)
|
||||
dbfn = "%s.%d" % (self.binpath, i + 1)
|
||||
stfn = "%s.%d" % (self.textpath, i)
|
||||
dtfn = "%s.%d" % (self.textpath, i + 1)
|
||||
if os.path.exists(sbfn):
|
||||
if os.path.exists(dbfn):
|
||||
os.remove(dbfn)
|
||||
os.rename(sbfn, dbfn)
|
||||
if os.path.exists(stfn):
|
||||
if os.path.exists(dtfn):
|
||||
os.remove(dtfn)
|
||||
os.rename(stfn, dtfn)
|
||||
# size rolling happens, add statistics count
|
||||
self.sizeRollingCount += 1
|
||||
dbfn = self.binpath + ".1"
|
||||
dtfn = self.textpath + ".1"
|
||||
if os.path.exists(dbfn):
|
||||
os.remove(dbfn)
|
||||
if os.path.exists(dtfn):
|
||||
os.remove(dtfn)
|
||||
if os.path.exists(self.binpath):
|
||||
os.rename(self.binpath, dbfn)
|
||||
if os.path.exists(self.textpath):
|
||||
os.rename(self.textpath, dtfn)
|
||||
return dbfn, dtfn
|
||||
|
||||
def _timeRoll(self):
|
||||
self.close()
|
||||
# get the time that this sequence started at and make it a TimeTuple
|
||||
currentTime = int(time.time())
|
||||
dstNow = time.localtime(currentTime)[-1]
|
||||
t = self.rolloverAt - self.interval
|
||||
if self.utc:
|
||||
timeTuple = time.gmtime(t)
|
||||
else:
|
||||
timeTuple = time.localtime(t)
|
||||
dstThen = timeTuple[-1]
|
||||
if dstNow != dstThen:
|
||||
if dstNow:
|
||||
addend = 3600
|
||||
else:
|
||||
addend = -3600
|
||||
timeTuple = time.localtime(t + addend)
|
||||
|
||||
# if size rolling files exist
|
||||
for i in range(self.sizeRollingCount, 0, -1):
|
||||
sbfn = "%s.%d" % ( self.binpath, i)
|
||||
dbfn = "%s.%s.%d" % (
|
||||
self.binpath, time.strftime(self.suffix, timeTuple),i)
|
||||
stfn = "%s.%d" % (self.textpath, i)
|
||||
dtfn = "%s.%s.%d" % (
|
||||
self.textpath, time.strftime(self.suffix, timeTuple), i)
|
||||
if os.path.exists(sbfn):
|
||||
if os.path.exists(dbfn):
|
||||
os.remove(dbfn)
|
||||
os.rename(sbfn, dbfn)
|
||||
if os.path.exists(stfn):
|
||||
if os.path.exists(dtfn):
|
||||
os.remove(dtfn)
|
||||
os.rename(stfn, dtfn)
|
||||
|
||||
# As time rolling happens, reset statistics count
|
||||
self.sizeRollingCount = 0
|
||||
dbfn = self.binpath + "." + time.strftime(self.suffix, timeTuple)
|
||||
dtfn = self.textpath + "." + time.strftime(self.suffix, timeTuple)
|
||||
|
||||
if os.path.exists(dbfn):
|
||||
os.remove(dbfn)
|
||||
if os.path.exists(dtfn):
|
||||
os.remove(dtfn)
|
||||
if os.path.exists(self.binpath):
|
||||
os.rename(self.binpath, dbfn)
|
||||
if os.path.exists(self.textpath):
|
||||
os.rename(self.textpath, dtfn)
|
||||
if self.backupCount > 0:
|
||||
for s in self.getFilesToDelete():
|
||||
os.remove(s)
|
||||
|
||||
newRolloverAt = self.computeRollover(currentTime)
|
||||
while newRolloverAt <= currentTime:
|
||||
newRolloverAt = newRolloverAt + self.interval
|
||||
#If DST changes and midnight or weekly rollover, adjust for this.
|
||||
if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
|
||||
dstAtRollover = time.localtime(newRolloverAt)[-1]
|
||||
if dstNow != dstAtRollover:
|
||||
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
|
||||
addend = -3600
|
||||
else: # DST bows out before next rollover, so we need to add an hour
|
||||
addend = 3600
|
||||
newRolloverAt += addend
|
||||
self.rolloverAt = newRolloverAt
|
||||
return dbfn, dtfn
|
||||
|
||||
def doRollover(self, rolling_type):
|
||||
"""
|
||||
do a rollover based on the rolling type.
|
||||
"""
|
||||
if rolling_type == RollingTypes.size_rolling:
|
||||
return self._sizeRoll()
|
||||
if rolling_type == RollingTypes.time_rolling:
|
||||
return self._timeRoll()
|
||||
|
||||
|
||||
class Logger(object):
|
||||
"""
|
||||
:param console: If true, [] will be used to denote non-text events. If
|
||||
@@ -122,26 +507,28 @@ class Logger(object):
|
||||
self.initialized = True
|
||||
self.filepath = confluent.config.configmanager.get_global("logdirectory")
|
||||
if self.filepath is None:
|
||||
self.filepath = "/var/log/confluent/"
|
||||
if os.name == 'nt':
|
||||
self.filepath = os.path.join(
|
||||
os.getenv('SystemDrive'), '\\ProgramData', 'confluent',
|
||||
'logs')
|
||||
else:
|
||||
self.filepath = "/var/log/confluent"
|
||||
self.isconsole = console
|
||||
if console:
|
||||
self.filepath += "consoles/"
|
||||
self.filepath = os.path.join(self.filepath, "consoles")
|
||||
if not os.path.isdir(self.filepath):
|
||||
os.makedirs(self.filepath, 448)
|
||||
self.textpath = self.filepath + logname
|
||||
self.binpath = self.filepath + logname + ".cbl"
|
||||
self.writer = None
|
||||
self.closer = None
|
||||
self.textfile = None
|
||||
self.binfile = None
|
||||
self.handler = TimedAndSizeRotatingFileHandler(self.filepath, logname,
|
||||
interval=1)
|
||||
self.lockfile = None
|
||||
self.logname = logname
|
||||
self.logentries = collections.deque()
|
||||
|
||||
def writedata(self):
|
||||
if self.textfile is None:
|
||||
self.textfile = open(self.textpath, mode='ab')
|
||||
if self.binfile is None:
|
||||
self.binfile = open(self.binpath, mode='ab')
|
||||
while self.logentries:
|
||||
textfile, binfile = self.handler.open()
|
||||
entry = self.logentries.popleft()
|
||||
ltype = entry[0]
|
||||
tstamp = entry[1]
|
||||
@@ -156,8 +543,8 @@ class Logger(object):
|
||||
elif not self.isconsole:
|
||||
textdate = time.strftime(
|
||||
'%b %d %H:%M:%S ', time.localtime(tstamp))
|
||||
fcntl.flock(self.textfile, fcntl.LOCK_EX)
|
||||
offset = self.textfile.tell() + len(textdate)
|
||||
flock(textfile, LOCK_EX)
|
||||
offset = textfile.tell() + len(textdate)
|
||||
datalen = len(data)
|
||||
eventaux = entry[4]
|
||||
if eventaux is None:
|
||||
@@ -175,53 +562,103 @@ class Logger(object):
|
||||
textrecord = textdate + data
|
||||
if not textrecord.endswith('\n'):
|
||||
textrecord += '\n'
|
||||
self.textfile.write(textrecord)
|
||||
fcntl.flock(self.textfile, fcntl.LOCK_UN)
|
||||
fcntl.flock(self.binfile, fcntl.LOCK_EX)
|
||||
self.binfile.write(binrecord)
|
||||
fcntl.flock(self.binfile, fcntl.LOCK_UN)
|
||||
self.textfile.flush()
|
||||
self.binfile.flush()
|
||||
files = self.handler.try_emit(binrecord, textrecord)
|
||||
if not files:
|
||||
self.handler.emit(binrecord, textrecord)
|
||||
flock(textfile, LOCK_UN)
|
||||
else:
|
||||
# Log the rolling event at first, then log the last data
|
||||
# which cause the rolling event.
|
||||
to_bfile, to_tfile = files
|
||||
self.logentries.appendleft(entry)
|
||||
roll_data = json.dumps({'previouslogfile': to_tfile})
|
||||
self.logentries.appendleft([DataTypes.event, tstamp, roll_data,
|
||||
Events.logrollover, None])
|
||||
if self.closer is None:
|
||||
self.closer = eventlet.spawn_after(15, self.closelog)
|
||||
self.writer = None
|
||||
|
||||
def read_recent_text(self, size):
|
||||
|
||||
def parse_last_rolling_files(textfile, offset, datalen):
|
||||
textfile.seek(offset, 0)
|
||||
textpath = json.loads(textfile.read(datalen))['previouslogfile']
|
||||
dir_name, base_name = os.path.split(textpath)
|
||||
temp = base_name.split('.')
|
||||
temp.insert(1,'cbl')
|
||||
# find the recent bin file
|
||||
binpath = os.path.join(dir_name, ".".join(temp))
|
||||
return textpath, binpath
|
||||
|
||||
textpath = self.handler.textpath
|
||||
binpath = self.handler.binpath
|
||||
try:
|
||||
textfile = open(self.textpath, mode='r')
|
||||
binfile = open(self.binpath, mode='r')
|
||||
textfile = open(textpath, mode='r')
|
||||
binfile = open(binpath, mode='r')
|
||||
except IOError:
|
||||
return '', 0, 0
|
||||
fcntl.flock(binfile, fcntl.LOCK_SH)
|
||||
flock(binfile, LOCK_SH)
|
||||
binfile.seek(0, 2)
|
||||
binidx = binfile.tell() - 16
|
||||
binidx = binfile.tell()
|
||||
currsize = 0
|
||||
offsets = []
|
||||
termstate = None
|
||||
recenttimestamp = 0
|
||||
access_last_rename = False
|
||||
flock(textfile, LOCK_SH)
|
||||
while binidx > 0 and currsize < size:
|
||||
binfile.seek(binidx, 0)
|
||||
binidx -= 16
|
||||
binfile.seek(binidx, 0)
|
||||
recbytes = binfile.read(16)
|
||||
(_, ltype, offset, datalen, tstamp, evtdata, eventaux, _) = \
|
||||
struct.unpack(">BBIHIBBH", recbytes)
|
||||
if ltype != 2:
|
||||
# rolling events found.
|
||||
if ltype == DataTypes.event and evtdata == Events.logrollover:
|
||||
# Now, we can only find the last renamed file which logging
|
||||
# the data.
|
||||
if access_last_rename == False:
|
||||
access_last_rename = True
|
||||
else:
|
||||
break
|
||||
textpath, binpath = parse_last_rolling_files(textfile, offset,
|
||||
datalen)
|
||||
# Rolling event detected, close the current bin file, then open
|
||||
# the renamed bin file.
|
||||
flock(binfile, LOCK_UN)
|
||||
binfile.close()
|
||||
try:
|
||||
binfile = open(binpath, mode='r')
|
||||
except IOError:
|
||||
return '', 0, 0
|
||||
flock(binfile, LOCK_SH)
|
||||
binfile.seek(0, 2)
|
||||
binidx = binfile.tell()
|
||||
# things have been set up for next iteration to dig to
|
||||
# previous log file, go to next iteration
|
||||
continue
|
||||
elif ltype != 2:
|
||||
continue
|
||||
if tstamp > recenttimestamp:
|
||||
recenttimestamp = tstamp
|
||||
currsize += datalen
|
||||
offsets.append((offset, datalen))
|
||||
offsets.append((offset, datalen, textpath))
|
||||
if termstate is None:
|
||||
termstate = eventaux
|
||||
fcntl.flock(binfile, fcntl.LOCK_UN)
|
||||
flock(binfile, LOCK_UN)
|
||||
binfile.close()
|
||||
textdata = ''
|
||||
fcntl.flock(textfile, fcntl.LOCK_SH)
|
||||
while offsets:
|
||||
(offset, length) = offsets.pop()
|
||||
(offset, length, textpath) = offsets.pop()
|
||||
if textfile.name != textpath:
|
||||
flock(textfile, LOCK_UN)
|
||||
textfile.close()
|
||||
try:
|
||||
textfile = open(textpath)
|
||||
except IOError:
|
||||
return '', 0, 0
|
||||
textfile.seek(offset, 0)
|
||||
textdata += textfile.read(length)
|
||||
fcntl.flock(textfile, fcntl.LOCK_UN)
|
||||
flock(textfile, LOCK_UN)
|
||||
textfile.close()
|
||||
if termstate is None:
|
||||
termstate = 0
|
||||
@@ -270,8 +707,5 @@ class Logger(object):
|
||||
self.writer = eventlet.spawn_after(2, self.writedata)
|
||||
|
||||
def closelog(self):
|
||||
self.textfile.close()
|
||||
self.binfile.close()
|
||||
self.textfile = None
|
||||
self.binfile = None
|
||||
self.handler.close()
|
||||
self.closer = None
|
||||
|
||||
@@ -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
|
||||
@@ -27,21 +27,36 @@
|
||||
|
||||
import atexit
|
||||
import confluent.auth as auth
|
||||
import confluent.config.conf as conf
|
||||
import confluent.config.configmanager as configmanager
|
||||
import confluent.consoleserver as consoleserver
|
||||
import confluent.core as confluentcore
|
||||
import confluent.httpapi as httpapi
|
||||
import confluent.log as log
|
||||
import confluent.sockapi as sockapi
|
||||
try:
|
||||
import confluent.sockapi as sockapi
|
||||
except ImportError:
|
||||
#On platforms without pwd, give up on the sockapi in general and be http
|
||||
#only for now
|
||||
pass
|
||||
import eventlet
|
||||
#import eventlet.backdoor as backdoor
|
||||
import fcntl
|
||||
havefcntl = True
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
havefcntl = False
|
||||
#import multiprocessing
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
def _daemonize():
|
||||
if not 'fork' in os.__dict__:
|
||||
return
|
||||
thispid = os.fork()
|
||||
if thispid > 0:
|
||||
os.waitpid(thispid, 0)
|
||||
@@ -106,19 +121,49 @@ def _checkpidfile():
|
||||
def terminate(signalname, frame):
|
||||
sys.exit(0)
|
||||
|
||||
def dumptrace(signalname, frame):
|
||||
ht = open('/var/log/confluent/hangtraces', 'a')
|
||||
ht.write('Dumping active trace on ' + time.strftime('%X %x\n'))
|
||||
ht.write(''.join(traceback.format_stack(frame)))
|
||||
ht.close()
|
||||
|
||||
def doexit():
|
||||
if not havefcntl:
|
||||
return
|
||||
pidfile = open('/var/run/confluent/pid')
|
||||
pid = pidfile.read()
|
||||
if pid == str(os.getpid()):
|
||||
os.remove('/var/run/confluent/pid')
|
||||
|
||||
|
||||
def _initsecurity(config):
|
||||
if config.has_option('security', 'externalcfgkey'):
|
||||
keyfile = config.get('security', 'externalcfgkey')
|
||||
with open(keyfile, 'r') as keyhandle:
|
||||
key = keyhandle.read()
|
||||
configmanager.init_masterkey(key)
|
||||
|
||||
|
||||
def run():
|
||||
_checkpidfile()
|
||||
confluentcore.load_plugins()
|
||||
signal.signal(signal.SIGUSR1, dumptrace)
|
||||
if havefcntl:
|
||||
_checkpidfile()
|
||||
conf.init_config()
|
||||
try:
|
||||
config = conf.get_config()
|
||||
_initsecurity(config)
|
||||
except:
|
||||
sys.stderr.write("Error unlocking credential store\n")
|
||||
doexit()
|
||||
sys.exit(1)
|
||||
try:
|
||||
confluentcore.load_plugins()
|
||||
except:
|
||||
doexit()
|
||||
raise
|
||||
_daemonize()
|
||||
_updatepidfile()
|
||||
if havefcntl:
|
||||
_updatepidfile()
|
||||
auth.init_auth()
|
||||
signal.signal(signal.SIGINT, terminate)
|
||||
signal.signal(signal.SIGTERM, terminate)
|
||||
@@ -127,11 +172,21 @@ def run():
|
||||
#dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
|
||||
# family=socket.AF_UNIX)
|
||||
#eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
|
||||
http_bind_host, http_bind_port = _get_connector_config('http')
|
||||
sock_bind_host, sock_bind_port = _get_connector_config('socket')
|
||||
consoleserver.start_console_sessions()
|
||||
webservice = httpapi.HttpApi()
|
||||
webservice = httpapi.HttpApi(http_bind_host, http_bind_port)
|
||||
webservice.start()
|
||||
sockservice = sockapi.SockApi()
|
||||
sockservice.start()
|
||||
try:
|
||||
sockservice = sockapi.SockApi(sock_bind_host, sock_bind_port)
|
||||
sockservice.start()
|
||||
except NameError:
|
||||
pass
|
||||
atexit.register(doexit)
|
||||
while 1:
|
||||
eventlet.sleep(100)
|
||||
|
||||
def _get_connector_config(session):
|
||||
host = conf.get_option(session, 'bindhost')
|
||||
port = conf.get_int_option(session, 'bindport')
|
||||
return (host, port)
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
import confluent.exceptions as exc
|
||||
import json
|
||||
|
||||
valid_health_values = set([
|
||||
'ok',
|
||||
'warning',
|
||||
'critical',
|
||||
'failed',
|
||||
'unknown',
|
||||
])
|
||||
|
||||
def _htmlify_structure(indict):
|
||||
ret = "<ul>"
|
||||
@@ -33,8 +40,14 @@ def _htmlify_structure(indict):
|
||||
ret += _htmlify_structure(indict[key])
|
||||
elif isinstance(indict, list):
|
||||
if len(indict) > 0:
|
||||
if type(indict[0]) in (str, unicode):
|
||||
ret += ",".join(indict)
|
||||
if type(indict[0]) in (str, unicode, None):
|
||||
nd = []
|
||||
for datum in indict:
|
||||
if datum is None:
|
||||
nd.append('')
|
||||
else:
|
||||
nd.append(datum)
|
||||
ret += ",".join(nd)
|
||||
else:
|
||||
for v in indict:
|
||||
ret += _htmlify_structure(v)
|
||||
@@ -75,7 +88,7 @@ class ConfluentMessage(object):
|
||||
self.kvpairs = self.kvpairs[node]
|
||||
|
||||
def html(self, extension=''):
|
||||
#this is used to facilitate the api explorer feature
|
||||
# this is used to facilitate the api explorer feature
|
||||
if not hasattr(self, 'stripped'):
|
||||
self.stripped = False
|
||||
if not hasattr(self, 'notnode'):
|
||||
@@ -94,8 +107,31 @@ class ConfluentMessage(object):
|
||||
for key in pairs.iterkeys():
|
||||
val = pairs[key]
|
||||
value = self.defaultvalue
|
||||
valtype = self.defaulttype
|
||||
if isinstance(val, dict) and 'type' in val:
|
||||
valtype = val['type']
|
||||
else:
|
||||
valtype = self.defaulttype
|
||||
notes = []
|
||||
|
||||
if isinstance(val, list):
|
||||
snippet += key + ":"
|
||||
if len(val) == 0 and not self.readonly:
|
||||
snippet += ('<input type="{0}" name="{1}" value="" '
|
||||
' "title="{2}">'
|
||||
).format(valtype, key, self.desc)
|
||||
for v in val:
|
||||
if self.readonly:
|
||||
snippet += _htmlify_structure(v)
|
||||
else:
|
||||
snippet += ('<input type="{0}" name="{1}" value="{2}" '
|
||||
' "title="{3}">\r'
|
||||
).format(valtype, key, v, self.desc)
|
||||
if not self.readonly:
|
||||
snippet += (
|
||||
'<input type="{0}" name="{1}" value="" title="{2}">'
|
||||
'<input type="checkbox" name="restexplorerhonorkey" '
|
||||
'value="{1}">\r').format(valtype, key, self.desc)
|
||||
return snippet
|
||||
if val is not None and 'value' in val:
|
||||
value = val['value']
|
||||
if 'inheritedfrom' in val:
|
||||
@@ -119,32 +155,13 @@ class ConfluentMessage(object):
|
||||
if 'inheritedfrom' in val:
|
||||
notes.append('Inherited from %s' % val['inheritedfrom'])
|
||||
value = '********'
|
||||
if isinstance(val, list):
|
||||
snippet += key + ":"
|
||||
if len(val) == 0 and not self.readonly:
|
||||
snippet += ('<input type="{0}" name="{1}" value="" '
|
||||
' "title="{2}">'
|
||||
).format(valtype, key, self.desc)
|
||||
for v in val:
|
||||
if self.readonly:
|
||||
snippet += _htmlify_structure(v)
|
||||
else:
|
||||
snippet += ('<input type="{0}" name="{1}" value="{2}" '
|
||||
' "title="{3}">'
|
||||
).format(valtype, key, v, self.desc)
|
||||
if not self.readonly:
|
||||
snippet += (
|
||||
'<input type="{0}" name="{1}" value="" title="{2}">'
|
||||
'<input type="checkbox" name="restexplorerhonorkey" '
|
||||
'value="{1}">').format(valtype, key, self.desc)
|
||||
return snippet
|
||||
if self.readonly:
|
||||
snippet += "{0}: {1}".format(key, value)
|
||||
else:
|
||||
snippet += (key + ":" +
|
||||
'<input type="{0}" name="{1}" value="{2}" '
|
||||
'title="{3}"><input type="checkbox" '
|
||||
'name="restexplorerhonorkey" value="{1}">'
|
||||
'name="restexplorerhonorkey" value="{1}"><br>\r'
|
||||
).format(valtype, key, value, self.desc)
|
||||
if len(notes) > 0:
|
||||
snippet += '(' + ','.join(notes) + ')'
|
||||
@@ -163,18 +180,18 @@ class ConfluentNodeError(object):
|
||||
return self.node + ":" + self.error
|
||||
|
||||
def strip_node(self, node):
|
||||
#NOTE(jbjohnso): For single node errors, raise exception to
|
||||
#trigger what a developer of that medium would expect
|
||||
# NOTE(jjohnson2): For single node errors, raise exception to
|
||||
# trigger what a developer of that medium would expect
|
||||
raise Exception(self.error)
|
||||
|
||||
|
||||
class ConfluentTargetTimeout(ConfluentNodeError):
|
||||
def __init__(self, node):
|
||||
def __init__(self, node, errstr='timeout'):
|
||||
self.node = node
|
||||
self.error = 'timeout'
|
||||
self.error = errstr
|
||||
|
||||
def strip_node(self, node):
|
||||
raise exc.TargetEndpointUnreachable
|
||||
raise exc.TargetEndpointUnreachable(self.error)
|
||||
|
||||
|
||||
class ConfluentTargetNotFound(ConfluentNodeError):
|
||||
@@ -185,6 +202,7 @@ class ConfluentTargetNotFound(ConfluentNodeError):
|
||||
def strip_node(self, node):
|
||||
raise exc.NotFoundException(self.error)
|
||||
|
||||
|
||||
class ConfluentTargetInvalidCredentials(ConfluentNodeError):
|
||||
def __init__(self, node):
|
||||
self.node = node
|
||||
@@ -248,7 +266,6 @@ class LinkRelation(ConfluentMessage):
|
||||
self.href = ''
|
||||
self.rel = ''
|
||||
|
||||
|
||||
def json(self):
|
||||
"""Provide json_hal style representation of the relation.
|
||||
|
||||
@@ -293,19 +310,68 @@ class ChildCollection(LinkRelation):
|
||||
extension)
|
||||
|
||||
|
||||
def get_input_message(path, operation, inputdata, nodes=None):
|
||||
def get_input_message(path, operation, inputdata, nodes=None, multinode=False):
|
||||
if path[0] == 'power' and path[1] == 'state' and operation != 'retrieve':
|
||||
return InputPowerMessage(path, nodes, inputdata)
|
||||
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
|
||||
return InputAttributes(path, inputdata, nodes)
|
||||
elif path == ['boot', 'nextdevice'] and operation != 'retrieve':
|
||||
return InputBootDevice(path, nodes, inputdata)
|
||||
elif path == [ 'identify' ] and operation != 'retrieve':
|
||||
elif (len(path) == 5 and
|
||||
path[:4] == ['configuration', 'management_controller', 'alerts',
|
||||
'destinations'] and operation != 'retrieve'):
|
||||
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 (path[:3] == ['configuration', 'management_controller', 'users'] and
|
||||
operation not in ('retrieve', 'delete') and path[-1] != 'all'):
|
||||
return InputCredential(path, inputdata, nodes)
|
||||
elif (path[:3] == ['configuration', 'management_controller', 'reset']
|
||||
and operation != 'retrieve'):
|
||||
return InputBMCReset(path, nodes, inputdata)
|
||||
elif (path[:3] == ['configuration', 'management_controller', 'identifier']
|
||||
and operation != 'retrieve'):
|
||||
return InputMCI(path, nodes, inputdata)
|
||||
elif (path[:4] == ['configuration', 'management_controller',
|
||||
'net_interfaces', 'management'] and operation != 'retrieve'):
|
||||
return InputNetworkConfiguration(path, nodes, inputdata)
|
||||
elif (path[:3] == ['configuration', 'management_controller', 'domain_name']
|
||||
and operation != 'retrieve'):
|
||||
return InputDomainName(path, nodes, inputdata)
|
||||
elif (path[:4] == ['configuration', 'management_controller', 'ntp',
|
||||
'enabled'] and operation != 'retrieve'):
|
||||
return InputNTPEnabled(path, nodes, inputdata)
|
||||
elif (path[:4] == ['configuration', 'management_controller', 'ntp',
|
||||
'servers'] and operation != 'retrieve' and len(path) == 5):
|
||||
return InputNTPServer(path, nodes, inputdata)
|
||||
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 = {}
|
||||
@@ -355,7 +421,7 @@ class InputAttributes(ConfluentMessage):
|
||||
# as above, use format() to see if string follows
|
||||
# expression, store value back in case of escapes
|
||||
tv = nodeattr[attr].format()
|
||||
nodeattr[attr] = tv
|
||||
nodeattr[attr] = str(tv)
|
||||
except (KeyError, IndexError):
|
||||
# an expression string will error if format() done
|
||||
# use that as cue to put it into config as an expr
|
||||
@@ -363,6 +429,80 @@ class InputAttributes(ConfluentMessage):
|
||||
return nodeattr
|
||||
|
||||
|
||||
class InputCredential(ConfluentMessage):
|
||||
valid_privilege_levels = set([
|
||||
'callback',
|
||||
'user',
|
||||
'operator',
|
||||
'administrator',
|
||||
'proprietary',
|
||||
'no_access',
|
||||
])
|
||||
|
||||
valid_enabled_values = set([
|
||||
'yes',
|
||||
'no'
|
||||
])
|
||||
|
||||
def __init__(self, path, inputdata, nodes=None):
|
||||
self.credentials = {}
|
||||
nestedmode = False
|
||||
if not inputdata:
|
||||
raise exc.InvalidArgumentException('no request data provided')
|
||||
|
||||
if len(path) == 4:
|
||||
inputdata['uid'] = path[-1]
|
||||
# if the operation is 'create' check if all fields are present
|
||||
elif ('uid' not in inputdata or 'privilege_level' not in inputdata or
|
||||
'username' not in inputdata or 'password' not in inputdata):
|
||||
raise exc.InvalidArgumentException('all fields are required')
|
||||
|
||||
if 'uid' not in inputdata:
|
||||
raise exc.InvalidArgumentException('uid is missing')
|
||||
if (isinstance(inputdata['uid'], str) and
|
||||
not inputdata['uid'].isdigit()):
|
||||
raise exc.InvalidArgumentException('uid must be a number')
|
||||
else:
|
||||
inputdata['uid'] = int(inputdata['uid'])
|
||||
if ('privilege_level' in inputdata and
|
||||
inputdata['privilege_level'] not in self.valid_privilege_levels):
|
||||
raise exc.InvalidArgumentException('privilege_level is not one of '
|
||||
+ ','.join(self.valid_privilege_levels))
|
||||
if 'username' in inputdata and len(inputdata['username']) > 16:
|
||||
raise exc.InvalidArgumentException(
|
||||
'name must be less than or = 16 chars')
|
||||
if 'password' in inputdata and len(inputdata['password']) > 20:
|
||||
raise exc.InvalidArgumentException('password has limit of 20 chars')
|
||||
|
||||
if ('enabled' in inputdata and
|
||||
inputdata['enabled'] not in self.valid_enabled_values):
|
||||
raise exc.InvalidArgumentException('valid values for enabled are '
|
||||
+ 'yes and no')
|
||||
|
||||
if nodes is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'This only supports per-node input')
|
||||
for node in nodes:
|
||||
self.credentials[node] = inputdata
|
||||
|
||||
def get_attributes(self, node):
|
||||
if node not in self.credentials:
|
||||
return {}
|
||||
credential = self.credentials[node]
|
||||
for attr in credentials:
|
||||
if type(credentials[attr]) in (str, unicode):
|
||||
try:
|
||||
# as above, use format() to see if string follows
|
||||
# expression, store value back in case of escapes
|
||||
tv = credential[attr].format()
|
||||
credential[attr] = tv
|
||||
except (KeyError, IndexError):
|
||||
# an expression string will error if format() done
|
||||
# use that as cue to put it into config as an expr
|
||||
credential[attr] = {'expression': credential[attr]}
|
||||
return credential
|
||||
|
||||
|
||||
class ConfluentInputMessage(ConfluentMessage):
|
||||
keyname = 'state'
|
||||
|
||||
@@ -372,7 +512,7 @@ class ConfluentInputMessage(ConfluentMessage):
|
||||
if not inputdata:
|
||||
raise exc.InvalidArgumentException('missing input data')
|
||||
if self.keyname not in inputdata:
|
||||
#assume we have nested information
|
||||
# assume we have nested information
|
||||
for key in nodes:
|
||||
if key not in inputdata:
|
||||
raise exc.InvalidArgumentException(key + ' not in request')
|
||||
@@ -388,7 +528,8 @@ class ConfluentInputMessage(ConfluentMessage):
|
||||
else: # we have a state argument not by node
|
||||
datum = inputdata
|
||||
if self.keyname not in datum:
|
||||
raise exc.InvalidArgumentException('missing {0} argument'.format(self.keyname))
|
||||
raise exc.InvalidArgumentException(
|
||||
'missing {0} argument'.format(self.keyname))
|
||||
elif datum[self.keyname] not in self.valid_values:
|
||||
raise exc.InvalidArgumentException(datum[self.keyname] +
|
||||
' is not one of ' +
|
||||
@@ -412,12 +553,125 @@ class InputPowerMessage(ConfluentInputMessage):
|
||||
'off',
|
||||
'reset',
|
||||
'boot',
|
||||
'diag',
|
||||
'shutdown',
|
||||
])
|
||||
|
||||
def powerstate(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
|
||||
class InputBMCReset(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
'reset',
|
||||
])
|
||||
|
||||
def state(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
|
||||
class InputMCI(ConfluentInputMessage):
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
self.inputbynode = {}
|
||||
self.stripped = False
|
||||
if not inputdata or 'identifier' not in inputdata:
|
||||
raise exc.InvalidArgumentException('missing input data')
|
||||
if len(inputdata['identifier']) > 64:
|
||||
raise exc.InvalidArgumentException(
|
||||
'identifier must be less than or = 64 chars')
|
||||
|
||||
if nodes is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'This only supports per-node input')
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = inputdata
|
||||
|
||||
def mci(self, node):
|
||||
return self.inputbynode[node]['identifier']
|
||||
|
||||
|
||||
class InputNetworkConfiguration(ConfluentInputMessage):
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
self.inputbynode = {}
|
||||
self.stripped = False
|
||||
if not inputdata:
|
||||
raise exc.InvalidArgumentException('missing input data')
|
||||
|
||||
if 'hw_addr' in inputdata:
|
||||
raise exc.InvalidArgumentException('hw_addr is a read only field')
|
||||
|
||||
if 'ipv4_address' not in inputdata:
|
||||
inputdata['ipv4_address'] = None
|
||||
|
||||
if 'ipv4_gateway' not in inputdata:
|
||||
inputdata['ipv4_gateway'] = None
|
||||
|
||||
if 'ipv4_configuration' in inputdata:
|
||||
if inputdata['ipv4_configuration'].lower() not in ['dhcp','static']:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Unrecognized ipv4_configuration')
|
||||
else:
|
||||
inputdata['ipv4_configuration'] = None
|
||||
if nodes is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'This only supports per-node input')
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = inputdata
|
||||
|
||||
def netconfig(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
|
||||
class InputDomainName(ConfluentInputMessage):
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
self.inputbynode = {}
|
||||
self.stripped = False
|
||||
if not inputdata or 'domain_name' not in inputdata:
|
||||
raise exc.InvalidArgumentException('missing input data')
|
||||
if len(inputdata['domain_name']) > 256:
|
||||
raise exc.InvalidArgumentException(
|
||||
'identifier must be less than or = 256 chars')
|
||||
|
||||
if nodes is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'This only supports per-node input')
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = inputdata['domain_name']
|
||||
|
||||
def domain_name(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
|
||||
class InputNTPServer(ConfluentInputMessage):
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
self.inputbynode = {}
|
||||
self.stripped = False
|
||||
if not inputdata or 'server' not in inputdata:
|
||||
raise exc.InvalidArgumentException('missing input data')
|
||||
if len(inputdata['server']) > 256:
|
||||
raise exc.InvalidArgumentException(
|
||||
'identifier must be less than or = 256 chars')
|
||||
|
||||
if nodes is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'This only supports per-node input')
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = str(inputdata['server'])
|
||||
|
||||
def ntp_server(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
|
||||
class InputNTPEnabled(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
'True',
|
||||
'False'
|
||||
])
|
||||
|
||||
def ntp_enabled(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
|
||||
class BootDevice(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'network',
|
||||
@@ -436,7 +690,7 @@ class BootDevice(ConfluentChoiceMessage):
|
||||
valid_paramset = {
|
||||
'bootmode': valid_bootmodes,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def __init__(self, node, device, bootmode='unspecified'):
|
||||
if device not in self.valid_values:
|
||||
@@ -448,7 +702,7 @@ class BootDevice(ConfluentChoiceMessage):
|
||||
self.kvpairs = {
|
||||
node: {
|
||||
'nextdevice': {'value': device},
|
||||
'bootmode': {'value': bootmode },
|
||||
'bootmode': {'value': bootmode},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,10 +768,164 @@ class PowerState(ConfluentChoiceMessage):
|
||||
'off',
|
||||
'reset',
|
||||
'boot',
|
||||
'shutdown',
|
||||
'diag',
|
||||
])
|
||||
keyname = 'state'
|
||||
|
||||
|
||||
class BMCReset(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'reset',
|
||||
])
|
||||
keyname = 'state'
|
||||
|
||||
|
||||
class NTPEnabled(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'True',
|
||||
'False',
|
||||
])
|
||||
|
||||
def __init__(self, node, enabled):
|
||||
self.stripped = False
|
||||
self.kvpairs = {
|
||||
node: {
|
||||
'state': {'value': str(enabled)},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class EventCollection(ConfluentMessage):
|
||||
"""A collection of events
|
||||
|
||||
This conveys a representation of an iterable of events. The following
|
||||
fields are supported:
|
||||
id (some data giving the class of event without the specific data of the
|
||||
event. For example, 'overtemp (1000 degrees celsius)' would have
|
||||
the same 'id' as 'overtemp (200 degrees celsius)
|
||||
component (specific name of the component this event references if any)
|
||||
component_type (A description of the sort of device component is)
|
||||
event (A text description of the event that occurred)
|
||||
severity (The text 'ok', 'warning', 'critical', 'failed', or 'unknown')
|
||||
timestamp (ISO 8601 compliant timestamp if available)
|
||||
"""
|
||||
readonly = True
|
||||
|
||||
def __init__(self, events=(), name=None):
|
||||
eventdata = []
|
||||
self.notnode = name is None
|
||||
for event in events:
|
||||
entry = {
|
||||
'id': event.get('id', None),
|
||||
'component': event.get('component', None),
|
||||
'component_type': event.get('component_type', None),
|
||||
'event': event.get('event', None),
|
||||
'severity': event['severity'],
|
||||
'timestamp': event.get('timestamp', None),
|
||||
'record_id': event.get('record_id', None),
|
||||
}
|
||||
if event['severity'] not in valid_health_values:
|
||||
raise exc.NotImplementedException(
|
||||
'Invalid severity - ' + repr(event['severity']))
|
||||
eventdata.append(entry)
|
||||
if self.notnode:
|
||||
self.kvpairs = {'events': eventdata}
|
||||
else:
|
||||
self.kvpairs = {name: {'events': eventdata}}
|
||||
|
||||
|
||||
class User(ConfluentMessage):
|
||||
def __init__(self, uid, username, privilege_level, name=None):
|
||||
self.desc = 'foo'
|
||||
self.stripped = False
|
||||
self.notnode = name is None
|
||||
kvpairs = {'username': {'value': username},
|
||||
'password': {'value': '', 'type': 'password'},
|
||||
'privilege_level': {'value': privilege_level},
|
||||
'enabled': {'value': ''}
|
||||
}
|
||||
if self.notnode:
|
||||
self.kvpairs = kvpairs
|
||||
else:
|
||||
self.kvpairs = {name: kvpairs}
|
||||
|
||||
|
||||
class UserCollection(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, users=(), name=None):
|
||||
self.notnode = name is None
|
||||
self.desc = 'list of users'
|
||||
userlist = []
|
||||
for user in users:
|
||||
entry = {
|
||||
'uid': user['uid'],
|
||||
'username': user['name'],
|
||||
'privilege_level': user['access']['privilege_level']
|
||||
}
|
||||
userlist.append(entry)
|
||||
if self.notnode:
|
||||
self.kvpairs = {'users': userlist}
|
||||
else:
|
||||
self.kvpairs = {name: {'users': userlist}}
|
||||
|
||||
|
||||
class AlertDestination(ConfluentMessage):
|
||||
def __init__(self, ip, acknowledge=False, acknowledge_timeout=None, retries=0, name=None):
|
||||
self.desc = 'foo'
|
||||
self.stripped = False
|
||||
self.notnode = name is None
|
||||
kvpairs = {'ip': {'value': ip},
|
||||
'acknowledge': {'value': acknowledge},
|
||||
'acknowledge_timeout': {'value': acknowledge_timeout},
|
||||
'retries': {'value': retries}}
|
||||
if self.notnode:
|
||||
self.kvpairs = kvpairs
|
||||
else:
|
||||
self.kvpairs = {name: kvpairs}
|
||||
|
||||
|
||||
class InputAlertDestination(ConfluentMessage):
|
||||
valid_alert_params = {
|
||||
'acknowledge': lambda x: False if type(x) in (unicode,str) and x.lower() == 'false' else bool(x),
|
||||
'acknowledge_timeout': lambda x: int(x) if x and x.isdigit() else None,
|
||||
'ip': lambda x: x,
|
||||
'retries': lambda x: int(x)
|
||||
}
|
||||
|
||||
def __init__(self, path, nodes, inputdata, multinode=False):
|
||||
self.alertcfg = {}
|
||||
if multinode: # keys are node names
|
||||
for node in inputdata:
|
||||
self.alertcfg[node] = inputdata[node]
|
||||
for key in inputdata[node]:
|
||||
if key not in self.valid_alert_params:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Unrecognized alert parameter ' + key)
|
||||
if isinstance(inputdata[node][key], dict):
|
||||
self.alertcfg[node][key] = \
|
||||
self.valid_alert_params[key](
|
||||
inputdata[node][key]['value'])
|
||||
else:
|
||||
self.alertcfg[node][key] = \
|
||||
self.valid_alert_params[key](inputdata[node][key])
|
||||
else:
|
||||
for key in inputdata:
|
||||
if key not in self.valid_alert_params:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Unrecognized alert parameter ' + key)
|
||||
if isinstance(inputdata[key], dict):
|
||||
inputdata[key] = self.valid_alert_params[key](
|
||||
inputdata[key]['value'])
|
||||
else:
|
||||
inputdata[key] = self.valid_alert_params[key](
|
||||
inputdata[key])
|
||||
for node in nodes:
|
||||
self.alertcfg[node] = inputdata
|
||||
|
||||
def alert_params_by_node(self, node):
|
||||
return self.alertcfg[node]
|
||||
|
||||
|
||||
class SensorReadings(ConfluentMessage):
|
||||
@@ -527,15 +935,19 @@ class SensorReadings(ConfluentMessage):
|
||||
readings = []
|
||||
self.notnode = name is None
|
||||
for sensor in sensors:
|
||||
sensordict = {'name': sensor['name']}
|
||||
if 'value' in sensor:
|
||||
sensordict['value'] = sensor['value']
|
||||
if 'units' in sensor:
|
||||
sensordict['units'] = sensor['units']
|
||||
if 'states' in sensor:
|
||||
sensordict['states'] = sensor['states']
|
||||
if 'health' in sensor:
|
||||
sensordict['health'] = sensor['health']
|
||||
sensordict = {'name': sensor.name}
|
||||
if hasattr(sensor, 'value'):
|
||||
sensordict['value'] = sensor.value
|
||||
if hasattr(sensor, 'units'):
|
||||
sensordict['units'] = sensor.units
|
||||
if hasattr(sensor, 'states'):
|
||||
sensordict['states'] = sensor.states
|
||||
if hasattr(sensor, 'state_ids'):
|
||||
sensordict['state_ids'] = sensor.state_ids
|
||||
if hasattr(sensor, 'health'):
|
||||
sensordict['health'] = sensor.health
|
||||
if hasattr(sensor, 'type'):
|
||||
sensordict['type'] = sensor.type
|
||||
readings.append(sensordict)
|
||||
if self.notnode:
|
||||
self.kvpairs = {'sensors': readings}
|
||||
@@ -543,14 +955,65 @@ class SensorReadings(ConfluentMessage):
|
||||
self.kvpairs = {name: {'sensors': readings}}
|
||||
|
||||
|
||||
class Firmware(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, data, name):
|
||||
self.notnode = name is None
|
||||
self.desc = 'Firmware information'
|
||||
if self.notnode:
|
||||
self.kvpairs = {'firmware': data}
|
||||
else:
|
||||
self.kvpairs = {name: {'firmware': data}}
|
||||
|
||||
|
||||
class KeyValueData(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, kvdata, name=None):
|
||||
self.notnode = name is None
|
||||
if self.notnode:
|
||||
self.kvpairs = kvdata
|
||||
else:
|
||||
self.kvpairs = {name: kvdata}
|
||||
|
||||
|
||||
class LEDStatus(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, data, name):
|
||||
self.notnode = name is None
|
||||
self.desc = 'led status'
|
||||
|
||||
if self.notnode:
|
||||
self.kvpairs = {'leds':data}
|
||||
else:
|
||||
self.kvpairs = {name: {'leds':data}}
|
||||
|
||||
|
||||
class NetworkConfiguration(ConfluentMessage):
|
||||
desc = 'Network configuration'
|
||||
|
||||
def __init__(self, name=None, ipv4addr=None, ipv4gateway=None,
|
||||
ipv4cfgmethod=None, hwaddr=None):
|
||||
self.notnode = name is None
|
||||
self.stripped = False
|
||||
|
||||
kvpairs = {
|
||||
'ipv4_address': {'value': ipv4addr},
|
||||
'ipv4_gateway': {'value': ipv4gateway},
|
||||
'ipv4_configuration': {'value': ipv4cfgmethod},
|
||||
'hw_addr': {'value': hwaddr},
|
||||
}
|
||||
if self.notnode:
|
||||
self.kvpairs = kvpairs
|
||||
else:
|
||||
self.kvpairs = {name: kvpairs}
|
||||
|
||||
|
||||
class HealthSummary(ConfluentMessage):
|
||||
readonly = True
|
||||
valid_values = set([
|
||||
'ok',
|
||||
'warning',
|
||||
'critical',
|
||||
'failed',
|
||||
])
|
||||
valid_values = valid_health_values
|
||||
|
||||
def __init__(self, health, name=None):
|
||||
self.stripped = False
|
||||
@@ -588,7 +1051,76 @@ class ListAttributes(ConfluentMessage):
|
||||
if self.notnode:
|
||||
self.kvpairs = kv
|
||||
else:
|
||||
self.kvpairs = {node: kv}
|
||||
self.kvpairs = {name: kv}
|
||||
|
||||
|
||||
class MCI(ConfluentMessage):
|
||||
def __init__(self, name=None, mci=None):
|
||||
self.notnode = name is None
|
||||
self.desc = 'BMC identifier'
|
||||
|
||||
kv = {'identifier': {'value': mci}}
|
||||
if self.notnode:
|
||||
self.kvpairs = kv
|
||||
else:
|
||||
self.kvpairs = {name: kv}
|
||||
|
||||
|
||||
class DomainName(ConfluentMessage):
|
||||
def __init__(self, name=None, dn=None):
|
||||
self.notnode = name is None
|
||||
self.desc = 'BMC domain name'
|
||||
|
||||
kv = {'domain_name': {'value': dn}}
|
||||
if self.notnode:
|
||||
self.kvpairs = kv
|
||||
else:
|
||||
self.kvpairs = {name: kv}
|
||||
|
||||
|
||||
class NTPServers(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, name=None, servers=None):
|
||||
self.notnode = name is None
|
||||
self.desc = 'NTP Server'
|
||||
|
||||
kv = []
|
||||
for idx in range(0, len(servers)):
|
||||
kv.append({str(idx+1): servers[idx]})
|
||||
if self.notnode:
|
||||
self.kvpairs = {'ntp_servers': kv}
|
||||
else:
|
||||
self.kvpairs = {name: {'ntp_servers': kv}}
|
||||
|
||||
|
||||
class NTPServer(ConfluentMessage):
|
||||
def __init__(self, name=None, server=None):
|
||||
self.notnode = name is None
|
||||
self.desc = 'NTP Server'
|
||||
|
||||
kv = {
|
||||
'server': {'value': server},
|
||||
}
|
||||
if self.notnode:
|
||||
self.kvpairs = kv
|
||||
else:
|
||||
self.kvpairs = {name: kv}
|
||||
|
||||
|
||||
class License(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, name=None, kvm=None):
|
||||
self.notnode = name is None
|
||||
self.desc = 'License'
|
||||
|
||||
kv = []
|
||||
kv.append({'kvm_availability': str(kvm)})
|
||||
if self.notnode:
|
||||
self.kvpairs = {'License': kv}
|
||||
else:
|
||||
self.kvpairs = {name: {'License': kv}}
|
||||
|
||||
|
||||
class CryptedAttributes(Attributes):
|
||||
@@ -611,5 +1143,5 @@ class CryptedAttributes(Attributes):
|
||||
self.kvpairs = nkv
|
||||
else:
|
||||
self.kvpairs = {
|
||||
node: nkv
|
||||
name: nkv
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
# the middle of strings and use of @ for anything is not in their syntax
|
||||
|
||||
|
||||
import copy
|
||||
import itertools
|
||||
import pyparsing as pp
|
||||
import re
|
||||
|
||||
# construct custom grammar with pyparsing
|
||||
_nodeword = pp.Word(pp.alphanums + '~^$/=-:.*+!')
|
||||
_nodeword = pp.Word(pp.alphanums + '~^$/=-_:.*+!')
|
||||
_nodebracket = pp.QuotedString(quoteChar='[', endQuoteChar=']',
|
||||
unquoteResults=False)
|
||||
_nodeatom = pp.Group(pp.OneOrMore(_nodeword | _nodebracket))
|
||||
@@ -160,7 +161,7 @@ class NodeRange(object):
|
||||
return set([entname])
|
||||
if self.cfm.is_nodegroup(entname):
|
||||
grpcfg = self.cfm.get_nodegroup_attributes(entname)
|
||||
nodes = grpcfg['nodes']
|
||||
nodes = copy.copy(grpcfg['nodes'])
|
||||
if 'noderange' in grpcfg and grpcfg['noderange']:
|
||||
nodes |= NodeRange(
|
||||
grpcfg['noderange']['value'], self.cfm).nodes
|
||||
@@ -185,7 +186,7 @@ class NodeRange(object):
|
||||
return set([element])
|
||||
if self.cfm.is_nodegroup(element):
|
||||
grpcfg = self.cfm.get_nodegroup_attributes(element)
|
||||
nodes = grpcfg['nodes']
|
||||
nodes = copy.copy(grpcfg['nodes'])
|
||||
if 'noderange' in grpcfg and grpcfg['noderange']:
|
||||
nodes |= NodeRange(
|
||||
grpcfg['noderange']['value'], self.cfm).nodes
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import atexit
|
||||
import confluent.exceptions as exc
|
||||
import confluent.interface.console as conapi
|
||||
import confluent.messages as msg
|
||||
@@ -30,6 +31,12 @@ import socket
|
||||
console.session.select = eventlet.green.select
|
||||
console.session.threading = eventlet.green.threading
|
||||
|
||||
|
||||
def exithandler():
|
||||
console.session.iothread.join()
|
||||
|
||||
atexit.register(exithandler)
|
||||
|
||||
_ipmiworkers = greenpool.GreenPool()
|
||||
|
||||
_ipmithread = None
|
||||
@@ -41,9 +48,43 @@ 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(' ', '_')
|
||||
return name.lower().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):
|
||||
@@ -223,22 +264,22 @@ def perform_request(operator, node, element,
|
||||
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 Exception as e:
|
||||
results.put(msg.ConfluentNodeError(
|
||||
node, 'IPMI PluginException (see stderr log): ' + str(e)))
|
||||
raise
|
||||
finally:
|
||||
results.put('Done')
|
||||
|
||||
persistent_ipmicmds = {}
|
||||
|
||||
|
||||
def _dict_sensor(pygreading):
|
||||
retdict = {'name': pygreading.name, 'value': pygreading.value,
|
||||
'states': pygreading.states, 'units': pygreading.units,
|
||||
'health': _str_health(pygreading.health)}
|
||||
return retdict
|
||||
|
||||
|
||||
class IpmiHandler(object):
|
||||
def __init__(self, operation, node, element, cfd, inputdata, cfg, output):
|
||||
self.sensormap = {}
|
||||
self.invmap = {}
|
||||
self.output = output
|
||||
self.sensorcategory = None
|
||||
self.broken = False
|
||||
@@ -285,12 +326,17 @@ class IpmiHandler(object):
|
||||
self._logevt.wait()
|
||||
self._logevt = None
|
||||
if self.broken:
|
||||
if self.error == 'timeout':
|
||||
self.output.put(msg.ConfluentTargetTimeout(self.node))
|
||||
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 ('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']:
|
||||
@@ -303,6 +349,190 @@ class IpmiHandler(object):
|
||||
self.identify()
|
||||
elif self.element[0] == 'sensors':
|
||||
self.handle_sensors()
|
||||
elif self.element[0] == 'configuration':
|
||||
self.handle_configuration()
|
||||
elif self.element[0] == 'inventory':
|
||||
self.handle_inventory()
|
||||
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()
|
||||
else:
|
||||
raise Exception('Not Implemented')
|
||||
|
||||
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', 'domain_name']:
|
||||
return self.handle_domain_name()
|
||||
elif self.element[1:3] == ['management_controller', 'ntp']:
|
||||
return self.handle_ntp()
|
||||
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 xrange(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()
|
||||
self.output.put(msg.NetworkConfiguration(
|
||||
self.node, ipv4addr=lancfg['ipv4_address'],
|
||||
ipv4gateway=lancfg['ipv4_gateway'],
|
||||
ipv4cfgmethod=lancfg['ipv4_configuration'],
|
||||
hwaddr=lancfg['mac_address']
|
||||
))
|
||||
elif self.op == 'update':
|
||||
config = self.inputdata.netconfig(self.node)
|
||||
self.ipmicmd.set_net_configuration(
|
||||
ipv4_address=config['ipv4_address'],
|
||||
ipv4_configuration=config['ipv4_configuration'],
|
||||
ipv4_gateway=config['ipv4_gateway'])
|
||||
|
||||
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'],
|
||||
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 'privilege_level' in user:
|
||||
self.ipmicmd.set_user_access(uid=user['uid'],
|
||||
privilege_level=user['privilege_level'])
|
||||
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 '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:
|
||||
@@ -324,7 +554,9 @@ class IpmiHandler(object):
|
||||
if ie.ipmicode == 203:
|
||||
continue
|
||||
raise
|
||||
readings.append(_dict_sensor(reading))
|
||||
if hasattr(reading, 'health'):
|
||||
reading.health = _str_health(reading.health)
|
||||
readings.append(reading)
|
||||
self.output.put(msg.SensorReadings(readings, name=self.node))
|
||||
else:
|
||||
self.make_sensor_map()
|
||||
@@ -335,21 +567,103 @@ class IpmiHandler(object):
|
||||
return
|
||||
reading = self.ipmicmd.get_sensor_reading(
|
||||
self.sensormap[sensorname])
|
||||
if hasattr(reading, 'health'):
|
||||
reading.health = _str_health(reading.health)
|
||||
self.output.put(
|
||||
msg.SensorReadings([_dict_sensor(reading)],
|
||||
msg.SensorReadings([reading],
|
||||
name=self.node))
|
||||
except pygexc.IpmiException:
|
||||
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 = []
|
||||
for id, data in self.ipmicmd.get_firmware():
|
||||
if component == 'all' or component == simplify_name(id):
|
||||
items.append({id: data})
|
||||
self.output.put(msg.Firmware(items, self.node))
|
||||
|
||||
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):
|
||||
invitems = []
|
||||
if component == 'all':
|
||||
for invdata in self.ipmicmd.get_inventory():
|
||||
if invdata[1] is None:
|
||||
newinf = {'present': False, 'information': None}
|
||||
else:
|
||||
sanitize_invdata(invdata[1])
|
||||
newinf = {'present': True, 'information': invdata[1]}
|
||||
newinf['name'] = invdata[0]
|
||||
invitems.append(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}
|
||||
else:
|
||||
sanitize_invdata(invdata)
|
||||
newinf = {'present': True, 'information': invdata}
|
||||
newinf['name'] = compname
|
||||
invitems.append(newinf)
|
||||
newinvdata = {'inventory': invitems}
|
||||
self.output.put(msg.KeyValueData(newinvdata, self.node))
|
||||
|
||||
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 len(self.element) == 3: # list sensors per category
|
||||
# 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):
|
||||
@@ -382,7 +696,9 @@ class IpmiHandler(object):
|
||||
if 'badreadings' in response:
|
||||
badsensors = []
|
||||
for reading in response['badreadings']:
|
||||
badsensors.append(_dict_sensor(reading))
|
||||
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')
|
||||
@@ -401,6 +717,7 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.BootDevice(node=self.node,
|
||||
device=bootdev['bootdev'],
|
||||
bootmode=bootmode))
|
||||
return
|
||||
elif 'update' == self.op:
|
||||
bootdev = self.inputdata.bootdevice(self.node)
|
||||
douefi = False
|
||||
@@ -418,24 +735,103 @@ class IpmiHandler(object):
|
||||
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)
|
||||
self.ipmicmd.set_power(powerstate, wait=30)
|
||||
power = self.ipmicmd.get_power()
|
||||
self.output.put(msg.PowerState(node=self.node,
|
||||
state=power['powerstate']))
|
||||
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_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_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()
|
||||
self.output.put(msg.NTPServer(self.node, servers[idx]))
|
||||
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(server[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 _str_health(health):
|
||||
if health == 'unknown':
|
||||
return health
|
||||
if pygconstants.Health.Failed & health:
|
||||
health = 'failed'
|
||||
elif pygconstants.Health.Critical & health:
|
||||
@@ -471,4 +867,9 @@ 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)
|
||||
|
||||
def delete(nodes, element, configmanager, inputdata):
|
||||
initthread()
|
||||
return perform_requests(
|
||||
'delete', nodes, element, configmanager, inputdata)
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
|
||||
# This is the socket api layer.
|
||||
# It implement unix and tls sockets
|
||||
#
|
||||
#
|
||||
|
||||
import atexit
|
||||
import errno
|
||||
import os
|
||||
import pwd
|
||||
import stat
|
||||
@@ -62,15 +63,23 @@ class ClientConsole(object):
|
||||
if not self.xmit:
|
||||
self.pendingdata.append(data)
|
||||
return
|
||||
tlvdata.send(self.client, data)
|
||||
send_data(self.client, data)
|
||||
|
||||
def startsending(self):
|
||||
self.xmit = True
|
||||
for datum in self.pendingdata:
|
||||
tlvdata.send(self.client, datum)
|
||||
send_data(self.client, datum)
|
||||
self.pendingdata = None
|
||||
|
||||
|
||||
def send_data(connection, data):
|
||||
try:
|
||||
tlvdata.send(connection, data)
|
||||
except IOError as ie:
|
||||
if ie.errno != errno.EPIPE:
|
||||
raise
|
||||
|
||||
|
||||
def sessionhdl(connection, authname, skipauth=False):
|
||||
# For now, trying to test the console stuff, so let's just do n4.
|
||||
authenticated = False
|
||||
@@ -84,9 +93,9 @@ def sessionhdl(connection, authname, skipauth=False):
|
||||
if authdata is not None:
|
||||
cfm = authdata[1]
|
||||
authenticated = True
|
||||
tlvdata.send(connection, "Confluent -- v0 --")
|
||||
send_data(connection, "Confluent -- v0 --")
|
||||
while not authenticated: # prompt for name and passphrase
|
||||
tlvdata.send(connection, {'authpassed': 0})
|
||||
send_data(connection, {'authpassed': 0})
|
||||
response = tlvdata.recv(connection)
|
||||
authname = response['username']
|
||||
passphrase = response['password']
|
||||
@@ -101,44 +110,49 @@ def sessionhdl(connection, authname, skipauth=False):
|
||||
else:
|
||||
authenticated = True
|
||||
cfm = authdata[1]
|
||||
tlvdata.send(connection, {'authpassed': 1})
|
||||
send_data(connection, {'authpassed': 1})
|
||||
request = tlvdata.recv(connection)
|
||||
while request is not None:
|
||||
try:
|
||||
process_request(
|
||||
connection, request, cfm, authdata, authname, skipauth)
|
||||
except exc.ForbiddenRequest:
|
||||
tlvdata.send(connection, {'errorcode': 403,
|
||||
send_data(connection, {'errorcode': 403,
|
||||
'error': 'Forbidden'})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except exc.TargetEndpointBadCredentials:
|
||||
tlvdata.send(connection, {'errorcode': 502,
|
||||
send_data(connection, {'errorcode': 502,
|
||||
'error': 'Bad Credentials'})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
except exc.TargetEndpointUnreachable:
|
||||
tlvdata.send(connection, {'errorcode': 504,
|
||||
'error': 'Unreachable Target'})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except exc.TargetEndpointUnreachable as tu:
|
||||
send_data(connection, {'errorcode': 504,
|
||||
'error': 'Unreachable Target - ' + str(
|
||||
tu)})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except exc.NotImplementedException:
|
||||
tlvdata.send(connection, {'errorcode': 501,
|
||||
send_data(connection, {'errorcode': 501,
|
||||
'error': 'Not Implemented'})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except exc.NotFoundException as nfe:
|
||||
tlvdata.send(connection, {'errorcode': 404,
|
||||
send_data(connection, {'errorcode': 404,
|
||||
'error': str(nfe)})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except exc.InvalidArgumentException as iae:
|
||||
tlvdata.send(connection, {'errorcode': 400,
|
||||
send_data(connection, {'errorcode': 400,
|
||||
'error': 'Bad Request - ' + str(iae)})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except exc.LockedCredentials as lockedcred:
|
||||
send_data(connection, {'errorcode': 500,
|
||||
'error': 'Locked Credential Store'})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
except SystemExit:
|
||||
sys.exit(0)
|
||||
except:
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
tlvdata.send(connection, {'errorcode': 500,
|
||||
send_data(connection, {'errorcode': 500,
|
||||
'error': 'Unexpected error'})
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
request = tlvdata.recv(connection)
|
||||
|
||||
|
||||
@@ -146,8 +160,8 @@ def send_response(responses, connection):
|
||||
if responses is None:
|
||||
return
|
||||
for rsp in responses:
|
||||
tlvdata.send(connection, rsp.raw())
|
||||
tlvdata.send(connection, {'_requestdone': 1})
|
||||
send_data(connection, rsp.raw())
|
||||
send_data(connection, {'_requestdone': 1})
|
||||
|
||||
|
||||
def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
@@ -187,11 +201,11 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
datacallback=ccons.sendall, skipreplay=skipreplay)
|
||||
if consession is None:
|
||||
raise Exception("TODO")
|
||||
tlvdata.send(connection, {'started': 1})
|
||||
send_data(connection, {'started': 1})
|
||||
ccons.startsending()
|
||||
bufferage = consession.get_buffer_age()
|
||||
if bufferage is not False:
|
||||
tlvdata.send(connection, {'bufferage': bufferage})
|
||||
send_data(connection, {'bufferage': bufferage})
|
||||
while consession is not None:
|
||||
data = tlvdata.recv(connection)
|
||||
if type(data) == dict:
|
||||
@@ -215,22 +229,22 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
else:
|
||||
hdlr = pluginapi.handle_path(path, operation, cfm, params)
|
||||
except exc.NotFoundException as e:
|
||||
tlvdata.send(connection, {"errorcode": 404,
|
||||
send_data(connection, {"errorcode": 404,
|
||||
"error": "Target not found - " + str(e)})
|
||||
tlvdata.send(connection, {"_requestdone": 1})
|
||||
send_data(connection, {"_requestdone": 1})
|
||||
except exc.InvalidArgumentException as e:
|
||||
tlvdata.send(connection, {"errorcode": 400,
|
||||
send_data(connection, {"errorcode": 400,
|
||||
"error": "Bad Request - " + str(e)})
|
||||
tlvdata.send(connection, {"_requestdone": 1})
|
||||
send_data(connection, {"_requestdone": 1})
|
||||
send_response(hdlr, connection)
|
||||
return
|
||||
|
||||
|
||||
def _tlshandler():
|
||||
def _tlshandler(bind_host, bind_port):
|
||||
plainsocket = socket.socket(socket.AF_INET6)
|
||||
plainsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
plainsocket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
plainsocket.bind(('::', 13001, 0, 0))
|
||||
plainsocket.bind((bind_host, bind_port, 0, 0))
|
||||
plainsocket.listen(5)
|
||||
while (1): # TODO: exithook
|
||||
cnn, addr = plainsocket.accept()
|
||||
@@ -257,6 +271,8 @@ def _unixdomainhandler():
|
||||
os.remove("/var/run/confluent/api.sock")
|
||||
except OSError: # if file does not exist, no big deal
|
||||
pass
|
||||
if not os.path.isdir("/var/run/confluent"):
|
||||
os.makedirs('/var/run/confluent', 0755)
|
||||
unixsocket.bind("/var/run/confluent/api.sock")
|
||||
os.chmod("/var/run/confluent/api.sock",
|
||||
stat.S_IWOTH | stat.S_IROTH | stat.S_IWGRP |
|
||||
@@ -289,14 +305,17 @@ def _unixdomainhandler():
|
||||
|
||||
|
||||
class SockApi(object):
|
||||
def __init__(self):
|
||||
def __init__(self, bindhost=None, bindport=None):
|
||||
self.tlsserver = None
|
||||
self.unixdomainserver = None
|
||||
self.bind_host = bindhost or '::'
|
||||
self.bind_port = bindport or 13001
|
||||
|
||||
def start(self):
|
||||
global auditlog
|
||||
global tracelog
|
||||
tracelog = log.Logger('trace')
|
||||
auditlog = log.Logger('audit')
|
||||
self.tlsserver = eventlet.spawn(_tlshandler)
|
||||
self.tlsserver = eventlet.spawn(
|
||||
_tlshandler, self.bind_host, self.bind_port)
|
||||
self.unixdomainserver = eventlet.spawn(_unixdomainhandler)
|
||||
|
||||
@@ -12,7 +12,7 @@ Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
Requires: pyghmi, eventlet, greenlet, pycrypto >= 2.6.1, confluent_client, PyPAM, pyparsing
|
||||
Requires: pyghmi, eventlet, greenlet, pycrypto >= 2.6.1, confluent_client, pyparsing
|
||||
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
|
||||
Url: http://xcat.sf.net/
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['c:/Python27/Scripts/confluentsrv.py'],
|
||||
pathex=[],
|
||||
hiddenimports=['pyghmi.constants', 'pyghmi.exceptions', 'pyghmi.ipmi.console', 'pyghmi.ipmi.private.constants', 'pyghmi.ipmi.private', 'pyghmi.ipmi.private.session', 'pyghmi.ipmi.command', 'pyghmi.ipmi.events', 'pyghmi.ipmi.fru', 'pyghmi.ipmi.private.spd', 'pyghmi.ipmi.oem.lookup', 'pyghmi.ipmi.oem.generic', 'pyghmi.ipmi.oem.lenovo', 'pyghmi.ipmi.private.util', 'pyghmi.ipmi.sdr'],
|
||||
hookspath=None,
|
||||
runtime_hooks=None,
|
||||
excludes=None,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
name='confluentsrv.exe',
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=True,
|
||||
console=True )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
Tree('confluent/plugins', prefix='confluent/plugins'),
|
||||
strip=None,
|
||||
upx=True,
|
||||
name='confluentsrv')
|
||||
@@ -1,3 +1,8 @@
|
||||
cd `dirname $0`
|
||||
VERSION=`cat VERSION`
|
||||
VERSION=`git tag -l|tail -n 1`
|
||||
NUMCOMMITS=`git rev-list $VERSION..HEAD|wc -l`
|
||||
if [ "$NUMCOMMITS" -ne 0 ]; then
|
||||
VERSION=$VERSION.dev$NUMCOMMITS.g`git rev-parse --short HEAD`
|
||||
fi
|
||||
echo $VERSION > VERSION
|
||||
sed -e "s/#VERSION#/$VERSION/" setup.py.tmpl > setup.py
|
||||
|
||||
@@ -15,6 +15,7 @@ setup(
|
||||
'pyghmi>=0.6.5'],
|
||||
scripts=['bin/confluent'],
|
||||
data_files=[('/etc/init.d', ['sysvinit/confluent']),
|
||||
('/usr/lib/systemd/system', ['systemd/confluent.service']),
|
||||
('/opt/confluent/lib/python/confluent/plugins/console/', [])],
|
||||
|
||||
)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# IBM(c) 2015 Apache 2.0
|
||||
[Unit]
|
||||
Description=Confluent hardware manager
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile=/var/run/confluent/pid
|
||||
ExecStart=/opt/confluent/bin/confluent
|
||||
ExecStop=/opt/confluent/bin/confetty shutdown /
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -11,35 +11,56 @@
|
||||
if [ -f /etc/init.d/functions ]; then
|
||||
. /etc/init.d/functions
|
||||
LOG_SUCCESS=success
|
||||
LOG_FAILURE=failure
|
||||
elif [ -f /lib/lsb/init-functions ]; then
|
||||
. /lib/lsb/init-functions
|
||||
LOG_SUCCESS=log_success_msg
|
||||
LOG_FAILURE=log_failure_msg
|
||||
else
|
||||
echo "Unknown platform"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
confluent=/opt/confluent/bin/confluent
|
||||
confetty=/opt/confluent/bin/confetty
|
||||
|
||||
stop() {
|
||||
echo -n 'Stopping Confluent: '
|
||||
if [ -S /var/run/confluent/api.sock ]; then
|
||||
$confetty shutdown /
|
||||
fi
|
||||
$LOG_SUCCESS
|
||||
echo
|
||||
return
|
||||
}
|
||||
|
||||
start() {
|
||||
echo -n 'Starting Confluent: '
|
||||
$confluent
|
||||
if [ $? -eq 0 ]; then
|
||||
$LOG_SUCCESS
|
||||
echo
|
||||
return 0
|
||||
else
|
||||
$LOG_FAILURE
|
||||
echo
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
case $1 in
|
||||
restart)
|
||||
if [ -S /var/run/confluent/api.sock ]; then
|
||||
echo -n 'Stopping Confluent '
|
||||
/opt/confluent/bin/confetty shutdown /
|
||||
fi
|
||||
echo -n 'Starting Confluent '
|
||||
/opt/confluent/bin/confluent
|
||||
$LOG_SUCCESS
|
||||
stop
|
||||
start
|
||||
;;
|
||||
start)
|
||||
echo -n 'Starting Confluent '
|
||||
/opt/confluent/bin/confluent
|
||||
$LOG_SUCCESS
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
echo -n 'Stopping Confluent '
|
||||
if [ -S /var/run/confluent/api.sock ]; then
|
||||
/opt/confluent/bin/confetty shutdown /
|
||||
fi
|
||||
$LOG_SUCCESS
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status -p /var/run/confluent/pid $confluent
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
Reference in New Issue
Block a user