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

Compare commits

...

101 Commits

Author SHA1 Message Date
Jarrod Johnson 6973736c6a Set password before setting username
The BMC is at much higher risk for rejecting the password
(e.g. the password does not pass complexity requirements).  If the
username changes, but the password is still default, it is very
confusing.  Give the password change the chance to break the
configuration process first.
2018-10-23 09:16:15 -04:00
Jarrod Johnson f9055a258e Provide specific completion behavior for noderun/nodeshell 2018-10-23 09:09:03 -04:00
Jarrod Johnson c784a4ec9b Fix noderun output of continuing commands
noderun always only output at the end, fix the
mistake in the select statement.
2018-10-18 15:43:57 -04:00
Jarrod Johnson f2dd501de9 Do not proceed to try to upload if file doesn't exist
After relaying the error, it went ahead and
attempted the update, contrary to any reasonable expectation.
2018-10-15 11:15:13 -04:00
Jarrod Johnson e9ba49a4aa Intercept another 'unexpected error'
During a particularly hectic init, Invalid Session ID
may occur if a command is ran particularly early.  Intercept
and replace a more clean message.
2018-10-12 15:46:54 -04:00
Jarrod Johnson deb90fbca9 Fix trace on early console connect
If the trace happens before tracelog is ready, just print the output
to the stdout log for now.
2018-10-12 14:56:54 -04:00
Jarrod Johnson 3105b9b1f9 Significantly rework the collective startup behavior
One, make the tracking bools enforce a lock to reduce confusion

Treat an initializing peer as failed, to avoid getting too fixated
on an uncertain target.

Make sure that no more than one follower is tried at a time by
killing before starting a new one, and syncing up the configmanager
state

Decline to act on an assimilation request if we are trying to connect
and also if the current leader asks us to connect and we already are.

Avoid calling get_leader while connecting, as that can cause a member
to decide to become a leader while trying to connect, by swapping
the reactions to the connect request.

Avoid trying to assimilate existing followers.

Fix some logging.
2018-10-12 11:45:23 -04:00
Jarrod Johnson f525c25ba6 Provide more verbose collective logging
This helps understand the flow in practice of collective behavior.
2018-10-11 15:15:11 -04:00
Jarrod Johnson 3012de1fe4 Prioritize deletion of transactioncount
If the invalidation is incomplete, make sure that transactioncount
is invalidated first to avoid it being able to propogate through
a collective.
2018-10-11 09:16:57 -04:00
Jarrod Johnson be930fc076 Add missing subsystem marker from a collective log 2018-10-10 16:30:28 -04:00
Jarrod Johnson 2d0199a4e9 Wrap bdb deletion in same lock that sync itself uses
If os.remove happens at a bad time, it causes an unfortunate behavior
in dbm.  Serialize this sort of operation to avoid the bad behavior.
2018-10-10 15:24:55 -04:00
Jarrod Johnson 6b70a4322a Fix rollback
The fix for the stale data introduced breaking clear rollback
Restore the behavior and make self._cfgstore a somewhat slower property
for now.
2018-10-10 15:22:20 -04:00
Jarrod Johnson 6a784e3a1c Ensure sync is complete prior to leaving configmanager sync
The initialization lock is meant to avoid collective and generic
initialization stepping on each other.  This is somewhat reduced in
efficacy if one has a sync running while the other is changing relevant
data.
2018-10-10 14:49:33 -04:00
Jarrod Johnson 3b2b96a4cf Force fullsync if dead sync thread likely
If the sync thread died previously, force the next sync to be full.
2018-10-10 14:32:13 -04:00
Jarrod Johnson 32ddb33de3 Fix error when trying to do fullsync without globals yet
If globals is missing, then do not break the sync trying to handle it
2018-10-10 13:11:15 -04:00
Jarrod Johnson b77ed8dbff Fix config sync on dead writer
The sync thread can die without clearing syncrunning.  Make sure that
the thread is alive *and* that the thread has not indicated
intent to give up.
2018-10-10 13:07:27 -04:00
Jarrod Johnson d5c093a30d Provide fallback for unexpected reply in collective show 2018-10-10 09:46:01 -04:00
Jarrod Johnson cf9d2a43e8 Revert "Provide fallback for unexpected reply in collective show"
This reverts commit 2f566fb81d.
2018-10-10 09:44:06 -04:00
Jarrod Johnson 2f566fb81d Provide fallback for unexpected reply in collective show 2018-10-10 09:41:25 -04:00
Jarrod Johnson 94c8cf3ff2 Apply the correct change to collate 2018-10-10 09:24:01 -04:00
Jarrod Johnson 8741a27c24 Merge branch '18csi' of github.com:jjohnson42/confluent into 18csi 2018-10-10 09:09:12 -04:00
Jarrod Johnson 1c494fc4fc Correct mistake in the collate log support 2018-10-10 09:08:45 -04:00
Jarrod Johnson 1ee418392f Provide better error behavior on missing collective.manager
collective.manager was a blanket response, make it per node and
only triggered by the bad nodes, not the rest.
2018-10-09 15:44:17 -04:00
Jarrod Johnson 2a7eeb6e08 Fix missing argument in calling a function 2018-10-09 15:21:11 -04:00
Jarrod Johnson 5c83c78a90 Add warning on incompatible ssh key with SLES12 2018-10-09 14:44:06 -04:00
Jarrod Johnson 6a466b0100 Avoid proxy consoles generating proxy consoles
When the client is a proxy term, disable ability to produce
proxy terminals.  This was wreaking havoc with client
count with ghosts and triggering output multiplication.
2018-10-09 13:21:02 -04:00
Jarrod Johnson 6b4a21d613 Add log option to collate for per node logs 2018-10-09 09:37:25 -04:00
Jarrod Johnson 5f46899358 Prevent clear_configuration from invaliding existing ConfigManager
Clear out the existing dictionary instead of replacing it.

