2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-29 08:41:00 +00:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Jarrod Johnson 188a1b411a Add batch support to nodeconfig 2018-08-02 16:53:56 -04:00
Jarrod Johnson 432b899a9d Merge branch 'master' into 18c 2018-08-02 16:41:26 -04:00
Jarrod Johnson d3ee71eb5e Merge branch 'master' into 18c 2018-07-30 09:33:43 -04:00
Jarrod Johnson 6f002b3d91 Merge branch 'master' into 18c 2018-07-26 14:18:48 -04:00
Jarrod Johnson 8e01686d4a Merge branch 'master' into 18c 2018-07-24 17:29:12 -04:00
Jarrod Johnson ddd3dea92d Merge branch 'master' into 18c 2018-07-24 16:50:17 -04:00
Jarrod Johnson 6f5481f007 Have collective coalesce watched updates
Particularly chatty output can make collate be unreasonable in
low quality terminals and links.  Throttle to about 4 times a second.
2018-07-24 15:20:46 -04:00
46 changed files with 6846 additions and 1194 deletions
-8
View File
@@ -48,8 +48,6 @@ argparser.add_option('-c', '--count', action='store_true',
argparser.add_option('-r', '--reverse', action='store_true',
help='Reverse sort order to show biggest output group '
'last')
argparser.add_option('-l', '--log', action='store', type='string', dest='log',
help='Log each output to file, using {node} as a placeholder for node.')
(options, args) = argparser.parse_args()
if sys.stdin.isatty():
argparser.print_help()
@@ -82,12 +80,6 @@ while fullline:
continue
if ': ' not in line:
line = 'UNKNOWN: ' + line
if options.log:
node, output = line.split(': ', 1)
currlog = options.log.format(node=node, nodename=node)
with open(currlog, mode='a') as log:
log.write(output + '\n')
continue
grouped.add_line(*line.split(': ', 1))
if options.watch:
if not holdoff:
+2 -3
View File
@@ -598,10 +598,9 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
if oldtcattr is not None:
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
# Request default color scheme, to undo potential weirdness of terminal
if sys.stdout.isatty():
sys.stdout.write('\x1b[m')
sys.stdout.write('\x1b[m')
if fullexit:
if sys.stdout.isatty() and os.environ.get('TERM', '') not in ('linux'):
if os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
sys.exit(code)
else:
+2 -17
View File
@@ -17,7 +17,6 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -48,8 +47,6 @@ argparser.add_option('-e', '--environment', action='store_true',
'same name')
argparser.add_option('-c', '--clear', action='store_true',
help='Clear attributes')
argparser.add_option('-p', '--prompt', action='store_true',
help='Prompt for attribute values interactively')
(options, args) = argparser.parse_args()
@@ -70,24 +67,12 @@ exitcode = 0
nodetype="noderange"
if len(args) > 1:
if "=" in args[1] or options.clear or options.environment or options.prompt:
if "=" in args[1] or options.clear or options.environment:
if "=" in args[1] and options.clear:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
argassign = None
if options.prompt:
argassign = {}
for arg in args[1:]:
oneval = 1
twoval = 2
while oneval != twoval:
oneval = getpass('Enter value for {0}: '.format(arg))
twoval = getpass('Confirm value for {0}: '.format(arg))
if oneval != twoval:
print('Values did not match.')
argassign[arg] = twoval
exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign)
exitcode=client.updateattrib(session,args,nodetype, noderange, options)
try:
# setting user output to what the user inputs
if args[1] == 'all':
-3
View File
@@ -35,9 +35,6 @@ argparser = optparse.OptionParser()
argparser.add_option('-b', '--bios', dest='biosmode',
action='store_true', default=False,
help='Request BIOS style boot (rather than UEFI)')
argparser.add_option('-u', '--uefi', dest='uefimode',
action='store_true', default=False,
help='Request UEFI style boot (rather than BIOS)')
argparser.add_option('-p', '--persist', dest='persist', action='store_true',
default=False,
help='Request the boot device be persistent rather than '
+68 -79
View File
@@ -19,6 +19,7 @@
import os
import signal
import optparse
import shlex
import sys
try:
@@ -47,6 +48,8 @@ argparser.add_option('-c', '--comparedefault', dest='comparedefault',
action='store_true', default=False,
help='Compare given settings to default or list settings '
'that are non default')
argparser.add_option('-b', '--batch', dest='batch', metavar='settings.batch',
default=False, help='Provide settings in a batch file')
argparser.add_option('-d', '--detail', dest='detail',
action='store_true', default=False,
help='Provide verbose information as available, such as '
@@ -55,16 +58,6 @@ argparser.add_option('-x', '--exclude', dest='exclude',
action='store_true', default=False,
help='Treat positional arguments as items to not '
'examine, compare, or restore default')
argparser.add_option('-a', '--advanced', dest='advanced',
action='store_true', default=False,
help='Include advanced settings, which are normally not '
'intended to be used without direction from the '
'relevant server vendor.')
argparser.add_option('-r', '--restoredefault', default=False,
dest='restoredefault', metavar="COMPONENT",
help='Restore the configuration of the node '
'to factory default for given component. '
'Currently only uefi is supported')
(options, args) = argparser.parse_args()
cfgpaths = {
@@ -118,75 +111,75 @@ def _assign_value():
assignment[key] = value
for param in args[1:]:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
setmode = True
forceset = True
continue
if needval:
key = needval
value = param
_assign_value()
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
def parse_config_line(arguments):
global setmode, forceset, key, value, needval, candidate, path, attrib
for param in arguments:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
forceset = True
continue
if needval:
key = needval
value = param
_assign_value()
elif param[-1] == ':':
needval = param[:-1]
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
_assign_value()
elif param[-1] == ':':
needval = param[:-1]
else:
needval = param
else:
needval = param
else:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
printsys.append(param)
elif param not in cfgpaths:
printsys.append(param)
elif param not in cfgpaths:
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
if options.batch:
printsys = []
argfile = open(options.batch, 'r')
argset = argfile.readline()
while argset:
parse_config_line(shlex.split(argset))
argset = argfile.readline()
else:
parse_config_line(args[1:])
session = client.Command()
rcode = 0
if options.restoredefault and options.restoredefault.lower() in (
'sys', 'system', 'uefi', 'bios'):
for fr in session.update(
'/noderange/{0}/configuration/system/clear'.format(noderange),
{'clear': True}):
rcode |= client.printerror(fr)
sys.exit(rcode)
elif options.restoredefault:
sys.stderr.write(
'Unrecognized component to restore defaults: {0}\n'.format(
options.restoredefault))
sys.exit(1)
if setmode:
if options.exclude:
sys.stderr.write('Cannot use exclude and assign at the same time\n')
@@ -231,11 +224,7 @@ else:
if printsys or options.exclude:
if printsys == 'all':
printsys = []
if (options.comparedefault or printsys == []) and not options.advanced:
path = '/noderange/{0}/configuration/system/all'.format(noderange)
else:
path = '/noderange/{0}/configuration/system/advanced'.format(
noderange)
path = '/noderange/{0}/configuration/system/all'.format(noderange)
rcode = client.print_attrib_path(path, session, printsys,
options)
sys.exit(rcode)
sys.exit(rcode)
+2 -9
View File
@@ -55,8 +55,7 @@ def printfirm(node, prefix, data):
components = ['all']
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
usage="Usage: %prog <noderange> [update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
(options, args) = argparser.parse_args()
@@ -67,15 +66,9 @@ try:
if args[1] == 'update':
upfile = args[2]
else:
if args[1] == 'list':
comps = args[2:]
else:
comps = args[1:]
components = []
for arg in comps:
for arg in args[1:]:
components += arg.split(',')
if not components:
components = ['all']
except IndexError:
argparser.print_help()
+1 -16
View File
@@ -17,7 +17,6 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -48,8 +47,6 @@ argparser.add_option('-e', '--environment', action='store_true',
'same name')
argparser.add_option('-c', '--clear', action='store_true',
help='Clear variables')
argparser.add_option('-p', '--prompt', action='store_true',
help='Prompt for attribute values interactively')
(options, args) = argparser.parse_args()
@@ -75,19 +72,7 @@ if len(args) > 1:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
argassign = None
if options.prompt:
argassign = {}
for arg in args[1:]:
oneval = 1
twoval = 2
while oneval != twoval:
oneval = getpass('Enter value for {0}: '.format(arg))
twoval = getpass('Confirm value for {0}: '.format(arg))
if oneval != twoval:
print('Values did not match.')
argassign[arg] = twoval
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options, argassign)
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options)
try:
# setting user output to what the user inputs
if args[1] == 'all':
-6
View File
@@ -87,7 +87,6 @@ def printerror(res, node=None):
url = '/noderange/{0}/inventory/hardware/all/all'
usedprefixes = set([])
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
@@ -130,11 +129,6 @@ try:
continue
for inv in res['databynode'][node]['inventory']:
prefix = inv['name']
idx = 2
while (node, prefix) in usedprefixes:
prefix = '{0} {1}'.format(inv['name'], idx)
idx += 1
usedprefixes.add((node, prefix))
if not inv['present']:
if not filters:
if options.json:
+1 -1
View File
@@ -85,7 +85,7 @@ def run():
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0)[0]:
while data and select.select([r], [], [], 0):
data = r.readline()
if data:
if desc['type'] == 'stdout':
-145
View File
@@ -1,145 +0,0 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
import time
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
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
import confluent.screensqueeze as sq
exitcode = 0
def get_update_progress(session, url):
for res in session.read(url):
status = res['phase']
percent = res['progress']
detail = res['detail']
if status == 'error':
text = 'error!'
else:
text = '{0}: {1:3.0f}%'.format(status, percent)
return text, status, detail
def printerror(res, node=None):
global exitcode
if 'errorcode' in res:
exitcode = res['errorcode']
if 'error' in res:
if node:
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
else:
sys.stderr.write('{0}\n'.format(res['error']))
if 'errorcode' not in res:
exitcode = 1
for node in res.get('databynode', ()):
printerror(res['databynode'][node], node)
def download_servicedata(noderange, media):
global exitcode
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
filename = os.path.abspath(media)
resource = '/noderange/{0}/support/servicedata/'.format(noderange)
upargs = {'filename': filename}
noderrs = {}
nodeurls = {}
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
output.set_output(nodename, 'error!')
noderrs[nodename] = res['databynode'][nodename].get(
'error', 'Unknown Error')
continue
watchurl = res['created']
currnode = watchurl.split('/')[1]
nodeurls[currnode] = '/' + watchurl
while nodeurls:
for node in list(nodeurls):
progress, status, err = get_update_progress(
session, nodeurls[node])
if status == 'error':
exitcode = 1
noderrs[node] = err
if status in ('error', 'complete', 'pending'):
list(session.delete(nodeurls[node]))
del nodeurls[node]
output.set_output(node, progress)
time.sleep(2)
allerrnodes = ','.join(noderrs)
if noderrs:
sys.stderr.write(
'Nodes had errors retrieving service data ({0})!\n'.format(allerrnodes))
for node in noderrs:
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
funmap = {
'servicedata': download_servicedata,
}
class OptParser(optparse.OptionParser):
def format_epilog(self, formatter):
return self.expand_prog_name(self.epilog)
def main():
argparser = OptParser(
usage="Usage: %prog <noderange> [servicedata] "
"<filename>",
epilog='\nservicedata will save service data to the given '
'directory\n'
'\n\nSee `man %prog` for more info.\n')
(options, args) = argparser.parse_args()
media = None
try:
noderange = args[0]
operation = args[1]
arglength = 2
if operation == 'servicedata':
media = args[2]
arglength = 3
if len(args) > arglength:
argparser.print_help()
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
try:
handler = funmap[operation]
except KeyError:
argparser.print_help()
sys.exit(1)
handler(noderange, media)
if __name__ == '__main__':
main()
+2 -11
View File
@@ -17,7 +17,6 @@
import anydbm as dbm
import errno
import fnmatch
import hashlib
import os
import shlex
@@ -312,7 +311,7 @@ def attrrequested(attr, attrlist, seenattributes):
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
if candidate in _attraliases:
candidate = _attraliases[candidate]
if fnmatch.fnmatch(attr.lower(), candidate.lower()):
if candidate.lower() == attr.lower():
seenattributes.add(truename)
return True
elif attr.lower().startswith(candidate.lower() + '.'):
@@ -494,7 +493,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
exitcode = 1
return exitcode
def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=None):
def updateattrib(session, updateargs, nodetype, noderange, options):
# update attribute
exitcode = 0
if options.clear:
@@ -527,14 +526,6 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
'attributes/all',
value, key)
sys.exit(exitcode)
elif dictassign:
for key in dictassign:
if nodetype == 'nodegroups':
exitcode = session.simple_nodegroups_command(
noderange, 'attributes/all', dictassign[key], key)
else:
exitcode = session.simple_noderange_command(
noderange, 'attributes/all', dictassign[key], key)
else:
if "=" in updateargs[1]:
try:
-216
View File
@@ -37,219 +37,3 @@ alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURR
alias nodesensors='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesensors'
alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesetboot'
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
_confluent_get_args()
{
CMPARGS=($COMP_LINE)
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
CMPARGS+=("")
fi
GENNED=""
for CAND in ${COMP_CANDIDATES[@]}; do
candarray=(${CAND//,/ })
matched=0
for c in "${candarray[@]}"; do
for arg in "${CMPARGS[@]}"; do
if [ "$arg" = "$c" ]; then
matched=1
break
fi
done
done
if [ 0 = $matched ]; then
for c in "${candarray[@]}"; do
GENNED+=" $c"
done
fi
done
}
function _confluent_generic_completion()
{
_confluent_get_args
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodeidentify_completion()
{
COMP_CANDIDATES=("on,off -h")
_confluent_generic_completion
}
_confluent_nodesetboot_completion()
{
COMP_CANDIDATES=("default,cd,network,setup,hd -h -b -p")
_confluent_generic_completion
}
_confluent_nodepower_completion()
{
COMP_CANDIDATES=("boot,off,on,status -h -p")
_confluent_generic_completion
}
_confluent_nodemedia_completion()
{
COMP_CANDIDATES=("list,upload,attach,detachall -h")
_confluent_get_args
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[-2]} == 'upload' ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodefirmware_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodeshell_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -c -- ${COMP_WORDS[-1]}))
return
fi
if [ $NUMARGS -gt 3 ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodesupport_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
compopt -o dirnames
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return
fi
}
_confluent_nn_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
_confluent_nr_completion()
{
CMPARGS=($COMP_LINE)
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
fi
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
#COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
_confluent_ng_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
complete -F _confluent_nr_completion nodeattrib
complete -F _confluent_nr_completion nodebmcreset
complete -F _confluent_nodesetboot_completion nodeboot
complete -F _confluent_nr_completion nodeconfig
complete -F _confluent_nn_completion nodeconsole
complete -F _confluent_nr_completion nodeeventlog
complete -F _confluent_nodefirmware_completion nodefirmware
complete -F _confluent_ng_completion nodegroupattrib
complete -F _confluent_ng_completion nodegroupremove
complete -F _confluent_nr_completion nodehealth
complete -F _confluent_nodeidentify_completion nodeidentify
complete -F _confluent_nr_completion nodeinventory
complete -F _confluent_nr_completion nodelist
complete -F _confluent_nodemedia_completion nodemedia
complete -F _confluent_nodepower_completion nodepower
complete -F _confluent_nr_completion noderemove
complete -F _confluent_nr_completion nodereseat
complete -F _confluent_nodeshell_completion noderun
complete -F _confluent_nr_completion nodesensors
complete -F _confluent_nodesetboot_completion nodesetboot
complete -F _confluent_nodeshell_completion nodeshell
complete -F _confluent_nodesupport_completion nodesupport
+2 -6
View File
@@ -3,7 +3,7 @@ collate(1) -- Organize text input by node
## SYNOPSIS
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r] [-l lognametemplate]`
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r]`
## DESCRIPTION
@@ -26,10 +26,6 @@ node and group names sorted alphanumerically.
Express all but the most common result group in terms of diff from
the most common result group
* `-l`, `--log`:
Save output per node to individual log files, replacing {node} in the name
with the nodename of each
* `-w`, `--watch`:
Update results dynamically as data becomes available, rather than
waiting for the command to fully complete.
@@ -89,4 +85,4 @@ node and group names sorted alphanumerically.
` Processors.ExecuteDisableBit=Enable`
-3
View File
@@ -7,7 +7,6 @@ nodeattrib(8) -- List or change confluent nodes attributes
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
## DESCRIPTION
@@ -41,8 +40,6 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
Clear specified nodeattributes
* `-e`, `--environment`:
Set specified attributes based on exported environment variable of matching name. Environment variable names may be lower case or all upper case. Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
* `-p`, `--prompt`:
Request interactive prompting to provide values rather than the command line or environment variables.
## EXAMPLES
* Listing matching nodes of a simple noderange:
+2 -34
View File
@@ -4,44 +4,12 @@ nodeboot(8) -- Reboot a confluent node to a specific device
## SYNOPSIS
`nodeboot <noderange>`
`nodeboot [options] <noderange>` [default|cd|network|setup|hd]
`nodeboot <noderange>` [net|setup]
## DESCRIPTION
**nodeboot** reboots nodes in a noderange. If an additional argument is given,
it sets the node to specifically boot to that as the next boot. This
performs an immediate reboot without waiting for the OS. To set the boot
device without inducing a reboot, see the `nodesetboot` command.
## OPTIONS
* `-b`, `--bios`:
For a system that supports both BIOS and UEFI style boot, request BIOS style
boot if supported (some platforms will UEFI boot with this flag anyway).
* `-u`, `--uefi`:
This flag does nothing, it is for command compatibility with xCAT's rsetboot
* `-p`, `--persist`:
For a system that supports it, mark the boot override to persist rather than
be a one time change. Many systems do not support this functionality.
* `default`:
Request a normal default boot with no particular device override
* `cd`:
Request boot from media. Note that this can include physical CD,
remote media mounted as CD/DVD, and detachable hard disks drives such as usb
key devices.
* `network`:
Request boot to network
* `setup`:
Request to enter the firmware configuration menu (e.g. F1 setup) on next boot.
* `hd`:
Boot straight to hard disk drive
it sets the node to specifically boot to that as the next boot.
## EXAMPLES
* Booting n3 and n4 to the default boot behavior:
+2 -4
View File
@@ -30,10 +30,8 @@ given as a node expression, as documented in the man page for nodeattribexpressi
Provide detailed data as available. This can include help text and valid
values for a setting.
* `-r`, `--restoredefault`:
Request that the specified component of the targeted nodes will have its
configuration reset to default. Currently the only component implemented
is uefi.
* `-b`, '--batch':
Provide arguments as lines of a file, rather than the command line.
## EXAMPLES
-1
View File
@@ -1,5 +1,4 @@
nodedefine(8) -- Define new confluent nodes
===================================================================
## SYNOPSIS
+1 -1
View File
@@ -4,7 +4,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
## SYNOPSIS
`nodefirmware <noderange>`
`nodefirmware <noderange> list|<components>|core`
`nodefirmware <noderange> <components>|core`
`nodefirmware <noderange> update [--backup] <filename>`
## DESCRIPTION
@@ -7,7 +7,6 @@ nodegroupattrib(8) -- List or change confluent nodegroup attributes
`nodegroupattrib <group> [<nodeattribute>...]`
`nodegroupattrib <group> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
`nodegroupattrib <group> [-c] [<nodeattribute1> <nodeattribute2=value2> ...]`
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
## DESCRIPTION
@@ -1,5 +1,4 @@
nodegroupdefine(8) -- Define new confluent node group
===================================================================
## SYNOPSIS
@@ -7,7 +6,7 @@ nodegroupdefine(8) -- Define new confluent node group
## DESCRIPTION
`nodegroupdefine` allows the definition of a new nodegroup for the confluent management
`nodegroupdefine` allows the definition of a new node for the confluent management
service. It may only define a single group name at a time.
It has the same syntax as `nodegroupattrib(8)`, and the commands differ in
that `nodegroupattrib(8)` will error if a node group does not exist.
+1 -4
View File
@@ -30,10 +30,7 @@ control.
* `-p`, `--persist`:
For a system that supports it, mark the boot override to persist rather than
be a one time change. Many systems do not support this functionality.
* `-u`, `--uefi`:
This flag does nothing, it is for command compatibility with xCAT's rsetboot
* `default`:
Request a normal default boot with no particular device override
-25
View File
@@ -1,25 +0,0 @@
nodesupport(8) -- Utilities for interacting with vendor support
=================================================================
## SYNOPSIS
`nodesupport <noderange> servicedata <directory or filename>`
## DESCRIPTION
`nodesupport` provides capabilities associated with interactiong with support.
Currently it only has the `servicedata` subcommand. `servicedata` takes
an argument that is either a directory name (that can be used for a single node
or multiple nodes) or a file name (only to be used with single node noderange)
## EXAMPLES
* Download support data from a single node to a specific filename
`# nodesupport d1 servicedata svcdata.out`
`d1:initializing: 15%`
* Download support data from multiple nodes to a directory
`# nodesupport d1-d4 servicedata service/`
`d1:initializing: 0% d2:initializing: 0% d3:initializing: 0% d4:initializing: 0%`
`# ls service/`
`d1.svcdata d2.svcdata d3.svcdata d4.svcdata`
+17
View File
@@ -0,0 +1,17 @@
[metadata]
name = confluent_common
summary = Confluent Common Libraries
description-file =
README.txt
author = Jarrod Johnson
author-email = jjohnson2@lenovo.com
home-page = http://xcat.sf.net/
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
[files]
+2 -3
View File
@@ -66,9 +66,8 @@ def join_collective(server, invitation):
'invitation': invitation,
'server': server}})
res = tlvdata.recv(s)
res = res.get('collective',
{'status': 'Unknown response: ' + repr(res)})
print(res.get('status', res.get('error', repr(res))))
print(res.get('collective',
{'status': 'Unknown response: ' + repr(res)})['status'])
def show_collective():
+28 -124
View File
@@ -18,14 +18,12 @@ import base64
import confluent.collective.invites as invites
import confluent.config.configmanager as cfm
import confluent.exceptions as exc
import confluent.log as log
import confluent.tlvdata as tlvdata
import confluent.util as util
import eventlet
import eventlet.green.socket as socket
import eventlet.green.ssl as ssl
import eventlet.green.threading as threading
import greenlet
import random
try:
import OpenSSL.crypto as crypto
@@ -35,41 +33,38 @@ except ImportError:
crypto = None
currentleader = None
cfginitlock = None
follower = None
retrythread = None
class ContextBool(object):
def __init__(self):
self.active = False
self.mylock = threading.RLock()
def __enter__(self):
self.active = True
self.mylock.__enter__()
def __exit__(self, exc_type, exc_val, exc_tb):
self.active = False
self.mylock.__exit__(exc_type, exc_val, exc_tb)
connecting = ContextBool()
leader_init = ContextBool()
def connect_to_leader(cert=None, name=None, leader=None):
global currentleader
global cfginitlock
global follower
if cfginitlock is None:
cfginitlock = threading.RLock()
if leader is None:
leader = currentleader
log.log({'info': 'Attempting connection to leader {0}'.format(leader),
'subsystem': 'collective'})
try:
remote = connect_to_collective(cert, leader)
except socket.error as e:
log.log({'error': 'Collective connection attempt to {0} failed: {1}'
''.format(leader, str(e)),
'subsystem': 'collective'})
except socket.error:
return False
with connecting:
with cfm._initlock:
with cfginitlock:
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
if name is None:
@@ -82,16 +77,10 @@ def connect_to_leader(cert=None, name=None, leader=None):
return False
if 'error' in keydata:
if 'backoff' in keydata:
log.log({
'info': 'Collective initialization in progress on '
'{0}'.format(leader),
'subsystem': 'collective'})
return False
eventlet.spawn_after(random.random(), connect_to_leader,
cert, name, leader)
return True
if 'leader' in keydata:
log.log(
{'info': 'Prospective leader {0} has redirected this '
'member to {1}'.format(leader, keydata['leader']),
'subsystem': 'collective'})
ldrc = cfm.get_collective_member_by_address(
keydata['leader'])
if ldrc and ldrc['name'] == name:
@@ -99,22 +88,13 @@ def connect_to_leader(cert=None, name=None, leader=None):
return connect_to_leader(name=name,
leader=keydata['leader'])
if 'txcount' in keydata:
log.log({'info':
'Prospective leader {0} has inferior '
'transaction count, becoming leader'
''.format(leader), 'subsystem': 'collective',
'subsystem': 'collective'})
return become_leader(remote)
print(keydata['error'])
return False
if follower is not None:
follower.kill()
cfm.stop_following()
follower = None
if follower:
follower.kill()
cfm.stop_following()
follower = None
log.log({'info': 'Following leader {0}'.format(leader),
'subsystem': 'collective'})
colldata = tlvdata.recv(remote)
globaldata = tlvdata.recv(remote)
dbi = tlvdata.recv(remote)
@@ -154,22 +134,11 @@ def connect_to_leader(cert=None, name=None, leader=None):
def follow_leader(remote):
global currentleader
cleanexit = False
try:
cfm.follow_channel(remote)
except greenlet.GreenletExit:
cleanexit = True
finally:
if cleanexit:
log.log({'info': 'Previous following cleanly closed',
'subsystem': 'collective'})
return
log.log({'info': 'Current leader has disappeared, restarting '
'collective membership', 'subsystem': 'collective'})
# The leader has folded, time to startup again...
cfm.stop_following()
currentleader = None
eventlet.spawn_n(start_collective)
cfm.follow_channel(remote)
# The leader has folded, time to startup again...
cfm.stop_following()
currentleader = None
eventlet.spawn_n(start_collective)
def connect_to_collective(cert, member):
@@ -322,8 +291,6 @@ def handle_connection(connection, cert, request, local=False):
f = open('/etc/confluent/cfg/myname', 'w')
f.write(name)
f.close()
log.log({'info': 'Connecting to collective due to join',
'subsystem': 'collective'})
eventlet.spawn_n(connect_to_leader, rsp['collective'][
'fingerprint'], name)
if 'enroll' == operation:
@@ -353,11 +320,6 @@ def handle_connection(connection, cert, request, local=False):
if 'assimilate' == operation:
drone = request['name']
droneinfo = cfm.get_collective_member(drone)
if not droneinfo:
tlvdata.send(connection,
{'error': 'Unrecognized leader, '
'redo invitation process'})
return
if not util.cert_matches(droneinfo['fingerprint'], cert):
tlvdata.send(connection,
{'error': 'Invalid certificate, '
@@ -367,22 +329,8 @@ def handle_connection(connection, cert, request, local=False):
tlvdata.send(connection,
{'error': 'Refusing to be assimilated by inferior'
'transaction count',
'txcount': cfm._txcount,})
'txcount': cfm._txcount})
return
if connecting.active:
# don't try to connect while actively already trying to connect
tlvdata.send(connection, {'status': 0})
connection.close()
return
if (currentleader == connection.getpeername()[0] and
follower and follower.isAlive()):
# if we are happily following this leader already, don't stir
# the pot
tlvdata.send(connection, {'status': 0})
connection.close()
return
log.log({'info': 'Connecting in response to assimilation',
'subsystem': 'collective'})
eventlet.spawn_n(connect_to_leader, None, None,
leader=connection.getpeername()[0])
tlvdata.send(connection, {'status': 0})
@@ -411,11 +359,6 @@ def handle_connection(connection, cert, request, local=False):
connection.close()
return
myself = connection.getsockname()[0]
if connecting.active:
tlvdata.send(connection, {'error': 'Connecting right now',
'backoff': True})
connection.close()
return
if myself != get_leader(connection):
tlvdata.send(
connection,
@@ -423,14 +366,17 @@ def handle_connection(connection, cert, request, local=False):
'in another castle', 'leader': currentleader})
connection.close()
return
if connecting.active:
tlvdata.send(connection, {'error': 'Connecting right now',
'backoff': True})
connection.close()
return
if request['txcount'] > cfm._txcount:
retire_as_leader()
tlvdata.send(connection,
{'error': 'Client has higher tranasaction count, '
'should assimilate me, connecting..',
'txcount': cfm._txcount})
log.log({'info': 'Connecting to leader due to superior '
'transaction count', 'subsystem': collective})
eventlet.spawn_n(connect_to_leader, None, None,
connection.getpeername()[0])
connection.close()
@@ -484,36 +430,11 @@ def try_assimilate(drone):
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
answer = tlvdata.recv(remote)
if not answer:
log.log(
{'error':
'No answer from {0} while trying to assimilate'.format(
drone),
'subsystem': 'collective'})
return
if 'txcount' in answer:
log.log({'info': 'Deferring to {0} due to transaction count'.format(
drone), 'subsystem': 'collective'})
if answer and 'error' in answer:
connect_to_leader(None, None, leader=remote.getpeername()[0])
return
if 'error' in answer:
log.log({
'error': 'Error encountered while attempting to '
'assimilate {0}: {1}'.format(drone, answer['error']),
'subsystem': 'collective'})
return
log.log({'info': 'Assimilated {0} into collective'.format(drone),
'subsystem': 'collective'})
def get_leader(connection):
if currentleader is None or connection.getpeername()[0] == currentleader:
if currentleader is None:
msg = 'Becoming leader as no leader known'
else:
msg = 'Becoming leader because {0} attempted to connect and it ' \
'is current leader'.format(currentleader)
log.log({'info': msg, 'subsystem': 'collective'})
become_leader(connection)
return currentleader
@@ -525,34 +446,27 @@ def retire_as_leader():
def become_leader(connection):
global currentleader
global follower
global retrythread
log.log({'info': 'Becoming leader of collective',
'subsystem': 'collective'})
if follower:
follower.kill()
cfm.stop_following()
follower = None
if retrythread:
retrythread.cancel()
retrythread = None
currentleader = connection.getsockname()[0]
skipaddr = connection.getpeername()[0]
myname = get_myname()
skipem = set(cfm.cfgstreams)
skipem.add(currentleader)
skipem.add(skipaddr)
for member in cfm.list_collective():
dronecandidate = cfm.get_collective_member(member)['address']
if dronecandidate in skipem or member == myname:
if dronecandidate in (currentleader, skipaddr) or member == myname:
continue
eventlet.spawn_n(try_assimilate, dronecandidate)
def startup():
global cfginitlock
members = list(cfm.list_collective())
if len(members) < 2:
# Not in collective mode, return
return
if cfginitlock is None:
cfginitlock = threading.RLock()
eventlet.spawn_n(start_collective)
def start_collective():
@@ -560,15 +474,7 @@ def start_collective():
global retrythread
if follower:
follower.kill()
cfm.stop_following()
follower = None
try:
if cfm.cfgstreams:
cfm.check_quorum()
# Do not start if we have quorum and are leader
return
except exc.DegradedCollective:
pass
if leader_init.active: # do not start trying to connect if we are
# xmitting data to a follower
return
@@ -579,8 +485,6 @@ def start_collective():
if cfm.cfgleader is None:
cfm.stop_following(True)
ldrcandidate = cfm.get_collective_member(member)['address']
log.log({'info': 'Performing startup attempt to {0}'.format(
ldrcandidate), 'subsystem': 'collective'})
if connect_to_leader(name=myname, leader=ldrcandidate):
break
else:
@@ -156,12 +156,6 @@ node = {
'indicates candidate managers, either for '
'high availability or load balancing purposes.')
},
'discovery.passwordrules': {
'description': 'Any specified rules shall be configured on the BMC '
'upon discovery. "expiration=no,loginfailures=no" '
'would disable password expiration and login failures '
'triggering a lockout.'
},
'discovery.policy': {
'description': 'Policy to use for auto-configuration of discovered '
'and identified nodes. Valid values are "manual", '
@@ -82,7 +82,6 @@ _dirtylock = threading.RLock()
_leaderlock = gthread.RLock()
_synclock = threading.RLock()
_rpclock = gthread.RLock()
_initlock = gthread.RLock()
_followerlocks = {}
_config_areas = ('nodegroups', 'nodes', 'usergroups', 'users')
tracelog = None
@@ -95,9 +94,7 @@ _hasquorum = True
_attraliases = {
'bmc': 'hardwaremanagement.manager',
'bmcuser': 'secret.hardwaremanagementuser',
'switchuser': 'secret.hardwaremanagementuser',
'bmcpass': 'secret.hardwaremanagementpassword',
'switchpass': 'secret.hardwaremanagementpassword',
}
def _mkpath(pathname):
@@ -626,7 +623,7 @@ def rollback_clear():
_cfgstore = _oldcfgstore
_oldtxcount = 0
_oldcfgstore = None
ConfigManager.wait_for_sync(True)
ConfigManager._bg_sync_to_file()
def clear_configuration():
@@ -646,13 +643,12 @@ def commit_clear():
global _oldcfgstore
_oldcfgstore = None
_oldtxcount = 0
with _synclock:
todelete = ('transactioncount', 'globals', 'collective') + _config_areas
for cfg in todelete:
try:
os.remove(os.path.join(ConfigManager._cfgdir, cfg))
except OSError as oe:
pass
todelete = _config_areas + ('globals', 'collective', 'transactioncount')
for cfg in todelete:
try:
os.remove(os.path.join(ConfigManager._cfgdir, cfg))
except OSError as oe:
pass
ConfigManager.wait_for_sync(True)
ConfigManager._bg_sync_to_file()
@@ -662,43 +658,41 @@ cfgleader = None
def follow_channel(channel):
global _txcount
global _hasquorum
try:
stop_leading()
stop_following(channel)
lh = StreamHandler(channel)
stop_leading()
stop_following(channel)
lh = StreamHandler(channel)
msg = lh.get_next_msg()
while msg:
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
rpc = ''
while len(rpc) < sz:
nrpc = channel.recv(sz - len(rpc))
if not nrpc:
raise Exception('Truncated message error')
rpc += nrpc
rpc = cPickle.loads(rpc)
if 'txcount' in rpc:
_txcount = rpc['txcount']
if 'function' in rpc:
try:
globals()[rpc['function']](*rpc['args'])
except Exception as e:
print(repr(e))
if 'xid' in rpc and rpc['xid']:
if rpc.get('exc', None):
_pendingchangesets[rpc['xid']].send_exception(rpc['exc'])
else:
_pendingchangesets[rpc['xid']].send()
if 'quorum' in rpc:
_hasquorum = rpc['quorum']
_push_rpc(channel, b'') # use null as ACK
msg = lh.get_next_msg()
while msg:
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
rpc = ''
while len(rpc) < sz:
nrpc = channel.recv(sz - len(rpc))
if not nrpc:
raise Exception('Truncated message error')
rpc += nrpc
rpc = cPickle.loads(rpc)
if 'txcount' in rpc:
_txcount = rpc['txcount']
if 'function' in rpc:
try:
globals()[rpc['function']](*rpc['args'])
except Exception as e:
print(repr(e))
if 'xid' in rpc and rpc['xid']:
if rpc.get('exc', None):
_pendingchangesets[rpc['xid']].send_exception(rpc['exc'])
else:
_pendingchangesets[rpc['xid']].send()
if 'quorum' in rpc:
_hasquorum = rpc['quorum']
_push_rpc(channel, b'') # use null as ACK
msg = lh.get_next_msg()
finally:
# mark the connection as broken
if cfgstreams:
stop_following(None)
else:
stop_following(True)
# mark the connection as broken
if cfgstreams:
stop_following(None)
else:
stop_following(True)
def add_collective_member(name, address, fingerprint):
@@ -953,46 +947,38 @@ class ConfigManager(object):
_nodecollwatchers = {}
_notifierids = {}
@property
def _cfgstore(self):
if self.tenant is None:
return _cfgstore['main']
return _cfgstore['tenant'][self.tenant]
def __init__(self, tenant, decrypt=False, username=None):
global _cfgstore
with _initlock:
if _cfgstore is None:
init()
self.decrypt = decrypt
self.current_user = username
if tenant is None:
self.tenant = None
if 'main' not in _cfgstore:
_cfgstore['main'] = {}
self._bg_sync_to_file()
if 'nodegroups' not in self._cfgstore: # This can happen during a clear... it seams... and if so it messes up...
self._cfgstore['nodegroups'] = {'everything': {'nodes': set()}}
_mark_dirtykey('nodegroups', 'everything', self.tenant)
self._bg_sync_to_file()
if 'nodes' not in self._cfgstore:
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
return
elif 'tenant' not in _cfgstore:
_cfgstore['tenant'] = {tenant: {}}
if _cfgstore is None:
init()
self.decrypt = decrypt
self.current_user = username
if tenant is None:
self.tenant = None
if 'main' not in _cfgstore:
_cfgstore['main'] = {}
self._bg_sync_to_file()
elif tenant not in _cfgstore['tenant']:
_cfgstore['tenant'][tenant] = {}
self._bg_sync_to_file()
self.tenant = tenant
self._cfgstore = _cfgstore['main']
if 'nodegroups' not in self._cfgstore:
self._cfgstore['nodegroups'] = {'everything': {}}
_mark_dirtykey('nodegroups', 'everything', self.tenant)
self._cfgstore['nodegroups'] = {'everything': {'nodes': set()}}
self._bg_sync_to_file()
if 'nodes' not in self._cfgstore:
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
return
elif 'tenant' not in _cfgstore:
_cfgstore['tenant'] = {tenant: {}}
self._bg_sync_to_file()
self.wait_for_sync()
elif tenant not in _cfgstore['tenant']:
_cfgstore['tenant'][tenant] = {}
self._bg_sync_to_file()
self.tenant = tenant
self._cfgstore = _cfgstore['tenant'][tenant]
if 'nodegroups' not in self._cfgstore:
self._cfgstore['nodegroups'] = {'everything': {}}
if 'nodes' not in self._cfgstore:
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
def get_collective_member(self, name):
return get_collective_member(name)
@@ -1240,9 +1226,9 @@ class ConfigManager(object):
def del_user(self, name):
if cfgleader:
return exec_on_leader('_rpc_master_del_user', self.tenant, name)
return exec_on_leader('_rpc_master_del_user', name)
if cfgstreams:
exec_on_followers('_rpc_del_user', self.tenant, name)
exec_on_followers('_rpc_del_user', name)
self._true_del_user(name)
def _true_del_user(self, name):
@@ -2064,13 +2050,9 @@ class ConfigManager(object):
if statelessmode:
return
with cls._syncstate:
if (cls._syncrunning and cls._cfgwriter is not None and
cls._cfgwriter.isAlive()):
if cls._syncrunning:
cls._writepending = True
return
if cls._syncrunning: # This suggests an unclean write attempt,
# do a fullsync as a recovery
fullsync = True
cls._syncrunning = True
# if the thread is exiting, join it to let it close, just in case
if cls._cfgwriter is not None:
@@ -2083,17 +2065,16 @@ class ConfigManager(object):
with _synclock:
if statelessmode:
return
_mkpath(cls._cfgdir)
with open(os.path.join(cls._cfgdir, 'transactioncount'), 'w') as f:
f.write(struct.pack('!Q', _txcount))
if (fullsync or 'dirtyglobals' in _cfgstore and
'globals' in _cfgstore):
if fullsync: # globals is not a given to be set..
if fullsync or 'dirtyglobals' in _cfgstore:
if fullsync:
dirtyglobals = _cfgstore['globals']
else:
with _dirtylock:
dirtyglobals = copy.deepcopy(_cfgstore['dirtyglobals'])
del _cfgstore['dirtyglobals']
_mkpath(cls._cfgdir)
globalf = dbm.open(os.path.join(cls._cfgdir, "globals"), 'c', 384) # 0600
try:
for globalkey in dirtyglobals:
+7 -11
View File
@@ -359,11 +359,8 @@ class ConsoleHandler(object):
except (exc.NotImplementedException, exc.NotFoundException):
self._console = None
except:
if _tracelog:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
else:
print(traceback.format_exc())
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
if not isinstance(self._console, conapi.Console):
self.clearbuffer()
self.connectstate = 'unconnected'
@@ -628,12 +625,12 @@ def start_console_sessions():
configmodule.hook_new_configmanagers(_start_tenant_sessions)
def connect_node(node, configmanager, username=None, direct=True):
def connect_node(node, configmanager, username=None):
attrval = configmanager.get_node_attributes(node, 'collective.manager')
myc = attrval.get(node, {}).get('collective.manager', {}).get(
'value', None)
myname = collective.get_myname()
if myc and myc != collective.get_myname() and direct:
if myc and myc != collective.get_myname():
minfo = configmodule.get_collective_member(myc)
return ProxyConsole(node, minfo, myname, configmanager, username)
consk = (node, configmanager.tenant)
@@ -761,9 +758,10 @@ class ConsoleSession(object):
'get_next_output' non-functional
:param skipreplay: If true, will skip the attempt to redraw the screen
"""
connector = connect_node
def __init__(self, node, configmanager, username, datacallback=None,
skipreplay=False, direct=True):
skipreplay=False):
self.registered = False
self.tenant = configmanager.tenant
if not configmanager.is_node(node):
@@ -771,8 +769,6 @@ class ConsoleSession(object):
self.username = username
self.node = node
self.configmanager = configmanager
self.direct = direct # true if client is directly connected versus
# relay
self.connect_session()
self.registered = True
self._evt = None
@@ -801,7 +797,7 @@ class ConsoleSession(object):
between console and shell.
"""
self.conshdl = connect_node(self.node, self.configmanager,
self.username, self.direct)
self.username)
def send_break(self):
"""Send break to remote system
"""
+7 -44
View File
@@ -201,14 +201,6 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
})
},
},
'_console': {
@@ -241,10 +233,6 @@ def _init_core():
'default': 'ipmi',
}),
},
'description': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'events': {
'hardware': {
'log': PluginRoute({
@@ -346,12 +334,6 @@ def _init_core():
},
},
'support': {
'servicedata': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
}
nodegroupresources = {
@@ -585,15 +567,6 @@ class BadPlugin(object):
self.node, self.plugin + ' is not a supported plugin')
class BadCollective(object):
def __init__(self, node):
self.node = node
def error(self, *args, **kwargs):
yield msg.ConfluentNodeError(
self.node, 'collective mode is active, but collective.manager '
'is not set for this node')
def abbreviate_noderange(configmanager, inputdata, operation):
if operation != 'create':
raise exc.InvalidArgumentException('Must be a create with nodes in list')
@@ -786,7 +759,6 @@ def handle_node_request(configmanager, inputdata, operation,
continue
elif list(cfm.list_collective()):
badcollnodes.append(node)
continue
if plugpath is not None:
try:
hfunc = getattr(pluginmap[plugpath], operation)
@@ -797,8 +769,11 @@ def handle_node_request(configmanager, inputdata, operation,
nodesbyhandler[hfunc].append(node)
else:
nodesbyhandler[hfunc] = [node]
for bn in badcollnodes:
nodesbyhandler[BadCollective(bn).error] = [bn]
if badcollnodes:
raise exc.ConfluentException(
'collective management active, '
'collective.manager must be set for {0}'.format(
','.join(badcollnodes)))
workers = greenpool.GreenPool()
numworkers = 0
for hfunc in nodesbyhandler:
@@ -834,8 +809,6 @@ def iterate_queue(numworkers, passvalues, strip=False):
if nv == 'theend':
completions += 1
else:
if isinstance(nv, Exception):
raise nv
if strip and not isinstance(nv, console.Console):
nv.strip_node(strip)
yield nv
@@ -849,8 +822,6 @@ def addtoqueue(theq, fun, kwargs):
else:
for pv in result:
theq.put(pv)
except Exception as e:
theq.put(e)
finally:
theq.put('theend')
@@ -866,16 +837,8 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
certfile='/etc/confluent/srvcert.pem')
except Exception:
for node in nodes:
if a:
yield msg.ConfluentResourceUnavailable(
node, 'Collective member {0} is unreachable'.format(
a['name']))
else:
yield msg.ConfluentResourceUnavailable(
node,
'"{0}" is not recognized as a collective member'.format(
manager))
yield msg.ConfluentResourceUnavailable(
node, 'Collective member {0} is unreachable'.format(a['name']))
return
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
binary_form=True)):
+2 -11
View File
@@ -86,8 +86,6 @@ import eventlet.greenpool
import eventlet.semaphore
autosensors = set()
scanner = None
class nesteddict(dict):
def __missing__(self, key):
@@ -383,7 +381,6 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
raise exc.InvalidArgumentException()
rescan()
return (msg.KeyValueData({'rescan': 'started'}),)
elif operation in ('update', 'create'):
if 'node' not in inputdata:
raise exc.InvalidArgumentException('Missing node name in input')
@@ -419,8 +416,6 @@ def handle_read_api_request(pathcomponents):
# TODO(jjohnson2): This should be more generalized...
# odd indexes into components are 'by-'*, even indexes
# starting at 2 are parameters to previous index
if pathcomponents == ['discovery', 'rescan']:
return (msg.KeyValueData({'scanning': bool(scanner)}),)
subcats, queryparms, indexof, coll = _parameterize_path(pathcomponents[1:])
if len(pathcomponents) == 1:
dirlist = [msg.ChildCollection(x + '/') for x in sorted(list(subcats))]
@@ -1147,11 +1142,7 @@ def _periodic_recheck(configmanager):
def rescan():
_map_unique_ids()
global scanner
if scanner:
return
else:
scanner = eventlet.spawn(slp.active_scan, safe_detected)
eventlet.spawn_n(slp.active_scan, safe_detected)
def start_detection():
@@ -1203,7 +1194,7 @@ def _map_unique_ids(nodes=None):
uuid_by_nodes = {}
fprint_by_nodes = {}
for uuid in nodes_by_uuid:
if not uuid_is_valid(uuid):
if not uuid_is_valid():
continue
node = nodes_by_uuid[uuid]
if node in bigmap:
@@ -45,7 +45,7 @@ class NodeHandler(generic.NodeHandler):
def config(self, nodename, reset=False):
self._bmcconfig(nodename, reset)
def _bmcconfig(self, nodename, reset=False, customconfig=None):
def _bmcconfig(self, nodename, reset=False):
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
@@ -74,8 +74,6 @@ class NodeHandler(generic.NodeHandler):
ic = self._get_ipmicmd(user, passwd)
else:
raise
if customconfig:
customconfig(ic)
currusers = ic.get_users()
lanchan = ic.get_network_channel()
userdata = ic.xraw_command(netfn=6, command=0x44, data=(lanchan,
@@ -124,18 +122,16 @@ class NodeHandler(generic.NodeHandler):
if currusers[uid]['name'] == newuser:
# Use existing account that has been created
newuserslot = uid
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
break
else:
newuserslot = lockedusers + 1
if newuserslot < 2:
newuserslot = 2
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
ic.set_user_name(newuserslot, newuser)
ic.set_user_access(newuserslot, lanchan,
privilege_level='administrator')
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
# Now to zap others
for uid in currusers:
if uid != newuserslot:
@@ -13,13 +13,11 @@
# limitations under the License.
import confluent.discovery.handlers.imm as immhandler
import confluent.util as util
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.oem.lenovo.imm as imm
class NodeHandler(immhandler.NodeHandler):
devname = 'XCC'
@@ -43,45 +41,16 @@ class NodeHandler(immhandler.NodeHandler):
#if ipmicmd:
# ipmicmd.ipmi_session.logout()
def validate_cert(self, certificate):
# broadly speaking, merely checks consistency moment to moment,
# but if https_cert gets stricter, this check means something
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def set_password_policy(self, ic):
ruleset = {'USER_GlobalMinPassChgInt': '0'}
for rule in self.ruleset.split(','):
if '=' not in rule:
continue
name, value = rule.split('=')
if value.lower() in ('no', 'none', 'disable', 'disabled'):
value = '0'
if name.lower() in ('expiry', 'expiration'):
ruleset['USER_GlobalPassExpPeriod'] = value
if int(value) < 5:
ruleset['USER_GlobalPassExpWarningPeriod'] = value
if name.lower() in ('lockout', 'loginfailures'):
if value.lower() in ('no', 'none', 'disable', 'disabled'):
value = '0'
ruleset['USER_GlobalMaxLoginFailures'] = value
ic.register_key_handler(self.validate_cert)
ic.oem_init()
ic._oem.immhandler.wc.grab_json_response('/api/dataset', ruleset)
def config(self, nodename, reset=False):
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
dpp = self.configmanager.get_node_attributes(
nodename, 'discovery.passwordrules')
self.ruleset = dpp.get(nodename, {}).get(
'discovery.passwordrules', {}).get('value', '')
ic = self._bmcconfig(nodename, customconfig=self.set_password_policy)
ic = self._bmcconfig(nodename)
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
return
# Ok, we can get the enclosure uuid now..
ic.oem_init()
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
enclosureuuid = ic._oem.immhandler.get_property(
-1
View File
@@ -106,7 +106,6 @@ class PubkeyInvalid(ConfluentException):
super(PubkeyInvalid, self).__init__(self, text)
self.fingerprint = fingerprint
self.attrname = attribname
self.message = text
bodydata = {'message': text,
'event': event,
'fingerprint': fingerprint,
+4 -19
View File
@@ -22,24 +22,19 @@ import confluent.exceptions as exc
import confluent.messages as msg
import eventlet
import os
import pwd
import socket
updatesbytarget = {}
uploadsbytarget = {}
downloadsbytarget = {}
updatepool = eventlet.greenpool.GreenPool(256)
def execupdate(handler, filename, updateobj, type, owner, node):
if type != 'ffdc' and not os.path.exists(filename):
def execupdate(handler, filename, updateobj, type):
if not os.path.exists(filename):
errstr = '{0} does not appear to exist on {1}'.format(
filename, socket.gethostname())
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': errstr})
return
if type == 'ffdc' and os.path.isdir(filename):
filename += '/' + node + '.svcdata'
try:
if type == 'firmware':
completion = handler(filename, progress=updateobj.handle_progress,
@@ -48,9 +43,6 @@ def execupdate(handler, filename, updateobj, type, owner, node):
completion = handler(filename, progress=updateobj.handle_progress)
if completion is None:
completion = 'complete'
if owner:
pwent = pwd.getpwnam(owner)
os.chown(filename, pwent.pw_uid, pwent.pw_gid)
updateobj.handle_progress({'phase': completion, 'progress': 100.0})
except exc.PubkeyInvalid as pi:
errstr = 'Certificate mismatch detected, does not match value in ' \
@@ -63,20 +55,18 @@ def execupdate(handler, filename, updateobj, type, owner, node):
class Updater(object):
def __init__(self, node, handler, filename, tenant=None, name=None,
bank=None, type='firmware', owner=None):
bank=None, type='firmware'):
self.bank = bank
self.node = node
self.phase = 'initializing'
self.detail = ''
self.percent = 0.0
self.updateproc = updatepool.spawn(execupdate, handler, filename,
self, type, owner, node)
self, type)
if type == 'firmware':
myparty = updatesbytarget
elif type == 'mediaupload':
myparty = uploadsbytarget
elif type == 'ffdc':
myparty = downloadsbytarget
if (node, tenant) not in myparty:
myparty[(node, tenant)] = {}
if name is None:
@@ -106,8 +96,6 @@ def remove_updates(nodes, tenant, element, type='firmware'):
upid = element[-1]
if type == 'firmware':
myparty = updatesbytarget
elif type == 'ffdc':
myparty = downloadsbytarget
else:
myparty = uploadsbytarget
for node in nodes:
@@ -127,9 +115,6 @@ def list_updates(nodes, tenant, element, type='firmware'):
if type == 'mediaupload':
myparty = uploadsbytarget
verb = 'upload'
elif type == 'ffdc':
verb = 'download'
myparty = downloadsbytarget
else:
myparty = updatesbytarget
verb = 'update'
-12
View File
@@ -416,17 +416,12 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
elif (path[:3] == ['configuration', 'system', 'all'] and
operation != 'retrieve'):
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
elif (path[:3] == ['configuration', 'system', 'clear'] and
operation != 'retrieve'):
return InputConfigClear(path, inputdata, nodes, configmanager)
elif 'inventory/firmware/updates/active' in '/'.join(path) and inputdata:
return InputFirmwareUpdate(path, nodes, inputdata)
elif '/'.join(path).startswith('media/detach'):
return DetachMedia(path, nodes, inputdata)
elif '/'.join(path).startswith('media/') and inputdata:
return InputMedia(path, nodes, inputdata)
elif '/'.join(path).startswith('support/servicedata') and inputdata:
return InputMedia(path, nodes, inputdata)
elif inputdata:
raise exc.InvalidArgumentException(
'No known input handler for request')
@@ -495,13 +490,6 @@ class InputExpression(ConfluentMessage):
nodeattr = deepcopy(self.nodeattribs[node])
return nodeattr
class InputConfigClear(ConfluentMessage):
def __init__(self, path, inputdata, nodes=None, configmanager=None):
if not inputdata:
raise exc.InvalidArgumentException('no request data provided')
if 'clear' not in inputdata or not inputdata['clear']:
raise exc.InvalidArgumentException('Input must be {"clear":true}')
class InputConfigChangeSet(InputExpression):
# For now, this is identical to InputExpression, later it may
# internalize formula expansion, but not now..
+13 -25
View File
@@ -171,7 +171,7 @@ def _extract_neighbor_data_b(args):
args are carried as a tuple, because of eventlet convenience
"""
switch, password, user, force = args[:4]
switch, password, user, force = args
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
now = util.monotonic_time()
if vintage > (now - 60) and not force:
@@ -220,19 +220,17 @@ def _extract_neighbor_data_b(args):
_neighdata[switch] = lldpdata
def update_switch_data(switch, configmanager, force=False, retexc=False):
def update_switch_data(switch, configmanager, force=False):
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
ndr = _extract_neighbor_data(switchcreds + (force, retexc))
if retexc and isinstance(ndr, Exception):
raise ndr
_extract_neighbor_data(switchcreds + (force,))
return _neighdata.get(switch, {})
def update_neighbors(configmanager, force=False, retexc=False):
return _update_neighbors_backend(configmanager, force, retexc)
def update_neighbors(configmanager, force=False):
return _update_neighbors_backend(configmanager, force)
def _update_neighbors_backend(configmanager, force, retexc):
def _update_neighbors_backend(configmanager, force):
global _neighdata
global _neighbypeerid
vintage = _neighdata.get('!!vintage', 0)
@@ -243,7 +241,7 @@ def _update_neighbors_backend(configmanager, force, retexc):
_neighbypeerid = {'!!vintage': now}
switches = netutil.list_switches(configmanager)
switchcreds = netutil.get_switchcreds(configmanager, switches)
switchcreds = [ x + (force, retexc) for x in switchcreds]
switchcreds = [ x + (force,) for x in switchcreds]
pool = GreenPool(64)
for ans in pool.imap(_extract_neighbor_data, switchcreds):
yield ans
@@ -260,15 +258,9 @@ def _extract_neighbor_data(args):
return
try:
with _updatelocks[switch]:
return _extract_neighbor_data_b(args)
except Exception as e:
yieldexc = False
if len(args) >= 5:
yieldexc = args[4]
if yieldexc:
return e
else:
log.logtrace()
_extract_neighbor_data_b(args)
except Exception:
log.logtrace()
if __name__ == '__main__':
# a quick one-shot test, args are switch and snmpv1 string for now
@@ -335,9 +327,7 @@ def _handle_neighbor_query(pathcomponents, configmanager):
# guaranteed
if (parms['by-peerid'] not in _neighbypeerid and
_neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60):
for x in update_neighbors(configmanager, retexc=True):
if isinstance(x, Exception):
raise x
list(update_neighbors(configmanager))
if parms['by-peerid'] not in _neighbypeerid:
raise exc.NotFoundException('No matching peer known')
return _dump_neighbordatum(_neighbypeerid[parms['by-peerid']])
@@ -346,11 +336,9 @@ def _handle_neighbor_query(pathcomponents, configmanager):
if listrequested not in multi_selectors | single_selectors:
raise exc.NotFoundException('{0} is not found'.format(listrequested))
if 'by-switch' in parms:
update_switch_data(parms['by-switch'], configmanager, retexc=True)
update_switch_data(parms['by-switch'], configmanager)
else:
for x in update_neighbors(configmanager, retexc=True):
if isinstance(x, Exception):
raise x
list(update_neighbors(configmanager))
return list_info(parms, listrequested)
@@ -47,7 +47,6 @@ import eventlet.semaphore
import re
_macmap = {}
_apimacmap = {}
_macsbyswitch = {}
_nodesbymac = {}
_switchportmap = {}
@@ -212,7 +211,7 @@ def _map_switch_backend(args):
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
newmacs = {}
_macsbyswitch[switch] = {}
for mac in mactobridge:
# We want to merge it so that when a mac appears in multiple
# places, it is captured.
@@ -224,10 +223,10 @@ def _map_switch_backend(args):
_macmap[mac].append((switch, ifname, maccounts[ifname]))
else:
_macmap[mac] = [(switch, ifname, maccounts[ifname])]
if ifname in newmacs:
newmacs[ifname].append(mac)
if ifname in _macsbyswitch[switch]:
_macsbyswitch[switch][ifname].append(mac)
else:
newmacs[ifname] = [mac]
_macsbyswitch[switch][ifname] = [mac]
nodename = _nodelookup(switch, ifname)
if nodename is not None:
if mac in _nodesbymac and _nodesbymac[mac][0] != nodename:
@@ -239,7 +238,6 @@ def _map_switch_backend(args):
_nodesbymac[mac] = (None, None)
else:
_nodesbymac[mac] = (nodename, maccounts[ifname])
_macsbyswitch[switch] = newmacs
switchbackoff = 30
@@ -297,7 +295,6 @@ def _finish_update(completions):
def _full_updatemacmap(configmanager):
global vintage
global _apimacmap
global _macmap
global _nodesbymac
global _switchportmap
@@ -310,6 +307,7 @@ def _full_updatemacmap(configmanager):
_macmap = {}
_nodesbymac = {}
_switchportmap = {}
_macsbyswitch = {}
if configmanager.tenant is not None:
raise exc.ForbiddenRequest(
'Network topology not available to tenants')
@@ -342,15 +340,11 @@ def _full_updatemacmap(configmanager):
_switchportmap[curswitch][portname] = None
else:
_switchportmap[curswitch][portname] = node
for switch in _macsbyswitch:
if switch not in switches:
del _macsbyswitch[switch]
switchauth = get_switchcreds(configmanager, switches)
pool = GreenPool(64)
for ans in pool.imap(_map_switch, switchauth):
vintage = util.monotonic_time()
yield ans
_apimacmap = _macmap
endtime = util.monotonic_time()
duration = endtime - start
duration = duration * 15 # wait 15 times as long as it takes to walk
@@ -430,7 +424,7 @@ def handle_read_api_request(pathcomponents, configmanager):
elif pathcomponents[2] == 'by-mac':
if len(pathcomponents) == 3:
return [msg.ChildCollection(x.replace(':', '-'))
for x in sorted(list(_apimacmap))]
for x in sorted(list(_macmap))]
elif len(pathcomponents) == 4:
return dump_macinfo(pathcomponents[-1])
elif pathcomponents[2] == 'by-switch':
@@ -463,8 +457,6 @@ def handle_read_api_request(pathcomponents, configmanager):
for x in sorted(maclist)]
if len(pathcomponents) == 8:
return dump_macinfo(pathcomponents[-1])
elif pathcomponents[2] == 'rescan':
return [msg.KeyValueData({'scanning': mapupdating.locked()})]
raise exc.NotFoundException('Unrecognized path {0}'.format(
'/'.join(pathcomponents)))
@@ -19,14 +19,12 @@ import confluent.firmwaremanager as firmwaremanager
import confluent.interface.console as conapi
import confluent.messages as msg
import confluent.util as util
import copy
import eventlet
import eventlet.event
import eventlet.green.threading as threading
import eventlet.greenpool as greenpool
import eventlet.queue as queue
import eventlet.support.greendns
from fnmatch import fnmatch
import pyghmi.constants as pygconstants
import pyghmi.exceptions as pygexc
console = eventlet.import_patched('pyghmi.ipmi.console')
@@ -34,37 +32,6 @@ ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
import socket
import ssl
pci_cache = {}
def get_dns_txt(qstring):
return eventlet.support.greendns.resolver.query(
qstring, 'TXT')[0].strings[0].replace('i=', '')
def get_pci_text_from_ids(subdevice, subvendor, device, vendor):
fqpi = '{0}.{1}.{2}.{3}'.format(subdevice, subvendor, device, vendor)
if fqpi in pci_cache:
return pci_cache[fqpi]
vendorstr = None
try:
vendorstr = get_dns_txt('{0}.pci.id.ucw.cz'.format(subvendor))
except Exception:
try:
vendorstr = get_dns_txt('{0}.pci.id.ucw.cz'.format(vendor))
except Exception:
pass
devstr = None
try:
devstr = get_dns_txt(fqpi + '.pci.id.ucw.cz')
except Exception:
try:
devstr = get_dns_txt('{0}.{1}.pci.id.ucw.cz'.format(
device, vendor))
except Exception:
pass
if vendorstr and devstr:
pci_cache[fqpi] = vendorstr, devstr
return vendorstr, devstr
# There is something not right with the RLocks used in pyghmi when
# eventlet comes into play. It seems like sometimes on acquire,
@@ -303,17 +270,13 @@ class IpmiConsole(conapi.Console):
kg=self.kg, force=True,
iohandler=self.handle_data)
self.solconnection.outputlock = NullLock()
while (self.solconnection and not self.solconnection.connected and
not (self.broken or self.solconnection.broken or
self.solconnection.ipmi_session.broken)):
while not self.solconnection.connected and not self.broken:
w = eventlet.event.Event()
_ipmiwaiters.append(w)
w.wait(15)
if (self.broken or not self.solconnection or
self.solconnection.broken or
self.solconnection.ipmi_session.broken):
if not self.error:
self.error = 'Unknown error'
w.wait()
if self.broken:
break
if self.broken:
if (self.error.startswith('Incorrect password') or
self.error.startswith('Unauthorized name')):
raise exc.TargetEndpointBadCredentials
@@ -418,7 +381,6 @@ class IpmiHandler(object):
self.error = None
eventlet.sleep(0)
self.cfg = cfd[node]
self.current_user = cfg.current_user
self.loggedin = False
self.node = node
self.element = element
@@ -429,8 +391,7 @@ class IpmiHandler(object):
self.tenant = cfg.tenant
tenant = cfg.tenant
if ((node, tenant) not in persistent_ipmicmds or
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged or
persistent_ipmicmds[(node, tenant)].ipmi_session.broken):
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged):
try:
persistent_ipmicmds[(node, tenant)].close_confluent()
except KeyError: # was no previous session
@@ -477,10 +438,6 @@ class IpmiHandler(object):
self.output.put(msg.ConfluentTargetTimeout(
self.node, self.error))
return
elif 'Invalid Session ID' in self.error:
self.output.put(msg.ConfluentTargetTimeout(
self.node, 'Temporary Login Error'))
return
elif ('Unauthorized' in self.error or
'Incorrect password' in self.error):
self.output.put(
@@ -520,10 +477,6 @@ class IpmiHandler(object):
self.decode_alert()
elif self.element == ['console', 'license']:
self.handle_license()
elif self.element == ['support', 'servicedata']:
self.handle_servicedata_fetch()
elif self.element == ['description']:
self.handle_description()
else:
raise Exception('Not Implemented')
@@ -543,14 +496,6 @@ class IpmiHandler(object):
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.ipmicmd.get_diagnostic_data,
self.inputdata.filename, self.tenant, type='ffdc',
owner=self.current_user)
self.output.put(msg.CreatedResource(
'nodes/{0}/support/servicedata/{1}'.format(self.node, u.name)))
def handle_attach_media(self):
try:
self.ipmicmd.attach_remote_media(self.inputdata.filename)
@@ -583,10 +528,6 @@ class IpmiHandler(object):
return self.handle_ntp()
elif self.element[1:3] == ['system', 'all']:
return self.handle_sysconfig()
elif self.element[1:3] == ['system', 'advanced']:
return self.handle_sysconfig(True)
elif self.element[1:3] == ['system', 'clear']:
return self.handle_sysconfigclear()
raise Exception('Not implemented')
def decode_alert(self):
@@ -873,14 +814,12 @@ class IpmiHandler(object):
if component == 'all':
for invdata in self.ipmicmd.get_inventory():
if invdata[1] is None:
newinf = {'present': False, 'information': None,
'name': invdata[0]}
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata[1])
newinf = {'present': True, 'information': invdata[1]}
newinf['name'] = invdata[1].get('name', invdata[0])
self.add_invitem(invitems, newinf)
newinf['name'] = invdata[0]
invitems.append(newinf)
else:
self.make_inventory_map()
compname = self.invmap.get(component, None)
@@ -889,13 +828,12 @@ class IpmiHandler(object):
return
invdata = self.ipmicmd.get_inventory_of_component(compname)
if invdata is None:
newinf = {'present': False, 'information': None,
'name': compname}
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata)
newinf = {'present': True, 'information': invdata,
'name': invdata.get('name', compname)}
self.add_invitem(invitems, newinf)
newinf = {'present': True, 'information': invdata}
newinf['name'] = compname
invitems.append(newinf)
except ssl.SSLEOFError:
errorneeded = msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
@@ -912,24 +850,6 @@ class IpmiHandler(object):
if errorneeded:
self.output.put(errorneeded)
def add_invitem(self, invitems, newinf):
if newinf.get('information', None) and 'name' in newinf['information']:
newinf = copy.deepcopy(newinf)
del newinf['information']['name']
if fnmatch(newinf['name'], 'Adapter ??:??:??') or fnmatch(
newinf['name'], 'PCIeGen? x*'):
myinf = newinf.get('information', {})
sdid = myinf.get('PCI Subsystem Device ID', None)
svid = myinf.get('PCI Subsystem Vendor ID', None)
did = myinf.get('PCI Device ID', None)
vid = myinf.get('PCI Vendor ID', None)
vstr, dstr = get_pci_text_from_ids(sdid, svid, did, vid)
if vstr:
newinf['information']['PCI Vendor'] = vstr
if dstr:
newinf['name'] = dstr
invitems.append(newinf)
def handle_sensors(self):
if self.element[-1] == '':
self.element = self.element[:-1]
@@ -1100,17 +1020,10 @@ class IpmiHandler(object):
self.ipmicmd.set_domain_name(dn)
return
def handle_sysconfigclear(self):
if 'read' == self.op:
raise exc.InvalidArgumentException(
'Cannot read the "clear" resource')
self.ipmicmd.clear_system_configuration()
def handle_sysconfig(self, advanced=False):
def handle_sysconfig(self):
if 'read' == self.op:
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration(
hideadvanced=not advanced)))
self.node, self.ipmicmd.get_system_configuration()))
elif 'update' == self.op:
self.ipmicmd.set_system_configuration(
self.inputdata.get_attributes(self.node))
@@ -1165,11 +1078,6 @@ class IpmiHandler(object):
self.output.put(msg.License(self.node, available))
return
def handle_description(self):
dsc = self.ipmicmd.get_description()
self.output.put(msg.KeyValueData(dsc, self.node))
def _str_health(health):
if isinstance(health, str):
return health
@@ -1214,9 +1122,6 @@ def retrieve(nodes, element, configmanager, inputdata):
elif '/'.join(element).startswith('media/uploads'):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element, 'mediaupload')
elif '/'.join(element).startswith('support/servicedata'):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element, 'ffdc')
else:
return perform_requests('read', nodes, element, configmanager, inputdata)
@@ -1228,8 +1133,5 @@ def delete(nodes, element, configmanager, inputdata):
elif '/'.join(element).startswith('media/uploads'):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element, type='mediaupload')
elif '/'.join(element).startswith('support/servicedata'):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element, type='ffdc')
return perform_requests(
'delete', nodes, element, configmanager, inputdata)
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2018 Lenovo
# 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.
@@ -22,22 +22,9 @@
import confluent.exceptions as cexc
import confluent.interface.console as conapi
import confluent.log as log
import cryptography
import eventlet
import hashlib
import sys
sys.modules['gssapi'] = None
paramiko = eventlet.import_patched('paramiko')
warnhostkey = False
if cryptography.__version__.split('.') < ['1', '5']:
# older cryptography with paramiko breaks most key support except
# ed25519
warnhostkey = True
paramiko.transport.Transport._preferred_keys = filter(
lambda x: 'ed25519' in x,
paramiko.transport.Transport._preferred_keys)
class HostKeyHandler(paramiko.client.MissingHostKeyPolicy):
@@ -123,26 +110,6 @@ class SshShell(conapi.Console):
self.username = ''
self.password = ''
self.datacallback('\r\nlogin as: ')
return
except cexc.PubkeyInvalid as pi:
self.keyaction = ''
self.candidatefprint = pi.fingerprint
self.datacallback(pi.message)
self.keyattrname = pi.attrname
self.datacallback('\r\nNew fingerprint: ' + pi.fingerprint)
self.inputmode = -1
self.datacallback('\r\nEnter "disconnect" or "accept": ')
return
except paramiko.SSHException as pi:
self.inputmode = -2
warn = str(pi)
if warnhostkey:
warn += ' (Older cryptography package on this host only ' \
'works with ed25519, check ssh startup on target ' \
'and permissions on /etc/ssh/*key)\r\n' \
'Press Enter to close...'
self.datacallback('\r\n' + warn)
return
self.inputmode = 2
self.connected = True
@@ -150,36 +117,7 @@ class SshShell(conapi.Console):
self.rxthread = eventlet.spawn(self.recvdata)
def write(self, data):
if self.inputmode == -2:
self.datacallback(conapi.ConsoleEvent.Disconnect)
return
elif self.inputmode == -1:
while len(data) and data[0] == b'\x7f' and len(self.keyaction):
self.datacallback('\b \b') # erase previously echoed value
self.keyaction = self.keyaction[:-1]
data = data[1:]
while len(data) and data[0] == b'\x7f':
data = data[1:]
while b'\x7f' in data:
delidx = data.index(b'\x7f')
data = data[:delidx - 1] + data[delidx + 1:]
self.keyaction += data
if '\r' in self.keyaction:
action = self.keyaction.split('\r')[0]
if action.lower() == 'accept':
self.nodeconfig.set_node_attributes(
{self.node:
{self.keyattrname: self.candidatefprint}})
self.datacallback('\r\n')
self.logon()
elif action.lower() == 'disconnect':
self.datacallback(conapi.ConsoleEvent.Disconnect)
else:
self.keyaction = ''
self.datacallback('\r\nEnter "disconnect" or "accept": ')
elif len(data) > 0:
self.datacallback(data)
elif self.inputmode == 0:
if self.inputmode == 0:
while len(data) and data[0] == b'\x7f' and len(self.username):
self.datacallback('\b \b') # erase previously echoed value
self.username = self.username[:-1]
+4 -9
View File
@@ -92,17 +92,12 @@ class Session(object):
errstr, errnum, erridx, answers = rsp
if errstr:
errstr = str(errstr)
finerr = errstr + ' while trying to connect to ' \
'{0}'.format(self.server)
if errstr in ('Unknown USM user', 'unknownUserName',
'wrongDigest', 'Wrong SNMP PDU digest'):
raise exc.TargetEndpointBadCredentials(finerr)
if errstr in ('unknownUserName', 'wrongDigest'):
raise exc.TargetEndpointBadCredentials(errstr)
# need to do bad credential versus timeout
raise exc.TargetEndpointUnreachable(finerr)
raise exc.TargetEndpointUnreachable(errstr)
elif errnum:
raise exc.ConfluentException(errnum.prettyPrint() +
' while trying to connect to '
'{0}'.format(self.server))
raise exc.ConfluentException(errnum.prettyPrint())
for ans in answers:
if not obj[0].isPrefixOf(ans[0]):
# PySNMP returns leftovers in a bulk command
+1 -11
View File
@@ -144,15 +144,6 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
send_data(connection, {'authpassed': 1})
request = tlvdata.recv(connection)
if 'collective' in request and skipauth:
if not libssl:
tlvdata.send(
connection,
{'collective': {'error': 'Server either does not have '
'python-pyopenssl installed or has an '
'incorrect version installed '
'(e.g. pyOpenSSL would need to be '
'replaced with python-pyopenssl)'}})
return
return collective.handle_connection(connection, None, request['collective'],
local=True)
while request is not None:
@@ -238,8 +229,7 @@ def start_proxy_term(connection, cert, request):
ccons = ClientConsole(connection)
consession = consoleserver.ConsoleSession(
node=request['node'], configmanager=cfm, username=request['user'],
datacallback=ccons.sendall, skipreplay=request['skipreplay'],
direct=False)
datacallback=ccons.sendall, skipreplay=request['skipreplay'])
term_interact(None, None, ccons, None, connection, consession, None)
def start_term(authname, cfm, connection, params, path, authdata, skipauth):
+22
View File
@@ -0,0 +1,22 @@
[metadata]
name = confluent_server
summary = Confluent systems management daemon
description-file =
README.txt
author = Jarrod Johnson
author-email = jbjohnso@us.ibm.com
home-page = http://xcat.sf.net/
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
[files]
packages =
confluent
confluent/interface
confluent/config
+1 -1
View File
@@ -4,7 +4,7 @@ Description=Confluent hardware manager
[Service]
Type=forking
#PIDFile=/var/run/confluent/pid
PIDFile=/var/run/confluent/pid
ExecStart=/opt/confluent/bin/confluent
ExecStop=/opt/confluent/bin/confetty shutdown /
Restart=on-failure
+38
View File
@@ -0,0 +1,38 @@
function getRequest(url, success) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.setRequestHeader('Accept', 'application/json');
request.onload = function() {
if (this.status >= 200 && this.status <= 400) {
success(JSON.parse(this.responseText));
}
};
request.send();
}
document.addEventListener('DOMContentLoaded', function() {
getRequest("/confluent-api/nodes/", function( data) {
var items = [];
var options = [];
var nodename = "";
data["_links"]["item"].forEach( function( val, key ) {
console.log(val);
if (typeof(val) == "object") {
nodename = val.href;
} else {
nodename = val;
}
console.log(nodename);
nodename = nodename.replace('/', '');
var myrow = document.createElement('div');
myrow.innerHTML = "<button id="+nodename+">"+nodename+"</button><br>";
document.getElementById("nodes").appendChild(myrow);
document.getElementById(nodename).addEventListener("click", function( event ) {
var tname = this.id;
var url = "/confluent-api/nodes/" + tname + "/console/session";
new ConsoleWindow(url, tname);
});
});
});
}); // end document
+742
View File
@@ -0,0 +1,742 @@
/**
* tty.js
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* Copyright 2014, IBM Corporation
* Copyright 2014, Lenovo
*/
;(function() {
'use strict';
/**
* Elements
*/
var document = this.document
, window = this
, root
, body
, h1
, open
, lights;
/**
* Helpers
*/
var EventEmitter = Terminal.EventEmitter
, inherits = Terminal.inherits
, on = Terminal.on
, off = Terminal.off
, cancel = Terminal.cancel;
function postRequest(url, data, success) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
success(JSON.parse(this.responseText));
}
};
if (data) {
request.send(JSON.stringify(data));
} else {
request.send("");
}
request = null;
}
/**
* Console
*/
function ConsoleWindow(consoleurl, nodename) {
var self = this;
if (!(this instanceof ConsoleWindow)) {
return new ConsoleWindow(consoleurl, nodename);
}
EventEmitter.call(this);
var el
, grip
, bar
, button
, title;
el = document.createElement('div');
el.className = 'window';
grip = document.createElement('div');
grip.className = 'grip';
bar = document.createElement('div');
bar.className = 'bar';
button = document.createElement('div');
button.innerHTML = 'x';
button.title = 'close';
button.className = 'tab';
title = document.createElement('div');
title.className = 'title';
title.innerHTML = nodename;
this.nodename = nodename;
this.element = el;
this.grip = grip;
this.bar = bar;
this.button = button;
this.title = title;
this.consoleurl = consoleurl;
this.tabs = [];
this.focused = null;
this.cols = 100; //Terminal.geometry[0];
this.rows = 30; //Terminal.geometry[1];
el.appendChild(grip);
el.appendChild(bar);
bar.appendChild(title);
bar.appendChild(button);
document.body.appendChild(el);
//tty.windows.push(this);
this.createTab();
this.focus();
this.bind();
this.tabs[0].once('open', function() {
//tty.emit('open window', self);
self.emit('open');
});
}
inherits(ConsoleWindow, EventEmitter);
ConsoleWindow.prototype.bind = function() {
var self = this
, el = this.element
, bar = this.bar
, grip = this.grip
, button = this.button
, last = 0;
on(button, 'click', function(ev) {
self.destroy();
return cancel(ev);
});
on(grip, 'mousedown', function(ev) {
self.focus();
self.resizing(ev);
return cancel(ev);
});
on(el, 'mousedown', function(ev) {
if (ev.target !== el && ev.target !== bar) return;
self.focus();
cancel(ev);
if (new Date - last < 600) {
return self.maximize();
}
last = new Date;
self.drag(ev);
return cancel(ev);
});
};
ConsoleWindow.prototype.focus = function() {
// Restack
var parent = this.element.parentNode;
if (parent) {
parent.removeChild(this.element);
parent.appendChild(this.element);
}
// Focus Foreground Tab
this.focused.focus();
//tty.emit('focus window', this);
this.emit('focus');
};
ConsoleWindow.prototype.destroy = function() {
if (this.destroyed) return;
this.destroyed = true;
if (this.minimize) this.minimize();
//splice(tty.windows, this);
//if (tty.windows.length) tty.windows[0].focus();
this.element.parentNode.removeChild(this.element);
this.each(function(term) {
term.destroy();
});
//tty.emit('close window', this);
this.emit('close');
};
ConsoleWindow.prototype.drag = function(ev) {
var self = this
, el = this.element;
if (this.minimize) return;
var drag = {
left: el.offsetLeft,
top: el.offsetTop,
pageX: ev.pageX,
pageY: ev.pageY
};
el.style.opacity = '0.60';
el.style.cursor = 'move';
document.documentElement.style.cursor = 'move';
function move(ev) {
el.style.left =
(drag.left + ev.pageX - drag.pageX) + 'px';
var tmptop = (drag.top + ev.pageY - drag.pageY);
if (tmptop < 0) {
tmptop = 0;
}
el.style.top = tmptop + 'px';
}
function up() {
el.style.opacity = '';
el.style.cursor = '';
document.documentElement.style.cursor = '';
off(document, 'mousemove', move);
off(document, 'mouseup', up);
var ev = {
left: el.style.left.replace(/\w+/g, ''),
top: el.style.top.replace(/\w+/g, '')
};
//tty.emit('drag window', self, ev);
self.emit('drag', ev);
}
on(document, 'mousemove', move);
on(document, 'mouseup', up);
};
ConsoleWindow.prototype.resizing = function(ev) {
var self = this
, el = this.element
, term = this.focused;
if (this.minimize) delete this.minimize;
var resize = {
w: el.clientWidth,
h: el.clientHeight
};
el.style.overflow = 'hidden';
el.style.opacity = '0.70';
el.style.cursor = 'se-resize';
document.documentElement.style.cursor = 'se-resize';
term.element.style.height = '100%';
function move(ev) {
var x, y;
y = el.offsetHeight - term.element.clientHeight;
x = ev.pageX - el.offsetLeft;
y = (ev.pageY - el.offsetTop) - y;
el.style.width = x + 'px';
el.style.height = y + 'px';
}
function up() {
var x, y;
x = el.clientWidth / resize.w;
y = el.clientHeight / resize.h;
x = (x * term.cols) | 0;
y = (y * term.rows) | 0;
self.resize(x, y);
el.style.width = '';
el.style.height = '';
el.style.overflow = '';
el.style.opacity = '';
el.style.cursor = '';
document.documentElement.style.cursor = '';
term.element.style.height = '';
off(document, 'mousemove', move);
off(document, 'mouseup', up);
}
on(document, 'mousemove', move);
on(document, 'mouseup', up);
};
ConsoleWindow.prototype.maximize = function() {
if (this.minimize) return this.minimize();
var self = this
, el = this.element
, term = this.focused
, x
, y;
var m = {
cols: term.cols,
rows: term.rows,
left: el.offsetLeft,
top: el.offsetTop,
root: root.className
};
this.minimize = function() {
delete this.minimize;
el.style.left = m.left + 'px';
el.style.top = m.top + 'px';
el.style.width = '';
el.style.height = '';
term.element.style.width = '';
term.element.style.height = '';
el.style.boxSizing = '';
self.grip.style.display = '';
root.className = m.root;
self.resize(m.cols, m.rows);
//tty.emit('minimize window', self);
self.emit('minimize');
};
window.scrollTo(0, 0);
x = root.clientWidth / term.element.offsetWidth;
y = root.clientHeight / term.element.offsetHeight;
x = (x * term.cols) | 0;
y = (y * term.rows) | 0;
el.style.left = '0px';
el.style.top = '0px';
el.style.width = '100%';
el.style.height = '100%';
term.element.style.width = '100%';
term.element.style.height = '100%';
el.style.boxSizing = 'border-box';
this.grip.style.display = 'none';
root.className = 'maximized';
this.resize(x, y);
//tty.emit('maximize window', this);
this.emit('maximize');
};
ConsoleWindow.prototype.resize = function(cols, rows) {
this.cols = cols;
this.rows = rows;
this.each(function(term) {
term.resize(cols, rows);
});
//tty.emit('resize window', this, cols, rows);
this.emit('resize', cols, rows);
};
ConsoleWindow.prototype.each = function(func) {
var i = this.tabs.length;
while (i--) {
func(this.tabs[i], i);
}
};
ConsoleWindow.prototype.createTab = function() {
return new Tab(this, this.consoleurl);
};
ConsoleWindow.prototype.highlight = function() {
var self = this;
this.element.style.borderColor = 'orange';
setTimeout(function() {
self.element.style.borderColor = '';
}, 200);
this.focus();
};
ConsoleWindow.prototype.focusTab = function(next) {
var tabs = this.tabs
, i = indexOf(tabs, this.focused)
, l = tabs.length;
if (!next) {
if (tabs[--i]) return tabs[i].focus();
if (tabs[--l]) return tabs[l].focus();
} else {
if (tabs[++i]) return tabs[i].focus();
if (tabs[0]) return tabs[0].focus();
}
return this.focused && this.focused.focus();
};
ConsoleWindow.prototype.nextTab = function() {
return this.focusTab(true);
};
ConsoleWindow.prototype.previousTab = function() {
return this.focusTab(false);
};
/**
* Tab
*/
function Tab(win, consoleurl) {
var self = this;
var cols = win.cols
, rows = win.rows;
Terminal.call(this, {
cols: cols,
rows: rows
});
var button = document.createElement('div');
button.className = 'tab';
button.innerHTML = '\u2022';
//win.bar.appendChild(button);
on(button, 'click', function(ev) {
if (ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) {
self.destroy();
} else {
self.focus();
}
return cancel(ev);
});
this.id = '';
this.consoleurl = consoleurl;
this.clientcount = 0;
this.connectstate = 'unconnected';
this.lasterror = ''
this.window = win;
this.button = button;
this.element = null;
this.process = '';
this.open();
this.hookKeys();
// Now begins the code that will embarass me when I actually know my way
// around javascript -jbjohnso
this.sessid = '';
this.datapending = false;
this.waitingdata = false;
this.sentdata = function(data, textStatus, jqXHR) {
if (this.waitingdata) {
postRequest(consoleurl, { session: this.sessid, bytes: this.waitingdata }, this.sentdata);
this.waitingdata = false;
} else {
this.datapending = false;
}
}.bind(this);
this.on('data', function(data) {
// Send data to console from terminal
if (this.datapending) {
if (!this.waitingdata) {
this.waitingdata = data;
} else {
this.waitingdata = this.waitingdata + data;
}
return;
}
this.datapending = true;
postRequest(consoleurl, { session: this.sessid, bytes: data }, this.sentdata);
}.bind(this));
this.gotdata = function(data, textStatus, jqXHR) {
if ("data" in data) {
this.write(data.data);
}
var updatetitle = false;
var updateinfo = [];
if ("connectstate" in data) {
updatetitle = true;
this.connectstate = data.connectstate;
}
if (this.connectstate != "connected") {
updateinfo.push(this.connectstate);
} else {
self.lasterror = '';
}
if ("error" in data) {
updatetitle = true;
this.lasterror = data.error
}
if (this.lasterror != '') {
updateinfo.push(this.lasterror);
}
if ("clientcount" in data) {
updatetitle = true;
this.clientcount = data.clientcount;
}
if (this.clientcount > 1) {
updateinfo.push("clients: " + this.clientcount.toString());
}
if (updatetitle == true) {
if (updateinfo.length > 0) {
this.window.title.innerHTML = this.window.nodename + " [" + updateinfo.join() + "]";
} else {
this.window.title.innerHTML = this.window.nodename;
}
}
postRequest(consoleurl, { session: this.sessid }, this.gotdata);
}.bind(this);
this.gotsession = function(data, textStatus, jqXHR) {
this.sessid = data.session
postRequest(consoleurl, { session: this.sessid }, this.gotdata);
}.bind(this);
postRequest(consoleurl, false, this.gotsession);
win.tabs.push(this);
};
inherits(Tab, Terminal);
Tab.prototype._write = Tab.prototype.write;
Tab.prototype.write = function(data) {
if (this.window.focused !== this) this.button.style.color = 'red';
return this._write(data);
};
Tab.prototype._focus = Tab.prototype.focus;
Tab.prototype.focus = function() {
if (Terminal.focus === this) return;
var win = this.window;
// maybe move to Tab.prototype.switch
if (win.focused !== this) {
if (win.focused) {
if (win.focused.element.parentNode) {
win.focused.element.parentNode.removeChild(win.focused.element);
}
win.focused.button.style.fontWeight = '';
}
win.element.appendChild(this.element);
win.focused = this;
//win.title.innerHTML = this.process;
this.button.style.fontWeight = 'bold';
this.button.style.color = '';
}
this._focus();
win.focus();
//tty.emit('focus tab', this);
this.emit('focus');
};
Tab.prototype._resize = Tab.prototype.resize;
Tab.prototype.resize = function(cols, rows) {
//this.socket.emit('resize', this.id, cols, rows);
this._resize(cols, rows);
//tty.emit('resize tab', this, cols, rows);
this.emit('resize', cols, rows);
};
Tab.prototype.__destroy = Tab.prototype.destroy;
Tab.prototype._destroy = function() {
if (this.destroyed) return;
this.destroyed = true;
var win = this.window;
this.button.parentNode.removeChild(this.button);
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
if (tty.terms[this.id]) delete tty.terms[this.id];
splice(win.tabs, this);
if (win.focused === this) {
win.previousTab();
}
if (!win.tabs.length) {
win.destroy();
}
this.__destroy();
};
Tab.prototype.destroy = function() {
if (this.destroyed) return;
//TODO: politely let server know of client closure
this._destroy();
//tty.emit('close tab', this);
this.emit('close');
};
Tab.prototype.hookKeys = function() {
var self = this;
// Alt-[jk] to quickly swap between windows.
this.on('key', function(key, ev) {
if (Terminal.focusKeys === false) {
return;
}
var offset
, i;
if (key === '\x1bj') {
offset = -1;
} else if (key === '\x1bk') {
offset = +1;
} else {
return;
}
i = indexOf(tty.windows, this.window) + offset;
this._ignoreNext();
if (tty.windows[i]) return tty.windows[i].highlight();
if (offset > 0) {
if (tty.windows[0]) return tty.windows[0].highlight();
} else {
i = tty.windows.length - 1;
if (tty.windows[i]) return tty.windows[i].highlight();
}
return this.window.highlight();
});
this.on('request paste', function(key) {
this.socket.emit('request paste', function(err, text) {
if (err) return;
self.send(text);
});
});
this.on('request create', function() {
this.window.createTab();
});
this.on('request term', function(key) {
if (this.window.tabs[key]) {
this.window.tabs[key].focus();
}
});
this.on('request term next', function(key) {
this.window.nextTab();
});
this.on('request term previous', function(key) {
this.window.previousTab();
});
};
Tab.prototype._ignoreNext = function() {
// Don't send the next key.
var handler = this.handler;
this.handler = function() {
this.handler = handler;
};
var showCursor = this.showCursor;
this.showCursor = function() {
this.showCursor = showCursor;
};
};
/**
* Program-specific Features
*/
Tab.prototype._bindMouse = Tab.prototype.bindMouse;
Tab.prototype.bindMouse = function() {
if (!Terminal.programFeatures) return this._bindMouse();
var self = this;
var wheelEvent = 'onmousewheel' in window
? 'mousewheel'
: 'DOMMouseScroll';
on(self.element, wheelEvent, function(ev) {
if (self.mouseEvents) return;
if ((ev.type === 'mousewheel' && ev.wheelDeltaY > 0)
|| (ev.type === 'DOMMouseScroll' && ev.detail < 0)) {
// page up
self.keyDown({keyCode: 33});
} else {
// page down
self.keyDown({keyCode: 34});
}
return cancel(ev);
});
return this._bindMouse();
};
/**
* Helpers
*/
function indexOf(obj, el) {
var i = obj.length;
while (i--) {
if (obj[i] === el) return i;
}
return -1;
}
function splice(obj, el) {
var i = indexOf(obj, el);
if (~i) obj.splice(i, 1);
}
function sanitize(text) {
if (!text) return '';
return (text + '').replace(/[&<>]/g, '')
}
this.ConsoleWindow = ConsoleWindow;
}).call(function() {
return this;
}());
File diff suppressed because it is too large Load Diff