mirror of
https://github.com/xcat2/confluent.git
synced 2025-01-24 00:23:53 +00:00
Merge remote-tracking branch 'refs/remotes/xcat2/master'
This commit is contained in:
commit
ec90ef889b
@ -235,7 +235,11 @@ def rcompleter(text, state):
|
||||
|
||||
|
||||
def parse_command(command):
|
||||
args = shlex.split(command, posix=True)
|
||||
try:
|
||||
args = shlex.split(command, posix=True)
|
||||
except ValueError as ve:
|
||||
print('Error: ' + ve.message)
|
||||
return []
|
||||
return args
|
||||
|
||||
|
||||
|
165
confluent_client/bin/nodeattrib
Normal file
165
confluent_client/bin/nodeattrib
Normal file
@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'alin37'
|
||||
|
||||
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
|
||||
|
||||
def attrrequested(attr, attrlist, seenattributes):
|
||||
for candidate in attrlist:
|
||||
truename = candidate
|
||||
if candidate.startswith('hm'):
|
||||
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
|
||||
if candidate == attr:
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
elif '.' not in candidate and attr.startswith(candidate + '.'):
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
return False
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [options] noderange [list of attributes] \
|
||||
\n %prog [options] noderange attribute1=value1,attribute2=value,...
|
||||
\n ''')
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear variables')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
showtype = 'current'
|
||||
requestargs=None
|
||||
try:
|
||||
noderange = args[0]
|
||||
nodelist = '/noderange/{0}/nodes/'.format(noderange)
|
||||
except IndexError:
|
||||
nodelist = '/nodes/'
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
|
||||
#Sets attributes
|
||||
if len(args) > 1:
|
||||
#clears attribute
|
||||
if options.clear:
|
||||
targpath = '/noderange/{0}/attributes/all'.format(noderange)
|
||||
keydata = {}
|
||||
for attrib in args[1:]:
|
||||
keydata[attrib] = None
|
||||
for res in session.update(targpath, keydata):
|
||||
if 'error' in res:
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
sys.stderr.write('Error: ' + res['error'] + '\n')
|
||||
sys.exit(exitcode)
|
||||
else:
|
||||
if args[1] == 'all':
|
||||
showtype = 'all'
|
||||
elif args[1] == 'current':
|
||||
showtype = 'current'
|
||||
elif "=" in args[1]:
|
||||
try:
|
||||
if len(args[1:]) > 1:
|
||||
for val in args[1:]:
|
||||
val = val.split('=')
|
||||
exitcode=session.simple_noderange_command(noderange, 'attributes/all'.format(noderange), val[1], val[0])
|
||||
else:
|
||||
val=args[1].split('=')
|
||||
exitcode=session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),val[1],val[0])
|
||||
except:
|
||||
sys.stderr.write('Error: {0} not a valid expression\n'.format(str (args[1:])))
|
||||
exitcode = 1
|
||||
sys.exit(exitcode)
|
||||
else:
|
||||
requestargs = args[1:]
|
||||
|
||||
# Lists all attributes
|
||||
if len(args) > 0:
|
||||
seenattributes = set([])
|
||||
for res in session.read('/noderange/{0}/attributes/{1}'.format(noderange,showtype)):
|
||||
if 'error' in res:
|
||||
print "found error"
|
||||
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 requestargs is None or attrrequested(attr, args[1:], seenattributes):
|
||||
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)
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(node, attr,
|
||||
currattr['broken'])
|
||||
elif isinstance(currattr, list) or isinstance(currattr, tuple):
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ', '.join(map(str, currattr)))
|
||||
elif isinstance(currattr, dict):
|
||||
dictout = []
|
||||
for k,v in currattr.items:
|
||||
dictout.append("{0}={1}".format(k,v))
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ', '.join(map(str, dictout)))
|
||||
else:
|
||||
print ("CODE ERROR" + repr(attr))
|
||||
|
||||
if options.blame or 'broken' in currattr:
|
||||
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:
|
||||
if requestargs:
|
||||
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)
|
0
confluent_client/bin/nodeboot.py → confluent_client/bin/nodeboot
Normal file → Executable file
0
confluent_client/bin/nodeboot.py → confluent_client/bin/nodeboot
Normal file → Executable file
0
confluent_client/bin/nodeconsole
Normal file → Executable file
0
confluent_client/bin/nodeconsole
Normal file → Executable file
13
confluent_client/bin/nodeeventlog
Normal file → Executable file
13
confluent_client/bin/nodeeventlog
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2016 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -16,6 +16,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from datetime import datetime as dt
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -26,13 +27,13 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange (clear)")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange> [clear]\n'.format(
|
||||
sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
deletemode = False
|
||||
|
11
confluent_client/bin/nodefirmware
Normal file → Executable file
11
confluent_client/bin/nodefirmware
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016 Lenovo
|
||||
# Copyright 2016-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
@ -57,12 +58,12 @@ def printfirm(node, prefix, data):
|
||||
print('{0}: {1}: {2}'.format(node, prefix, version))
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange>\n'.format(
|
||||
sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
try:
|
||||
session = client.Command()
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -16,6 +16,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -28,10 +29,12 @@ import confluent.client as client
|
||||
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
sys.stderr.write('Usage: {0} <noderange>\n'.format(sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -25,10 +26,12 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off]")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
sys.stderr.write('Usage: {0} <noderange> [on|off]\n'.format(sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
identifystate = None
|
||||
|
9
confluent_client/bin/nodeinventory
Normal file → Executable file
9
confluent_client/bin/nodeinventory
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016 Lenovo
|
||||
# Copyright 2016-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
@ -69,12 +70,12 @@ def printerror(res, node=None):
|
||||
exitcode = 1
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange>\n'.format(
|
||||
sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
try:
|
||||
session = client.Command()
|
||||
|
20
confluent_client/bin/nodelist
Normal file → Executable file
20
confluent_client/bin/nodelist
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -42,7 +42,7 @@ def attrrequested(attr, attrlist, seenattributes):
|
||||
return True
|
||||
return False
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange [list of attributes")
|
||||
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()
|
||||
@ -76,7 +76,21 @@ if len(args) > 1:
|
||||
attrout = '{0}: {1}: ********'.format(node, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
if options.blame:
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(node, attr,
|
||||
currattr['broken'])
|
||||
elif isinstance(currattr, list) or isinstance(currattr, tuple):
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ', '.join(map(str, currattr)))
|
||||
elif isinstance(currattr, dict):
|
||||
dictout = []
|
||||
for k, v in currattr.items:
|
||||
dictout.append("{0}={1}".format(k, v))
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ', '.join(map(str, dictout)))
|
||||
else:
|
||||
print ("CODE ERROR" + repr(attr))
|
||||
|
||||
if options.blame or 'broken' in currattr:
|
||||
blamedata = []
|
||||
if 'inheritedfrom' in currattr:
|
||||
blamedata.append('inherited from group {0}'.format(
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -25,13 +26,14 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange "
|
||||
"([status|on|off|shutdown|boot|reset])")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange> ([status|on|off|shutdown|boot|reset]\n'.format(
|
||||
sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
setstate = None
|
||||
@ -43,5 +45,6 @@ if len(sys.argv) > 2:
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
session.add_precede_key('oldstate')
|
||||
sys.exit(
|
||||
session.simple_noderange_command(noderange, '/power/state', setstate))
|
86
confluent_client/bin/noderun
Executable file
86
confluent_client/bin/noderun
Executable file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import shlex
|
||||
import subprocess
|
||||
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 node commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
nodeforpopen = {}
|
||||
popens = []
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
ex = exp['databynode']
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
popens.append(nopen)
|
||||
nodeforpopen[nopen] = node
|
||||
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
exitcode = 0
|
||||
for pop in popens:
|
||||
node = nodeforpopen[pop]
|
||||
pipedesc[pop.stdout] = { 'node': node, 'popen': pop, 'type': 'stdout'}
|
||||
pipedesc[pop.stderr] = {'node': node, 'popen': pop, 'type': 'stderr'}
|
||||
all.add(pop.stdout)
|
||||
all.add(pop.stderr)
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all and rdy:
|
||||
for r in rdy:
|
||||
data = r.readline()
|
||||
desc = pipedesc[r]
|
||||
if data:
|
||||
node = desc['node']
|
||||
if desc['type'] == 'stdout':
|
||||
sys.stdout.write('{0}: {1}'.format(node,data))
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -40,7 +40,7 @@ sensorcollections = {
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange [sensor(s)")
|
||||
usage="Usage: %prog [options] noderange ([sensor(s)])")
|
||||
argparser.add_option('-i', '--interval', type='float',
|
||||
help='Interval to do repeated samples over')
|
||||
argparser.add_option('-n', '--numreadings', type='int',
|
||||
@ -60,7 +60,7 @@ if options.numreadings:
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_usage()
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
sensors = []
|
||||
for sensorgroup in args[1:]:
|
||||
|
@ -26,7 +26,8 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser()
|
||||
argparser = optparse.OptionParser(
|
||||
usage='Usage: %prog [options] noderange [default|cd|network|setup|hd]')
|
||||
argparser.add_option('-b', '--bios', dest='biosmode',
|
||||
action='store_true', default=False,
|
||||
help='Request BIOS style boot (rather than UEFI)')
|
||||
@ -34,15 +35,16 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
default=False,
|
||||
help='Request the boot device be persistent rather than '
|
||||
'one time')
|
||||
argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
|
||||
default=True,
|
||||
help='Request UEFI style boot (rather than BIOS)')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
sys.stderr.write(
|
||||
'Usage: {0} <noderange> [default|cd|network|setup|hd]\n'.format(
|
||||
sys.argv[0]))
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
bootdev = None
|
||||
if len(sys.argv) > 2:
|
||||
|
@ -44,6 +44,7 @@ def _parseserver(string):
|
||||
class Command(object):
|
||||
|
||||
def __init__(self, server=None):
|
||||
self._prevkeyname = None
|
||||
self.connection = None
|
||||
if server is None:
|
||||
if 'CONFLUENT_HOST' in os.environ:
|
||||
@ -74,6 +75,9 @@ class Command(object):
|
||||
if authdata['authpassed'] == 1:
|
||||
self.authenticated = True
|
||||
|
||||
def add_precede_key(self, keyname):
|
||||
self._prevkeyname = keyname
|
||||
|
||||
def handle_results(self, ikey, rc, res):
|
||||
if 'error' in res:
|
||||
sys.stderr.write('Error: {0}\n'.format(res['error']))
|
||||
@ -93,7 +97,17 @@ class Command(object):
|
||||
else:
|
||||
rc |= 1
|
||||
elif ikey in res[node]:
|
||||
print('{0}: {1}'.format(node, res[node][ikey]['value']))
|
||||
if 'value' in res[node][ikey]:
|
||||
val = res[node][ikey]['value']
|
||||
elif 'isset' in res[node][ikey]:
|
||||
val = '********' if res[node][ikey] else ''
|
||||
else:
|
||||
val = repr(res[node][ikey])
|
||||
if self._prevkeyname and self._prevkeyname in res[node]:
|
||||
print('{0}: {2}->{1}'.format(
|
||||
node, val, res[node][self._prevkeyname]['value']))
|
||||
else:
|
||||
print('{0}: {1}'.format(node, val))
|
||||
return rc
|
||||
|
||||
def simple_noderange_command(self, noderange, resource, input=None,
|
||||
@ -223,7 +237,12 @@ def send_request(operation, path, server, parameters=None):
|
||||
tlvdata.send(server, payload)
|
||||
result = tlvdata.recv(server)
|
||||
while '_requestdone' not in result:
|
||||
yield result
|
||||
try:
|
||||
yield result
|
||||
except GeneratorExit:
|
||||
while '_requestdone' not in result:
|
||||
result = tlvdata.recv(server)
|
||||
raise
|
||||
result = tlvdata.recv(server)
|
||||
|
||||
|
||||
|
37
confluent_client/doc/man/confetty.ronn
Normal file
37
confluent_client/doc/man/confetty.ronn
Normal file
@ -0,0 +1,37 @@
|
||||
confetty(1) --- Interactive confluent client
|
||||
=================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`confetty`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**confetty** launches an interactive CLI session to the
|
||||
confluent service. It provides a filesystem-like
|
||||
view of the confluent interface. It is intended to
|
||||
be mostly an aid for developing client software, with
|
||||
day to day administration generally being easier with
|
||||
the various function specific commands.
|
||||
|
||||
## COMMANDS
|
||||
|
||||
The CLI may be navigated by shell commands and some other
|
||||
commands.
|
||||
|
||||
* `cd`:
|
||||
Change the location within the tree
|
||||
* `ls`:
|
||||
List the elements within the current directory/tree
|
||||
* `show` **ELEMENT**, `cat` **ELEMENT**:
|
||||
Display the result of reading a specific element (by full or relative path)
|
||||
* `unset` **ELEMENT** **ATTRIBUTE**
|
||||
For an element with attributes, request to clear the value of the attribue
|
||||
* `set` **ELEMENT** **ATTRIBUTE**=**VALUE**
|
||||
Set the specified attribute to the given value
|
||||
* `start` **ELEMENT**
|
||||
Start a console session indicated by **ELEMENT** (e.g. /nodes/n1/console/session)
|
||||
* `rm` **ELEMENT**
|
||||
Request removal of an element. (e.g. rm events/hardware/log clears log from a node)
|
||||
|
||||
|
75
confluent_client/doc/man/nodeattrib.ronn
Normal file
75
confluent_client/doc/man/nodeattrib.ronn
Normal file
@ -0,0 +1,75 @@
|
||||
nodeattrib(1) -- List or change confluent nodes attributes
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeattrib` `noderange` [ current | all ]
|
||||
`nodeattrib` `noderange` [-b] [<nodeattribute>...]
|
||||
`nodeattrib` `noderange` [<nodeattribute1=value1> <nodeattribute2=value2> ...]
|
||||
`nodeattrib` `noderange` [-c] [<nodeattribute1> <nodeattribute2=value2> ...]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodeattrib** queries the confluent server to get information about nodes. In
|
||||
the simplest form, it simply takes the given noderange(5) and lists the
|
||||
matching nodes, one line at a time.
|
||||
|
||||
If a list of node attribute names are given, the value of those are also
|
||||
displayed. If `-b` is specified, it will also display information on
|
||||
how inherited and expression based attributes are defined. There is more
|
||||
information on node attributes in nodeattributes(5) man page.
|
||||
If `-c` is specified, this will set the nodeattribute to a null valid.
|
||||
This is different from setting the value to an empty string.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Annotate inherited and expression based attributes to show their base value.
|
||||
* `-c`, `--clear`:
|
||||
Clear given nodeattributes since '' is not the same as empty
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
`# nodeattrib n1-n2`
|
||||
`n1`: console.method: ipmi
|
||||
`n1`: hardwaremanagement.manager: 172.30.3.1
|
||||
`n2`: console.method: ipmi
|
||||
`n2`: hardwaremanagement.manager: 172.30.3.2
|
||||
|
||||
* Getting an attribute of nodes matching a noderange:
|
||||
`# nodeattrib n1,n2 hardwaremanagement.manager`
|
||||
`n1: hardwaremanagement.manager: 172.30.3.1`
|
||||
`n2: hardwaremanagement.manager: 172.30.3.2`
|
||||
|
||||
* Getting a group of attributes while determining what group defines them:
|
||||
`# nodeattrib n1,n2 hardwaremanagement --blame`
|
||||
`n1: hardwaremanagement.manager: 172.30.3.1`
|
||||
`n1: hardwaremanagement.method: ipmi (inherited from group everything)`
|
||||
`n1: hardwaremanagement.switch: r8e1`
|
||||
`n1: hardwaremanagement.switchport: 14`
|
||||
`n2: hardwaremanagement.manager: 172.30.3.2`
|
||||
`n2: hardwaremanagement.method: ipmi (inherited from group everything)`
|
||||
`n2: hardwaremanagement.switch: r8e1`
|
||||
`n2: hardwaremanagement.switchport: 2`
|
||||
|
||||
* Listing matching nodes of a simple noderange that are set:
|
||||
`# nodeattrib n1-n2 current`
|
||||
`n1`: console.method: ipmi
|
||||
`n1`: hardwaremanagement.manager: 172.30.3.1
|
||||
`n2`: console.method: ipmi
|
||||
`n2`: hardwaremanagement.manager: 172.30.3.2
|
||||
|
||||
* Change attribute on nodes of a simple noderange:
|
||||
`# nodeattrib n1-n2 console.method=serial`
|
||||
`n1`: console.method: serial
|
||||
`n1`: hardwaremanagement.manager: 172.30.3.1
|
||||
`n2`: console.method: serial
|
||||
`n2`: hardwaremanagement.manager: 172.30.3.2
|
||||
|
||||
* Clear attribute on nodes of a simple noderange, if you want to retain the variable set the attribute to "":
|
||||
`# nodeattrib n1-n2 -c console.method`
|
||||
`# nodeattrib n1-n2 console.method`
|
||||
Error: console.logging not a valid attribute
|
||||
|
||||
|
||||
|
30
confluent_client/doc/man/nodeconsole.ronn
Normal file
30
confluent_client/doc/man/nodeconsole.ronn
Normal file
@ -0,0 +1,30 @@
|
||||
nodeconsole(1) -- Open a console to a confluent node
|
||||
=====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
`nodeconsole` `node`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodeconsole** opens an interactive console session to a given node. This is the
|
||||
text or serial console of a system. Exiting is done by hitting `Ctrl-e`, then `c`,
|
||||
then `.`. Note that console output by default is additionally logged to
|
||||
`/var/log/confluent/consoles/`**NODENAME**.
|
||||
|
||||
## ESCAPE SEQUENCE COMMANDS
|
||||
|
||||
While connected to a console, a number of commands may be performed through escape
|
||||
sequences. To begin an command escape sequence, hit `Ctrl-e`, then `c`. The next
|
||||
keystroke will be interpreted as a command. The following commands are available.
|
||||
|
||||
* `.`:
|
||||
Exit the session and return to the command prompt
|
||||
* `b`:
|
||||
Send a break to the remote console when possible (some console plugins may not support this)
|
||||
* `o`:
|
||||
Request confluent to disconnect and reconnect to console. For example if there is suspicion
|
||||
that the console has gone inoperable, but would work if reconnected.
|
||||
* `?`:
|
||||
Get a list of supported commands
|
||||
* `<enter>`:
|
||||
Abandon entering an escape sequence command
|
@ -1,4 +1,7 @@
|
||||
from setuptools import setup
|
||||
import os
|
||||
|
||||
scriptlist = ['bin/{0}'.format(d) for d in os.listdir('bin/')]
|
||||
|
||||
setup(
|
||||
name='confluent_client',
|
||||
@ -7,9 +10,6 @@ setup(
|
||||
author_email='jjohnson2@lenovo.com',
|
||||
url='http://xcat.sf.net/',
|
||||
packages=['confluent'],
|
||||
scripts=['bin/confetty', 'bin/nodeconsole', 'bin/nodeeventlog',
|
||||
'bin/nodefirmware', 'bin/nodehealth', 'bin/nodeidentify',
|
||||
'bin/nodeinventory', 'bin/nodelist', 'bin/nodepower',
|
||||
'bin/nodesensors', 'bin/nodesetboot'],
|
||||
scripts=scriptlist,
|
||||
data_files=[('/etc/profile.d', ['confluent_env.sh'])],
|
||||
)
|
||||
|
67
confluent_server/bin/confluentdbutil
Executable file
67
confluent_server/bin/confluentdbutil
Executable file
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import optparse
|
||||
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.config.configmanager as cfm
|
||||
import confluent.config.conf as conf
|
||||
import confluent.main as main
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] [dump|restore] [path]")
|
||||
argparser.add_option('-p', '--password',
|
||||
help='Password to use to protect/unlock a protected dump')
|
||||
argparser.add_option('-r', '--redact', action='store_true',
|
||||
help='Redact potentially sensitive data rather than store')
|
||||
argparser.add_option('-u', '--unprotected', action='store_true',
|
||||
help='Specify that no password should be used to protect'
|
||||
' the key information. Fields will be encrypted, '
|
||||
'but keys.json will contain unencrypted decryption'
|
||||
' keys that may be used to read the dump')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 2 or args[0] not in ('dump', 'restore'):
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
dumpdir = args[1]
|
||||
|
||||
|
||||
if args[0] == 'restore':
|
||||
pid = main.is_running()
|
||||
if pid is not None:
|
||||
print("Confluent is running, must shut down to restore db")
|
||||
sys.exit(1)
|
||||
cfm.restore_db_from_directory(dumpdir, options.password)
|
||||
elif args[0] == 'dump':
|
||||
if options.password is None and not (options.unprotected or options.redact):
|
||||
print("Must indicate a password to protect or -u to opt opt of "
|
||||
"secure value protection or -r to skip all protected data")
|
||||
sys.exit(1)
|
||||
os.umask(077)
|
||||
main._initsecurity(conf.get_config())
|
||||
if not os.path.exists(dumpdir):
|
||||
os.makedirs(dumpdir)
|
||||
cfm.dump_db_to_directory(dumpdir, options.password, options.redact)
|
||||
|
||||
|
||||
|
@ -37,6 +37,7 @@ _passcache = {}
|
||||
_passchecking = {}
|
||||
|
||||
authworkers = None
|
||||
authcleaner = None
|
||||
|
||||
|
||||
class Credentials(object):
|
||||
@ -195,6 +196,13 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
#such a beast could be passed into pyghmi as a way for pyghmi to
|
||||
#magically get offload of the crypto functions without having
|
||||
#to explicitly get into the eventlet tpool game
|
||||
global authworkers
|
||||
global authcleaner
|
||||
if authworkers is None:
|
||||
authworkers = multiprocessing.Pool(processes=1)
|
||||
else:
|
||||
authcleaner.cancel()
|
||||
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
|
||||
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
|
||||
del _passchecking[(user, tenant)]
|
||||
eventlet.sleep(0.05) # either way, we want to stall so that client can't
|
||||
@ -211,19 +219,16 @@ def _apply_pbkdf(passphrase, salt):
|
||||
lambda p, s: hmac.new(p, s, hashlib.sha256).digest())
|
||||
|
||||
|
||||
def _clean_authworkers():
|
||||
global authworkers
|
||||
global authcleaner
|
||||
authworkers = None
|
||||
authcleaner = None
|
||||
|
||||
|
||||
def _do_pbkdf(passphrase, salt):
|
||||
# we must get it over to the authworkers pool or else get blocked in
|
||||
# compute. However, we do want to wait for result, so we have
|
||||
# one of the exceedingly rare sort of circumstances where 'apply'
|
||||
# actually makes sense
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
||||
|
||||
|
||||
def init_auth():
|
||||
# have a some auth workers available. Keep them distinct from
|
||||
# the general populace of workers to avoid unauthorized users
|
||||
# starving out productive work
|
||||
global authworkers
|
||||
# for now we'll just have one auth worker and see if there is any
|
||||
# demand for more. I personally doubt it.
|
||||
authworkers = multiprocessing.Pool(processes=1)
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
@ -65,6 +65,7 @@ import anydbm as dbm
|
||||
import ast
|
||||
import base64
|
||||
import confluent.config.attributes as allattributes
|
||||
import confluent.config.conf as conf
|
||||
import confluent.log
|
||||
import confluent.util
|
||||
import confluent.exceptions as exc
|
||||
@ -128,6 +129,18 @@ def _get_protected_key(keydict, password, paramname):
|
||||
raise exc.LockedCredentials("No available decryption key")
|
||||
|
||||
|
||||
def _parse_key(keydata, password=None):
|
||||
if keydata.startswith('*unencrypted:'):
|
||||
return base64.b64decode(keydata[13:])
|
||||
elif password:
|
||||
salt, iv, crypt, hmac = [base64.b64decode(x)
|
||||
for x in keydata.split('!')]
|
||||
privkey, integkey = _derive_keys(password, salt)
|
||||
return decrypt_value([iv, crypt, hmac], privkey, integkey)
|
||||
raise(exc.LockedCredentials(
|
||||
"Passphrase protected secret requires password"))
|
||||
|
||||
|
||||
def _format_key(key, password=None):
|
||||
if password is not None:
|
||||
salt = os.urandom(32)
|
||||
@ -707,6 +720,8 @@ class ConfigManager(object):
|
||||
:param uid: Custom identifier number if desired. Defaults to random.
|
||||
:param displayname: Optional long format name for UI consumption
|
||||
"""
|
||||
if 'idmap' not in _cfgstore['main']:
|
||||
_cfgstore['main']['idmap'] = {}
|
||||
if uid is None:
|
||||
uid = _generate_new_id()
|
||||
else:
|
||||
@ -720,8 +735,6 @@ class ConfigManager(object):
|
||||
self._cfgstore['users'][name] = {'id': uid}
|
||||
if displayname is not None:
|
||||
self._cfgstore['users'][name]['displayname'] = displayname
|
||||
if 'idmap' not in _cfgstore['main']:
|
||||
_cfgstore['main']['idmap'] = {}
|
||||
_cfgstore['main']['idmap'][uid] = {
|
||||
'tenant': self.tenant,
|
||||
'username': name
|
||||
@ -761,6 +774,14 @@ class ConfigManager(object):
|
||||
decrypt=self.decrypt)
|
||||
return nodeobj
|
||||
|
||||
def expand_attrib_expression(self, nodelist, expression):
|
||||
if type(nodelist) in (unicode, str):
|
||||
nodelist = (nodelist,)
|
||||
for node in nodelist:
|
||||
cfgobj = self._cfgstore['nodes'][node]
|
||||
fmt = _ExpressionFormat(cfgobj, node)
|
||||
yield (node, fmt.format(expression))
|
||||
|
||||
def get_node_attributes(self, nodelist, attributes=(), decrypt=None):
|
||||
if decrypt is None:
|
||||
decrypt = self.decrypt
|
||||
@ -1179,6 +1200,70 @@ class ConfigManager(object):
|
||||
self._bg_sync_to_file()
|
||||
#TODO: wait for synchronization to suceed/fail??)
|
||||
|
||||
def _load_from_json(self, jsondata):
|
||||
"""Load fresh configuration data from jsondata
|
||||
|
||||
:param jsondata: String of jsondata
|
||||
:return:
|
||||
"""
|
||||
dumpdata = json.loads(jsondata)
|
||||
tmpconfig = {}
|
||||
for confarea in _config_areas:
|
||||
if confarea not in dumpdata:
|
||||
continue
|
||||
tmpconfig[confarea] = {}
|
||||
for element in dumpdata[confarea]:
|
||||
newelement = copy.deepcopy(dumpdata[confarea][element])
|
||||
for attribute in dumpdata[confarea][element]:
|
||||
if newelement[attribute] == '*REDACTED*':
|
||||
raise Exception(
|
||||
"Unable to restore from redacted backup")
|
||||
elif attribute == 'cryptpass':
|
||||
passparts = newelement[attribute].split('!')
|
||||
newelement[attribute] = tuple([base64.b64decode(x)
|
||||
for x in passparts])
|
||||
elif 'cryptvalue' in newelement[attribute]:
|
||||
bincrypt = newelement[attribute]['cryptvalue']
|
||||
bincrypt = tuple([base64.b64decode(x)
|
||||
for x in bincrypt.split('!')])
|
||||
newelement[attribute]['cryptvalue'] = bincrypt
|
||||
elif attribute in ('nodes', '_expressionkeys'):
|
||||
# A group with nodes
|
||||
# delete it and defer until nodes are being added
|
||||
# which will implicitly fill this up
|
||||
# Or _expressionkeys attribute, which will similarly
|
||||
# be rebuilt
|
||||
del newelement[attribute]
|
||||
tmpconfig[confarea][element] = newelement
|
||||
# We made it through above section without an exception, go ahead and
|
||||
# replace
|
||||
# Start by erasing the dbm files if present
|
||||
for confarea in _config_areas:
|
||||
try:
|
||||
os.unlink(os.path.join(self._cfgdir, confarea))
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
pass
|
||||
# Now we have to iterate through each fixed up element, using the
|
||||
# set attribute to flesh out inheritence and expressions
|
||||
for confarea in _config_areas:
|
||||
if confarea not in tmpconfig:
|
||||
continue
|
||||
if confarea == 'nodes':
|
||||
self.set_node_attributes(tmpconfig[confarea], True)
|
||||
elif confarea == 'nodegroups':
|
||||
self.set_group_attributes(tmpconfig[confarea], True)
|
||||
elif confarea == 'users':
|
||||
for user in tmpconfig[confarea]:
|
||||
uid = tmpconfig[confarea].get('id', None)
|
||||
displayname = tmpconfig[confarea].get('displayname', None)
|
||||
self.create_user(user, uid=uid, displayname=displayname)
|
||||
if 'cryptpass' in tmpconfig[confarea][user]:
|
||||
self._cfgstore['users'][user]['cryptpass'] = \
|
||||
tmpconfig[confarea][user]['cryptpass']
|
||||
_mark_dirtykey('users', user, self.tenant)
|
||||
self._bg_sync_to_file()
|
||||
|
||||
def _dump_to_json(self, redact=None):
|
||||
"""Dump the configuration in json form to output
|
||||
|
||||
@ -1336,28 +1421,80 @@ class ConfigManager(object):
|
||||
self._recalculate_expressions(cfgobj[key], formatter, node,
|
||||
changeset)
|
||||
|
||||
|
||||
def _restore_keys(jsond, password, newpassword=None):
|
||||
# the jsond from the restored file, password (if any) used to protect
|
||||
# the file, and newpassword to use, (also check the service.cfg file)
|
||||
global _masterkey
|
||||
global _masterintegritykey
|
||||
keydata = json.loads(jsond)
|
||||
cryptkey = _parse_key(keydata['cryptkey'], password)
|
||||
integritykey = _parse_key(keydata['integritykey'], password)
|
||||
conf.init_config()
|
||||
cfg = conf.get_config()
|
||||
if cfg.has_option('security', 'externalcfgkey'):
|
||||
keyfilename = cfg.get('security', 'externalcfgkey')
|
||||
with open(keyfilename, 'r') as keyfile:
|
||||
newpassword = keyfile.read()
|
||||
set_global('master_privacy_key', _format_key(cryptkey,
|
||||
password=newpassword))
|
||||
set_global('master_integrity_key', _format_key(integritykey,
|
||||
password=newpassword))
|
||||
_masterkey = cryptkey
|
||||
_masterintegritykey = integritykey
|
||||
ConfigManager.wait_for_sync()
|
||||
# At this point, we should have the key situation all sorted
|
||||
|
||||
|
||||
def _dump_keys(password):
|
||||
if _masterkey is None or _masterintegritykey is None:
|
||||
init_masterkey()
|
||||
cryptkey = _format_key(_masterkey, password=password)
|
||||
cryptkey = '!'.join(map(base64.b64encode, cryptkey['passphraseprotected']))
|
||||
if 'passphraseprotected' in cryptkey:
|
||||
cryptkey = '!'.join(map(base64.b64encode,
|
||||
cryptkey['passphraseprotected']))
|
||||
else:
|
||||
cryptkey = '*unencrypted:{0}'.format(base64.b64encode(
|
||||
cryptkey['unencryptedvalue']))
|
||||
integritykey = _format_key(_masterintegritykey, password=password)
|
||||
integritykey = '!'.join(map(base64.b64encode, integritykey['passphraseprotected']))
|
||||
if 'passphraseprotected' in integritykey:
|
||||
integritykey = '!'.join(map(base64.b64encode,
|
||||
integritykey['passphraseprotected']))
|
||||
else:
|
||||
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
|
||||
integritykey['unencryptedvalue']))
|
||||
return json.dumps({'cryptkey': cryptkey, 'integritykey': integritykey},
|
||||
sort_keys=True, indent=4, separators=(',', ': '))
|
||||
|
||||
|
||||
def restore_db_from_directory(location, password):
|
||||
try:
|
||||
with open(os.path.join(location, 'keys.json'), 'r') as cfgfile:
|
||||
keydata = cfgfile.read()
|
||||
json.loads(keydata)
|
||||
_restore_keys(keydata, password)
|
||||
except IOError as e:
|
||||
if e.errno == 2:
|
||||
raise Exception("Cannot restore without keys, this may be a "
|
||||
"redacted dump")
|
||||
with open(os.path.join(location, 'main.json'), 'r') as cfgfile:
|
||||
cfgdata = cfgfile.read()
|
||||
ConfigManager(tenant=None)._load_from_json(cfgdata)
|
||||
|
||||
|
||||
def dump_db_to_directory(location, password, redact=None):
|
||||
with open(os.path.join(location, 'keys.json'), 'w') as cfgfile:
|
||||
cfgfile.write(_dump_keys(password))
|
||||
cfgfile.write('\n')
|
||||
if not redact:
|
||||
with open(os.path.join(location, 'keys.json'), 'w') as cfgfile:
|
||||
cfgfile.write(_dump_keys(password))
|
||||
cfgfile.write('\n')
|
||||
with open(os.path.join(location, 'main.json'), 'w') as cfgfile:
|
||||
cfgfile.write(ConfigManager(tenant=None)._dump_to_json(redact=redact))
|
||||
cfgfile.write('\n')
|
||||
try:
|
||||
for tenant in os.listdir(
|
||||
os.path.join(ConfigManager._cfgdir, '/tenants/')):
|
||||
with open(os.path.join(location, tenant + '.json'), 'w') as cfgfile:
|
||||
with open(os.path.join(location, 'tenants', tenant,
|
||||
'main.json'), 'w') as cfgfile:
|
||||
cfgfile.write(ConfigManager(tenant=tenant)._dump_to_json(
|
||||
redact=redact))
|
||||
cfgfile.write('\n')
|
||||
|
@ -122,6 +122,7 @@ def _init_core():
|
||||
'attributes': {
|
||||
'all': PluginRoute({'handler': 'attributes'}),
|
||||
'current': PluginRoute({'handler': 'attributes'}),
|
||||
'expression': PluginRoute({'handler': 'attributes'}),
|
||||
},
|
||||
'boot': {
|
||||
'nextdevice': PluginRoute({
|
||||
|
@ -35,6 +35,7 @@ import eventlet.greenthread
|
||||
import greenlet
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
import urlparse
|
||||
@ -233,6 +234,17 @@ def _csrf_valid(env, session):
|
||||
# oblige the request and apply a new token to the
|
||||
# session
|
||||
session['csrftoken'] = util.randomstring(32)
|
||||
elif 'HTTP_REFERER' in env:
|
||||
# If there is a referrer, make sure it stays consistent
|
||||
# across the session. A change in referer is a bad thing
|
||||
try:
|
||||
referer = env['HTTP_REFERER'].split('/')[2]
|
||||
except IndexError:
|
||||
return False
|
||||
if 'validreferer' not in session:
|
||||
session['validreferer'] = referer
|
||||
elif session['validreferer'] != referer:
|
||||
return False
|
||||
return True
|
||||
# The session has CSRF protection enabled, only mark valid if
|
||||
# the client has provided an auth token and that token matches the
|
||||
@ -257,15 +269,15 @@ def _authorize_request(env, operation):
|
||||
sessionid = cc['confluentsessionid'].value
|
||||
sessid = sessionid
|
||||
if sessionid in httpsessions:
|
||||
if env['PATH_INFO'] == '/sessions/current/logout':
|
||||
targets = []
|
||||
for mythread in httpsessions[sessionid]['inflight']:
|
||||
targets.append(mythread)
|
||||
for mythread in targets:
|
||||
eventlet.greenthread.kill(mythread)
|
||||
del httpsessions[sessionid]
|
||||
return ('logout',)
|
||||
if _csrf_valid(env, httpsessions[sessionid]):
|
||||
if env['PATH_INFO'] == '/sessions/current/logout':
|
||||
targets = []
|
||||
for mythread in httpsessions[sessionid]['inflight']:
|
||||
targets.append(mythread)
|
||||
for mythread in targets:
|
||||
eventlet.greenthread.kill(mythread)
|
||||
del httpsessions[sessionid]
|
||||
return ('logout',)
|
||||
httpsessions[sessionid]['expiry'] = time.time() + 90
|
||||
name = httpsessions[sessionid]['name']
|
||||
authdata = auth.authorize(
|
||||
@ -273,6 +285,12 @@ def _authorize_request(env, operation):
|
||||
skipuserobj=httpsessions[sessionid]['skipuserobject'])
|
||||
if (not authdata) and 'HTTP_AUTHORIZATION' in env:
|
||||
if env['PATH_INFO'] == '/sessions/current/logout':
|
||||
if 'HTTP_REFERER' in env:
|
||||
# note that this doesn't actually do harm
|
||||
# otherwise, but this way do not give appearance
|
||||
# of something having a side effect if it has the smell
|
||||
# of a CSRF
|
||||
return {'code': 401}
|
||||
return ('logout',)
|
||||
name, passphrase = base64.b64decode(
|
||||
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
|
||||
@ -369,7 +387,8 @@ def resourcehandler_backend(env, start_response):
|
||||
"""Function to handle new wsgi requests
|
||||
"""
|
||||
mimetype, extension = _pick_mimetype(env)
|
||||
headers = [('Content-Type', mimetype), ('Cache-Control', 'no-cache'),
|
||||
headers = [('Content-Type', mimetype), ('Cache-Control', 'no-store'),
|
||||
('Pragma', 'no-cache'),
|
||||
('X-Content-Type-Options', 'nosniff'),
|
||||
('Content-Security-Policy', "default-src 'self'"),
|
||||
('X-XSS-Protection', '1'), ('X-Frame-Options', 'deny'),
|
||||
@ -723,9 +742,20 @@ def serve(bind_host, bind_port):
|
||||
#but deps are simpler without flup
|
||||
#also, the potential for direct http can be handy
|
||||
#todo remains unix domain socket for even http
|
||||
eventlet.wsgi.server(
|
||||
eventlet.listen((bind_host, bind_port, 0, 0), family=socket.AF_INET6),
|
||||
resourcehandler, log=False, log_output=False, debug=False)
|
||||
sock = None
|
||||
while not sock:
|
||||
try:
|
||||
sock = eventlet.listen(
|
||||
(bind_host, bind_port, 0, 0), family=socket.AF_INET6)
|
||||
except socket.error as e:
|
||||
if e.errno != 98:
|
||||
raise
|
||||
sys.stderr.write(
|
||||
'Failed to open HTTP due to busy port, trying again in'
|
||||
' a second\n')
|
||||
eventlet.sleep(1)
|
||||
eventlet.wsgi.server(sock, resourcehandler, log=False, log_output=False,
|
||||
debug=False)
|
||||
|
||||
|
||||
class HttpApi(object):
|
||||
|
@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -89,15 +89,41 @@ def _updatepidfile():
|
||||
pidfile.close()
|
||||
|
||||
|
||||
def is_running():
|
||||
# Utility function for utilities to check if confluent is running
|
||||
try:
|
||||
pidfile = open('/var/run/confluent/pid', 'r+')
|
||||
fcntl.flock(pidfile, fcntl.LOCK_SH)
|
||||
pid = pidfile.read()
|
||||
if pid != '':
|
||||
try:
|
||||
os.kill(int(pid), 0)
|
||||
return pid
|
||||
except OSError:
|
||||
# There is no process running by that pid, must be stale
|
||||
pass
|
||||
fcntl.flock(pidfile, fcntl.LOCK_UN)
|
||||
pidfile.close()
|
||||
except IOError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _checkpidfile():
|
||||
try:
|
||||
pidfile = open('/var/run/confluent/pid', 'r+')
|
||||
fcntl.flock(pidfile, fcntl.LOCK_EX)
|
||||
pid = pidfile.read()
|
||||
if pid != '':
|
||||
print ('/var/run/confluent/pid exists and indicates %s is still '
|
||||
'running' % pid)
|
||||
sys.exit(1)
|
||||
try:
|
||||
os.kill(int(pid), 0)
|
||||
print ('/var/run/confluent/pid exists and indicates %s is still '
|
||||
'running' % pid)
|
||||
sys.exit(1)
|
||||
except OSError:
|
||||
# There is no process running by that pid, must be stale
|
||||
pass
|
||||
pidfile.seek(0)
|
||||
pidfile.write(str(os.getpid()))
|
||||
fcntl.flock(pidfile, fcntl.LOCK_UN)
|
||||
pidfile.close()
|
||||
@ -196,13 +222,14 @@ def run():
|
||||
_daemonize()
|
||||
if havefcntl:
|
||||
_updatepidfile()
|
||||
auth.init_auth()
|
||||
signal.signal(signal.SIGINT, terminate)
|
||||
signal.signal(signal.SIGTERM, terminate)
|
||||
#TODO(jbjohnso): eventlet has a bug about unix domain sockets, this code
|
||||
#works with bugs fixed
|
||||
if dbgif:
|
||||
oumask = os.umask(0077)
|
||||
try:
|
||||
os.remove('/var/run/confluent/dbg.sock')
|
||||
except OSError:
|
||||
pass # We are not expecting the file to exist
|
||||
dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
|
||||
family=socket.AF_UNIX)
|
||||
eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
|
||||
|
@ -797,6 +797,11 @@ class PowerState(ConfluentChoiceMessage):
|
||||
])
|
||||
keyname = 'state'
|
||||
|
||||
def __init__(self, node, state, oldstate=None):
|
||||
super(PowerState, self).__init__(node, state)
|
||||
if oldstate is not None:
|
||||
self.kvpairs[node]['oldstate'] = {'value': oldstate}
|
||||
|
||||
|
||||
class BMCReset(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
|
@ -152,6 +152,20 @@ def update_nodegroup(group, element, configmanager, inputdata):
|
||||
return retrieve_nodegroup(group, element, configmanager, inputdata)
|
||||
|
||||
|
||||
def _expand_expression(nodes, configmanager, inputdata):
|
||||
expression = inputdata.get_attributes(list(nodes)[0])
|
||||
if type(expression) is dict:
|
||||
expression = expression['expression']
|
||||
if type(expression) is dict:
|
||||
expression = expression['expression']
|
||||
for expanded in configmanager.expand_attrib_expression(nodes, expression):
|
||||
yield msg.KeyValueData({'value': expanded[1]}, expanded[0])
|
||||
|
||||
|
||||
def create(nodes, element, configmanager, inputdata):
|
||||
if nodes is not None and element[-1] == 'expression':
|
||||
return _expand_expression(nodes, configmanager, inputdata)
|
||||
|
||||
def update_nodes(nodes, element, configmanager, inputdata):
|
||||
updatedict = {}
|
||||
for node in nodes:
|
||||
|
@ -344,8 +344,8 @@ class IpmiHandler(object):
|
||||
except socket.gaierror as ge:
|
||||
if ge[0] == -2:
|
||||
raise exc.TargetEndpointUnreachable(ge[1])
|
||||
raise
|
||||
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
|
||||
self.ipmicmd.setup_confluent_keyhandler()
|
||||
|
||||
bootdevices = {
|
||||
'optical': 'cd'
|
||||
@ -356,7 +356,9 @@ class IpmiHandler(object):
|
||||
self.broken = True
|
||||
self.error = response['error']
|
||||
else:
|
||||
self.ipmicmd = ipmicmd
|
||||
self.loggedin = True
|
||||
self.ipmicmd.setup_confluent_keyhandler()
|
||||
self._logevt.set()
|
||||
|
||||
def handle_request(self):
|
||||
@ -793,10 +795,22 @@ class IpmiHandler(object):
|
||||
return
|
||||
elif 'update' == self.op:
|
||||
powerstate = self.inputdata.powerstate(self.node)
|
||||
oldpower = None
|
||||
if powerstate == 'boot':
|
||||
oldpower = self.ipmicmd.get_power()
|
||||
if 'powerstate' in oldpower:
|
||||
oldpower = oldpower['powerstate']
|
||||
self.ipmicmd.set_power(powerstate, wait=30)
|
||||
power = self.ipmicmd.get_power()
|
||||
if powerstate == 'boot' and oldpower == 'on':
|
||||
power = {'powerstate': 'reset'}
|
||||
else:
|
||||
power = self.ipmicmd.get_power()
|
||||
if powerstate == 'reset' and power['powerstate'] == 'on':
|
||||
power['powerstate'] = 'reset'
|
||||
|
||||
self.output.put(msg.PowerState(node=self.node,
|
||||
state=power['powerstate']))
|
||||
state=power['powerstate'],
|
||||
oldstate=oldpower))
|
||||
return
|
||||
|
||||
def handle_reset(self):
|
||||
|
@ -170,7 +170,8 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
auditlog.log(auditmsg)
|
||||
try:
|
||||
if operation == 'start':
|
||||
return start_term(authname, cfm, connection, params, path)
|
||||
return start_term(authname, cfm, connection, params, path,
|
||||
authdata, skipauth)
|
||||
elif operation == 'shutdown':
|
||||
configmanager.ConfigManager.shutdown()
|
||||
else:
|
||||
@ -187,7 +188,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
return
|
||||
|
||||
|
||||
def start_term(authname, cfm, connection, params, path):
|
||||
def start_term(authname, cfm, connection, params, path, authdata, skipauth):
|
||||
elems = path.split('/')
|
||||
if len(elems) < 4 or elems[1] != 'nodes':
|
||||
raise exc.InvalidArgumentException('Invalid path {0}'.format(path))
|
||||
@ -233,7 +234,9 @@ def start_term(authname, cfm, connection, params, path):
|
||||
consession.reopen()
|
||||
continue
|
||||
else:
|
||||
raise Exception("TODO")
|
||||
process_request(connection, data, cfm, authdata, authname,
|
||||
skipauth)
|
||||
continue
|
||||
if not data:
|
||||
consession.destroy()
|
||||
return
|
||||
@ -244,7 +247,16 @@ 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((bind_host, bind_port, 0, 0))
|
||||
bound = False
|
||||
while not bound:
|
||||
try:
|
||||
plainsocket.bind((bind_host, bind_port, 0, 0))
|
||||
bound = True
|
||||
except socket.error as e:
|
||||
if e.errno != 98:
|
||||
raise
|
||||
sys.stderr.write('TLS Socket in use, retrying in 1 second\n')
|
||||
eventlet.sleep(1)
|
||||
plainsocket.listen(5)
|
||||
while (1): # TODO: exithook
|
||||
cnn, addr = plainsocket.accept()
|
||||
|
@ -33,6 +33,9 @@ done
|
||||
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
|
||||
cat INSTALLED_FILES
|
||||
|
||||
%post
|
||||
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent; fi
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
|
@ -5,7 +5,7 @@ setup(
|
||||
name='confluent_server',
|
||||
version='#VERSION#',
|
||||
author='Jarrod Johnson',
|
||||
author_email='jbjohnso@us.ibm.com',
|
||||
author_email='jjohnson2@lenovo.com',
|
||||
url='http://xcat.sf.net/',
|
||||
description='confluent systems management server',
|
||||
packages=['confluent', 'confluent/config', 'confluent/interface',
|
||||
@ -14,7 +14,7 @@ setup(
|
||||
'confluent/plugins/configuration/'],
|
||||
install_requires=['paramiko', 'pycrypto>=2.6', 'confluent_client>=0.1.0', 'eventlet',
|
||||
'pyghmi>=0.6.5'],
|
||||
scripts=['bin/confluent'],
|
||||
scripts=['bin/confluent', 'bin/confluentdbutil'],
|
||||
data_files=[('/etc/init.d', ['sysvinit/confluent']),
|
||||
('/usr/lib/systemd/system', ['systemd/confluent.service']),
|
||||
('/opt/confluent/lib/python/confluent/plugins/console/', [])],
|
||||
|
Loading…
x
Reference in New Issue
Block a user