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

Compare commits

...

126 Commits

Author SHA1 Message Date
zhaoertao 5702f7372b Have nodes/ipminode/power/state work for xcat node 2019-02-22 22:10:35 -05:00
zhaoertao f9e2f2e656 Map xcat node object to confluent node model 2019-02-22 02:53:49 -05:00
ertaozh 1dcca5d3eb Update Shebang and copyright year 2019-02-20 13:58:58 +08:00
zhaoertao 0845a914c6 Add url /xcat to retrieve nodes from xCAT db 2019-02-14 21:58:23 -05:00
Jarrod Johnson 1378b01feb Fix nodeconfig handling of multiple colons
If multiple colon designated settings are present, correctly
associate settings with appropriate key.
2019-01-16 11:31:06 -05:00
Jarrod Johnson 18713797b9 Fix pane splitting with large numbers of nodes
The tcons logic to select layout each iteration is now replicated,
to avoid too-small panes.
2019-01-15 16:00:39 -05:00
Jarrod Johnson 6588ac0990 Fix nodereseat false success message
nodereseat was reporting success when failure was actually encountered.
Address by injecting the node specific error.
2019-01-15 11:51:43 -05:00
Jarrod Johnson efd13c960f Add man page for nodelicense 2019-01-14 16:08:48 -05:00
Jarrod Johnson 4f14de0136 Add nodelicense command
This provides Bash access to the license management features.
2019-01-14 16:06:13 -05:00
Jarrod Johnson 6cecf6635c Merge branch 'master' of github.com:jjohnson42/confluent 2019-01-14 16:02:37 -05:00
Jarrod Johnson 5b7807fe3d Add nodelicense command
This provides Bash access to the license management features.
2019-01-14 16:02:15 -05:00
Jarrod Johnson 5f52ee9578 Support unique filenames through attribute expansion
Particularly in trying to support license keys, serial number
handling is at least required.  Easiest and most powerful and most
consistent path is to use the same attribute expansion as everywhere
else.
2019-01-14 15:41:17 -05:00
Jarrod Johnson 88275f5110 Fix xcc handler on incomplete data
A datum may come in without an attribute member at all.  Treat
this as 'false', obviously.
2019-01-14 15:37:04 -05:00
Jarrod Johnson 55c5bff0f9 Add support for uploading a license key
This provides the most important piece of license key management.
2019-01-14 15:09:49 -05:00
Jarrod Johnson 3782127590 Implement retrieving license info from XCC
With changes to pyghmi, we can now get license data.  Prepare
API to provide information about the licenses.
2019-01-14 14:55:03 -05:00
Jarrod Johnson 74355bd6f9 Merge branch 'master' of github.com:jjohnson42/confluent 2019-01-14 09:17:01 -05:00
Jarrod Johnson bc995520b7 Fix confetty tab completion
The refactoring to main caused scope of readline to be incorrect.
Pulled the scope back into namespace.
2019-01-14 09:15:59 -05:00
Jarrod Johnson 2d63e68494 Enable support for TCP_FASTOPEN
It might matter in some context.
2019-01-10 15:17:34 -05:00
Jarrod Johnson ceba3ff34f Fix remote collective console
The changes to support size data for ssh was not carried fully
through the proxyconsole object.  Fix this support.
2019-01-09 11:34:19 -05:00
Jarrod Johnson 334ec00f3f Properly report client use of bad async id
Previously an expired async id would trigger unexpected error.  Now
it returns an error to client and avoids the trace log.
2019-01-08 15:43:41 -05:00
Jarrod Johnson 35380e5bac Add missing import
errno was erroneously omitted from previous commit
2018-12-12 10:15:33 -05:00
Jarrod Johnson 66471624bc Support caching SDR for pyghmi versions that support it
This will improve performance across confluent restarts for commands
like nodesensors, nodehealth, and nodeinventory.
2018-12-12 09:53:36 -05:00
Jarrod Johnson 8a03bc48de Tentatively store certutil
Commit to repository, even though not yet used.  It is likely to
be renamed.  The purpose is to help generate an appropriate self signed
cert for https including all the ip addresses as subject alternative
names so that names or addresses may be used with installers that
have had the cert injected.
2018-12-11 13:51:46 -05:00
Jarrod Johnson 5831be091a Auto-rescan on missed manual discovery
Same as in the bulk discover support.
2018-12-11 09:17:08 -05:00
Jarrod Johnson 8197c750bb Add batch support to nodeconfig 2018-12-10 09:47:49 -05:00
Jarrod Johnson a9f0312acd Add support for exporting only a mac csv
For the specific task of importing mac addresses but not
perturbing other xCAT configuration, provide output that can be used
with tabrestore.
2018-12-07 14:08:34 -05:00
Jarrod Johnson 21700e914a Add confluent2xcat command
Add command for exporting node data as an xCAT stanza file
2018-12-07 13:59:01 -05:00
Jarrod Johnson 5e15ae8e30 Update nodeconsole man page for tiling 2018-12-04 10:41:50 -05:00
Jarrod Johnson 5eef76f5d3 Try a more straightforward use of tiling
Rather than retiling for every node, defer tiling until the end.
This dramatically improves the startup time for a moderate number
of systems.
2018-12-03 15:57:08 -05:00
Jarrod Johnson df72bfa715 Merge branch 'master' of github.com:jjohnson42/confluent 2018-12-03 15:53:24 -05:00
Jarrod Johnson a6333459c4 Add tile option to nodeconsole
This allows nodeconsole to produce the 'tcons' behavior without
xCAT dependency.
2018-12-03 15:53:15 -05:00
Jarrod Johnson 075891bf74 Add option to confetty to help keep terminal open on early exit
For contexts where early execution execution almost always means
some sort of error, but the terminal automatically closes,
provide a minimum time to execute option so confetty will
forcibly slow things down in such a likely scenario.
2018-12-03 14:49:19 -05:00
Jarrod Johnson a937511354 Fix nodesetboot argument parsing 2018-11-30 16:09:47 -05:00
Jarrod Johnson 22503e7e11 Inject blank lines in ansible hosts file
This looks a bit more like a natural hosts file.
2018-11-29 15:21:37 -05:00
Jarrod Johnson 01d73308ed Pull back spurious blank lines 2018-11-29 15:04:56 -05:00
Jarrod Johnson cb16385ddb Add confluent2ansible
Utility that wil take a noderange and generate an appropriate
ansible hosts file, including carrying over pertinent groups.
2018-11-29 15:04:27 -05:00
Jarrod Johnson 853585f942 Have nodeattrib output be sorted
This make nodelist and nodeattrib output much easier to follow.
2018-11-29 10:02:37 -05:00
Jarrod Johnson 53996a1e9d Increase likelihood of IPMI results being sorted
Without incurring execution penalty, endeavor to sort available
data in an opportunistic way.  This certainly does not guarantee order,
but it makes it highly likely for quick queries to appear orderly.
2018-11-29 09:43:53 -05:00
Jarrod Johnson 558171124c Fix nodediscover man page formatting 2018-11-27 16:14:15 -05:00
Jarrod Johnson 05d028e9e9 Fix formatting of nodediscover man page
The list of fields could not be nicely nested in ronn,
give up but have hard line breaks as a compromise.
2018-11-27 15:36:36 -05:00
Jarrod Johnson b0a3bb18fe Add terminal resizing to http api
This enables javascript clients to submit terminal changes.
2018-11-27 09:12:53 -05:00
Jarrod Johnson 7207013abc Have httpapi be able to accept initial size on consoles
consoles and shells can now put into the body parameters including
the width and height.
2018-11-26 16:43:52 -05:00
Jarrod Johnson b511a02f20 Have correct size on connect for shell session
In addition to resize, also support initial size being set
2018-11-26 16:21:31 -05:00
Jarrod Johnson c60cb3a027 Implement resize from CLI client
The CLI resize is wired up for ssh usage.  At the time of this commit,
initial size is not handled.
2018-11-26 15:31:36 -05:00
Jarrod Johnson 9f137fa6d4 Begin work to send resize to remote
While we do not have a path forward for auto-resize in SOL consoles,
we can provide the capability for ssh consoles.  The CLI being enabled
is of course nice, but running ssh directly is still suggested in such
a context and ultimately the Web GUI will be the most prominent
consumer of the information.
2018-11-20 16:01:36 -05:00
Jarrod Johnson aa92898249 Have nodestorage create optionally read names from args
This is more symmetric with the way delete works.
2018-11-19 15:32:03 -05:00
Jarrod Johnson 2cb694b374 Update man page of nodediscover 2018-11-19 14:46:47 -05:00
Jarrod Johnson 24fd1e72e1 Expose advertised, not valided IPs in discovery
There are circumstances where the advertised IP is
useful information, even if that IP cannot be validated.

It is also the case that most of the time the advertised IP
is useless if not validated, so it is an optional field.
2018-11-19 14:39:56 -05:00
Jarrod Johnson 5b21a5b2c3 Fix spreadsheet support for the BMC gateway
The spreadsheet support used incorrect attribute name.
2018-11-19 09:50:04 -05:00
Jarrod Johnson c8ad94313e Provide useful error when trying to delete a volume that doesn't exist 2018-11-16 11:54:46 -05:00
Jarrod Johnson f71d51769b Provide clean print of created volumes
This presents the returned data in a manner consistent
with nodestorage show
2018-11-16 11:27:26 -05:00
Jarrod Johnson b5213e6972 Show data about created volumes upon volume completion.
Provide concrete feedback as to the results of the creation operation.
2018-11-16 11:19:27 -05:00
Jarrod Johnson 8d72f4d64a Fix volume name creation
The volume names were not being honored, fix passing the arguments.
2018-11-15 16:25:03 -05:00
Jarrod Johnson fcd46c7b72 Provide cleaner output for nodestorage delete
Rather than dumping the structure of the response, provide a consistent
plain text output.
2018-11-15 16:18:24 -05:00
Jarrod Johnson 6efb8ff1b7 Add globbing protection to nodestorage command
This mitigates risk of mistargeting a nodestorage command due to
unfortunate bash glob match.
2018-11-15 16:15:01 -05:00
Jarrod Johnson c227c2353b Provide better error on nodediscover assign
Rather than the rather useless unexpected error, actually
describe specific cases of wrong username or password.
2018-11-15 13:41:48 -05:00
Jarrod Johnson cb2133e871 Change strategy for pam.d entry
The symbolic link did not stay a symbolic link.
Use %post in rpm instead.
2018-11-14 15:11:05 -05:00
Jarrod Johnson c56c1948a9 Add confluent pam to packaging
This enables PAM passwords to work by default,
rather than requiring user to perform more
configuration.
2018-11-14 14:55:26 -05:00
Jarrod Johnson 34b30a1bd8 Rework password authentication
Have the cache operate against the sha256 of the passphrase rather
than the passhprase directly (mitigate presence of password in memory).

Pull PAM into the cache behavior to accelerate PAM based usage.

Rearrange things so that both built in confluent and PAM passwords
can be used.
2018-11-14 14:52:03 -05:00
Jarrod Johnson 0e7bfe4f40 Use scanning status to control rescan behaviors
This causes the manual assignment to sleep less and for the rescan
command to meaningfully slow down shell requests to provide better sense
of when scan completes.
2018-11-14 14:17:12 -05:00
Jarrod Johnson a93a759b72 Make /networking/macs data available through nodediscover
This will help combine the data together more easily.
2018-11-14 14:11:03 -05:00
Jarrod Johnson cba514692c Bring the XCC check fix to IMM
The same check that works for XCC should be applied to IMM
2018-11-13 16:17:39 -05:00
Jarrod Johnson 2bb0a70a24 Provide an xid if one is not preserved
The SLP xid may be gone in a recheck, default to 42 in such a case.
2018-11-13 16:09:37 -05:00
Jarrod Johnson 1b84cd43fe Fix the auto-healing support
There was a mismatch between the handlers signature and expectation by
the discovery core.
2018-11-13 15:55:24 -05:00
Jarrod Johnson 39839a9f2a Add auto-healing of incomplete SLP data
If a device handler indicates an SLP data as incomplete, schedule a
attempt to fixup that specific one.
2018-11-13 15:37:41 -05:00
Jarrod Johnson 8473cb1cf8 Survive SLP values with =
If the slp parsing produces = in the value, ignore it and do best
effort to get the viable data out.
2018-11-09 15:38:40 -05:00
Jarrod Johnson 1a10b5d747 Have confluent2lxca use current config for address
In a confluent2lxca scenario, it is highly probably that confluent's
attribute is not viable for LXCA usage (link local address).

Give the current configuration the opportunity to replace the
confluent reference with the current IPv4 of the host.
2018-11-09 08:53:10 -05:00
Jarrod Johnson 82a7012527 Add a command to generate xClarity Administrator import
This will generate a bulk import file for use with the xClarity
Administrator appliance.
2018-11-09 08:33:04 -05:00
Jarrod Johnson 199a948b33 Fix ordering without custom field list
The order argument only had any meaning when used in custom fields.
Add a loop in the custom field agnostic section of the code to correct
the behavior.
2018-11-09 08:32:28 -05:00
Jarrod Johnson 6fc305d6a0 Add ordering by field to nodediscover list
This provides single column sort for more managable listing of
nodediscover items.
2018-11-06 16:34:22 -05:00
Jarrod Johnson 75b6221dcf Allow requesting custom columns
This allows for more data to be presented and some useless data
to be pruned.
2018-11-06 15:52:20 -05:00
Jarrod Johnson bd5032268e Fix csv output mode of nodediscover list
The changes made for tabulator broke csv output, fix that back.
2018-11-06 15:33:04 -05:00
Jarrod Johnson 0aa0289b06 Clean out dead code
There is some dead and commented code to be cleaned out.
2018-11-06 15:30:18 -05:00
Jarrod Johnson 96df7871cb Convert nodediscover to use Tabulator
This opens the path to more customized output and have the appropriate
formatting.  Also revise the Tabulator code to more closely match
the original nodediscover list output.
2018-11-06 15:28:27 -05:00
Jarrod Johnson 82ee69b56c Age out macmap on node database changes
This mitigates problems where automation fails to quickly
react to changes/fixes in the net attributes.
2018-11-06 08:18:37 -05:00
Jarrod Johnson 37899ad39e Do not inject 'None' into builddesc
If such data came from remote system, do not cause a stack trace.
2018-11-05 10:43:36 -05:00
Jarrod Johnson 43fb4467b7 Add error if chain goes off into non-existent nodes
Various behaviors break if the administrator declares a chain goes
to nowhere, throw up an error and hopefully see it caught to detect
this misconfiguration.
2018-11-02 15:56:34 -04:00
Jarrod Johnson f5f9403eff Fix incorrect variable name in nodediscover
The field name was incorrectly referencing the last field
rather than the current.
2018-11-02 15:50:02 -04:00
Jarrod Johnson 78e7381442 Add implicit rescan on unmatched csv data
If the csv file contains criteria we can't match, implicitly
try a rescan automatically to try to catch it.
2018-11-02 15:18:55 -04:00
Jarrod Johnson 005cefc914 Add volume creation and deletion to nodestorage 2018-11-01 16:59:42 -04:00
Jarrod Johnson 5f38cce51e Finish nodestorage show
It now shows the pertinent details of storage on a node.
2018-11-01 10:43:28 -04:00
Jarrod Johnson 5d358eaeb0 Switch to output format more like nodeinventory
Other confluent commands (apart from nodediscovery) set a particular
convention.  Go by that convention (at least by default)
2018-10-30 16:28:59 -04:00
Jarrod Johnson 115a6e9dd1 Add helper for tabular output
Provide a quick class for auto-sizing tabular output.
2018-10-30 16:23:34 -04:00
Jarrod Johnson a034d05cc8 Draft tabular output of disk information
Print out roughly the data that will be desired in the storage show.
2018-10-30 16:10:37 -04:00
Jarrod Johnson 6378f823f3 Add resource name to the storage messages
The storage messages need to have the references preserved
for CLI consumption.
2018-10-30 15:46:54 -04:00
Jarrod Johnson 079dfed11e Add type to storage messages
Storage messages are likely to be grouped together and need
more to disambiguate them.
2018-10-30 15:28:03 -04:00
Jarrod Johnson d836bf7298 Map health to nagios compatible codes
nodehealth adds granularity and health to the return code.
Rather than only reflecting failure running command, also
have nodehealth assign exit code according to the expectations
of nagios.
2018-10-30 13:56:09 -04:00
Jarrod Johnson 25b969a4db Add 'all' element to configuration/storage
Provide a way to provide all the data about storage configuration
in a single call.
2018-10-30 10:25:41 -04:00
Jarrod Johnson 950abde20e Remove 'all' from storage subelements
This concept is potentially problematic, as a volume named 'all' is
valid.  Additionally, if really wanting all, the caller probably wants
the full storage configuration, better provide that up a level to
avoid requiring many calls to do that.
2018-10-30 10:05:48 -04:00
Jarrod Johnson 37be696f23 Merge branch 'master' of github.com:jjohnson42/confluent 2018-10-29 14:56:37 -04:00
Jarrod Johnson 73cab3774d Add support for volume creation 2018-10-26 14:31:28 -04:00
Jarrod Johnson c2d52d4f83 Add functionality to delete volumes
Further enriching the storage api by adding the ability to delete
a volume.
2018-10-25 16:31:32 -04:00
Jarrod Johnson fd17559a73 Show result of changing disk state.
When changing disk state, let the caller know the result explicitly.
2018-10-25 16:11:54 -04:00
Jarrod Johnson 3945ccd5c3 Advance storage configuration API
Enumerate disks in an array in disks list.

Add array associated with disk to disk data.

