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

Compare commits

..

162 Commits

Author SHA1 Message Date
Jarrod Johnson d528d45820 Add configuration to opt into password policies. 2019-07-11 10:19:46 -04:00
Jarrod Johnson 4eeac8d71a Explore password evaluation as an option.
Password rules may be relevant to some scenarios.  In such a case, this
can provide guidance if the BMC does not have such a facility or
alternatively provide friendlier warnings than the BMC provides
around shortcomings of the password.
2019-07-10 15:32:47 -04:00
Jarrod Johnson 6cc0eb0797 Add reassign to nodediscover
Allow nodediscover to do 'reassign' to repeat discovery process for a discovered
node, without requiring an additional identifier.
2019-07-09 13:53:30 -04:00
Jarrod Johnson bfd0de1a4a Add expiration to more places 2019-07-09 13:26:48 -04:00
Jarrod Johnson a787ac62c3 Add expiration data from ipmi plugin
When pyghmi provides the information, enrich the return
data.
2019-07-09 12:48:56 -04:00
Jarrod Johnson fd9b4a8650 Add support for expiration data, when available 2019-07-09 10:55:33 -04:00
Jarrod Johnson 373bf3dca7 Remove stray whitespace
Fix formatting mistakes
2019-07-09 10:18:34 -04:00
Jarrod Johnson 0ad0c626c2 Restore password policy set to nodediscover
The shift away from IPMI had omitted the password policy set.
Amend the function and restore it to the sequence of events.
2019-07-08 16:25:07 -04:00
Jarrod Johnson fbc4fc6846 Make unexpected error more specific
Often a usable summary message is obfuscated.  Assume the subject line
is safe to relay, but continue to do a more verbose trace.
2019-07-08 14:28:56 -04:00
Jarrod Johnson 3efc153615 Improve conversion reliability
It was frequent that a token expiration would impact attempt to convert
an account.  Suppress the token based authentication to more reliably
have a fresh login.

Additionally, mitigate chance of exhausting user login limit.

Finally, switch to a generated password for the temporary account.  Should something go awry
between deleting the third-party account and recreating it, this
means the system will have to be reset through OS or F1 menu.  However this is better
than the risk of a well known backdoor account being inadvertently
created.
2019-07-03 11:39:36 -04:00
Jarrod Johnson b7ff093e48 Fix ubuntu package install location 2019-07-02 14:42:45 -04:00
Jarrod Johnson 7275e98039 Remove IPMI specific parameters to user
Users cannot have these settings in redfish
2019-07-01 11:23:28 -04:00
Jarrod Johnson 2b0c50dc23 Refresh connection parameters on retry
If the parameters changing cause a login failure,
take the opportunity to refresh that information to work
in the midst of a rediscover, for example.
2019-07-01 09:15:01 -04:00
Jarrod Johnson 54439d5f18 Fix cause of former 'login process died'
If a session died without calling logged, the confluent plugin
instance would be orphaned.  Detect this situation to trigger a
retry.
2019-07-01 09:05:08 -04:00
Jarrod Johnson 65b4cbe8cc Revised fix for redfish name missing case 2019-06-28 16:02:19 -04:00
Jarrod Johnson c8931ae6e7 Revert "Carry fix for null names to redfish"
This reverts commit 8bbeeafa49.
2019-06-28 16:01:26 -04:00
Jarrod Johnson 083f5c8654 Add python-dateutil rpm dependency 2019-06-28 15:41:17 -04:00
Jarrod Johnson 8bbeeafa49 Carry fix for null names to redfish 2019-06-28 15:14:59 -04:00
Jarrod Johnson 422f210f74 Handle adapters with null names
Under some situations, null names are given
for adapters, apply generic treatment to such
devices.
2019-06-28 15:13:56 -04:00
Jarrod Johnson 2e6029bd2c Add a location.height attribute
This permits user to specify height for either unsupported systems
or to speed up the rackview drawing.
2019-06-28 09:30:53 -04:00
Jarrod Johnson 81c0adbbe3 More python 3 compatibility fixes
Improve more commands and modules to parse and execute under python 3.
2019-06-28 08:51:57 -04:00
Jarrod Johnson 6d5f0cdb16 Make TCP_FASTOPEN opportunistic
Certain Linux-like but not Linux environments fail
at this.
2019-06-27 15:36:27 -04:00
Jarrod Johnson c633286019 Add missing dateutil dependency to apt 2019-06-27 15:31:00 -04:00
Jarrod Johnson ba113d6445 Fix various python2-isms
This is far from a complete python3 support, but it lets a significant volume
of remote commands run under python3
2019-06-27 15:28:59 -04:00
Jarrod Johnson d2efb16c71 Remove unneeded line 2019-06-27 15:21:35 -04:00
Jarrod Johnson 739e302506 Add an example for just disabling password complexity 2019-06-27 15:20:24 -04:00
Jarrod Johnson ae181b7753 Improve nodelicense error messages
Check and exit should the file or directory not exist.
2019-06-26 13:24:24 -04:00
Jarrod Johnson b76b415a6e Update nodelicense man page 2019-06-26 08:45:11 -04:00
Jarrod Johnson ef2b324eed Flush csv output per row
Improve responsiveness of csv output when piping
2019-06-25 11:03:50 -04:00
Jarrod Johnson ffe9606de1 Auto-repair 'login process died' condition
The cause is still unknown, but we should be able to at least
repair automatically.
2019-06-24 16:34:56 -04:00
Jarrod Johnson 8ed2d5a551 Fix confulent2xcat usage text
It was incorrectly referencing ansible.
2019-06-24 14:17:32 -04:00
Jarrod Johnson 3501b3c347 Add state info to all licenses
The all resource was not retrieving the licenses.
2019-06-21 15:52:24 -04:00
Jarrod Johnson e55314d759 Add display of inactive licenses
Inactive licenses are also an issue.
2019-06-21 10:17:52 -04:00
Jarrod Johnson 27410a9b6b Fix discover of XCC
Incorrect module path was indicated.
2019-06-20 10:56:09 -04:00
Jarrod Johnson 2db01746d5 Support deletion of licenses from XCC 2019-06-19 16:35:24 -04:00
Jarrod Johnson 208be0beef Implement nodelicense save
Add ability to save licenses from
the XCC.
2019-06-19 15:43:45 -04:00
Jarrod Johnson d34f8af798 Rework redfish support for user management
The redfish user management api couldn't quite be identical,
adjust to the changes.
2019-06-13 15:02:53 -04:00
Jarrod Johnson b2013e93c5 Mitigate performance impact of oem sensors
The sensormap was being regenerated each time a sensor
was asked for.  Make the sensor map
a more perisstent fixture.
2019-06-12 14:47:28 -04:00
Jarrod Johnson 2a72a6184d Fix confluent-wide pauses during redfish
The lenovo OEM module was unpatched in import and
thus it inflicted a confluent-wide sleep instead of
a patched time.sleep.
2019-06-12 10:16:19 -04:00
Jarrod Johnson 7e4dcfa99c Merge branch 'master' of github.com:jjohnson42/confluent 2019-06-11 11:07:31 -04:00
Jarrod Johnson ee82831370 Do not flag file: as insecure
Locally hosted media should not be considered
insecure.
2019-06-11 11:06:59 -04:00
Jarrod Johnson 0869669ef6 Better isolate system config fault
System configuration fault will now be contained to node.
2019-06-07 16:57:03 -04:00
Jarrod Johnson 6a77a13539 Improve error for unserializable collective data
XML errors turn out to be unserializable.  Catch this general
class of problems and provide a less devastating behavior.

Doing an xml.fromstring('') is an example of how to trigger it.
2019-06-07 16:47:17 -04:00
Jarrod Johnson 56e9a67ef8 Fix boundary issue on oversize file
If the logfile is large enough to cause
a struct.error, just force a size
roll and continue.

Also unconditionally unlock the file.
2019-06-07 15:54:12 -04:00
Jarrod Johnson 52d5eb9876 Add stripsize to the redfish plugin
Storage creation was not passing through the stripsize parameter.
2019-06-07 09:34:29 -04:00
Jarrod Johnson b819a488f1 Mirror hotspare handling from ipmi to redfish
The redfish plugin should handle the same disk information as ipmi plugin.
2019-06-06 15:13:19 -04:00
Jarrod Johnson 3fc31f7332 Add dedicated hot spare information
If an array contains dedicated hot spare, properly
report it alongside the member disks.
2019-06-06 11:37:30 -04:00
Jarrod Johnson 21c3579287 Add setdisk and stripsize
More nodestorage improvements.
2019-06-05 15:54:29 -04:00
Jarrod Johnson 4a094f669e Do not break for zsh users
The completion functionality shall be skipped
unless the shell is bash specifically.
2019-06-05 14:27:08 -04:00
Jarrod Johnson 67eecffd29 Force stripsize to numeric
This should be a numeric value, even if it came in as a string.
2019-06-05 11:22:35 -04:00
Jarrod Johnson e288e8bad5 Add stripsize to the input on volume
Explictly ensure a stripsize key.
2019-06-05 11:16:44 -04:00
Jarrod Johnson a8cad7a70f Add stripsize to API
Allow the caller to select a custom stripsize if desired.
2019-06-04 16:33:10 -04:00
Jarrod Johnson 6de605c298 Switch to python2/3 agnostic lower
The string.lower is not in python3
2019-06-04 16:27:20 -04:00
Jarrod Johnson e09c2ed8eb Support more convoluted detail
Some uefi settings details contain
more tricky detail information.
2019-06-03 15:30:32 -04:00
Jarrod Johnson cd5366e73f Fix nodeconfig for non-ascii choices 2019-05-31 14:47:07 -04:00
Jarrod Johnson 509f8c30d5 Remove use of IPMI in XCC config
The XCC configuration is now entirely
over https and well suited for an ipmi disabled scenario.
2019-05-31 08:50:36 -04:00
Jarrod Johnson c63c8076bb Use https to set network on XCC
This quite nearly completes the removal
of IPMI requirement during bootstrap.
2019-05-29 16:36:13 -04:00
Jarrod Johnson 6800c8055c Implement IPMI-free xcc config (work in progress) 2019-05-29 14:31:39 -04:00
Jarrod Johnson ffc55b1594 Reduce concurrency to redfish targets
Some redfish implementations did
not handle a relatively open ended barrage
of concurrent requests.  Try limitting to 4 concurrent
requests to evaluate how the implementations handle it.
2019-05-20 11:03:26 -04:00
Jarrod Johnson 481342340e Fix potential nodesensors crash
There is a tiny chance that time will tick between two
calls.  This fixes it.
2019-05-16 14:37:18 -04:00
Jarrod Johnson d33c6be758 Fix forwarder over custom https ports 2019-05-16 14:36:39 -04:00
Jarrod Johnson 44f3630cf5 Further amend formatting of nodestorage manual 2019-05-14 13:56:20 -04:00
Jarrod Johnson 3eaba23e6f Fix formatting of nodestorage man page 2019-05-14 13:53:28 -04:00
Jarrod Johnson 5ac0a6e650 Fix raid configuration for operator
Operator was not allowed to create arrays.
2019-05-14 10:42:42 -04:00
Jarrod Johnson 9ac83665c6 Isolate redfish node errors
If code experiences an issue specific to a node, isolate that fault to the node.
2019-05-13 14:37:47 -04:00
Jarrod Johnson 30f9d28c2c Merge branch 'master' of github.com:jjohnson42/confluent 2019-05-13 13:36:17 -04:00
Jarrod Johnson 0168e46f24 Isolate individual node errors in ipmi plugin
If an individual node experiences an unexpected error,
isolate the fallout to that specific node.
2019-05-13 13:35:03 -04:00
Jarrod Johnson 067e99d6ce Merge branch 'master' of github.com:jjohnson42/confluent 2019-05-10 14:56:48 -04:00
Jarrod Johnson ad828e609d Reduce bad default login tries
For an SD530 XCC, we would incur 4 attempts at default:
-To pre-config enable SMM, we try once
-Due to pyghmi auto-degrade, try again as oper
-Then during the actual config phase, try again
-Again, try again as oper

Now with pyghmi opt-out, we will force to try only as admin, eliminating the second try.