This prevents configmanager objects from being stuck.
2018-10-08 16:51:58 -04:00
Jarrod Johnson 20a37f8db5 Add mention of the -u options to the manpages. 2018-10-08 11:02:33 -04:00
Jarrod Johnson c6b8aaf372 Fix mistake in the nodegroupdefine man page 2018-10-08 10:57:48 -04:00
Jarrod Johnson 5baab5bef4 Add more stateful to completion
Allow it to sense words already used in command.  Refactor to common
code for similar ones.
2018-10-08 10:47:38 -04:00
Jarrod Johnson 73c06fd25e Fix display of error on join of collective 2018-10-08 09:54:03 -04:00
Jarrod Johnson 8d9a082739 Provide better exceptions and propogate them to client on snmp
When doing snmp, messages would always go to log only, even if the
user was at the confetty cli.  Give user access to knowing the error
impacting the query.
2018-10-04 14:59:25 -04:00
Jarrod Johnson 32602fbba3 Provide interactive handling of key mismatch in ssh sessions
Before, ssh would close without so much as a warning, fix this by
dealing with the key data.
2018-10-04 10:23:55 -04:00
Jarrod Johnson 2f616d4586 Better error when collective.manager is set to something invalid
If the collective.manager field does not correspond to any collective
manager, give a useful error rather than unexpected error.
2018-10-03 16:23:20 -04:00
Jarrod Johnson 15dc4937ee Add hyphen options to various completion 2018-10-02 14:44:25 -04:00
Jarrod Johnson 10cb1b77dd Extend the nodeboot man page 2018-10-02 14:36:07 -04:00
Jarrod Johnson d86e1fc4eb Give the cfg init a lock
Move collective manager and configmanager to share a configinitlock,
so that bad timings during internal initialization and collective
activity cannot interfere and produce corrupt database.