Show detailed data on arrays and volumes.
2018-10-25 16:07:07 -04:00
Jarrod Johnson 7c006b33bc Implement functionality to configure disks
Disks may now have their state changed between hotspare, unconfigured,
and jbod.
2018-10-25 14:48:58 -04:00
Jarrod Johnson c96b5f0270 Fix spurious trace on immediate exit confetty
When confetty exits without doing anything, it causes
sockapi to reference an empty request.  Check for that before
checking if it is a collective request.
2018-10-25 14:12:15 -04:00
Jarrod Johnson 7e72240d98 Add protection against duplicate import data
nodediscover assign from csv will now guard against human error
in putting the same data in multiple times.
2018-10-25 11:07:56 -04:00
Jarrod Johnson c150a848fd Normalize drive state
Since drive state is a key api feature, make sure it is normalized
to a set vocabulary.
2018-10-24 16:36:38 -04:00
Jarrod Johnson bc7a197a8c Merge branch '18csi' 2018-10-23 09:16:32 -04:00
Jarrod Johnson d70c3dcad7 Merge branch '18csi' 2018-10-23 09:10:01 -04:00
Jarrod Johnson 76a96fa054 Merge branch '18csi' 2018-10-18 15:44:13 -04:00
Jarrod Johnson 2ff47b886d 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:36 -04:00
Jarrod Johnson 51037e2487 Change 'status' to 'state'
This is more consistent with several other confluent messages.
2018-10-17 09:37:31 -04:00
Jarrod Johnson 614b916ca4 Merge branch '18csi' 2018-10-16 09:39:00 -04:00
Jarrod Johnson 4077346d30 Rename api from drives to disks
It just feels more appropriate to use disks rather than drives.
2018-10-16 09:00:25 -04:00
Jarrod Johnson 8a4bf22a7e Further flesh out the storage api support
Add ability to list arrays and volumes.
2018-10-15 16:27:12 -04:00
Jarrod Johnson f835057ae4 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:14:59 -04:00
Jarrod Johnson 48913218a6 Merge branch 'master' of github.com:jjohnson42/confluent 2018-10-12 15:47:44 -04:00
Jarrod Johnson a11e4b71ce Merge branch '18csi' 2018-10-12 15:47:38 -04:00
Jarrod Johnson 8cae5ea101 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:28:21 -04:00
Jarrod Johnson f830658818 Merge branch '18csi' 2018-10-12 13:33:09 -04:00
Jarrod Johnson 92a3b02dc5 Merge branch '18csi' 2018-10-10 09:24:18 -04:00
Jarrod Johnson 049897fc15 Merge branch '18csi' 2018-10-10 09:21:07 -04:00
Jarrod Johnson 67b93ee6b0 Merge branch '18csi' 2018-10-09 09:46:44 -04:00
Jarrod Johnson a3bc17b465 Merge branch '18csi' 2018-10-08 15:56:43 -04:00
Jarrod Johnson 29fcd59f8b Fix usage of check_quorum()
It is not a boolean, it is exception driven.
2018-10-01 16:02:45 -04:00
Jarrod Johnson c13ebe36ae 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:31 -04:00
Jarrod Johnson 9495a41f85 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:42 -04:00
Jarrod Johnson 484435c732 Fix /networking/macs/by-mac
The module apimacmap was not correctly scoped.
2018-10-01 14:40:25 -04:00
Jarrod Johnson a5d5f4a6a4 Fix missing tenant argument on user management function
The tenant was omitted preventing those particular rpc calls from
working correctly.
2018-10-01 14:03:45 -04:00
Jarrod Johnson 51194b275b Do not restart on intentional kill
Additionally, add some output to help filter events log
2018-10-01 09:35:02 -04:00
Jarrod Johnson e634817b34 Add more collective event log handling
More detail to analyze how the collective membership is handled.
2018-09-27 15:14:45 -04:00
Jarrod Johnson 7f06c69d3c Add more logging to collective process 2018-09-27 10:49:57 -04:00
Jarrod Johnson b5af770f21 Add logging to the assimilation phase of collective
When attempting assimilation, provide logging about the attempt.
2018-09-27 10:38:00 -04:00
Jarrod Johnson 247650868e 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:30:30 -04:00
Jarrod Johnson 9e8b2dd973 Begin storage configuration support
Start by adding a storage oriented configuration area, and have it be
able to list and show detail on disks.
2018-09-26 15:29:29 -04:00
50 changed files with 3210 additions and 321 deletions
+167 -123
View File
@@ -2,7 +2,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2016 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.
@@ -49,6 +49,7 @@ import select
import shlex
import signal
import socket
import struct
import sys
import time
try:
@@ -89,6 +90,11 @@ netserver = None
laststate = {}
class BailOut(Exception):
def __init__(self, errorcode=0):
self.errorcode = errorcode
def print_help():
print("confetty provides a filesystem like interface to confluent. "
"Navigation is done using the same commands as would be used in a "
@@ -353,7 +359,7 @@ def do_command(command, server):
if argv[0] == 'exit':
if os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
sys.exit(0)
raise Bailout()
elif argv[0] in ('help', '?'):
return print_help()
elif argv[0] == 'cd':
@@ -434,6 +440,10 @@ def do_command(command, server):
currconsole = targpath
startrequest = {'operation': 'start', 'path': targpath,
'parameters': {}}
height, width = struct.unpack(
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
startrequest['parameters']['width'] = width
startrequest['parameters']['height'] = height
for param in argv[2:]:
(parmkey, parmval) = param.split("=")
startrequest['parameters'][parmkey] = parmval
@@ -478,7 +488,8 @@ def createresource(args):
collection = targpath
else:
collection, _, resname = targpath.rpartition('/')
keydata['name'] = resname
if 'name' not in keydata:
keydata['name'] = resname
makecall(session.create, (collection, keydata))
@@ -575,11 +586,20 @@ def fullpath_target(currpath, forcepath=False):
ntarget += '/'
return ntarget
def do_resize(a, b):
if not inconsole:
return
height, width = struct.unpack(
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
tlvdata.send(session.connection, {'operation': 'resize', 'width': width,
'height': height})
def startconsole(nodename):
global inconsole
global consolename
global didconsole
signal.signal(signal.SIGWINCH, do_resize)
didconsole = True
consolename = nodename
tty.setraw(sys.stdin.fileno())
@@ -603,7 +623,7 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
if fullexit:
if sys.stdout.isatty() and os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
sys.exit(code)
raise BailOut(code)
else:
tlvdata.send(session.connection, {'operation': 'stop',
'path': currconsole})
@@ -796,6 +816,10 @@ parser.add_option("-s", "--server", dest="netserver",
parser.add_option("-c", "--control", dest="controlpath",
help="Path to offer terminal control",
metavar="PATH")
parser.add_option(
'-m', '--mintime', default=0,
help='Minimum time to run or else pause for input (used to keep a '
'terminal from closing quickly on error)')
opts, shellargs = parser.parse_args()
username = None
@@ -824,45 +848,6 @@ def server_connect():
passphrase = getpass.getpass("Passphrase: ")
session.authenticate(username, passphrase)
try:
server_connect()
except EOFError, KeyboardInterrupt:
sys.exit(0)
except socket.gaierror:
sys.stderr.write('Could not connect to confluent\n')
sys.exit(1)
# clear on start can help with readable of TUI, but it
# can be annoying, so for now don't do it.
# sys.stdout.write('\x1b[H\x1b[J')
# sys.stdout.flush()
if sys.stdout.isatty():
import readline
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set bell-style none")
dl = readline.get_completer_delims().replace('-', '')
readline.set_completer_delims(dl)
readline.set_completer(completer)
doexit = False
inconsole = False
pendingcommand = ""
session_node = get_session_node(shellargs)
if session_node is not None:
consoleonly = True
do_command("start /nodes/%s/console/session" % session_node, netserver)
doexit = True
elif shellargs:
command = " ".join(shellargs)
do_command(command, netserver)
quitconfetty(fullexit=True, fixterm=False)
powerstate = None
powertime = None
def check_power_state():
tlvdata.send(
session.connection,
@@ -871,86 +856,145 @@ def check_power_state():
return
while inconsole or not doexit:
if inconsole:
try:
rdylist, _, _ = select.select(
(sys.stdin, session.connection), (), (), 10)
except select.error:
rdylist = ()
for fh in rdylist:
if fh == session.connection:
# this only should get called in the
# case of a console session
# each command should slurp up all relevant
# recv potential
#fh.read()
try:
data = tlvdata.recv(fh)
except Exception:
data = None
if type(data) == dict:
updatestatus(data)
continue
if data is not None:
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
try:
sys.stdout.write(data)
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in data:
sys.stdout.write(d)
now = time.time()
if ('showtime' not in laststate or
(now // 60) != laststate['showtime'] // 60):
# don't bother churning if minute does not change
laststate['showtime'] = now
updatestatus()
sys.stdout.flush()
else:
deadline = 5
connected = False
while not connected and deadline > 0:
try:
server_connect()
connected = True
except (socket.gaierror, socket.error):
pass
if not connected:
time.sleep(1)
deadline -=1
if connected:
do_command(
"start /nodes/%s/console/session skipreplay=True" % consolename,
netserver)
else:
doexit = True
inconsole = False
sys.stdout.write("\r\n[remote disconnected]\r\n")
break
else:
try:
myinput = fh.read()
myinput = check_escape_seq(myinput, fh)
if myinput:
tlvdata.send(session.connection, myinput)
except IOError:
pass
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
powertime = time.time()
powerstate = True
check_power_state()
else:
currcommand = prompt()
try:
do_command(currcommand, netserver)
except socket.error:
if sys.stdout.isatty():
import readline
def main():
global inconsole
try:
server_connect()
except EOFError, KeyboardInterrupt:
raise BailOut(0)
except socket.gaierror:
sys.stderr.write('Could not connect to confluent\n')
raise BailOut(1)
# clear on start can help with readable of TUI, but it
# can be annoying, so for now don't do it.
# sys.stdout.write('\x1b[H\x1b[J')
# sys.stdout.flush()
global powerstate, powertime, clearpowermessage
if sys.stdout.isatty():
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set bell-style none")
dl = readline.get_completer_delims().replace('-', '')
readline.set_completer_delims(dl)
readline.set_completer(completer)
doexit = False
inconsole = False
pendingcommand = ""
session_node = get_session_node(shellargs)
if session_node is not None:
consoleonly = True
do_command("start /nodes/%s/console/session" % session_node, netserver)
doexit = True
elif shellargs:
command = " ".join(shellargs)
do_command(command, netserver)
quitconfetty(fullexit=True, fixterm=False)
powerstate = None
powertime = None
while inconsole or not doexit:
if inconsole:
try:
rdylist, _, _ = select.select(
(sys.stdin, session.connection), (), (), 10)
except select.error:
rdylist = ()
for fh in rdylist:
if fh == session.connection:
# this only should get called in the
# case of a console session
# each command should slurp up all relevant
# recv potential
#fh.read()
try:
data = tlvdata.recv(fh)
except Exception:
data = None
if type(data) == dict:
updatestatus(data)
continue
if data is not None:
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
try:
sys.stdout.write(data)
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in data:
sys.stdout.write(d)
now = time.time()
if ('showtime' not in laststate or
(now // 60) != laststate['showtime'] // 60):
# don't bother churning if minute does not change
laststate['showtime'] = now
updatestatus()
sys.stdout.flush()
else:
deadline = 5
connected = False
while not connected and deadline > 0:
try:
server_connect()
connected = True
except (socket.gaierror, socket.error):
pass
if not connected:
time.sleep(1)
deadline -=1
if connected:
do_command(
"start /nodes/%s/console/session skipreplay=True" % consolename,
netserver)
else:
doexit = True
inconsole = False
sys.stdout.write("\r\n[remote disconnected]\r\n")
break
else:
try:
myinput = fh.read()
myinput = check_escape_seq(myinput, fh)
if myinput:
tlvdata.send(session.connection, myinput)
except IOError:
pass
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
powertime = time.time()
powerstate = True
check_power_state()
else:
currcommand = prompt()
try:
server_connect()
do_command(currcommand, netserver)
except socket.error:
doexit = True
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
try:
server_connect()
do_command(currcommand, netserver)
except socket.error:
doexit = True
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
if __name__ == '__main__':
errcode = 0
deadline = 0
if opts.mintime:
deadline = os.times()[4] + float(opts.mintime)
try:
main()
except BailOut as e:
errcode = e.errorcode
finally:
if deadline and os.times()[4] < deadline:
sys.stderr.write('[Exited early, hit enter to continue]')
sys.stdin.readline()
sys.exit(errcode)
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python
import optparse
import signal
import sys
import os
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.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts
\n ''')
argparser.add_option('-o', '--output',
help='Ansible hosts file')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
if not options.output:
sys.stderr.write('Output file must be specified by -o\n')
sys.exit(1)
sess = client.Command()
databynode = {}
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
for node in res.get('databynode', {}):
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
nodesbygroup = {}
with open(options.output, 'w') as importfile:
needempty = False
for node in sortutil.natural_sort(databynode):
data = databynode[node]
if not data.get('groups', []):
importfile.write(node + '\n')
needempty = True
for g in data.get('groups', []):
if g not in nodesbygroup:
nodesbygroup[g] = set([node])
else:
nodesbygroup[g].add(node)
if needempty:
importfile.write('\n')
for group in sortutil.natural_sort(nodesbygroup):
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(nodesbygroup[group]):
importfile.write('{0}\n'.format(node))
importfile.write('\n')
if __name__ == '__main__':
main()
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python
import csv
import optparse
import signal
import sys
import os
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.sortutil as sortutil
headers = '# Type,Serial Number,Current IP,Current username,Current password,New password,Recovery password,Switch enable password,New IPv4,IPv4 Subnet mask,IPv4 Default gateway,IPv4 DNS1,IPv4 DNS2,New IPv6,IPv6 Prefix,IPv6 Default gateway,IPv6 DNS1,IPv6 DNS2,Domain,Host name,Display name,Rack,Lowest Rack Unit,Height,Force,Stored credentials ID,Managed authentication,Group Name'.split(',')
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o bulkimport.csv
\n ''')
argparser.add_option('-o', '--output',
help='File to write for bulk import into xClarity Administrator')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
if not options.output:
sys.stderr.write('Output file must be specified by -o\n')
sys.exit(1)
if 'XCCUSER' not in os.environ or 'XCCPASS' not in os.environ:
sys.stderr.write('Must specify XCCUSER and XCCPASS in environment variables\n')
sys.exit(1)
xccuser = os.environ['XCCUSER']
xccpass = os.environ['XCCPASS']
sess = client.Command()
databynode = {}
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
for node in res.get('databynode', {}):
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
for res in sess.read(
'/noderange/{0}/configuration/management_controller/net_interfaces/management'.format(
noderange)):
for node in res.get('databynode', {}):
currip = res['databynode'][node].get('ipv4_address', {}).get(
'value', '')
if currip:
databynode[node]['hardwaremanagement.manager'] = {
'value': currip.split('/', 1)[0]}
with open(options.output, 'w') as importfile:
bulkimport = csv.writer(importfile)
bulkimport.writerow(headers)
for node in sortutil.natural_sort(databynode):
data = databynode[node]
row = ['server', lookupdata(data, 'id.serial'), lookupdata(data, 'hardwaremanagement.manager'), xccuser, xccpass, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', node, lookupdata(data, 'location.rack'), lookupdata(data, 'location.u'), '', '', '','', '']
bulkimport.writerow(row)
if __name__ == '__main__':
main()
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python
import csv
import optparse
import signal
import sys
import os
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.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts
\n ''')
argparser.add_option('-o', '--output',
help='xCAT stanza file')
argparser.add_option('-m', '--macs',
help='xCAT macs.csv to write')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
if not (options.output or options.macs):
sys.stderr.write('Output file must be specified by -o or -m\n')
sys.exit(1)
sess = client.Command()
databynode = {}
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
for node in res.get('databynode', {}):
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
if options.output:
with open(options.output, 'w') as importfile:
for node in sortutil.natural_sort(databynode):
xcatattrs = collect_attribute_data(databynode, node)
importfile.write('{0}:\n'.format(node))
importfile.write(' objtype=node\n')
importfile.write(' arch=x86_64\n')
importfile.write(' netboot=xnba\n')
importfile.write(' mgt=ipmi\n')
for attr in xcatattrs:
if xcatattrs[attr] is None:
continue
importfile.write(' {0}={1}\n'.format(attr,
xcatattrs[attr]))
importfile.write('\n')
if options.macs:
with open(options.macs, 'w') as importfile:
macsv = csv.writer(importfile)
macsv.writerow(['#node', 'mac'])
for node in sortutil.natural_sort(databynode):
xcatattrs = collect_attribute_data(databynode, node)
macsv.writerow([node, xcatattrs['mac']])
def collect_attribute_data(databynode, node):
data = databynode[node]
xcatattrs = {}
xcatattrs['groups'] = ','.join(data.get('groups', []))
xcatattrs['bmc'] = data.get('hardwaremanagement.manager', {}).get(
'value', None)
xcatattrs['mpa'] = data.get('enclosure.manager', {}).get(
'value', None)
xcatattrs['slotid'] = data.get('enclosure.bay', {}).get(
'value', None)
gotmac = False
for key in data:
if key.startswith('net.') and 'hwaddr' in key:
currmac = data[key].get('value', None)
if currmac:
if gotmac:
sys.stderr.write(
'Ignoring {0} and using only {1} for mac, '
'multiple macs not supported by '
'confluent2xcat\n'.format(key, gotmac))
continue
gotmac = key
xcatattrs['mac'] = currmac
return xcatattrs
if __name__ == '__main__':
main()
+67 -51
View File
@@ -19,6 +19,7 @@
import os
import signal
import optparse
import shlex
import sys
try:
@@ -47,6 +48,8 @@ argparser.add_option('-c', '--comparedefault', dest='comparedefault',
action='store_true', default=False,
help='Compare given settings to default or list settings '
'that are non default')
argparser.add_option('-b', '--batch', dest='batch', metavar='settings.batch',
default=False, help='Provide settings in a batch file')
argparser.add_option('-d', '--detail', dest='detail',
action='store_true', default=False,
help='Provide verbose information as available, such as '
@@ -118,61 +121,74 @@ def _assign_value():
assignment[key] = value
for param in args[1:]:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
setmode = True
forceset = True
continue
if needval:
key = needval
value = param
_assign_value()
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
def parse_config_line(arguments):
global setmode, forceset, key, value, needval, candidate, path, attrib
for param in arguments:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
forceset = True
continue
if needval:
key = needval
needval = None
value = param
_assign_value()
elif param[-1] == ':':
needval = param[:-1]
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
_assign_value()
elif param[-1] == ':':
needval = param[:-1]
else:
needval = param
else:
needval = param
else:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
printsys.append(param)
elif param not in cfgpaths:
printsys.append(param)
elif param not in cfgpaths:
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
if options.batch:
printsys = []
argfile = open(options.batch, 'r')
argset = argfile.readline()
while argset:
parse_config_line(shlex.split(argset))
argset = argfile.readline()
else:
parse_config_line(args[1:])
session = client.Command()
rcode = 0
if options.restoredefault and options.restoredefault.lower() in (
+42 -2
View File
@@ -17,7 +17,14 @@
import optparse
import os
import subprocess
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
argparser = optparse.OptionParser(
@@ -26,9 +33,42 @@ argparser = optparse.OptionParser(
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
"For example, ctrl-'e', then 'c', then '.' will exit the current "
"console")
argparser.add_option('-t', '--tile', action='store_true', default=False,
help='Tile console windows in the terminal')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
sys.exit(1)
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
if options.tile:
nodes = []
sess = client.Command()
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
node = res.get('item', {}).get('href', '/').replace('/', '')
if not node:
sys.stderr.write(res.get('error', repr(res)) + '\n')
sys.exit(1)
nodes.append(node)
initial = True
pane = 0
for node in sortutil.natural_sort(nodes):
if initial:
initial = False
subprocess.call(
['tmux', 'new-session', '-d', '-s',
'nodeconsole_{0}'.format(os.getpid()), '-x', '800', '-y',
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
else:
subprocess.call(['tmux', 'select-pane', '-t', str(pane)])
pane += 1
subprocess.call(
['tmux', 'split', '-h',
'{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
subprocess.call(['tmux', 'select-layout', 'tiled'])
subprocess.call(['tmux', 'select-pane', '-t', '0'])
os.execlp('tmux', 'tmux', 'attach', '-t', 'nodeconsole_{0}'.format(
os.getpid()))
else:
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
+91 -22
View File
@@ -19,6 +19,7 @@ import csv
import optparse
import os
import sys
import time
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
@@ -26,31 +27,52 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
tabformat = '{0:>15}|{1:>15}|{2:>15}|{3:>36}|{4:>17}|{5:>12}|{6:>48}'
columns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
'Current IP Addresses']
delimit = ['-' * 15, '-' * 15, '-' * 15, '-' * 36, '-' * 17, '-' * 12,
'-' * 48]
defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
'Current IP Addresses']
columnmapping = {
'Node': 'nodename',
'Model': 'modelnumber',
'Serial': 'serialnumber',
'UUID': 'uuid',
'Type': 'types',
'Current IP Addresses': 'ipaddrs',
'IP': 'ipaddrs',
'Bay': 'bay',
'Mac Address': 'macs',
'Mac': 'macs',
'Switch': 'switch',
'Port': 'port',
'Advertised IP': 'otheripaddrs',
'Other IP': 'otheripaddrs',
}
def dumpmacs(procinfo):
return ','.join(procinfo['macs']) # + procinfo.get('relatedmacs', []))
def print_disco(options, session, currmac):
def print_disco(options, session, currmac, outhandler, columns):
procinfo = {}
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
procinfo.update(tmpinfo)
record = [procinfo['nodename'], procinfo['modelnumber'],
procinfo['serialnumber'], procinfo['uuid'], dumpmacs(procinfo),
','.join(procinfo['types']),
','.join(sorted(procinfo['ipaddrs']))]
if 'Switch' in columns or 'Port' in columns:
for tmpinfo in session.read(
'/networking/macs/by-mac/{0}'.format(currmac)):
if 'ports' in tmpinfo:
# The api sorts so that the most specific available value
# is last
procinfo.update(tmpinfo['ports'][-1])
record = []
for col in columns:
rawcol = columnmapping[col]
rawval = procinfo.get(rawcol, '')
if isinstance(rawval, list):
record.append(','.join(rawval))
else:
record.append(str(rawval))
if options.csv:
csv.writer(sys.stdout).writerow(record)
else:
print(tabformat.format(*record))
outhandler.add_row(record)
def process_header(header):
@@ -72,7 +94,7 @@ def process_header(header):
elif datum in ('bmc', 'imm', 'xcc'):
fields.append('hardwaremanagement.manager')
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
fields.append('net.bmc.gateway')
fields.append('net.bmc.ipv4_gateway')
elif datum in ('bmcuser', 'username', 'user'):
fields.append('secret.hardwaremanagementuser')
elif datum in ('bmcpass', 'password', 'pass'):
@@ -122,18 +144,35 @@ def datum_to_attrib(datum):
del datum['node']
return datum
unique_fields = frozenset(['serial', 'mac', 'uuid'])
def import_csv(options, session):
nodedata = []
unique_data = {}
with open(options.importfile, 'r') as datasrc:
records = csv.reader(datasrc)
fields = process_header(next(records))
for field in fields:
if field in unique_fields:
unique_data[field] = set([])
for record in records:
currfields = list(fields)
nodedatum = {}
for datum in record:
nodedatum[currfields.pop(0)] = datum
currfield = currfields.pop(0)
if currfield in unique_fields:
if datum in unique_data[currfield]:
sys.stderr.write(
"Import contains duplicate values "
"({0} with value {1}\n".format(currfield, datum)
)
sys.exit(1)
unique_data[currfield].add(datum)
nodedatum[currfield] = datum
if not datum_complete(nodedatum):
sys.exit(1)
if not search_record(nodedatum, options, session):
blocking_scan(session)
if not search_record(nodedatum, options, session):
sys.stderr.write(
"Could not match the following data: " +
@@ -165,13 +204,30 @@ def import_csv(options, session):
def list_discovery(options, session):
outhandler = None
orderby = None
if options.fields:
columns = []
for field in options.fields.split(','):
for cdt in columnmapping:
if cdt.lower().replace(
' ', '') == field.lower().replace(' ', ''):
columns.append(cdt)
else:
columns = defcolumns
if options.order:
for field in columns:
if options.order.lower() == field.lower():
orderby = field
if options.csv:
csv.writer(sys.stdout).writerow(columns)
else:
print(tabformat.format(*columns))
print(tabformat.format(*delimit))
outhandler = client.Tabulator(columns)
for mac in list_matching_macs(options, session):
print_disco(options, session, mac)
print_disco(options, session, mac, outhandler, columns)
if outhandler:
for row in outhandler.get_table(orderby):
print(row)
def clear_discovery(options, session):
for mac in list_matching_macs(options, session):
@@ -220,6 +276,10 @@ def assign_discovery(options, session):
if abort:
sys.exit(1)
matches = list_matching_macs(options, session)
if not matches:
# Do a rescan to catch missing requested data
blocking_scan(session)
matches = list_matching_macs(options, session)
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
@@ -230,6 +290,10 @@ def assign_discovery(options, session):
else:
print(repr(res))
def blocking_scan(session):
list(session.update('/discovery/rescan', {'rescan': 'start'}))
while(list(session.read('/discovery/rescan'))[0].get('scanning', False)):
time.sleep(0.5)
def main():
@@ -266,6 +330,11 @@ def main():
parser.add_option('-i', '--import', dest='importfile',
help='Import bulk assignment data from given CSV file',
metavar='IMPORT.CSV')
parser.add_option('-f', '--fields', dest='fields',
help='Select fields for output',
metavar='FIELDS')
parser.add_option('-o', '--order', dest='order',
help='Order output by given field', metavar='ORDER')
(options, args) = parser.parse_args()
if len(args) == 0 or args[0] not in ('list', 'assign', 'rescan', 'clear'):
parser.print_help()
@@ -278,8 +347,8 @@ def main():
if args[0] == 'assign':
assign_discovery(options, session)
if args[0] == 'rescan':
list(session.update('/discovery/rescan', {'rescan': 'start'}))
print("Rescan initiated")
blocking_scan(session)
print("Rescan complete")
if __name__ == '__main__':
+4 -4
View File
@@ -37,14 +37,14 @@ exitcode = 0
def printfirm(node, prefix, data):
if 'model' in data:
if 'model' in data and data['model']:
prefix += ' ' + data['model']
builddesc = []
if 'build' in data:
if 'build' in data and data['build']:
builddesc.append(data['build'])
if 'date' in data:
if 'date' in data and data['date']:
builddesc.append(data['date'])
if 'version' in data:
if 'version' in data and data['version']:
version = data['version']
if builddesc:
version += ' ({0})'.format(' '.join(builddesc))
+11 -2
View File
@@ -44,6 +44,13 @@ except IndexError:
client.check_globbing(noderange)
codemappings = {
'ok': 0,
'warning': 1,
'critical': 2,
'failed': 2,
}
def main():
global session, exitcode, healthbynode, healthexplanations, health, node, sensor, explanation
session = client.Command()
@@ -66,9 +73,11 @@ def main():
if 'error' in health[node]:
sys.stderr.write('{0}: Error: {1}\n'.format(
node, health[node]['error']))
exitcode |= 1
exitcode = 3
if 'health' in health[node]:
healthbynode[node] = health[node]['health']['value']
if codemappings[healthbynode[node]] > exitcode:
exitcode = codemappings[healthbynode[node]]
if 'sensors' in health[node]:
healthexplanations[node] = []
for sensor in health[node]['sensors']:
@@ -94,5 +103,5 @@ try:
main()
except KeyboardInterrupt:
print('')
sys.exit(0)
sys.exit(exitcode)
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 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
exitcode = 0
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][install <file>]")
(options, args) = argparser.parse_args()
upfile = None
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'install':
upfile = args[2]
else:
components = ['all']
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
def install_license(session, filename):
global exitcode
resource = '/noderange/{0}/configuration/' \
'management_controller/licenses/'.format(noderange)
filename = os.path.abspath(filename)
instargs = {'filename': filename}
for res in session.create(resource, instargs):
pass # print(repr(res))
show_licenses()
def show_licenses(session):
global exitcode
firmware_shown = False
for res in session.read(
'/noderange/{0}/configuration/management_controller/licenses/'
'all'.format(noderange)):
for node in res.get('databynode', {}):
for license in res['databynode'][node].get('License', []):
print('{0}: {1}'.format(node, license.get('feature',
'Unknown')))
try:
session = client.Command()
if upfile is None:
show_licenses(session)
else:
install_license(session, upfile)
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
+4 -1
View File
@@ -46,7 +46,10 @@ def main():
noderange = args[0]
nodelist = '/noderange/{0}/nodes/'.format(noderange)
except IndexError:
nodelist = '/nodes/'
if os.environ.get('TEST_XCAT'):
nodelist = "/xcat/nodes/"
else:
nodelist = '/nodes/'
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
+3 -3
View File
@@ -53,8 +53,8 @@ except IndexError:
argparser.print_help()
sys.exit(1)
bootdev = None
if len(sys.argv) > 2:
bootdev = sys.argv[2]
if len(args) > 1:
bootdev = args[1]
if bootdev in ('net', 'pxe'):
bootdev = 'network'
session = client.Command()
@@ -65,4 +65,4 @@ else:
bootmode = 'uefi'
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist))
persistent=options.persist))
+203
View File
@@ -0,0 +1,203 @@
#!/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
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
exitcode = 0
class OptParser(optparse.OptionParser):
def format_epilog(self, formatter):
return self.expand_prog_name(self.epilog)
def mbtohuman(mb):
if mb > 1000000:
return '{0:.3f} TB'.format(mb/1000000.0)
if mb > 1000:
return '{0:.3f} GB'.format(mb/1000.0)
return '{0:.3f} MB'.format(mb)
def showstorage(noderange, options, args):
global exitcode
session = client.Command()
disks = {}
arrays = {}
volumes = {}
scfg = session.read('/noderange/{0}/configuration/storage/all'.format(
noderange))
_print_cfg(scfg)
def _print_cfg(scfg):
global exitcode
storagebynode = {}
for e in scfg:
if 'error' in e:
sys.stderr.write(e['error'] + '\n')
exitcode = e.get('errorcode', 1)
for node in e.get('databynode', {}):
if node not in storagebynode:
storagebynode[node] = {'disks': [], 'arrays': [],
'volumes': []}
curr = e['databynode'][node]
storagebynode[node][curr['type'] + 's'].append(curr)
for node in storagebynode:
for disk in sorted(storagebynode[node]['disks'],
key=lambda x: x['name']):
print('{0}: Disk {1} Description: {2}'.format(
node, disk['name'], disk['description']))
print('{0}: Disk {1} State: {2}'.format(node, disk['name'],
disk['state']))
print('{0}: Disk {1} FRU: {2}'.format(node, disk['name'],
disk['fru']))
print('{0}: Disk {1} Serial Number: {2}'.format(node, disk['name'],
disk['serial']))
if disk['array']:
print('{0}: Disk {1} Array: {2}'.format(node, disk['name'],
disk['array']))
for arr in storagebynode[node]['arrays']:
print('{0}: Array {1} Available Capacity: {2}'.format(
node, arr['id'], mbtohuman(arr['available'])))
print('{0}: Array {1} Total Capacity: {2}'.format(
node, arr['id'], mbtohuman(arr['capacity'])))
print('{0}: Array {1} RAID: {2}'.format(node, arr['id'],
arr['raid']))
print('{0}: Array {1} Disks: {2}'.format(node, arr['id'], ','.join(
arr['disks'])))
print(
'{0}: Array {1} Volumes: {2}'.format(node, arr['id'], ','.join(
arr['volumes'])))
for vol in storagebynode[node]['volumes']:
print('{0}: Volume {1}: Size: {2}'.format(node, vol['name'],
mbtohuman(vol['size'])))
print('{0}: Volume {1}: State: {2}'.format(node, vol['name'],
vol['state']))
print('{0}: Volume {1}: Array {2}'.format(node, vol['name'],
vol['array']))
def createstorage(noderange, options, args):
if options.raidlevel is None or options.disks is None:
sys.stderr.write('-r and -d are required arguments to create array\n')
sys.exit(1)
session = client.Command()
names = options.name
if names is None:
names = ''.join(args)
parms = {'disks': options.disks, 'raidlevel': options.raidlevel,
'name': names}
if options.size:
parms['size'] = options.size
_print_cfg(session.create(
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
noderange, names), parms))
def deletestorage(noderange, options, args):
if options.name is None:
if len(args) == 1:
names = args[0]
else:
sys.stderr.write('-n is required to indicate volume(s) to delete\n')
sys.exit(1)
else:
names = options.name
session = client.Command()
for rsp in session.delete(
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
noderange, names)):
if 'deleted' in rsp:
print('Deleted: {0}'.format(rsp['deleted']))
elif 'databynode' in rsp:
for node in rsp['databynode']:
if 'error' in rsp['databynode'][node]:
sys.stderr.write('{0}: {1}\n'.format(
node, rsp['databynode'][node]['error']))
else:
sys.stderr.write('{0}: {1}\n'.format(
node, repr(rsp['databynode'][node])))
else:
print(repr(rsp))
def setstorage(noderange, options, args):
pass
funmap = {
'create': createstorage,
'show': showstorage,
'set': setstorage,
'delete': deletestorage,
'rm': deletestorage,
}
def main():
argparser = OptParser(
usage='Usage: %prog <noderange> [show|create|set|delete]',
epilog='',
)
argparser.add_option('-r', '--raidlevel', type='int',
help='RAID level to use when creating an array')
argparser.add_option('-d', '--disks', type='str',
help='Comma separated list of disks to use, or the '
'word "rest" to indicate use of all available '
'disks')
argparser.add_option('-s', '--size', type='str',
help='Comma separated list of sizes to use when '
'creating volumes. The sizes may be absolute '
'size (e.g. 16gb), percentage (10%) or the word '
'"rest" to use remaining capacity, default '
'behavior is to use all capacity to make a '
'volume')
argparser.add_option('-n', '--name', type='str',
help='Comma separated list of names to use when '
'naming volumes, or selecting a volume for '
'delete. Default behavior is to use '
'implementation provided default names.')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
operation = args[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, options, args[2:])
if __name__ == '__main__':
main()
+36
View File
@@ -25,6 +25,7 @@ import socket
import ssl
import sys
import confluent.tlvdata as tlvdata
import confluent.sortutil as sortutil
SO_PASSCRED = 16
@@ -35,6 +36,41 @@ _attraliases = {
}
class Tabulator(object):
def __init__(self, headers):
self.headers = headers
self.rows = []
def add_row(self, row):
self.rows.append(row)
def get_table(self, order=None):
i = 0
fmtstr = ''
separator = []
for head in self.headers:
if order and order == head:
order = i
neededlen = len(head)
for row in self.rows:
if len(row[i]) > neededlen:
neededlen = len(row[i])
separator.append('-' * (neededlen + 1))
fmtstr += '{{{0}:>{1}}}|'.format(i, neededlen + 1)
i = i + 1
fmtstr = fmtstr[:-1]
yield fmtstr.format(*self.headers)
yield fmtstr.format(*separator)
if order is not None:
for row in sorted(
self.rows,
key=lambda x: sortutil.naturalize_string(x[order])):
yield fmtstr.format(*row)
else:
for row in self.rows:
yield fmtstr.format(*row)
def printerror(res, node=None):
exitcode = 0
if 'errorcode' in res:
+21
View File
@@ -36,7 +36,9 @@ alias nodereseat='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export C
alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderun'
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 nodestorage='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodestorage'
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelicense'
_confluent_get_args()
@@ -152,6 +154,24 @@ _confluent_nodeshell_completion()
fi
}
_confluent_nodelicense_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "install list" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return
fi
}
_confluent_nodesupport_completion()
{
_confluent_get_args
@@ -252,4 +272,5 @@ complete -F _confluent_nr_completion nodesensors
complete -F _confluent_nodesetboot_completion nodesetboot
complete -F _confluent_nodeshell_completion nodeshell
complete -F _confluent_nodesupport_completion nodesupport
complete -F _confluent_nodelicense_completion nodelicense
+3
View File
@@ -35,6 +35,9 @@ given as a node expression, as documented in the man page for nodeattribexpressi
configuration reset to default. Currently the only component implemented
is uefi.
* `-b`, '--batch':
Provide arguments as lines of a file, rather than the command line.
## EXAMPLES
* Showing the current IP configuration of noderange BMC/IMM/XCC:
+7 -1
View File
@@ -2,7 +2,7 @@ nodeconsole(8) -- Open a console to a confluent node
=====================================================
## SYNOPSIS
`nodeconsole <node>`
`nodeconsole [options] <noderange>`
## DESCRIPTION
@@ -16,6 +16,12 @@ will initiate an automatic retry interval that is randomized between 2 and 4 min
The reopen escape sequence below requests an immediate retry, as does connecting
a new session.
## OPTIONS
* `-t`, `--tile`:
Use tmux to arrange consoles of the given noderange into a tiled layout on
the terminal screen
## ESCAPE SEQUENCE COMMANDS
While connected to a console, a number of commands may be performed through escape
@@ -75,6 +75,20 @@ the nodes.
* `-e MAC`, `--ethaddr=MAC`:
Operate against the system with the specified MAC
address
* `-f FIELDS`, `--fields=FIELDS`:
Request a custom set of fields. The available fields are:
Node: The node name if a correlation has been identified
Model: The model number
Serial: The serial number
UUID: The UUID as it should appear in DMI
Type: Device type (e.g. lenovo-xcc, pxe-client, etc)
IP: The confirmed working IP addresses associated with the record
Mac: Mac address of the relevant network interface
Switch: The nearest detected switch to the entry
Port: Port of the switch that most closely connects to the network interface
Advertised IP: IP addresses that may not have been confirmed, but are advertised
* `-o ORDER`, `--order=ORDER`:
Order output by given field. Field names are the same as documented in the -f argument.
* `-t TYPE`, `--type=TYPE`:
Operate against the system of the specified type
* `-c`, `--csv`:
+11
View File
@@ -0,0 +1,11 @@
nodelicense(8) -- Manage license keys on BMC
=================================================================
## SYNOPSIS
`nodelicense <noderange> [list|install <filename>]`
## DESCRIPTION
`nodelicense` shows and installs license keys on supported BMCs
+1
View File
@@ -1,3 +1,4 @@
include pam/*
include sysvinit/*
include systemd/*
include sysctl/*
+50
View File
@@ -0,0 +1,50 @@
import shutil
import socket
import subprocess
import tempfile
def get_openssl_conf_location():
# CentOS/RHAT
return '/etc/pki/tls/openssl.cnf'
def get_ip_addresses():
lines = subprocess.check_output('ip addr'.split(' '))
for line in lines.split('\n'):
if line.startswith(' inet6 '):
line = line.replace(' inet6 ', '').split('/')[0]
if line.startswith('fe80::'):
continue
if line == '::1':
continue
elif line.startswith(' inet '):
line = line.replace(' inet ', '').split('/')[0]
if line == '127.0.0.1':
continue
if line.startswith('169.254.'):
continue
else:
continue
yield line
def create_certificate():
shortname = socket.gethostname().split('.')[0]
longname = socket.getfqdn()
subprocess.check_call(
'openssl ecparam -name secp384r1 -genkey -out privkey.pem'.split(' '))
san = ['IP:{0}'.format(x) for x in get_ip_addresses()]
san.append('DNS:{0}'.format(shortname))
san.append('DNS:{0}'.format(longname))
san = ','.join(san)
sslcfg = get_openssl_conf_location()
tmpconfig = tempfile.mktemp()
shutil.copy2(sslcfg, tmpconfig)
with open(tmpconfig, 'a') as cfgfile:
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
subprocess.check_call(
'openssl req -new -x509 -key privkey.pem -days 7300 -out cert.pem '
'-subj /CN={0} -extensions SAN '
'-config {1}'.format(longname, tmpconfig).split(' ')
)
if __name__ == '__main__':
create_certificate()
+7 -1
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016 Lenovo
# Copyright 2016-2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -83,6 +83,8 @@ class AsyncSession(object):
self.reaper = eventlet.spawn_after(15, self.destroy)
def add(self, requestid, rsp):
if self.responses is None:
return
self.responses.append((requestid, rsp))
if self._evt:
self._evt.send()
@@ -107,6 +109,7 @@ class AsyncSession(object):
for console in self.consoles:
_consolesessions[console]['session'].destroy()
self.consoles = None
self.responses = None
del _asyncsessions[self.asyncid]
def run_handler(self, handler, requestid):
@@ -166,6 +169,9 @@ def handle_async(env, querydict, threadset):
currsess = AsyncSession()
yield messages.AsyncSession(currsess.asyncid)
return
if querydict['asyncid'] not in _asyncsessions:
raise exc.InvalidArgumentException(
'Invalid or expired async id')
mythreadid = greenlet.getcurrent()
threadset.add(mythreadid)
loggedout = None
+46 -43
View File
@@ -158,20 +158,13 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
# but here there's no need for that
eventlet.sleep(0.5)
credobj = Credentials(user, passphrase)
try:
pammy = PAM.pam()
pammy.start(_pamservice, user, credobj.pam_conv)
pammy.authenticate()
pammy.acct_mgmt()
del pammy
return authorize(user, element, tenant, skipuserobj=False)
except NameError:
pass
except PAM.error:
if credobj.haspam:
return None
cfm = configmanager.ConfigManager(tenant, username=user)
ucfg = cfm.get_user(user)
if ucfg is None:
eventlet.sleep(0.05)
return None
if (user, tenant) in _passcache:
if passphrase == _passcache[(user, tenant)]:
if hashlib.sha256(passphrase).digest() == _passcache[(user, tenant)]:
return authorize(user, element, tenant)
else:
# In case of someone trying to guess,
@@ -179,38 +172,48 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
# invalidate cache and force the slower check
del _passcache[(user, tenant)]
return None
cfm = configmanager.ConfigManager(tenant, username=user)
ucfg = cfm.get_user(user)
if ucfg is None or 'cryptpass' not in ucfg:
eventlet.sleep(0.05) # stall even on test for existence of a username
return None
_passchecking[(user, tenant)] = True
# TODO(jbjohnso): WORKERPOOL
# PBKDF2 is, by design, cpu intensive
# throw it at the worker pool when implemented
# maybe a distinct worker pool, wondering about starving out non-auth stuff
salt, crypt = ucfg['cryptpass']
# execute inside tpool to get greenthreads to give it a special thread
# world
#TODO(jbjohnso): util function to generically offload a call
#such a beast could be passed into pyghmi as a way for pyghmi to
#magically get offload of the crypto functions without having
#to explicitly get into the eventlet tpool game
global authworkers
global authcleaner
if authworkers is None:
authworkers = multiprocessing.Pool(processes=1)
else:
authcleaner.cancel()
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
del _passchecking[(user, tenant)]
eventlet.sleep(0.05) # either way, we want to stall so that client can't
if 'cryptpass' in ucfg:
_passchecking[(user, tenant)] = True
# TODO(jbjohnso): WORKERPOOL
# PBKDF2 is, by design, cpu intensive
# throw it at the worker pool when implemented
# maybe a distinct worker pool, wondering about starving out non-auth stuff
salt, crypt = ucfg['cryptpass']
# execute inside tpool to get greenthreads to give it a special thread
# world
# TODO(jbjohnso): util function to generically offload a call
# such a beast could be passed into pyghmi as a way for pyghmi to
# magically get offload of the crypto functions without having
# to explicitly get into the eventlet tpool game
global authworkers
global authcleaner
if authworkers is None:
authworkers = multiprocessing.Pool(processes=1)
else:
authcleaner.cancel()
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
del _passchecking[(user, tenant)]
eventlet.sleep(
0.05) # either way, we want to stall so that client can't
# determine failure because there is a delay, valid response will
# delay as well
if crypt == crypted:
_passcache[(user, tenant)] = passphrase
return authorize(user, element, tenant)
if crypt == crypted:
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant)
try:
pammy = PAM.pam()
pammy.start(_pamservice, user, credobj.pam_conv)
pammy.authenticate()
pammy.acct_mgmt()
del pammy
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, skipuserobj=False)
except NameError:
pass
except PAM.error:
pass
eventlet.sleep(0.05) # stall even on test for existence of a username
return None
+29 -8
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2017 Lenovo
# Copyright 2017-2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -145,8 +145,9 @@ class ConsoleHandler(object):
_genwatchattribs = frozenset(('console.method', 'console.logging',
'collective.manager'))
def __init__(self, node, configmanager):
def __init__(self, node, configmanager, width=80, height=24):
self.clearpending = False
self.initsize = (width, height)
self._dologging = True
self._is_local = True
self._isondemand = False
@@ -372,6 +373,7 @@ class ConsoleHandler(object):
'error': self.error})
return
self.send_break = self._console.send_break
self.resize = self._console.resize
if self._attribwatcher:
self.cfgmgr.remove_watcher(self._attribwatcher)
self._attribwatcher = None
@@ -383,6 +385,7 @@ class ConsoleHandler(object):
self._attribwatcher = self.cfgmgr.watch_attributes(
(self.node,), attribstowatch, self._attribschanged)
try:
self.resize(width=self.initsize[0], height=self.initsize[1])
self._console.connect(self.get_console_output)
except exc.TargetEndpointBadCredentials:
self.clearbuffer()
@@ -628,17 +631,20 @@ def start_console_sessions():
configmodule.hook_new_configmanagers(_start_tenant_sessions)
def connect_node(node, configmanager, username=None, direct=True):
def connect_node(node, configmanager, username=None, direct=True, width=80,
height=24):
attrval = configmanager.get_node_attributes(node, 'collective.manager')
myc = attrval.get(node, {}).get('collective.manager', {}).get(
'value', None)
myname = collective.get_myname()
if myc and myc != collective.get_myname() and direct:
minfo = configmodule.get_collective_member(myc)
return ProxyConsole(node, minfo, myname, configmanager, username)
return ProxyConsole(node, minfo, myname, configmanager, username,
width, height)
consk = (node, configmanager.tenant)
if consk not in _handled_consoles:
_handled_consoles[consk] = ConsoleHandler(node, configmanager)
_handled_consoles[consk] = ConsoleHandler(node, configmanager, width,
height)
return _handled_consoles[consk]
# A stub console handler that just passes through to a remote confluent
@@ -647,8 +653,10 @@ def connect_node(node, configmanager, username=None, direct=True):
class ProxyConsole(object):
_genwatchattribs = frozenset(('collective.manager',))
def __init__(self, node, managerinfo, myname, configmanager, user):
def __init__(self, node, managerinfo, myname, configmanager, user, width,
height):
self.skipreplay = True
self.initsize = (width, height)
self.managerinfo = managerinfo
self.myname = myname
self.cfm = configmanager
@@ -659,6 +667,7 @@ class ProxyConsole(object):
self._attribwatcher = configmanager.watch_attributes(
(self.node,), self._genwatchattribs, self._attribschanged)
def _attribschanged(self, nodeattribs, configmanager, **kwargs):
if self.clisession:
self.clisession.detach()
@@ -700,6 +709,8 @@ class ProxyConsole(object):
'tenant': self.cfm.tenant,
'node': self.node,
'skipreplay': self.skipreplay,
'width': self.initsize[0],
'height': self.initsize[1],
#TODO(jjohnson2): declare myself as a proxy,
#facilitate redirect rather than relay on manager change
},
@@ -739,6 +750,10 @@ class ProxyConsole(object):
def reopen(self):
tlvdata.send(self.remote, {'operation': 'reopen'})
def resize(self, width, height):
tlvdata.send(self.remote, {'operation': 'resize', 'width': width,
'height': height})
# this represents some api view of a console handler. This handles things like
# holding the caller specific queue data, for example, when http api should be
@@ -763,7 +778,7 @@ class ConsoleSession(object):
"""
def __init__(self, node, configmanager, username, datacallback=None,
skipreplay=False, direct=True):
skipreplay=False, direct=True, width=80, height=24):
self.registered = False
self.tenant = configmanager.tenant
if not configmanager.is_node(node):
@@ -773,6 +788,8 @@ class ConsoleSession(object):
self.configmanager = configmanager
self.direct = direct # true if client is directly connected versus
# relay
self.width = width
self.height = height
self.connect_session()
self.registered = True
self._evt = None
@@ -801,12 +818,16 @@ class ConsoleSession(object):
between console and shell.
"""
self.conshdl = connect_node(self.node, self.configmanager,
self.username, self.direct)
self.username, self.direct, self.width,
self.height)
def send_break(self):
"""Send break to remote system
"""
self.conshdl.send_break()
def resize(self, width, height):
self.conshdl.resize(width, height)
def get_buffer_age(self):
"""Get the age in seconds of the buffered data
+34 -2
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2017 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.
@@ -123,7 +123,7 @@ def load_plugins():
rootcollections = ['discovery/', 'events/', 'networking/',
'noderange/', 'nodes/', 'nodegroups/', 'users/', 'version']
'noderange/', 'nodes/', 'nodegroups/', 'users/', 'xcat/', 'version']
class PluginRoute(object):
@@ -165,6 +165,10 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'licenses': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'net_interfaces': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
@@ -196,6 +200,24 @@ def _init_core():
}),
},
},
'storage': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'arrays': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'disks': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'volumes': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
})
},
'system': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
@@ -955,6 +977,16 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
"""
pathcomponents = path.split('/')
del pathcomponents[0] # discard the value from leading /
xcat_url = False
if pathcomponents and pathcomponents[0] == 'xcat':
xcat_url = True
if 'xcat/' in rootcollections:
rootcollections.remove('xcat/')
del pathcomponents[0]
elif 'xcat/' not in rootcollections:
rootcollections.insert(-1, 'xcat/')
if xcat_url:
configmanager.log("Start to deal with xCAT URL: %s" % path)
if pathcomponents[-1] == '':
del pathcomponents[-1]
if not pathcomponents: # root collection list
+30 -5
View File
@@ -211,6 +211,8 @@ def send_discovery_datum(info):
if infotype in servicenames:
types.append(servicenames[infotype])
yield msg.KeyValueData({'types': types})
if 'otheraddresses' in info:
yield msg.KeyValueData({'otheripaddrs': list(info['otheraddresses'])})
def _info_matches(info, criteria):
@@ -394,8 +396,15 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
'Unable to {0} to {1}'.format(operation,
'/'.join(pathcomponents)))
handler = info['handler'].NodeHandler(info, configmanager)
eval_node(configmanager, handler, info, inputdata['node'],
manual=True)
try:
eval_node(configmanager, handler, info, inputdata['node'],
manual=True)
except Exception as e:
# or... incorrect passworod provided..
if 'Incorrect password' in str(e) or 'Unauthorized name' in str(e):
return [msg.ConfluentTargetInvalidCredentials(
inputdata['node'])]
raise
return [msg.AssignedResource(inputdata['node'])]
elif operation == 'delete':
mac = _get_mac_from_query(pathcomponents)
@@ -463,6 +472,9 @@ def _recheck_nodes_backend(nodeattribs, configmanager):
_map_unique_ids(nodeattribs)
# for the nodes whose attributes have changed, consider them as potential
# strangers
if nodeattribs:
macmap.vintage = 0 # expire current mac map data, in case
# the attributes changed impacted the result
for node in nodeattribs:
if node in known_nodes:
for somemac in known_nodes[node]:
@@ -576,6 +588,11 @@ def detected(info):
break
else: # no nodehandler, ignore for now
return
if (handler and not handler.NodeHandler.adequate(info) and
info.get('protocol', None)):
eventlet.spawn_after(10, info['protocol'].fix_info, info,
safe_detected)
return
try:
snum = info['attributes']['enclosure-serial-number'][0].strip()
if snum:
@@ -622,6 +639,9 @@ def detected(info):
uuid = info.get('uuid', None)
if uuid_is_valid(uuid):
known_uuids[uuid][info['hwaddr']] = info
info['otheraddresses'] = set([])
for i4addr in info.get('attributes', {}).get('ipv4-address', []):
info['otheraddresses'].add(i4addr)
if handler and handler.https_supported and not handler.https_cert:
if handler.cert_fail_reason == 'unreachable':
log.log(
@@ -691,6 +711,10 @@ def get_enclosure_chain_head(nodename, cfg):
if ne in members:
raise exc.InvalidArgumentException(
'Circular chain that includes ' + nodename)
if not cfg.is_node(ne):
raise exc.InvalidArgumentException(
'{0} is chained to nonexistent node {1} '.format(
nodename, ne))
nodename = ne
members.append(nodename)
return nodename
@@ -1096,6 +1120,7 @@ needaddhandled = False
def _handle_nodelist_change(configmanager):
global needaddhandled
global nodeaddhandler
macmap.vintage = 0 # the current mac map is probably inaccurate
_recheck_nodes((), configmanager)
if needaddhandled:
needaddhandled = False
@@ -1151,7 +1176,7 @@ def rescan():
if scanner:
return
else:
scanner = eventlet.spawn(slp.active_scan, safe_detected)
scanner = eventlet.spawn(slp.active_scan, safe_detected, slp)
def start_detection():
@@ -1180,8 +1205,8 @@ def stop_autosense():
autosensors.discard(watcher)
def start_autosense():
autosensors.add(eventlet.spawn(slp.snoop, safe_detected))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected))
autosensors.add(eventlet.spawn(slp.snoop, safe_detected, slp))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected, pxe))
nodes_by_fprint = {}
@@ -40,6 +40,12 @@ class NodeHandler(object):
targsa = info['addresses'][0]
self.ipaddr = targsa[0]
@classmethod
def adequate(cls, info):
# Check if the referenced info is really enough, if false, a rescan
# may occur against the target in a short while
return True
def scan(self):
# Do completely passive things to enhance data.
# Probe is permitted to for example attempt a login
@@ -21,6 +21,12 @@ import struct
class NodeHandler(bmchandler.NodeHandler):
devname = 'IMM'
@classmethod
def adequate(cls, info):
# We can sometimes receive a partially initialized SLP packet
# This is not adequate for being satisfied
return bool(info['attributes'])
def scan(self):
slpattrs = self.info.get('attributes', {})
self.isdense = False
@@ -23,6 +23,12 @@ import pyghmi.ipmi.oem.lenovo.imm as imm
class NodeHandler(immhandler.NodeHandler):
devname = 'XCC'
@classmethod
def adequate(cls, info):
# We can sometimes receive a partially initialized SLP packet
# This is not adequate for being satisfied
return bool(info.get('attributes', {}))
def preconfig(self):
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
@@ -72,7 +72,7 @@ def find_info_in_options(rq, optidx):
return uuid, arch
return uuid, arch
def snoop(handler):
def snoop(handler, protocol=None):
#TODO(jjohnson2): ipv6 socket and multicast for DHCPv6, should that be
#prominent
#TODO(jjohnson2): IP_PKTINFO, recvmsg to get the destination ip, per
@@ -255,7 +255,7 @@ def _parse_attrlist(attrstr):
currattr = currattr.decode('utf-8')
attribs[currattr] = None
else:
attrname, attrval = currattr.split('=')
attrname, attrval = currattr.split('=', 1)
attrname = attrname.decode('utf-8')
attribs[attrname] = []
for val in attrval.split(','):
@@ -295,9 +295,11 @@ def _parse_attrlist(attrstr):
return attribs
def _parse_attrs(data, parsed):
def _parse_attrs(data, parsed, xid=None):
headinfo = _parse_slp_header(data)
if headinfo['function'] != 7 or headinfo['xid'] != parsed['xid']:
if xid is None:
xid = parsed['xid']
if headinfo['function'] != 7 or headinfo['xid'] != xid:
return
payload = headinfo['payload']
if struct.unpack('!H', bytes(payload[:2]))[0] != 0:
@@ -307,8 +309,18 @@ def _parse_attrs(data, parsed):
parsed['attributes'] = _parse_attrlist(attrstr)
def fix_info(info, handler):
if '_attempts' not in info:
info['_attempts'] = 10
if info['_attempts'] == 0:
return
info['_attempts'] -= 1
_add_attributes(info)
handler(info)
def _add_attributes(parsed):
attrq = _generate_attr_request(parsed['services'][0], parsed['xid'])
xid = parsed.get('xid', 42)
attrq = _generate_attr_request(parsed['services'][0], xid)
target = None
# prefer reaching out to an fe80 if present, to be highly robust
# in face of network changes
@@ -329,7 +341,7 @@ def _add_attributes(parsed):
net.sendall(attrq)
rsp = net.recv(8192)
net.close()
_parse_attrs(rsp, parsed)
_parse_attrs(rsp, parsed, xid)
def query_srvtypes(target):
@@ -383,7 +395,7 @@ def rescan(handler):
handler(scanned)
def snoop(handler):
def snoop(handler, protocol=None):
"""Watch for SLP activity
handler will be called with a dictionary of relevant attributes
@@ -392,7 +404,7 @@ def snoop(handler):
:return:
"""
tracelog = log.Logger('trace')
active_scan(handler)
active_scan(handler, protocol)
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
slpg = socket.inet_pton(socket.AF_INET6, 'ff01::123')
@@ -469,13 +481,14 @@ def snoop(handler):
peerbymacaddress[mac]['xid'] = 1
_add_attributes(peerbymacaddress[mac])
peerbymacaddress[mac]['hwaddr'] = mac
peerbymacaddress[mac]['protocol'] = protocol
handler(peerbymacaddress[mac])
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def active_scan(handler):
def active_scan(handler, protocol=None):
known_peers = set([])
for scanned in scan():
for addr in scanned['addresses']:
@@ -486,6 +499,7 @@ def active_scan(handler):
break
known_peers.add(addr)
else:
scanned['protocol'] = protocol
handler(scanned)
+16 -3
View File
@@ -30,6 +30,7 @@ import confluent.asynchttp
import confluent.shellserver as shellserver
import confluent.tlvdata
import confluent.util as util
from confluent.xcclient.xcat_manager import xCATConfigManager
import copy
import eventlet
import eventlet.greenthread
@@ -56,7 +57,7 @@ opmap = {
'PUT': 'update',
'DELETE': 'delete',
}
xcat_cfm = None
class RobustCookie(Cookie.SimpleCookie):
# this is very bad form, but BaseCookie has a terrible flaw
@@ -437,6 +438,11 @@ def resourcehandler_backend(env, start_response):
("Set-Cookie", m.OutputString())
for m in authorized['cookie'].values())
cfgmgr = authorized['cfgmgr']
if (env['PATH_INFO'].startswith('/xcat')):
global xcat_cfm
if xcat_cfm is None:
xcat_cfm = xCATConfigManager()
cfgmgr = xcat_cfm
if (operation == 'create') and env['PATH_INFO'] == '/sessions/current/async':
pagecontent = ""
try:
@@ -496,6 +502,8 @@ def resourcehandler_backend(env, start_response):
skipreplay = False
if 'skipreplay' in querydict and querydict['skipreplay']:
skipreplay = True
width = querydict.get('width', 80)
height = querydict.get('height', 24)
datacallback = None
async = None
if 'HTTP_CONFLUENTASYNCID' in env:
@@ -507,13 +515,13 @@ def resourcehandler_backend(env, start_response):
consession = shellserver.ShellSession(
node=nodename, configmanager=cfgmgr,
username=authorized['username'], skipreplay=skipreplay,
datacallback=datacallback
datacallback=datacallback, width=width, height=height
)
else:
consession = consoleserver.ConsoleSession(
node=nodename, configmanager=cfgmgr,
username=authorized['username'], skipreplay=skipreplay,
datacallback=datacallback
datacallback=datacallback, width=width, height=height
)
except exc.NotFoundException:
start_response("404 Not found", headers)
@@ -548,6 +556,9 @@ def resourcehandler_backend(env, start_response):
elif 'action' in querydict:
if querydict['action'] == 'break':
consolesessions[querydict['session']]['session'].send_break()
elif querydict['action'] == 'resize':
consolesessions[querydict['session']]['session'].resize(
width=querydict['width'], height=querydict['height'])
elif querydict['action'] == 'reopen':
consolesessions[querydict['session']]['session'].reopen()
else:
@@ -784,6 +795,8 @@ def serve(bind_host, bind_port):
'Failed to open HTTP due to busy port, trying again in'
' a second\n')
eventlet.sleep(1)
# TCP_FASTOPEN
sock.setsockopt(socket.SOL_TCP, 23, 5)
eventlet.wsgi.server(sock, resourcehandler, log=False, log_output=False,
debug=False, socket_timeout=60)
@@ -41,6 +41,11 @@ class Console(object):
the remote console"""
return
def resize(self, width, height):
"""This function is responsible for relaying resize request from
client terminal to remote console"""
return
def ping(self):
"""This function is a hint to the console plugin that now would be a
nice time to assess health of console connection. Plugins that see
+181 -10
View File
@@ -32,6 +32,11 @@ valid_health_values = set([
'unknown',
])
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
def _htmlify_structure(indict):
ret = "<ul>"
if isinstance(indict, dict):
@@ -419,29 +424,60 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
elif (path[:3] == ['configuration', 'system', 'clear'] and
operation != 'retrieve'):
return InputConfigClear(path, inputdata, nodes, configmanager)
elif (path[:3] == ['configuration', 'storage', 'disks'] and
operation != 'retrieve'):
return InputDisk(path, nodes, inputdata)
elif (path[:3] == ['configuration', 'storage', 'volumes'] and
operation in ('update', 'create')):
return InputVolumes(path, nodes, inputdata)
elif 'inventory/firmware/updates/active' in '/'.join(path) and inputdata:
return InputFirmwareUpdate(path, nodes, inputdata)
return InputFirmwareUpdate(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith('media/detach'):
return DetachMedia(path, nodes, inputdata)
elif '/'.join(path).startswith('media/') and inputdata:
return InputMedia(path, nodes, inputdata)
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith('support/servicedata') and inputdata:
return InputMedia(path, nodes, inputdata)
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith(
'configuration/management_controller/licenses') and inputdata:
return InputLicense(path, nodes, inputdata, configmanager)
elif inputdata:
raise exc.InvalidArgumentException(
'No known input handler for request')
class InputFirmwareUpdate(ConfluentMessage):
def __init__(self, path, nodes, inputdata):
self.filename = inputdata.get('filename', inputdata.get('url', None))
def __init__(self, path, nodes, inputdata, configmanager):
self._filename = inputdata.get('filename', inputdata.get('url', None))
self.bank = inputdata.get('bank', None)
self.nodes = nodes
self.filebynode = {}
self._complexname = False
for expanded in configmanager.expand_attrib_expression(
nodes, self._filename):
node, value = expanded
if value != self._filename:
self._complexname = True
self.filebynode[node] = value
@property
def filename(self):
if self._complexname:
raise Exception('User requested substitutions, but code is '
'written against old api, code must be fixed or '
'skip {} expansion')
return self._filename
def nodefile(self, node):
return self.filebynode[node]
class InputMedia(InputFirmwareUpdate):
# Use InputFirmwareUpdate
pass
class InputLicense(InputFirmwareUpdate):
pass
class DetachMedia(ConfluentMessage):
def __init__(self, path, nodes, inputdata):
@@ -523,7 +559,6 @@ class InputConfigChangeSet(InputExpression):
[node], attrs[attr]))[0][1]
return endattrs
class InputAttributes(ConfluentMessage):
# This is particularly designed for attributes, where a simple string
# should become either a string value or a dict with {'expression':} to
@@ -701,6 +736,70 @@ class InputIdentifyMessage(ConfluentInputMessage):
keyname = 'identify'
class InputDisk(ConfluentInputMessage):
valid_values = set([
'jbod',
'unconfigured',
'hotspare',
])
keyname = 'state'
class InputVolumes(ConfluentInputMessage):
def __init__(self, path, nodes, inputdata):
self.inputbynode = {}
self.stripped = False
if not inputdata:
raise exc.InvalidArgumentException('missing input data')
if isinstance(inputdata, dict):
volnames = [None]
if len(path) == 6:
volnames = path[-1]
volnames = inputdata.get('name', volnames)
if not isinstance(volnames, list):
volnames = volnames.split(',')
sizes = inputdata.get('size', [None])
if not isinstance(sizes, list):
sizes = sizes.split(',')
disks = inputdata.get('disks', [])
if not disks:
raise exc.InvalidArgumentException(
'disks are currently required to create a volume')
raidlvl = inputdata.get('raidlevel', None)
inputdata = []
for size in sizes:
if volnames:
currname = volnames.pop(0)
else:
currname = None
inputdata.append(
{'name': currname, 'size': size,
'disks': disks,
'raidlevel': raidlvl})
for node in nodes:
self.inputbynode[node] = []
for input in inputdata:
volname = None
if len(path) == 6:
volname = path[-1]
volname = input.get('name', volname)
if not volname:
volname = None
volsize = input.get('size', None)
if isinstance(input['disks'], list):
disks = input['disks']
else:
disks = input['disks'].split(',')
raidlvl = input.get('raidlevel', None)
for node in nodes:
self.inputbynode[node].append({'name': volname,
'size': volsize,
'disks': disks,
'raidlevel': raidlvl,
})
class InputPowerMessage(ConfluentInputMessage):
valid_values = set([
'on',
@@ -1241,6 +1340,74 @@ class KeyValueData(ConfluentMessage):
else:
self.kvpairs = {name: kvdata}
class Array(ConfluentMessage):
def __init__(self, name, disks=None, raid=None, volumes=None,
id=None, capacity=None, available=None):
self.kvpairs = {
name: {
'type': 'array',
'disks': disks,
'raid': raid,
'id': id,
'volumes': volumes,
'capacity': capacity,
'available': available,
}
}
class Volume(ConfluentMessage):
def __init__(self, name, volname, size, state, array):
self.kvpairs = {
name: {
'type': 'volume',
'name': simplify_name(volname),
'label': volname,
'size': size,
'state': state,
'array': array,
}
}
class Disk(ConfluentMessage):
valid_states = set([
'jbod',
'unconfigured',
'hotspare',
'online',
])
state_aliases = {
'unconfigured good': 'unconfigured',
'global hot spare': 'hotspare',
}
def _normalize_state(self, instate):
newstate = instate.lower()
if newstate in self.valid_states:
return newstate
elif newstate in self.state_aliases:
return self.state_aliases[newstate]
raise Exception("Unknown state {0}".format(instate))
def __init__(self, name, label=None, description=None,
diskid=None, state=None, serial=None, fru=None,
array=None):
state = self._normalize_state(state)
self.kvpairs = {
name: {
'type': 'disk',
'name': simplify_name(label),
'label': label,
'description': description,
'diskid': diskid,
'state': state,
'serial': serial,
'fru': fru,
'array': array,
}
}
class LEDStatus(ConfluentMessage):
readonly = True
@@ -1388,12 +1555,12 @@ class NTPServer(ConfluentMessage):
class License(ConfluentMessage):
readonly = True
def __init__(self, name=None, kvm=None):
def __init__(self, name=None, kvm=None, feature=None):
self.notnode = name is None
self.desc = 'License'
kv = []
kv.append({'kvm_availability': str(kvm)})
kv.append({'kvm_availability': str(kvm), 'feature': feature})
if self.notnode:
self.kvpairs = {'License': kv}
else:
@@ -1408,9 +1575,13 @@ class CryptedAttributes(Attributes):
self.desc = desc
nkv = {}
for key in kv.iterkeys():
nkv[key] = {'isset': False}
try:
if kv[key] is not None and kv[key]['cryptvalue'] != '':
if kv[key] is None:
continue
elif not isinstance(kv[key], dict):
self.defaulttype = 'text'
nkv[key] = kv[key]
elif kv[key] is not None and kv[key]['cryptvalue'] != '':
nkv[key] = {'isset': True}
nkv[key]['inheritedfrom'] = kv[key]['inheritedfrom']
except KeyError:
@@ -100,7 +100,7 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
def retrieve_nodes(nodes, element, configmanager, inputdata):
attributes = configmanager.get_node_attributes(nodes)
if element[-1] == 'all':
for node in nodes:
for node in util.natural_sort(nodes):
theattrs = set(allattributes.node).union(set(attributes[node]))
for attribute in sorted(theattrs):
if attribute in attributes[node]: # have a setting for it
@@ -125,9 +125,11 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
allattributes.node.get(
attribute, {}).get('description', ''))
elif element[-1] == 'current':
for node in sorted(list(attributes)):
for node in util.natural_sort(list(attributes)):
for attribute in sorted(attributes[node].iterkeys()):
currattr = attributes[node][attribute]
if currattr is None:
continue
try:
desc = allattributes.node[attribute]['description']
except KeyError:
@@ -140,6 +142,8 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
elif isinstance(currattr, list):
yield msg.ListAttributes(
node, {attribute: currattr}, desc)
elif isinstance(currattr, str):
yield msg.Attributes(node, {attribute: currattr}, desc)
else:
print attribute
print repr(currattr)
@@ -14,6 +14,7 @@
import confluent.core as core
import confluent.messages as msg
import pyghmi.exceptions as pygexc
import confluent.exceptions as exc
def update(nodes, element, configmanager, inputdata):
emebs = configmanager.get_node_attributes(
@@ -36,3 +37,5 @@ def update(nodes, element, configmanager, inputdata):
yield rsp
except pygexc.UnsupportedFunctionality as uf:
yield msg.ConfluentNodeError(node, str(uf))
except exc.TargetEndpointUnreachable as uf:
yield msg.ConfluentNodeError(node, str(uf))
@@ -1,5 +1,5 @@
# Copyright 2014 IBM Corporation
# Copyright 2015-2017 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.
@@ -20,6 +20,7 @@ import confluent.interface.console as conapi
import confluent.messages as msg
import confluent.util as util
import copy
import errno
import eventlet
import eventlet.event
import eventlet.green.threading as threading
@@ -27,8 +28,10 @@ import eventlet.greenpool as greenpool
import eventlet.queue as queue
import eventlet.support.greendns
from fnmatch import fnmatch
import os
import pyghmi.constants as pygconstants
import pyghmi.exceptions as pygexc
import pyghmi.storage as storage
console = eventlet.import_patched('pyghmi.ipmi.console')
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
import socket
@@ -129,7 +132,8 @@ def hex2bin(hexstring):
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-')
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
def sanitize_invdata(indata):
@@ -170,6 +174,17 @@ class IpmiCommandWrapper(ipmicommand.Command):
'hardwaremanagement.manager'), self._attribschanged)
super(self.__class__, self).__init__(**kwargs)
self.setup_confluent_keyhandler()
try:
os.makedirs('/var/cache/confluent/ipmi/')
except OSError as e:
if e.errno != errno.EEXIST or not os.path.isdir(
'/var/cache/confluent/ipmi/'):
raise
try:
self.set_sdr_cachedir('/var/cache/confluent/ipmi/')
except Exception:
pass
def setup_confluent_keyhandler(self):
self.register_key_handler(util.TLSCertVerifier(
@@ -340,28 +355,40 @@ class IpmiConsole(conapi.Console):
self.solconnection.send_break()
def perform_requests(operator, nodes, element, cfg, inputdata):
def perform_requests(operator, nodes, element, cfg, inputdata, realop):
cryptit = cfg.decrypt
cfg.decrypt = True
configdata = cfg.get_node_attributes(nodes, _configattributes)
cfg.decrypt = cryptit
resultdata = queue.LightQueue()
livingthreads = set([])
numnodes = len(nodes)
for node in nodes:
livingthreads.add(_ipmiworkers.spawn(
perform_request, operator, node, element, configdata, inputdata,
cfg, resultdata))
cfg, resultdata, realop))
while livingthreads:
try:
bundle = []
datum = resultdata.get(timeout=10)
while datum:
if datum != 'Done':
if isinstance(datum, Exception):
raise datum
yield datum
datum = resultdata.get_nowait()
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
len(datum.kvpairs) == 1):
bundle.append((datum.kvpairs.keys()[0], datum))
numnodes -= 1
else:
yield datum
timeout = 0.1 if numnodes else 0.001
datum = resultdata.get(timeout=timeout)
except queue.Empty:
pass
finally:
for datum in sorted(
bundle, key=lambda x: util.naturalize_string(x[0])):
yield datum[1]
for t in list(livingthreads):
if t.dead:
livingthreads.discard(t)
@@ -376,10 +403,10 @@ def perform_requests(operator, nodes, element, cfg, inputdata):
def perform_request(operator, node, element,
configdata, inputdata, cfg, results):
configdata, inputdata, cfg, results, realop):
try:
return IpmiHandler(operator, node, element, configdata, inputdata,
cfg, results).handle_request()
cfg, results, realop).handle_request()
except pygexc.IpmiException as ipmiexc:
excmsg = str(ipmiexc)
if excmsg in ('Session no longer connected', 'timeout'):
@@ -391,8 +418,8 @@ def perform_request(operator, node, element,
results.put(msg.ConfluentTargetTimeout(node, str(tu)))
except ssl.SSLEOFError:
results.put(msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
'the target BMC'))
node, 'Unable to communicate with the https server on '
'the target BMC'))
except exc.PubkeyInvalid:
results.put(msg.ConfluentNodeError(
node,
@@ -409,7 +436,8 @@ def perform_request(operator, node, element,
persistent_ipmicmds = {}
class IpmiHandler(object):
def __init__(self, operation, node, element, cfd, inputdata, cfg, output):
def __init__(self, operation, node, element, cfd, inputdata, cfg, output,
realop):
self.sensormap = {}
self.invmap = {}
self.output = output
@@ -423,6 +451,7 @@ class IpmiHandler(object):
self.node = node
self.element = element
self.op = operation
self.realop = realop
connparams = get_conn_params(node, self.cfg)
self.ipmicmd = None
self.inputdata = inputdata
@@ -500,6 +529,8 @@ class IpmiHandler(object):
self.identify()
elif self.element[0] == 'sensors':
self.handle_sensors()
elif self.element[:2] == ['configuration', 'storage']:
self.handle_storage()
elif self.element[0] == 'configuration':
self.handle_configuration()
elif self.element[:3] == ['inventory', 'firmware', 'updates']:
@@ -529,7 +560,7 @@ class IpmiHandler(object):
def handle_update(self):
u = firmwaremanager.Updater(self.node, self.ipmicmd.update_firmware,
self.inputdata.filename, self.tenant,
self.inputdata.nodefile(self.node), self.tenant,
bank=self.inputdata.bank)
self.output.put(
msg.CreatedResource(
@@ -538,7 +569,7 @@ class IpmiHandler(object):
def handle_media_upload(self):
u = firmwaremanager.Updater(self.node, self.ipmicmd.upload_media,
self.inputdata.filename, self.tenant,
self.inputdata.nodefile(self.node), self.tenant,
type='mediaupload')
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
@@ -546,14 +577,15 @@ class IpmiHandler(object):
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.ipmicmd.get_diagnostic_data,
self.inputdata.filename, self.tenant, type='ffdc',
self.inputdata.nodefile(self.node), 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)
self.ipmicmd.attach_remote_media(self.inputdata.nodefile(
self.node))
except pygexc.UnsupportedFunctionality as uf:
self.output.put(msg.ConfluentNodeError(self.node, str(uf)))
@@ -587,6 +619,8 @@ class IpmiHandler(object):
return self.handle_sysconfig(True)
elif self.element[1:3] == ['system', 'clear']:
return self.handle_sysconfigclear()
elif self.element[1:3] == ['management_controller', 'licenses']:
return self.handle_licenses()
raise Exception('Not implemented')
def decode_alert(self):
@@ -930,6 +964,113 @@ class IpmiHandler(object):
newinf['name'] = dstr
invitems.append(newinf)
def handle_storage(self):
if self.element[-1] == '':
self.element = self.element[:-1]
storelem = self.element[2:]
if 'read' == self.op:
return self._show_storage(storelem)
elif 'update' == self.realop:
return self._update_storage(storelem)
elif 'delete' == self.op:
return self._delete_storage(storelem)
elif 'create' == self.realop:
return self._create_storage(storelem)
def _delete_storage(self, storelem):
if len(storelem) < 2 or storelem[0] != 'volumes':
raise exc.InvalidArgumentException('Must target a specific volume')
volname = storelem[-1]
curr = self.ipmicmd.get_storage_configuration()
volumes = []
volsfound = False
toremove = storage.ConfigSpec(arrays=[storage.Array(volumes=volumes)])
for pool in curr.arrays:
for vol in pool.volumes:
if simplify_name(vol.name) == volname:
volsfound = True
volumes.append(vol)
if not volsfound:
self.output.put(msg.ConfluentTargetNotFound(
self.node, "No volume named '{0}' found".format(volname)))
return
self.ipmicmd.remove_storage_configuration(toremove)
self.output.put(msg.DeletedResource(volname))
def _create_storage(self, storelem):
if 'volumes' not in storelem:
raise exc.InvalidArgumentException('Can only create volumes')
vols = []
thedisks = None
currcfg = self.ipmicmd.get_storage_configuration()
currnames = []
for arr in currcfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for vol in arr.volumes:
currnames.append(vol.name)
disks = []
vols = []
vol = self.inputdata.inputbynode[self.node][0]
raidlvl = vol['raidlevel']
for disk in currcfg.disks:
if simplify_name(disk.name) in vol['disks']:
disks.append(disk)
elif (disk.status == 'Unconfigured Good' and
vol['disks'][0] in ('remainder', 'rest')):
disks.append(disk)
elif vol['disks'][0] == 'all':
disks.append(disk)
for vol in self.inputdata.inputbynode[self.node]:
if thedisks and thedisks != vol['disks']:
raise exc.InvalidArgumentException(
'Not currently supported to create multiple arrays '
'in a single request')
if raidlvl and vol['raidlevel'] != raidlvl:
raise exc.InvalidArgumentException('Cannot mix raid levels in '
'a single array')
vols.append(storage.Volume(name=vol['name'], size=vol['size']))
newcfg = storage.ConfigSpec(
arrays=(storage.Array(raid=raidlvl, disks=disks, volumes=vols),))
self.ipmicmd.apply_storage_configuration(newcfg)
for vol in self.inputdata.inputbynode[self.node]:
if vol['name'] is None:
newcfg = self.ipmicmd.get_storage_configuration()
for arr in newcfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for vol in arr.volumes:
if vol.name not in currnames:
self.output.put(
msg.Volume(self.node, vol.name, vol.size,
vol.status, arrname))
return
else:
self._show_storage(storelem[:1] + [vol['name']])
def _update_storage(self, storelem):
if storelem[0] == 'disks':
if len(storelem) == 1:
raise exc.InvalidArgumentException('Must target a disk')
self.set_disk(storelem[-1],
self.inputdata.inputbynode[self.node])
self._show_storage(storelem)
def _show_storage(self, storelem):
if storelem[0] == 'disks':
if len(storelem) == 1:
return self.list_disks()
return self.show_disk(storelem[1])
elif storelem[0] == 'arrays':
if len(storelem) == 1:
return self.list_arrays()
return self.show_array(storelem[1])
elif storelem[0] == 'volumes':
if len(storelem) == 1:
return self.list_volumes()
return self.show_volume(storelem[1])
elif storelem[0] == 'all':
return self._show_all_storage()
def handle_sensors(self):
if self.element[-1] == '':
self.element = self.element[:-1]
@@ -953,6 +1094,100 @@ class IpmiHandler(object):
return True
return False
def set_disk(self, name, state):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
if (name == 'all' or simplify_name(disk.name) == name or
disk == name):
disk.status = state
self.ipmicmd.apply_storage_configuration(
storage.ConfigSpec(disks=scfg.disks))
def _show_all_storage(self):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru))
for arr in scfg.arrays:
for disk in arr.disks:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, array='{0}-{1}'.format(*arr.id)))
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
self._detail_array(arr, arrname, True)
def show_disk(self, name):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
if simplify_name(disk.name) == name or disk == name:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru))
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for disk in arr.disks:
if (name == 'all' or simplify_name(disk.name) == name or
disk == name):
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, arrname))
def list_disks(self):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
for arr in scfg.arrays:
for disk in arr.disks:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
def list_arrays(self):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
self.output.put(msg.ChildCollection('{0}-{1}'.format(*arr.id)))
def show_array(self, name):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
if arrname == name:
self._detail_array(arr, arrname)
def _detail_array(self, arr, arrname, detailvol=False):
vols = []
for vol in arr.volumes:
vols.append(simplify_name(vol.name))
disks = []
for disk in arr.disks:
disks.append(simplify_name(disk.name))
self.output.put(msg.Array(self.node, disks, arr.raid,
vols, arrname, arr.capacity,
arr.available_capacity))
if detailvol:
for vol in arr.volumes:
self.output.put(msg.Volume(self.node, vol.name, vol.size,
vol.status, arrname))
def show_volume(self, name):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for vol in arr.volumes:
if name == simplify_name(vol.name):
self.output.put(msg.Volume(self.node, vol.name, vol.size,
vol.status, arrname))
def list_volumes(self):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
for vol in arr.volumes:
self.output.put(msg.ChildCollection(simplify_name(vol.name)))
def list_sensors(self):
try:
sensors = self.ipmicmd.get_sensor_descriptions()
@@ -1165,6 +1400,27 @@ class IpmiHandler(object):
self.output.put(msg.License(self.node, available))
return
def handle_licenses(self):
if self.element[-1] == '':
self.element = self.element[:-1]
if self.op in ('create', 'update'):
self.ipmicmd.apply_license(self.inputdata.nodefile(self.node))
if len(self.element) == 3:
self.output.put(msg.ChildCollection('all'))
i = 1
for lic in self.ipmicmd.get_licenses():
self.output.put(msg.ChildCollection(str(i)))
i += 1
return
licname = self.element[3]
if licname == 'all':
for lic in self.ipmicmd.get_licenses():
self.output.put(msg.License(self.node, feature=lic['name']))
else:
index = int(licname)
lic = list(self.ipmicmd.get_licenses())[index - 1]
self.output.put(msg.License(self.node, feature=lic['name']))
def handle_description(self):
dsc = self.ipmicmd.get_description()
self.output.put(msg.KeyValueData(dsc, self.node))
@@ -1190,7 +1446,7 @@ def initthread():
_ipmithread = eventlet.spawn(_ipmi_evtloop)
def create(nodes, element, configmanager, inputdata):
def create(nodes, element, configmanager, inputdata, realop='create'):
initthread()
if element == ['_console', 'session']:
if len(nodes) > 1:
@@ -1198,12 +1454,12 @@ def create(nodes, element, configmanager, inputdata):
return IpmiConsole(nodes[0], configmanager)
else:
return perform_requests(
'update', nodes, element, configmanager, inputdata)
'update', nodes, element, configmanager, inputdata, realop)
def update(nodes, element, configmanager, inputdata):
initthread()
return create(nodes, element, configmanager, inputdata)
return create(nodes, element, configmanager, inputdata, 'update')
def retrieve(nodes, element, configmanager, inputdata):
@@ -1218,7 +1474,8 @@ def retrieve(nodes, element, configmanager, inputdata):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element, 'ffdc')
else:
return perform_requests('read', nodes, element, configmanager, inputdata)
return perform_requests('read', nodes, element, configmanager,
inputdata, 'read')
def delete(nodes, element, configmanager, inputdata):
initthread()
@@ -1232,4 +1489,5 @@ def delete(nodes, element, configmanager, inputdata):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element, type='ffdc')
return perform_requests(
'delete', nodes, element, configmanager, inputdata)
'delete', nodes, element, configmanager, inputdata, 'delete')
@@ -80,8 +80,18 @@ class SshShell(conapi.Console):
self.nodeconfig = config
self.username = username
self.password = password
self.connected = False
self.width = 80
self.height = 24
self.inputmode = 0 # 0 = username, 1 = password...
def resize(self, width, height):
self.width = width
self.height = height
if not self.connected:
return
self.shell.resize_pty(width=width, height=height)
def recvdata(self):
while self.connected:
pendingdata = self.shell.recv(8192)
@@ -146,7 +156,8 @@ class SshShell(conapi.Console):
return
self.inputmode = 2
self.connected = True
self.shell = self.ssh.invoke_shell()
self.shell = self.ssh.invoke_shell(width=self.width,
height=self.height)
self.rxthread = eventlet.spawn(self.recvdata)
def write(self, data):
+5 -4
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016 Lenovo
# Copyright 2016-2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -87,12 +87,13 @@ class ShellSession(consoleserver.ConsoleSession):
"""
def __init__(self, node, configmanager, username, datacallback=None,
skipreplay=False, sessionid=None):
skipreplay=False, sessionid=None, width=80, height=24):
self.sessionid = sessionid
self.configmanager = configmanager
self.node = node
super(ShellSession, self).__init__(node, configmanager, username,
datacallback, skipreplay)
datacallback, skipreplay,
width=width, height=height)
def connect_session(self):
global activesessions
@@ -105,7 +106,7 @@ class ShellSession(consoleserver.ConsoleSession):
self.sessionid += 1
self.sessionid = str(self.sessionid)
if self.sessionid not in activesessions[(tenant, self.node, self.username)]:
activesessions[(tenant, self.node, self.username)][self.sessionid] = _ShellHandler(self.node, self.configmanager)
activesessions[(tenant, self.node, self.username)][self.sessionid] = _ShellHandler(self.node, self.configmanager, width=self.width, height=self.height)
self.conshdl = activesessions[(self.configmanager.tenant, self.node, self.username)][self.sessionid]
def destroy(self):
+18 -4
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# 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.
@@ -46,7 +46,9 @@ import confluent.core as pluginapi
import confluent.shellserver as shellserver
import confluent.collective.manager as collective
import confluent.util as util
from confluent.xcclient.xcat_manager import xCATConfigManager
xcat_cfm = None
tracelog = None
auditlog = None
try:
@@ -115,6 +117,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
if authdata is not None:
cfm = authdata[1]
authenticated = True
if xcat_cfm is None:
xcat_cfm = xCATConfigManager()
send_data(connection, "Confluent -- v0 --")
while not authenticated: # prompt for name and passphrase
send_data(connection, {'authpassed': 0})
@@ -143,7 +147,7 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
cfm = authdata[1]
send_data(connection, {'authpassed': 1})
request = tlvdata.recv(connection)
if 'collective' in request and skipauth:
if request and 'collective' in request and skipauth:
if not libssl:
tlvdata.send(
connection,
@@ -156,6 +160,9 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
return collective.handle_connection(connection, None, request['collective'],
local=True)
while request is not None:
if request['path'].startswith('/xcat'):
global xcat_cfm
cfm = xcat_cfm
try:
process_request(
connection, request, cfm, authdata, authname, skipauth)
@@ -239,7 +246,8 @@ def start_proxy_term(connection, cert, request):
consession = consoleserver.ConsoleSession(
node=request['node'], configmanager=cfm, username=request['user'],
datacallback=ccons.sendall, skipreplay=request['skipreplay'],
direct=False)
direct=False, width=request.get('width', 80), height=request.get(
'height', 24))
term_interact(None, None, ccons, None, connection, consession, None)
def start_term(authname, cfm, connection, params, path, authdata, skipauth):
@@ -263,7 +271,8 @@ def start_term(authname, cfm, connection, params, path, authdata, skipauth):
consession = shellserver.ShellSession(
node=node, configmanager=cfm, username=authname,
datacallback=ccons.sendall, skipreplay=skipreplay,
sessionid=sessionid)
sessionid=sessionid, width=params.get('width', 80),
height=params.get('height', 24))
else:
raise exc.InvalidArgumentException('Invalid path {0}'.format(path))
if consession is None:
@@ -291,6 +300,9 @@ def term_interact(authdata, authname, ccons, cfm, connection, consession,
elif data['operation'] == 'reopen':
consession.reopen()
continue
elif data['operation'] == 'resize':
consession.resize(width=data['width'], height=data['height'])
continue
else:
try:
process_request(connection, data, cfm, authdata, authname,
@@ -324,6 +336,8 @@ def _tlshandler(bind_host, bind_port):
raise
sys.stderr.write('TLS Socket in use, retrying in 1 second\n')
eventlet.sleep(1)
# Enable TCP_FASTOPEN
plainsocket.setsockopt(socket.SOL_TCP, 23, 5)
plainsocket.listen(5)
while (1): # TODO: exithook
cnn, addr = plainsocket.accept()
@@ -0,0 +1,93 @@
###############################################################################
# IBM(c) 2019 EPL license http://www.eclipse.org/legal/epl-v10.html
###############################################################################
# -*- encoding: utf-8 -*-
# 'node', which can be considered a 'system' or a 'vm'
nodeattrs = {
'groups': 'nodelist.groups',
'collective.manager': None,
'discovery.passwordrules': None,
'discovery.policy': ['switch', 'mtms', 'sequential'],
'info.note': 'nodelist.comments',
'location.room': 'nodepos.room',
'location.row': 'nodepos.comments',
'location.rack': 'nodepos.rack',
'location.u': 'nodepos.u',
'console.logging': 'site.consoleondemand',
'console.method': 'nodehm.cons',
'virtualization.host': 'vm.host',
'hardwaremanagement.manager': 'ipmi.bmc',
'hardwaremanagement.method': 'nodehm.mgt',
'enclosure.bay': None,
'enclosure.extends': None,
'enclosure.manager': None,
'id.model': 'vpd.mtm',
'id.serial': 'vpd.serial',
'id.uuid': 'vpd.uuid',
'net.bootable': None,
'net.ipv4_gateway': None,
'net.hwaddr': 'mac.mac',
'net.switch': 'switch.switch',
'net.switchport': 'switch.port',
'secret.snmpcommunity': 'site.snmpc',
'secret.ipmikg': None,
'secret.hardwaremanagementuser': 'ipmi.username',
'secret.hardwaremanagementpassword': 'ipmi.password',
'pubkeys.addpolicy': None,
'pubkeys.ssh': None,
}
def conattr2xcatattr(attrs=()):
if '*' in attrs or attrs == ():
return nodeattrs
attr_dict = {}
if not isinstance(attrs, list):
attr_dict[attrs] = nodeattrs.get(attrs, None)
else:
for attr in attrs:
attr_dict[attr] = nodeattrs.get(attr, None)
return attr_dict
def conattr2xcatattrv1(attrs):
if '*' in attrs:
attrs = nodeattrs.keys()
all_attr_set=set(nodeattrs.keys())
check_set=set(attrs)
unknown_attrs = check_set - all_attr_set
known_attrs = check_set & all_attr_set
if len(unknown_attrs) > 0:
return "The attrs " + str(unknown_attrs) + " are unknown"
tab_cols={}
error_attrs=[]
for attr in known_attrs:
xcatattr = nodeattrs[attr]
if xcatattr is None:
print(attr + " value is None?")
error_attrs.append(attr)
elif isinstance(xcatattr, list):
for k in xcatattr:
t,p,c = k.rpartition('.')
if t in tab_cols:
tab_cols[t].append(c)
else:
tab_cols[t] = [c]
else:
t,p,c = xcatattr.rpartition('.')
if t in tab_cols:
tab_cols[t].append(c)
else:
tab_cols[t] = [c]
if len(error_attrs):
return "The attrs " + str(error_attrs) + " are not supported yet"
return tab_cols
if __name__ == '__main__':
#print(conattr2xcatattrv2(['hardwaremanagement.manager','collective.manager', 'secret.hardwaremanagementuser']))
#print(conattr2xcatattr(['hardwaremanagement.manager','collective.manager', 'secret.hardwaremanagementuser']))
#print(conattr2xcatattr())
#mydict = conattr2xcatattr(['hardwaremanagement.manager','collective.manager', 'secret.hardwaremanagementuser'])
mydict = conattr2xcatattr('*')
print(get_node_attr(mydict))
+350
View File
@@ -0,0 +1,350 @@
#!/usr/bin/env python
###############################################################################
# IBM(c) 2019 EPL license http://www.eclipse.org/legal/epl-v10.html
###############################################################################
# -*- coding: utf-8 -*-
#
from __future__ import print_function
import sys
import dbobject
from dbobject import *
from dbsession import DBsession
from sqlalchemy import or_
#from xcclient.shell import CommandException
from exceptions import *
import utils
def create_or_update(session,tabcls,key,newdict,ismatrixtable=True):
tabkeys=tabcls.primkeys()
#for matrix table, remove the record if all the non-key values are None or blank
delrow=1
#for flat table, keep the record untouch if the non-key values are None
skiprow=1
objkeys=tabcls.getobjkey()
if type(key) not in (list,tuple):
key=[key]
keyvals=zip(objkeys,key)
for (keyname,keyval) in keyvals:
newdict[keyname]=keyval
tabcols=tabcls.getcolumns()
for item in newdict.keys():
if item not in tabcols:
if newdict[item] is None:
del newdict[item]
continue
else:
raise BadSchemaException("Error: no column '"+item+"' in table "+tabcls.__tablename__+", might caused by mismatch between schema version and xCAT version!")
if item not in objkeys and newdict[item]:
skiprow=0
delrow=0
if item not in objkeys and newdict[item] is None:
newdict[item]=''
#delete table rows when (1)the object keys are None or blank (2)the key is not specified in newdict and all non-key values are blank
if item not in objkeys and newdict[item]!='':
delrow=0
if item == 'disable' and newdict[item]=='':
newdict[item]=None
if not ismatrixtable:
if skiprow:
return
#do not remove for flat table
delrow=0
#if tabcls.__tablename__=='switch':
# import pdb
# pdb.set_trace()
try:
query=session.query(tabcls)
for tabkey in tabkeys:
query=query.filter(getattr(tabcls,tabkey) == newdict[tabkey])
record=query.all()
except Exception, e:
raise DBException("Error: query xCAT table "+tabcls.__tablename__+" failed: "+str(e))
if record:
if delrow:
try:
for item in record:
session.delete(item)
except Exception, e:
raise DBException("Error: delete "+key+" is failed: "+str(e))
#else:
# print("delete row in xCAT table "+tabcls.__tablename__+".")
else:
try:
#query=session.query(tabcls)
#for tabkey in tabkeys:
# query=query.filter(getattr(tabcls,tabkey) == newdict[tabkey])
query.update(newdict)
except Exception, e:
raise DBException("Error: import object "+key+" is failed: "+str(e))
elif delrow == 0:
try:
session.execute(tabcls.__table__.insert(), newdict)
except Exception, e:
raise DBException("Error: import object "+key+" is failed: "+str(e))
class matrixdbfactory():
def __init__(self,dbsession):
self._dbsession=dbsession
def gettab(self,tabs,keys=[]):
ret={}
for tabname in tabs:
dbsession=self._dbsession.loadSession(tabname);
if hasattr(dbobject,tabname):
tab=getattr(dbobject,tabname)
else:
continue
tabobjs=[]
tabkeys=tab.primkeys()
if not keys or len(keys)==0:
tabobjs=dbsession.query(tab).filter(or_(tab.disable == None, tab.disable.notin_(['1','yes']))).all()
elif len(tabkeys)==1:
tabobjs = dbsession.query(tab).filter(getattr(tab, tabkeys[0]).in_(keys),
or_(tab.disable == None, tab.disable.notin_(['1', 'yes']))).all()
elif len(tabkeys)>1:
for key in keys:
if type(key)!=tuple:
key=[key]
kvdict=dict(zip(tabkeys,key))
query=dbsession.query(tab)
for key,value in kvdict.items():
query=query.filter(getattr(tab,key).in_([value]))
query=query.filter(or_(tab.disable == None, tab.disable.notin_(['1','yes'])))
tabobj=query.all()
tabobjs.extend(tabobj)
if not tabobjs:
continue
objkeyname=tab.getobjkey()
dictoftab={}
for myobj in tabobjs:
mydict=myobj.getdict()
if len(objkeyname)==1:
mykey=mydict[tab.__tablename__+'.'+objkeyname[0]]
elif len(objkeyname)>1:
mykeylist=[]
for key in objkeyname:
mykeylist.append(mydict[tab.__tablename__+'.'+key])
mykey=tuple(mykeylist)
if mykey not in dictoftab.keys():
dictoftab[mykey]=mydict
else:
if type(dictoftab[mykey])!=list:
dictoftab[mykey]=[dictoftab[mykey]]
dictoftab[mykey].append(mydict)
for mykey in dictoftab.keys():
if mykey not in ret.keys():
ret[mykey]={}
if type(dictoftab[mykey])==list:
if tabname not in ret[mykey].keys():
ret[mykey][tabname]=[]
ret[mykey][tabname].extend(dictoftab[mykey])
else:
ret[mykey].update(dictoftab[mykey])
for mykey in ret.keys():
if len(ret[mykey].keys())==1 and ret[mykey].keys()[0] in tabs :
ret[mykey]=ret[mykey][ret[mykey].keys()[0]]
return ret
def settab(self,tabdict=None):
#print("=========matrixdbfactory:settab========")
#print(tabdict)
#print("\n")
if tabdict is None:
return None
for key in tabdict.keys():
utils.verbose(" writting object: "+str(key),file=sys.stdout)
#clear any existing table entries before adding new entries
df=dbfactory(self._dbsession)
df.cleartab(tabdict[key].keys(),[key])
for tab in tabdict[key].keys():
dbsession=self._dbsession.loadSession(tab);
if hasattr(dbobject,tab):
tabcls=getattr(dbobject,tab)
else:
continue
for record in tabdict[key][tab]:
if tabcls.isValid(key,record):
create_or_update(dbsession,tabcls,key,record)
class flatdbfactory() :
def __init__(self,dbsession):
self._dbsession=dbsession
def gettab(self,tabs,keys=None):
ret={}
if keys:
rootkey=keys[0]
else:
rootkey='clustersite'
ret[rootkey]={}
for tabname in tabs:
dbsession=self._dbsession.loadSession(tabname);
if hasattr(dbobject,tabname):
tab=getattr(dbobject,tabname)
else:
continue
tabobj=dbsession.query(tab).filter(or_(tab.disable == None,tab.disable.notin_(['1','yes']))).all()
if not tabobj:
continue
for myobj in tabobj:
mydict=myobj.getdict()
ret[rootkey].update(mydict)
return ret
def settab(self,tabdict=None):
#print("======flatdbfactory:settab======")
#print(tabdict)
if tabdict is None:
return None
for key in tabdict.keys():
for tab in tabdict[key].keys():
if hasattr(dbobject,tab):
tabcls=getattr(dbobject,tab)
else:
continue
tabkey=tabcls.getobjkey()[0]
rowentlist=tabcls.dict2tabentry(tabdict[key][tab][0])
dbsession=self._dbsession.loadSession(tab)
for rowent in rowentlist:
if tabcls.isValid(key, rowent):
create_or_update(dbsession,tabcls,rowent[tabkey],rowent,False)
class dbfactory():
def __init__(self,dbsession):
self._dbsession=dbsession
def gettab(self,tabs,keys=None):
flattabs=[]
matrixtabs=[]
mydict={}
for tab in tabs:
if hasattr(dbobject,tab):
tabcls=getattr(dbobject,tab)
else:
continue
if tabcls.getTabtype() == 'flat':
flattabs.append(tab)
else:
matrixtabs.append(tab)
if flattabs:
df_flat=flatdbfactory(self._dbsession)
mydict.update(df_flat.gettab(flattabs,keys))
if matrixtabs:
df_matrix=matrixdbfactory(self._dbsession)
mydict.update(df_matrix.gettab(matrixtabs,keys))
return mydict
#convert db dict from format {key:{tab.col=value}} to {key:{tab:{col}}}
def __tabtransform(self,dbdict):
#print("__tabtransform")
#print(dbdict)
flattabdict={}
matrixtabdict={}
#try:
for key in dbdict.keys():
dbentlist=[]
rawdbents=dbdict[key]
if type(rawdbents)==dict:
dbentlist.append(rawdbents)
else:
dbentlist.extend(rawdbents)
for dbent in dbentlist:
#print("@@@@@@@@@@@@")
#print(dbent)
#print("@@@@@@@@@@@@")
rowdict={}
for tabcol in dbent.keys():
(tab,col)=tabcol.split('.')
if tab not in rowdict.keys():
rowdict[tab]={}
if col not in rowdict[tab].keys():
rowdict[tab][col]={}
rowdict[tab][col]=dbent[tabcol]
for tab in rowdict.keys():
if hasattr(dbobject,tab):
tabcls=getattr(dbobject,tab)
else:
continue
if tabcls.getTabtype() == 'flat':
if key not in flattabdict.keys():
flattabdict[key]={}
if tab not in flattabdict[key].keys():
flattabdict[key][tab]=[]
flattabdict[key][tab].append(rowdict[tab])
else:
if key not in matrixtabdict.keys():
matrixtabdict[key]={}
if tab not in matrixtabdict[key].keys():
matrixtabdict[key][tab]=[]
matrixtabdict[key][tab].append(rowdict[tab])
return(matrixtabdict,flattabdict)
def settab(self,dbdict=None):
if dbdict is None:
return None
(matrixtabdict,flattabdict)=self.__tabtransform(dbdict)
if flattabdict:
df_flat=flatdbfactory(self._dbsession)
mydict=df_flat.settab(flattabdict)
if matrixtabdict:
df_matrix=matrixdbfactory(self._dbsession)
mydict=df_matrix.settab(matrixtabdict)
#except Exception as e:
# raise ("Error: import object failed.")
#else:
# print("import object successfully.")
def cleartab(self,tabs,objkey=[]):
for tab in tabs:
if hasattr(dbobject,tab):
tabcls=getattr(dbobject,tab)
else:
continue
tabkey=tabcls.getobjkey()[0]
ReservedKeys=tabcls.getReservedKeys()
dbsession=self._dbsession.loadSession(tab)
try:
query=dbsession.query(tabcls)
query=query.filter(or_(tabcls.disable == None, tabcls.disable.notin_(['1','yes'])))
if ReservedKeys:
query=query.filter(getattr(tabcls,tabkey).notin_(ReservedKeys))
if objkey:
query=query.filter(getattr(tabcls,tabkey).in_(objkey))
query.delete(synchronize_session='fetch')
except Exception as e:
raise DBException("Error: failed to clear table "+str(tab)+": "+str(e))
#else:
# print("table "+tab+ "cleared!")
if __name__ == "__main__":
pass
+459
View File
@@ -0,0 +1,459 @@
#!/usr/bin/env python
###############################################################################
# IBM(c) 2019 EPL license http://www.eclipse.org/legal/epl-v10.html
###############################################################################
# -*- coding: utf-8 -*-
#
from dbsession import *
from copy import *
from sqlalchemy import inspect
import pdb
class mixin(object):
def getdict(self):
mydict={}
for mykey in self.__dict__.keys():
if mykey in self.__table__.columns:
mydict[self.__tablename__+'.'+mykey.encode()]= self.__dict__[mykey] if self.__dict__[mykey] is None else self.__dict__[mykey].encode()
try:
self.__class__.outprocess(mydict)
except:
pass
return mydict
#return a tuple of table primary keys
@classmethod
def primkeys(cls):
ins = inspect(cls)
prikeys=[ item.key for item in ins.primary_key ]
prikeys.sort(None,None,reverse=False)
return tuple(prikeys)
#return the key of object in table row
@classmethod
def getobjkey(cls):
return cls.primkeys()
@classmethod
def isValid(cls, netname, tabdict):
return True
@classmethod
def dict2tabentry(self,objdict):
pass
@classmethod
def getcolumns(self):
return self.__table__.columns.keys()
@classmethod
def getTabtype(self):
return 'matrix'
@classmethod
def getReservedKeys(self):
return []
########################################################################
class passwd(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('passwd')
__tablename__ = 'passwd'
__table_args__ = {'autoload':True}
@classmethod
def primkeys(cls):
return ('key','username')
@classmethod
def getobjkey(cls):
return tuple(['key'])
########################################################################
class networks(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('networks')
__tablename__ = 'networks'
__table_args__ = {'autoload':True}
@classmethod
def primkeys(cls):
return tuple(['netname'])
@classmethod
def getobjkey(cls):
return tuple(['netname'])
@classmethod
def isValid(cls, netname, tabdict):
eptkey=0
if 'net' not in tabdict.keys() or not tabdict['net']:
print("Error: net value should not be empty for xCAT network object "+netname)
eptkey=1
if 'mask' not in tabdict.keys() or not tabdict['mask']:
print ("Error: mask value should not be empty for xCAT network object "+netname)
eptkey=1
if eptkey:
return False
else:
return True
########################################################################
class routes(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('routes')
__tablename__ = 'routes'
__table_args__ = {'autoload':True}
########################################################################
class nodetype(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nodetype')
__tablename__ = 'nodetype'
__table_args__ = {'autoload':True}
########################################################################
'''
class hosts(Base,mixin):
""""""
__tablename__ = 'hosts'
__table_args__ = {'autoload':True}
'''
########################################################################
class noderes(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('noderes')
__tablename__ = 'noderes'
__table_args__ = {'autoload':True}
########################################################################
class switch(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('switch')
__tablename__ = 'switch'
__table_args__ = {'autoload':True}
@classmethod
def getobjkey(cls):
return tuple(['node'])
########################################################################
class switches(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('switches')
__tablename__ = 'switches'
__table_args__ = {'autoload':True}
########################################################################
class mac(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('mac')
__tablename__ = 'mac'
__table_args__ = {'autoload':True}
########################################################################
class hwinv(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('hwinv')
__tablename__ = 'hwinv'
__table_args__ = {'autoload':True}
########################################################################
class postscripts(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('postscripts')
__tablename__ = 'postscripts'
__table_args__ = {'autoload':True}
@classmethod
def getReservedKeys(self):
return ('xcatdefaults','service')
########################################################################
class bootparams(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('bootparams')
__tablename__ = 'bootparams'
__table_args__ = {'autoload':True}
########################################################################
class nodelist(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nodelist')
__tablename__ = 'nodelist'
__table_args__ = {'autoload':True}
########################################################################
class vm(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('vm')
__tablename__ = 'vm'
__table_args__ = {'autoload':True}
########################################################################
class policy(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('policy')
__tablename__ = 'policy'
__table_args__ = {'autoload':True}
########################################################################
class nodehm(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nodehm')
__tablename__ = 'nodehm'
__table_args__ = {'autoload':True}
########################################################################
class nodegroup(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nodegroup')
__tablename__ = 'nodegroup'
__table_args__ = {'autoload':True}
########################################################################
class vpd(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('vpd')
__tablename__ = 'vpd'
__table_args__ = {'autoload':True}
########################################################################
class servicenode(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('servicenode')
__tablename__ = 'servicenode'
__table_args__ = {'autoload':True}
########################################################################
class hosts(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('hosts')
__tablename__ = 'hosts'
__table_args__ = {'autoload':True}
########################################################################
class nics(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nics')
__tablename__ = 'nics'
__table_args__ = {'autoload':True}
########################################################################
class openbmc(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('openbmc')
__tablename__ = 'openbmc'
__table_args__ = {'autoload':True}
########################################################################
class prodkey(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('prodkey')
__tablename__ = 'prodkey'
__table_args__ = {'autoload':True}
@classmethod
def getobjkey(cls):
return tuple(['node'])
########################################################################
class domain(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('domain')
__tablename__ = 'domain'
__table_args__ = {'autoload':True}
########################################################################
class chain(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('chain')
__tablename__ = 'chain'
__table_args__ = {'autoload':True}
########################################################################
class rack(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('rack')
__tablename__ = 'rack'
__table_args__ = {'autoload':True}
########################################################################
class nodepos(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nodepos')
__tablename__ = 'nodepos'
__table_args__ = {'autoload':True}
########################################################################
class ppc(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('ppc')
__tablename__ = 'ppc'
__table_args__ = {'autoload':True}
########################################################################
class ppchcp(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('ppchcp')
__tablename__ = 'ppchcp'
__table_args__ = {'autoload':True}
########################################################################
class mp(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('mp')
__tablename__ = 'mp'
__table_args__ = {'autoload':True}
########################################################################
class zvm(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('zvm')
__tablename__ = 'zvm'
__table_args__ = {'autoload':True}
########################################################################
class mpa(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('mpa')
__tablename__ = 'mpa'
__table_args__ = {'autoload':True}
########################################################################
class pdu(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('pdu')
__tablename__ = 'pdu'
__table_args__ = {'autoload':True}
########################################################################
class pduoutlet(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('pduoutlet')
__tablename__ = 'pduoutlet'
__table_args__ = {'autoload':True}
########################################################################
class cfgmgt(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('cfgmgt')
__tablename__ = 'cfgmgt'
__table_args__ = {'autoload':True}
########################################################################
class hypervisor(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('hypervisor')
__tablename__ = 'hypervisor'
__table_args__ = {'autoload':True}
########################################################################
class iscsi(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('iscsi')
__tablename__ = 'iscsi'
__table_args__ = {'autoload':True}
########################################################################
class mic(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('mic')
__tablename__ = 'mic'
__table_args__ = {'autoload':True}
########################################################################
class ppcdirect(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('ppcdirect')
__tablename__ = 'ppcdirect'
__table_args__ = {'autoload':True}
@classmethod
def primkeys(cls):
return tuple(['hcp'])
########################################################################
class storage(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('storage')
__tablename__ = 'storage'
__table_args__ = {'autoload':True}
########################################################################
class websrv(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('websrv')
__tablename__ = 'websrv'
__table_args__ = {'autoload':True}
########################################################################
class prescripts(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('prescripts')
__tablename__ = 'prescripts'
__table_args__ = {'autoload':True}
########################################################################
class ipmi(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('ipmi')
__tablename__ = 'ipmi'
__table_args__ = {'autoload':True}
########################################################################
class osimage(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('osimage')
__tablename__ = 'osimage'
__table_args__ = {'autoload':True}
########################################################################
class linuximage(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('linuximage')
__tablename__ = 'linuximage'
__table_args__ = {'autoload':True}
########################################################################
class winimage(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('winimage')
__tablename__ = 'winimage'
__table_args__ = {'autoload':True}
########################################################################
class nimimage(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('nimimage')
__tablename__ = 'nimimage'
__table_args__ = {'autoload':True}
########################################################################
class zone(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('zone')
__tablename__ = 'zone'
__table_args__ = {'autoload':True}
########################################################################
class osdistro(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('osdistro')
__tablename__ = 'osdistro'
__table_args__ = {'autoload':True}
########################################################################
class site(Base,mixin):
""""""
Base.metadata.bind = DBsession.getEngine('site')
__tablename__ = 'site'
__table_args__ = {'autoload':True}
########################################################################
def getdict(self):
mydict={}
mykey=self.__dict__['key']
mydict[self.__tablename__+'.'+mykey]=mykey=self.__dict__['value']
return mydict
@classmethod
def dict2tabentry(self,objdict):
mydict={}
ret=[]
for key in objdict.keys():
mydict['key']=key
mydict['value']=objdict[key]
mydict['disable']=None
ret.append(deepcopy(mydict))
return ret
@classmethod
def getTabtype(self):
return 'flat'
#----------------------------------------------------------------------
def query_table_by_node(session, tclass, tkey):
""""""
result=session.query(tclass).filter(tclass.node == tkey).all()
if not result:
return None
return result[0].getdict()
def query_nodelist_by_key(session, nodelist):
""""""
nodelist_value = {}
for node in nodelist:
nodelist_value[node]={}
classlist = [Bootparams,Nodetype,Hosts,Switch,Mac,Noderes]
for eachclass in classlist:
clsdict = query_table_by_node(session,eachclass,node)
nodelist_value[node].update(clsdict)
return nodelist_value
if __name__ == "__main__":
pass
@@ -0,0 +1,128 @@
#!/usr/bin/env python
###############################################################################
# IBM(c) 2019 EPL license http://www.eclipse.org/legal/epl-v10.html
###############################################################################
# -*- coding: utf-8 -*-
#
from sqlalchemy import create_engine,inspect
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import re
import os
import sqlalchemy.exc
from exceptions import *
import codecs
codecs.register(lambda name: codecs.lookup('utf8') if name == 'utf8mb4' else None)
Base = declarative_base()
Base.metadata.bind = None;
class Singleton(object):
def __new__(cls,*args,**kwargs):
if not hasattr(cls,'_inst'):
cls._inst=super(Singleton,cls).__new__(cls,*args,**kwargs)
return cls._inst
class DBsession(Singleton):
_dbcfgpath='/etc/xcat/cfgloc'
_dbcfgregex=re.compile("^(\S+):dbname=(\S+);host=(\S+)\|(\S+)\|(\S*)$")
def __init__(self):
self._sessions={}
def __del__(self):
self.close()
#check whether the backend db is sqlite
@classmethod
def isSqlite(cls):
if os.path.exists(cls._dbcfgpath):
return False
else:
return True
#create DB engine according to the xcat cfgloc file
@classmethod
def createEngine(cls,tablename=None):
if not cls.isSqlite():
dbcfgfile = open(cls._dbcfgpath)
dbcfgloc = dbcfgfile.read( )
dbcfgfile.close()
try:
(dbtype,dbname,dbhost,dbusername,dbpasswd)=re.findall(cls._dbcfgregex,dbcfgloc)[0]
except:
raise BadDBHdlException("Error: invalid cfgloc file: %(o)s", o=cls._dbcfgpath)
if dbtype == 'Pg':
conn="postgresql+psycopg2://"
elif dbtype == 'mysql':
conn="mysql+pymysql://"
engine_value = conn+dbusername+':'+dbpasswd+'@'+dbhost+'/'+dbname
elif tablename:
engine_value = 'sqlite:////etc/xcat/'+tablename+'.sqlite'
else:
raise BadDBHdlException("Error: table name not specified!")
engine=create_engine(engine_value, echo=False)
return engine
@classmethod
def getEngine(cls,tablename=None):
""""""
if not cls.isSqlite():
if not Base.metadata.bind:
engine=cls.createEngine()
else:
return Base.metadata.bind
elif tablename:
engine=cls.createEngine(tablename)
return engine
#bind Base.metadata to db engine
@classmethod
def createSession(cls,tablename=None):
""""""
engine=cls.getEngine(tablename)
#metadata = Base.metadata
#print metadata.tables.keys()
Session = sessionmaker(bind=engine)
session = Session()
return session
#get db session
def loadSession(self,tablename=None):
if self.__class__.isSqlite():
if tablename not in self._sessions.keys():
session=self.__class__.createSession(tablename)
if session:
self._sessions[tablename]=session
return session
else:
return self._sessions[tablename]
else:
if self._sessions:
mykey=self._sessions.keys()[0]
return self._sessions[mykey]
else:
session=self.__class__.createSession(tablename)
self._sessions['_globaldbsession']=session
return session
#commit all the transactions
def commit(self):
for session in self._sessions.keys():
self._sessions[session].commit()
#close all sessions
def close(self):
for session in self._sessions.keys():
self._sessions[session].close()
del self._sessions[session]
#for debug: print the self._sessions
def dumpsession(self):
print(self._sessions)
if __name__ == "__main__":
pass
@@ -0,0 +1,18 @@
#xcat version string
xcat_version=""
#xcat version number
xcat_verno=""
#xcat service running?
isxcatrunning=0
implicitEnvVars={'OBJNAME':{'description':"the object name to import"},
'GITROOT':{'description':"the root path of the git repo where the inventory file resides in"},
'GITBRANCH':{'description':"the current git branch name of the inventory file to import"},
'GITTAG':{'description':"the current git tag of the inventory file to import"},
'GITCOMMIT':{'description':"the current git commit number of the inventory file to import"}}
#verbose?
verbose=False
@@ -0,0 +1,231 @@
#!/usr/bin/env python
###############################################################################
# IBM(c) 2019 EPL license http://www.eclipse.org/legal/epl-v10.html
###############################################################################
# -*- coding: utf-8 -*-
# common helper subroutines
#
from __future__ import print_function
import os
import re
import subprocess
import json
import yaml
import sys
import globalvars
from exceptions import *
from contextlib import contextmanager
def runCommand(cmd, env=None):
"""
Run one command only, when you don't want to bother setting up
the Popen stuff.
(retcode,out,err)=runCommand('lsxcatd -v')
"""
try:
p = subprocess.Popen(cmd,
env=env,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
except OSError,e:
return p.returncode,out, err
return p.returncode,out, err
#remove the dict entries whose value is null or ''
def Util_rmnullindict(mydict):
for key in mydict.keys():
if isinstance(mydict[key],dict):
Util_rmnullindict(mydict[key])
if not mydict[key].keys():
del mydict[key]
else:
if not mydict[key]:
del mydict[key]
# replace the "{{x}}" variables in the value of dict "mydict"
# with the values of "vardict[x]"
def Util_subvarsindict(mydict,vardict):
for key in mydict.keys():
if isinstance(mydict[key],dict):
Util_subvarsindict(mydict[key],vardict)
elif isinstance(mydict[key],list):
for idx,val in enumerate(mydict[key]):
if isinstance(val,str):
mydict[key][idx]=re.sub(r'\{\{(.*)\}\}',lambda m:vardict.get(m.group(1)),val)
elif isinstance(mydict[key],str):
mydict[key]=re.sub(r'\{\{(.*)\}\}',lambda m:vardict.get(m.group(1)),mydict[key])
# get the dict value mydict[a][b][c] with key path a.b.c
def Util_getdictval(mydict,keystr):
if not isinstance(mydict,dict):
return None
dictkeyregex=re.compile("([^\.]+)\.?(\S+)*")
result=re.findall(dictkeyregex,keystr)
if result:
(key,remdkey)=result[0]
if key not in mydict.keys():
return None
if remdkey:
return Util_getdictval(mydict[key],remdkey)
else:
return mydict[key]
# get the dict value mydict[a][b][c] with key path a.b.c
def Util_setdictval(mydict,keystr,value):
dictkeyregex=re.compile("([^\.]+)\.?(\S+)*")
result=re.findall(dictkeyregex,keystr)
if result:
(key,remdkey)=result[0]
if remdkey:
if key not in mydict.keys():
mydict[key]={}
Util_setdictval(mydict[key],remdkey,value)
else:
mydict[key]=value
#remove dict key [a][b][c] with key path a.b.c
def Util_deldictkey(mydict,keystr):
dictkeyregex=re.compile("([^\.]+)\.?(\S+)*")
result=re.findall(dictkeyregex,keystr)
if result:
(key,remdkey)=result[0]
if remdkey:
if key not in mydict.keys():
mydict[key]={}
Util_deldictkey(mydict[key],remdkey)
else:
del mydict[key]
def loadfile(filename):
if not os.path.exists(filename):
raise FileNotExistException("Error: File '%s' does not exist, please check..." % filename)
contents={}
fmt = 'json'
with open(filename,"r") as fh:
f=fh.read()
if not f.startswith('{'):
fmt = 'yaml'
try:
contents=json.loads(f)
except ValueError:
try:
contents = yaml.load(f)
except Exception,e:
raise InvalidFileException("Error: failed to load file \"%s\", please validate the file with 'yamllint %s'(for yaml format) or 'cat %s|python -mjson.tool'(for json format)!"%(filename,filename,filename))
return contents, fmt
return None, fmt
#initialize the global vars in globalvars.py
def initglobal():
if os.path.exists("/var/run/xcatd.pid"):
globalvars.isxcatrunning=1
else:
globalvars.isxcatrunning=0
if globalvars.isxcatrunning:
(retcode,out,err)=runCommand("XCATBYPASS=0 lsxcatd -v")
if retcode!=0 or not globalvars.isxcatrunning:
(retcode,out,err)=runCommand("XCATBYPASS=1 lsxcatd -v")
if retcode!=0:
globalvars.xcat_version=""
globalvars.xcat_verno=""
else:
globalvars.xcat_version=out.strip()
globalvars.xcat_verno=globalvars.xcat_version.split(' ')[1]
# if "key" of d1 or "key" of d1[key] not in d2, delete it
def filter_dict_keys(d1, d2):
tmp_d1 = d1
for key in tmp_d1.keys():
if key not in d2:
del tmp_d1[key]
continue
if type(tmp_d1[key]) != dict:
continue
for subkey in tmp_d1[key].keys():
if subkey not in d2[key]:
del tmp_d1[key][subkey]
return tmp_d1
#print if -v|--verbose specified
def verbose(message,file=sys.stdout):
if globalvars.verbose:
print("%s"%(message),file=file)
#get home directory of the current user
def gethome():
home = os.path.expanduser("~")
return home
#strip single and double quotes from string
def stripquotes(instring):
return instring.strip('"').strip("'")
#redirect stdout to stream
@contextmanager
def stdout_redirector(stream):
old_stdout = sys.stdout
sys.stdout = stream
try:
yield
finally:
sys.stdout = old_stdout
#redirect stderr to stream
@contextmanager
def stderr_redirector(stream):
old_stderr = sys.stderr
sys.stderr = stream
try:
yield
finally:
sys.stderr = old_stderr
#traverse the inventory object directory and get the object type and object names
def traverseobjdir(path):
if not os.path.isdir(path):
return None
ret={}
from manager import InventoryFactory
for subdir in os.listdir(path):
objpath=os.path.join(path,subdir)
if os.path.isdir(objpath):
if os.path.isfile(os.path.join(objpath,'definition.yaml')):
objfile=os.path.join(objpath,'definition.yaml')
with open(objfile, 'r') as stream:
try:
obj_attr_dict = yaml.load(stream)
except:
continue
elif os.path.isfile(os.path.join(objpath,'definition.json')):
objfile=os.path.join(objpath,'definition.json')
with open(objfile, 'r') as stream:
string=stream.read()
try:
obj_attr_dict = json.loads(string)
except:
continue
else:
continue
objtypes=list(set(obj_attr_dict.keys()) & set(InventoryFactory.getvalidobjtypes(ignorepartial=1)))
if objtypes and len(objtypes)==1:
objtype=objtypes[0]
if objtype not in ret.keys():
ret[objtype]=[subdir]
else:
ret[objtype].append(subdir)
return ret
@@ -0,0 +1,147 @@
#!/usr/bin/env python
###############################################################################
# IBM(c) 2019 EPL license http://www.eclipse.org/legal/epl-v10.html
###############################################################################
# -*- coding: utf-8 -*-
import os
import confluent.log as log
from functools import wraps
from .dbsession import DBsession
from .dbfactory import dbfactory
from .attributes import conattr2xcatattr
xcat_dbi=None
data_store=None
xcfm_logger=None
def xcat_manager_decorator(function):
@wraps(function)
def decorator(*args, **kwargs):
xcfm_logger.log("In xCAT configManager: run " + function.__name__)
return function(*args, **kwargs)
return decorator
class xCATConfigManager(object):
global data_store
data_store={}
data_store['nodes']={}
data_store['nodegroup']=[]
def __init__(self):
global xcat_dbi
if xcat_dbi is None:
dbsession = DBsession()
xcat_dbi = dbfactory(dbsession)
global xcfm_logger
if xcfm_logger is None:
xcfm_logger = log.Logger("xcat_manager")
xcfm_logger.log("Init xCAT configManager")
self.decrypt = False
self.current_user = 'xcat'
self.tenant = None
self._list_nodes()
self._list_nodegroup()
def _get_data_from_db(self):
try:
nodelist_set = xcat_dbi.gettab(['nodelist'])
nodelist = nodelist_set.keys()
except KeyError:
data_store['nodes'] = {}
return
attrs_dict = conattr2xcatattr('*')
xcat_dbs=attrs_dict.values()
db_hash = {}
for e in xcat_dbs:
if e is None:
continue
elif isinstance(e, list):
continue
else:
t,p,c=e.rpartition('.')
if t in db_hash:
db_hash[t].append(c)
else:
db_hash[t]=[c]
dataset = xcat_dbi.gettab(db_hash.keys())
for node in nodelist:
nodeinfo = {}
for attr in attrs_dict.keys():
self.log("The attr:" + attr)
nodeinfo[attr] = {}
if attrs_dict[attr] is None:
nodeinfo[attr]['value'] = None
elif isinstance(attrs_dict[attr], list):
nodeinfo[attr]['value'] = attrs_dict[attr]
elif attrs_dict[attr] in dataset[node]:
nodeinfo[attr]['value'] = dataset[node][attrs_dict[attr]]
else:
nodeinfo[attr]['value'] = attrs_dict[attr]
data_store['nodes'][node] = nodeinfo
def _list_nodes(self):
self._get_data_from_db()
def _list_nodegroup(self):
try:
dataset = xcat_dbi.gettab(['nodegroup'])
data_store['nodegroup'] = dataset.keys()
except KeyError:
data_store['nodegroup'] = []
@xcat_manager_decorator
def list_nodes(self):
try:
dataset = xcat_dbi.gettab(['nodelist', 'nodegroup'])
return iter(dataset.keys())
except KeyError:
return []
@xcat_manager_decorator
def get_groups(self, sizesort=False):
if len(data_store['nodegroup']) == 0:
self._list_nodegroup()
return data_store['nodegroup']
@xcat_manager_decorator
def is_node(self, node):
return node in data_store['nodes'].keys()
@xcat_manager_decorator
def is_nodegroup(self, nodegroup):
return nodegroup in data_store['nodegroup']
@xcat_manager_decorator
def check_quorum(self):
pass
@xcat_manager_decorator
def get_node_attributes(self, nodelist, attributes=(), decrypt=None):
ret_dict = {}
if '*' in nodelist:
nodelist = data_store['nodes'].keys()
if len(nodelist) == 0:
return None
if len(set(nodelist) - set(data_store['nodes'].keys())):
self._get_data_from_db()
if '*' in attributes or attributes == ():
for node in nodelist:
ret_dict[node] = data_store['nodes'][node]
self.log(self.__class__.__name__ + "==> get all attributes for: " + str(nodelist))
self.log(str(ret_dict))
return ret_dict
self.log(self.__class__.__name__ + "==> get attributes:" + str(attributes) + "for: " + str(nodelist))
for node in nodelist:
nodeinfo = {}
for attr in attributes:
nodeinfo[attr] = data_store['nodes'][node][attr]
ret_dict[node] = nodeinfo
return ret_dict
@xcat_manager_decorator
def get_collective_member(self, name):
return None
#return get_collective_member(name)
@xcat_manager_decorator
def watch_attributes(self, nodes, attributes, callback):
pass
def log(self, logmsg):
xcfm_logger.log(logmsg)
if __name__ == '__main__':
xcfm = xCATConfigManager()
xcfm.get_node_attributes()
@@ -40,6 +40,9 @@ true
%post
sysctl -p /usr/lib/sysctl.d/confluent.conf >& /dev/null
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
if [ ! -e /etc/pam.d/confluent ]; then
ln -s /etc/pam.d/sshd /etc/pam.d/confluent
fi
true
%clean
+1
View File
@@ -16,6 +16,7 @@ setup(
'confluent/plugins/hardwaremanagement/',
'confluent/plugins/shell/',
'confluent/collective/',
'confluent/xcclient/',
'confluent/plugins/configuration/'],
install_requires=['paramiko', 'pycrypto>=2.6', 'confluent_client>=0.1.0', 'eventlet',
'dnspython', 'netifaces', 'pyte', 'pysnmp', 'pyparsing',