The SD530 code will now mark that the default creds failed so that
the config phase will know to skip that.
2019-05-10 14:34:39 -04:00
Jarrod Johnson cc5a5c9972 Fix operator add and delete of nodes
This permits operators to run nodedefine and noderemove.
2019-05-10 13:15:19 -04:00
Jarrod Johnson cd2361b80b Fix nodediscover clear for operators
Operators should be allowed to delete discovery data.
2019-05-10 13:11:04 -04:00
Jarrod Johnson c042583a64 Add support for CSV formatted data
Presume CSV semantics for input.
2019-05-10 11:08:12 -04:00
Jarrod Johnson e32d3cf4cc Add auto-index determination to stats
This allows it to auto-skip over units, for example.
2019-05-10 10:34:56 -04:00
Jarrod Johnson 2b86c878a8 Cleanly handle bad credentials in redfish
Provide a similar experience to the ipmi plugin.
2019-05-07 16:19:30 -04:00
Jarrod Johnson 3564de8c6d Fix web consoles/shells for operators
Operator role needed more permissions to act as expected.
2019-05-07 15:58:34 -04:00
Jarrod Johnson 7b5361a019 Add expressions to Operator role
noderun/nodeshell would not work for operators without this.
2019-05-03 09:06:20 -04:00
Jarrod Johnson 65e1dfcc57 Fix nodesensors with redfish plugin
redfish plugin does not produce the same data as ipmi,
tolerate that difference.
2019-05-02 10:54:15 -04:00
Jarrod Johnson ba039e9e3e Fix nodeeventlog on ipmi devices
Changes for redfish broke against ipmi plugin
2019-05-02 10:21:24 -04:00
Jarrod Johnson a6809aae98 Add Monitor role
Add a monitor role that is only viable for monitoring relevant
tasks.
2019-05-02 10:04:40 -04:00
Jarrod Johnson 4d5bfb13bf Add support for Operator role
Support a reduced privilege user that can still perform
most operations, but cannot modify, delete, or add
users/groups to confluent or to BMCs.
2019-05-01 16:57:15 -04:00
Jarrod Johnson 93e9a54e86 Relay redfish error strings to client
This provides a much better experience than 'unexpected'
error.
2019-05-01 11:46:02 -04:00
Jarrod Johnson 25028c8acc Merge branch '2.2.cme' 2019-05-01 09:18:09 -04:00
Jarrod Johnson 906c671d90 Fix misakes in usergroups
Deletion was incorrect and restore from json did not work.
2019-04-30 16:18:36 -04:00
Jarrod Johnson 8fbd99cf5c Fix misakes in usergroups
Deletion was incorrect and restore from json did not work.
2019-04-30 16:18:12 -04:00
Jarrod Johnson c86ac2885f Fix overly verbose log on client close
When a client would close (e.g. an unathenticated nodelist),
a large trace be logged.  Fix by returning silently in such a case.
2019-04-30 15:27:50 -04:00
Jarrod Johnson 952fa3d022 Add user groups to confluent
This allows a system/ldap group to be used instead of directly
specifying individual authorized users.
2019-04-30 15:27:41 -04:00
Jarrod Johnson 90e0f93d37 Module to assist with advanced user manipulation
Currently holds the logic to ascertain the system groups
for a system user.
2019-04-30 15:27:34 -04:00
Jarrod Johnson d78adc334d Fix overly verbose log on client close
When a client would close (e.g. an unathenticated nodelist),
a large trace be logged.  Fix by returning silently in such a case.
2019-04-30 15:03:55 -04:00
Jarrod Johnson 31f2161b57 Add user groups to confluent
This allows a system/ldap group to be used instead of directly
specifying individual authorized users.
2019-04-30 14:55:54 -04:00
Jarrod Johnson 571a34cba2 Module to assist with advanced user manipulation
Currently holds the logic to ascertain the system groups
for a system user.
2019-04-30 13:23:26 -04:00
Jarrod Johnson 52fa5158f6 Fix display of final bin members in verbose 2019-04-26 16:51:44 -04:00
Jarrod Johnson 907f66ae8b Have the range be more precise on verbose 2019-04-26 16:43:53 -04:00
Jarrod Johnson c8c275f804 Fix indentation error 2019-04-26 16:40:47 -04:00
Jarrod Johnson 7a08fee4b5 Actually fix the verbose range 2019-04-26 16:29:33 -04:00
Jarrod Johnson 36f0d888cd Fix the verbose output boundaries. 2019-04-26 16:17:28 -04:00
Jarrod Johnson 81451a6451 Add options to stats
Implement verbose, text plot, and custom select bins
2019-04-26 16:04:01 -04:00
Jarrod Johnson 02eb195e3f Change topline of script to be consistent 2019-04-25 14:47:20 -04:00
Jarrod Johnson 87e7a90c37 Move stats into the client
stats is good enough to be promoted from prototype to a confluent
client component.
2019-04-25 14:46:18 -04:00
Jarrod Johnson bafc25005f Flesh out stats with arguments 2019-04-25 14:45:47 -04:00
Jarrod Johnson 33c1137ccf Remove use of tmp file in stats 2019-04-25 13:59:15 -04:00
Jarrod Johnson abfeef5a0a Merge branch '2.2' 2019-04-25 13:53:41 -04:00
Jarrod Johnson e81579f414 Add a prototype stats command for CLI commands 2019-04-25 13:53:34 -04:00
Jarrod Johnson 6a8cb8deaa Merge branch '2.2' 2019-04-22 13:17:16 -04:00
Jarrod Johnson c6516f9d62 Support redfish event logs 2019-04-22 12:54:49 -04:00
Jarrod Johnson 6290c169f5 Disable MIB resolution for raw requests
MIB resolution turns out to be rather CPU intensive, and the
current SNMP consumers don't want the resolution anyway.
2019-04-16 08:46:50 -04:00
Jarrod Johnson 037ed43c70 Merge branch 'master' of github.com:jjohnson42/confluent 2019-04-15 13:48:20 -04:00
Jarrod Johnson 0a816acf4f Add completion for attributes in node*attrib
Make some of the tedium of the long attribute names bearable
through tab completion.
2019-04-15 13:38:56 -04:00
Jarrod Johnson 2c9c778ca7 Fix compatibility without module_type
Some vendors do not provide module_type
value.  For such vendors, simply omit
the information rather than fail.
2019-04-15 11:08:56 -04:00
Jarrod Johnson bf005eace6 Merge branch 'master' of github.com:jjohnson42/confluent 2019-04-11 16:27:13 -04:00
Jarrod Johnson 34c6d6a4d7 Choose an easier name for the reader 2019-04-11 16:26:59 -04:00
Jarrod Johnson b402ddd656 Add more keystrokes and easier use
No longer require cbl file be specified manually.
2019-04-11 16:26:14 -04:00
Jarrod Johnson 89cf255ae7 Add '-n' option to nodeshell and noderun
Provide ability to suppress node prefix for nodeshell.
This for example can be a quick 'makehosts' substituted and
similar.
2019-04-11 09:17:38 -04:00
Jarrod Johnson d6097ca706 Merge branch 'master' of github.com:jjohnson42/confluent 2019-04-08 10:15:10 -04:00
Jarrod Johnson c3eed19309 Fix adequate check on inadequate IMMs 2019-04-08 10:14:49 -04:00
Jarrod Johnson 40dbe63336 Script to disable password expiry after expired on SMM 2019-04-05 16:53:32 -04:00
Jarrod Johnson d7d3ae344c Skip empty nodes list
A noderange based nodegroup would have
the empty nodes list cluttering the output.
Skip empty nodes list in current settings.
2019-04-05 09:17:45 -04:00
Jarrod Johnson 5c4944a1e4 Provide a sample script for fixing expired credentials 2019-04-05 08:37:31 -04:00
Jarrod Johnson 7fecd0ac5c Add man page for collective command 2019-04-04 10:18:06 -04:00
Jarrod Johnson 6e26b19c67 Autofill collective.manager on discovery if not set
To improve the ease of use, if an administrator has a collective but
does not designate a collective.manager for a node being discovered,
default to the collective member that executes the discovery.
2019-04-04 09:49:02 -04:00
Jarrod Johnson 5d572f17f9 Enhance collective usage output
Collective usage output provided no hints as to how to access more detailed
help.  Amend the wording to make this more clear/obvious.
2019-04-03 14:23:30 -04:00
Jarrod Johnson ae49cf290e Fix key hold-down behavior in cbl reader
The retry print could get stuck in loop and the input
could queue up too many keypresses.
2019-04-03 11:44:05 -04:00
Jarrod Johnson 5ead803c8a Fix up the injected clear delimiting
The delimiting was not being navigated correctly.
2019-04-03 10:38:35 -04:00
Jarrod Johnson dce25d802e Fix SLP hangs on bad targets
Have SLP timeout if there are endpoints that can half-hang
a connection.
2019-04-03 08:34:42 -04:00
Jarrod Johnson c28a963d62 Update the pyopenssl message
It makes it more clear that a restart would be
required to pull in updated dependency.
2019-04-02 09:39:06 -04:00
Jarrod Johnson 3c21ca8739 Tolerate an XCC with downed web service
Make the best of the situation by trying to continue
without the policy applied.
2019-04-02 09:36:37 -04:00
Jarrod Johnson 996b1ba45b Rework prototype cbl reader
It is still not right, but it is a bit easier to work with
to figure out what's wrong with it.
2019-04-01 16:56:14 -04:00
Jarrod Johnson b2c1137321 Fix nodeconfig formatting
The man page did not have a hard line break.
2019-04-01 14:16:20 -04:00
Jarrod Johnson d484e9db43 Update log.py comment for accuracy
The metadata format was out of date.
2019-04-01 10:35:20 -04:00
Jarrod Johnson 6397709e47 Try to force split across clear screens 2019-03-29 17:07:01 -04:00
Jarrod Johnson e0c0f0f1f3 Greatly flesh out the cbl reader 2019-03-29 16:01:40 -04:00
Jarrod Johnson 2c8681a9f3 Opportunisticly start mac rescan on rescan
While the network rescan might be too slow to hold up
general rescan, at least begin a rescan of switches
when a rescan is requested.
2019-03-29 14:01:36 -04:00
Jarrod Johnson b927572872 Sample file for processing CBL files 2019-03-29 11:17:05 -04:00
Jarrod Johnson b5df380ee4 Update nodeinventory for redfish memory
Redfish doesn't present some data that was in the IPMI spec
2019-03-28 13:37:13 -04:00
Jarrod Johnson f79dac7bd2 Add wildcard documentation to noderange man page. 2019-03-28 11:14:29 -04:00
Jarrod Johnson fc5f16fb01 Add wildcard documentation to nodeattrib/nodelist 2019-03-28 11:12:36 -04:00
Jarrod Johnson 907d25164f Replace the network error with a local error
This is a bit more clear about the cause when local commands fail.
2019-03-28 11:03:57 -04:00
Jarrod Johnson 9379c85d0e Fix nodegrouplist man page 2019-03-28 10:05:35 -04:00
Jarrod Johnson bacba8972a Fix debian build process 2019-03-26 13:49:54 -04:00
Jarrod Johnson 5404497e70 Change wheezy to depend on old package name 2019-03-26 13:47:06 -04:00
Jarrod Johnson 8c886b751c Skip pushing static config if config already matches
A strategy of manually adding DHCP managed nodes produced static-baking
when not desired.  For now skip the baking in if the address matches.
2019-03-26 13:43:30 -04:00
Jarrod Johnson 69630edfa9 Handle more generics from Lenovo Redfish
Several generics can come back for adapters, handle them to trigger
pcie enhanced lookup.
2019-03-25 15:45:36 -04:00
Jarrod Johnson cdce1f1833 Add concurrency pool to pyghmi usage
pyghmi adds pool for concurrency, utilize that to accelerate operations
that need multiple resources in parallel.
2019-03-22 15:56:58 -04:00
Jarrod Johnson bf24d0f501 Change wheezy name to python-confluent... 2019-03-19 15:03:06 -04:00
Jarrod Johnson 1d6111b8dd Merge branch 'master' of github.com:jjohnson42/confluent 2019-03-18 15:02:55 -04:00
Jarrod Johnson e59d237d11 Add draft redfish plugin
It can do power, system config, set boot device, identify.
2019-03-18 15:02:21 -04:00
Jarrod Johnson fb3dc9a200 Merge branch 'master' of github.com:jjohnson42/confluent 2019-03-14 13:07:09 -04:00
Jarrod Johnson b0d2d44b75 Update to follow DCD
The DCD signal can be used to detect remote connect attempt
2019-03-14 13:05:59 -04:00
Jarrod Johnson f1e83d938b Add carrier detect to autocons sample
For genesis, not necessarily relevant, but may be
very relevant for OS behaviors in other contexts.
2019-03-14 09:33:58 -04:00
Jarrod Johnson e643b7ed7d Remove inadvertent duplicate command
Harmless, but should be removed.
2019-03-13 15:13:13 -04:00
Jarrod Johnson 163b29a07c Update autocons.py
This is the version that landed in xCAT genesis
2019-03-13 15:05:42 -04:00
Jarrod Johnson c5fa0bfd79 Draft attempt at autocons utility
Make every effort to do early boot console enablement.
2019-03-12 19:31:16 -04:00
Jarrod Johnson a258653186 Merge branch 'master' of github.com:jjohnson42/confluent 2019-03-11 09:08:28 -04:00
Jarrod Johnson 898ff065e0 Merge branch 'master' into umaster 2019-03-08 16:04:19 -05:00
Jarrod Johnson 779b5c9ede Speed up cnos health and add stubs
Add concurrency to accelerate nodehealth and provide stubs
for the as-yet unimplemented functionality.
2019-03-08 16:02:52 -05:00
Jarrod Johnson 5be08ddb1d Fix missing sub-health info on CNOS health 2019-03-08 13:38:38 -05:00
Jarrod Johnson c649ae5fec Have it appropriate do IPv4 and IPv6 2019-03-08 13:25:53 -05:00
Jarrod Johnson c3f2e131b2 Add CNOS plugin for Lenovo switches 2019-03-08 13:25:04 -05:00
Jarrod Johnson 0e2e6267cd Add ability for noderange to wildcard attrib names
Useful for net.*attribs to search when nic is unknown.
2019-03-07 15:19:17 -05:00
Jarrod Johnson e8119d330d Add IPv4 search to copernicus search 2019-03-07 10:24:26 -05:00
Jarrod Johnson 5ae6717709 Rename findconfluent 2019-03-07 08:57:52 -05:00
Jarrod Johnson 0cd94447f7 Update to search multiple interfaces 2019-03-06 16:48:37 -05:00
Jarrod Johnson ce4f8c1837 Sample C code for locating confluent server 2019-03-06 16:22:55 -05:00
45 changed files with 3129 additions and 227 deletions
+9
View File
@@ -504,6 +504,13 @@ def makecall(callout, args):
if 'errorcode' in response:
exitcode = response['errorcode']
sys.stderr.write('Error: ' + response['error'] + '\n')
if 'databynode' in response:
lresponse = response['databynode']
for node in lresponse:
if 'errorcode' in lresponse[node]:
exitcode = lresponse[node]['errorcode']
if 'error' in lresponse[node]:
sys.stderr.write('{0}: Error - {1}\n'.format(node, lresponse[node]['error']))
def clearvalues(resource, attribs):
@@ -997,4 +1004,6 @@ if __name__ == '__main__':
if deadline and os.times()[4] < deadline:
sys.stderr.write('[Exited early, hit enter to continue]')
sys.stdin.readline()
if errcode == 0:
errcode = exitcode
sys.exit(errcode)
+1 -1
View File
@@ -26,7 +26,7 @@ def lookupdata(data, key):
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts
usage='''\n %prog noderange -o xcatnodes.def
\n ''')
argparser.add_option('-o', '--output',
help='xCAT stanza file')
+1 -1
View File
@@ -126,6 +126,6 @@ else:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print res['item']['href'].replace('/', '')
print(res['item']['href'].replace('/', ''))
sys.exit(exitcode)
+13 -9
View File
@@ -232,8 +232,10 @@ def clear_discovery(options, session):
else:
print(repr(res))
def list_matching_macs(options, session):
def list_matching_macs(options, session, node=None):
path = '/discovery/'
if node:
path += 'by-node/{0}/'.format(node)
if options.model:
path += 'by-model/{0}/'.format(options.model)
if options.serial:
@@ -256,25 +258,25 @@ def list_matching_macs(options, session):
path += 'by-mac/'
return [x['item']['href'] for x in session.read(path)]
def assign_discovery(options, session):
def assign_discovery(options, session, needid=True):
abort = False
if options.importfile:
return import_csv(options, session)
if not (options.serial or options.uuid or options.mac):
if not options.node:
sys.stderr.write("Node (-n) must be specified for assignment\n")
abort = True
if needid and not (options.serial or options.uuid or options.mac):
sys.stderr.write(
"UUID (-u), serial (-s), or ether address (-e) required for "
"assignment\n")
abort = True
if not options.node:
sys.stderr.write("Node (-n) must be specified for assignment\n")
abort = True
if abort:
sys.exit(1)
matches = list_matching_macs(options, session)
matches = list_matching_macs(options, session, None if needid else options.node)
if not matches:
# Do a rescan to catch missing requested data
blocking_scan(session)
matches = list_matching_macs(options, session)
matches = list_matching_macs(options, session, None if needid else options.node)
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
@@ -332,7 +334,7 @@ def main():
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'):
if len(args) == 0 or args[0] not in ('list', 'assign', 'reassign', 'rescan', 'clear'):
parser.print_help()
sys.exit(1)
session = client.Command()
@@ -342,6 +344,8 @@ def main():
clear_discovery(options, session)
if args[0] == 'assign':
assign_discovery(options, session)
if args[0] == 'reassign':
assign_discovery(options, session, False)
if args[0] == 'rescan':
blocking_scan(session)
print("Rescan complete")
+6 -2
View File
@@ -33,7 +33,8 @@ if path.startswith('/opt'):
import confluent.client as client
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange [clear]")
@@ -77,7 +78,10 @@ def format_event(evt):
pass
dscparts.append(evttext)
retparts.append(' - '.join(dscparts))
return ' '.join(retparts)
msg = evt.get('message')
if not msg:
msg = ''
return ' '.join(retparts) + msg
if deletemode:
+2 -1
View File
@@ -32,7 +32,8 @@ if path.startswith('/opt'):
import confluent.client as client
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
(options, args) = argparser.parse_args()
+13 -9
View File
@@ -34,7 +34,8 @@ if path.startswith('/opt'):
import confluent.client as client
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
filters = []
@@ -50,15 +51,17 @@ def print_mem_info(node, prefix, meminfo):
memdescfmt = '{0}GB PC'
if meminfo['memory_type'] == 'DDR3 SDRAM':
memdescfmt += '3-{1} '
elif meminfo['memory_type'] == 'DDR4 SDRAM':
elif 'DDR4' in meminfo['memory_type']:
memdescfmt += '4-{1} '
else:
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
return
if meminfo['ecc']:
if meminfo.get('ecc', False):
memdescfmt += 'ECC '
capacity = meminfo['capacity_mb'] / 1024
memdescfmt += meminfo['module_type']
modtype = meminfo.get('module_type', None)
if modtype:
memdescfmt += modtype
memdesc = memdescfmt.format(capacity, meminfo['speed'])
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
print('{0}: {1} manufacturer: {2}'.format(
@@ -66,10 +69,11 @@ def print_mem_info(node, prefix, meminfo):
print('{0}: {1} model: {2}'.format(node, prefix, meminfo['model']))
print('{0}: {1} serial number: {2}'.format(node, prefix,
meminfo['serial']))
print('{0}: {1} manufacture date: {2}'.format(node, prefix,
meminfo['manufacture_date']))
print('{0}: {1} manufacture location: {2}'.format(
node, prefix, meminfo['manufacture_location']))
if 'manufacture_date' in meminfo:
print('{0}: {1} manufacture date: {2}'.format(node, prefix,
meminfo['manufacture_date']))
print('{0}: {1} manufacture location: {2}'.format(
node, prefix, meminfo['manufacture_location']))
exitcode = 0
@@ -142,7 +146,7 @@ try:
databynode[node] = {}
databynode[node][prefix] = inv
else:
print '{0}: {1}: Not Present'.format(node, prefix)
print('{0}: {1}: Not Present'.format(node, prefix))
continue
info = inv['information']
info.pop('board_extra', None)
+66 -9
View File
@@ -36,14 +36,20 @@ exitcode = 0
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][install <file>]")
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
(options, args) = argparser.parse_args()
upfile = None
downdir = None
delete = False
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'install':
upfile = args[2]
elif args[1] == 'save':
downdir = args[2]
elif args[1] == 'delete':
delete = args[2]
else:
components = ['all']
except IndexError:
@@ -51,34 +57,85 @@ except IndexError:
sys.exit(1)
client.check_globbing(noderange)
def install_license(session, filename):
global exitcode
if not os.path.exists(filename):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
filename))
sys.exit(404)
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()
show_licenses(session)
def save_licenses(session, dirname):
global exitcode
resource = '/noderange/{0}/configuration/' \
'management_controller/save_licenses'.format(noderange)
filename = os.path.abspath(dirname)
if not os.path.exists(filename):
sys.stderr.write('Unable to locate specified directory {0}\n'.format(
filename))
sys.exit(404)
instargs = {'dirname': filename}
for res in session.create(resource, instargs):
for node in res.get('databynode', {}):
fname = res['databynode'][node].get('filename', None)
if fname:
print('{0}: Saved license to {1}'.format(node, fname))
else:
sys.stderr.write('{0}: {1}', node, repr(res['databynode'][node]))
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')))
msg = '{0}: {1}'.format(node, license.get('feature',
'Unknown'))
if license.get('state', 'Active') != 'Active':
msg += ' ({0})'.format(license['state'])
print(msg)
def delete_license(session, licname):
global exitcode
licstodel = []
for res in list(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', []):
if license.get('feature', None) == licname:
prefix = '/nodes/{0}/configuration/management_controller/licenses/'.format(node)
for currlic in list(session.read(prefix)):
currlic = currlic.get('item', {}).get('href', 'all')
if currlic == 'all':
continue
currname = list(session.read(prefix + currlic))[0]
currname = currname.get('License', [{}])[0].get('feature', None)
if currname == licname:
list(session.delete(prefix + currlic))
show_licenses(session)
try:
session = client.Command()
if upfile is None:
show_licenses(session)
else:
if upfile:
install_license(session, upfile)
elif downdir:
save_licenses(session, downdir)
elif delete:
delete_license(session, delete)
else:
show_licenses(session)
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
sys.exit(exitcode)
+1 -1
View File
@@ -61,7 +61,7 @@ def main():
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print res['item']['href'].replace('/', '')
print(res['item']['href'].replace('/', ''))
sys.exit(exitcode)
+2 -1
View File
@@ -83,7 +83,8 @@ def list_media(noderange, media):
for node in res.get('databynode', []):
url = res['databynode'][node].get('url', None)
name = res['databynode'][node].get('name', None)
if url and not res['databynode'][node].get('secure', False):
if (url and not url.startswith('file:') and
not res['databynode'][node].get('secure', False)):
name += ' (insecure)'
if not name:
continue
+9 -6
View File
@@ -114,7 +114,8 @@ def sensorpass(showout=True, appendtime=False):
continue
for redundant_state in ('Non-Critical', 'Critical'):
try:
sensedata['states'].remove(redundant_state)
if sensedata.get('states', False):
sensedata['states'].remove(redundant_state)
except ValueError:
pass
resultdata[node][sensedata['name']] = sensedata
@@ -132,11 +133,12 @@ def sensorpass(showout=True, appendtime=False):
showval = u' {0} '.format(sensedata['value'])
if sensedata['units'] not in (None, u''):
showval += sensedata['units']
if sensedata['health'] != 'ok':
if sensedata.get('health', 'ok') != 'ok':
datadescription = [sensedata['health']]
else:
datadescription = []
datadescription.extend(sensedata['states'])
if sensedata.get('states', False):
datadescription.extend(sensedata['states'])
if datadescription:
if showval == '':
showval += u' {0}'.format(
@@ -148,7 +150,7 @@ def sensorpass(showout=True, appendtime=False):
showval += ' @' + time.strftime(
'%Y-%m-%dT%H:%M:%S')
print(u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval).encode('utf-8'))
node, sensedata['name'], showval))
sys.stdout.flush()
return resultdata
@@ -183,6 +185,7 @@ def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
except KeyError:
rowdata.append('N/A')
csvwriter.writerow(rowdata)
sys.stdout.flush()
def main():
@@ -196,7 +199,7 @@ def main():
orderedsensors.append(name)
orderedsensors.sort()
for name in orderedsensors:
headernames.append(sensorheaders[name].encode('utf-8'))
headernames.append(sensorheaders[name])
if options.csv:
linebyline = False
csvwriter = csv.writer(sys.stdout)
@@ -220,7 +223,7 @@ def main():
sys.exit(exitcode)
sleeptime = nextstart - os.times()[4]
if sleeptime > 0:
time.sleep(nextstart - os.times()[4])
time.sleep(sleeptime)
else:
if options.csv:
format_csv(csvwriter, orderedsensors, resdata, showtime=False)
+23 -4
View File
@@ -115,6 +115,8 @@ def createstorage(noderange, options, args):
'name': names}
if options.size:
parms['size'] = options.size
if options.stripsizes:
parms['stripsizes'] = options.stripsizes
_print_cfg(session.create(
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
noderange, names), parms))
@@ -147,13 +149,26 @@ def deletestorage(noderange, options, args):
print(repr(rsp))
# def setstorage(noderange, options, args):
# pass
def setdisk(noderange, options, args):
if options.disks is None:
if len(args):
names = args.pop(0)
else:
sys.stderr.write('-d is required to indicate disk to modify\n')
sys.exit(1)
else:
names = options.disks
if not len(args) or args[0] not in ('hotspare', 'jbod', 'unconfigured'):
sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n')
sys.exit(1)
session = client.Command()
scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]})
_print_cfg(scfg)
funmap = {
'create': createstorage,
'show': showstorage,
# 'set': setstorage,
'diskset': setdisk,
'delete': deletestorage,
'rm': deletestorage,
}
@@ -162,7 +177,7 @@ funmap = {
def main():
argparser = OptParser(
usage='Usage: %prog <noderange> [show|create|delete]',
usage='Usage: %prog <noderange> [show|create|delete|diskset]',
epilog='',
)
argparser.add_option('-r', '--raidlevel', type='int',
@@ -183,6 +198,10 @@ def main():
'naming volumes, or selecting a volume for '
'delete. Default behavior is to use '
'implementation provided default names.')
argparser.add_option('-z', '--stripsizes', type='str',
help='Comma separated list of stripsizes to use when creating volumes. '
'This value is in kilobytes. The default behavior is to allow the '
'storage controller to decide.')
(options, args) = argparser.parse_args()
if len(args) == 1:
args.append('show')
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env 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 argparse
import csv
import fcntl
import io
import numpy as np
import os
import sixel
import subprocess
import sys
class DumbWriter(sixel.SixelWriter):
def restore_position(self, output):
return
def plot(gui, output, plotdata, bins):
import matplotlib as mpl
if not gui:
mpl.use('Agg')
import matplotlib.pyplot as plt
n, bins, patches = plt.hist(plotdata, bins)
plt.show()
if not gui:
if output:
tdata = output
else:
tdata = io.BytesIO()
plt.savefig(tdata)
if not gui and not output:
writer = DumbWriter()
writer.draw(tdata)
return n, bins
def textplot(plotdata, bins):
n, bins = np.histogram(plotdata, bins)
labels = []
for bin in bins:
labels.append('{0:0.1f}'.format(bin))
width = 80
# Since this will be primarily piped into, hard to get
# terminal width
labelwidth = 0
for lab in labels:
if len(lab) > labelwidth:
labelwidth = len(lab)
width -= (labelwidth) + 1
labelfmt = '{{0:>{0}s}}|'.format(labelwidth)
maxn = 0.0
for lgth in n:
if lgth > maxn:
maxn = float(lgth)
for i in range(len(n)):
print(labelfmt.format(labels[i]) + '=' * int(np.round((n[i]/maxn) * width)))
return n, bins
histogram = False
aparser = argparse.ArgumentParser(description='Quick access to common statistics')
aparser.add_argument('-c', type=int, default=0, help='Column number to analyze (default is last column)')
aparser.add_argument('-d', default=None, help='Value used to separate columns')
aparser.add_argument('-x', default=False, action='store_true', help='Output histogram in sixel format')
aparser.add_argument('-s', default=0, help='Number of header lines to skip before processing')
aparser.add_argument('-g', default=False, action='store_true', help='Open histogram in separate graphical window')
aparser.add_argument('-o', default=None, help='Output histogram to the specified filename in PNG format')
aparser.add_argument('-t', default=False, action='store_true', help='Output a histogram in text format')
aparser.add_argument('-v', default=False, action='store_true', help='Attempt to list nodes relevant to each histogram bar (requires -s, -o, or -t)')
aparser.add_argument('-b', type=int, default=10, help='Number of bins to use in histogram (default is 10)')
args = aparser.parse_args(sys.argv[1:])
plotdata = []
headlines = int(args.s)
while headlines >= 0:
data = sys.stdin.readline()
headlines -= 1
if args.d:
delimiter = args.d
else:
if '\t' in data:
delimiter = '\t'
elif ' ' in data:
delimiter = ' '
elif ',' in data:
delimiter = ','
else:
delimiter = ' ' # handle single column
data = list(csv.reader([data], delimiter=delimiter))[0]
nodebydatum = {}
idx = args.c - 1
autoidx = False
while data:
node = None
if ':' in data[0]:
node, data[0] = data[0].split(':', 1)
else:
node = data[0]
if idx == -1 and not autoidx:
while not autoidx:
try:
datum = float(data[idx])
except ValueError:
idx -= 1
continue
except IndexError:
sys.stderr.write('Unable to identify a numerical column\n')
sys.exit(1)
autoidx = True
else:
datum = float(data[idx])
if node:
if datum in nodebydatum:
nodebydatum[datum].add(node)
else:
nodebydatum[datum] = set([node])
plotdata.append(datum)
data = sys.stdin.readline()
data = list(csv.reader([data], delimiter=delimiter))[0]
n = None
if args.g or args.o or args.x:
n, bins = plot(args.g, args.o, plotdata, bins=args.b)
if args.t:
n, bins = textplot(plotdata, bins=args.b)
print('Samples: {5} Min: {3} Median: {0} Mean: {1} Max: {4} StandardDeviation: {2} Sum: {6}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata), np.sum(plotdata)))
if args.v and n is not None and nodebydatum:
print('')
currbin = bins[0]
bins = bins[1:]
currbinmembers = []
for datum in sorted(nodebydatum):
if datum > bins[0]:
nextbin = None
endbin = bins[0]
while len(bins) and bins[0] < datum:
nextbin = bins[0]
bins = bins[1:]
if not nextbin:
nextbin = np.max(plotdata)
print('Entries between {0} and {1}'.format(currbin, endbin))
currbin = nextbin
print('-' * 80)
print(','.join(sorted(currbinmembers)))
print('')
print('')
currbinmembers = []
for node in nodebydatum[datum]:
currbinmembers.append(node)
if currbinmembers:
print('Entries between {0} and {1}'.format(currbin, np.max(plotdata)))
print('-' * 80)
print(','.join(sorted(currbinmembers)))
print('')
print('')
+20 -9
View File
@@ -15,7 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import anydbm as dbm
try:
import anydbm as dbm
except ImportError:
import dbm
import csv
import errno
import fnmatch
@@ -322,14 +325,19 @@ class Command(object):
if knownhosts:
certdata = self.connection.getpeercert(binary_form=True)
fingerprint = 'sha512$' + hashlib.sha512(certdata).hexdigest()
fingerprint = fingerprint.encode('utf-8')
hostid = '@'.join((port, server))
khf = dbm.open(os.path.join(clientcfgdir, "knownhosts"), 'c', 384)
if hostid in khf:
if fingerprint == khf[hostid]:
return
else:
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
try:
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
except NameError:
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
cprint('Adding new key for %s:%s' % (server, port))
@@ -393,7 +401,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
for node in sorted(res['databynode']):
for attr, val in sorted(
res['databynode'][node].items(),
key=lambda (k, v): v.get('sortid', k) if isinstance(v, dict) else k):
key=lambda k: k[1].get('sortid', k[0]) if isinstance(k[1], dict) else k[0]):
if attr == 'error':
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
continue
@@ -478,11 +486,14 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
details = False
if details:
if currattr.get('help', None):
attrout += ' (Help: {0})'.format(
currattr['help'].encode('utf-8'))
attrout += u' (Help: {0})'.format(
currattr['help'])
if currattr.get('possible', None):
attrout += ' (Choices: {0})'.format(
','.join(currattr['possible']))
try:
attrout += u' (Choices: {0})'.format(
','.join(currattr['possible']))
except TypeError:
pass
cprint(attrout)
if not exitcode:
if requestargs:
@@ -652,4 +663,4 @@ def check_globbing(noderange):
'bash or change directories such that there is no filename '
'that would conflict.'
'\n'.format(noderange))
sys.exit(1)
sys.exit(1)
+2
View File
@@ -34,6 +34,8 @@ def decodestr(value):
ret = value.decode('cp437')
except UnicodeDecodeError:
ret = value
except AttributeError:
return value
return ret
def unicode_dictvalues(dictdata):
+8 -1
View File
@@ -39,6 +39,8 @@ alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
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'
# Do not continue for non-bash shells, the rest of this sets up bash completion functions
[ -z "$BASH_VERSION" -o -z "$PS1" ] && return
_confluent_get_args()
@@ -158,7 +160,7 @@ _confluent_nodelicense_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "install list" -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
@@ -166,6 +168,11 @@ _confluent_nodelicense_completion()
COMPREPLY=()
return
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'save' ]; then
compopt -o dirnames
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return
+8 -2
View File
@@ -3,9 +3,15 @@ nodelicense(8) -- Manage license keys on BMC
## SYNOPSIS
`nodelicense <noderange> [list|install <filename>]`
`nodelicense <noderange> [list|install <filename>|delete <license>|save <directory>]`
## DESCRIPTION
`nodelicense` shows and installs license keys on supported BMCs
`nodelicense` manages license keys on supported BMCs. Without an argument, the command
lists currently installed license. Using `delete` will remove the specified license name
from th eBMC. The `save` subcommand will take the passed directory (which may be in the form
of /path/to/{node}/ to have the node name substituted for each node) and back up installed licenses
to that directory. The `install` command will take the specified filename and install. The filename
argument may be of the form xcc_fod_0034_7X21{id.serial}.key to have the serial number substituted
to allow unique licenses to be specified in a single command.
+2 -2
View File
@@ -35,8 +35,8 @@ the noderange.
`$ nodestorage d5 delete somedata`
`Deleted: somedata`
* Creating a raid5 of 4 disks and a volume named `somedata`:
`$ nodestorage d5 create -r 5 -d drive0,drive_1,drive_2,drive_3 -n somedata
* Creating a raid5 of 4 disks and a volume named `somedata`:
`$ nodestorage d5 create -r 5 -d drive0,drive_1,drive_2,drive_3 -n somedata`
`d5: Volume somedata: Size: 1.905 TB`
`d5: Volume somedata: State: Optimal`
`d5: Volume somedata: Array 1-2`
+2 -1
View File
@@ -32,7 +32,7 @@ if [ "$OPKGNAME" = "confluent-server" ]; then
if grep wheezy /etc/os-release; then
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome/' debian/control
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' debian/control
fi
if grep wheezy /etc/os-release; then
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
@@ -42,6 +42,7 @@ if [ "$OPKGNAME" = "confluent-server" ]; then
fi
head -n -1 debian/control > debian/control1
mv debian/control1 debian/control
echo 'export PYBUILD_INSTALL_ARGS=--install-lib=/opt/confluent/lib/python' >> debian/rules
#echo 'Provides: python-'$DPKGNAME >> debian/control
#echo 'Conflicts: python-'$DPKGNAME >> debian/control
#echo 'Replaces: python-'$DPKGNAME' (<<2)' >> debian/control
+88 -10
View File
@@ -23,9 +23,11 @@ import confluent.config.configmanager as configmanager
import eventlet
import eventlet.tpool
import Cryptodome.Protocol.KDF as KDF
from fnmatch import fnmatch
import hashlib
import hmac
import multiprocessing
import confluent.userutil as userutil
try:
import PAM
except ImportError:
@@ -39,7 +41,59 @@ _passchecking = {}
authworkers = None
authcleaner = None
_allowedbyrole = {
'Operator': {
'retrieve': ['*'],
'create': [
'/noderange/',
'/nodes/',
'/node*/media/uploads/',
'/node*/inventory/firmware/updates/*',
'/node*/suppport/servicedata*',
'/node*/attributes/expression',
'/nodes/*/console/session*',
'/nodes/*/shell/sessions*',
'/node*/configuration/*',
],
'update': [
'/discovery/*',
'/networking/macs/rescan',
'/node*/power/state',
'/node*/power/reseat',
'/node*/attributes/*',
'/node*/media/*tach',
'/node*/boot/nextdevice',
'/node*/identify',
'/node*/configuration/*',
],
'start': [
'/nodes/*/console/session*',
'/nodes/*/shell/sessions*',
],
'delete': [
'/discovery/*',
'/node*',
],
},
'Monitor': {
'retrieve': [
'/node*/health/hardware',
'/node*/power/state',
'/node*/sensors/*',
'/nodes/',
'/',
],
}
}
_deniedbyrole = {
# This supersedes the above and is only consulted after the allowed has happened
'Operator': {
'update': [
'/node*/configuration/management_controller/users/*',
]
}
}
class Credentials(object):
def __init__(self, username, passphrase):
self.username = username
@@ -112,21 +166,37 @@ def authorize(name, element, tenant=False, operation='create',
and the relevant ConfigManager object for the context of the
request.
"""
if operation not in ('create', 'start', 'update', 'retrieve', 'delete'):
return None
# skipuserobj is a leftover from the now abandoned plan to use pam session
# to do authorization and authentication. Now confluent always does authorization
# even if pam does authentication.
if operation not in ('create', 'start', 'update', 'retrieve', 'delete', None):
return False
user, tenant = _get_usertenant(name, tenant)
if tenant is not None and not configmanager.is_tenant(tenant):
return None
return False
manager = configmanager.ConfigManager(tenant, username=user)
if skipuserobj:
return None, manager, user, tenant, skipuserobj
userobj = manager.get_user(user)
if not userobj:
for group in userutil.grouplist(user):
userobj = manager.get_usergroup(group)
if userobj:
break
if userobj: # returning
role = userobj.get('role', 'Administrator')
if element and role != 'Administrator':
for rule in _allowedbyrole.get(role, {}).get(operation, []):
if fnmatch(element, rule):
break
else:
return False
for rule in _deniedbyrole.get(role, {}).get(operation, []):
if fnmatch(element, rule):
return False
return userobj, manager, user, tenant, skipuserobj
return None
return False
def check_user_passphrase(name, passphrase, element=None, tenant=False):
def check_user_passphrase(name, passphrase, operation=None, element=None, tenant=False):
"""Check a a login name and passphrase for authenticity and authorization
The function combines authentication and authorization into one function.
@@ -160,12 +230,20 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
credobj = Credentials(user, passphrase)
cfm = configmanager.ConfigManager(tenant, username=user)
ucfg = cfm.get_user(user)
if ucfg is None:
try:
for group in userutil.grouplist(user):
ucfg = cfm.get_usergroup(group)
if ucfg:
break
except KeyError:
pass
if ucfg is None:
eventlet.sleep(0.05)
return None
if (user, tenant) in _passcache:
if hashlib.sha256(passphrase).digest() == _passcache[(user, tenant)]:
return authorize(user, element, tenant)
return authorize(user, element, tenant, operation=operation)
else:
# In case of someone trying to guess,
# while someone is legitimately logged in
@@ -200,7 +278,7 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
# delay as well
if crypt == crypted:
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant)
return authorize(user, element, tenant, operation)
try:
pammy = PAM.pam()
pammy.start(_pamservice, user, credobj.pam_conv)
@@ -208,7 +286,7 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
pammy.acct_mgmt()
del pammy
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, skipuserobj=False)
return authorize(user, element, tenant, operation, skipuserobj=False)
except NameError:
pass
except PAM.error:
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015 Lenovo
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -183,6 +183,9 @@ node = {
'freeform text data without concern for issues in how '
'the server will process it.',
},
'location.height': {
'description': 'Height in RU of the system (defaults to query the systems)',
},
'location.room': {
'description': 'Room description for the node',
},
@@ -99,6 +99,7 @@ _attraliases = {
'bmcpass': 'secret.hardwaremanagementpassword',
'switchpass': 'secret.hardwaremanagementpassword',
}
_validroles = ('Administrator', 'Operator', 'Monitor')
def _mkpath(pathname):
try:
@@ -169,6 +170,24 @@ def _do_notifier(cfg, watcher, callback):
logException()
def _rpc_master_del_usergroup(tenant, name):
ConfigManager(tenant).del_usergroup(name)
def _rpc_del_usergroup(tenant, name):
ConfigManager(tenant)._true_del_usergroup(name)
def _rpc_master_set_usergroup(tenant, name, attributemap):
ConfigManager(tenant).set_user(name, attributemap)
def _rpc_set_usergroup(tenant, name, attributemap):
ConfigManager(tenant)._true_set_user(name, attributemap)
def _rpc_master_set_user(tenant, name, attributemap):
ConfigManager(tenant).set_user(name, attributemap)
@@ -220,9 +239,19 @@ def _rpc_del_user(tenant, name):
def _rpc_master_create_user(tenant, *args):
ConfigManager(tenant).create_user(*args)
def _rpc_master_create_usergroup(tenant, *args):
ConfigManager(tenant).create_usergroup(*args)
def _rpc_create_user(tenant, *args):
ConfigManager(tenant)._true_create_user(*args)
def _rpc_create_usergroup(tenant, *args):
ConfigManager(tenant)._true_create_usergroup(*args)
def _rpc_master_del_groups(tenant, groups):
ConfigManager(tenant).del_groups(groups)
@@ -1182,6 +1211,12 @@ class ConfigManager(object):
except KeyError:
return []
def list_usergroups(self):
try:
return list(self._cfgstore['usergroups'])
except KeyError:
return []
def get_user(self, name):
"""Get user information from DB
@@ -1219,12 +1254,46 @@ class ConfigManager(object):
:param groupname: the name of teh group to modify
:param attributemap: The mapping of keys to values to set
"""
if cfgleader:
return exec_on_leader('_rpc_master_set_usergroup', self.tenant,
groupname, attributemap)
if cfgstreams:
exec_on_followers('_rpc_set_usergroup', self.tenant, groupname,
attributemap)
self._true_set_usergroup(groupname, attributemap)
def _true_set_usergroup(self, groupname, attributemap):
for attribute in attributemap:
self._cfgstore['usergroups'][attribute] = attributemap[attribute]
if attribute == 'role':
therole = None
for candrole in _validroles:
if candrole.lower().startswith(attributemap[attribute].lower()):
therole = candrole
if therole not in _validroles:
raise ValueError(
'Unrecognized role "{0}" (valid roles: {1})'.format(attributemap[attribute], ','.join(_validroles)))
attributemap[attribute] = therole
self._cfgstore['usergroups'][groupname][attribute] = attributemap[attribute]
_mark_dirtykey('usergroups', groupname, self.tenant)
self._bg_sync_to_file()
def create_usergroup(self, groupname, role="Administrator"):
"""Create a new user
:param groupname: The name of the user group
:param role: The role the user should be considered. Can be
"Administrator" or "Technician", defaults to
"Administrator"
"""
if cfgleader:
return exec_on_leader('_rpc_master_create_usergroup', self.tenant,
groupname, role)
if cfgstreams:
exec_on_followers('_rpc_create_usergroup', self.tenant, groupname,
role)
self._true_create_usergroup(groupname, role)
def _true_create_usergroup(self, groupname, role="Administrator"):
if 'usergroups' not in self._cfgstore:
self._cfgstore['usergroups'] = {}
groupname = groupname.encode('utf-8')
@@ -1232,6 +1301,20 @@ class ConfigManager(object):
raise Exception("Duplicate groupname requested")
self._cfgstore['usergroups'][groupname] = {'role': role}
_mark_dirtykey('usergroups', groupname, self.tenant)
self._bg_sync_to_file()
def del_usergroup(self, name):
if cfgleader:
return exec_on_leader('_rpc_master_del_usergroup', self.tenant, name)
if cfgstreams:
exec_on_followers('_rpc_del_usergroup', self.tenant, name)
self._true_del_usergroup(name)
def _true_del_usergroup(self, name):
if name in self._cfgstore['usergroups']:
del self._cfgstore['usergroups'][name]
_mark_dirtykey('usergroups', name, self.tenant)
self._bg_sync_to_file()
def set_user(self, name, attributemap):
"""Set user attribute(s)
@@ -1249,6 +1332,15 @@ class ConfigManager(object):
def _true_set_user(self, name, attributemap):
user = self._cfgstore['users'][name]
for attribute in attributemap:
if attribute == 'role':
therole = None
for candrole in _validroles:
if candrole.lower().startswith(attributemap[attribute].lower()):
therole = candrole
if therole not in _validroles:
raise ValueError(
'Unrecognized role "{0}" (valid roles: {1})'.format(attributemap[attribute], ','.join(_validroles)))
attributemap[attribute] = therole
if attribute == 'password':
salt = os.urandom(8)
#TODO: WORKERPOOL, offload password set to a worker
@@ -1525,7 +1617,7 @@ class ConfigManager(object):
del attribmap[group][attr]
if 'noderange' in attribmap[group]:
if len(attribmap[group]) > 1:
raise ValueErorr('noderange attribute must be set by itself')
raise ValueError('noderange attribute must be set by itself')
for attr in attribmap[group]:
if attr in _attraliases:
newattr = _attraliases[attr]
@@ -2077,6 +2169,9 @@ class ConfigManager(object):
self.set_node_attributes(tmpconfig[confarea], True)
elif confarea == 'nodegroups':
self.set_group_attributes(tmpconfig[confarea], True)
elif confarea == 'usergroups':
for usergroup in tmpconfig[confarea]:
self.create_usergroup(usergroup)
elif confarea == 'users':
for user in tmpconfig[confarea]:
uid = tmpconfig[confarea].get('id', None)
+66 -5
View File
@@ -124,7 +124,7 @@ def load_plugins():
rootcollections = ['discovery/', 'events/', 'networking/',
'noderange/', 'nodes/', 'nodegroups/', 'users/', 'version']
'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' , 'users/', 'version']
class PluginRoute(object):
@@ -171,6 +171,10 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'save_licenses': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'net_interfaces': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
@@ -396,13 +400,33 @@ def create_user(inputdata, configmanager):
configmanager.create_user(username, attributemap=inputdata)
def create_usergroup(inputdata, configmanager):
try:
groupname = inputdata['name']
del inputdata['name']
except (KeyError, ValueError):
raise exc.InvalidArgumentException()
configmanager.create_usergroup(groupname)
def update_usergroup(groupname, attribmap, configmanager):
try:
configmanager.set_usergroup(groupname, attribmap)
except ValueError as e:
raise exc.InvalidArgumentException(str(e))
def update_user(name, attribmap, configmanager):
try:
configmanager.set_user(name, attribmap)
except ValueError:
raise exc.InvalidArgumentException()
except ValueError as e:
raise exc.InvalidArgumentException(str(e))
def show_usergroup(groupname, configmanager):
groupinfo = configmanager.get_usergroup(groupname)
for attr in groupinfo:
yield msg.Attributes(kv={attr: groupinfo[attr]})
def show_user(name, configmanager):
userobj = configmanager.get_user(name)
rv = {}
@@ -419,6 +443,10 @@ def show_user(name, configmanager):
rv[attr] = userobj[attr]
yield msg.Attributes(kv={attr: rv[attr]},
desc=attrscheme.user[attr]['description'])
if 'role' in userobj:
yield msg.Attributes(kv={'role': userobj['role']})
def stripnode(iterablersp, node):
@@ -451,6 +479,10 @@ def delete_user(user, configmanager):
configmanager.del_user(user)
yield msg.DeletedResource(user)
def delete_usergroup(usergroup, configmanager):
configmanager.del_usergroup(usergroup)
yield msg.DeletedResource(usergroup)
def delete_nodegroup_collection(collectionpath, configmanager):
if len(collectionpath) == 2: # just the nodegroup
@@ -678,7 +710,11 @@ def handle_dispatch(connection, cert, dispatch, peername):
def _forward_rsp(connection, res):
r = pickle.dumps(res)
try:
r = pickle.dumps(res)
except TypeError:
r = pickle.dumps(Exception(
'Cannot serialize error, check collective.manager error logs for details' + str(res)))
rlen = len(r)
if not rlen:
return
@@ -887,7 +923,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
a = configmanager.get_collective_member(manager)
try:
remote = socket.create_connection((a['address'], 13001))
remote.settimeout(90)
remote.settimeout(180)
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
@@ -1005,6 +1041,31 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
configmanager, inputdata, operation, pathcomponents)
elif pathcomponents[0] == 'version':
return (msg.Attributes(kv={'version': confluent.__version__}),)
elif pathcomponents[0] == 'usergroups':
# TODO: when non-administrator accounts exist,
# they must only be allowed to see their own user
try:
usergroup = pathcomponents[1]
except IndexError: # it's just users/
if operation == 'create':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata,
configmanager=configmanager)
create_usergroup(inputdata.attribs, configmanager)
return iterate_collections(configmanager.list_usergroups(),
forcecollection=False)
if usergroup not in configmanager.list_usergroups():
raise exc.NotFoundException("Invalid usergroup %s" % usergroup)
if operation == 'retrieve':
return show_usergroup(usergroup, configmanager)
elif operation == 'delete':
return delete_usergroup(usergroup, configmanager)
elif operation == 'update':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata,
configmanager=configmanager)
update_usergroup(usergroup, inputdata.attribs, configmanager)
return show_usergroup(usergroup, configmanager)
elif pathcomponents[0] == 'users':
# TODO: when non-administrator accounts exist,
# they must only be allowed to see their own user
@@ -32,10 +32,21 @@ DEFAULT_PASS = 'PASSW0RD'
class NodeHandler(generic.NodeHandler):
def _get_ipmicmd(self, user=DEFAULT_USER, password=DEFAULT_PASS):
return ipmicommand.Command(self.ipaddr, user, password)
def _get_ipmicmd(self, user=None, password=None):
priv = None
if user is None or password is None:
if self.trieddefault:
raise pygexc.IpmiException()
priv = 4 # manually indicate priv to avoid double-attempt
if user is None:
user = DEFAULT_USER
if password is None:
password = DEFAULT_PASS
return ipmicommand.Command(self.ipaddr, user, password,
privlevel=priv, keepalive=False)
def __init__(self, info, configmanager):
self.trieddefault = None
super(NodeHandler, self).__init__(info, configmanager)
def probe(self):
@@ -68,6 +68,23 @@ class NodeHandler(object):
def _savecert(self, certificate):
self._fp = certificate
return True
def get_node_credentials(self, nodename, creds, defuser, defpass):
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
havecustomcreds = False
if user is not None and user != defuser:
havecustomcreds = True
else:
user = defuser
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd is not None and passwd != defpass:
havecustomcreds = True
else:
passwd = defpass
return user, passwd, not havecustomcreds
@property
def cert_fail_reason(self):
@@ -15,7 +15,6 @@
import confluent.discovery.handlers.bmc as bmchandler
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.private.util as pygutil
import string
import struct
class NodeHandler(bmchandler.NodeHandler):
@@ -44,7 +43,7 @@ class NodeHandler(bmchandler.NodeHandler):
uuidprefix[12:16]
self.info['uuid'] = uuidprefix + '-' + '-'.join(
wronguuid.split('-')[3:])
self.info['uuid'] = string.lower(self.info['uuid'])
self.info['uuid'] = self.info['uuid'].lower()
if ff not in ('dense-computing', 'BC2'):
# do not probe unless it's a dense platform
return
@@ -12,10 +12,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import confluent.discovery.handlers.imm as immhandler
import confluent.netutil as netutil
import confluent.util as util
import eventlet
import eventlet.support.greendns
import json
import os
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.oem.lenovo.imm as imm
xcc = eventlet.import_patched('pyghmi.redfish.oem.lenovo.xcc')
import pyghmi.util.webclient as webclient
import struct
getaddrinfo = eventlet.support.greendns.getaddrinfo
def fixup_uuid(uuidprop):
baduuid = ''.join(uuidprop.split())
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
return '-'.join(uuid).upper()
@@ -23,6 +40,13 @@ import pyghmi.ipmi.oem.lenovo.imm as imm
class NodeHandler(immhandler.NodeHandler):
devname = 'XCC'
def __init__(self, info, configmanager):
self._xcchdlr = None
self._wc = None
self.nodename = None
self._atdefaultcreds = True
super(NodeHandler, self).__init__(info, configmanager)
@classmethod
def adequate(cls, info):
# We can sometimes receive a partially initialized SLP packet
@@ -33,6 +57,7 @@ class NodeHandler(immhandler.NodeHandler):
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
return
self.trieddefault = None # Reset state on a preconfig attempt
# attempt to enable SMM
#it's normal to get a 'not supported' (193) for systems without an SMM
ipmicmd = None
@@ -44,6 +69,7 @@ class NodeHandler(immhandler.NodeHandler):
'Incorrect password' not in str(e)):
# raise an issue if anything other than to be expected
raise
self.trieddefault = True
#TODO: decide how to clean out if important
#as it stands, this can step on itself
#if ipmicmd:
@@ -55,7 +81,33 @@ class NodeHandler(immhandler.NodeHandler):
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def set_password_policy(self, ic):
@property
def wc(self):
if self._wc is None:
self._wc = webclient.SecureHTTPConnection(
self.ipaddr, 443, verifycallback=self.validate_cert)
self._wc.connect()
self._xcchdlr = xcc.OEMHandler(None, None, self._wc, False)
if not self.trieddefault:
self._xcchdlr.set_credentials('USERID', 'PASSW0RD')
wc = self._xcchdlr.get_webclient()
if wc:
return wc
self.trieddefault = True
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(
self.nodename, creds, 'USERID', 'PASSW0RD')
if isdefault:
return
self._atdefaultcreds = False
self._xcchdlr.set_credentials(user, passwd)
wc = self._xcchdlr.get_webclient()
if wc:
return wc
def set_password_policy(self):
ruleset = {'USER_GlobalMinPassChgInt': '0'}
for rule in self.ruleset.split(','):
if '=' not in rule:
@@ -75,15 +127,88 @@ class NodeHandler(immhandler.NodeHandler):
ruleset['USER_GlobalPassComplexRequired'] = value
if name.lower() == 'reuse':
ruleset['USER_GlobalMinPassReuseCycle'] = value
ic.register_key_handler(self.validate_cert)
ic.oem_init()
try:
ic._oem.immhandler.wc.grab_json_response('/api/dataset', ruleset)
self.wc.grab_json_response('/api/dataset', ruleset)
except Exception as e:
print(repr(e))
pass
def _get_next_userid(self, wc):
userinfo = wc.grab_json_response('/api/dataset/imm_users')
userinfo = userinfo['items'][0]['users']
for user in userinfo:
if user['users_user_name'] == '':
return user['users_user_id']
def _setup_xcc_account(self, username, passwd, wc):
userinfo = wc.grab_json_response('/api/dataset/imm_users')
uid = None
for user in userinfo['items'][0]['users']:
if user['users_user_name'] == username:
uid = user['users_user_id']
break
else:
for user in userinfo['items'][0]['users']:
if user['users_user_name'] == 'USERID':
uid = user['users_user_id']
break
if not uid:
raise Exception("XCC has neither the default user nor configured user")
# The following will work if the password is force change or normal..
wc.grab_json_response('/api/function',
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
if username != 'USERID':
wc.grab_json_response(
'/api/function',
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,'.format(uid, username)})
def _convert_sha256account(self, user, passwd, wc):
# First check if the specified user is sha256...
userinfo = wc.grab_json_response('/api/dataset/imm_users')
curruser = None
uid = None
for userent in userinfo['items'][0]['users']:
if userent['users_user_name'] == user:
curruser = userent
break
if curruser.get('users_pass_is_sha256', 0):
self._wc = None
wc = self.wc
nwc = wc.dupe()
# Have to convert it for being useful with most Lenovo automation tools
# This requires deleting the account entirely and trying again
tmpuid = self._get_next_userid(wc)
try:
tpass = base64.b64encode(os.urandom(9)) + 'Iw47$'
userparams = "{0},6pmu0ezczzcp,{1},1,4,0,0,0,0,,8,".format(tmpuid, tpass)
result = wc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
adata = json.dumps({
'username': '6pmu0ezczzcp',
'password': tpass,
})
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'}
nwc.request('POST', '/api/login', adata, headers)
rsp = nwc.getresponse()
if rsp.status == 200:
rspdata = json.loads(rsp.read())
nwc.set_header('Content-Type', 'application/json')
nwc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
if '_csrf_token' in wc.cookies:
nwc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
if rspdata.get('reason', False):
newpass = base64.b64encode(os.urandom(9)) + 'q4J$'
nwc.grab_json_response(
'/api/function',
{'USER_UserPassChange': '{0},{1}'.format(tmpuid, newpass)})
nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)})
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, passwd)
nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
finally:
self._wc = None
self.wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
def config(self, nodename, reset=False):
self.nodename = nodename
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
@@ -91,17 +216,49 @@ class NodeHandler(immhandler.NodeHandler):
nodename, 'discovery.passwordrules')
self.ruleset = dpp.get(nodename, {}).get(
'discovery.passwordrules', {}).get('value', '')
ic = self._bmcconfig(nodename, customconfig=self.set_password_policy)
wc = self.wc
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
self.set_password_policy()
if self._atdefaultcreds:
if not isdefault:
self._setup_xcc_account(user, passwd, wc)
self._convert_sha256account(user, passwd, wc)
cd = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager'], True)
cd = cd.get(nodename, {})
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
'fe80::')):
newip = cd['hardwaremanagement.manager']['value']
newipinfo = getaddrinfo(newip, 0)[0]
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
newmask = netutil.cidr_to_mask(netconfig['prefix'])
# do not change the ipv4_config if the current config looks
statargs = {'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0', 'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask}
if netconfig['ipv4_gateway']:
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
wc.grab_json_response('/api/dataset', statargs)
elif self.ipaddr.startswith('fe80::'):
self.configmanager.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
return
# Ok, we can get the enclosure uuid now..
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
enclosureuuid = self.info.get('attributes', {}).get('chassis-uuid', [None])[0]
if enclosureuuid:
enclosureuuid = imm.fixup_uuid(enclosureuuid).lower()
enclosureuuid = enclosureuuid.lower()
em = self.configmanager.get_node_attributes(nodename,
'enclosure.manager')
em = em.get(nodename, {}).get('enclosure.manager', {}).get(
@@ -110,8 +267,3 @@ class NodeHandler(immhandler.NodeHandler):
if em:
self.configmanager.set_node_attributes(
{em: {'id.uuid': enclosureuuid}})
# TODO(jjohnson2): web based init config for future prevalidated cert scheme
# def config(self, nodename):
# return
+23 -16
View File
@@ -269,6 +269,9 @@ def _authorize_request(env, operation):
name = ''
sessionid = None
cookie = Cookie.SimpleCookie()
element = env['PATH_INFO']
if element.startswith('/sessions/current/'):
element = None
if 'HTTP_COOKIE' in env:
#attempt to use the cookie. If it matches
cc = RobustCookie()
@@ -290,7 +293,7 @@ def _authorize_request(env, operation):
httpsessions[sessionid]['expiry'] = time.time() + 90
name = httpsessions[sessionid]['name']
authdata = auth.authorize(
name, element=None,
name, element=element, operation=operation,
skipuserobj=httpsessions[sessionid]['skipuserobject'])
if (not authdata) and 'HTTP_AUTHORIZATION' in env:
if env['PATH_INFO'] == '/sessions/current/logout':
@@ -303,8 +306,10 @@ def _authorize_request(env, operation):
return ('logout',)
name, passphrase = base64.b64decode(
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
authdata = auth.check_user_passphrase(name, passphrase, element=None)
if not authdata:
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
if authdata is False:
return {'code': 403}
elif not authdata:
return {'code': 401}
sessid = util.randomstring(32)
while sessid in httpsessions:
@@ -341,15 +346,10 @@ def _authorize_request(env, operation):
if 'csrftoken' in httpsessions[sessid]:
authinfo['authtoken'] = httpsessions[sessid]['csrftoken']
return authinfo
else:
elif authdata is None:
return {'code': 401}
# TODO(jbjohnso): actually evaluate the request for authorization
# In theory, the x509 or http auth stuff will get translated and then
# passed on to the core authorization function in an appropriate form
# expresses return in the form of http code
# 401 if there is no known identity
# 403 if valid identity, but no access
# going to run 200 just to get going for now
else:
return {'code': 403}
def _pick_mimetype(env):
@@ -384,11 +384,11 @@ def resourcehandler(env, start_response):
try:
for rsp in resourcehandler_backend(env, start_response):
yield rsp
except:
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
start_response('500 - Internal Server Error', [])
yield '500 - Internal Server Error'
start_response('500 - ' + str(e), [])
yield '500 - ' + str(e)
return
@@ -429,7 +429,7 @@ def resourcehandler_backend(env, start_response):
return
if authorized['code'] == 403:
start_response('403 Forbidden', badauth)
yield 'authorization failed'
yield 'Forbidden'
return
if authorized['code'] != 200:
raise Exception("Unrecognized code from auth engine")
@@ -469,6 +469,10 @@ def resourcehandler_backend(env, start_response):
funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'],
authorized['sessionid'])
host = env['HTTP_X_FORWARDED_HOST']
if ']' in host:
host = host.split(']')[0] + ']'
elif ':' in host:
host = host.rsplit(':', 1)[0]
url = 'https://{0}:{1}/'.format(host, funport)
start_response('302', [('Location', url)])
yield 'Our princess is in another castle!'
@@ -790,7 +794,10 @@ def serve(bind_host, bind_port):
' a second\n')
eventlet.sleep(1)
# TCP_FASTOPEN
sock.setsockopt(socket.SOL_TCP, 23, 5)
try:
sock.setsockopt(socket.SOL_TCP, 23, 5)
except Exception:
pass # we gave it our best shot there
try:
eventlet.wsgi.server(sock, resourcehandler, log=False, log_output=False,
debug=False, socket_timeout=60)
+33 -22
View File
@@ -51,11 +51,15 @@
# - leading bit reserved, 0 for now
# - length of metadata record 7 bits
# - type of data referenced by this entry (one byte), currently:
# 0=text event, 1=json, 2=console data
# 0=text event, 1=json, 2=console data, 3=event
# - offset into the text log to begin (4 bytes)
# - length of data referenced by this entry (2 bytes)
# - UTC timestamp of this entry in seconds since epoch (unsigned 32 bit?)
# - CRC32 over the record
# - UTC timestamp of this entry in seconds since epoch (unsigned 32 bit)
# - Event type (per 'Events' class below)
# - Event data (per event, currently used by connect/disconnect to represent
# single or multiple connections by user and for 'appmode' and 'shiftin'
# status for console
# - 2 reserved bytes
# (a future extended version might include suport for Forward Secure Sealing
# or other fields)
@@ -576,28 +580,35 @@ class Logger(object):
textdate = time.strftime(
'%b %d %H:%M:%S ', time.localtime(tstamp))
flock(textfile, LOCK_EX)
offset = textfile.tell() + len(textdate)
datalen = len(data)
eventaux = entry[4]
if eventaux is None:
eventaux = 0
# metadata length is always 16 for this code at the moment
binrecord = struct.pack(
">BBIHIBBH", 16, ltype, offset, datalen, tstamp, evtdata,
eventaux, 0)
if self.isconsole:
if ltype == 2:
textrecord = data
try:
offset = textfile.tell() + len(textdate)
datalen = len(data)
eventaux = entry[4]
if eventaux is None:
eventaux = 0
# metadata length is always 16 for this code at the moment
binrecord = struct.pack(
">BBIHIBBH", 16, ltype, offset, datalen, tstamp, evtdata,
eventaux, 0)
if self.isconsole:
if ltype == 2:
textrecord = data
else:
textrecord = textdate + data + ']'
else:
textrecord = textdate + data + ']'
else:
textrecord = textdate + data
if not textrecord.endswith('\n'):
textrecord += '\n'
files = self.handler.try_emit(binrecord, textrecord)
textrecord = textdate + data
if not textrecord.endswith('\n'):
textrecord += '\n'
files = self.handler.try_emit(binrecord, textrecord)
except struct.error:
files = self.handler.doRollover(RollingTypes.size_rolling)
finally:
try:
flock(textfile, LOCK_UN)
except Exception:
pass
if not files:
self.handler.emit(binrecord, textrecord)
flock(textfile, LOCK_UN)
else:
# Log the rolling event at first, then log the last data
# which cause the rolling event.
+1 -1
View File
@@ -237,7 +237,7 @@ def run():
signal.signal(signal.SIGTERM, terminate)
collective.startup()
if dbgif:
oumask = os.umask(0077)
oumask = os.umask(0o077)
try:
os.remove('/var/run/confluent/dbg.sock')
except OSError:
+80 -22
View File
@@ -20,6 +20,7 @@
# format. This is also how different data formats are supported
import confluent.exceptions as exc
import confluent.config.configmanager as cfm
import confluent.config.conf as cfgfile
from copy import deepcopy
from datetime import datetime
import json
@@ -32,6 +33,15 @@ valid_health_values = set([
'unknown',
])
passcomplexity = cfgfile.get_option('policy', 'passwordminlength')
passminlength = cfgfile.get_option('policy', 'minpasswordlength')
if passminlength:
passminlength = int(passminlength)
if passcomplexity:
passcomplexity = int(passcomplexity)
else:
passcomplexity = 0
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
@@ -401,7 +411,7 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
return InputExpression(path, inputdata, nodes)
elif path == ['attributes', 'rename']:
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
elif path[0] in ('attributes', 'users', 'usergroups') and operation != 'retrieve':
return InputAttributes(path, inputdata, nodes)
elif path == ['boot', 'nextdevice'] and operation != 'retrieve':
return InputBootDevice(path, nodes, inputdata)
@@ -458,6 +468,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith('support/servicedata') and inputdata:
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith('configuration/management_controller/save_licenses') and inputdata:
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith(
'configuration/management_controller/licenses') and inputdata:
return InputLicense(path, nodes, inputdata, configmanager)
@@ -468,7 +480,7 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
class InputFirmwareUpdate(ConfluentMessage):
def __init__(self, path, nodes, inputdata, configmanager):
self._filename = inputdata.get('filename', inputdata.get('url', None))
self._filename = inputdata.get('filename', inputdata.get('url', inputdata.get('dirname', None)))
self.bank = inputdata.get('bank', None)
self.nodes = nodes
self.filebynode = {}
@@ -510,6 +522,10 @@ class Media(ConfluentMessage):
def __init__(self, node, media):
self.kvpairs = {node: {'name': media.name, 'url': media.url}}
class SavedFile(ConfluentMessage):
def __init__(self, node, file):
self.kvpairs = {node: {'filename': file}}
class InputAlertData(ConfluentMessage):
def __init__(self, path, inputdata, nodes=None):
@@ -662,13 +678,51 @@ class InputAttributes(ConfluentMessage):
)
return nodeattr
def checkPassword(password, username):
lowercase = set('abcdefghijklmnopqrstuvwxyz')
uppercase = set('abcdefghijklmnopqrstuvwxyz'.upper())
numbers = set('0123456789')
special = set('`~!@#$%^&*()-_=+[{]};:"/?.>,<' + "'")
if len(password) < passminlength:
raise exc.InvalidArgumentException('Password must be at least {0} characters long'.format(passminlength))
if not isinstance(passcomplexity, int) or passcomplexity < 1:
return
if not bool(set(password.lower()) & lowercase): # rule 1
raise exc.InvalidArgumentException('Password must contain at least one letter')
if passcomplexity < 2:
return
thepass = set(password)
if not bool(thepass & numbers): # rule 2
raise exc.InvalidArgumentException('Password must contain at least one number')
if passcomplexity < 3:
return
classes = 0
for charclass in (lowercase, uppercase, special):
if bool(thepass & charclass):
classes += 1
if classes < 2:
raise exc.InvalidArgumentException('Password must contain at least two of upper case letter, lower case letter, and/or special character')
if passcomplexity < 4:
return
if username and password in (username, username[::-1]): # rule 4
raise exc.InvalidArgumentException('Password must not be similar to username')
if passcomplexity < 5:
return
for char in thepass:
if char * 3 in password:
raise exc.InvalidArgumentException('Password must not contain any of the same character repeated 3 times')
class InputCredential(ConfluentMessage):
valid_privilege_levels = set([
'callback',
'user',
'ReadOnly',
'operator',
'Operator',
'administrator',
'Administrator',
'proprietary',
'no_access',
])
@@ -686,33 +740,21 @@ class InputCredential(ConfluentMessage):
if len(path) == 4:
inputdata['uid'] = path[-1]
# if the operation is 'create' check if all fields are present
missingattrs = []
for attrname in ('uid', 'privilege_level', 'username', 'password'):
if attrname not in inputdata:
missingattrs.append(attrname)
if missingattrs:
raise exc.InvalidArgumentException(
'Required fields missing: {0}'.format(','.join(missingattrs)))
if (isinstance(inputdata['uid'], str) and
not inputdata['uid'].isdigit()):
raise exc.InvalidArgumentException('uid must be a number')
inputdata['uid'] = inputdata['uid']
else:
inputdata['uid'] = int(inputdata['uid'])
if ('privilege_level' in inputdata and
inputdata['privilege_level'] not in self.valid_privilege_levels):
raise exc.InvalidArgumentException('privilege_level is not one of '
+ ','.join(self.valid_privilege_levels))
if 'username' in inputdata and len(inputdata['username']) > 16:
raise exc.InvalidArgumentException(
'name must be less than or = 16 chars')
if 'password' in inputdata and len(inputdata['password']) > 20:
raise exc.InvalidArgumentException('password has limit of 20 chars')
if ('enabled' in inputdata and
inputdata['enabled'] not in self.valid_enabled_values):
raise exc.InvalidArgumentException('valid values for enabled are '
+ 'yes and no')
if 'password' in inputdata and (passcomplexity or passminlength):
checkPassword(inputdata['password'], inputdata.get('username', None))
if nodes is None:
raise exc.InvalidArgumentException(
'This only supports per-node input')
@@ -809,6 +851,9 @@ class InputVolumes(ConfluentInputMessage):
sizes = inputdata.get('size', [None])
if not isinstance(sizes, list):
sizes = sizes.split(',')
stripsizes = inputdata.get('stripsizes', [None])
if not isinstance(stripsizes, list):
stripsizes = stripsizes.split(',')
disks = inputdata.get('disks', [])
if not disks:
raise exc.InvalidArgumentException(
@@ -820,8 +865,15 @@ class InputVolumes(ConfluentInputMessage):
currname = volnames.pop(0)
else:
currname = None
if stripsizes:
currstripsize = stripsizes.pop(0)
if currstripsize:
currstripsize = int(currstripsize)
else:
currstripsize = None
inputdata.append(
{'name': currname, 'size': size,
'stripsize': currstripsize,
'disks': disks,
'raidlevel': raidlvl})
for node in nodes:
@@ -843,6 +895,7 @@ class InputVolumes(ConfluentInputMessage):
self.inputbynode[node].append({'name': volname,
'size': volsize,
'disks': disks,
'stripsize': input.get('stripsize', None),
'raidlevel': raidlvl,
})
@@ -1192,6 +1245,7 @@ class EventCollection(ConfluentMessage):
'event': event.get('event', None),
'severity': event['severity'],
'timestamp': event.get('timestamp', None),
'message': event.get('message', None),
'record_id': event.get('record_id', None),
}
if event['severity'] not in valid_health_values:
@@ -1246,14 +1300,15 @@ class AsyncSession(ConfluentMessage):
self.kvpairs = {'asyncid': id}
class User(ConfluentMessage):
def __init__(self, uid, username, privilege_level, name=None):
def __init__(self, uid, username, privilege_level, name=None, expiration=None):
self.desc = 'foo'
self.stripped = False
self.notnode = name is None
kvpairs = {'username': {'value': username},
'password': {'value': '', 'type': 'password'},
'privilege_level': {'value': privilege_level},
'enabled': {'value': ''}
'enabled': {'value': ''},
'expiration': {'value': expiration},
}
if self.notnode:
self.kvpairs = kvpairs
@@ -1272,6 +1327,7 @@ class UserCollection(ConfluentMessage):
entry = {
'uid': user['uid'],
'username': user['name'],
'expiration': user.get('expiration', None),
'privilege_level': user['access']['privilege_level']
}
userlist.append(entry)
@@ -1403,12 +1459,13 @@ class Array(ConfluentMessage):
}
class Volume(ConfluentMessage):
def __init__(self, name, volname, size, state, array):
def __init__(self, name, volname, size, state, array, stripsize=None):
self.kvpairs = {
name: {
'type': 'volume',
'name': simplify_name(volname),
'label': volname,
'stripsize': stripsize,
'size': size,
'state': state,
'array': array,
@@ -1425,6 +1482,7 @@ class Disk(ConfluentMessage):
state_aliases = {
'unconfigured good': 'unconfigured',
'global hot spare': 'hotspare',
'dedicated hot spare': 'hotspare',
}
def _normalize_state(self, instate):
@@ -1602,12 +1660,12 @@ class NTPServer(ConfluentMessage):
class License(ConfluentMessage):
readonly = True
def __init__(self, name=None, kvm=None, feature=None):
def __init__(self, name=None, kvm=None, feature=None, state=None):
self.notnode = name is None
self.desc = 'License'
kv = []
kv.append({'kvm_availability': str(kvm), 'feature': feature})
kv.append({'kvm_availability': str(kvm), 'feature': feature, 'state': state})
if self.notnode:
self.kvpairs = {'License': kv}
else:
+13
View File
@@ -24,6 +24,19 @@ import eventlet.support.greendns
getaddrinfo = eventlet.support.greendns.getaddrinfo
def mask_to_cidr(mask):
maskn = socket.inet_pton(socket.AF_INET, mask)
maskn = struct.unpack('!I', maskn)[0]
cidr = 32
while maskn & 0b1 == 0 and cidr > 0:
cidr -= 1
maskn >>= 1
return cidr
def cidr_to_mask(cidr):
return socket.inet_ntop(
socket.AF_INET, struct.pack('!I', (2**32 - 1) ^ (2**(32 - cidr) - 1)))
def ip_on_same_subnet(first, second, prefix):
addrinf = socket.getaddrinfo(first, None, 0, socket.SOCK_STREAM)[0]
fam = addrinf[0]
@@ -36,6 +36,8 @@ console = eventlet.import_patched('pyghmi.ipmi.console')
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
import socket
import ssl
import traceback
if not hasattr(ssl, 'SSLEOFError'):
ssl.SSLEOFError = None
@@ -169,8 +171,10 @@ class IpmiCommandWrapper(ipmicommand.Command):
def __init__(self, node, cfm, **kwargs):
self.cfm = cfm
self.node = node
self.sensormap = {}
self._inhealth = False
self._lasthealth = None
kwargs['keepalive'] = False
self._attribwatcher = cfm.watch_attributes(
(node,), ('secret.hardwaremanagementuser', 'collective.manager',
'secret.hardwaremanagementpassword', 'secret.ipmikg',
@@ -229,7 +233,6 @@ def _ipmi_evtloop():
waiter = _ipmiwaiters.pop()
waiter.send()
except: # TODO(jbjohnso): log the trace into the log
import traceback
traceback.print_exc()
@@ -431,8 +434,8 @@ def perform_request(operator, node, element,
except pygexc.InvalidParameterValue as e:
results.put(msg.ConfluentNodeError(node, str(e)))
except Exception as e:
results.put(e)
raise
results.put(msg.ConfluentNodeError(node, 'Unexpected Error'))
traceback.print_exc()
finally:
results.put('Done')
@@ -441,7 +444,6 @@ persistent_ipmicmds = {}
class IpmiHandler(object):
def __init__(self, operation, node, element, cfd, inputdata, cfg, output,
realop):
self.sensormap = {}
self.invmap = {}
self.output = output
self.sensorcategory = None
@@ -460,11 +462,12 @@ class IpmiHandler(object):
self.inputdata = inputdata
self.tenant = cfg.tenant
tenant = cfg.tenant
if ((node, tenant) not in persistent_ipmicmds or
while ((node, tenant) not in persistent_ipmicmds or
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged or
persistent_ipmicmds[(node, tenant)].ipmi_session.broken):
try:
persistent_ipmicmds[(node, tenant)].close_confluent()
persistent_ipmicmds[(node, tenant)].ipmi_session._mark_broken()
except KeyError: # was no previous session
pass
try:
@@ -476,13 +479,17 @@ class IpmiHandler(object):
ipmisess = persistent_ipmicmds[(node, tenant)].ipmi_session
begin = util.monotonic_time()
while ((not (self.broken or self.loggedin)) and
while ((not (ipmisess.broken or self.loggedin)) and
(util.monotonic_time() - begin) < 30):
ipmisess.wait_for_rsp(31 - (util.monotonic_time() - begin))
if not (self.broken or self.loggedin):
ipmisess._mark_broken()
raise exc.TargetEndpointUnreachable(
"Login process to " + connparams['bmc'] + " died")
if self.broken or self.loggedin:
break
cfd = cfg.get_node_attributes(node, _configattributes, decrypt=True)
self.cfg = cfd[node]
connparams = get_conn_params(node, self.cfg)
ipmisess._mark_broken()
# raise exc.TargetEndpointUnreachable(
# "Login process to " + connparams['bmc'] + " died")
except socket.gaierror as ge:
if ge[0] == -2:
raise exc.TargetEndpointUnreachable(ge[1])
@@ -624,6 +631,8 @@ class IpmiHandler(object):
return self.handle_sysconfigclear()
elif self.element[1:3] == ['management_controller', 'licenses']:
return self.handle_licenses()
elif self.element[1:3] == ['management_controller', 'save_licenses']:
return self.save_licenses()
raise Exception('Not implemented')
def decode_alert(self):
@@ -740,6 +749,7 @@ class IpmiHandler(object):
uid=data['uid'],
username=data['name'],
privilege_level=data['access']['privilege_level'],
expiration=data['expiration'],
name=self.node))
return
elif self.op == 'update':
@@ -798,7 +808,7 @@ class IpmiHandler(object):
sensors = self.ipmicmd.get_sensor_descriptions()
for sensor in sensors:
resourcename = sensor['name']
self.sensormap[simplify_name(resourcename)] = resourcename
self.ipmicmd.sensormap[simplify_name(resourcename)] = resourcename
def read_sensors(self, sensorname):
if sensorname == 'all':
@@ -819,15 +829,16 @@ class IpmiHandler(object):
readings.append(reading)
self.output.put(msg.SensorReadings(readings, name=self.node))
else:
self.make_sensor_map()
if sensorname not in self.sensormap:
if sensorname not in self.ipmicmd.sensormap:
self.make_sensor_map()
if sensorname not in self.ipmicmd.sensormap:
self.output.put(
msg.ConfluentTargetNotFound(self.node,
'Sensor not found'))
return
try:
reading = self.ipmicmd.get_sensor_reading(
self.sensormap[sensorname])
self.ipmicmd.sensormap[sensorname])
if hasattr(reading, 'health'):
reading.health = _str_health(reading.health)
self.output.put(
@@ -958,8 +969,8 @@ class IpmiHandler(object):
if newinf.get('information', None) and 'name' in newinf['information']:
newinf = copy.deepcopy(newinf)
del newinf['information']['name']
if fnmatch(newinf['name'], 'Adapter ??:??:??') or fnmatch(
newinf['name'], 'PCIeGen? x*'):
if (fnmatch(newinf['name'], 'Adapter ??:??:??') or fnmatch(
newinf['name'], 'PCIeGen? x*') or not newinf['name']):
myinf = newinf.get('information', {})
sdid = myinf.get('PCI Subsystem Device ID', None)
svid = myinf.get('PCI Subsystem Vendor ID', None)
@@ -1036,7 +1047,7 @@ class IpmiHandler(object):
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']))
vols.append(storage.Volume(name=vol['name'], size=vol['size'], stripsize=vol['stripsize']))
newcfg = storage.ConfigSpec(
arrays=(storage.Array(raid=raidlvl, disks=disks, volumes=vols),))
self.ipmicmd.apply_storage_configuration(newcfg)
@@ -1124,6 +1135,11 @@ class IpmiHandler(object):
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, array='{0}-{1}'.format(*arr.id)))
for disk in arr.hotspares:
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)
@@ -1145,6 +1161,13 @@ class IpmiHandler(object):
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, arrname))
for disk in arr.hotspares:
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()
@@ -1153,6 +1176,8 @@ class IpmiHandler(object):
for arr in scfg.arrays:
for disk in arr.disks:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
for disk in arr.hotspares:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
def list_arrays(self):
scfg = self.ipmicmd.get_storage_configuration()
@@ -1173,6 +1198,8 @@ class IpmiHandler(object):
disks = []
for disk in arr.disks:
disks.append(simplify_name(disk.name))
for disk in arr.hotspares:
disks.append(simplify_name(disk.name))
self.output.put(msg.Array(self.node, disks, arr.raid,
vols, arrname, arr.capacity,
arr.available_capacity))
@@ -1351,9 +1378,13 @@ class IpmiHandler(object):
def handle_sysconfig(self, advanced=False):
if 'read' == self.op:
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration(
hideadvanced=not advanced)))
try:
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration(
hideadvanced=not advanced)))
except Exception as e:
self.output.put(
msg.ConfluentNodeError(self.node, str(e)))
elif 'update' == self.op:
self.ipmicmd.set_system_configuration(
self.inputdata.get_attributes(self.node))
@@ -1408,6 +1439,11 @@ class IpmiHandler(object):
self.output.put(msg.License(self.node, available))
return
def save_licenses(self):
directory = self.inputdata.nodefile(self.node)
for saved in self.ipmicmd.save_licenses(directory):
self.output.put(msg.SavedFile(self.node, saved))
def handle_licenses(self):
if self.element[-1] == '':
self.element = self.element[:-1]
@@ -1423,12 +1459,17 @@ class IpmiHandler(object):
licname = self.element[3]
if licname == 'all':
for lic in self.ipmicmd.get_licenses():
self.output.put(msg.License(self.node, feature=lic['name']))
if self.op == 'delete':
self.ipmicmd.delete_license(lic['name'])
else:
self.output.put(msg.License(self.node, feature=lic['name'], state=lic.get('state', 'Active')))
else:
index = int(licname)
lic = list(self.ipmicmd.get_licenses())[index - 1]
self.output.put(msg.License(self.node, feature=lic['name']))
if self.op == 'delete':
self.ipmicmd.delete_license(lic['name'])
else:
self.output.put(msg.License(self.node, feature=lic['name'], state=lic.get('state', 'Active')))
def handle_description(self):
dsc = self.ipmicmd.get_description()
self.output.put(msg.KeyValueData(dsc, self.node))
File diff suppressed because it is too large Load Diff
+9 -7
View File
@@ -120,13 +120,15 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
cfm = configmanager.ConfigManager(tenant=None, username=authname)
elif authname:
authdata = auth.authorize(authname, element=None)
if authdata is not None:
if authdata:
cfm = authdata[1]
authenticated = True
send_data(connection, "Confluent -- v0 --")
while not authenticated: # prompt for name and passphrase
send_data(connection, {'authpassed': 0})
response = tlvdata.recv(connection)
if not response:
return
if 'collective' in response:
return collective.handle_connection(connection, cert,
response['collective'])
@@ -143,7 +145,7 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
# element path, that authorization will need to be called
# per request the user makes
authdata = auth.check_user_passphrase(authname, passphrase)
if authdata is None:
if not authdata:
auditlog.log(
{'operation': 'connect', 'user': authname, 'allowed': False})
else:
@@ -180,11 +182,11 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
send_data(connection, {'_requestdone': 1})
except SystemExit:
sys.exit(0)
except:
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
send_data(connection, {'errorcode': 500,
'error': 'Unexpected error'})
'error': 'Unexpected error - ' + str(e)})
send_data(connection, {'_requestdone': 1})
request = tlvdata.recv(connection)
@@ -210,7 +212,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
}
if not skipauth:
authdata = auth.authorize(authdata[2], path, authdata[3], operation)
if authdata is None:
if not authdata:
auditmsg['allowed'] = False
auditlog.log(auditmsg)
raise exc.ForbiddenRequest()
@@ -311,12 +313,12 @@ def term_interact(authdata, authname, ccons, cfm, connection, consession,
try:
process_request(connection, data, cfm, authdata, authname,
skipauth)
except Exception:
except Exception as e:
tracelog.log(traceback.format_exc(),
ltype=log.DataTypes.event,
event=log.Events.stacktrace)
send_data(connection, {'errorcode': 500,
'error': 'Unexpected error'})
'error': 'Unexpected error - ' + str(e)})
send_data(connection, {'_requestdone': 1})
continue
if not data:
+40
View File
@@ -0,0 +1,40 @@
from ctypes import *
from ctypes.util import find_library
import grp
import pwd
import os
libc = cdll.LoadLibrary(find_library('libc'))
_getgrouplist = libc.getgrouplist
_getgrouplist.restype = c_int32
class TooSmallException(Exception):
def __init__(self, count):
self.count = count
super(TooSmallException, self).__init__()
def getgrouplist(name, gid, ng=32):
_getgrouplist.argtypes = [c_char_p, c_uint, POINTER(c_uint * ng), POINTER(c_int)]
glist = (c_uint * ng)()
nglist = c_int(ng)
count = _getgrouplist(name, gid, byref(glist), byref(nglist))
if count < 0:
raise TooSmallException(nglist.value)
for gidx in range(count):
gent = glist[gidx]
yield grp.getgrgid(gent).gr_name
def grouplist(username):
pent = pwd.getpwnam(username)
try:
groups = getgrouplist(pent.pw_name, pent.pw_gid)
except TooSmallException as e:
groups = getgrouplist(pent.pw_name, pent.pw_gid, e.count)
return list(groups)
if __name__ == '__main__':
import sys
print(repr(grouplist(sys.argv[1])))
+1 -1
View File
@@ -12,7 +12,7 @@ Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
Url: http://xcat.sf.net/
+76
View File
@@ -0,0 +1,76 @@
#include <termios.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define COM1 0x3f8
#define COM2 0x2f8
#define COM3 0x3e8
#define COM4 0x2e8
#define SPEEDNOOP 0
#define SPEED9600 3
#define SPEED19200 4
#define SPEED57600 6
#define SPEED115200 7
int main(int argc, char* argv[]) {
struct termios tty;
int ttyf;
int spcr;
int currspeed;
speed_t cspeed;
char buff[128];
uint64_t address;
spcr = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
if (spcr < 0) {
exit(0);
}
if (read(spcr, buff, 80) < 80) {
exit(0);
}
if (buff[8] != 2) exit(0); //revision 2
if (buff[36] != 0) exit(0); //16550 only
if (buff[40] != 1) exit(0); //IO only
address = *(uint64_t *)(buff + 44);
currspeed = buff[58];
if (address == COM1) {
strncpy(buff, "/dev/ttyS0", 128);
} else if (address == COM2) {
strncpy(buff, "/dev/ttyS1", 128);
} else if (address == COM3) {
strncpy(buff, "/dev/ttyS2", 128);
} else if (address == COM4) {
strncpy(buff, "/dev/ttyS3", 128);
} else {
exit(0);
}
if (currspeed == SPEED9600) {
cspeed = B9600;
} else if (currspeed == SPEED19200) {
cspeed = B19200;
} else if (currspeed == SPEED57600) {
cspeed = B57600;
} else if (currspeed == SPEED115200) {
cspeed = B115200;
} else if (currspeed == SPEED115200) {
cspeed = 0;
} else {
exit(0);
}
printf("%s\n", buff);
ttyf = open(buff, O_RDWR | O_NOCTTY);
tcgetattr(ttyf, &tty);
if (cspeed) {
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
}
tcsetattr(ttyf, TCSANOW, &tty);
ioctl(ttyf, TIOCCONS, 0);
}
+90
View File
@@ -0,0 +1,90 @@
import fcntl
import os
import signal
import struct
import subprocess
import termios
import time
addrtoname = {
0x3f8: '/dev/ttyS0',
0x2f8: '/dev/ttyS1',
0x3e8: '/dev/ttyS2',
0x2e8: '/dev/ttyS3',
}
speedmap = {
0: None,
3: 9600,
4: 19200,
6: 57600,
7: 115200,
}
termiobaud = {
9600: termios.B9600,
19200: termios.B19200,
57600: termios.B57600,
115200: termios.B115200,
}
def do_serial_config():
if 'console=ttyS' in open('/proc/cmdline').read():
return None # Do not do autoconsole if manually configured
spcr = open("/sys/firmware/acpi/tables/SPCR", "rb")
spcr = bytearray(spcr.read())
if spcr[8] != 2 or spcr[36] != 0 or spcr[40] != 1:
return None
address = struct.unpack('<Q', spcr[44:52])[0]
tty = None
try:
tty = addrtoname[address]
except KeyError:
return None
retval = { 'tty': tty }
try:
retval['speed'] = speedmap[spcr[58]]
except KeyError:
return None
if retval['speed']:
ttyf = os.open(tty, os.O_RDWR | os.O_NOCTTY)
currattr = termios.tcgetattr(ttyf)
currattr[4:6] = [0, termiobaud[retval['speed']]]
termios.tcsetattr(ttyf, termios.TCSANOW, currattr)
retval['connected'] = bool(struct.unpack('<I', fcntl.ioctl(
ttyf, termios.TIOCMGET, '\x00\x00\x00\x00'))[0] & termios.TIOCM_CAR)
os.close(ttyf)
return retval
def is_connected(tty):
ttyf = os.open(tty, os.O_RDWR | os.O_NOCTTY)
retval = bool(struct.unpack('<I', fcntl.ioctl(
ttyf, termios.TIOCMGET, '\x00\x00\x00\x00'))[0] & termios.TIOCM_CAR)
os.close(ttyf)
return retval
if __name__ == '__main__':
serialinfo = do_serial_config()
if serialinfo:
running = False
while True:
if running and running.poll() is not None:
running = False
if running and not is_connected(serialinfo['tty']):
try:
running.terminate()
running.wait()
except Exception:
pass
time.sleep(0.5)
running = subprocess.Popen(['/bin/sh', '-c', 'exec screen -x console <> {0} >&0 2>&1'.format(serialinfo['tty'])])
time.sleep(0.5)
try:
running.terminate()
running.wait()
except Exception:
pass
running = False
elif not running and is_connected(serialinfo['tty']):
running = subprocess.Popen(['/bin/sh', '-c', 'exec screen -x console <> {0} >&0 2>&1'.format(serialinfo['tty'])])
time.sleep(0.5)
+167
View File
@@ -0,0 +1,167 @@
#include <dirent.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if_arp.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <net/if.h>
int add_uuid(char* destination, int maxsize) {
int uuidf;
int uuidsize;
uuidf = open("/sys/devices/virtual/dmi/id/product_uuid", O_RDONLY);
if (uuidf < 1) { return 0; }
strncpy(destination, "/uuid=", maxsize);
uuidsize = read(uuidf, destination + 6, maxsize - 6);
close(uuidf);
if (destination[uuidsize + 5] == '\n') {
destination[uuidsize + 5 ] = 0;
}
return uuidsize + 6;
}
int add_macs(char* destination, int maxsize) {
struct ifaddrs *ifc, *ifa;
struct sockaddr_ll *lla;
int offset;
char macaddr[32];
offset = 0;
getifaddrs(&ifa);
for (ifc = ifa; ifc != NULL; ifc = ifc->ifa_next) {
if (ifc->ifa_addr->sa_family != AF_PACKET)
continue;
lla = (struct sockaddr_ll *)ifc->ifa_addr;
if (lla->sll_hatype == ARPHRD_INFINIBAND) {
snprintf(macaddr, 32, "/mac=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
lla->sll_addr[12], lla->sll_addr[13], lla->sll_addr[14],
lla->sll_addr[15], lla->sll_addr[16], lla->sll_addr[17],
lla->sll_addr[18], lla->sll_addr[19]
);
} else if (lla->sll_hatype == ARPHRD_ETHER) {
snprintf(macaddr, 32, "/mac=%02x:%02x:%02x:%02x:%02x:%02x",
lla->sll_addr[0], lla->sll_addr[1], lla->sll_addr[2],
lla->sll_addr[3], lla->sll_addr[4], lla->sll_addr[5],
lla->sll_addr[6]
);
} else {
continue;
}
strncpy(destination + offset, macaddr, maxsize - offset);
offset += strnlen(macaddr, 32);
}
freeifaddrs(ifa);
}
int main(int argc, char* argv[]) {
struct ifaddrs *ifc, *ifa;
struct sockaddr_in6 *in6;
struct sockaddr_in *in, *bin;
int ns, n4;
struct sockaddr_in6 addr, dst;
struct sockaddr_in addr4, dst4;
char msg[1024];
char lastmsg[1024];
int ifidx, offset;
fd_set rfds;
struct timeval tv;
socklen_t dstsize, dst4size;
dstsize = sizeof(dst);
dst4size = sizeof(dst4);
memset(msg, 0, 1024);
memset(&addr, 0, sizeof(addr));
memset(&dst, 0, sizeof(dst));
memset(&dst4, 0, sizeof(dst4));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = htons(190);
addr4.sin_family = AF_INET;
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
addr4.sin_port = htons(190);
dst.sin6_family = AF_INET6;
dst.sin6_port = htons(1900);
inet_pton(AF_INET6, "ff02::c", &dst.sin6_addr);
dst4.sin_family = AF_INET;
dst4.sin_port = htons(1900);
inet_pton(AF_INET, "239.255.255.250", &dst4.sin_addr);
strncpy(msg, "M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:", 1024);
offset = strnlen(msg, 1024);
if (argc > 1) {
snprintf(msg + offset, 1024 - offset, "/node=%s", argv[1]);
offset = strnlen(msg, 1024);
}
add_uuid(msg + offset, 1024 - offset);
offset = strnlen(msg, 1024);
add_macs(msg + offset, 1024 - offset);
offset = strnlen(msg, 1024);
ns = socket(AF_INET6, SOCK_DGRAM, 0);
n4 = socket(AF_INET, SOCK_DGRAM, 0);
ifidx = 1; /* reuse ifidx because it's an unused int here */
setsockopt(n4, SOL_SOCKET, SO_BROADCAST, &ifidx, sizeof(ifidx));
setsockopt(ns, IPPROTO_IPV6, IPV6_V6ONLY, &ifidx, sizeof(ifidx));
/* For now, bind to 190 to prove we are a privileged process */
bind(n4, (const struct sockaddr *)&addr4, sizeof(addr4));
bind(ns, (const struct sockaddr *)&addr, sizeof(addr));
getifaddrs(&ifa);
for (ifc = ifa; ifc != NULL; ifc = ifc->ifa_next) {
if (!ifc->ifa_addr) continue;
if (ifc->ifa_flags & IFF_LOOPBACK) continue;
if (ifc->ifa_flags & IFF_MULTICAST != IFF_MULTICAST) continue;
if (ifc->ifa_addr->sa_family == AF_INET6) {
in6 = (struct sockaddr_in6 *)ifc->ifa_addr;
if (in6->sin6_scope_id == 0)
continue;
ifidx = in6->sin6_scope_id;
setsockopt(ns, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifidx, sizeof(ifidx));
sendto(ns, msg, strnlen(msg, 1024), 0, (const struct sockaddr *)&dst, sizeof(dst));
} else if (ifc->ifa_addr->sa_family == AF_INET) {
in = (struct sockaddr_in *)ifc->ifa_addr;
bin = (struct sockaddr_in *)ifc->ifa_ifu.ifu_broadaddr;
bin->sin_port = htons(1900);
setsockopt(n4, IPPROTO_IP, IP_MULTICAST_IF, &in->sin_addr, sizeof(in->sin_addr));
sendto(n4, msg, strnlen(msg, 1024), 0, (const struct sockaddr *)&dst4, sizeof(dst4));
sendto(n4, msg, strnlen(msg, 1024), 0, (const struct sockaddr *)bin, sizeof(*bin));
}
}
FD_ZERO(&rfds);
FD_SET(n4, &rfds);
FD_SET(ns, &rfds);
tv.tv_sec = 10;
tv.tv_usec = 0;
ifidx = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
while (ifidx) {
if (ifidx == -1) perror("Unable to select");
if (ifidx) {
if (FD_ISSET(n4, &rfds)) {
recvfrom(n4, msg, 1024, 0, (struct sockaddr *)&dst4, &dst4size);
inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size);
/* Take measure from printing out the same ip twice in a row */
if (strncmp(lastmsg, msg, 1024) != 0) {
printf("%s\n", msg);
strncpy(lastmsg, msg, 1024);
}
}
if (FD_ISSET(ns, &rfds)) {
recvfrom(ns, msg, 1024, 0, (struct sockaddr *)&dst, &dstsize);
inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize);
if (strncmp(lastmsg, msg, 1024) != 0) {
printf("%s\n", msg);
strncpy(lastmsg, msg, 1024);
}
}
}
tv.tv_sec = 0;
tv.tv_usec = 500000;
FD_SET(n4, &rfds);
FD_SET(ns, &rfds);
ifidx = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
}
}
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env python
import pyghmi.util.webclient as webclient
import json
import os
import sys
missingargs = False
if 'XCCUSER' not in os.environ:
print('Must set XCCUSER environment variable')
missingargs = True
if 'XCCPASS' not in os.environ:
print('Must set XCCPASS environment variable')
missingargs = True
if missingargs:
sys.exit(1)
w = webclient.SecureHTTPConnection(sys.argv[1], 443, verifycallback=lambda x: True)
w.connect()
adata = json.dumps({'username': os.environ['XCCUSER'], 'password': os.environ['XCCPASS']})
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'}
w.request('POST', '/api/login', adata, headers)
rsp = w.getresponse()
if rsp.status == 200:
rspdata = json.loads(rsp.read())
w.set_header('Content-Type', 'application/json')
w.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
if '_csrf_token' in w.cookies:
w.set_header('X-XSRF-TOKEN', w.cookies['_csrf_token'])
print(repr(w.grab_json_response('/api/dataset', {
'USER_GlobalPassComplexRequired': '0',
})))
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python
import pyghmi.util.webclient as webclient
import json
import os
import sys
tmppassword = 'to3BdS91ABrd'
missingargs = False
if 'XCCUSER' not in os.environ:
print('Must set XCCUSER environment variable')
missingargs = True
if 'XCCPASS' not in os.environ:
print('Must set XCCPASS environment variable')
missingargs = True
if missingargs:
sys.exit(1)
w = webclient.SecureHTTPConnection(sys.argv[1], 443, verifycallback=lambda x: True)
w.connect()
adata = json.dumps({'username': os.environ['XCCUSER'], 'password': os.environ['XCCPASS']})
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'}
w.request('POST', '/api/login', adata, headers)
rsp = w.getresponse()
if rsp.status != 200:
rsp.read()
adata = json.dumps({'username': os.environ['XCCUSER'], 'password': tmppassword})
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'}
w.request('POST', '/api/login', adata, headers)
rsp = w.getresponse()
if rsp.status == 200:
rspdata = json.loads(rsp.read())
w.set_header('Content-Type', 'application/json')
w.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
if '_csrf_token' in w.cookies:
w.set_header('X-XSRF-TOKEN', w.cookies['_csrf_token'])
if rspdata.get('pwchg_required', False):
print(repr(w.grab_json_response('/api/function', {'USER_UserPassChange': '1,to3BdS91ABrd'})))
print(repr(w.grab_json_response('/api/dataset', {
'USER_GlobalPassExpWarningPeriod': '0',
'USER_GlobalPassExpPeriod': '0',
'USER_GlobalMinPassReuseCycle': '0',
'USER_GlobalMinPassReuseCycle': '0',
'USER_GlobalMinPassChgInt': '0',
})))
print(repr(w.grab_json_response('/api/function', {'USER_UserPassChange': '1,' + os.environ['XCCPASS']})))
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python
import pyghmi.util.webclient as webclient
from xml.etree.ElementTree import fromstring
import os
import sys
tmppassword = 'to3BdS91ABrd'
missingargs = False
if 'SMMUSER' not in os.environ:
print('Must set SMMUSER environment variable')
missingargs = True
if 'SMMPASS' not in os.environ:
print('Must set SMMPASS environment variable')
missingargs = True
if missingargs:
sys.exit(1)
w = webclient.SecureHTTPConnection(sys.argv[1], 443, verifycallback=lambda x: True)
w.connect()
adata = 'user={0}&password={1}'.format(os.environ['SMMUSER'], os.environ['SMMPASS'])
bdata = 'user={0}&password={1}'.format(os.environ['SMMUSER'], tmppassword)
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
w.request('POST', '/data/login', adata, headers)
rsp = w.getresponse()
rspdata = rsp.read()
restorepwd = False
if 'authResult>1' in rspdata:
restorepwd = True
w.request('POST', '/data/login', bdata, headers)
rsp = w.getresponse()
rspdata = rsp.read()
if 'renew_account' in rspdata:
restorepwd = True
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
w.set_header('ST2', st2)
w.request('POST', '/data/changepwd', 'oripwd={0}&newpwd={1}'.format(os.environ['SMMPASS'], tmppassword))
rsp = w.getresponse()
rspdata = rsp.read()
w.request('POST', '/data/login', bdata, headers)
rsp = w.getresponse()
rspdata = rsp.read()
if 'authResult>0' in rspdata:
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
w.set_header('ST2', st2)
rules = 'set=passwordDurationDays:0,passwordExpireWarningDays:0,passwordChangeInterval:0'
w.request('POST', '/data', rules)
rsp = w.getresponse()
print(repr(rsp.read()))
if restorepwd:
w.request('POST', '/data/changepwd', 'oripwd={1}&newpwd={0}'.format(os.environ['SMMPASS'], tmppassword))
rsp = w.getresponse()
print(repr(rsp.read()))
+199
View File
@@ -0,0 +1,199 @@
#!/usr/bin/python
import collections
import os
import struct
import sys
import time
import fcntl
import select
import termios
import tty
def writeout(data):
done = False
try:
sys.stdout.write(data)
done = True
except IOError:
time.sleep(0.1)
pass
class LogReplay(object):
def __init__(self, logfile, cblfile):
self.bin = open(cblfile, 'r')
self.txt = open(logfile, 'r')
self.cleardata = []
self.clearidx = 0
self.pendingdata = collections.deque([])
self.priordata = collections.deque([])
self.laststamp = None
self.needclear = False
def _rewind(self, datasize=None):
curroffset = self.bin.tell() - 16
if self.cleardata and self.clearidx > 1:
self.clearidx -= 1
priordata = self.cleardata[self.clearidx - 1]
return curroffset, priordata
self.cleardata = []
self.clearidx = 0
newoffset = curroffset - 32
if newoffset < 0: #TODO: Follow a log roll
newoffset = 0
if datasize:
while datasize > 0 and newoffset > 0:
self.bin.seek(newoffset)
tmprec = self.bin.read(16)
newoffset -= 32
tmprec = struct.unpack('!BBIHIBBH', tmprec)
if tmprec[1] == 2:
datasize -= tmprec[3]
if newoffset >= 0:
self.bin.seek(newoffset)
return curroffset, None
def debuginfo(self):
return '{0}, {1}'.format(self.bin.tell(), self.clearidx)
def get_output(self, reverse=False):
endoffset = None
output = ''
if reverse: # Forget the uncommited future, if present
output += '\x1b[2J\x1b[H'
endoffset, priordata = self._rewind(4096)
if priordata is not None:
return priordata, 1
elif self.needclear:
output += '\x1b[2J\x1b[H'
self.needclear = False
if self.cleardata and self.clearidx < len(self.cleardata):
datachunk = self.cleardata[self.clearidx]
self.clearidx += 1
return datachunk, 1
self.cleardata = []
self.clearidx = 0
while (not reverse) or (self.bin.tell() < endoffset):
record = self.bin.read(16)
if not record:
return '', 0
record = struct.unpack('!BBIHIBBH', record)
if record[0] > 16:
# Unsupported record, skip
self.bin.seek(record[0] - 16, 1)
continue
type = record[1]
offset = record[2]
size = record[3]
evtdata = record[5]
auxdata = record[6]
if type == 3:
#TODO: provide data for status bar
continue
elif type == 2:
self.laststamp = record[4]
self.txt.seek(offset)
txtout = self.txt.read(size)
if reverse and self.bin.tell() < endoffset:
output += txtout
continue
if '\x1b[2J' in txtout:
self.cleardata = txtout.split('\x1b[2J')
for idx in range(1, len(self.cleardata)):
self.cleardata[idx] = '\x1b[2J' + self.cleardata[idx]
self.clearidx = 0
if not self.cleardata[0]:
self.cleardata = self.cleardata[1:]
if self.cleardata:
if reverse:
output = self.cleardata[-1]
self.clearidx = len(self.cleardata)
else:
output += self.cleardata[0]
self.clearidx = 1
else:
output += txtout
break
if endoffset is not None and endoffset >= 0:
self.bin.seek(endoffset)
return output, 1
def begin(self):
self.needclear = True
self.bin.seek(0)
def end(self):
self.bin.seek(0, 2)
def main(txtfile, binfile):
replay = LogReplay(txtfile, binfile)
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
tty.setraw(sys.stdin.fileno())
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
reverse = False
skipnext = False
quitit = False
writeout('\x1b[2J\x1b[;H')
try:
while not quitit:
if not skipnext:
newdata, delay = replay.get_output(reverse)
skipnext = False
reverse = False
if newdata:
writeout(newdata)
writeout('\x1b]0;[Time: {0}]\x07'.format(
time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))))
sys.stdout.flush()
while True:
select.select((sys.stdin,), (), (), 86400)
myinput = sys.stdin.read()
if myinput.startswith('\x1b[C') or myinput.startswith('\x1bOC') or myinput == '\r': # right
break
elif myinput.startswith('\x1b[D') or myinput.startswith('\x1bOD') or myinput == 'y': # left
writeout('\x1b[2J\x1b[;H')
reverse = True
break
elif myinput == 'G' or myinput.startswith('\x1b[F'):
replay.end()
reverse = True
break
elif myinput == 'g' or myinput.startswith('\x1b[H'):
replay.begin()
break
elif myinput.lower() == 'q' or myinput == '\x03':
quitit = True
break
elif myinput.lower() == 'd':
writeout('\x1b];{0}\x07'.format(replay.debuginfo()))
sys.stdout.flush()
else:
pass # print(repr(myinput))
except Exception:
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
writeout('\x1b[m')
raise
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
writeout('\x1b[m')
if __name__ == '__main__':
txtfile = sys.argv[1]
if len(sys.argv) > 2:
binfile = sys.argv[2]
else:
if os.path.exists(txtfile + '.cbl'):
binfile = txtfile + '.cbl'
else:
fileparts = txtfile.split('.')
prefix = '.'.join(fileparts[:-1])
binfile = prefix + '.cbl.' + fileparts[-1]
if not os.path.exists(binfile):
sys.stderr.write('Unable to locate cbl file\n')
sys.exit(1)
main(txtfile, binfile)
-37
View File
@@ -1,37 +0,0 @@
#!/usr/bin/python
import numpy as np
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import os
import sixel
import subprocess
import sys
import tempfile
class DumbWriter(sixel.SixelWriter):
def restore_position(self, output):
return
plotdata = []
data = sys.stdin.readline()
while data:
datum = float(data.split()[-1])
plotdata.append(datum)
data = sys.stdin.readline()
n, bins, patches = plt.hist(plotdata, 20)
plt.show()
path = tempfile.mkdtemp()
plt.savefig('{0}/histo.png'.format(path))
writer = DumbWriter()
writer.draw('{0}/histo.png'.format(path))
print('Min: {3} Median: {0} Mean: {1} Max: {4} Standard Deviation: {2} Samples: {5}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata)))
os.remove('{0}/histo.png'.format(path))
os.rmdir(path)