This became an issue with the fix for 'everything' disappearing.
2018-10-02 10:17:44 -04:00
Jarrod Johnson 78a1741e0e Fix usage of check_quorum()
It is not a boolean, it is exception driven.
2018-10-01 16:02:16 -04:00
Jarrod Johnson 4329c1d388 Have collective start bail out if leader
Leader should not relinquish if quorum, so don't bother in such
a case.
2018-10-01 15:50:49 -04:00
Jarrod Johnson b0b5493ff7 Cancel retry if we become leader
If an instance is first to start, it's retry should be canceled
when other members prod it to become leader.
2018-10-01 15:29:18 -04:00
Jarrod Johnson 326f56219b Fix /networking/macs/by-mac
The module apimacmap was not correctly scoped.
2018-10-01 14:40:02 -04:00
Jarrod Johnson e098c0ba91 Fix missing tenant argument on user management function
The tenant was omitted preventing those particular rpc calls from
working correctly.
2018-10-01 14:04:03 -04:00
Jarrod Johnson 61e7c90ad1 Do not restart on intentional kill
Additionally, add some output to help filter events log
2018-10-01 10:32:55 -04:00
Jarrod Johnson e57cdf9a7b Add more collective event log handling
More detail to analyze how the collective membership is handled.
2018-09-27 15:15:05 -04:00
Jarrod Johnson 10ce7a9de9 Add more logging to collective process 2018-09-27 10:51:06 -04:00
Jarrod Johnson 0724ad812b Add logging to the assimilation phase of collective
When attempting assimilation, provide logging about the attempt.
2018-09-27 10:51:01 -04:00
Jarrod Johnson a3b0b0240d Abort assimilation attempt on non-member cleanly
If a confluent instance has forgotten the collective, more cleanly
handle the situation, and abort the assimilation rather than assuming
the peer should be leader, unless txcount specifically is called out
as the reason.
2018-09-27 10:50:54 -04:00
Jarrod Johnson 99fdb20f87 Add nodemedia specific completion 2018-09-21 14:22:46 -04:00
Jarrod Johnson 60bb4c89fb Add specific completions for nodeboot and nodesetboot 2018-09-21 14:09:49 -04:00
Jarrod Johnson 5d52fd2fc1 Add nodeidentify specific completion
Additionally refactor common code into a function
2018-09-21 13:54:43 -04:00
Jarrod Johnson 18bebde337 Disable gssapi in paramiko
It is just plain broken, workaround by tanking calls to gssapi prior
to pulling in paramiko.
2018-09-21 13:46:07 -04:00
Jarrod Johnson c68c4d8cf7 Fix the 'list' subcommand of nodefirmware
nodefirmware did not properly set up if 'list' was given
2018-09-21 10:41:15 -04:00
Jarrod Johnson 1de84f0417 Add missing nodesupport man page 2018-09-21 09:57:34 -04:00
Jarrod Johnson 44bf2872b7 Provide more tailored completion for some commands
Additionally, modify the nodefirmware command to have a `list`
subcommand, so that tab completion doesn't aggressively send
someone to update.
2018-09-21 09:57:17 -04:00
Jarrod Johnson c209010126 Add tab completion for bash
For interactive bash use, have some completions
2018-09-20 16:02:45 -04:00
Jarrod Johnson 21b4a2f6f3 Merge branch 'master' of github.com:jjohnson42/confluent 2018-09-20 11:05:24 -04:00
Jarrod Johnson 36fc23d692 Avoid VT control codes on exit through pipe
Piped commands were subjected to terminal control sequences that
could interfere with desired operation.
2018-09-20 11:05:00 -04:00
Jarrod Johnson f601032a66 Fix everything group missing if nodegroup created before node
everything group was not making it to disk unless a node is created
first.  Correctly mark the need for disk sync to fix.
2018-09-14 16:50:20 -04:00
Jarrod Johnson 7c550bd68e Fix prefix fixup
It was not allowing same label across nodes.
2018-09-10 15:09:50 -04:00
Jarrod Johnson db5f861dc5 Fix introduced typo in error message 2018-09-10 14:25:04 -04:00
Jarrod Johnson d04be19ae5 Preferentially use a 'name' subfield as 'name'
Pyghmi now may suggest a more useful name.  The component name
is unique, but 'name' can indicate the common name of things with
multiple instances.
2018-09-07 14:37:02 -04:00
Jarrod Johnson 07532e2a3f Have nodeinventory disambiguate duplicate labels.
The data is still there for putting identical cards together, but
the prefix is unique, particularly important for json mode.
2018-09-07 11:49:13 -04:00
Jarrod Johnson e7be24d478 Revert "Fix non-unique name for similar inventory items."
This reverts commit 47a53a51e4.
2018-09-07 11:44:01 -04:00
Jarrod Johnson 34b7abcb2d Change systemd unit to not have PIDFile
systemctl restart *always* prints a worrying message
with pidfile.
2018-09-07 11:27:43 -04:00
Jarrod Johnson 47a53a51e4 Fix non-unique name for similar inventory items. 2018-09-07 11:16:09 -04:00
Jarrod Johnson abc15974da Merge branch 'master' of github.com:jjohnson42/confluent 2018-09-07 11:11:26 -04:00
Jarrod Johnson b3bf6929df Add replacement logic for another generic variant
In IMM, PCeGen3 x8 and similar is also possible.
2018-09-06 16:16:26 -04:00
Jarrod Johnson 2a8d61ecf6 Enrich the less than useful 'Adapter' inventory items
We can provide DNS provided info about such generic items to
make them look more fleshed out.
2018-09-06 16:10:48 -04:00
Jarrod Johnson cf3e9037ab Provide 'discovery.passwordrules'
This provides an ability to designate the desired rules that
are applied in the wake of automatic discovery.  The most popular
would be 'expiry=no,loginfailures=0'
2018-09-05 15:50:36 -04:00
Jarrod Johnson 03135543a6 Add 'switchuser' and 'switchpass' aliases 2018-09-05 13:51:19 -04:00
Jarrod Johnson 38228ebc9b Fix the prompting code changes 2018-09-04 11:09:26 -04:00
Jarrod Johnson d6110c7118 Add -p to nodegroupattrib
Also modify the man pages to reflect the updates.
2018-09-04 09:56:51 -04:00
Jarrod Johnson f92b1ed4a3 Implement ability to prompt for nodeattrib options.
For certain attributes, notably passwords, it is sometimes desirable
to prompt interactively to help facilitate keeping such data out of
bash_history, screen sharing, and ps output.  -e enables this if the
user is aware of how to use 'read', -p is a quicker way to enable this.
2018-09-04 09:38:01 -04:00
Jarrod Johnson 368087fb51 Have nodeattrib and nodeconfig accept wildcard to select values 2018-08-30 10:14:03 -04:00
Jarrod Johnson 46d62e67de Do not include advanced in comparedefault by default
Advanced settings may do unusual things, only check if explicitly
requested to do so.
2018-08-30 09:58:15 -04:00
Jarrod Johnson 118d1aec0d Allow nodeboot to harmlessly take -u
It doesn't actually change behavior, but request for compatibility
is being honored.
2018-08-30 09:39:20 -04:00
Jarrod Johnson 7c9089c87d Change nodeconfig -r to take a parameter
This opens up for future ability to control the restore to default.
2018-08-30 09:35:16 -04:00
Jarrod Johnson ba18b9936f Fix mistakes in previous commit 2018-08-29 15:15:34 -04:00
Jarrod Johnson 3b7ecd0095 Add ability to clear system configuration
This provides a method to request the system firmware be restored to
factory defaults.
2018-08-29 14:49:19 -04:00
Jarrod Johnson 19e9583b47 Fix formatting on node*define man page sources 2018-08-28 15:08:03 -04:00
Jarrod Johnson 8352007570 Limit to one active scan at a time
Additionally, provide read access to rescan for discovery.
2018-08-28 11:25:48 -04:00
Jarrod Johnson f7965d235a Improve /networking/macs API behavior
For the 'by-mac', only remove the structure when it is ready for API
view without changing internal view.

For the 'by-switch', do the update per switch and after it's done.

Provide ability to check scan status through reading
/networking/macs/rescan
2018-08-28 11:10:32 -04:00
Jarrod Johnson 6aec9534e7 Fixes for nodesupport 2018-08-23 16:56:40 -04:00
Jarrod Johnson 3ee6334db2 Fix transfer owner argument for servicedata 2018-08-23 16:48:10 -04:00
Jarrod Johnson 582a4de62d Add CLI and directory support for nodesupport 2018-08-23 16:36:41 -04:00
Jarrod Johnson c9959d4082 More of the service data retrieval api
More progress is made toward the goal
2018-08-23 16:14:49 -04:00
Jarrod Johnson fa11fb54cb Add API support for getting service data
Service data retrieval is a common activity required
for interacting with support.
2018-08-23 15:39:25 -04:00
Jarrod Johnson ee3b824870 Add /description to nodes api 2018-08-23 08:44:41 -04:00
Jarrod Johnson 55f5b30369 Merge branch '21' 2018-08-22 16:29:58 -04:00
Jarrod Johnson 784e4bed2f Force cleanup if follow thread dies of exception
If something killed a follow thread, it was not always able to fire the
recovery off.  Wrap the risky code in a try statement.
2018-08-20 15:02:34 -04:00
Jarrod Johnson df7cba00fd Amend the message on collective failure 2018-08-17 16:45:45 -04:00
Jarrod Johnson dfb720d0ee Have collective command warn if the libssl library is not viable
Main example is RedHat providing pyOpenSSL of relatively ancient
vintage.
2018-08-17 13:57:13 -04:00
Jarrod Johnson f5d5cbd67b Have collective command warn if the libssl library is not viable
Main example is RedHat providing pyOpenSSL of relatively ancient
vintage.
2018-08-17 13:56:38 -04:00
Jarrod Johnson 319fec2145 Add advanced to nodeconfig 2018-08-17 11:16:11 -04:00
Jarrod Johnson 8787d23b3a Add advanced to API for system configuration.
pyghmi makes hiding optional advanced settings.
2018-08-17 10:59:50 -04:00
Jarrod Johnson 9b48110155 Do not proceed a logged, but broken session
It shouldn't be possible for this to be the case, but out of an
abundance of caution, check for this.  So far only produced this by
forcing broken = True in a debug session.  Intended to catch an alleged
scenario where console was managing to use a broken session (fixed in
pyghmi) and have confluent also recognize the situation for non-console
usage).
2018-08-16 14:43:16 -04:00
Jarrod Johnson 3064e7bef6 Ensure path is made prior to creating transactioncount
Fresh install will be missing /etc/confluent/cfg.  Advance the
_mkpath call to fix this problem.
2018-08-08 18:05:45 -04:00
Jarrod Johnson 1d4df8af3a Fix extraneous error in log on connectivity loss 2018-08-07 15:43:53 -04:00
Jarrod Johnson 2aba6e469c Correct variable name in the 'connected' fix 2018-08-07 15:31:41 -04:00
Jarrod Johnson de58593f14 Fix inability to notice underlying broken layers of the SOL
Through an unknown set of circumstances, an solconnection could be
stuck 'connecting'.  In every case analyzed, the ipmi_session was
broken.  Use that to detect a class of failure and react appropriately.
2018-08-07 15:12:53 -04:00
46 changed files with 1144 additions and 6778 deletions
+8
View File
@@ -48,6 +48,8 @@ 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()
@@ -80,6 +82,12 @@ 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:
+3 -2
View File
@@ -598,9 +598,10 @@ 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
sys.stdout.write('\x1b[m')
if sys.stdout.isatty():
sys.stdout.write('\x1b[m')
if fullexit:
if os.environ.get('TERM', '') not in ('linux'):
if sys.stdout.isatty() and os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
sys.exit(code)
else:
+17 -2
View File
@@ -17,6 +17,7 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -47,6 +48,8 @@ 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()
@@ -67,12 +70,24 @@ exitcode = 0
nodetype="noderange"
if len(args) > 1:
if "=" in args[1] or options.clear or options.environment:
if "=" in args[1] or options.clear or options.environment or options.prompt:
if "=" in args[1] and options.clear:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
exitcode=client.updateattrib(session,args,nodetype, noderange, options)
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)
try:
# setting user output to what the user inputs
if args[1] == 'all':
+3
View File
@@ -35,6 +35,9 @@ 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 '
+28 -2
View File
@@ -55,6 +55,16 @@ 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 = {
@@ -165,6 +175,18 @@ for param in args[1:]:
queryparms[path][attrib] = param
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')
@@ -209,7 +231,11 @@ else:
if printsys or options.exclude:
if printsys == 'all':
printsys = []
path = '/noderange/{0}/configuration/system/all'.format(noderange)
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)
rcode = client.print_attrib_path(path, session, printsys,
options)
sys.exit(rcode)
sys.exit(rcode)
+9 -2
View File
@@ -55,7 +55,8 @@ def printfirm(node, prefix, data):
components = ['all']
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [update [--backup <file>]]|[<components>]")
usage="Usage: "
"%prog <noderange> [list][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()
@@ -66,9 +67,15 @@ try:
if args[1] == 'update':
upfile = args[2]
else:
if args[1] == 'list':
comps = args[2:]
else:
comps = args[1:]
components = []
for arg in args[1:]:
for arg in comps:
components += arg.split(',')
if not components:
components = ['all']
except IndexError:
argparser.print_help()
+16 -1
View File
@@ -17,6 +17,7 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -47,6 +48,8 @@ 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()
@@ -72,7 +75,19 @@ if len(args) > 1:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options)
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)
try:
# setting user output to what the user inputs
if args[1] == 'all':
+6
View File
@@ -87,6 +87,7 @@ 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]")
@@ -129,6 +130,11 @@ 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):
while data and select.select([r], [], [], 0)[0]:
data = r.readline()
if data:
if desc['type'] == 'stdout':
+145
View File
@@ -0,0 +1,145 @@
#!/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()
+11 -2
View File
@@ -17,6 +17,7 @@
import anydbm as dbm
import errno
import fnmatch
import hashlib
import os
import shlex
@@ -311,7 +312,7 @@ def attrrequested(attr, attrlist, seenattributes):
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
if candidate in _attraliases:
candidate = _attraliases[candidate]
if candidate.lower() == attr.lower():
if fnmatch.fnmatch(attr.lower(), candidate.lower()):
seenattributes.add(truename)
return True
elif attr.lower().startswith(candidate.lower() + '.'):
@@ -493,7 +494,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
exitcode = 1
return exitcode
def updateattrib(session, updateargs, nodetype, noderange, options):
def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=None):
# update attribute
exitcode = 0
if options.clear:
@@ -526,6 +527,14 @@ def updateattrib(session, updateargs, nodetype, noderange, options):
'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,3 +37,219 @@ 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
+6 -2
View File
@@ -3,7 +3,7 @@ collate(1) -- Organize text input by node
## SYNOPSIS
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r]`
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r] [-l lognametemplate]`
## DESCRIPTION
@@ -26,6 +26,10 @@ 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.
@@ -85,4 +89,4 @@ node and group names sorted alphanumerically.
` Processors.ExecuteDisableBit=Enable`
+3
View File
@@ -7,6 +7,7 @@ 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
@@ -40,6 +41,8 @@ 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:
+34 -2
View File
@@ -4,12 +4,44 @@ nodeboot(8) -- Reboot a confluent node to a specific device
## SYNOPSIS
`nodeboot <noderange>`
`nodeboot <noderange>` [net|setup]
`nodeboot [options] <noderange>` [default|cd|network|setup|hd]
## 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.
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
## EXAMPLES
* Booting n3 and n4 to the default boot behavior:
+5
View File
@@ -30,6 +30,11 @@ 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.
## EXAMPLES
* Showing the current IP configuration of noderange BMC/IMM/XCC:
+1
View File
@@ -1,4 +1,5 @@
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> <components>|core`
`nodefirmware <noderange> list|<components>|core`
`nodefirmware <noderange> update [--backup] <filename>`
## DESCRIPTION
@@ -7,6 +7,7 @@ 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,4 +1,5 @@
nodegroupdefine(8) -- Define new confluent node group
===================================================================
## SYNOPSIS
@@ -6,7 +7,7 @@ nodegroupdefine(8) -- Define new confluent node group
## DESCRIPTION
`nodegroupdefine` allows the definition of a new node for the confluent management
`nodegroupdefine` allows the definition of a new nodegroup 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.
+4 -1
View File
@@ -30,7 +30,10 @@ 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
@@ -0,0 +1,25 @@
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
@@ -1,17 +0,0 @@
[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]
+3 -2
View File
@@ -66,8 +66,9 @@ def join_collective(server, invitation):
'invitation': invitation,
'server': server}})
res = tlvdata.recv(s)
print(res.get('collective',
{'status': 'Unknown response: ' + repr(res)})['status'])
res = res.get('collective',
{'status': 'Unknown response: ' + repr(res)})
print(res.get('status', res.get('error', repr(res))))
def show_collective():
+124 -28
View File
@@ -18,12 +18,14 @@ 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
@@ -33,38 +35,41 @@ 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:
except socket.error as e:
log.log({'error': 'Collective connection attempt to {0} failed: {1}'
''.format(leader, str(e)),
'subsystem': 'collective'})
return False
with connecting:
with cfginitlock:
with cfm._initlock:
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
if name is None:
@@ -77,10 +82,16 @@ def connect_to_leader(cert=None, name=None, leader=None):
return False
if 'error' in keydata:
if 'backoff' in keydata:
eventlet.spawn_after(random.random(), connect_to_leader,
cert, name, leader)
return True
log.log({
'info': 'Collective initialization in progress on '
'{0}'.format(leader),
'subsystem': 'collective'})
return False
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:
@@ -88,13 +99,22 @@ 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)
@@ -134,11 +154,22 @@ def connect_to_leader(cert=None, name=None, leader=None):
def follow_leader(remote):
global currentleader
cfm.follow_channel(remote)
# The leader has folded, time to startup again...
cfm.stop_following()
currentleader = None
eventlet.spawn_n(start_collective)
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)
def connect_to_collective(cert, member):
@@ -291,6 +322,8 @@ 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:
@@ -320,6 +353,11 @@ 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, '
@@ -329,8 +367,22 @@ 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})
@@ -359,6 +411,11 @@ 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,
@@ -366,17 +423,14 @@ 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()
@@ -430,11 +484,36 @@ def try_assimilate(drone):
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
answer = tlvdata.recv(remote)
if answer and 'error' in answer:
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'})
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
@@ -446,27 +525,34 @@ 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 (currentleader, skipaddr) or member == myname:
if dronecandidate in skipem 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():
@@ -474,7 +560,15 @@ 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
@@ -485,6 +579,8 @@ 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,6 +156,12 @@ 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,6 +82,7 @@ _dirtylock = threading.RLock()
_leaderlock = gthread.RLock()
_synclock = threading.RLock()
_rpclock = gthread.RLock()
_initlock = gthread.RLock()
_followerlocks = {}
_config_areas = ('nodegroups', 'nodes', 'usergroups', 'users')
tracelog = None
@@ -94,7 +95,9 @@ _hasquorum = True
_attraliases = {
'bmc': 'hardwaremanagement.manager',
'bmcuser': 'secret.hardwaremanagementuser',
'switchuser': 'secret.hardwaremanagementuser',
'bmcpass': 'secret.hardwaremanagementpassword',
'switchpass': 'secret.hardwaremanagementpassword',
}
def _mkpath(pathname):
@@ -623,7 +626,7 @@ def rollback_clear():
_cfgstore = _oldcfgstore
_oldtxcount = 0
_oldcfgstore = None
ConfigManager._bg_sync_to_file()
ConfigManager.wait_for_sync(True)
def clear_configuration():
@@ -643,12 +646,13 @@ def commit_clear():
global _oldcfgstore
_oldcfgstore = None
_oldtxcount = 0
todelete = _config_areas + ('globals', 'collective', 'transactioncount')
for cfg in todelete:
try:
os.remove(os.path.join(ConfigManager._cfgdir, cfg))
except OSError as oe:
pass
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
ConfigManager.wait_for_sync(True)
ConfigManager._bg_sync_to_file()
@@ -658,41 +662,43 @@ cfgleader = None
def follow_channel(channel):
global _txcount
global _hasquorum
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
try:
stop_leading()
stop_following(channel)
lh = StreamHandler(channel)
msg = lh.get_next_msg()
# mark the connection as broken
if cfgstreams:
stop_following(None)
else:
stop_following(True)
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)
def add_collective_member(name, address, fingerprint):
@@ -947,38 +953,46 @@ 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
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'] = {}
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: {}}
self._bg_sync_to_file()
self._cfgstore = _cfgstore['main']
elif tenant not in _cfgstore['tenant']:
_cfgstore['tenant'][tenant] = {}
self._bg_sync_to_file()
self.tenant = tenant
if 'nodegroups' not in self._cfgstore:
self._cfgstore['nodegroups'] = {'everything': {'nodes': set()}}
self._bg_sync_to_file()
self._cfgstore['nodegroups'] = {'everything': {}}
_mark_dirtykey('nodegroups', 'everything', self.tenant)
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()
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()
self.wait_for_sync()
def get_collective_member(self, name):
return get_collective_member(name)
@@ -1226,9 +1240,9 @@ class ConfigManager(object):
def del_user(self, name):
if cfgleader:
return exec_on_leader('_rpc_master_del_user', name)
return exec_on_leader('_rpc_master_del_user', self.tenant, name)
if cfgstreams:
exec_on_followers('_rpc_del_user', name)
exec_on_followers('_rpc_del_user', self.tenant, name)
self._true_del_user(name)
def _true_del_user(self, name):
@@ -2050,9 +2064,13 @@ class ConfigManager(object):
if statelessmode:
return
with cls._syncstate:
if cls._syncrunning:
if (cls._syncrunning and cls._cfgwriter is not None and
cls._cfgwriter.isAlive()):
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:
@@ -2065,16 +2083,17 @@ 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:
if fullsync:
if (fullsync or 'dirtyglobals' in _cfgstore and
'globals' in _cfgstore):
if fullsync: # globals is not a given to be set..
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:
+11 -7
View File
@@ -359,8 +359,11 @@ class ConsoleHandler(object):
except (exc.NotImplementedException, exc.NotFoundException):
self._console = None
except:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
if _tracelog:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
else:
print(traceback.format_exc())
if not isinstance(self._console, conapi.Console):
self.clearbuffer()
self.connectstate = 'unconnected'
@@ -625,12 +628,12 @@ def start_console_sessions():
configmodule.hook_new_configmanagers(_start_tenant_sessions)
def connect_node(node, configmanager, username=None):
def connect_node(node, configmanager, username=None, direct=True):
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():
if myc and myc != collective.get_myname() and direct:
minfo = configmodule.get_collective_member(myc)
return ProxyConsole(node, minfo, myname, configmanager, username)
consk = (node, configmanager.tenant)
@@ -758,10 +761,9 @@ 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):
skipreplay=False, direct=True):
self.registered = False
self.tenant = configmanager.tenant
if not configmanager.is_node(node):
@@ -769,6 +771,8 @@ 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
@@ -797,7 +801,7 @@ class ConsoleSession(object):
between console and shell.
"""
self.conshdl = connect_node(self.node, self.configmanager,
self.username)
self.username, self.direct)
def send_break(self):
"""Send break to remote system
"""
+44 -7
View File
@@ -201,6 +201,14 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
})
},
},
'_console': {
@@ -233,6 +241,10 @@ def _init_core():
'default': 'ipmi',
}),
},
'description': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'events': {
'hardware': {
'log': PluginRoute({
@@ -334,6 +346,12 @@ def _init_core():
},
},
'support': {
'servicedata': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
}
nodegroupresources = {
@@ -567,6 +585,15 @@ 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')
@@ -759,6 +786,7 @@ 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)
@@ -769,11 +797,8 @@ def handle_node_request(configmanager, inputdata, operation,
nodesbyhandler[hfunc].append(node)
else:
nodesbyhandler[hfunc] = [node]
if badcollnodes:
raise exc.ConfluentException(
'collective management active, '
'collective.manager must be set for {0}'.format(
','.join(badcollnodes)))
for bn in badcollnodes:
nodesbyhandler[BadCollective(bn).error] = [bn]
workers = greenpool.GreenPool()
numworkers = 0
for hfunc in nodesbyhandler:
@@ -809,6 +834,8 @@ 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
@@ -822,6 +849,8 @@ def addtoqueue(theq, fun, kwargs):
else:
for pv in result:
theq.put(pv)
except Exception as e:
theq.put(e)
finally:
theq.put('theend')
@@ -837,8 +866,16 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
certfile='/etc/confluent/srvcert.pem')
except Exception:
for node in nodes:
yield msg.ConfluentResourceUnavailable(
node, 'Collective member {0} is unreachable'.format(a['name']))
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))
return
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
binary_form=True)):
+11 -2
View File
@@ -86,6 +86,8 @@ import eventlet.greenpool
import eventlet.semaphore
autosensors = set()
scanner = None
class nesteddict(dict):
def __missing__(self, key):
@@ -381,6 +383,7 @@ 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')
@@ -416,6 +419,8 @@ 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))]
@@ -1142,7 +1147,11 @@ def _periodic_recheck(configmanager):
def rescan():
_map_unique_ids()
eventlet.spawn_n(slp.active_scan, safe_detected)
global scanner
if scanner:
return
else:
scanner = eventlet.spawn(slp.active_scan, safe_detected)
def start_detection():
@@ -1194,7 +1203,7 @@ def _map_unique_ids(nodes=None):
uuid_by_nodes = {}
fprint_by_nodes = {}
for uuid in nodes_by_uuid:
if not uuid_is_valid():
if not uuid_is_valid(uuid):
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):
def _bmcconfig(self, nodename, reset=False, customconfig=None):
# 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,6 +74,8 @@ 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,
@@ -122,16 +124,18 @@ 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,11 +13,13 @@
# 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'
@@ -41,16 +43,45 @@ 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.
ic = self._bmcconfig(nodename)
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)
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,6 +106,7 @@ 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,
+19 -4
View File
@@ -22,19 +22,24 @@ 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):
if not os.path.exists(filename):
def execupdate(handler, filename, updateobj, type, owner, node):
if type != 'ffdc' and 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,
@@ -43,6 +48,9 @@ def execupdate(handler, filename, updateobj, type):
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 ' \
@@ -55,18 +63,20 @@ def execupdate(handler, filename, updateobj, type):
class Updater(object):
def __init__(self, node, handler, filename, tenant=None, name=None,
bank=None, type='firmware'):
bank=None, type='firmware', owner=None):
self.bank = bank
self.node = node
self.phase = 'initializing'
self.detail = ''
self.percent = 0.0
self.updateproc = updatepool.spawn(execupdate, handler, filename,
self, type)
self, type, owner, node)
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:
@@ -96,6 +106,8 @@ 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:
@@ -115,6 +127,9 @@ 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,12 +416,17 @@ 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')
@@ -490,6 +495,13 @@ 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..
+25 -13
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
switch, password, user, force = args[:4]
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
now = util.monotonic_time()
if vintage > (now - 60) and not force:
@@ -220,17 +220,19 @@ def _extract_neighbor_data_b(args):
_neighdata[switch] = lldpdata
def update_switch_data(switch, configmanager, force=False):
def update_switch_data(switch, configmanager, force=False, retexc=False):
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
_extract_neighbor_data(switchcreds + (force,))
ndr = _extract_neighbor_data(switchcreds + (force, retexc))
if retexc and isinstance(ndr, Exception):
raise ndr
return _neighdata.get(switch, {})
def update_neighbors(configmanager, force=False):
return _update_neighbors_backend(configmanager, force)
def update_neighbors(configmanager, force=False, retexc=False):
return _update_neighbors_backend(configmanager, force, retexc)
def _update_neighbors_backend(configmanager, force):
def _update_neighbors_backend(configmanager, force, retexc):
global _neighdata
global _neighbypeerid
vintage = _neighdata.get('!!vintage', 0)
@@ -241,7 +243,7 @@ def _update_neighbors_backend(configmanager, force):
_neighbypeerid = {'!!vintage': now}
switches = netutil.list_switches(configmanager)
switchcreds = netutil.get_switchcreds(configmanager, switches)
switchcreds = [ x + (force,) for x in switchcreds]
switchcreds = [ x + (force, retexc) for x in switchcreds]
pool = GreenPool(64)
for ans in pool.imap(_extract_neighbor_data, switchcreds):
yield ans
@@ -258,9 +260,15 @@ def _extract_neighbor_data(args):
return
try:
with _updatelocks[switch]:
_extract_neighbor_data_b(args)
except Exception:
log.logtrace()
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()
if __name__ == '__main__':
# a quick one-shot test, args are switch and snmpv1 string for now
@@ -327,7 +335,9 @@ def _handle_neighbor_query(pathcomponents, configmanager):
# guaranteed
if (parms['by-peerid'] not in _neighbypeerid and
_neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60):
list(update_neighbors(configmanager))
for x in update_neighbors(configmanager, retexc=True):
if isinstance(x, Exception):
raise x
if parms['by-peerid'] not in _neighbypeerid:
raise exc.NotFoundException('No matching peer known')
return _dump_neighbordatum(_neighbypeerid[parms['by-peerid']])
@@ -336,9 +346,11 @@ 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)
update_switch_data(parms['by-switch'], configmanager, retexc=True)
else:
list(update_neighbors(configmanager))
for x in update_neighbors(configmanager, retexc=True):
if isinstance(x, Exception):
raise x
return list_info(parms, listrequested)
@@ -47,6 +47,7 @@ import eventlet.semaphore
import re
_macmap = {}
_apimacmap = {}
_macsbyswitch = {}
_nodesbymac = {}
_switchportmap = {}
@@ -211,7 +212,7 @@ def _map_switch_backend(args):
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
_macsbyswitch[switch] = {}
newmacs = {}
for mac in mactobridge:
# We want to merge it so that when a mac appears in multiple
# places, it is captured.
@@ -223,10 +224,10 @@ def _map_switch_backend(args):
_macmap[mac].append((switch, ifname, maccounts[ifname]))
else:
_macmap[mac] = [(switch, ifname, maccounts[ifname])]
if ifname in _macsbyswitch[switch]:
_macsbyswitch[switch][ifname].append(mac)
if ifname in newmacs:
newmacs[ifname].append(mac)
else:
_macsbyswitch[switch][ifname] = [mac]
newmacs[ifname] = [mac]
nodename = _nodelookup(switch, ifname)
if nodename is not None:
if mac in _nodesbymac and _nodesbymac[mac][0] != nodename:
@@ -238,6 +239,7 @@ def _map_switch_backend(args):
_nodesbymac[mac] = (None, None)
else:
_nodesbymac[mac] = (nodename, maccounts[ifname])
_macsbyswitch[switch] = newmacs
switchbackoff = 30
@@ -295,6 +297,7 @@ def _finish_update(completions):
def _full_updatemacmap(configmanager):
global vintage
global _apimacmap
global _macmap
global _nodesbymac
global _switchportmap
@@ -307,7 +310,6 @@ def _full_updatemacmap(configmanager):
_macmap = {}
_nodesbymac = {}
_switchportmap = {}
_macsbyswitch = {}
if configmanager.tenant is not None:
raise exc.ForbiddenRequest(
'Network topology not available to tenants')
@@ -340,11 +342,15 @@ 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
@@ -424,7 +430,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(_macmap))]
for x in sorted(list(_apimacmap))]
elif len(pathcomponents) == 4:
return dump_macinfo(pathcomponents[-1])
elif pathcomponents[2] == 'by-switch':
@@ -457,6 +463,8 @@ 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,12 +19,14 @@ 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')
@@ -32,6 +34,37 @@ 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,
@@ -270,13 +303,17 @@ class IpmiConsole(conapi.Console):
kg=self.kg, force=True,
iohandler=self.handle_data)
self.solconnection.outputlock = NullLock()
while not self.solconnection.connected and not self.broken:
while (self.solconnection and not self.solconnection.connected and
not (self.broken or self.solconnection.broken or
self.solconnection.ipmi_session.broken)):
w = eventlet.event.Event()
_ipmiwaiters.append(w)
w.wait()
if self.broken:
break
if self.broken:
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'
if (self.error.startswith('Incorrect password') or
self.error.startswith('Unauthorized name')):
raise exc.TargetEndpointBadCredentials
@@ -381,6 +418,7 @@ 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
@@ -391,7 +429,8 @@ 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):
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged or
persistent_ipmicmds[(node, tenant)].ipmi_session.broken):
try:
persistent_ipmicmds[(node, tenant)].close_confluent()
except KeyError: # was no previous session
@@ -438,6 +477,10 @@ 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(
@@ -477,6 +520,10 @@ 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')
@@ -496,6 +543,14 @@ 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)
@@ -528,6 +583,10 @@ 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):
@@ -814,12 +873,14 @@ class IpmiHandler(object):
if component == 'all':
for invdata in self.ipmicmd.get_inventory():
if invdata[1] is None:
newinf = {'present': False, 'information': None}
newinf = {'present': False, 'information': None,
'name': invdata[0]}
else:
sanitize_invdata(invdata[1])
newinf = {'present': True, 'information': invdata[1]}
newinf['name'] = invdata[0]
invitems.append(newinf)
newinf['name'] = invdata[1].get('name', invdata[0])
self.add_invitem(invitems, newinf)
else:
self.make_inventory_map()
compname = self.invmap.get(component, None)
@@ -828,12 +889,13 @@ class IpmiHandler(object):
return
invdata = self.ipmicmd.get_inventory_of_component(compname)
if invdata is None:
newinf = {'present': False, 'information': None}
newinf = {'present': False, 'information': None,
'name': compname}
else:
sanitize_invdata(invdata)
newinf = {'present': True, 'information': invdata}
newinf['name'] = compname
invitems.append(newinf)
newinf = {'present': True, 'information': invdata,
'name': invdata.get('name', compname)}
self.add_invitem(invitems, newinf)
except ssl.SSLEOFError:
errorneeded = msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
@@ -850,6 +912,24 @@ 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]
@@ -1020,10 +1100,17 @@ class IpmiHandler(object):
self.ipmicmd.set_domain_name(dn)
return
def handle_sysconfig(self):
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):
if 'read' == self.op:
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration()))
self.node, self.ipmicmd.get_system_configuration(
hideadvanced=not advanced)))
elif 'update' == self.op:
self.ipmicmd.set_system_configuration(
self.inputdata.get_attributes(self.node))
@@ -1078,6 +1165,11 @@ 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
@@ -1122,6 +1214,9 @@ 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)
@@ -1133,5 +1228,8 @@ 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 Lenovo
# Copyright 2015-2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,9 +22,22 @@
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):
@@ -110,6 +123,26 @@ 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
@@ -117,7 +150,36 @@ class SshShell(conapi.Console):
self.rxthread = eventlet.spawn(self.recvdata)
def write(self, data):
if self.inputmode == 0:
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:
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]
+9 -4
View File
@@ -92,12 +92,17 @@ class Session(object):
errstr, errnum, erridx, answers = rsp
if errstr:
errstr = str(errstr)
if errstr in ('unknownUserName', 'wrongDigest'):
raise exc.TargetEndpointBadCredentials(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)
# need to do bad credential versus timeout
raise exc.TargetEndpointUnreachable(errstr)
raise exc.TargetEndpointUnreachable(finerr)
elif errnum:
raise exc.ConfluentException(errnum.prettyPrint())
raise exc.ConfluentException(errnum.prettyPrint() +
' while trying to connect to '
'{0}'.format(self.server))
for ans in answers:
if not obj[0].isPrefixOf(ans[0]):
# PySNMP returns leftovers in a bulk command
+11 -1
View File
@@ -144,6 +144,15 @@ 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:
@@ -229,7 +238,8 @@ 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'])
datacallback=ccons.sendall, skipreplay=request['skipreplay'],
direct=False)
term_interact(None, None, ccons, None, connection, consession, None)
def start_term(authname, cfm, connection, params, path, authdata, skipauth):
-22
View File
@@ -1,22 +0,0 @@
[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
@@ -1,38 +0,0 @@
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
@@ -1,742 +0,0 @@
/**
* 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