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

Compare commits

..

307 Commits

Author SHA1 Message Date
Jarrod Johnson da400b11d0 Temporarily back out check_power_state
It blocks ctrl-e mysteriously.
2017-06-14 09:21:08 -04:00
Jarrod Johnson d9042e5d4c Support newer pyte
pyte changed the buffer format, adjust strategy to be compatible both with new and old.
2017-06-13 09:06:43 -04:00
Jarrod Johnson e443aa6530 Fix line wrap in nodeattrib man page 2017-06-12 17:20:32 -04:00
Jarrod Johnson 8993f80b13 Update synopsis of the nodeattrib man page 2017-06-12 16:50:06 -04:00
Jarrod Johnson 8e00f3a795 Fix up nodeattrib usage message 2017-06-12 16:45:53 -04:00
Jarrod Johnson 1f6fa7636b Fix synopsis for nodeattrib -c 2017-06-12 16:10:02 -04:00
Jarrod Johnson d86bacf4d8 Add man pages for confluent and confluentdbutil 2017-06-12 14:58:34 -04:00
Jarrod Johnson 7230ec1306 Change nodelist usage message per test request 2017-06-12 14:40:04 -04:00
Jarrod Johnson 00d5416737 Merge branch 'discovery' of hpcgitlab.labs.lenovo.com:jjohnson2/confluent into discovery 2017-06-08 10:08:03 -04:00
Jarrod Johnson b22073e106 Limit children of noderun/nodeshell
Implement throttling of concurrent processes.  While 64 might be a bit conservative in this day and
age, we were exceeding FD_SETSIZE, and maybe we were also lucky.  Throttle to 168 for the moment.
2017-06-08 10:05:24 -04:00
Jarrod Johnson 03369c5d5e Fix handling of prefix length calculation
It was too naive about the mask, endiannes swaps bytes, not bits.
Fix by converting everytihng to big endian, where the mask is
guaranteed to be contiguous set of bits.
2017-06-05 09:21:58 -04:00
Jarrod Johnson 0b30b92bc9 Use default PIPE handling
Python overrides PIPE handling with silly behaviors, have the confluent commands restore
system defaults rather than Python behavior.
2017-06-01 09:03:15 -04:00
Jarrod Johnson dd90af1414 Revert "Implement Windows compatibility for slp, maybe ssdp"
This reverts commit 9e13724037.
2017-06-01 08:55:28 -04:00
Jarrod Johnson 9e13724037 Implement Windows compatibility for slp, maybe ssdp
Windows was unable to execute slp.  The use of inet_pton makes 'snoop'
impossible still, but perhaps not needed in a windows utility form...
2017-05-31 16:57:55 -04:00
Jarrod Johnson bbe839ea5d Fix noderun and nodeshell on long commands
Before it required 'rdy' to come back, and give up if empty.  Really we care about
all exclusively.
2017-05-31 16:26:55 -04:00
Jarrod Johnson ff7bdcc16e Avoid corrupting the terminal session on out of band issues
For out of band issues, keep the terminal state active.
2017-05-31 13:19:19 -04:00
Jarrod Johnson 6cd961786d Handle a blank result for bridge to ifindex
If blank, ignore and continue, rather than generating a trace.
2017-05-31 11:55:30 -04:00
Jarrod Johnson 18fa8f0b41 Do not generate trace on bridge without interface
Bridge ports can exist without a physical port.  Skip over such a port rather than error.
2017-05-31 11:52:46 -04:00
Jarrod Johnson 6e5375918d Fix unexpected error on node deletion
Node deletion was incorrectly trying to react to '_attrgrobs'.  Fix the
behavior to ignore it.
2017-05-31 08:53:54 -04:00
Jarrod Johnson 046c1e8ec4 Clean up periodic recheck scheduling
Observed that a number of concurrent rechecks were happening.  Do some clean
up in the hopes of straightening out the situation.
2017-05-30 14:06:31 -04:00
Jarrod Johnson eb48f2c1f7 Apply fingerprint match logic on recheck
On scan/snoop, we were ignoring if certificate already matched up.
Bring this logic to the recheck logic, so that things behave consistently.
2017-05-30 10:50:31 -04:00
Jarrod Johnson d25ac000f4 Have by-node get natural sorted 2017-05-30 09:18:11 -04:00
Jarrod Johnson a47bdf4181 Put discovery in order
Provide alphabetic ordering for most elements.  A TODO would be natural
sort at least for nodenames
2017-05-30 09:07:47 -04:00
Jarrod Johnson caf41345a1 Add 'by-state' to discovery
Provides a convenient method to focus on unidentified nodes.

Also, alphabatize the mac addresses on return.
2017-05-30 08:55:22 -04:00
Jarrod Johnson 3d6a1881c8 Fix [] inside regex
Regex uses [], support it in a normal way
2017-05-26 15:23:43 -04:00
Jarrod Johnson 5083977664 Rework node range evaluation priority
= and ~ operatiors would work incorrectly with '-', move filter and name regex up in priority
2017-05-26 15:06:52 -04:00
Jarrod Johnson 53821d98e6 Fix errors when switch and/or switchport are not set
It was nagging about conflicts, when not actually set.  Treat ''
as being the same as not setting at all.
2017-05-26 13:55:54 -04:00
Jarrod Johnson fdb8ef6919 Fix typoes in man page for nodeconsole 2017-05-26 13:20:34 -04:00
Jarrod Johnson b08bf6ae97 Be less aggressive about scanning interval
If there are slow switches in the mix, do our best to ensure solid
30 second interval of peace and quiet between scans, rather than a
scanning window per 30 seconds.
2017-05-26 12:52:42 -04:00
Jarrod Johnson 3318b3cda7 Ignore certificate check when manual
When doing manual discovery, repeat the process regardless of
certificate fingerprint state, since manual process is supposed
to do that.
2017-05-26 08:32:28 -04:00
Jarrod Johnson e97f768170 Fix format string of log message
The message was missing a close parenthesis.
2017-05-25 16:30:09 -04:00
Jarrod Johnson 9eacb4e3f0 Prioritize community if set
If the community is set, ignore user/password.  This fits expectations
a bit more.
2017-05-25 15:02:08 -04:00
Jarrod Johnson 77bce756cd Support secret.snmpcommunity
Also fix error where stale by-port data was left on a rescan.
2017-05-25 14:33:50 -04:00
Jarrod Johnson 2decaac8ea Be more tolerant on auto user deletion
Life is not perfect, we may not be able to really clean unexpected
accounts.
2017-05-23 16:14:32 -04:00
Jarrod Johnson e0a62a2533 Provide cleaner log when config fails
For now, provide a catch all to get some indication of problem,
rather than silent failure.
2017-05-23 10:53:54 -04:00
Jarrod Johnson f8d2fd8f46 Move up 'known_nodes' assignment
known_nodes is not some structure that was meant to be protected.
When an unexpected situation comes up (such as config failing), we
were losing track of the discovery info.
2017-05-23 10:44:15 -04:00
Jarrod Johnson 7f18f5fde0 Fix attrib based rediscovery
The wrong dictionary was offered up for manipulation.
2017-05-23 10:15:04 -04:00
Jarrod Johnson f07371c756 Add a python dbg cli, to use the dbg.sock without nc and with up arrow 2017-05-22 16:55:25 -04:00
Jarrod Johnson 179fcd9dcf Fix incorrect message in the login process failure message. 2017-05-22 14:28:52 -04:00
Jarrod Johnson e79a5ecd12 Merge branch 'discovery' of hpcgitlab.labs.lenovo.com:jjohnson2/confluent into discovery 2017-05-22 14:17:55 -04:00
Jarrod Johnson 2faf187d2d Use standard nodepawer usage on bad argument 2017-05-22 14:17:35 -04:00
Jarrod Johnson 703d8f273f Provide a script to summarize hangtraces files 2017-05-22 09:24:58 -04:00
Jarrod Johnson db0bc41441 Sort port entries in a show mac info
This means that the most interesting entry is probably the last.
2017-05-20 18:46:33 -04:00
Jarrod Johnson 97effaae01 Modify networking macs api to be more extendable and consistent
When we get down to a switch, make the macs 'by-mac', since 'by-vlan' is a likely future extension.
2017-05-20 18:07:35 -04:00
Jarrod Johnson 0a5424fbb2 Fix scope of module global dictionaries
In the refactoring, accidentally kept the map information
incorrectly scoped to be disposed of.
2017-05-20 17:41:59 -04:00
Jarrod Johnson 3571947332 Change names of portions of the API 2017-05-20 17:39:30 -04:00
Jarrod Johnson 72289f89cb Make sure switch update sweep completes in background
Switch scan was interrupted in the event of a successful scan.
Change to background execution on generatorexit.
2017-05-20 11:04:47 -04:00
Jarrod Johnson bf90431101 Implement no-qbridge mode and workaround for switch mistake
Sometimes switches erroneously use interface index instead of bridge index
in the bridge mib.  Workaround by assuming this is probably the case if
none of the macs seem to map to an interface.  Note that it's still important
to accept that a bridge port might not have an interface (internal mac address)
so only make the assumption if *none* of the mac addresses mapped to an interface.
2017-05-20 10:11:44 -04:00
Jarrod Johnson 784758fa25 Tweak documentation
For synopsis, remove optional indication.
For usage, change to common terminology with synopsis.
2017-05-17 15:19:22 -04:00
Jarrod Johnson 6c5536c840 Implement manual discovery
/discovery/by-mac/ -> set 40-f2-e9-af-45-a4 node=r1

Allow manual assignment as if it were detected through switch or chassis
2017-05-17 14:25:46 -04:00
Jarrod Johnson 64ebc9b16f Have by-type and by-model filter each other
There was too much misleading output when combining them.
2017-05-17 11:49:50 -04:00
Jarrod Johnson 213b027f62 Report unavaliable sensors as unavailable rather than timeout 2017-05-17 11:16:27 -04:00
Jarrod Johnson e9ddbb8785 Rework discovery api
The code is much cleaner, much less repitition.
2017-05-16 17:18:23 -04:00
Jarrod Johnson 28eb1180c7 Only join the thread if it is there 2017-05-15 11:36:50 -04:00
Jarrod Johnson 892d01d054 Fix use of WindowsError
OSError is the base class, use that instead of WindowsError
since Linux python is befuddled.
2017-05-15 11:34:11 -04:00
Jarrod Johnson 949609ce64 Be picky about the input message
set rescan=start

Is the valid argument
2017-05-15 11:02:53 -04:00
Jarrod Johnson 21b99728d7 Be picky about where rescan is called, just in case 2017-05-15 10:59:41 -04:00
Jarrod Johnson 79b4482298 Provide rescan functionality for network macmap 2017-05-15 10:57:24 -04:00
Jarrod Johnson 40f700c46d Provide SLP rescan
Create /discovery/rescan resource, and hook in an SLP rescan.
2017-05-15 10:16:54 -04:00
Jarrod Johnson 652d9c0a55 Support PXE based discovery
PXE can't do https, so core needed a fair amount of work
to deal with handler without https (or any ipaddr for that matter).
2017-05-12 16:46:45 -04:00
Jarrod Johnson 20d7eb62f4 Support gateway setting in bmc discovery 2017-05-12 09:55:10 -04:00
Jarrod Johnson 34623d5bbd Document plans for medium term PXE discovery
Handling of getting the mac address into the attributes accurately
is complicated, document the design point for future.
2017-05-12 08:32:57 -04:00
Jarrod Johnson 0e7072a256 Fix support for ports with hyphens in the name 2017-05-11 16:48:54 -04:00
Jarrod Johnson 298b9bcfff Add net.ipv4_gateway attribute and function to fetch config
Provide a command to implement common extraction of nic configuration
from the attributes, so that code can be extended/manipulated in a
common place.
2017-05-11 16:40:59 -04:00
Jarrod Johnson ddfdae6bf0 Change to using KeyValueData message for discovery and mac data
This is properly representing the data as read-only information.
2017-05-10 17:58:23 -04:00
Jarrod Johnson b77a97db43 Add '/networking/' section to the API
Provide a mechanism to explore the internal information about switch
mac tables.
2017-05-10 14:28:57 -04:00
Jarrod Johnson 1e1d8dc000 Support trying default then target credentials
If something is discovered that has already been set up, try
first the default credentials, and failing that see if perhaps
the final user/password will work.  If the credentials match neither,
then there's nothing for us to do than log.
2017-05-09 14:40:27 -04:00
Jarrod Johnson 4adb2adc12 Merge branch 'master' into discovery 2017-05-09 08:39:17 -04:00
Jarrod Johnson 0e9e5da102 Properly respond with not found under by-type and by-model
Previously, a KeyError would trigger Unexpected error, correct this.
2017-05-08 16:53:55 -04:00
Jarrod Johnson 777c419cb8 Backout checking for str/unicode
A dict is a perfectly cromulant value for an attribute (e.g. with an
expression).
2017-05-08 16:50:55 -04:00
Jarrod Johnson 96916b57ae Support net.* for multiple nics in macmap 2017-05-08 14:27:09 -04:00
Jarrod Johnson 40ccf9d325 Support fetch by wildcard
Code needs to be able to retrieve by wildcard to be effective,
provide that support again limited to fnmatch '*' style wildcards.
2017-05-08 14:15:56 -04:00
Jarrod Johnson 6bd36580ba Support net.* attributes
Allow named groupings of net attributes.
2017-05-08 13:53:38 -04:00
Jarrod Johnson 37f12296b8 Support wildcard watchers
In preperation for 'net.<name>.attributes', provide
ability for code to watch wildcards to get the open ended ones.
2017-05-08 12:00:16 -04:00
Jarrod Johnson cd9eefa100 Document plan change on nic attributes
Document change is long term strategy on the network attributes.
The previous design was an overreaction to the mess of the
nics/mac/hosts table and inability to cleanly deal with bonds and
bridges.  I think this design does a better job compromising between
overcomplicating the simple case and having the power to accomodate
the complex case.  Note that IMO, network manager overcomplicates the
device to connection relationship, and I want to avoid that.
2017-05-08 11:07:46 -04:00
Jarrod Johnson 2c72931641 Add status to nodepower man page
Would normally have omitted it, but it is in usage.  And the usage
message is cleaner without having to explain you can skip status.
Have the man page explain that 'status' acts the same as no argument.
2017-05-08 10:39:53 -04:00
Jarrod Johnson 0731bfe29a Add bulk node creation and deletion
Now create and rm can be done in /noderange (user beware)
2017-05-05 16:55:40 -04:00
Jarrod Johnson 667a1030b4 Fix node*attrib commands show of undefined attributes
It would unfairly call things an invalid attribute.
2017-05-05 15:30:10 -04:00
Jarrod Johnson 4c6e3fcc4f Change powered off notification 2017-05-05 15:16:08 -04:00
Jarrod Johnson baa4d8bd1a Support 'custom.' prefix attributes
Allow freeform use of 'custom.' prefix, for arbitrary
attributes of user chosing (without type and typo checking)
2017-05-05 15:03:31 -04:00
Jarrod Johnson 816445e4b6 Fix man page directory location 2017-05-03 16:28:21 -04:00
Jarrod Johnson 8cce35dbcb Correct typo in nodeattribexpressions man 2017-05-03 15:01:13 -04:00
Jarrod Johnson dabb93fc04 Fix man page paths for sections 5 and 8 2017-05-03 14:33:17 -04:00
Jarrod Johnson 120b70f7ab Add usage to nodegroupattrib now that it is repaired 2017-05-03 14:30:04 -04:00
Jarrod Johnson fb626f7713 Fix nodegroupattrib command 2017-05-03 14:27:16 -04:00
Jarrod Johnson 41c22d9e66 Provide more man pages
Correct the lack of documentation for confluent commands.
2017-05-03 14:27:05 -04:00
Jarrod Johnson 6b6b7f06b7 Merge branch 'master' into discovery 2017-05-03 09:45:39 -04:00
Jarrod Johnson 4595c417ee Merge branch 'master' into discovery 2017-05-02 15:39:33 -04:00
Jarrod Johnson f5fea79ee4 Provide info.note attribute
Provide an attribute for open ended human use.
2017-05-02 14:26:28 -04:00
Jarrod Johnson 58ab997105 Fix nodeattrib behavior on undefined attribute. 2017-05-02 09:58:14 -04:00
Jarrod Johnson 819318b763 Add man page for nodeidentify 2017-05-01 16:14:21 -04:00
Jarrod Johnson d47dfd8a73 Add a man page for nodepower 2017-05-01 16:07:12 -04:00
Jarrod Johnson bcfc6966c3 Update the man page for nodeconsole 2017-05-01 14:19:08 -04:00
Jarrod Johnson e1cb97e836 Fix formatting issues in nodeattrib man 2017-05-01 14:11:44 -04:00
Jarrod Johnson 7f2df84167 Fix nodeattrib behavior on undefined attribute. 2017-05-01 14:04:26 -04:00
Jarrod Johnson ce1a5b464f Fix nodeattrib behavior without argument 2017-05-01 14:02:41 -04:00
Jarrod Johnson 4fb41a991e Fix formatting of nodeattrib man page 2017-05-01 13:45:29 -04:00
Jarrod Johnson bd735e8902 Fix the nodeinventory args parsing without 'firm' 2017-04-28 16:02:32 -04:00
Jarrod Johnson 7dcbd75561 Add full path to man pages 2017-04-28 15:35:16 -04:00
Jarrod Johnson 4ec06f8940 Fix argument to ronn to generate roff 2017-04-28 15:12:17 -04:00
Jarrod Johnson 60b91d5202 Invoke man page builder for rpm build 2017-04-28 15:10:09 -04:00
Jarrod Johnson 4ce382ced2 Add a script to build man pages 2017-04-28 15:09:00 -04:00
Jarrod Johnson d85fddd555 Prepare for man page packaging 2017-04-28 15:07:06 -04:00
Jarrod Johnson c150202585 Correct argv for nodefirmware from nodeinventory 2017-04-28 14:27:57 -04:00
Jarrod Johnson 8cde65a9e6 Correct the exec in previous commit 2017-04-28 13:59:48 -04:00
Jarrod Johnson d1abace7da If 'firm' appears as the argument, make it nodefirmware
This better enables use of alias rinv to nodeinventory.  Though it
is a smidge slower than nodefirmware.
2017-04-28 13:52:13 -04:00
Jarrod Johnson 412166922a Add '/version'
Provide version info via API
2017-04-28 13:20:26 -04:00
Jarrod Johnson 5e34ebe525 Quote the version number. 2017-04-28 11:54:41 -04:00
Jarrod Johnson 8274167298 Add version info to confluent module 2017-04-28 11:39:57 -04:00
Jarrod Johnson f5e6461fd2 Add help to confetty
Help command and text, generic for now.
2017-04-28 10:50:59 -04:00
Jarrod Johnson c9ffe77582 Make ls/dir behavior consistent with filesystem
In a filesystem, 'ls' does not become 'cat'.  Have confetty do the
same thing.  'cat' and 'show' act the same.
2017-04-28 10:15:15 -04:00
Jarrod Johnson 60ec9c7c51 Background nodelist change handling
The discovery reaction to a node being deleted or added is laborious, and redundant.  Background
the handling and make sure only one instance executes and aggregate redundant rechecks to a single
re-execution at the end.
2017-04-28 09:14:55 -04:00
Jarrod Johnson 1d04405099 Merge branch 'master' into discovery 2017-04-27 16:49:26 -04:00
Jarrod Johnson 16db3f1b6a Merge branch 'master' into discovery 2017-04-27 16:48:20 -04:00
Jarrod Johnson a1b0ab15c5 Change to str for getting string of shlex
ve.message was deprecated, but str is current.
2017-04-27 16:01:30 -04:00
Jarrod Johnson 2a1794ddad Enrich snoop SLP to look like scan
Scanned data was doing work to flesh out responses, but not snoop.

Add attributes to snoop flow and make the result data look more
similar.
2017-04-27 16:01:06 -04:00
Jarrod Johnson 7704523410 Provide discovery detail in deep levels 2017-04-27 14:48:59 -04:00
Jarrod Johnson 4cc57acea9 Provide detail information on detected nodes
More data may come, but for now provide the basic
information like serial number, model number, detected nodename,
ip address, and so forth.
2017-04-27 14:18:22 -04:00
Jarrod Johnson 70aa1fd114 Merge branch 'master' into discovery
# Conflicts:
#	confluent_server/confluent/plugins/hardwaremanagement/ipmi.py
2017-04-26 14:38:05 -04:00
Jarrod Johnson ba27207b83 Rework ipmi worker management
Provide a more concrete measurement of
children, rather than relying upon a
sentinel value on the queue.  It seems
that even using 'finally' didn't assure
that we always get that sentinel value
before a worker dies.  Sentinel value
still used to avoid a long wait in the
usual case.
2017-04-25 15:26:56 -04:00
Jarrod Johnson be12f163a5 Stub ability to get detailed info in discovery
Looking directly at a chunk of information,
temporarily use repr as a stub until a proper messages
is done.  This would break horribly in the web, for example.
2017-04-24 15:48:44 -04:00
Jarrod Johnson 20e00b266a Merge branch 'master' into discovery 2017-04-21 10:30:02 -04:00
Jarrod Johnson ae03ca9fd8 Merge remote-tracking branch 'upstream/master' into discovery 2017-04-20 16:26:05 -04:00
Jarrod Johnson 1812e16559 Merge branch 'master' into discovery 2017-04-20 14:59:52 -04:00
Jarrod Johnson be971fd5e7 Comment out not yet implemented autonode attributes
Since this is going to be pulled out of scope for now,
comment out the attributes until relevant.
2017-04-17 16:47:05 -04:00
Jarrod Johnson 8675929a16 Coerce binary operation arguments to numerics
This supports numeric manipulation of other attributes.
2017-04-12 13:26:48 -04:00
Jarrod Johnson a2d440d83b Correct missing argument
__new__ was missed in the last commit.
2017-04-11 15:41:26 -04:00
Jarrod Johnson e0d6d6b18c Revert "Try to mitigate chance of two greenthreads writing"
This reverts commit d83fb76254.

This could get stuck in various scenarios.
2017-04-11 15:16:04 -04:00
Jarrod Johnson e47038b2dd Change the stdout/stderr/trace logs to not buffer things over time
If something went completely off the rails, it could easily fill up lots of memory with log entries in the 2 seconds it
would buffer.  For now disable the buffering on key debug logs, as the main purpose was reducing IOPs in the per-node
console logs anyway.  A future behavior may be to also limit the size and/or number of outstanding log entries before
committing to disk.
2017-04-11 14:39:29 -04:00
Jarrod Johnson a5e84e2eb0 Rework some of the explorable discovery data
This is a bit easier to read and avoids some code replication.
2017-04-07 16:52:18 -04:00
Jarrod Johnson 6f633e0bfb Address a number of corner cases in console
The pyte parser state can get corrupted.  Unfortunately, we
don't know how, but we can detect and repair.

Also, the number of livesessions can change mid iteraction, change code to deal with changes
in the midst of handling data.

Finally, if trying to write data to the console fails for any unexpected reason,
log the trace and start the disconnect process.
2017-04-07 13:44:40 -04:00
Jarrod Johnson 2112fd71f5 Merge branch 'master' into discovery 2017-04-07 09:41:32 -04:00
Jarrod Johnson d83fb76254 Try to mitigate chance of two greenthreads writing
consoleserver, at least for one, is writing data direct.
At least once in a while, from a long distance from a potentially
spotty network connection is triggering a conflicting write.
Hopefully this mitigates it.
2017-04-06 11:25:33 -04:00
Jarrod Johnson 064514d6ec Fix omitted dl
dl parameter was omitted by mistake in previous commits.
2017-04-04 16:22:39 -04:00
Jarrod Johnson 3e8271afa8 Revert "Declare stdout to be in utf-8 across the board"
This reverts commit 26a8ba53bd.
2017-04-04 14:52:45 -04:00
Jarrod Johnson 3980be4e3a Revert "Use raw stdout for console"
This reverts commit 3b8d0ef40a.
2017-04-04 14:52:24 -04:00
Jarrod Johnson 67d61fc803 Revert "Use native stdout in confetty"
This reverts commit 439f4c7709.
2017-04-04 14:52:10 -04:00
Jarrod Johnson 5f3f073e9c Fix tab completion on things with hyphens in it. 2017-04-04 11:58:55 -04:00
Jarrod Johnson 439f4c7709 Use native stdout in confetty
A number of things break other than console, like tab completion.
2017-04-04 11:29:32 -04:00
Jarrod Johnson 3b8d0ef40a Use raw stdout for console
The codecs streamwriter does poorly with some console
data.
2017-04-04 11:11:10 -04:00
Jarrod Johnson 26a8ba53bd Declare stdout to be in utf-8 across the board
Python changes behavior when going through a pipe, consistently use UTF-8
instead.
2017-04-04 09:00:21 -04:00
Jarrod Johnson c9192ab170 Do not take invalid serial numbers
Serial numbers that are empty or all spaces are now filtered.
2017-04-04 08:55:39 -04:00
Jarrod Johnson 00598578a6 Numerous discovery anomaly fixes
For one, ensure mac address is valid in the neighutil.py, rather than
trying to blacklist.  Apart from FAILED, INCOMPLETE is possible,
and it's just a bad idea to blacklist.

When getting the https_cert has an unexpected situation, now
say it is because of unreachable all the time.

Refactor the loop in the node recheck so that the inner loop is brought out.
Catch and print any exceptions, but carry on to the next entry regardless.

Do not exit out of above loop because one thing is unreachable.
2017-04-03 11:33:52 -04:00
Jarrod Johnson 74740e01b1 Further expand the discovery api support 2017-03-31 16:59:19 -04:00
Jarrod Johnson 0034309fe1 Flesh out some of the api 2017-03-31 16:49:02 -04:00
Jarrod Johnson 0b7f2a45e9 First phase of API explorable discovery 2017-03-31 16:06:33 -04:00
Jarrod Johnson 261783f971 Provide instrumentation functions
Prepare to enable API browsing of discovery data
2017-03-31 15:58:08 -04:00
Jarrod Johnson 9f8feb971d Remove the unused skeleton of discovery
It was useful as note, but the actual design deviated.
2017-03-31 14:37:45 -04:00
Jarrod Johnson 2959fa7e33 Add IMM discovery
Apart from CMM, IMMv2 platform systems should work in theory.
2017-03-31 11:33:09 -04:00
Jarrod Johnson 0b40b876c0 Provide more specific guidance on SNMP issues
Previously, many things manifested as 'Timeout',
particularly bad credentials.  Provide some fixes
to the log messages.
2017-03-30 14:56:21 -04:00
Jarrod Johnson 6952f7cab9 Fix discovery and messaging around certain scenarios
If a node has a random ip address, then static, then goes back,
it was not updating the addresses.  When an address is updated,
go ahead and do a deeper investigation.

If anything should go wrong unexpectedly, don't let the periodic recheck
forever die.

If for some reason we have bad ip in the info dictionary, log a more accurate
message.
2017-03-30 12:22:53 -04:00
Jarrod Johnson c2c81b0167 Do not use a FAILED entry for arp
An arp entry for FAILED is not usable, so do not
accept that either.
2017-03-30 11:04:35 -04:00
Jarrod Johnson ac0626a51c Provide more information about unexpected error
We know the specific ip, mac, and *maybe* nodename,
provide that info to help debug an issue.
2017-03-30 09:44:47 -04:00
Jarrod Johnson 9effdee15a Guad recheck with a semaphore
While measures have been taken to improve sanity,
should something go wrong, make recheckers that are duplicate
run in serialy rather than parallel.
2017-03-29 15:55:48 -04:00
Jarrod Johnson 65580b1d60 Avoid exponential growth of recheckers
cancel() happening while _periodic_recheck is already running
would result in duplicate periodic_rechecks, each which could
then repeat the mistake, leading to exponential growth of
the recheck process.
2017-03-29 15:53:10 -04:00
Jarrod Johnson 9c41031df3 Fix mac scanning back to back
If find_node_by_mac were called in different contexts concurrently, the
slightly later starter would see the vintage be recent and bail, though
scanning is not, in fact ready yet.  Change to early exit strategy after checking
the semaphore phase, to make concurrent calls wait.
2017-03-29 14:45:13 -04:00
Jarrod Johnson 740cccd82e Tolerate a target that won't TCP SLP 2017-03-29 14:33:30 -04:00
Jarrod Johnson 344bb1229c Wrap mac map update in a semaphore
We only want one scan of the switches occurring at any given moment.
2017-03-29 09:48:44 -04:00
Jarrod Johnson 935acc2a10 Avoid futile thrashing on switch search 2017-03-29 09:28:25 -04:00
Jarrod Johnson cd841fb43e Update comments, add a rescan for future 2017-03-27 13:52:04 -04:00
Jarrod Johnson 447a3b3ffe Accept updated ip addresses
Merge new ip info rather than just discarding it.
2017-03-27 11:11:18 -04:00
Jarrod Johnson 108c9d3e9d Do not actually discover if config errors are found
We will defer until someone actually fixes them.
2017-03-24 15:31:11 -04:00
Jarrod Johnson cc98290d68 Fix a number of issues when switches misconfigured
For one, if a "" is used, ignore that as a switch.
Second, if "" is a username, then treat that as 'None'.
Finally, make sure event shows something even if it doesn't know what it is.
2017-03-24 15:21:36 -04:00
Jarrod Johnson 1fce78ed39 Adjust for SMM fix
We can now more quickly set the IP address, as the SMM problem is really
resolved.
2017-03-24 10:47:25 -04:00
Jarrod Johnson 5bc4d20eef Accelerate recheck on apparent https not running
If we see https is not running, be aggressive and check back in
a minute, rather than waiting 15 minutes, to reduce discovery latency.
2017-03-21 16:10:40 -04:00
Jarrod Johnson 5dbd345a49 Avoid scriptlet error if it's no big deal 2017-03-21 15:46:03 -04:00
Jarrod Johnson 62839b76b7 Move probe into per node function
probe() was being missed on updates.  Now it hits both.

Also, log and defer if node is not running https cert on recheck.
2017-03-21 14:49:35 -04:00
Jarrod Johnson 848745bb4a Store list for future use 2017-03-21 11:21:24 -04:00
Jarrod Johnson f68a8c80bb Implement alternate enclosure scheme
Previously, it relied upon the handler successfully
populating 'enclosure.bay'.  It seems that failures can happen
after all, so instead check if it *should* be an enclosure, and
use a more bulletproof indicator of whether the thing *is*
an enclosure.
2017-03-21 10:37:52 -04:00
Jarrod Johnson 7f185db64f Track known_nodes and unknown better
If something was known from a prior life, still store it as
a known_nodes to allow config changes to be handled.
Also, if an unexpected condition occurs, ensure that
we track it as unknown.
2017-03-21 10:17:36 -04:00
Jarrod Johnson 2cd2e170e0 Merge branch 'discovery' of hpcgitlab.labs.lenovo.com:jjohnson2/confluent into discovery 2017-03-21 08:27:27 -04:00
Jarrod Johnson 0df75d3102 Stop trying to logout on preconfig
The logout was stepping on itself.
2017-03-20 18:50:38 -04:00
Jarrod Johnson 5b8aebaa3e Remove known nodes from unknown_info
Regardless of outcome, a discover_node should
not be in 'unknown_info' anymore.
2017-03-20 11:12:44 -04:00
Jarrod Johnson 0577c9a99c Suppress error output on try-restart 2017-03-18 17:32:48 -04:00
Jarrod Johnson 12ffdf9504 Clear titlebar on exit
If the system running confetty does not manage titlebar, clear what we have
on way out, to avoid misleading output.
2017-03-18 16:07:25 -04:00
Jarrod Johnson c2a4913c78 Defer discovery of targets not running https
The https certificate is critical for ascertaining whether something is new or
existing.  Also, config step may depend upon https availability if we have
pre-approved https certificates.
2017-03-18 15:03:13 -04:00
Jarrod Johnson f34770529e Fix detection of well known nodes up front
The second parameter was not being coerced into an iterable.
2017-03-18 14:39:24 -04:00
Jarrod Johnson 0336a07f17 Only log on real discovery
Was logging on storing already discovered nodes, fix the mistake.
2017-03-18 14:24:30 -04:00
Jarrod Johnson b57dbcd89b Fix spelling of permissive 2017-03-18 14:03:01 -04:00
Jarrod Johnson 7948cd1bb2 Fix attriute responsiveness
The recheck procedure can delete parts of unknown_info.  Detect and
move past.
2017-03-18 13:59:14 -04:00
Jarrod Johnson 049a728be1 Improve appearance of no buffer message 2017-03-18 13:34:15 -04:00
Jarrod Johnson 42c775a7a4 Remove attempt to rebuild buffer from log file
It's at odds with the strategy of valid buffer only if we have confidence.
Clients reconnecting without replay still have a good experience, since their
terminal serves as the buffer.
2017-03-18 12:20:32 -04:00
Jarrod Johnson 9cf074e1a2 Log some discovery behavior
Provide some guidance on the flow of discovery and suggestions
if the user is facing a surprise situation.
2017-03-18 12:13:57 -04:00
Jarrod Johnson 631509a32a Enhance buffer replay behavior
Provide a clearer message explaining why no replay buffer.  Also clear the message away
when data is sent/captured.
2017-03-18 11:09:41 -04:00
Jarrod Johnson 4e4fbeb907 Go back to resetting the SMM on config
The SMM issue around config is not fixed after all.
2017-03-17 10:08:43 -04:00
Jarrod Johnson b92fddc647 Clear buffer on loss of connection
When a connection is lost, the data is at high risk of being stale.  In such
a case, clear out the current terminal buffer with a message.
2017-03-16 16:54:03 -04:00
Jarrod Johnson 79474ea9fb Restore cursor position explicitly
This allows full redraw of whitespace and very precisely
restores the position of the cursor to the right place.
2017-03-16 11:27:30 -04:00
Jarrod Johnson 639a7352ad Change to stateful utf8 decoding
Since we are receiving bytes of data, switch to incremental decoder, which
will accomodate partial multi-byte utf-8.  Also simplify by using translate and
skipping the final utf-8 decode attempt, which would be impossible, but use
'replace' in cp437, just in case.
2017-03-16 11:13:38 -04:00
Jarrod Johnson 4708630bac Put cursor in correct place on most reconnects
Before, the terminal redraw would draw to the end.  Avoid
trailing new line and whitespace to make prompt look almost right.
Space in PS1 is not honored.
2017-03-15 15:12:33 -04:00
Jarrod Johnson a0be58c9cb Provide cleaner error on a missing plugin
If a missing or mispelled plugin were specified, it would take out a whole
noderange.  This will provide a useful message and isolate the fault.
2017-03-15 14:51:19 -04:00
Jarrod Johnson 71e89c005b Let slip the dogs of discovery
We have firmware fixes in place, we can enable
pretty much the full discovery.
2017-03-15 11:25:53 -04:00
Jarrod Johnson 1dc8fc973d Rework the normalize
Seemingly, decode then encode can mess up some utf-8.
If possible, leave it untouched, apart from binary replacement.
translate won't work on a byte string, so use replace.
2017-03-15 10:07:18 -04:00
Jarrod Johnson a517436f71 On IO error, drop failed input
It is possible to get an IO error on input.  Ignore this and carry on.
2017-03-15 09:29:06 -04:00
Jarrod Johnson ed34b30992 Implement a normalization to UTF-8
Not only using the codec, but also giving it a bit of 'help' on
CP437 characters not caught by encode.
2017-03-14 22:37:33 -04:00
Jarrod Johnson 8ca1553c33 Switch to a skip above maxlen
Previous strategy would not account for data vs. control codes.
2017-03-14 18:38:33 -04:00
Jarrod Johnson 02266a4be6 Allow whitespace padding to max detected width
This makes some background drawing look nicer.
2017-03-14 17:35:51 -04:00
Jarrod Johnson 70e5d1dc52 Note to implement better whitespace 2017-03-14 17:23:45 -04:00
Jarrod Johnson 191726776c Delete now stale code
It was preserved 'just in case', but new code is working and we always have git.
2017-03-14 16:53:05 -04:00
Jarrod Johnson c99d446a63 Add missing pyte dependency 2017-03-14 16:42:44 -04:00
Jarrod Johnson e3318ec5ef Implement virtual terminal
Provide a virtual 100x31 terminal instead of a dumb byte buffer.
This allows higher quality redraw on reconnect.
2017-03-14 16:21:26 -04:00
Jarrod Johnson cbded58fc2 Retry failed write with partial writes
Sometimes when dealing with certain terminal settings
and large volume of data, we need to fall back to a slow
scheme to write out.
2017-03-13 13:57:37 -04:00
Jarrod Johnson 005877d3eb Rename hardwaremanagement.switch and switchport
Make it net.switchport and net.switch to avoid inconsistency
when ultimately implementing support for in-band and out of
band nics
2017-03-07 14:11:44 -05:00
Jarrod Johnson 8497c28ebe Defer node add watcher as well
For now, keep delete watchers as is,
so that handlers have a chance to say goodbye
before the nodes actually go away.
2017-03-06 08:52:28 -05:00
Jarrod Johnson 2d76c0aefc Log a couple of common cases that don't need to be trace
Start using the event log to record things like this.
2017-03-05 10:04:46 -05:00
Jarrod Johnson 2e5ec9264f Pass an empty tuple to recheck_nodes
recheck_nodes is expecting an iterable, so give it one.
2017-03-05 09:47:25 -05:00
Jarrod Johnson 9734aa65bf Merge branch 'master' into discovery 2017-03-05 09:21:38 -05:00
Jarrod Johnson 46ed0eb38a Merge branch 'master' into discovery 2017-03-05 08:29:59 -05:00
Jarrod Johnson 85d2a0e6aa Fix disambiguation 2017-03-03 19:06:15 -05:00
Jarrod Johnson 5f88067134 Merge branch 'master' into discovery 2017-03-03 18:41:26 -05:00
Jarrod Johnson eb63a6fcda Note to show strategy for disambiguating enclosure/node
It is a tricky situation when detecting on switch.  If something has a bay
and is in fact an enclosure.manager itself, that means it will be a node
inside the enclosure, and not the enclosure manager.
2017-03-03 17:11:31 -05:00
Jarrod Johnson 8be3c2a80e Add some support for resetting discovered nodes
If a node has been discovered incorrectly, react to attempts to clear the
info to reconfigure the node.
2017-03-03 16:56:25 -05:00
Jarrod Johnson 2ce26cbb59 Translate name to IPv4 address 2017-03-02 17:02:17 -05:00
Jarrod Johnson 44808fd343 Translate name to IPv4 address 2017-03-02 16:14:54 -05:00
Jarrod Johnson 467fea7d8e Accept resolvable names for prefix length 2017-03-02 15:43:57 -05:00
Jarrod Johnson 39463ddcb7 Add missing rpm dependencies 2017-03-02 14:27:45 -05:00
Jarrod Johnson cdb3801993 Fix notifier handler involvement in setting value
Avoid extraneous error in setting values and performance
issue when manipulating an attribute with a registered watcher.
2017-03-02 11:20:17 -05:00
Jarrod Johnson af478816e9 Add hardwaremanagement.manager to monitor
React to a change in the hardwaremanagement.manager value,
to allow for cases where a user sets it after it would have been
needed.
2017-03-01 15:33:53 -05:00
Jarrod Johnson 79e57a442d Actually run the discovery mechanism 2017-03-01 15:06:25 -05:00
Jarrod Johnson 38d6354725 Provide mechanism to recheck nodes
When configuration changes happen, double check known(ish)
targets to see if they can discover now.
2017-03-01 14:58:25 -05:00
Jarrod Johnson b2ba28f0fb Implement workaround for SMM config
SMM configuration only reliably applies after reset.
Reset SMM to enact process for now.
2017-03-01 11:45:19 -05:00
Jarrod Johnson 9bf8d65df1 Merge branch 'master' into discovery 2017-03-01 10:31:49 -05:00
Jarrod Johnson 7c048c9c54 Merge branch 'master' into discovery 2017-03-01 10:24:42 -05:00
Jarrod Johnson 9fbf500b6c Make configuration of detected things be concurrent
This should speed up discovery in event of a slow target.
2017-03-01 09:38:11 -05:00
Jarrod Johnson 147be0c92c Handle XCCs without some attributes 2017-02-28 16:03:56 -05:00
Jarrod Johnson 54583c604f Do not encounter fatal issue when https is not running 2017-02-28 15:15:19 -05:00
Jarrod Johnson de41a665b2 Merge branch 'master' into discovery 2017-02-28 14:08:53 -05:00
Jarrod Johnson 98981f00a9 Actually set up Nodes managed by SMM
The XCCs are now configured if the policies allow it, whether or not the SMM is configured.
2017-02-27 17:04:52 -05:00
Jarrod Johnson 074a8debf4 Setup user on generic bmc case 2017-02-24 15:38:27 -05:00
Jarrod Johnson a4d66cdbc9 Protect the protocols from exceptions
Consolidate unexpected error handling in a safe_detected flow.
2017-02-23 14:53:12 -05:00
Jarrod Johnson 2270e64148 Have PXE return services in a consistent fashion 2017-02-23 14:48:15 -05:00
Jarrod Johnson 0e99d99051 On an XCC, probe to get uuid and enclosure info as possible. 2017-02-23 14:45:38 -05:00
Jarrod Johnson b0ddc66e06 Move SMM notes to smm.py for future
Don't clutter the bmc.py with the SMM notes
2017-02-22 16:23:01 -05:00
Jarrod Johnson 364a17f31a Implement IPv4 addr/netmask config apply
Also start getting ready for user/password code tomorrow.
2017-02-21 16:58:07 -05:00
Jarrod Johnson 04d18c4b22 Complete module rename in xcc 2017-02-21 14:19:17 -05:00
Jarrod Johnson 44330f2351 Add new subdirectory to setup.py 2017-02-21 14:13:14 -05:00
Jarrod Johnson 117cf71065 Rearrange the protocols into a subdirectory
It's a little less cluttered this way.
2017-02-21 13:38:24 -05:00
Jarrod Johnson 44b58d58ae Fix message on BMC user creation 2017-02-21 11:02:20 -05:00
Jarrod Johnson b5a924bdb6 Handle null fingerprint
If no existing fingerprint, then a cert can't possibly match, return
False.
2017-02-21 09:37:31 -05:00
Jarrod Johnson d1fd17200f Fix spelling mistake in cert_matches 2017-02-21 09:29:46 -05:00
Jarrod Johnson b035bd6c1e Add hooks to recheck on reconfigure
This will allow us to be responsive to changes in configuration.
2017-02-21 09:17:57 -05:00
Jarrod Johnson 02e2cb1673 Further advance discovery
This commit starts to lay the groundwork for
handling the security policy differences with
respect to TLS keys on port 443.  This requires
that the discovered entitity support https for now,
which is a given (even if the web service is not enabled,
it does provide a cert).

IMMv2 may never be able to follow the 'secure' path due
to uncertainty of whether there's a licensed web ui or not).
2017-02-18 09:23:34 -05:00
Jarrod Johnson 53954426a1 Begin working on xcc interrogation/preconfig 2017-02-17 17:01:11 -05:00
Jarrod Johnson d02c4e51f9 Fix mistakes in the discovery code to date 2017-02-15 17:11:59 -05:00
Jarrod Johnson 26e9db263d Add discovery and networking bits to packaging 2017-02-15 16:53:42 -05:00
Jarrod Johnson ae56085f95 Fix typo in the command 2017-02-15 15:36:23 -05:00
Jarrod Johnson 125160e0f5 Add attribute to control discovery policy 2017-02-15 15:23:26 -05:00
Jarrod Johnson 2f251dd05a Continue work on mac search capability 2017-02-15 14:03:28 -05:00
Jarrod Johnson 88b2b49d3b Merge branch 'master' into discovery 2017-02-15 11:53:12 -05:00
Jarrod Johnson 1fb5c0c177 Begin work on node handlers
The handler framework will be the key for supporting multiple node types.
Each handler is supposed to support a variety of stages through the
process.

First a unicast interrogation to get some data (ideally without
logging in, or by logging in with default credentials).

Second upon identification, preconfig will be called, if and only if the endpoint would become a node,
but should not yet do anything big, just paving the way for verification or dependent discovery configuration.

Finally, config will be called to deploy configuration according to the node and referenced
config manager.
2017-02-15 11:47:05 -05:00
Jarrod Johnson 9fdccf8371 Begin working on xcc interrogation/preconfig 2017-02-14 17:07:49 -05:00
Jarrod Johnson f5d7fdcb58 Update design commentary in discovery
Note the preconfigure stage
2017-02-14 16:34:47 -05:00
Jarrod Johnson b639720190 Tweak available attributes. 2017-02-14 16:34:19 -05:00
Jarrod Johnson 347dbd8cd1 Move neighutil up to a more general location
It isn't necessarily discovery specific
2017-02-14 16:26:45 -05:00
Jarrod Johnson 204b47d174 Add function to get the prefix length
When we get to configuration of a system,
we currently will have the ip address, but
not the prefix length.  Use this as a way
to divine the information automatically.
2017-02-14 16:24:21 -05:00
Jarrod Johnson d738a4c61f Do not repr twice
Data in the initial scan stage was repeated twice in
unit test.  Fix the extra output
2017-02-13 17:00:27 -05:00
Jarrod Johnson 6ebbd91592 Fix handling of certain scenarios
One issue is that SLP data can have bad data in the bytes. Present
a decode error.  So far, the raw value was erroneously '\xff\xff....'

Another is that a network with aliases will reject IP_ADD_MEMBERSHIP, due
to the ambiguity.
2017-02-13 16:52:44 -05:00
Jarrod Johnson 17f587252e Merge branch 'master' into discovery 2017-02-10 16:42:15 -05:00
Jarrod Johnson 5f1a2ed69c Add some helpful output delimiters to unit test 2017-02-10 15:47:13 -05:00
Jarrod Johnson 0668b90ba9 Improve the unit test for mac map
Actually get to using the config manager.
2017-02-10 15:45:58 -05:00
Jarrod Johnson fa4a759bc1 Start effort to consolidate snoop open scan
Scan and pass results as snoop answers.  They should
be like snoop answers, but with extra detail that will
be discarded.
2017-02-09 17:03:05 -05:00
Jarrod Johnson 890e23f2d9 Improve symmetry of scan and snoop in SLP
addresses versus peers, hwaddr versus mac, and services versus service
2017-02-09 14:59:01 -05:00
Jarrod Johnson e08a79b067 Update copyright year 2017-02-09 14:46:56 -05:00
Jarrod Johnson 0dc42567c2 Add a pxe-client service
For consistency with other plugins, model a pxe request as a 'pxe-client'
service being detected
2017-02-09 14:41:11 -05:00
Jarrod Johnson 94a3e83b04 Fix for multicast on multiple nics (SSDP)
It turns out that binding to INADDR_ANY doesn't work right for
multicast.

Workaround by adding membership for each ip.
2017-02-09 14:39:02 -05:00
Jarrod Johnson 7f71d01c2d Fix for multicast on multiple nics
It turns out that binding to INADDR_ANY doesn't work right for
multicast.

Workaround by adding membership for each ip.
2017-02-09 13:46:14 -05:00
Jarrod Johnson bd31358649 Support XCC 2017-02-08 16:42:20 -05:00
Jarrod Johnson be054689f8 Prepare for future 2017-02-08 16:41:16 -05:00
Jarrod Johnson 4fada9b931 Fix issue when no handler specified 2017-02-08 16:39:24 -05:00
Jarrod Johnson 795e4c1273 Merge branch 'master' into discovery 2017-02-07 13:45:33 -05:00
Jarrod Johnson 9174b46f66 Avoid mac address table lookups for common static
Some IP addresses are commonly used as static values.
In such a case, inferring a nice, clean 1:1 mac:ip relationship
can end in tears.  For those, don't even pretend to know the
associated mac address, even if the neighbor table would have
it.
2017-01-18 16:31:53 -05:00
Jarrod Johnson 1f3c93d7ee Merge branch 'master' into discovery 2017-01-18 15:38:41 -05:00
Jarrod Johnson c85d02d889 Merge branch 'master' into discovery 2017-01-12 07:41:25 -05:00
Jarrod Johnson 8c7c2026d9 Merge branch 'discovery' of github.com:jjohnson42/confluent into discovery 2017-01-03 15:48:37 -05:00
Jarrod Johnson e847a5b871 Properly handle SNMPv1 community only
If given SNMPv1 community only, use the data
correctly.
2016-11-29 10:45:16 -05:00
Jarrod Johnson c022466bf8 Merge branch 'discovery' of github.com:jjohnson42/confluent into discovery 2016-11-29 10:35:32 -05:00
Jarrod Johnson e03e9e0736 Have core actually start using the modules
This allows testing of the concept in a more complete form.
2016-11-16 16:39:12 -05:00
Jarrod Johnson 0caa7b5519 Consolidate interface for scan
Make the discovery modules implement a more
consistent interface strategy.
2016-11-16 16:02:54 -05:00
Jarrod Johnson c5f1e60414 Aggregate SSDP scan replies
Like in the SLP code, make efforts to group responses about the same peer together.
2016-11-16 13:00:15 -05:00
Jarrod Johnson 1f5c7208b8 Add a standalone test for slp
Allow individual test of the slp snoop
functionality.
2016-11-16 09:00:10 -05:00
Jarrod Johnson 3edba0f8bb Refactor neigh table handling and expand SSDP
SSDP support snoop is implemented.
2016-11-15 15:57:55 -05:00
Jarrod Johnson 786bf00521 Extend SSDP to listen
This lays the groundwork to implement notifying an application of
SSDP entries coming and going on a network.
2016-11-14 16:37:16 -05:00
Jarrod Johnson b944357071 Extract the client architecture from PXE
Since PXE contains client architecture, this may
one day be a useful piece of data to control boot.
Put it in now while the code is fresh in my mind.
2016-11-10 17:02:04 -05:00
Jarrod Johnson 59a7b190d5 Implement PXE snoop
This one only returns MAC and UUID, so the handler
must be special, compared to the ones that actually
identify an IP to interrogate.
2016-11-10 16:30:11 -05:00
Jarrod Johnson bd364ded70 Add notations for eventually doing pxe
This will for now be a 'snoop only' discovery.  One day this may also serve
for controlling PXE when DHCP sends an IP (we'd use 4011) and for static
hosts.
2016-11-10 09:55:06 -05:00
Jarrod Johnson da74830353 Comment in a sample of an SSDP notify
For use when implementing 'snoop' functionality.
2016-11-09 16:52:45 -05:00
Jarrod Johnson fff0488f89 Initial SSDP work
Refactor relevant bits of SLP implementation and do SSDP.
Note it still needs the monitoring for SSDP NOTIFY packets.
Unfortunately, this is another protocol that embeds the
address into the packet, making it tricky to have a sane
reusable function for the v4/v6 multicast/broadcast scheme.
2016-11-09 16:49:47 -05:00
Jarrod Johnson 63431deffb Merge branch 'master' into discovery 2016-11-09 10:40:06 -05:00
Jarrod Johnson a1757571b4 Write an SLP plugin, document flow in core
This serves as the framework that will ultimately feed
the 'detected' collection and manage the automation of
flow from being detected to a fully configured and managed
endpoint.
2016-10-27 16:49:58 -04:00
Jarrod Johnson 3ecb7aaf83 Move discovery into a subtree
This will make better room for providing a
suite of discovery protocols (namely slp,
ssdp, and mdns).
2016-10-27 16:30:10 -04:00
Jarrod Johnson 1ee166ea4e Tweak the proposed autonode attributes
Provide a pair of attributes for an enclosure class
device distinct from a directly connected server.
2016-10-27 16:27:31 -04:00
Jarrod Johnson 8e15dee11d Merge branch 'master' of github.com:xcat2/confluent into discovery 2016-10-27 16:14:05 -04:00
Jarrod Johnson 3610b16a6c Merge branch 'master' into discovery 2016-10-06 14:48:22 -04:00
Jarrod Johnson 4d50007377 Rework the attribute names for node generation
Accomodate likely scenario of mixing and matching direct rackmount and
enclosure based systems.
2016-10-04 14:39:38 -04:00
Jarrod Johnson d3e2af0215 Merge branch 'master' into discovery 2016-10-04 13:23:11 -04:00
Jarrod Johnson cff71ad95b Merge branch 'discovery' of github.com:jjohnson42/confluent into discovery 2016-10-04 13:13:29 -04:00
Jarrod Johnson edeef69ae9 Put out some concept autonode attributes
Something to put forth as a framework for the derivation
of generated node attributes.  One key gap is handling at
least one level of recursion so that a generated enclosure
manager can generate nodes.  Will probably steer clear of
arbitrarily deep recursion (switch can generate switch, etc).
2016-09-23 13:49:35 -04:00
Jarrod Johnson 4505e582b2 Add LLDP component
LLDP is to play a bigger rolle in confluent discovery.
This provides the starter module to gather such data.
2016-09-23 13:49:35 -04:00
Jarrod Johnson cf4fda2f7d Flesh out some of the design points
Mostly adding more comments in the areas that
will be implementing the design so that I'll
remember what I had in mind.
2016-09-23 13:49:35 -04:00
Jarrod Johnson 770559040e Add discovery core skeleton
This adds what will become the hub of discovery flow management,
as documented in the file's comments.
2016-09-23 13:49:35 -04:00
Jarrod Johnson 711061347c Begin adding hooks to work discovery
Discovery will be more explicitly organized.  This should
make it easier for folks to understand and have common paths
for embracing new schemes as appropriate.  At this point
PXE, SLP, SSDP, and mDNS are likely discovery schemes, and
'location' providers would be Ethernet switches and server
blade enclosure managers.
2016-09-23 13:49:35 -04:00
Jarrod Johnson 25169c2838 Defer console startup until after API startup
Consoles starting up would potentially delay API availaility.  Change
by having the API having ample time to startup, then commence the
busy work of starting cnosole sessions.
2016-09-13 11:33:25 -04:00
Jarrod Johnson 9685870c14 Merge branch 'master' into discovery 2016-09-09 09:15:25 -04:00
Jarrod Johnson 8221760fbd Put out some concept autonode attributes
Something to put forth as a framework for the derivation
of generated node attributes.  One key gap is handling at
least one level of recursion so that a generated enclosure
manager can generate nodes.  Will probably steer clear of
arbitrarily deep recursion (switch can generate switch, etc).
2016-09-01 17:46:51 -04:00
Jarrod Johnson 673785f576 Add LLDP component
LLDP is to play a bigger rolle in confluent discovery.
This provides the starter module to gather such data.
2016-09-01 11:32:30 -04:00
Jarrod Johnson 51ae476077 Flesh out some of the design points
Mostly adding more comments in the areas that
will be implementing the design so that I'll
remember what I had in mind.
2016-08-24 16:44:07 -04:00
Jarrod Johnson 8f7199bce6 Add discovery core skeleton
This adds what will become the hub of discovery flow management,
as documented in the file's comments.
2016-08-24 15:50:52 -04:00
Jarrod Johnson d282db0c02 Begin adding hooks to work discovery
Discovery will be more explicitly organized.  This should
make it easier for folks to understand and have common paths
for embracing new schemes as appropriate.  At this point
PXE, SLP, SSDP, and mDNS are likely discovery schemes, and
'location' providers would be Ethernet switches and server
blade enclosure managers.
2016-08-24 15:48:49 -04:00
88 changed files with 613 additions and 4765 deletions
-86
View File
@@ -1,86 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This is a utility to bring xcoll (plus enhancements) to confluent
# the core engine is textgroup.py, this simply provides a CLI to use
# generically
import optparse
import os
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.textgroup as tg
import confluent.client
argparser = optparse.OptionParser(usage="Usage: <other command> | %prog [options]")
argparser.add_option('-a', '--abbreviate', action='store_true',
help='Attempt to use confluent server to shorten noderanges')
argparser.add_option('-d', '--diff', action='store_true',
help='Show what differs between most common '
'output group and others')
argparser.add_option('-w', '--watch', action='store_true',
help='Show intermediate results while running')
argparser.add_option('-s', '--skipcommon', action='store_true',
help='Do not print most common result, only non modal '
'groups, useful when combined with -d')
argparser.add_option('-c', '--count', action='store_true',
help='Also display count of nodes in a given group')
argparser.add_option('-r', '--reverse', action='store_true',
help='Reverse sort order to show biggest output group '
'last')
(options, args) = argparser.parse_args()
if sys.stdin.isatty():
argparser.print_help()
sys.exit(1)
grouped = tg.GroupedData()
if options.abbreviate:
grouped = tg.GroupedData(confluent.client.Command())
else:
grouped = tg.GroupedData()
def print_current():
if options.diff:
grouped.print_deviants(skipmodal=options.skipcommon, count=options.count,
reverse=options.reverse)
else:
grouped.print_all(skipmodal=options.skipcommon,
count=options.count,
reverse=options.reverse)
sys.stdout.flush()
fullline = sys.stdin.readline()
while fullline:
for line in fullline.split('\n'):
if not line:
continue
if ': ' not in line:
line = 'UNKNOWN: ' + line
grouped.add_line(*line.split(': ', 1))
if options.watch:
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
print_current()
fullline = sys.stdin.readline()
if not options.watch:
print_current()
+28 -56
View File
@@ -76,7 +76,6 @@ import confluent.tlvdata as tlvdata
import confluent.client as client
conserversequence = '\x05c' # ctrl-e, c
clearpowermessage = False
oldtcattr = None
fd = sys.stdin
@@ -116,7 +115,6 @@ def print_help():
def updatestatus(stateinfo={}):
global powerstate, powertime, clearpowermessage
status = consolename
info = []
for statekey in stateinfo:
@@ -131,15 +129,6 @@ def updatestatus(stateinfo={}):
# error will be repeated if relevant
# avoid keeping it around as stale
del laststate['error']
if 'state' in stateinfo: # currently only read power means anything
newpowerstate = stateinfo['state']['value']
if newpowerstate != powerstate and newpowerstate == 'off':
sys.stdout.write("\x1b[2J\x1b[;H[powered off]\r\n")
clearpowermessage = True
if newpowerstate == 'on' and clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
powerstate = newpowerstate
if 'clientcount' in laststate and laststate['clientcount'] != 1:
info.append('clients: %d' % laststate['clientcount'])
if 'bufferage' in stateinfo and stateinfo['bufferage'] is not None:
@@ -154,7 +143,7 @@ def updatestatus(stateinfo={}):
info.append(time.strftime('%H:%M', time.localtime(showtime)))
if info:
status += ' [' + ','.join(info) + ']'
if os.environ.get('TERM', '') not in ('linux'):
if os.environ['TERM'] not in ('linux'):
sys.stdout.write('\x1b]0;console: %s\x07' % status)
sys.stdout.flush()
@@ -186,7 +175,7 @@ def recurse_format(datum, levels=0):
def prompt():
if os.environ.get('TERM', '') not in ('linux'):
if os.environ['TERM'] not in ('linux'):
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
try:
return raw_input(target + ' -> ')
@@ -295,17 +284,14 @@ def print_result(res):
if 'databynode' in res:
print_result(res['databynode'])
return
for key in sorted(res):
for key in res.iterkeys():
notes = []
if res[key] is None:
attrstr = '%s=""' % key
elif type(res[key]) == list:
attrstr = '%s=%s' % (key, recurse_format(res[key]))
elif not isinstance(res[key], dict):
try:
print '{0}: {1}'.format(key, res[key])
except UnicodeEncodeError:
print '{0}: {1}'.format(key, repr(res[key]))
print '{0}: {1}'.format(key, res[key])
continue
elif 'value' in res[key] and res[key]['value'] is not None:
attrstr = '%s="%s"' % (key, res[key]['value'])
@@ -351,7 +337,7 @@ def do_command(command, server):
return
argv[0] = argv[0].lower()
if argv[0] == 'exit':
if os.environ.get('TERM', '') not in ('linux'):
if os.environ['TERM'] not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
sys.exit(0)
elif argv[0] in ('help', '?'):
@@ -474,11 +460,8 @@ def createresource(args):
if keydata is None:
return
targpath = fullpath_target(resname)
if targpath.startswith('/noderange//'):
collection = targpath
else:
collection, _, resname = targpath.rpartition('/')
keydata['name'] = resname
collection, _, resname = targpath.rpartition('/')
keydata['name'] = resname
makecall(session.create, (collection, keydata))
@@ -600,7 +583,7 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
# Request default color scheme, to undo potential weirdness of terminal
sys.stdout.write('\x1b[m')
if fullexit:
if os.environ.get('TERM', '') not in ('linux'):
if os.environ['TERM'] not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
sys.exit(code)
else:
@@ -726,23 +709,8 @@ def conserver_command(filehandle, localcommand):
else:
print("Unknown power state.]\r")
check_power_state()
elif localcommand[0] == 'r':
sys.stdout.write('\x1b7\x1b[999;999H\x1b[6n')
sys.stdout.flush()
reply = ''
while 'R' not in reply:
try:
reply += sys.stdin.read(1)
except IOError:
pass
reply = reply.replace('\x1b[', '')
reply = reply.replace('R', '')
height, width = reply.split(';')
sys.stdout.write('\x1b8')
sys.stdout.flush()
print('sending stty commands]')
return 'stty columns {0}\rstty rows {1}\r'.format(width, height)
#check_power_state()
elif localcommand[0] == '?':
print("help]\r")
print(". exit console\r")
@@ -753,7 +721,6 @@ def conserver_command(filehandle, localcommand):
print("pbs boot to setup\r")
print("pbn boot to network\r")
print("pb<ent> boot to default\r")
print("r send stty command to resize terminal\r")
print("<cr> abort command\r")
elif localcommand[0] == '\x0d':
print("ignored]\r")
@@ -857,17 +824,27 @@ powertime = None
def check_power_state():
tlvdata.send(
session.connection,
{'operation': 'retrieve',
'path': '/nodes/' + consolename + '/power/state'})
return
global powerstate, powertime
for rsp in session.read('/nodes/' + consolename + '/power/state'):
if type(rsp) == dict and 'state' in rsp:
newpowerstate = rsp['state']['value']
powertime = time.time()
if newpowerstate != powerstate and newpowerstate == 'off':
sys.stdout.write("\x1b[2J\x1b[;H[powered off]\r\n")
powerstate = newpowerstate
elif type(rsp) == dict and '_requestdone' in rsp:
break
elif type(rsp) == dict:
updatestatus(rsp)
else:
sys.stdout.write(rsp)
sys.stdout.flush()
while inconsole or not doexit:
if inconsole:
rdylist, _, _ = select.select(
(sys.stdin, session.connection), (), (), 10)
(sys.stdin, session.connection), (), (), 60)
for fh in rdylist:
if fh == session.connection:
# this only should get called in the
@@ -883,9 +860,6 @@ while inconsole or not doexit:
updatestatus(data)
continue
if data is not None:
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
try:
sys.stdout.write(data)
except IOError: # Some times circumstances are bad
@@ -928,10 +902,8 @@ while inconsole or not doexit:
tlvdata.send(session.connection, myinput)
except IOError:
pass
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
powertime = time.time()
powerstate = True
check_power_state()
#if powerstate is None or powertime < time.time() - 60: # Check powerstate every 60 seconds
# check_power_state()
else:
currcommand = prompt()
try:
-19
View File
@@ -1,19 +0,0 @@
#!/bin/sh
# This will take a given directory and make a 'big floppy image'
# out of it, suitable for nodemedia upload.
if [ -z "$1" -o -z "$2" ]; then
echo "Usage: $0 <directory> <imagefile>"
exit 1
fi
# Get the needed payload side
SIZE=$(du -sB 512 $1|awk '{print $1}')
ENTRIES=$(find $1 |wc -l)
# Also, each file and directory has overhead, pad size by 32kb per file,
# which should be overkill but other values proved to be inadequate
# A deeper understanding of VFAT would probably allow for more precise value
SIZE=$((SIZE + ENTRIES * 64))
dd if=/dev/zero of=$2 bs=512 count=$SIZE
# Make a big single sided floppy with many many tracks, saves on complex math
mformat -i $2 -d 1 -t $SIZE -s 1 -h 1 ::
mcopy -i $2 -s $1/* ::
+1 -10
View File
@@ -37,14 +37,10 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog [-b] noderange [list of attributes] \
\n %prog -c noderange <list of attributes> \
\n %prog -e noderange <attribute names to set> \
\n %prog noderange attribute1=value1 attribute2=value,...
\n ''')
argparser.add_option('-b', '--blame', action='store_true',
help='Show information about how attributes inherited')
argparser.add_option('-e', '--environment', action='store_true',
help='Set attributes, but from environment variable of '
'same name')
argparser.add_option('-c', '--clear', action='store_true',
help='Clear attributes')
(options, args) = argparser.parse_args()
@@ -59,7 +55,6 @@ try:
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
@@ -67,11 +62,7 @@ exitcode = 0
nodetype="noderange"
if len(args) > 1:
if "=" in args[1] or options.clear or options.environment:
if "=" in args[1] and options.clear:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
if "=" in args[1] or options.clear:
exitcode=client.updateattrib(session,args,nodetype, noderange, options)
try:
# setting user output to what the user inputs
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
errorNodes = set([])
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
# Determine which nodes were successful and print them
allNodes = set([])
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
allNodes.add(node['item']['href'].replace("/", ""))
goodNodes = allNodes - errorNodes
for node in goodNodes:
print node + ": BMC Reset Successful"
sys.exit(success)
+6 -8
View File
@@ -49,7 +49,6 @@ except IndexError:
'Usage: {0} <noderange> [default|cd|network|setup|hd]\n'.format(
sys.argv[0]))
sys.exit(1)
client.check_globbing(noderange)
bootdev = None
if len(sys.argv) > 2:
bootdev = sys.argv[2]
@@ -62,12 +61,11 @@ if options.biosmode:
else:
bootmode = 'uefi'
errnodes = set([])
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist,
errnodes=errnodes)
if errnodes:
noderange = noderange + ',-(' + ','.join(errnodes) + ')'
rc |= session.simple_noderange_command(noderange, '/power/state', 'boot')
sys.exit(rc)
persistent=options.persist)
if rc:
sys.exit(rc)
else:
sys.exit(session.simple_noderange_command(noderange, '/power/state', 'boot'))
-213
View File
@@ -1,213 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import signal
import optparse
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
class NullOpt(object):
blame = None
clear = None
def bailout(msg, code=1):
sys.stderr.write(msg + '\n')
sys.exit(code)
argparser = optparse.OptionParser()
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
action='store_true', default=False,
help='Compare given settings to default or list settings '
'that are non default')
argparser.add_option('-d', '--detail', dest='detail',
action='store_true', default=False,
help='Provide verbose information as available, such as '
'help text and possible valid values')
argparser.add_option('-x', '--exclude', dest='exclude',
action='store_true', default=False,
help='Treat positional arguments as items to not '
'examine, compare, or restore default')
(options, args) = argparser.parse_args()
cfgpaths = {
'bmc.ipv4_address': (
'configuration/management_controller/net_interfaces/management',
'ipv4_address'),
'bmc.ipv4_method': (
'configuration/management_controller/net_interfaces/management',
'ipv4_configuration'),
'bmc.ipv4_gateway': (
'configuration/management_controller/net_interfaces/management',
'ipv4_gateway'),
}
autodeps = {
'bmc.ipv4_address': (('bmc.ipv4_method', 'static'),)
}
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
setmode = None
assignment = {}
queryparms = {}
printsys = []
setsys = {}
forceset = False
needval = None
if len(args) == 1 or options.exclude:
if not options.exclude:
printsys = 'all'
for candidate in cfgpaths:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
def _assign_value():
if key not in cfgpaths:
setsys[key] = value
for depkey, depval in autodeps.get(key, []):
assignment[depkey] = depval
assignment[key] = value
for param in args[1:]:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
setmode = True
forceset = True
continue
if needval:
key = needval
value = param
_assign_value()
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
_assign_value()
elif param[-1] == ':':
needval = param[:-1]
else:
needval = param
else:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
printsys.append(param)
elif param not in cfgpaths:
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
session = client.Command()
rcode = 0
if setmode:
if options.exclude:
sys.stderr.write('Cannot use exclude and assign at the same time\n')
sys.exit(1)
updatebypath = {}
attrnamebypath = {}
for key in assignment:
if key not in cfgpaths:
path = 'configuration/system/all'
attrib = key
else:
path, attrib = cfgpaths[key]
if path not in updatebypath:
updatebypath[path] = {}
attrnamebypath[path] = {}
updatebypath[path][attrib] = assignment[key]
attrnamebypath[path][attrib] = key
# well, we want to expand things..
# check ipv4, if requested change method to static
for path in updatebypath:
for fr in session.update('/noderange/{0}/{1}'.format(noderange, path),
updatebypath[path]):
for node in fr['databynode']:
r = fr['databynode'][node]
if 'error' in r:
sys.stderr.write(node + ': ' + r['error'] + '\n')
if 'errorcode' in r:
rcode |= r['errorcode']
if 'value' not in r:
continue
keyval = r['value']
key, val = keyval.split('=')
if key in attrnamebypath[path]:
key = attrnamebypath[path][key]
print('{0}: {1}: {2}'.format(node, key, val))
else:
for path in queryparms:
if options.comparedefault:
continue
client.print_attrib_path(path, session, list(queryparms[path]),
NullOpt(), queryparms[path])
if printsys or options.exclude:
if printsys == 'all':
printsys = []
path = '/noderange/{0}/configuration/system/all'.format(noderange)
client.print_attrib_path(path, session, printsys,
options)
sys.exit(rcode)
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange attribute1=value1 attribute2=value,...
\n ''')
(options, args) = argparser.parse_args()
requestargs=None
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
attribs = {'name': noderange}
for arg in args[1:]:
key, val = arg.split('=')
attribs[key] = val
for r in session.create('/noderange/', attribs):
if 'error' in r:
sys.stderr.write(r['error'] + '\n')
exitcode |= 1
if 'created' in r:
print('{0}: created'.format(r['created']))
sys.exit(exitcode)
-286
View File
@@ -1,286 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import csv
import optparse
import os
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
tabformat = '{0:>15}|{1:>15}|{2:>15}|{3:>36}|{4:>17}|{5:>12}|{6:>48}'
columns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
'Current IP Addresses']
delimit = ['-' * 15, '-' * 15, '-' * 15, '-' * 36, '-' * 17, '-' * 12,
'-' * 48]
def dumpmacs(procinfo):
return ','.join(procinfo['macs']) # + procinfo.get('relatedmacs', []))
def print_disco(options, session, currmac):
procinfo = {}
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
procinfo.update(tmpinfo)
record = [procinfo['nodename'], procinfo['modelnumber'],
procinfo['serialnumber'], procinfo['uuid'], dumpmacs(procinfo),
','.join(procinfo['types']),
','.join(sorted(procinfo['ipaddrs']))]
if options.csv:
csv.writer(sys.stdout).writerow(record)
else:
print(tabformat.format(*record))
def process_header(header):
# normalize likely header titles
fields = []
broken = False
for datum in header:
datum = datum.lower()
if datum.startswith('node') or datum.startswith('name'):
fields.append('node')
elif datum in ('nodegroup', 'nodegroups', 'group', 'groups'):
fields.append('groups')
elif datum.startswith('mac') or datum.startswith('ether'):
fields.append('mac')
elif datum.startswith('serial') or datum in ('sn', 's/n'):
fields.append('serial')
elif datum == 'uuid':
fields.append('uuid')
elif datum in ('bmc', 'imm', 'xcc'):
fields.append('hardwaremanagement.manager')
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
fields.append('net.bmc.gateway')
elif datum in ('bmcuser', 'username', 'user'):
fields.append('secret.hardwaremanagementuser')
elif datum in ('bmcpass', 'password', 'pass'):
fields.append('secret.hardwaremanagementpassword')
else:
print("Unrecognized column name {0}".format(datum))
broken = True
if broken:
sys.exit(1)
return tuple(fields)
def datum_complete(datum):
if 'node' not in datum or not datum['node']:
sys.stderr.write('Nodename is a required field')
return False
provided = set(datum)
required = set(['serial', 'uuid', 'mac'])
for field in provided & required:
if datum[field]:
break
else:
sys.stderr.write('One of the fields "Serial Number", "UUID", or '
'"MAC Address" must be provided')
return False
return True
searchkeys = set(['mac', 'serial', 'uuid'])
def search_record(datum, options, session):
for searchkey in searchkeys:
options.__dict__[searchkey] = None
for searchkey in searchkeys & set(datum):
options.__dict__[searchkey] = datum[searchkey]
return list(list_matching_macs(options, session))
def datum_to_attrib(datum):
for key in ('serial', 'uuid', 'mac'):
try:
del datum[key]
except KeyError:
pass
datum['name'] = datum['node']
del datum['node']
return datum
def import_csv(options, session):
nodedata = []
with open(options.importfile, 'r') as datasrc:
records = csv.reader(datasrc)
fields = process_header(next(records))
for record in records:
currfields = list(fields)
nodedatum = {}
for datum in record:
nodedatum[currfields.pop(0)] = datum
if not datum_complete(nodedatum):
sys.exit(1)
if not search_record(nodedatum, options, session):
sys.stderr.write(
"Could not match the following data: " +
repr(nodedatum) + '\n')
sys.exit(1)
nodedata.append(nodedatum)
for datum in nodedata:
maclist = search_record(datum, options, session)
datum = datum_to_attrib(datum)
nodename = datum['name']
for res in session.create('/nodes/', datum):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
continue
elif 'created' in res:
print('Defined ' + res['created'])
else:
print(repr(res))
for mac in maclist:
for res in session.update('/discovery/by-mac/{0}'.format(mac),
{'node': nodename}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
continue
elif 'assigned' in res:
print('Discovered ' + res['assigned'])
else:
print(repr(res))
def list_discovery(options, session):
if options.csv:
csv.writer(sys.stdout).writerow(columns)
else:
print(tabformat.format(*columns))
print(tabformat.format(*delimit))
for mac in list_matching_macs(options, session):
print_disco(options, session, mac)
def clear_discovery(options, session):
for mac in list_matching_macs(options, session):
for res in session.delete('/discovery/by-mac/{0}'.format(mac)):
if 'deleted' in res:
print('Cleared info for {0}'.format(res['deleted']))
else:
print(repr(res))
def list_matching_macs(options, session):
path = '/discovery/'
if options.model:
path += 'by-model/{0}/'.format(options.model)
if options.serial:
path += 'by-serial/{0}/'.format(options.serial)
if options.uuid:
path += 'by-uuid/{0}/'.format(options.uuid)
if options.type:
path += 'by-type/{0}/'.format(options.type)
if options.state:
if options.state == 'unknown':
options.state = 'unidentified'
path += 'by-state/{0}/'.format(options.state).lower()
if options.mac:
path += 'by-mac/{0}'.format(options.mac)
result = list(session.read(path))[0]
if 'error' in result:
return []
return [options.mac.replace(':', '-')]
else:
path += 'by-mac/'
return [x['item']['href'] for x in session.read(path)]
def assign_discovery(options, session):
abort = False
if options.importfile:
return import_csv(options, session)
if 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)
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
{'node': options.node}):
if 'assigned' in res:
print('Assigned: {0}'.format(res['assigned']))
else:
print(repr(res))
def main():
parser = optparse.OptionParser(
usage='Usage: %prog [list|assign|rescan|clear] [options]')
# -a for 'address' maybe?
# order by
# show state (discovered or..
# nodediscover approve?
# flush to clear old data out? (e.g. no good way to age pxe data)
# also delete discovery datum... more targeted
# defect: -t lenovo-imm returns all
parser.add_option('-m', '--model', dest='model',
help='Operate with nodes matching the specified model '
'number', metavar='MODEL')
parser.add_option('-d', '--discoverystate', dest='state',
help='The discovery state of the entries (discovered, '
'identified, and unidentified)')
parser.add_option('-s', '--serial', dest='serial',
help='Operate against the system matching the specified '
'serial number', metavar='SERIAL')
parser.add_option('-u', '--uuid', dest='uuid',
help='Operate against the system matching the specified '
'UUID', metavar='UUID')
parser.add_option('-n', '--node', help='Operate with the given nodename')
parser.add_option('-e', '--ethaddr', dest='mac',
help='Operate against the system with the specified MAC '
'address', metavar='MAC')
parser.add_option('-t', '--type', dest='type',
help='Operate against the system of the specified type',
metavar='TYPE')
parser.add_option('-c', '--csv', dest='csv',
help='Use CSV formatted output', action='store_true')
parser.add_option('-i', '--import', dest='importfile',
help='Import bulk assignment data from given CSV file',
metavar='IMPORT.CSV')
(options, args) = parser.parse_args()
if len(args) == 0 or args[0] not in ('list', 'assign', 'rescan', 'clear'):
parser.print_help()
sys.exit(1)
session = client.Command()
if args[0] == 'list':
list_discovery(options, session)
if args[0] == 'clear':
clear_discovery(options, session)
if args[0] == 'assign':
assign_discovery(options, session)
if args[0] == 'rescan':
list(session.update('/discovery/rescan', {'rescan': 'start'}))
print("Rescan initiated")
if __name__ == '__main__':
main()
+1 -10
View File
@@ -15,7 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import codecs
from datetime import datetime as dt
import optparse
import os
@@ -33,8 +32,6 @@ if path.startswith('/opt'):
import confluent.client as client
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange [clear]")
(options, args) = argparser.parse_args()
@@ -43,17 +40,11 @@ try:
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
deletemode = False
if len(sys.argv) > 3:
argparser.print_help()
sys.exit(1)
if len(sys.argv) == 3:
if sys.argv[2] == 'clear':
deletemode = True
else:
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
+14 -94
View File
@@ -19,7 +19,6 @@ import optparse
import os
import signal
import sys
import time
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
@@ -31,7 +30,7 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.screensqueeze as sq
exitcode = 0
@@ -64,107 +63,28 @@ def printfirm(node, prefix, data):
version = ' '.join(builddesc)
print('{0}: {1}: {2}'.format(node, prefix, version))
components = ['all']
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
(options, args) = argparser.parse_args()
upfile = None
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'update':
upfile = args[2]
else:
components = []
for arg in args[1:]:
components += arg.split(',')
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
def get_update_progress(session, url):
for res in session.read(url):
status = res['phase']
percent = res['progress']
detail = res['detail']
if status == 'error':
text = 'error!'
else:
text = '{0}: {1:3.0f}%'.format(status, percent)
return text, status, detail
def update_firmware(session, filename):
global exitcode
output = sq.ScreenPrinter(noderange, session)
nodeurls = {}
filename = os.path.abspath(filename)
resource = '/noderange/{0}/inventory/firmware/updates/active'.format(
noderange)
upargs = {'filename': filename}
if options.backup:
upargs['bank'] = 'backup'
noderrs = {}
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
output.set_output(nodename, 'error!')
noderrs[nodename] = res['databynode'][nodename].get(
'error', 'Unknown Error')
continue
watchurl = res['created']
currnode = watchurl.split('/')[1]
nodeurls[currnode] = '/' + watchurl
while nodeurls:
for node in list(nodeurls):
progress, status, err = get_update_progress(
session, nodeurls[node])
if status == 'error':
exitcode = 1
noderrs[node] = err
if status in ('error', 'complete', 'pending'):
list(session.delete(nodeurls[node]))
del nodeurls[node]
output.set_output(node, progress)
time.sleep(2)
allerrnodes = ','.join(noderrs)
if noderrs:
sys.stderr.write(
'Nodes had errors updating ({0})!\n'.format(allerrnodes))
for node in noderrs:
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
def show_firmware(session):
firmware_shown = False
for component in components:
for res in session.read(
'/noderange/{0}/inventory/firmware/all/{1}'.format(
noderange, component)):
printerror(res)
if 'databynode' not in res:
continue
for node in res['databynode']:
printerror(res['databynode'][node], node)
if 'firmware' not in res['databynode'][node]:
continue
for inv in res['databynode'][node]['firmware']:
for prefix in inv:
firmware_shown = True
printfirm(node, prefix, inv[prefix])
if not firmware_shown:
argparser.print_help()
try:
session = client.Command()
if upfile is None:
show_firmware(session)
else:
update_firmware(session, upfile)
for res in session.read('/noderange/{0}/inventory/firmware/all/all'.format(
noderange)):
printerror(res)
if 'databynode' not in res:
continue
for node in res['databynode']:
printerror(res['databynode'][node], node)
if 'firmware' not in res['databynode'][node]:
continue
for inv in res['databynode'][node]['firmware']:
for prefix in inv:
printfirm(node, prefix, inv[prefix])
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
+2 -11
View File
@@ -36,15 +36,11 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog [options] \
\n %prog [options] nodegroup [list of attributes] \
\n %prog [options] nodegroup nodes=value1,value2 \
\n %prog -e nodegroup <attribute names to set> \
\n %prog [options] nodegroup nodes=value1,value2
\n %prog [options] nodegroup group=value1,value2 \
\n %prog [options] nodegroup group=value1,value2
\n ''')
argparser.add_option('-b', '--blame', action='store_true',
help='Show information about how attributes inherited')
argparser.add_option('-e', '--environment', action='store_true',
help='Set attributes, but from environment variable of '
'same name')
argparser.add_option('-c', '--clear', action='store_true',
help='Clear variables')
(options, args) = argparser.parse_args()
@@ -60,7 +56,6 @@ try:
nodelist = '/{0}/{1}/'.format(nodetype,nodegroups)
except IndexError:
nodelist = '/nodegroups/'
client.check_globbing(nodegroups)
session = client.Command()
exitcode = 0
@@ -68,10 +63,6 @@ exitcode = 0
if len(args) > 1:
showtype = 'all'
if "=" in args[1] and options.clear:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options)
try:
# setting user output to what the user inputs
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange attribute1=value1 attribute2=value,...
\n ''')
(options, args) = argparser.parse_args()
requestargs=None
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
attribs = {'name': noderange}
for arg in args[1:]:
key, val = arg.split('=')
attribs[key] = val
for r in session.create('/nodegroups/', attribs):
if 'error' in r:
sys.stderr.write(r['error'] + '\n')
exitcode |= 1
if 'created' in r:
print('{0}: created'.format(r['created']))
sys.exit(exitcode)
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange
\n ''')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
sys.exit(1)
noderange = args[0]
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
for r in session.delete('/nodegroups/{0}'.format(noderange)):
if 'error' in r:
sys.stderr.write(r['error'] + '\n')
exitcode |= 1
if 'deleted' in r:
print('{0}: deleted'.format(r['deleted']))
sys.exit(exitcode)
-1
View File
@@ -41,7 +41,6 @@ try:
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
def main():
+3 -3
View File
@@ -38,13 +38,13 @@ try:
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
identifystate = None
if len(sys.argv) > 2:
identifystate = sys.argv[2]
else:
argparser.print_help()
sys.exit(1)
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
sys.exit(
+8 -43
View File
@@ -15,10 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import codecs
import optparse
import os
import re
import signal
import sys
@@ -33,10 +31,6 @@ if path.startswith('/opt'):
import confluent.client as client
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
filters = []
def pretty(text):
if text == 'pcislot':
@@ -46,17 +40,14 @@ def pretty(text):
return text
def print_mem_info(node, prefix, meminfo):
capacity = meminfo['capacity_mb'] / 1024
memdescfmt = '{0}GB PC'
if meminfo['memory_type'] == 'DDR3 SDRAM':
memdescfmt += '3-{1} '
elif meminfo['memory_type'] == 'DDR4 SDRAM':
memdescfmt += '4-{1} '
else:
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
return
if meminfo['ecc']:
memdescfmt += 'ECC '
capacity = meminfo['capacity_mb'] / 1024
memdescfmt += meminfo['module_type']
memdesc = memdescfmt.format(capacity, meminfo['speed'])
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
@@ -85,37 +76,19 @@ def printerror(res, node=None):
exitcode = 1
url = '/noderange/{0}/inventory/hardware/all/all'
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
(options, args) = argparser.parse_args()
try:
noderange = sys.argv[1]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
if len(args) > 1:
if args[1] == 'firm':
os.execlp('nodefirmware', 'nodefirmware', noderange)
else:
url = '/noderange/{0}/inventory/hardware/all/system'
for arg in args:
for arg in arg.split(','):
if arg == 'serial':
filters.append(re.compile('serial number'))
elif arg == 'model':
filters.append(re.compile('^model'))
filters.append(re.compile('product name'))
elif arg == 'uuid':
filters.append(re.compile('uuid'))
elif arg == 'mac':
filters.append(re.compile('mac address'))
url = '/noderange/{0}/inventory/hardware/all/all'
if len(args) > 1 and args[1] == 'firm':
os.execlp('nodefirmware', 'nodefirmware', noderange)
try:
session = client.Command()
for res in session.read(url.format(noderange)):
for res in session.read('/noderange/{0}/inventory/hardware/all/all'.format(
noderange)):
printerror(res)
if 'databynode' not in res:
continue
@@ -126,8 +99,7 @@ try:
for inv in res['databynode'][node]['inventory']:
prefix = inv['name']
if not inv['present']:
if not filters:
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)
@@ -135,16 +107,9 @@ try:
info.pop('chassis_extra', None)
info.pop('product_extra', None)
if 'memory_type' in info:
if not filters:
print_mem_info(node, prefix, info)
print_mem_info(node, prefix, info)
continue
for datum in info:
if filters:
for filter in filters:
if filter.match(datum.lower()):
break
else:
continue
if info[datum] is None:
continue
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
-1
View File
@@ -47,7 +47,6 @@ def main():
nodelist = '/noderange/{0}/nodes/'.format(noderange)
except IndexError:
nodelist = '/nodes/'
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
showtype='all'
-190
View File
@@ -1,190 +0,0 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
import time
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.screensqueeze as sq
exitcode = 0
def get_update_progress(session, url):
for res in session.read(url):
status = res['phase']
percent = res['progress']
detail = res['detail']
if status == 'error':
text = 'error!'
else:
text = '{0}: {1:3.0f}%'.format(status, percent)
return text, status, detail
def printerror(res, node=None):
global exitcode
if 'errorcode' in res:
exitcode = res['errorcode']
if 'error' in res:
if node:
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
else:
sys.stderr.write('{0}\n'.format(res['error']))
if 'errorcode' not in res:
exitcode = 1
for node in res.get('databynode', ()):
printerror(res['databynode'][node], node)
def attach_media(noderange, media):
global exitcode
session = client.Command()
if ':' not in media:
sys.stderr.write('Full URL required for attach\n')
sys.exit(1)
resource = '/noderange/{0}/media/attach'.format(noderange)
for res in session.update(resource, {'url': media}):
printerror(res)
list_media(noderange, media)
def list_media(noderange, media):
session = client.Command()
resource = '/noderange/{0}/media/current'.format(noderange)
for res in session.read(resource):
printerror(res)
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):
name += ' (insecure)'
if not name:
continue
print('{0}: {1}'.format(node, url + '/' + name if url else name))
def detach_media(noderange, media):
global exitcode
session = client.Command()
resource = '/noderange/{0}/media/detach'.format(noderange)
for res in session.update(resource, {'detachall': 1}):
printerror(res)
def upload_media(noderange, media):
global exitcode
if not os.path.exists(media):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
media))
sys.exit(404)
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
filename = os.path.abspath(media)
resource = '/noderange/{0}/media/uploads/'.format(noderange)
upargs = {'filename': filename}
noderrs = {}
nodeurls = {}
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
output.set_output(nodename, 'error!')
noderrs[nodename] = res['databynode'][nodename].get(
'error', 'Unknown Error')
continue
watchurl = res['created']
currnode = watchurl.split('/')[1]
nodeurls[currnode] = '/' + watchurl
while nodeurls:
for node in list(nodeurls):
progress, status, err = get_update_progress(
session, nodeurls[node])
if status == 'error':
exitcode = 1
noderrs[node] = err
if status in ('error', 'complete', 'pending'):
list(session.delete(nodeurls[node]))
del nodeurls[node]
output.set_output(node, progress)
time.sleep(2)
allerrnodes = ','.join(noderrs)
if noderrs:
sys.stderr.write(
'Nodes had errors receiving media ({0})!\n'.format(allerrnodes))
for node in noderrs:
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
list_media(noderange, media)
funmap = {
'upload': upload_media,
'attach': attach_media,
'detachall': detach_media,
'list': list_media,
}
class OptParser(optparse.OptionParser):
def format_epilog(self, formatter):
return self.expand_prog_name(self.epilog)
def main():
argparser = OptParser(
usage="Usage: %prog <noderange> [list|upload|attach|detachall] "
"<filename>|all|<url>",
epilog='\nupload will take the specified file and upload it to the '
'BMC.\n\n'
'attach will instruct the BMC to connect a remote media to the '
'specified url.\n\ndetachall will remove *ALL* uploaded and '
'attached urls from the BMC\n\nlist shows currently mounted '
'media.\n\nSee `man %prog` for more info.\n')
(options, args) = argparser.parse_args()
media = None
try:
noderange = args[0]
operation = args[1]
arglength = 2
if operation in ('attach', 'upload'):
media = args[2]
arglength = 3
if len(args) > arglength:
argparser.print_help()
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
try:
handler = funmap[operation]
except KeyError:
argparser.print_help()
sys.exit(1)
handler(noderange, media)
if __name__ == '__main__':
main()
+1 -1
View File
@@ -40,7 +40,7 @@ try:
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
setstate = None
if len(sys.argv) > 2:
if setstate == 'softoff':
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange
\n ''')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
sys.exit(1)
noderange = args[0]
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
for r in session.delete('/noderange/{0}'.format(noderange)):
if 'error' in r:
sys.stderr.write(r['error'] + '\n')
exitcode |= 1
if 'deleted' in r:
print('{0}: deleted'.format(r['deleted']))
sys.exit(exitcode)
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
errorNodes = set([])
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
# Determine which nodes were successful and print them
allNodes = set([])
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
allNodes.add(node['item']['href'].replace("/", ""))
goodNodes = allNodes - errorNodes
for node in goodNodes:
print node + ": Reseat successful"
sys.exit(success)
+24 -43
View File
@@ -34,25 +34,21 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of commands to run at a time')
concurrentprocs = 168
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser = optparse.OptionParser(
usage="Usage: %prog node commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.disable_interspersed_args()
(options, args) = argparser.parse_args()
if len(args) < 2:
argparser.print_help()
sys.exit(1)
concurrentprocs = options.count
client.check_globbing(args[0])
c = client.Command()
cmdstr = " ".join(args[1:])
@@ -60,14 +56,10 @@ def run():
all = set([])
pipedesc = {}
pendingexecs = deque()
exitcode = 0
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
ex = exp['databynode']
for node in ex:
cmd = ex[node]['value'].encode('utf-8')
cmdv = shlex.split(cmd)
@@ -76,39 +68,28 @@ def run():
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
exitcode = 0
rdy, _, _ = select.select(all, [], [], 10)
while all:
pernodeout = {}
for r in rdy:
data = r.readline()
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0):
data = r.readline()
if data:
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = []
pernodeout[node].append(data)
else:
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.flush()
if data:
node = desc['node']
if desc['type'] == 'stdout':
sys.stdout.write('{0}: {1}'.format(node,data))
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
r.close()
if desc['type'] == 'stdout' and pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
sys.stderr.write('{0}: {1}'.format(node, data))
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
if desc['type'] == 'stdout' and pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
if all:
rdy, _, _ = select.select(all, [], [], 10)
sys.exit(exitcode)
@@ -126,4 +107,4 @@ def run_cmdv(node, cmdv, all, pipedesc):
if __name__ == '__main__':
run()
run()
+1 -5
View File
@@ -36,7 +36,6 @@ import confluent.client as client
sensorcollections = {
'all': 'sensors/hardware/all/all',
'energy': 'sensors/hardware/energy/all',
'temperature': 'sensors/hardware/temperature/all',
'temp': 'sensors/hardware/temperature/all',
'power': 'sensors/hardware/power/all',
@@ -65,7 +64,6 @@ if options.numreadings:
options.interval = 1
try:
noderange = args[0]
client.check_globbing(noderange)
except IndexError:
argparser.print_help()
sys.exit(1)
@@ -80,6 +78,7 @@ for sensorgroup in args[1:]:
sensors.append('sensors/hardware/all/' + sensor)
if not sensors:
sensors = ['sensors/hardware/all/all']
session = client.Command()
exitcode = 0
sensorheaders = {}
@@ -126,8 +125,6 @@ def sensorpass(showout=True, appendtime=False):
if showout:
if sensedata['value'] is None:
showval = ''
elif isinstance(sensedata['value'], float):
showval = u' {0:.5f} '.format(sensedata['value'])
else:
showval = u' {0} '.format(sensedata['value'])
if sensedata['units'] not in (None, u''):
@@ -149,7 +146,6 @@ def sensorpass(showout=True, appendtime=False):
'%Y-%m-%dT%H:%M:%S')
print(u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval).encode('utf-8'))
sys.stdout.flush()
return resultdata
-1
View File
@@ -48,7 +48,6 @@ argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
try:
noderange = args[0]
client.check_globbing(noderange)
except IndexError:
argparser.print_help()
sys.exit(1)
+26 -46
View File
@@ -19,6 +19,7 @@ from collections import deque
import optparse
import os
import select
import shlex
import signal
import subprocess
import sys
@@ -33,26 +34,21 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of commands to run at a time')
concurrentprocs = 168
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser = optparse.OptionParser(
usage="Usage: %prog node commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.disable_interspersed_args()
(options, args) = argparser.parse_args()
if len(args) < 2:
argparser.print_help()
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
cmdstr = " ".join(args[1:])
@@ -60,56 +56,40 @@ def run():
all = set([])
pipedesc = {}
pendingexecs = deque()
exitcode = 0
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
ex = exp['databynode']
for node in ex:
cmd = ex[node]['value'].encode('utf-8')
cmdv = ['ssh', node, cmd]
cmdv = ['ssh', node] + shlex.split(cmd)
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
exitcode = 0
rdy, _, _ = select.select(all, [], [], 10)
while all:
pernodeout = {}
for r in rdy:
data = r.readline()
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0):
data = r.readline()
if data:
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = []
pernodeout[node].append(data)
else:
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.flush()
if data:
node = desc['node']
if desc['type'] == 'stdout':
sys.stdout.write('{0}: {1}'.format(node,data))
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
r.close()
if desc['type'] == 'stdout' and pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
sys.stderr.write('{0}: {1}'.format(node, data))
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
if desc['type'] == 'stdout' and pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
if all:
rdy, _, _ = select.select(all, [], [], 10)
sys.exit(exitcode)
@@ -127,4 +107,4 @@ def run_cmdv(node, cmdv, all, pipedesc):
if __name__ == '__main__':
run()
run()
+44 -176
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 Lenovo
# Copyright 2015-2016 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import anydbm as dbm
import errno
import hashlib
import os
import shlex
import socket
import ssl
import sys
@@ -27,15 +26,6 @@ import confluent.tlvdata as tlvdata
SO_PASSCRED = 16
_attraliases = {
'bmc': 'hardwaremanagement.manager',
'bmcuser': 'secret.hardwaremanagementuser',
'bmcpass': 'secret.hardwaremanagementpassword',
}
def cprint(txt):
print(txt)
sys.stdout.flush()
def _parseserver(string):
if ']:' in string:
@@ -55,7 +45,6 @@ class Command(object):
def __init__(self, server=None):
self._prevkeyname = None
self.connection = None
self._currnoderange = None
if server is None:
if 'CONFLUENT_HOST' in os.environ:
self.serverloc = os.environ['CONFLUENT_HOST']
@@ -88,10 +77,8 @@ class Command(object):
def add_precede_key(self, keyname):
self._prevkeyname = keyname
def handle_results(self, ikey, rc, res, errnodes=None):
def handle_results(self, ikey, rc, res):
if 'error' in res:
if errnodes is not None:
errnodes.add(self._currnoderange)
sys.stderr.write('Error: {0}\n'.format(res['error']))
if 'errorcode' in res:
return res['errorcode']
@@ -102,8 +89,6 @@ class Command(object):
res = res['databynode']
for node in res:
if 'error' in res[node]:
if errnodes is not None:
errnodes.add(node)
sys.stderr.write('{0}: Error: {1}\n'.format(
node, res[node]['error']))
if 'errorcode' in res[node]:
@@ -118,16 +103,15 @@ class Command(object):
else:
val = repr(res[node][ikey])
if self._prevkeyname and self._prevkeyname in res[node]:
cprint('{0}: {2}->{1}'.format(
print('{0}: {2}->{1}'.format(
node, val, res[node][self._prevkeyname]['value']))
else:
cprint('{0}: {1}'.format(node, val))
print('{0}: {1}'.format(node, val))
return rc
def simple_noderange_command(self, noderange, resource, input=None,
key=None, errnodes=None, **kwargs):
key=None, **kwargs):
try:
self._currnoderange = noderange
rc = 0
if resource[0] == '/':
resource = resource[1:]
@@ -139,16 +123,15 @@ class Command(object):
if input is None:
for res in self.read('/noderange/{0}/{1}'.format(
noderange, resource)):
rc = self.handle_results(ikey, rc, res, errnodes)
rc = self.handle_results(ikey, rc, res)
else:
kwargs[ikey] = input
for res in self.update('/noderange/{0}/{1}'.format(
noderange, resource), kwargs):
rc = self.handle_results(ikey, rc, res, errnodes)
self._currnoderange = None
rc = self.handle_results(ikey, rc, res)
return rc
except KeyboardInterrupt:
cprint('')
print('')
return 0
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
@@ -172,7 +155,7 @@ class Command(object):
rc = self.handle_results(ikey, rc, res)
return rc
except KeyboardInterrupt:
cprint('')
print('')
return 0
def read(self, path, parameters=None):
@@ -254,7 +237,7 @@ class Command(object):
"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))
print 'Adding new key for %s:%s' % (server, port)
khf[hostid] = fingerprint
@@ -288,59 +271,42 @@ def attrrequested(attr, attrlist, seenattributes):
truename = candidate
if candidate.startswith('hm'):
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
if candidate in _attraliases:
candidate = _attraliases[candidate]
if candidate.lower() == attr.lower():
if candidate == attr:
seenattributes.add(truename)
return True
elif attr.lower().startswith(candidate.lower() + '.'):
elif attr.startswith(candidate + '.'):
seenattributes.add(truename)
return True
return False
def printattributes(session, requestargs, showtype, nodetype, noderange, options):
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
return print_attrib_path(path, session, requestargs, options)
def print_attrib_path(path, session, requestargs, options, rename=None):
exitcode = 0
seenattributes = set([])
for res in session.read(path):
for res in session.read('/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
continue
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):
for node in res['databynode']:
for attr in res['databynode'][node]:
seenattributes.add(attr)
if rename:
printattr = rename.get(attr, attr)
else:
printattr = attr
currattr = res['databynode'][node][attr]
if show_attr(attr, requestargs, seenattributes, options):
if (requestargs is None or requestargs == [] or attrrequested(attr, requestargs, seenattributes)):
if 'value' in currattr:
if currattr['value'] is not None:
val = currattr['value']
if isinstance(val, list):
val = ','.join(val)
attrout = '{0}: {1}: {2}'.format(
node, printattr, val)
node, attr, currattr['value'])
else:
attrout = '{0}: {1}:'.format(node, printattr)
attrout = '{0}: {1}:'.format(node, attr)
elif 'isset' in currattr:
if currattr['isset']:
attrout = '{0}: {1}: ********'.format(node,
printattr)
attrout = '{0}: {1}: ********'.format(node, attr)
else:
attrout = '{0}: {1}:'.format(node, printattr)
attrout = '{0}: {1}:'.format(node, attr)
elif 'broken' in currattr:
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
'{2}'.format(node, printattr,
'{2}'.format(node, attr,
currattr['broken'])
elif isinstance(currattr, list) or isinstance(currattr, tuple):
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, currattr)))
@@ -348,14 +314,11 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
dictout = []
for k, v in currattr.items:
dictout.append("{0}={1}".format(k, v))
attrout = '{0}: {1}: {2}'.format(node, printattr, ','.join(map(str, dictout)))
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, dictout)))
else:
cprint("CODE ERROR" + repr(attr))
try:
blame = options.blame
except AttributeError:
blame = False
if blame or 'broken' in currattr:
print ("CODE ERROR" + repr(attr))
if options.blame or 'broken' in currattr:
blamedata = []
if 'inheritedfrom' in currattr:
blamedata.append('inherited from group {0}'.format(
@@ -367,36 +330,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
currattr['expression']))
if blamedata:
attrout += ' (' + ', '.join(blamedata) + ')'
try:
comparedefault = options.comparedefault
except AttributeError:
comparedefault = False
if comparedefault:
try:
exclude = options.exclude
except AttributeError:
exclude = False
if ((requestargs and not exclude) or
(currattr.get('default', None) is not None and
currattr.get('value', None) is not None and
currattr['value'] != currattr['default'])):
cprint('{0}: {1}: {2} (Default: {3})'.format(
node, printattr, currattr['value'],
currattr['default']))
else:
try:
details = options.detail
except AttributeError:
details = False
if details:
if currattr.get('help', None):
attrout += ' (Help: {0})'.format(
currattr['help'].encode('utf-8'))
if currattr.get('possible', None):
attrout += ' (Choices: {0})'.format(
','.join(currattr['possible']))
cprint(attrout)
print attrout
if not exitcode:
if requestargs:
for attr in requestargs:
@@ -406,19 +340,6 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
return exitcode
def show_attr(attr, requestargs, seenattributes, options):
try:
reverse = options.exclude
except AttributeError:
reverse = False
if requestargs is None or requestargs == []:
return True
processattr = attrrequested(attr, requestargs, seenattributes)
if reverse:
processattr = not processattr
return processattr
def printgroupattributes(session, requestargs, showtype, nodetype, noderange, options):
exitcode = 0
seenattributes = set([])
@@ -456,8 +377,8 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
dictout.append("{0}={1}".format(k, v))
attrout = '{0}: {1}: {2}'.format(noderange, attr, ','.join(map(str, dictout)))
else:
cprint("CODE ERROR" + repr(attr))
cprint(attrout)
print ("CODE ERROR" + repr(attr))
print attrout
if not exitcode:
if requestargs:
for attr in requestargs:
@@ -480,81 +401,28 @@ def updateattrib(session, updateargs, nodetype, noderange, options):
exitcode = res['errorcode']
sys.stderr.write('Error: ' + res['error'] + '\n')
sys.exit(exitcode)
elif hasattr(options, 'environment') and options.environment:
for key in updateargs[1:]:
key = key.replace('.', '_')
value = os.environ.get(
key, os.environ[key.upper()])
# Let's do one pass to make sure that there's not a usage problem
for key in updateargs[1:]:
key = key.replace('.', '_')
value = os.environ.get(
key, os.environ[key.upper()])
if (nodetype == "nodegroups"):
exitcode = session.simple_nodegroups_command(noderange,
'attributes/all',
value, key)
else:
exitcode = session.simple_noderange_command(noderange,
'attributes/all',
value, key)
sys.exit(exitcode)
else:
if "=" in updateargs[1]:
try:
for val in updateargs[1:]:
val = val.split('=')
if val[0][-1] in (',', '-', '^'):
key = val[0][:-1]
if val[0][-1] == ',':
value = {'prepend': val[1]}
elif val[0][-1] in ('-', '^'):
value = {'remove': val[1]}
if len(updateargs[1:]) > 1:
for val in updateargs[1:]:
val = val.split('=')
if (nodetype == "nodegroups"):
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all'.format(noderange),
val[1],val[0])
else:
exitcode = session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),
val[1], val[0])
else:
val = updateargs[1].split('=')
if nodetype == "nodegroups" :
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all'.format(noderange),
val[1], val[0])
else:
key = val[0]
value = val[1]
if (nodetype == "nodegroups"):
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all',
value, key)
else:
exitcode = session.simple_noderange_command(noderange, 'attributes/all',
value, key)
exitcode = session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),
val[1], val[0])
except:
sys.stderr.write('Error: {0} not a valid expression\n'.format(str(updateargs[1:])))
exitcode = 1
sys.exit(exitcode)
return exitcode
# So we try to prevent bad things from happening when globbing
# We tried to head this off at the shell, but the various solutions would end
# up breaking the shell in various ways (breaking pipe capability if using
# DEBUG, breaking globbing if in pipe, etc)
# Then we tried to parse the original commandline instead, however shlex isn't
# going to parse full bourne language (e.g. knowing that '|' and '>' and
# a world of other things would not be in our command line
# so finally, just make sure the noderange appears verbatim in the command line
# if we glob to something, then bash will change noderange and this should
# detect it and save the user from tragedy
def check_globbing(noderange):
if not os.path.exists(noderange):
return True
rawargs = os.environ.get('CURRENT_CMDLINE', None)
if rawargs:
rawargs = shlex.split(rawargs)
for arg in rawargs:
if arg.startswith('$'):
arg = arg[1:]
if arg.endswith(';'):
arg = arg[:-1]
arg = os.environ.get(arg, '$' + arg)
if arg.startswith(noderange):
break
else:
sys.stderr.write(
'Shell glob conflict detected, specified target "{0}" '
'not in command line, but is a file. You can use "set -f" in '
'bash or change directories such that there is no filename '
'that would conflict.'
'\n'.format(noderange))
sys.exit(1)
@@ -1,85 +0,0 @@
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import fcntl
import sys
import struct
import termios
def get_screenwidth():
return struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
b'....'))[1]
class ScreenPrinter(object):
def __init__(self, noderange, client, textlen=4):
self.squeeze = sys.stdout.isatty()
self.textlen = textlen
self.noderange = noderange
self.client = client
self.nodeoutput = {}
self.nodelist = []
maxlen = 0
for ans in self.client.read('/noderange/{0}/nodes/'.format(noderange)):
if 'error' in ans:
sys.stderr.write(ans['error'])
continue
nodename = ans['item']['href'][:-1]
if len(nodename) > maxlen:
maxlen = len(nodename)
self.nodelist.append(nodename)
self.nodeoutput[nodename] = ''
self.nodenamelen = maxlen
self.textlen = textlen
self.fieldwidth = maxlen + textlen + 1 # 1 for column
def set_output(self, node, text):
self.nodeoutput[node] = text
if len(text) >= self.textlen:
self.textlen = len(text) + 1
self.fieldwidth = self.textlen + self.nodenamelen + 1
self.drawscreen()
def drawscreen(self):
if self.squeeze:
currwidth = get_screenwidth()
numfields = currwidth // self.fieldwidth
fieldformat = '{{0:>{0}}}:{{1:{1}}}'.format(self.nodenamelen,
self.textlen)
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
else:
numfields = 1
fieldformat = '{0}: {1}'
currfields = 0
for node in self.nodelist:
if currfields >= numfields:
sys.stdout.write('\n')
currfields = 1
else:
currfields += 1
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
sys.stdout.write('\n')
sys.stdout.flush()
if __name__ == '__main__':
import confluent.client as client
c = client.Command()
p = ScreenPrinter('n1-n13', c)
p.set_output('n3', 'Upload: 67%')
-43
View File
@@ -1,43 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2016 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 re
numregex = re.compile('([0-9]+)')
def naturalize_string(key):
"""Analyzes string in a human way to enable natural sort
:param key: The node name to analyze
:returns: A structure that can be consumed by 'sorted'
"""
return [int(text) if text.isdigit() else text.lower()
for text in re.split(numregex, key)]
def natural_sort(iterable):
"""Return a sort using natural sort if possible
:param iterable:
:return:
"""
try:
return sorted(iterable, key=naturalize_string)
except TypeError:
# The natural sort attempt failed, fallback to ascii sort
return sorted(iterable)
-217
View File
@@ -1,217 +0,0 @@
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import difflib
import re
import sys
try:
range = xrange
except NameError:
pass
numregex = re.compile('([0-9]+)')
def humanify_nodename(nodename):
"""Analyzes nodename in a human way to enable natural sort
:param nodename: The node name to analyze
:returns: A structure that can be consumed by 'sorted'
"""
return [int(text) if text.isdigit() else text.lower()
for text in re.split(numregex, nodename)]
def _colorize_line(orig, mask):
highlighted = False
newline = orig[0]
for i in range(1, len(orig)):
if i > len(mask) - 1:
if highlighted:
newline += '\x1b[0m'
newline += orig[i:]
break
if highlighted and mask[i] == ' ':
highlighted = False
newline += '\x1b[0m'
elif not highlighted and mask[i] != ' ':
highlighted = True
newline += '\x1b[31m'
newline += orig[i]
newline += '\x1b[0m'
return newline
def near_diff(idx, diffdata):
maxidx = idx + 3
if maxidx > len(diffdata) -1:
maxidx = len(diffdata) - 1
idx = idx - 3
if idx < 0:
idx = 0
while idx <= maxidx:
if len(diffdata[idx]) > 0 and diffdata[idx][0] in ('+', '-'):
return True
idx += 1
return False
def colordiff(first, second):
diffdata = list(difflib.ndiff(first, second))
quiet = True
for i in range(len(diffdata)):
if i < len(diffdata) - 1 and diffdata[i + 1].startswith('?'):
if quiet:
quiet = False
yield '@@'
yield _colorize_line(diffdata[i], diffdata[i + 1])
elif not diffdata[i].startswith('?') and near_diff(i, diffdata):
if quiet:
quiet = False
yield '@@'
yield diffdata[i]
elif not diffdata[i].startswith('?'):
quiet = True
class GroupedData(object):
'''A post processor to sort and compare per-node data
:param confluentconnection: If given, will attempt to use the connection to abbreviate noderanges
'''
def __init__(self, confluentconnection=None):
self.bynode = {}
self.byoutput = {}
self.header = {}
self.client = confluentconnection
def generate_byoutput(self):
self.byoutput = {}
for n in self.bynode:
output = '\n'.join(self.bynode[n])
if output not in self.byoutput:
self.byoutput[output] = set([n])
else:
self.byoutput[output].add(n)
def add_line(self, node, line):
if node not in self.bynode:
self.bynode[node] = [line]
else:
self.bynode[node].append(line)
def get_group_text(self, nodes):
if self.client:
headerkey = ','.join(sorted(nodes))
if headerkey in self.header:
return self.header[headerkey]
noderange = ''
for reply in self.client.create('/noderange//abbreviate',
{'nodes': sorted(nodes)}):
noderange = reply['noderange']
self.header[headerkey] = noderange
return noderange
else:
return ','.join(sorted(nodes, key=humanify_nodename))
def print_all(self, output=sys.stdout, skipmodal=False, reverse=False,
count=False):
self.generate_byoutput()
modaloutput = None
ismodal = True
if reverse:
outdatalist = sorted(
self.byoutput, key=lambda x: [len(self.byoutput[x]),
humanify_nodename(
self.get_group_text(
self.byoutput[x]
))])
else:
outdatalist = sorted(
self.byoutput, key=lambda x: [0 - len(self.byoutput[x]),
humanify_nodename(
self.get_group_text(
self.byoutput[x]
))])
if reverse and skipmodal:
# if reversed, the last is biggest and should be skipped if modal
outdatalist = outdatalist[:-1]
for outdata in outdatalist:
if not reverse and skipmodal:
# If big first, this makes skipmodal skip first
skipmodal = False
continue
currout = '====================================\n'
currout += self.get_group_text(self.byoutput[outdata])
currout += '\n====================================\n'
if count:
currout += 'Count: {0}'.format(len(list(
self.byoutput[outdata])))
currout += '\n====================================\n'
currout += outdata
currout += '\n\n'
output.write(currout)
output.flush()
def print_deviants(self, output=sys.stdout, skipmodal=False, reverse=False,
count=False):
self.generate_byoutput()
modaloutput = None
ismodal = True
revoutput = []
for outdata in sorted(
self.byoutput, key=lambda x: [0 - len(self.byoutput[x]),
humanify_nodename(
self.get_group_text(
self.byoutput[x]
))]):
if modaloutput is None:
modaloutput = outdata
if skipmodal:
skipmodal = False
ismodal = False
continue
currout = '====================================\n'
currout += self.get_group_text(self.byoutput[outdata])
currout += '\n====================================\n'
if count:
currout += 'Count: {0}'.format(len(list(
self.byoutput[outdata])))
currout += '\n====================================\n'
if ismodal:
ismodal = False
currout += outdata
else:
currout += '\n'.join(colordiff(modaloutput.split('\n'),
outdata.split('\n')))
currout += '\n\n'
if reverse:
revoutput.append(currout)
else:
output.write(currout)
for currout in reversed(revoutput):
output.write(currout)
output.flush()
if __name__ == '__main__':
groupoutput = GroupedData()
for line in sys.stdin.read().split('\n'):
if not line:
continue
groupoutput.add_line(*line.split(': ', 1))
groupoutput.print_deviants()
-35
View File
@@ -2,38 +2,3 @@ PATH=/opt/confluent/bin:$PATH
export PATH
MANPATH=/opt/confluent/share/man:$MANPATH
export MANPATH
# The aliases below are to signify that file globbing is unwelcome at the shell
# this avoids a problem if a user does a noderange like 'n[21-33] and there is a file
# in the directory like 'n3' that causes the parameter to change and target a totally
# different node
# Unfortunately in bourne shell, we cannot reliably ensure a prepended set-f
# and an appended set +f are both run. alias seems to be the only mechanism
# that can intervene before glob expansion, but it lacks power.
# putting it into a function to append is all well and good, *except* that
# if doing something like 'nodepower compute|grep' causes set -f to execute
# in current shell, and the function to be in a subshell and leaves globbing
# disabled in the parent shell. Instead, store the current command in a
# variable and use that to check for misglobbed noderanges, which was the goal
alias nodeattrib='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeattrib'
alias nodebmcreset='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodebmcreset'
alias nodeboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeboot'
alias nodeconfig='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconfig'
alias nodeconsole='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconsole'
alias nodedefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedefine'
alias nodeeventlog='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeeventlog'
alias nodefirmware='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodefirmware'
alias nodegroupattrib='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupattrib'
alias nodegroupdefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupdefine'
alias nodegroupremove='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupremove'
alias nodehealth='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodehealth'
alias nodeidentify='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeidentify'
alias nodeinventory='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeinventory'
alias nodelist='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelist'
alias nodemedia='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodemedia'
alias nodepower='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodepower'
alias noderemove='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderemove'
alias nodereseat='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodereseat'
alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderun'
alias nodesensors='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesensors'
alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesetboot'
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
-88
View File
@@ -1,88 +0,0 @@
collate(1) -- Organize text input by node
==============================
## SYNOPSIS
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r]`
## DESCRIPTION
**collate** takes input in the form of <nodename>: <data> and groups
the output for each <nodename> together, and checks for identical data to further group nodes together.
Output groups are sorted in descending size such that the topmost group is the largest, and the
last output group is the smallest. In the event of equally sized output groups,
the groups are sorted alphanumerically by their header, and each header has
node and group names sorted alphanumerically.
## OPTIONS
* `-a`, `--abbreviate`:
Use confluent to attempt to shorten the noderange. This can help identify
when output differs along a telling group boundary. For example, if
output suggests a large number of nodes are unreachable, abbreviate
showing 'rack1' as being unreachable may make more obvious a possible cause.
* `-d`, `--diff`:
Express all but the most common result group in terms of diff from
the most common result group
* `-w`, `--watch`:
Update results dynamically as data becomes available, rather than
waiting for the command to fully complete.
* `-s`, `--skipcommon`:
Suppress printing of the most common result text group. This is used to
focus on stray output against a well known and expected result.
* `-c`, `--count`:
Print a count of the number of nodes in an output group under the
noderange.
* `-r`, `--reverse`:
Rather than starting with most common to least common, start with
the least common and print the most common last.
## EXAMPLES
* Organizing power state of multiple nodes:
`# nodepower n1-n12 | collate`
`====================================`
`n1,n2,n3,n4,n7,n8,n9,n10,n11,n12`
`====================================`
`on`
` `
`====================================`
`n5,n6`
`====================================`
`off`
* Using diff to detect distinct UEFI configuration
`# pasu n1-n4 show Processors|collate -d -s`
`====================================`
`n3`
`====================================`
`@@`
` Processors.ProcessorPerformanceStates=Enable`
` Processors.C-States=Enable`
` Processors.PackageACPIC-StateLimit=ACPI C3`
`- Processors.C1EnhancedMode=Enable`
`+ Processors.C1EnhancedMode=Disable`
`- Processors.Hyper-Threading=Enable`
`+ Processors.Hyper-Threading=Disable`
` Processors.ExecuteDisableBit=Enable`
` Processors.IntelVirtualizationTechnology=Enable`
` `
`====================================`
`n1`
`====================================`
`@@`
` Processors.ProcessorPerformanceStates=Enable`
` Processors.C-States=Enable`
` Processors.PackageACPIC-StateLimit=ACPI C3`
`- Processors.C1EnhancedMode=Enable`
`+ Processors.C1EnhancedMode=Disable`
` Processors.Hyper-Threading=Enable`
` Processors.ExecuteDisableBit=Enable`
-6
View File
@@ -6,7 +6,6 @@ nodeattrib(8) -- List or change confluent nodes attributes
`nodeattrib [-b] <noderange> [<nodeattribute>...]`
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
## DESCRIPTION
@@ -22,9 +21,6 @@ For a full list of attributes, run `nodeattrib <node> all` against a node.
If `-c` is specified, this will set the nodeattribute to a null valid.
This is different from setting the value to an empty string.
For the `groups` attribute, it is possible to add a group by doing
`groups,=<newgroup>`` and to remove by doing `groups^=<oldgroup>`
Note that `nodeattrib <group>` will likely not provide the expected behavior.
See nodegroupattrib(8) command on how to manage attributes on a group level.
@@ -34,8 +30,6 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
Annotate inherited and expression based attributes to show their base value.
* `-c`, `--clear`:
Clear specified nodeattributes
* `-e`, `--environment`:
Set specified attributes based on exported environment variable of matching name. Environment variable names may be lower case or all upper case. Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
## EXAMPLES
* Listing matching nodes of a simple noderange:
@@ -39,7 +39,7 @@ number of operators at the end to indicate formatting changes.
Another common element to pull into an expression is the node name in whole:
`hardwaremanagement.manager={node}-imm`
`hardwaremanagement.manager={nodename}-imm`
Additionally other attributes may be pulled in:
@@ -47,7 +47,7 @@ Additionally other attributes may be pulled in:
Multiple expressions are permissible within a single attribute:
`hardwaremanagement.manager={node}-{hardwaremanagement.method}`
`hardwaremanagement.manager={nodename}-{hardwaremanagement.method}`
A note to developers: in general the API layer will automatically recognize a
generic set attribute to string with expression syntax and import it as an
@@ -1,19 +0,0 @@
nodebmcreset(8) -- Reset management controller
=========================================================
## SYNOPSIS
`nodebmcreset <noderange>`
## DESCRIPTION
`nodebmcreset` allows you to reset the management controller of the specified noderange
## EXAMPLES:
* Reset the management controller for nodes n1 through n4:
`# nodebmcreset n1-n4`
`n1: BMC Reset Successful`
`n2: BMC Reset Successful`
`n3: BMC Reset Successful`
`n4: BMC Reset Successful`
-50
View File
@@ -1,50 +0,0 @@
nodeconfig(8) -- Show or change node configuration
==================================================
## SYNOPSIS
`nodeconfig <noderange> [options] [<configuration>..]`
`nodeconfig <noderange> [options] [<configuration=value>..]`
## DESCRIPTION
**nodeconfig** manages the configuration of nodes managed by confluent.
Rather than manipulating the confluent database, this actually modifies the
running configuration on the node firmware. Calling without '=' will show the
current value, and '=' will change the value. Network information can be
given as a node expression, as documented in the man page for nodeattribexpressions(5).
## OPTIONS
* `-c`, `--comparedefault`:
Take the given settings and compare against default value, if available. If
no configuration values are specified, it will show only those that differ.
If combined with `-x`, will show all differing values except those indicated
by `-x`
* `-x`, `--exclude`:
Rather than listing only the specified configuration parameters, list all
attributes except for the specified ones
* `-d`, `--detail`:
Provide detailed data as available. This can include help text and valid
values for a setting.
## EXAMPLES
* Showing the current IP configuration of noderange BMC/IMM/XCC:
`# nodeconfig s3,s4 bmc`
`s3: bmc.ipv4_address: 172.30.254.193/16`
`s3: bmc.ipv4_method: DHCP`
`s3: bmc.ipv4_gateway: 172.30.0.6`
`s4: bmc.ipv4_address: 172.30.254.192/16`
`s4: bmc.ipv4_method: DHCP`
`s4: bmc.ipv4_gateway: 172.30.0.6`
* Changing nodes `s3` and `s4` to have the ip addressess 10.1.2.3 and 10.1.2.4 with a 16 bit subnet mask:
`# nodeconfig s3,s4 bmc.ipv4_address=10.1.2.{n1}/16`
## SEE ALSO
nodeattribexpressions(5)
@@ -11,11 +11,6 @@ text or serial console of a system. Exiting is done by hitting `Ctrl-e`, then `
then `.`. Note that console output by default is additionally logged to
`/var/log/confluent/consoles/`**NODENAME**.
When the console connection to the target is broken, then confluent on backend
will initiate an automatic retry interval that is randomized between 2 and 4 minutes.
The reopen escape sequence below requests an immediate retry, as does connecting
a new session.
## ESCAPE SEQUENCE COMMANDS
While connected to a console, a number of commands may be performed through escape
@@ -47,10 +42,6 @@ keystroke will be interpreted as a command. The following commands are availabl
* `pbn`:
[Power Boot Network]
Request immediate boot to network
* `r`:
[send Resize]
This queries the current terminal and sends stty commands to advertise the user termineal
size to the remote console
* `?`:
Get a list of supported commands
* `<ent>`:
-28
View File
@@ -1,28 +0,0 @@
nodedefine(8) -- Define new confluent nodes
## SYNOPSIS
`nodedefine <noderange> [nodeattribute1=value1> <nodeattribute2=value2> ...]`
## DESCRIPTION
`nodedefine` allows the definition of new nodes for the confluent management
system. It has the same syntax as `nodeattrib(8)`, and the commands differ in
that `nodeattrib(8)` will error if a node does not exist.
## EXAMPLES
* Define two racks of nodes, named r{rack}u{u}:
`# nodedefine r1u1-r2u4`
`r1u4: created`
`r1u1: created`
`r1u2: created`
`r1u3: created`
`r2u4: created`
`r2u3: created`
`r2u2: created`
`r2u1: created`
## SEE ALSO
noderange(5), nodeattribexpressions(8)
-130
View File
@@ -1,130 +0,0 @@
nodediscover(8) -- List or manage confluent node discovery
=========================================================
## SYNOPSIS
`nodediscover rescan`
`nodediscover [options] list`
`nodediscover [options] assign`
`nodediscover [options] rescan`
`nodediscover [options] clear`
## DESCRIPTION
**nodediscover** provides streamlined access to the confluent discovery data
and assignment. Nodes are detected through either an active scan (as occurs
at service startup and on request by nodediscover rescan) or through passive
detection (as a target comes online, it may attempt to register with the
network).
**nodediscover list** provides the currently known data in tabular format. The
data may be filtered by various parameters, as denoted in the options below.
**nodediscover assign** performs manual discovery, assigning an entry to a node
identity or, using `-i`, using a csv file to assign nodes all at once. For
example, a spreadsheet of serial numbers to desired node names could be used.
**nodediscover rescan** requests the server to do an active sweep for new
devices. Generally every effort is made to passively detect devices as they
become available (as they boot or are plugged in), however sometimes an active
scan is the best approach to catch something that appears to be missing.
**nodedsicover clear** requests the server forget about a selection of
detected device. It takes the same arguments as **nodediscover list**.
## CSV FORMAT
The CSV format used by nodediscover consists of one header describing the
columns followed by the data. The available columns are:
* node: The name desired for the node in confluent
* groups: A comma delimited list of groups to put the node into (using normal CSV escape rules for the commas)
* mac: The mac address of the node
* serial: The serial number of the node
* uuid: The uuid of the node
* bmc: The name or ip address that should be assigned to the BMC, regardless of current address
* bmc_gateway: IP address of gateway, if desired
* bmcuser: The desired username for the BMC to have as administrator
* bmcpass: The desired password for the BMC to require
Note that node is the only mandatory field. To identify the systems, one of
mac, serial, or uuid should be specified, it is pointless to provide more than
one of these columns. Other attributes if not provided may be defined through
nodeattrib or group inherited. It is possible to define nodes without ever
providing a BMC ip, in which case IPv6 will be used automatically if possible.
One example of a valid CSV file would be:
node,serial,bmc,bmcuser,bmcpass
n1,06DPMDF,172.30.204.1,admin,Passw0rd12
n2,J30002HG,172.30.204.2,admin,Passw0rd12
Which would use the serial number to assign the name and other three values to
the nodes.
## OPTIONS
* `-m MODEL`, `--model=MODEL`:
Operate with nodes matching the specified model number
* `-s SERIAL`, `--serial=SERIAL`:
Operate against the system matching the specified
serial number
* `-u UUID`, `--uuid=UUID`:
Operate against the system matching the specified UUID
* `-n NODE`, `--node=NODE`:
Operate with the given nodename
* `-e MAC`, `--ethaddr=MAC`:
Operate against the system with the specified MAC
address
* `-t TYPE`, `--type=TYPE`:
Operate against the system of the specified type
* `-c`, `--csv`:
Use CSV formatted output
* `-i IMPORT.CSV`, `--import=IMPORT.CSV`:
Import bulk assignment data from given CSV file
* `-d STATE`, `--discoverystate=STATE`:
Indicate devices with a particular state. The states are listed below
* discovered: The device has been identified and has also had discovery
activities performed, including any relevant certificate
exchanges and deploying user and network configuration.
* identified: The device has been identified as to what node it is
supposed to be, however no active changes to the attributes
or device configuration has been performed. This is
generally discovery awaiting approval due to
discovery.policy specifying a strict security model.
* unidentified: A device has been sensed, but no node identity has been
established at all. It provides data that can be used
for nodediscover assign, as well as current IP addresses
that can be used for manual efforts as possible.
## EXAMPLES
* Listing all detected Lenovo IMMv2 systems on a local network:
`# nodediscover list -t lenovo-imm2`
` Node| Model| Serial| UUID| Mac Address| Type| Current IP Addresses`
`---------------|---------------|---------------|------------------------------------|-----------------|------------|------------------------------------------------`
` r2| 5463AC1| 06DPMDF|5f7133b8-c8cb-11e4-99a9-40f2e9b91018|40:f2:e9:b9:10:1d| lenovo-imm2| 172.30.204.1,fe80::42f2:e9ff:feb9:101d%eth1`
` | 7906AC1| 06PBX15|e98d483d-2759-11e1-8ffd-5cf3fc11249c|5c:f3:fc:11:24:9f| lenovo-imm2| 172.30.3.12,fe80::5ef3:fcff:fe11:249f%eth1`
` n1| 8737AC1| 23XXH41|14dd3ba6-5c38-11e1-931a-5cf3fc6e4680|5c:f3:fc:6e:13:e1| lenovo-imm2| 172.30.3.1,fe80::5ef3:fcff:fe6e:13e1%eth1`
` n7| 8737AC1| 23XXH32|79d2ce28-5cd5-11e1-8c86-5cf3fc6e46b0|5c:f3:fc:6e:13:f9| lenovo-imm2| 172.30.3.7,fe80::5ef3:fcff:fe6e:13f9%eth1`
` n8| 8737AC1| 23XXH49|551a8438-5cd5-11e1-8d6c-5cf3fc6e4708|5c:f3:fc:6e:14:25| lenovo-imm2| 172.30.3.8,fe80::5ef3:fcff:fe6e:1425%eth1`
` n3| 8737AC1| 23XXH30|1dd7f7b3-5da5-11e1-baf0-5cf3fc6e4738|5c:f3:fc:6e:14:3d| lenovo-imm2| 172.30.3.3,fe80::5ef3:fcff:fe6e:143d%eth1`
` n4| 8737AC1| 23XXH35|45b81dae-5d9b-11e1-8337-5cf3fc6e4858|5c:f3:fc:6e:14:cd| lenovo-imm2| 172.30.3.4,fe80::5ef3:fcff:fe6e:14cd%eth1`
` n11| 8737AC1| 23XXH12|31d90128-5c37-11e1-bdb7-5cf3fc6e4920|5c:f3:fc:6e:15:31| lenovo-imm2| 172.30.3.11,fe80::5ef3:fcff:fe6e:1531%eth1`
` n13| 8737AC1| 23XXH44|e23a138a-5cd3-11e1-8f3d-5cf3fc6e4950|5c:f3:fc:6e:15:49| lenovo-imm2| 172.30.3.13,fe80::5ef3:fcff:fe6e:1549%eth1`
` | 8737AC1| 23XXH29|5cd1216b-5c37-11e1-ba0c-5cf3fc6e49c8|5c:f3:fc:6e:15:85| lenovo-imm2| 172.30.3.9,fe80::5ef3:fcff:fe6e:1585%eth1`
` | 8737AC1| 23ZYT44|f4bf48ca-71f0-11e1-b274-5cf3fc6e4f10|5c:f3:fc:6e:18:29| lenovo-imm2| 172.30.3.10,fe80::5ef3:fcff:fe6e:1829%eth1`
` hpcedr| 7915AC1| 06DRHL5|a64e3014-d7e3-11e1-8d21-6cae8b1dff32|6c:ae:8b:1d:ff:36| lenovo-imm2| 172.30.254.250,fe80::6eae:8bff:fe1d:ff36%eth1`
` | 8737AC1| 06YRWC3|3af85a51-7efd-11e3-8599-000af7482e00|6c:ae:8b:32:cb:c5| lenovo-imm2| 172.30.3.6,fe80::6eae:8bff:fe32:cbc5%eth1`
` | 8737AC1| 06YRWB7|b230f62e-7efd-11e3-9773-000af7482980|6c:ae:8b:32:cd:01| lenovo-imm2| 172.30.3.5,fe80::6eae:8bff:fe32:cd01%eth1`
` | 8737AC1| 06YRWC7|09586005-7efe-11e3-9f03-000af7482df0|6c:ae:8b:32:cd:a5| lenovo-imm2| 172.30.3.2,fe80::6eae:8bff:fe32:cda5%eth1`
* Manually assign a single node according to serial number:
`[root@odin ~]# nodediscover assign -s 06PBX15 -n n12`
`Assigned: n12`
* Bulk execute discovery based on spreadsheet:
`[root@odin ~]# nodediscover assign -i import.csv`
`Defined r2`
`Discovered r2`
`Defined c1`
`Discovered c1`
+1 -14
View File
@@ -4,23 +4,10 @@ nodefirmware(8) -- Report firmware information on confluent nodes
## SYNOPSIS
`nodefirmware <noderange>`
`nodefirmware <noderange> <components>|core`
`nodefirmware <noderange> update [--backup] <filename>`
## DESCRIPTION
`nodefirmware` reports and updates various firmware on nodes. By default it
will retrieve all firmware, but can be directed to fetch specific firmware by
calling out the name of the firmware (e.g. uefi or xcc) or request reading only
core firmware firmware by using the word 'core', which is generally a quicker
operation.
In the update form, it accepts a single file and attempts to update it using
the out of band facilities. Firmware updates can end in one of three states:
* `error`: The attempted update encountered an error that prevented successful install. Nodes experiencing this state will be reported at the end and more detail on the error will be given
* `pending`: The firmware update process has completed, but the firmware will not be active until the relevant component next resets. Generally speaking, for UEFI update the system will need a reboot, and for BMC updates, the `nodebmcreset` command will begin the process to activate the firmware.
* `complete`: The firmware update process has completed and activation has proceeded. Note that while the activation process has commenced, the component may still be in the process of rebooting when nodefirmware exits.
`nodefirmware` reports various pieces of firmware on confluent nodes.
## EXAMPLES
@@ -1,23 +0,0 @@
nodegroupdefine(8) -- Define new confluent node group
## SYNOPSIS
`nodegroupdefine <groupname> [nodeattribute1=value1> <nodeattribute2=value2> ...]`
## DESCRIPTION
`nodegroupdefine` allows the definition of a new node for the confluent management
service. It may only define a single group name at a time.
It has the same syntax as `nodegroupattrib(8)`, and the commands differ in
that `nodegroupattrib(8)` will error if a node group does not exist.
## EXAMPLES
* Create a group called `compute`:
`# nodegroupdefine compute`
`compute: created`
## SEE ALSO
nodeattribexpressions(8), nodegroupattrib(8), nodegroupremove(8)
@@ -1,18 +0,0 @@
nodegroupremove(8) -- Remove a nodegroup from the confluent database
====================================================================
## SYNOPSIS
`nodegroupremove <noderange>`
## DESCRIPTION
`nodegroupremove` simply removes the given single nodegroup from the confluent database.
## EXAMPLES
* Remove group called testgroup
`# nodegroupremove testgroup`
`testgroup: deleted`
+2 -17
View File
@@ -3,15 +3,13 @@ nodeinventory(8) -- Get hardware inventory of confluent node
## SYNOPSIS
`nodeinventory <noderange> [serial|model|uuid|mac]`
`nodeinventory <noderange>`
## DESCRIPTION
`nodeinventory` pulls information about hardware of a node. This includes
information such as adapters, serial numbers, processors, and memory modules,
as supported by the platforms hardware management implementation. It accepts
arguments such as serial or model or others as listed above to filter
output to specific data.
as supported by the platforms hardware management implementation.
## EXAMPLES
@@ -106,16 +104,3 @@ output to specific data.
`r1: Broadcom NetXtreme Gigabit Ethernet Controller MAC Address 4: 40:f2:e9:af:45:a3`
`r1: Broadcom NetXtreme Gigabit Ethernet Controller PCI slot: 1b:00`
`r1: Broadcom NetXtreme Gigabit Ethernet Controller location: Onboard`
* Reporting just the mac addresses of a node named r2:
`# nodeinventory r2 mac`
`r2: System MAC Address 1: 40:f2:e9:b9:10:18`
`r2: System MAC Address 2: 40:f2:e9:b9:10:19`
`r2: System MAC Address 3: 40:f2:e9:b9:10:1a`
`r2: System MAC Address 4: 40:f2:e9:b9:10:1b`
* Getting the model number and serial number of a node named s2:
`# nodeinventory stark2 serial model`
`s2: System Product name: LENOVO THINKSYSTEM SD530 SERVER`
`s2: System Serial Number: DEV0000002`
`s2: System Model: 7X2104Z028`
-56
View File
@@ -1,56 +0,0 @@
nodemedia(8) -- Manage server remote media
=========================================================
## SYNOPSIS
`nodemedia <noderange> [attach|detachall|list|upload] [options] <media>`
## DESCRIPTION
**nodemedia** manages the remote media functionality of supported BMCs.
`list` shows all the current remote media the BMCs of the noderange are
providing to the host platform. The string (insecure) is appended to URLs that
are mounted in an insecure fashion. http is insecure, and https is also
insecure when no meaningful certificate validation is performed. Currently
there is no action that can change this, and this is purely informational. A
future version of software may provide a means to increase security of attached
remote media.
`detachall` removes all the currently provided media to the host. This unlinks
remote media from urls and deletes uploaded media from the BMC.
`upload` takes the given media image and uploads it to the BMC. This causes
the remote media to reside internally to the system without having to go
to the network after the upload. This is more constrained, for example the
Lenovo xClarity Controller has a limit of 50 megabytes, but it has zero ongoing
load on the media source.
`attach` takes a URL to a remote media as an argument, and has the given
BMCs map a virtual USB device to that url. Content is loaded on demand, and
as such that URL is referenced potentially once for every IO operation that
the host platform attempts.
## OPTIONS
* `-c`:
Specify the maximum number of instances to run concurrently.
## EXAMPLES
* Listing currently mounted media:
`# nodemedia s1-s4 list`
`s1: boot.img`
`s2: boot.img`
`s4: boot.img`
* Uploading a small boot image to the BMC:
`# nodemedia s1-s4 upload boot.img`
`s1:complete: 100% s2:complete: 100% s3:complete: 100% s4:complete: 100%`
`s1: boot.img`
`s4: boot.img`
`s2: boot.img`
* Attaching a larger ISO for on-demand access:
`# nodemedia s1,s4 attach http://172.30.0.6/install/rhel74.iso`
`s4: http://172.30.0.6/install/rhel74.iso (insecure)`
`s1: http://172.30.0.6/install/rhel74.iso (insecure)`
-57
View File
@@ -1,57 +0,0 @@
noderange(5) -- Indicate a target set of nodes in confluent
=========================================================
## SYNOPSIS
`<noderange>`
## DESCRIPTION
<noderange> is a syntax that can be used in most Confluent commands to conveniently specify a list of nodes. The result is that the command will be applied to a range of nodes, often in parallel.
## EXAMPLES:
The simplest noderange is a single node name:
`n1`
Nodes and groups may be used interchangeably. The following may be used in any context where n1 would be accepted as a noderange:
`rack1`
Commonly, there is a desire to target a range of elements. There are a few identically behaving syntaxes for the purpose.
`n1:n20`
`n1-n20`
`n[1-20]`
Note that numbers may be zero padded or not, it will automatically detect the padding amount and adjust members of the range accordingly. Ranges also can understand multiple numeric values changing:
`r[1-3]u[01-10]`
`r1u01-r3u10`
Ranges can also be applied to group names, and all above syntaxes are compatible:
`rack1-rack10`
`rack[1-10]`
Also, regular expressions may be used to indicate nodes with names matching certain patterns:
`~r1u..`
The other major noderange primitive is indicating nodes by some attribute value:
`location.rack=7`
Commas can be used to indicate multiple nodes, and can mix and match any of the above primitives. The following can be a valid single noderange, combining any and all members of each comma separated component
`n1,n2,rack1,storage,location.rack=9,~s1..,n20-n30`
Exclusions can be done by prepending a ‘-‘ before a portion of a noderange:
`rack1,-n2`
`compute,-rack1`
`compute,-location.row=12`
To indicate nodes that match multiple selections at once (set intersection), the @ symbol may be used:
`compute@rack1`
`location.rack=10@compute`
For complex expressions, () may be used to indicate order of expanding the noderange to be explicit
`rack1,-(console.logging=full@compute)`
Noderange syntax can also indicate ‘pagination’, or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
`rack1>3<6`
The above would show the seventh through ninth nodes of the rack1 group. Like all other noderange operations, this may be combined with any of the above, but must appear as the very last operation. Ordering is done with a natural sort.
-25
View File
@@ -1,25 +0,0 @@
noderemove(8) -- Remove nodes from the confluent management service
===================================================================
## SYNOPSIS
`noderemove <noderange>`
## DESCRIPTION
`noderemove` simply removes the given noderange from the confluent database.
## EXAMPLES
* Remove two racks each with 4 nodes:
`# noderemove r1u1-r2u4`
`r1u4: deleted`
`r1u1: deleted`
`r1u2: deleted`
`r1u3: deleted`
`r2u4: deleted`
`r2u3: deleted`
`r2u2: deleted`
`r2u1: deleted`
-19
View File
@@ -1,19 +0,0 @@
nodereseat(8) -- Request a reseat of a node
============================================
## SYNOPSIS
`nodereseat <noderange>`
## DESCRIPTION
`nodereseat` requests the enclosure manager of the current node to reseat that
node's slot. This should be equivalent to removing the system entirely from
the chassis and putting it back in, but without actually having to do so.
## EXAMPLES
* Reseating the node `s1`:
`# nodereseat s1`
`s1: Reseat successful`
+1 -1
View File
@@ -4,7 +4,7 @@ nodesetboot(8) -- Check or set next boot device for noderange
## SYNOPSIS
`nodesetboot <noderange>`
`nodesetboot [options] <noderange> [default|cd|network|setup|hd]`
`nodesetboot [options] <noderang> [default|cd|network|setup|hd]`
## DESCRIPTION
+2 -8
View File
@@ -3,19 +3,13 @@ nodeshell(8) -- Execute command on many nodes in a noderange through ssh
## SYNOPSIS
`nodeshell <noderange> [options] <command to execute on each node>`
`nodeshell <noderange> <command to execute on each node>`
## DESCRIPTION
Allows execution of a command on many nodes in parallel. Like noderun(8), it
accepts and interpolates confluent attribute expressions as documented in
nodeattribexpressions(5). `nodeshell` provides stdout as stdout and stderr
as stderr, unlike psh which combines all stdout and stderr into stdout.
## OPTIONS
* `-c`:
Specify the maximum number of instances to run concurrently.
nodeattribexpressions(5).
## EXAMPLES
-2
View File
@@ -1,10 +1,8 @@
#!/bin/sh
cd `dirname $0`/doc/man
mkdir -p ../../man/man1
mkdir -p ../../man/man5
mkdir -p ../../man/man8
ronn -r *.ronn
mv *.1 ../../man/man1/
mv *.5 ../../man/man5/
mv *.8 ../../man/man8/
-1
View File
@@ -4,7 +4,6 @@ import os
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh'])]
try:
scriptlist = ['bin/{0}'.format(d) for d in os.listdir('bin/')]
data_files.append(('/opt/confluent/share/man/man1', ['man/man1/' + x for x in os.listdir('man/man1')]))
data_files.append(('/opt/confluent/share/man/man5', ['man/man5/' + x for x in os.listdir('man/man5')]))
data_files.append(('/opt/confluent/share/man/man8', ['man/man8/' + x for x in os.listdir('man/man8')]))
except OSError:
-1
View File
@@ -1,3 +1,2 @@
include sysvinit/*
include systemd/*
include sysctl/*
-1
View File
@@ -1 +0,0 @@
__version__ = "1.5.0.dev395.ggacb3a20"
@@ -279,17 +279,12 @@ node = {
'description': 'The method used to perform operations such as power '
'control, get sensor data, get inventory, and so on. '
},
'enclosure.bay': {
'description': 'The bay in the enclosure, if any',
# 'appliesto': ['system'],
},
'enclosure.extends': {
'description': 'When using an extendable enclosure, this is the node '
'representing the manager that is one closer to the '
'uplink.',
},
'enclosure.manager': {
'description': "The management device for this node's chassis",
# 'appliesto': ['system'],
},
'enclosure.bay': {
'description': 'The bay in the enclosure, if any',
# 'appliesto': ['system'],
},
@@ -298,39 +293,17 @@ node = {
#IBM Flex)''',
# 'appliesto': ['system'],
# },
'id.model': {
'description': 'The model number of a node. In scenarios where there '
'is both a name and a model number, it is generally '
'expected that this would be the generally more '
'specific model number.'
},
'id.serial': {
'description': 'The manufacturer serial number of node',
},
# 'id.serial': {
# 'description': 'The manufacturer serial number of node',
# },
'id.uuid': {
'description': 'The UUID of the node as presented in DMI.',
},
# For single interface mac collection, net.bootable suffices
# For multiple interface mac collection, we perhaps add an address field and use subnet affinity as a clue
# to disambiguate multiple addresses for external provisioning
# For internal provisioning, the UUID matters rather than the MAC, though subnet affinity may
# still be a factor to select the appropriate static config to send down. In such a case bonding
# is hopefully more likely as that's a bit easier.
# Start with the first case and only document that, the other thoughts can be future items if they turn up
'net.bootable': {
'type': bool,
'description': 'Whether or not the indicated network interface is to be used for booting. This is used by '
'the discovery process to decide where to place the mac address of a detected PXE nic.',
},
'net.ipv4_gateway': {
'description': 'The IPv4 gateway to use if applicable. As is the '
'case for other net attributes, net.eth0.ipv4_gateway '
'and similar is accepted.'
},
'net.hwaddr': {
'description': 'The hardware address, aka MAC address of the interface indicated, generally populated by the '
'PXE discovery mechanism'
},
# 'net.pxe': { 'description': 'Whether pxe will be used on this interface'
# TODO(jjohnson2): Above being 'true' will control whether mac addresses
# are stored in this nics attribute on pxe-client discovery, since
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 Lenovo
# Copyright 2015 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -67,7 +67,6 @@ import base64
import confluent.config.attributes as allattributes
import confluent.config.conf as conf
import confluent.log
import confluent.noderange as noderange
import confluent.util
import confluent.exceptions as exc
import copy
@@ -91,14 +90,6 @@ _masterintegritykey = None
_dirtylock = threading.RLock()
_config_areas = ('nodegroups', 'nodes', 'usergroups', 'users')
tracelog = None
statelessmode = False
_cfgstore = None
_attraliases = {
'bmc': 'hardwaremanagement.manager',
'bmcuser': 'secret.hardwaremanagementuser',
'bmcpass': 'secret.hardwaremanagementpassword',
}
def _mkpath(pathname):
try:
@@ -232,48 +223,25 @@ def decrypt_value(cryptvalue,
return value[0:-padsize]
def fixup_attribute(attrname, attrval):
# Normalize some data, for example strings and numbers to bool
attrname = _get_valid_attrname(attrname)
if attrname not in allattributes.node: # no fixup possible
return attrval
if 'type' in allattributes.node[attrname] and not isinstance(attrval, allattributes.node[attrname]['type']):
if (allattributes.node[attrname]['type'] == bool and
(isinstance(attrval, str) or isinstance(attrval, unicode))):
return attrval.lower() in ('true', '1', 'y', 'yes', 'enable', 'enabled')
return attrval
def attribute_is_invalid(attrname, attrval):
if attrname.startswith('custom.'):
# No type checking or name checking is provided for custom,
# it's not possible
return False
attrname = _get_valid_attrname(attrname)
if attrname not in allattributes.node:
# Otherwise, it must be in the allattributes key list
return True
if 'type' in allattributes.node[attrname]:
if not isinstance(attrval, allattributes.node[attrname]['type']):
# it is valid if it is {'value': actualvalue}
if (isinstance(attrval, dict) and 'value' in attrval and
isinstance(attrval['value'],
allattributes.node[attrname]['type'])):
return False
# provide type checking for attributes with a specific type
return True
return False
def _get_valid_attrname(attrname):
if attrname.startswith('net.'):
# For net.* attribtues, split on the dots and put back together
# longer term we might want a generic approach, but
# right now it's just net. attributes
netattrparts = attrname.split('.')
attrname = netattrparts[0] + '.' + netattrparts[-1]
return attrname
if attrname not in allattributes.node:
# Otherwise, it must be in the allattributes key list
return True
if 'type' in allattributes.node[attrname]:
if not isinstance(attrval, allattributes.node[attrname]['type']):
# provide type checking for attributes with a specific type
return True
return False
def crypt_value(value,
key=None,
@@ -291,10 +259,7 @@ def crypt_value(value,
neededpad = 16 - (len(value) % 16)
pad = chr(neededpad) * neededpad
value += pad
try:
cryptval = crypter.encrypt(value)
except TypeError:
cryptval = crypter.encrypt(value.encode('utf-8'))
cryptval = crypter.encrypt(value)
hmac = HMAC.new(integritykey, cryptval, SHA256).digest()
return iv, cryptval, hmac
@@ -308,7 +273,7 @@ def _load_dict_from_dbm(dpath, tdb):
currdict[elem] = {}
currdict = currdict[elem]
try:
for tk in dbe:
for tk in dbe.iterkeys():
currdict[tk] = cPickle.loads(dbe[tk])
except AttributeError:
tk = dbe.firstkey()
@@ -332,8 +297,6 @@ def get_global(globalname):
:param globalname: The global parameter name to read
"""
if _cfgstore is None:
init()
try:
return _cfgstore['globals'][globalname]
except KeyError:
@@ -350,8 +313,6 @@ def set_global(globalname, value):
:param globalname: The global parameter name to store
:param value: The value to set the global parameter to.
"""
if _cfgstore is None:
init()
with _dirtylock:
if 'dirtyglobals' not in _cfgstore:
_cfgstore['dirtyglobals'] = set()
@@ -422,27 +383,20 @@ class _ExpressionFormat(string.Formatter):
return node.n
elif isinstance(node, ast.Attribute):
#ok, we have something with a dot
left = node
key = ''
while isinstance(left, ast.Attribute):
# Loop through, to handle multi dot expressions
# such as 'net.pxe.hwaddr'
key = '.' + left.attr + key
left = left.value
key = left.id + key
if (not key.startswith('custom.') and
_get_valid_attrname(key) not in allattributes.node):
raise ValueError(
'{0} is not a valid attribute name'.format(key))
val = self._expand_attribute(key)
return val['value'] if val and 'value' in val else ""
left = node.value.id
right = node.attr
key = left + '.' + right
if '_expressionkeys' not in self._nodeobj:
self._nodeobj['_expressionkeys'] = set([key])
else:
self._nodeobj['_expressionkeys'].add(key)
val = _decode_attribute(key, self._nodeobj,
formatter=self)
return val['value'] if 'value' in val else ""
elif isinstance(node, ast.Name):
var = node.id
if var in ('node', 'nodename'):
if var == 'nodename':
return self._nodename
if var in _attraliases:
val = self._expand_attribute(_attraliases[var])
return val['value'] if 'value' in val else ""
mg = re.match(self.posmatch, var)
if mg:
idx = int(mg.group(1))
@@ -451,12 +405,13 @@ class _ExpressionFormat(string.Formatter):
return int(self._numbers[idx - 1])
else:
if var in self._nodeobj:
val = self._expand_attribute(var)
return val['value'] if val and 'value' in val else ""
elif (not var.startswith('custom.') and
_get_valid_attrname(var) not in allattributes.node):
raise ValueError(
'{0} is not a valid attribute name'.format(var))
if '_expressionkeys' not in self._nodeobj:
self._nodeobj['_expressionkeys'] = set([var])
else:
self._nodeobj['_expressionkeys'].add(var)
val = _decode_attribute(var, self._nodeobj,
formatter=self)
return val['value'] if 'value' in val else ""
elif isinstance(node, ast.BinOp):
optype = type(node.op)
if optype not in self._supported_ops:
@@ -465,15 +420,6 @@ class _ExpressionFormat(string.Formatter):
return op(int(self._handle_ast_node(node.left)),
int(self._handle_ast_node(node.right)))
def _expand_attribute(self, key):
if '_expressionkeys' not in self._nodeobj:
self._nodeobj['_expressionkeys'] = set([key])
else:
self._nodeobj['_expressionkeys'].add(key)
val = _decode_attribute(key, self._nodeobj,
formatter=self)
return val
def _decode_attribute(attribute, nodeobj, formatter=None, decrypt=False):
if attribute not in nodeobj:
@@ -527,7 +473,7 @@ def hook_new_configmanagers(callback):
#TODO(jbjohnso): actually live up to the promise of ongoing callbacks
callback(ConfigManager(None))
try:
for tenant in _cfgstore['tenant']:
for tenant in _cfgstore['tenant'].iterkeys():
callback(ConfigManager(tenant))
except KeyError:
pass
@@ -549,8 +495,6 @@ class ConfigManager(object):
def __init__(self, tenant, decrypt=False, username=None):
global _cfgstore
if _cfgstore is None:
init()
self.decrypt = decrypt
self.current_user = username
if tenant is None:
@@ -653,7 +597,7 @@ class ConfigManager(object):
def watch_attributes(self, nodes, attributes, callback):
"""
Watch a list of attributes for changes on a list of nodes. The
attributes may be literal, or a filename style wildcard like
attributes may be literal, or a filename style wildcard like
'net*.switch'
:param nodes: An iterable of node names to be watching
@@ -733,7 +677,7 @@ class ConfigManager(object):
def list_users(self):
try:
return list(self._cfgstore['users'])
return self._cfgstore['users'].iterkeys()
except KeyError:
return []
@@ -775,7 +719,7 @@ class ConfigManager(object):
:param attributemap: The mapping of keys to values to set
"""
for attribute in attributemap:
for attribute in attributemap.iterkeys():
self._cfgstore['usergroups'][attribute] = attributemap[attribute]
_mark_dirtykey('usergroups', groupname, self.tenant)
@@ -858,23 +802,19 @@ class ConfigManager(object):
def is_nodegroup(self, nodegroup):
return nodegroup in self._cfgstore['nodegroups']
def get_groups(self, sizesort=False):
if sizesort:
return reversed(
sorted(self._cfgstore['nodegroups'], key=lambda x: len(
self._cfgstore['nodegroups'][x]['nodes'])))
return iter(self._cfgstore['nodegroups'])
def get_groups(self):
return self._cfgstore['nodegroups'].iterkeys()
def list_nodes(self):
try:
return iter(self._cfgstore['nodes'])
return self._cfgstore['nodes'].iterkeys()
except KeyError:
return []
def get_nodegroup_attributes(self, nodegroup, attributes=()):
cfgnodeobj = self._cfgstore['nodegroups'][nodegroup]
if not attributes:
attributes = cfgnodeobj
attributes = cfgnodeobj.iterkeys()
nodeobj = {}
for attribute in attributes:
if attribute.startswith('_'):
@@ -933,7 +873,7 @@ class ConfigManager(object):
groupcfg = self._cfgstore['nodegroups'][group]
except KeyError: # something did not exist, nothing to do
return
for attrib in groupcfg:
for attrib in groupcfg.iterkeys():
self._do_inheritance(nodecfg, attrib, node, changeset)
_addchange(changeset, node, attrib)
@@ -987,7 +927,7 @@ class ConfigManager(object):
return
def _sync_groups_to_node(self, groups, node, changeset):
for group in self._cfgstore['nodegroups']:
for group in self._cfgstore['nodegroups'].iterkeys():
if group not in groups:
if node in self._cfgstore['nodegroups'][group]['nodes']:
self._cfgstore['nodegroups'][group]['nodes'].discard(node)
@@ -1004,7 +944,7 @@ class ConfigManager(object):
self._node_added_to_group(node, group, changeset)
def _sync_nodes_to_group(self, nodes, group, changeset):
for node in self._cfgstore['nodes']:
for node in self._cfgstore['nodes'].iterkeys():
if node not in nodes and 'groups' in self._cfgstore['nodes'][node]:
if group in self._cfgstore['nodes'][node]['groups']:
self._cfgstore['nodes'][node]['groups'].remove(group)
@@ -1025,49 +965,17 @@ class ConfigManager(object):
def set_group_attributes(self, attribmap, autocreate=False):
changeset = {}
for group in attribmap:
for group in attribmap.iterkeys():
if group == '':
raise ValueError('"{0}" is not a valid group name'.format(
group))
if autocreate:
try:
noderange._parser.parseString(
'({0})'.format(group)).asList()
except noderange.pp.ParseException as pe:
raise ValueError('"{0}" is not a valid group name'.format(
group))
if not autocreate and group not in self._cfgstore['nodegroups']:
raise ValueError("{0} group does not exist".format(group))
for attr in attribmap[group]:
# first do a pass to normalize out any aliased attribute names
if attr in _attraliases:
newattr = _attraliases[attr]
attribmap[group][newattr] = attribmap[group][attr]
del attribmap[group][attr]
for attr in attribmap[group]:
if attr in _attraliases:
newattr = _attraliases[attr]
attribmap[group][newattr] = attribmap[group][attr]
del attribmap[group][attr]
if attr not in ('nodes', 'noderange'):
attrval = fixup_attribute(attr, attribmap[group][attr])
if attribute_is_invalid(attr, attrval):
errstr = "{0} attribute is invalid".format(attr)
raise ValueError(errstr)
attribmap[group][attr] = attrval
for attr in attribmap[group].iterkeys():
if (attr not in ('nodes', 'noderange') and
attribute_is_invalid(attr, attribmap[group][attr])):
raise ValueError("{0} attribute is invalid".format(attr))
if attr == 'nodes':
if isinstance(attribmap[group][attr], dict):
currnodes = list(self.get_nodegroup_attributes(
group, ['nodes']).get('nodes', []))
if attribmap[group][attr].get('prepend', False):
newnodes = attribmap[group][attr][
'prepend'].split(',')
attribmap[group][attr] = newnodes + currnodes
elif attribmap[group][attr].get('remove', False):
delnodes = attribmap[group][attr][
'remove'].split(',')
attribmap[group][attr] = [
x for x in currnodes if x not in delnodes]
if not isinstance(attribmap[group][attr], list):
if type(attribmap[group][attr]) is unicode or type(attribmap[group][attr]) is str:
attribmap[group][attr]=attribmap[group][attr].split(",")
@@ -1078,17 +986,16 @@ class ConfigManager(object):
raise ValueError(
"{0} node does not exist to add to {1}".format(
node, group))
for group in attribmap:
for group in attribmap.iterkeys():
group = group.encode('utf-8')
if group not in self._cfgstore['nodegroups']:
self._cfgstore['nodegroups'][group] = {'nodes': set()}
cfgobj = self._cfgstore['nodegroups'][group]
for attr in attribmap[group]:
for attr in attribmap[group].iterkeys():
if attr == 'nodes':
newdict = set(attribmap[group][attr])
elif (isinstance(attribmap[group][attr], str) or
isinstance(attribmap[group][attr], unicode) or
isinstance(attribmap[group][attr], bool)):
isinstance(attribmap[group][attr], unicode)):
newdict = {'value': attribmap[group][attr]}
else:
newdict = attribmap[group][attr]
@@ -1112,13 +1019,6 @@ class ConfigManager(object):
def clear_group_attributes(self, groups, attributes):
changeset = {}
realattributes = []
for attrname in list(attributes):
if attrname in _attraliases:
realattributes.append(_attraliases[attrname])
else:
realattributes.append(attrname)
attributes = realattributes
if type(groups) in (str, unicode):
groups = (groups,)
for group in groups:
@@ -1250,13 +1150,6 @@ class ConfigManager(object):
def clear_node_attributes(self, nodes, attributes):
# accumulate all changes into a changeset and push in one go
changeset = {}
realattributes = []
for attrname in list(attributes):
if attrname in _attraliases:
realattributes.append(_attraliases[attrname])
else:
realattributes.append(attrname)
attributes = realattributes
for node in nodes:
node = node.encode('utf-8')
try:
@@ -1283,7 +1176,7 @@ class ConfigManager(object):
self._bg_sync_to_file()
def add_node_attributes(self, attribmap):
for node in attribmap:
for node in attribmap.iterkeys():
if 'groups' not in attribmap[node]:
attribmap[node]['groups'] = []
self.set_node_attributes(attribmap, autocreate=True)
@@ -1296,47 +1189,22 @@ class ConfigManager(object):
changeset = {}
# first do a sanity check of the input upfront
# this mitigates risk of arguments being partially applied
for node in attribmap:
for node in attribmap.iterkeys():
node = node.encode('utf-8')
if node == '':
raise ValueError('"{0}" is not a valid node name'.format(node))
if autocreate:
try:
noderange._parser.parseString(
'({0})'.format(node)).asList()
except noderange.pp.ParseException as pe:
raise ValueError(
'"{0}" is not a valid node name'.format(node))
if autocreate is False and node not in self._cfgstore['nodes']:
raise ValueError("node {0} does not exist".format(node))
for attrname in list(attribmap[node]):
if attrname in _attraliases:
truename = _attraliases[attrname]
attribmap[node][truename] = attribmap[node][attrname]
del attribmap[node][attrname]
for attrname in attribmap[node]:
for attrname in attribmap[node].iterkeys():
attrval = attribmap[node][attrname]
try:
if (allattributes.node[attrname]['type'] == 'list' and
type(attrval) in (str, unicode)):
type(attrval) in (str, unicode)):
attrval = attrval.split(",")
except KeyError:
pass
if attrname == 'groups':
if isinstance(attribmap[node]['groups'], dict):
currgroups = self.get_node_attributes(
node, 'groups').get(node, {}).get('groups', [])
if attribmap[node]['groups'].get('prepend', False):
newgroups = attribmap[node]['groups'][
'prepend'].split(',')
attribmap[node]['groups'] = newgroups + currgroups
elif attribmap[node]['groups'].get('remove', False):
delgroups = attribmap[node]['groups'][
'remove'].split(',')
newgroups = [
x for x in currgroups if x not in delgroups]
attribmap[node]['groups'] = newgroups
elif type(attribmap[node]['groups']) != list:
if type(attribmap[node]['groups']) != list:
attribmap[node]['groups']=attribmap[node]['groups'].split(",")
for group in attribmap[node]['groups']:
if group not in self._cfgstore['nodegroups']:
@@ -1345,14 +1213,11 @@ class ConfigManager(object):
if ('everything' in self._cfgstore['nodegroups'] and
'everything' not in attribmap[node]['groups']):
attribmap[node]['groups'].append('everything')
else:
attrval = fixup_attribute(attrname, attrval)
if attribute_is_invalid(attrname, attrval):
errstr = "{0} attribute on node {1} is invalid".format(
attrname, node)
raise ValueError(errstr)
attribmap[node][attrname] = attrval
for node in attribmap:
elif attribute_is_invalid(attrname, attrval):
errstr = "{0} attribute on node {1} is invalid".format(
attrname, node)
raise ValueError(errstr)
for node in attribmap.iterkeys():
node = node.encode('utf-8')
exprmgr = None
if node not in self._cfgstore['nodes']:
@@ -1360,10 +1225,9 @@ class ConfigManager(object):
self._cfgstore['nodes'][node] = {}
cfgobj = self._cfgstore['nodes'][node]
recalcexpressions = False
for attrname in attribmap[node]:
for attrname in attribmap[node].iterkeys():
if (isinstance(attribmap[node][attrname], str) or
isinstance(attribmap[node][attrname], unicode) or
isinstance(attribmap[node][attrname], bool)):
isinstance(attribmap[node][attrname], unicode)):
newdict = {'value': attribmap[node][attrname]}
else:
newdict = attribmap[node][attrname]
@@ -1447,9 +1311,7 @@ class ConfigManager(object):
pass
# Now we have to iterate through each fixed up element, using the
# set attribute to flesh out inheritence and expressions
_cfgstore['main']['idmap'] = {}
for confarea in _config_areas:
self._cfgstore[confarea] = {}
if confarea not in tmpconfig:
continue
if confarea == 'nodes':
@@ -1485,10 +1347,10 @@ class ConfigManager(object):
if confarea not in self._cfgstore:
continue
dumpdata[confarea] = {}
for element in self._cfgstore[confarea]:
for element in self._cfgstore[confarea].iterkeys():
dumpdata[confarea][element] = \
copy.deepcopy(self._cfgstore[confarea][element])
for attribute in self._cfgstore[confarea][element]:
for attribute in self._cfgstore[confarea][element].iterkeys():
if 'inheritedfrom' in dumpdata[confarea][element][attribute]:
del dumpdata[confarea][element][attribute]
elif (attribute == 'cryptpass' or
@@ -1546,8 +1408,6 @@ class ConfigManager(object):
@classmethod
def _bg_sync_to_file(cls):
if statelessmode:
return
with cls._syncstate:
if cls._syncrunning:
cls._writepending = True
@@ -1561,8 +1421,6 @@ class ConfigManager(object):
@classmethod
def _sync_to_file(cls):
if statelessmode:
return
if 'dirtyglobals' in _cfgstore:
with _dirtylock:
dirtyglobals = copy.deepcopy(_cfgstore['dirtyglobals'])
@@ -1583,7 +1441,7 @@ class ConfigManager(object):
with _dirtylock:
currdirt = copy.deepcopy(_cfgstore['dirtykeys'])
del _cfgstore['dirtykeys']
for tenant in currdirt:
for tenant in currdirt.iterkeys():
dkdict = currdirt[tenant]
if tenant is None:
pathname = cls._cfgdir
@@ -1591,7 +1449,7 @@ class ConfigManager(object):
else:
pathname = os.path.join(cls._cfgdir, 'tenants', tenant)
currdict = _cfgstore['tenant'][tenant]
for category in dkdict:
for category in dkdict.iterkeys():
_mkpath(pathname)
dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600
try:
@@ -1614,7 +1472,7 @@ class ConfigManager(object):
return cls._sync_to_file()
def _recalculate_expressions(self, cfgobj, formatter, node, changeset):
for key in cfgobj:
for key in cfgobj.iterkeys():
if not isinstance(cfgobj[key], dict):
continue
if 'expression' in cfgobj[key]:
@@ -1708,15 +1566,11 @@ def dump_db_to_directory(location, password, redact=None):
except OSError:
pass
def init(stateless=False):
global _cfgstore
if stateless:
_cfgstore = {}
return
try:
ConfigManager._read_from_path()
except IOError:
_cfgstore = {}
try:
ConfigManager._read_from_path()
except IOError:
_cfgstore = {}
# some unit tests worth implementing:
+10 -23
View File
@@ -145,7 +145,6 @@ class ConsoleHandler(object):
self._dologging = True
self._isondemand = False
self.error = None
self._retrytime = 0
self.cfgmgr = configmanager
self.node = node
self.connectstate = 'unconnected'
@@ -191,16 +190,6 @@ class ConsoleHandler(object):
self.connectstate = 'connecting'
eventlet.spawn(self._connect)
def _get_retry_time(self):
clustsize = len(self.cfgmgr._cfgstore['nodes'])
self._retrytime = self._retrytime * 2 + 1
if self._retrytime > 120:
self._retrytime = 120
retrytime = clustsize * 0.05 * self._retrytime
if retrytime > 120:
retrytime = 120
return retrytime + (retrytime * random.random())
def feedbuffer(self, data):
try:
self.termstream.feed(data)
@@ -219,12 +208,11 @@ class ConsoleHandler(object):
self._isondemand = False
elif 'console.logging' not in attrvalue[self.node]:
self._isondemand = False
else:
if (attrvalue[self.node]['console.logging']['value'] not in (
'full', '', 'buffer')):
self._isondemand = True
if (attrvalue[self.node]['console.logging']['value']) in ('none', 'memory'):
self._dologging = False
elif (attrvalue[self.node]['console.logging']['value'] not in (
'full', '')):
self._isondemand = True
elif (attrvalue[self.node]['console.logging']['value']) == 'none':
self._dologging = False
def get_buffer_age(self):
"""Return age of buffered data
@@ -259,7 +247,7 @@ class ConsoleHandler(object):
return
else:
self._ondemand()
if logvalue in ('none', 'memory'):
if logvalue == 'none':
self._dologging = False
if not self._isondemand or self.livesessions:
eventlet.spawn(self._connect)
@@ -323,7 +311,7 @@ class ConsoleHandler(object):
self._console = plugin.handle_path(
self._plugin_path.format(self.node),
"create", self.cfgmgr)
except (exc.NotImplementedException, exc.NotFoundException):
except exc.NotImplementedException:
self._console = None
except:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
@@ -354,7 +342,7 @@ class ConsoleHandler(object):
self.connectstate = 'unconnected'
self._send_rcpts({'connectstate': self.connectstate,
'error': self.error})
retrytime = self._get_retry_time()
retrytime = 120 + (120 * random.random())
if not self.reconnect:
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
return
@@ -364,7 +352,7 @@ class ConsoleHandler(object):
self.connectstate = 'unconnected'
self._send_rcpts({'connectstate': self.connectstate,
'error': self.error})
retrytime = self._get_retry_time()
retrytime = 120 + (120 * random.random())
if not self.reconnect:
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
return
@@ -376,7 +364,7 @@ class ConsoleHandler(object):
self.connectstate = 'unconnected'
self._send_rcpts({'connectstate': self.connectstate,
'error': self.error})
retrytime = self._get_retry_time()
retrytime = 120 + (120 * random.random())
if not self.reconnect:
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
return
@@ -384,7 +372,6 @@ class ConsoleHandler(object):
def _got_connected(self):
self.connectstate = 'connected'
self._retrytime = 0
self.log(
logdata='console connected', ltype=log.DataTypes.event,
event=log.Events.consoleconnect)
+8 -73
View File
@@ -172,13 +172,7 @@ def _init_core():
'default': 'ipmi',
}),
},
},
'system': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
}
},
'_console': {
'session': PluginRoute({
@@ -191,11 +185,6 @@ def _init_core():
'handler': 'ssh',
}),
},
'_enclosure': {
'reseat_bay': PluginRoute(
{'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi'}),
},
'shell': {
# another special case similar to console
'sessions': PluginCollection({
@@ -222,10 +211,6 @@ def _init_core():
}),
},
},
#'forward': {
# # Another dummy value, currently only for the gui
# 'web': None,
#},
'health': {
'hardware': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
@@ -248,39 +233,13 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'updates': {
'active': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
},
},
'media': {
'uploads': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'attach': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'detach': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'current': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
'power': {
'state': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'reseat': PluginRoute({'handler': 'enclosure'}),
},
'sensors': {
'hardware': {
@@ -288,10 +247,6 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'energy': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'temperature': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
@@ -371,12 +326,10 @@ def iterate_collections(iterable, forcecollection=True):
def iterate_resources(fancydict):
for resource in fancydict:
for resource in fancydict.iterkeys():
if resource.startswith("_"):
continue
if resource == 'abbreviate':
pass
elif not isinstance(fancydict[resource], PluginRoute): # a resource
if not isinstance(fancydict[resource], PluginRoute): # a resource
resource += '/'
yield msg.ChildCollection(resource)
@@ -410,8 +363,7 @@ def delete_node_collection(collectionpath, configmanager, isnoderange):
def enumerate_nodegroup_collection(collectionpath, configmanager):
nodegroup = collectionpath[1]
if not configmanager.is_nodegroup(nodegroup):
raise exc.NotFoundException(
'Invalid nodegroup: {0} not found'.format(nodegroup))
raise exc.NotFoundException("Invalid element requested")
del collectionpath[0:2]
collection = nested_lookup(nodegroupresources, collectionpath)
return iterate_resources(collection)
@@ -431,7 +383,6 @@ def enumerate_node_collection(collectionpath, configmanager):
collection = nested_lookup(noderesources, collectionpath[2:])
if len(collectionpath) == 2 and collectionpath[0] == 'noderange':
collection['nodes'] = {}
collection['abbreviate'] = {}
if not isinstance(collection, dict):
raise exc.NotFoundException("Invalid element requested")
return iterate_resources(collection)
@@ -544,16 +495,6 @@ class BadPlugin(object):
self.node, self.plugin + ' is not a supported plugin')
def abbreviate_noderange(configmanager, inputdata, operation):
if operation != 'create':
raise exc.InvalidArgumentException('Must be a create with nodes in list')
if 'nodes' not in inputdata:
raise exc.InvalidArgumentException('Must be given list of nodes under key named nodes')
if isinstance(inputdata['nodes'], str) or isinstance(inputdata['nodes'], unicode):
inputdata['nodes'] = inputdata['nodes'].split(',')
return (msg.KeyValueData({'noderange': noderange.ReverseNodeRange(inputdata['nodes'], configmanager).noderange}),)
def handle_node_request(configmanager, inputdata, operation,
pathcomponents, autostrip=True):
iscollection = False
@@ -566,8 +507,6 @@ def handle_node_request(configmanager, inputdata, operation,
# facility anyway
isnoderange = False
pathcomponents = pathcomponents[2:]
elif len(pathcomponents) == 3 and pathcomponents[2] == 'abbreviate':
return abbreviate_noderange(configmanager, inputdata, operation)
else:
isnoderange = True
else:
@@ -576,8 +515,7 @@ def handle_node_request(configmanager, inputdata, operation,
nodeorrange = pathcomponents[1]
if not isnoderange and not configmanager.is_node(nodeorrange):
raise exc.NotFoundException("Invalid Node")
if isnoderange and not (len(pathcomponents) == 3 and
pathcomponents[2] == 'abbreviate'):
if isnoderange:
try:
nodes = noderange.NodeRange(nodeorrange, configmanager).nodes
except Exception as e:
@@ -636,8 +574,7 @@ def handle_node_request(configmanager, inputdata, operation,
passvalues = []
plugroute = routespec.routeinfo
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata, nodes, isnoderange,
configmanager)
pathcomponents, operation, inputdata, nodes, isnoderange)
if 'handler' in plugroute: # fixed handler definition, easy enough
if isinstance(plugroute['handler'], str):
hfunc = getattr(pluginmap[plugroute['handler']], operation)
@@ -744,8 +681,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
except IndexError: # it's just users/
if operation == 'create':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata,
configmanager=configmanager)
pathcomponents, operation, inputdata)
create_user(inputdata.attribs, configmanager)
return iterate_collections(configmanager.list_users(),
forcecollection=False)
@@ -757,8 +693,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
return delete_user(user, configmanager)
elif operation == 'update':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata,
configmanager=configmanager)
pathcomponents, operation, inputdata)
update_user(user, inputdata.attribs, configmanager)
return show_user(user, configmanager)
elif pathcomponents[0] == 'events':
+106 -448
View File
@@ -61,7 +61,6 @@
# retry until uppercase, lowercase, digit, and symbol all present)
# - Apply defined configuration to endpoint
import base64
import confluent.config.configmanager as cfm
import confluent.discovery.protocols.pxe as pxe
#import confluent.discovery.protocols.ssdp as ssdp
@@ -76,13 +75,9 @@ import confluent.messages as msg
import confluent.networking.macmap as macmap
import confluent.noderange as noderange
import confluent.util as util
import eventlet
import traceback
webclient = eventlet.import_patched('pyghmi.util.webclient')
import eventlet
import eventlet.greenpool
import eventlet.semaphore
class nesteddict(dict):
@@ -96,7 +91,6 @@ nodehandlers = {
'service:management-hardware.Lenovo:lenovo-xclarity-controller': xcc,
'service:management-hardware.IBM:integrated-management-module2': imm,
'pxe-client': pxeh,
'service:io-device.Lenovo:management-module': None,
}
servicenames = {
@@ -104,7 +98,6 @@ servicenames = {
'service:lenovo-smm': 'lenovo-smm',
'service:management-hardware.Lenovo:lenovo-xclarity-controller': 'lenovo-xcc',
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
'service:io-device.Lenovo:management-module': 'lenovo-switch',
}
servicebyname = {
@@ -112,11 +105,7 @@ servicebyname = {
'lenovo-smm': 'service:lenovo-smm',
'lenovo-xcc': 'service:management-hardware.Lenovo:lenovo-xclarity-controller',
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
'lenovo-switch': 'service:io-device.Lenovo:management-module',
}
discopool = eventlet.greenpool.GreenPool(500)
runningevals = {}
# Passive-only auto-detection protocols:
# PXE
@@ -148,60 +137,17 @@ runningevals = {}
known_info = {}
known_services = {}
known_serials = {}
known_uuids = nesteddict()
known_nodes = nesteddict()
unknown_info = {}
pending_nodes = {}
pending_by_uuid = {}
def enrich_pxe_info(info):
sn = None
mn = None
nodename = info.get('nodename', None)
uuid = info.get('uuid', '')
if not uuid_is_valid(uuid):
return info
for mac in known_uuids.get(uuid, {}):
if not sn and 'serialnumber' in known_uuids[uuid][mac]:
info['serialnumber'] = known_uuids[uuid][mac]['serialnumber']
if not mn and 'modelnumber' in known_uuids[uuid][mac]:
info['modelnumber'] = known_uuids[uuid][mac]['modelnumber']
if nodename is None and 'nodename' in known_uuids[uuid][mac]:
info['nodename'] = known_uuids[uuid][mac]['nodename']
def uuid_is_valid(uuid):
if not uuid:
return False
return uuid.lower() not in ('00000000-0000-0000-0000-000000000000',
'ffffffff-ffff-ffff-ffff-ffffffffffff',
'00112233-4455-6677-8899-aabbccddeeff',
'20202020-2020-2020-2020-202020202020')
def send_discovery_datum(info):
addresses = info.get('addresses', [])
if info['handler'] == pxeh:
enrich_pxe_info(info)
yield msg.KeyValueData({'nodename': info.get('nodename', '')})
yield msg.KeyValueData({'ipaddrs': [x[0] for x in addresses]})
sn = info.get('serialnumber', '')
mn = info.get('modelnumber', '')
uuid = info.get('uuid', '')
if uuid:
relatedmacs = []
for mac in known_uuids.get(uuid, {}):
if mac and mac != info.get('hwaddr', ''):
relatedmacs.append(mac)
if relatedmacs:
yield msg.KeyValueData({'relatedmacs': relatedmacs})
yield msg.KeyValueData({'serialnumber': sn})
yield msg.KeyValueData({'modelnumber': mn})
yield msg.KeyValueData({'uuid': uuid})
if 'enclosure.bay' in info:
yield msg.KeyValueData({'bay': int(info['enclosure.bay'])})
yield msg.KeyValueData({'serialnumber': info.get('serialnumber', '')})
yield msg.KeyValueData({'modelnumber': info.get('modelnumber', '')})
yield msg.KeyValueData({'macs': [info.get('hwaddr', '')]})
types = []
for infotype in info.get('services', []):
@@ -216,7 +162,6 @@ def _info_matches(info, criteria):
node = criteria.get('by-node', None)
serial = criteria.get('by-serial', None)
status = criteria.get('by-state', None)
uuid = criteria.get('by-uuid', None)
if model and info.get('modelnumber', None) != model:
return False
if devtype and devtype not in info.get('services', []):
@@ -227,8 +172,6 @@ def _info_matches(info, criteria):
return False
if status and info.get('discostatus', None) != status:
return False
if uuid and info.get('uuid', None) != uuid:
return False
return True
@@ -250,14 +193,6 @@ def list_matching_serials(criteria):
if _info_matches(info, criteria):
yield msg.ChildCollection(serial + '/')
def list_matching_uuids(criteria):
for uuid in sorted(list(known_uuids)):
for mac in known_uuids[uuid]:
info = known_uuids[uuid][mac]
if _info_matches(info, criteria):
yield msg.ChildCollection(uuid + '/')
break
def list_matching_states(criteria):
return [msg.ChildCollection(x) for x in ('discovered/', 'identified/',
@@ -303,19 +238,18 @@ list_info = {
'by-model': list_matching_models,
'by-mac': list_matching_macs,
'by-state': list_matching_states,
'by-uuid': list_matching_uuids,
}
multi_selectors = set([
'by-type',
'by-model',
'by-state',
'by-uuid',
])
node_selectors = set([
'by-node',
#'by-uuid',
'by-serial',
])
@@ -338,7 +272,7 @@ def _parameterize_path(pathcomponents):
if key not in validselectors:
raise exc.NotFoundException('{0} is not valid here'.format(key))
if key == 'by-type':
keyparams[key] = servicebyname.get(val, '!!!!invalid-type')
keyparams[key] = servicebyname.get(val, None)
else:
keyparams[key] = val
validselectors.discard(key)
@@ -359,37 +293,24 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
raise exc.InvalidArgumentException()
rescan()
return (msg.KeyValueData({'rescan': 'started'}),)
elif operation in ('update', 'create'):
elif (operation in ('update', 'create')):
if 'node' not in inputdata:
raise exc.InvalidArgumentException('Missing node name in input')
mac = _get_mac_from_query(pathcomponents)
_, queryparms, _, _ = _parameterize_path(pathcomponents[1:])
if 'by-mac' not in queryparms:
raise exc.InvalidArgumentException('Must target using "by-mac"')
mac = queryparms['by-mac'].replace('-', ':')
if mac not in known_info:
raise exc.NotFoundException('{0} not found'.format(mac))
info = known_info[mac]
if info['handler'] is None:
raise exc.NotImplementedException(
'Unable to {0} to {1}'.format(operation,
'/'.join(pathcomponents)))
handler = info['handler'].NodeHandler(info, configmanager)
eval_node(configmanager, handler, info, inputdata['node'],
manual=True)
return [msg.AssignedResource(inputdata['node'])]
elif operation == 'delete':
mac = _get_mac_from_query(pathcomponents)
del known_info[mac]
return [msg.DeletedResource(mac)]
raise exc.NotImplementedException(
'Unable to {0} to {1}'.format(operation, '/'.join(pathcomponents)))
def _get_mac_from_query(pathcomponents):
_, queryparms, _, _ = _parameterize_path(pathcomponents[1:])
if 'by-mac' not in queryparms:
raise exc.InvalidArgumentException('Must target using "by-mac"')
mac = queryparms['by-mac'].replace('-', ':')
if mac not in known_info:
raise exc.NotFoundException('{0} not found'.format(mac))
return mac
def handle_read_api_request(pathcomponents):
# TODO(jjohnson2): This should be more generalized...
# odd indexes into components are 'by-'*, even indexes
@@ -423,14 +344,6 @@ def detected_models():
def _recheck_nodes(nodeattribs, configmanager):
if rechecklock.locked():
# if already in progress, don't run again
# it may make sense to schedule a repeat, but will try the easier and less redundant way first
return
with rechecklock:
return _recheck_nodes_backend(nodeattribs, configmanager)
def _recheck_nodes_backend(nodeattribs, configmanager):
global rechecker
_map_unique_ids(nodeattribs)
# for the nodes whose attributes have changed, consider them as potential
@@ -451,29 +364,18 @@ def _recheck_nodes_backend(nodeattribs, configmanager):
# policy or hadn't been able to verify key
for nodename in pending_nodes:
info = pending_nodes[nodename]
try:
if info['handler'] is None:
next
handler = info['handler'].NodeHandler(info, configmanager)
discopool.spawn_n(eval_node, configmanager, handler, info, nodename)
except Exception:
traceback.print_exc()
log.log({'error': 'Unexpected error during discovery of {0}, check debug '
'logs'.format(nodename)})
handler = info['handler'].NodeHandler(info, configmanager)
eventlet.spawn_n(eval_node, configmanager, handler, info, nodename)
def _recheck_single_unknown(configmanager, mac):
info = unknown_info.get(mac, None)
_recheck_single_unknown_info(configmanager, info)
def _recheck_single_unknown_info(configmanager, info):
global rechecker
global rechecktime
if not info or info['handler'] is None:
info = unknown_info.get(mac, None)
if not info:
return
if info['handler'] != pxeh and not info.get('addresses', None):
#log.log({'info': 'Missing address information in ' + repr(info)})
log.log({'info': 'Missing address information in ' + repr(info)})
return
handler = info['handler'].NodeHandler(info, configmanager)
if handler.https_supported and not handler.https_cert:
@@ -484,8 +386,9 @@ def _recheck_single_unknown_info(configmanager, info):
''.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
# addresses data is bad, delete the offending ip
info['addresses'] = [x for x in info.get('addresses', []) if x != handler.ipaddr]
# addresses data is bad, clear it, to force repair next
# opportunity
info['addresses'] = []
# TODO(jjohnson2): rescan due to bad peer addr data?
# not just wait around for the next announce
return
@@ -495,15 +398,15 @@ def _recheck_single_unknown_info(configmanager, info):
'https, will examine later'.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
if rechecker is not None and rechecktime > util.monotonic_time() + 60:
rechecker.cancel()
# if cancel did not result in dead, then we are in progress
if rechecker is None or rechecker.dead:
rechecktime = util.monotonic_time() + 300
rechecker = eventlet.spawn_after(300, _periodic_recheck,
rechecktime = util.monotonic_time() + 60
rechecker = eventlet.spawn_after(60, _periodic_recheck,
configmanager)
return
nodename, info['maccount'] = get_nodename(configmanager, handler, info)
nodename = get_nodename(configmanager, handler, info)
if nodename:
if handler.https_supported:
dp = configmanager.get_node_attributes([nodename],
@@ -515,32 +418,28 @@ def _recheck_single_unknown_info(configmanager, info):
known_nodes[nodename][info['hwaddr']] = info
info['discostatus'] = 'discovered'
return # already known, no need for more
discopool.spawn_n(eval_node, configmanager, handler, info, nodename)
eventlet.spawn_n(eval_node, configmanager, handler, info, nodename)
def safe_detected(info):
if 'hwaddr' not in info or not info['hwaddr']:
return
if info['hwaddr'] in runningevals:
# Do not evaluate the same mac multiple times at once
return
runningevals[info['hwaddr']] = discopool.spawn(eval_detected, info)
eventlet.spawn_n(eval_detected, info)
def eval_detected(info):
try:
detected(info)
return detected(info)
except Exception as e:
traceback.print_exc()
del runningevals[info['hwaddr']]
def detected(info):
global rechecker
global rechecktime
if 'hwaddr' not in info:
return # For now, require hwaddr field to proceed
# later, manual and CMM discovery may act on SN and/or UUID
for service in info['services']:
if service in nodehandlers:
if nodehandlers.get(service, None):
if service not in known_services:
known_services[service] = set([])
handler = nodehandlers[service]
@@ -549,7 +448,7 @@ def detected(info):
else: # no nodehandler, ignore for now
return
try:
snum = info['attributes']['enclosure-serial-number'][0].strip()
snum = info['attributes']['enclosure-serial-number'][0].rstrip()
if snum:
info['serialnumber'] = snum
known_serials[info['serialnumber']] = info
@@ -588,39 +487,34 @@ def detected(info):
return
known_info[info['hwaddr']] = info
cfg = cfm.ConfigManager(None)
if handler:
handler = handler.NodeHandler(info, cfg)
handler.scan()
uuid = info.get('uuid', None)
if uuid_is_valid(uuid):
known_uuids[uuid][info['hwaddr']] = info
if handler and handler.https_supported and not handler.https_cert:
handler = handler.NodeHandler(info, cfg)
if handler.https_supported and not handler.https_cert:
if handler.cert_fail_reason == 'unreachable':
log.log(
{
'info': '{0} with hwaddr {1} is not reachable by https '
'at address {2}'.format(
'info': '{0} with hwaddr {1} is not reachable at {2}'
''.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
info['addresses'] = [x for x in info.get('addresses', []) if x != handler.ipaddr]
info['addresses'] = []
return
log.log(
{'info': '{0} with hwaddr {1} at address {2} is not yet running '
'https, will examine later'.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
if rechecker is not None and rechecktime > util.monotonic_time() + 60:
rechecker.cancel()
if rechecker is None or rechecker.dead:
rechecktime = util.monotonic_time() + 300
rechecker = eventlet.spawn_after(300, _periodic_recheck, cfg)
rechecktime = util.monotonic_time() + 60
rechecker = eventlet.spawn_after(60, _periodic_recheck, cfg)
unknown_info[info['hwaddr']] = info
info['discostatus'] = 'unidentfied'
#TODO, eventlet spawn after to recheck sooner, or somehow else
# influence periodic recheck to shorten delay?
return
nodename, info['maccount'] = get_nodename(cfg, handler, info)
if nodename and handler and handler.https_supported:
nodename = get_nodename(cfg, handler, info)
if nodename and handler.https_supported:
dp = cfg.get_node_attributes([nodename],
('pubkeys.tls_hardwaremanager',))
lastfp = dp.get(nodename, {}).get('pubkeys.tls_hardwaremanager',
@@ -633,9 +527,9 @@ def detected(info):
#TODO(jjohnson2): We might have to get UUID for certain searches...
#for now defer probe until inside eval_node. We might not have
#a nodename without probe in the future.
if nodename and handler:
if nodename:
eval_node(cfg, handler, info, nodename)
elif handler:
else:
log.log(
{'info': 'Detected unknown {0} with hwaddr {1} at '
'address {2}'.format(
@@ -645,190 +539,34 @@ def detected(info):
unknown_info[info['hwaddr']] = info
def b64tohex(b64str):
bd = base64.b64decode(b64str)
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
def get_enclosure_chain_head(nodename, cfg):
ne = True
members = [nodename]
while ne:
ne = cfg.get_node_attributes(
nodename, 'enclosure.extends').get(nodename, {}).get(
'enclosure.extends', {}).get('value', None)
if not ne:
return nodename
if ne in members:
raise exc.InvalidArgumentException(
'Circular chain that includes ' + nodename)
nodename = ne
members.append(nodename)
return nodename
def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True):
# nodename is the head of the chain, cfg is a configmanager, handler
# is the handler of the current candidate, nl is optional indication
# of the next link in the chain, checkswitch can disable the switch
# search if not indicated by current situation
# returns the new name and whether it has been securely validated or not
# first we check to see if directly connected
mycert = handler.https_cert
if checkswitch:
fprints = macmap.get_node_fingerprints(nodename, cfg)
for fprint in fprints:
if util.cert_matches(fprint[0], mycert):
# ok we have a direct match, it is this node
return nodename, fprint[1]
# ok, unable to get it, need to traverse the chain from the beginning
if not nl:
nl = list(cfg.filter_node_attributes(
'enclosure.extends=' + nodename))
while nl:
if len(nl) != 1:
raise exc.InvalidArgumentException('Multiple enclosures trying to '
'extend a single enclosure')
cd = cfg.get_node_attributes(nodename, ['hardwaremanagement.manager',
'pubkeys.tls_hardwaremanager'])
smmaddr = cd[nodename]['hardwaremanagement.manager']['value']
pkey = cd[nodename].get('pubkeys.tls_hardwaremanager', {}).get(
'value', None)
if pkey:
cv = util.TLSCertVerifier(
cfg, nodename, 'pubkeys.tls_hardwaremanager').verify_cert
for fprint in get_smm_neighbor_fingerprints(smmaddr, cv):
if util.cert_matches(fprint, mycert):
# a trusted chain member vouched for the cert
# so it's validated
return nl[0], True
# advance down the chain by one and try again
nodename = nl[0]
nl = list(cfg.filter_node_attributes(
'enclosure.extends=' + nodename))
return None, False
def get_smm_neighbor_fingerprints(smmaddr, cv):
if ':' in smmaddr:
smmaddr = '[{0}]'.format(smmaddr)
wc = webclient.SecureHTTPConnection(smmaddr, verifycallback=cv)
neighs = wc.grab_json_response('/scripts/neighdata.json')
for idx in (4, 5):
if 'sha256' not in neighs[idx]:
continue
yield 'sha256$' + b64tohex(neighs[idx]['sha256'])
def get_nodename(cfg, handler, info):
nodename = None
maccount = None
info['verified'] = False
if not handler:
return None, None
if handler.https_supported:
currcert = handler.https_cert
if not currcert:
info['discofailure'] = 'nohttps'
return None, None
currprint = util.get_fingerprint(currcert, 'sha256')
nodename = nodes_by_fprint.get(currprint, None)
if not nodename:
# Try SHA512 as well
currprint = util.get_fingerprint(currcert)
nodename = nodes_by_fprint.get(currprint, None)
if not handler.https_supported:
curruuid = info['uuid']
nodename = nodes_by_uuid.get(curruuid, None)
if nodename is None:
# TODO: if there are too many matches on port for a
# given type, error! Can't just arbitarily limit,
# shared nic with vms is possible and valid
nodename = macmap.find_node_by_mac(info['hwaddr'], cfg)
return nodename
currcert = handler.https_cert
if not currcert:
info['discofailure'] = 'nohttps'
return None
currprint = util.get_fingerprint(currcert)
nodename = nodes_by_fprint.get(currprint, None)
if not nodename:
curruuid = info.get('uuid', None)
if uuid_is_valid(curruuid):
nodename = nodes_by_uuid.get(curruuid, None)
if nodename is None:
_map_unique_ids()
nodename = nodes_by_uuid.get(curruuid, None)
if not nodename:
# Ok, see if it is something with a chassis-uuid and discover by
# chassis
nodename = get_nodename_from_enclosures(cfg, info)
if not nodename and handler.devname == 'SMM':
nodename = get_nodename_from_chained_smms(cfg, handler, info)
if not nodename: # as a last resort, search switches for info
# This is the slowest potential operation, so we hope for the
# best to occur prior to this
nodename, macinfo = macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
maccount = macinfo['maccount']
if nodename:
if handler.devname == 'SMM':
nl = list(cfg.filter_node_attributes(
'enclosure.extends=' + nodename))
if nl:
# We found an SMM, and it's in a chain per configuration
# we need to ask the switch for the fingerprint to see
# if we have a match or not
newnodename, v = get_chained_smm_name(nodename, cfg,
handler, nl)
if newnodename:
# while this started by switch, it was disambiguated
info['verified'] = v
return newnodename, None
if (nodename and
not handler.discoverable_by_switch(macinfo['maccount'])):
if handler.devname == 'SMM':
errorstr = 'Attempt to discover SMM by switch, but chained ' \
'topology or incorrect net attributes detected, ' \
'which is not compatible with switch discovery ' \
'of SMM, nodename would have been ' \
'{0}'.format(nodename)
log.log({'error': errorstr})
return None, None
return nodename, maccount
def get_nodename_from_chained_smms(cfg, handler, info):
nodename = None
for fprint in get_smm_neighbor_fingerprints(
handler.ipaddr, lambda x: True):
if fprint in nodes_by_fprint:
# need to chase the whole chain
# to support either direction
chead = get_enclosure_chain_head(nodes_by_fprint[fprint],
cfg)
newnodename, v = get_chained_smm_name(
chead, cfg, handler, checkswitch=False)
if newnodename:
info['verified'] = v
nodename = newnodename
return nodename
def get_nodename_from_enclosures(cfg, info):
nodename = None
cuuid = info.get('attributes', {}).get('chassis-uuid', [None])[0]
if cuuid and cuuid in nodes_by_uuid:
encl = nodes_by_uuid[cuuid]
bay = info.get('enclosure.bay', None)
if bay:
tnl = cfg.filter_node_attributes('enclosure.manager=' + encl)
tnl = list(
cfg.filter_node_attributes('enclosure.bay={0}'.format(bay),
tnl))
if len(tnl) == 1:
# This is not a secure assurance, because it's by
# uuid instead of a key
nodename = tnl[0]
nodename = macmap.find_node_by_mac(info['hwaddr'], cfg)
return nodename
def eval_node(cfg, handler, info, nodename, manual=False):
try:
handler.probe() # unicast interrogation as possible to get more data
# for now, we search switch only, ideally we search cmm, smm, and
# switch concurrently
# do some preconfig, for example, to bring a SMM online if applicable
errorstr = handler.preconfig()
if errorstr:
if manual:
raise exc.InvalidArgumentException(errorstr)
log.log({'error': errorstr})
return
handler.preconfig()
except Exception as e:
unknown_info[info['hwaddr']] = info
info['discostatus'] = 'unidentified'
@@ -856,7 +594,6 @@ def eval_node(cfg, handler, info, nodename, manual=False):
if not handler.is_enclosure and nl:
# The specified node is an enclosure (has nodes mapped to it), but
# what we are talking to is *not* an enclosure
# might be ambiguous, need to match chassis-uuid as well..
if 'enclosure.bay' not in info:
unknown_info[info['hwaddr']] = info
info['discostatus'] = 'unidentified'
@@ -868,38 +605,11 @@ def eval_node(cfg, handler, info, nodename, manual=False):
raise exc.InvalidArgumentException(errorstr)
log.log({'error': errorstr})
return
enl = list(cfg.filter_node_attributes('enclosure.extends=' + nodename))
if enl:
# ambiguous SMM situation according to the configuration, we need
# to match uuid
encuuid = info['attributes'].get('chasis-uuid', None)
if encuuid:
enl = list(cfg.filter_node_attributes('id.uuid=' + encuuid))
if len(enl) != 1:
# errorstr = 'No SMM by given UUID known, *yet*'
# if manual:
# raise exc.InvalidArgumentException(errorstr)
# log.log({'error': errorstr})
if encuuid in pending_by_uuid:
pending_by_uuid[encuuid].add(info)
else:
pending_by_uuid[encuuid] = set([info])
return
# We found the real smm, replace the list with the actual smm
# to continue
nl = list(cfg.filter_node_attributes(
'enclosure.manager=' + enl[0]))
else:
errorstr = 'Chained SMM configuration with older XCC, ' \
'unable to perform zero power discovery'
if manual:
raise exc.InvalidArgumentException(errorstr)
log.log({'error': errorstr})
return
# search for nodes fitting our description using filters
# lead with the most specific to have a small second pass
nl = list(cfg.filter_node_attributes(
'enclosure.bay={0}'.format(info['enclosure.bay']), nl))
nl = cfg.filter_node_attributes(
'enclosure.bay=' + info['enclosure.bay'], nl)
nl = list(nl)
if len(nl) != 1:
info['discofailure'] = 'ambigconfig'
if len(nl):
@@ -926,29 +636,6 @@ def eval_node(cfg, handler, info, nodename, manual=False):
pending_nodes[nodename] = info
else:
# we can and did accurately discover by switch or in enclosure
# but... is this really ok? could be on an upstream port or
# erroneously put in the enclosure with no nodes yet
# so first, see if the candidate node is a chain host
if info['maccount']:
# discovery happened through switch
nl = list(cfg.filter_node_attributes(
'enclosure.extends=' + nodename))
if nl:
# The candidate nodename is the head of a chain, we must
# validate the smm certificate by the switch
macmap.get_node_fingerprint(nodename, cfg)
util.handler.cert_matches(fprint, handler.https_cert)
return
if (info['maccount'] and
not handler.discoverable_by_switch(info['maccount'])):
errorstr = 'The detected node {0} was detected using switch, ' \
'however the relevant port has too many macs learned ' \
'for this type of device ({1}) to be discovered by ' \
'switch.'.format(nodename, handler.devname)
if manual:
raise exc.InvalidArgumentException(errorstr)
log.log({'error': errorstr})
return
if not discover_node(cfg, handler, info, nodename, manual):
pending_nodes[nodename] = info
@@ -963,20 +650,14 @@ def discover_node(cfg, handler, info, nodename, manual):
'pubkeys.tls_hardwaremanager'))
policy = dp.get(nodename, {}).get('discovery.policy', {}).get(
'value', None)
if policy is None:
policy = ''
policies = set(policy.split(','))
lastfp = dp.get(nodename, {}).get('pubkeys.tls_hardwaremanager',
{}).get('value', None)
# TODO(jjohnson2): permissive requires we guarantee storage of
# the pubkeys, which is deferred for a little bit
# Also, 'secure', when we have the needed infrastructure done
# in some product or another.
curruuid = info.get('uuid', False)
if 'pxe' in policies and info['handler'] == pxeh:
return do_pxe_discovery(cfg, handler, info, manual, nodename, policies)
elif ('permissive' in policies and handler.https_supported and lastfp and
not util.cert_matches(lastfp, handler.https_cert) and not manual):
if (policy == 'permissive' and handler.https_supported and lastfp and
not manual):
info['discofailure'] = 'fingerprint'
log.log({'info': 'Detected replacement of {0} with existing '
'fingerprint and permissive discovery policy, not '
@@ -984,18 +665,33 @@ def discover_node(cfg, handler, info, nodename, manual):
'pubkeys.tls_hardwaremanager attribute is cleared '
'first'.format(nodename)})
return False # With a permissive policy, do not discover new
elif policies & set(('open', 'permissive')) or manual:
elif policy in ('open', 'permissive') or manual:
info['nodename'] = nodename
if info['handler'] == pxeh:
return do_pxe_discovery(cfg, handler, info, manual, nodename, policies)
if not handler.https_supported:
# use uuid based scheme in lieu of tls cert, ideally only
# for stateless 'discovery' targets like pxe, where data does not
# change
if info['uuid'] in known_pxe_uuids:
return True
uuidinfo = cfg.get_node_attributes(nodename, 'id.uuid')
known_pxe_uuids[info['uuid']] = nodename
# TODO(jjohnson2): This is messing with the attrib database
# so it should only be possible if policy is 'open'
#
if manual or policy == 'open':
olduuid = uuidinfo.get(nodename, {}).get('id.uuid', None)
if 'uuid' in info and info['uuid'] != olduuid:
cfg.set_node_attributes(
{nodename: {'id.uuid': info['uuid']}})
log.log({'info': 'Detected {0} ({1} with mac {2})'.format(
nodename, handler.devname, info['hwaddr'])})
return True
elif manual or not util.cert_matches(lastfp, handler.https_cert):
# only 'discover' if it is not the same as last time
try:
handler.config(nodename)
except Exception as e:
info['discofailure'] = 'bug'
if manual:
raise
log.log(
{'error':
'Error encountered trying to set up {0}, {1}'.format(
@@ -1005,20 +701,14 @@ def discover_node(cfg, handler, info, nodename, manual):
newnodeattribs = {}
if 'uuid' in info:
newnodeattribs['id.uuid'] = info['uuid']
if 'serialnumber' in info:
newnodeattribs['id.serial'] = info['serialnumber']
if 'modelnumber' in info:
newnodeattribs['id.model'] = info['modelnumber']
if handler.https_cert:
newnodeattribs['pubkeys.tls_hardwaremanager'] = \
util.get_fingerprint(handler.https_cert, 'sha256')
util.get_fingerprint(handler.https_cert)
if newnodeattribs:
cfg.set_node_attributes({nodename: newnodeattribs})
log.log({'info': 'Discovered {0} ({1})'.format(nodename,
handler.devname)})
info['discostatus'] = 'discovered'
for i in pending_by_uuid.get(curruuid, []):
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
return True
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
'value allowing discovery (open or permissive)'.format(
@@ -1027,39 +717,6 @@ def discover_node(cfg, handler, info, nodename, manual):
return False
def do_pxe_discovery(cfg, handler, info, manual, nodename, policies):
# use uuid based scheme in lieu of tls cert, ideally only
# for stateless 'discovery' targets like pxe, where data does not
# change
uuidinfo = cfg.get_node_attributes(nodename, ['id.uuid', 'id.serial', 'id.model', 'net*.bootable'])
if manual or policies & set(('open', 'pxe')):
enrich_pxe_info(info)
attribs = {}
olduuid = uuidinfo.get(nodename, {}).get('id.uuid', None)
uuid = info.get('uuid', None)
if uuid and uuid != olduuid:
attribs['id.uuid'] = info['uuid']
sn = info.get('serialnumber', None)
mn = info.get('modelnumber', None)
if sn and sn != uuidinfo.get(nodename, {}).get('id.serial', None):
attribs['id.serial'] = sn
if mn and mn != uuidinfo.get(nodename, {}).get('id.model', None):
attribs['id.model'] = mn
for attrname in uuidinfo.get(nodename, {}):
if attrname.endswith('.bootable') and uuidinfo[nodename][attrname].get('value', None):
newattrname = attrname[:-8] + 'hwaddr'
attribs[newattrname] = info['hwaddr']
if attribs:
cfg.set_node_attributes({nodename: attribs})
if info['uuid'] in known_pxe_uuids:
return True
if uuid_is_valid(info['uuid']):
known_pxe_uuids[info['uuid']] = nodename
log.log({'info': 'Detected {0} ({1} with mac {2})'.format(
nodename, handler.devname, info['hwaddr'])})
return True
attribwatcher = None
nodeaddhandler = None
needaddhandled = False
@@ -1085,7 +742,7 @@ def newnodes(added, deleting, configmanager):
attribwatcher = configmanager.watch_attributes(
allnodes, ('discovery.policy', 'net*.switch',
'hardwaremanagement.manager', 'net*.switchport', 'id.uuid',
'pubkeys.tls_hardwaremanager', 'net*.bootable'), _recheck_nodes)
'pubkeys.tls_hardwaremanager'), _recheck_nodes)
if nodeaddhandler:
needaddhandled = True
else:
@@ -1101,18 +758,21 @@ def _periodic_recheck(configmanager):
global rechecker
global rechecktime
rechecker = None
try:
_recheck_nodes((), configmanager)
except Exception:
traceback.print_exc()
log.log({'error': 'Unexpected error during discovery, check debug '
'logs'})
# if rechecker is set, it means that an accelerated schedule
# for rechecker was requested in the course of recheck_nodes
if rechecker is None:
rechecktime = util.monotonic_time() + 900
rechecker = eventlet.spawn_after(900, _periodic_recheck,
configmanager)
# There shouldn't be anything causing this to double up, but just in case
# use a semaphore to absolutely guarantee this doesn't multiply
with rechecklock:
try:
_recheck_nodes((), configmanager)
except Exception:
traceback.print_exc()
log.log({'error': 'Unexpected error during discovery, check debug '
'logs'})
# if rechecker is set, it means that an accelerated schedule
# for rechecker was requested in the course of recheck_nodes
if rechecker is None:
rechecktime = util.monotonic_time() + 900
rechecker = eventlet.spawn_after(900, _periodic_recheck,
configmanager)
def rescan():
@@ -1160,33 +820,31 @@ def _map_unique_ids(nodes=None):
uuid_by_nodes = {}
fprint_by_nodes = {}
for uuid in nodes_by_uuid:
if not uuid_is_valid():
continue
node = nodes_by_uuid[uuid]
if node in bigmap:
uuid_by_nodes[node] = uuid
for fprint in nodes_by_fprint:
node = nodes_by_fprint[fprint]
if node in bigmap:
fprint_by_nodes[node] = fprint
fprint_by_nodes[node] =fprint
for node in bigmap:
if node in uuid_by_nodes:
del nodes_by_uuid[uuid_by_nodes[node]]
if node in fprint_by_nodes:
del nodes_by_fprint[fprint_by_nodes[node]]
uuid = bigmap[node].get('id.uuid', {}).get('value', None)
if uuid_is_valid(uuid):
if uuid:
nodes_by_uuid[uuid] = node
fprint = bigmap[node].get(
'pubkeys.tls_hardwaremanager', {}).get('value', None)
if fprint:
nodes_by_fprint[fprint] = node
for uuid in known_pxe_uuids:
if uuid_is_valid(uuid) and uuid not in nodes_by_uuid:
if uuid not in nodes_by_uuid:
nodes_by_uuid[uuid] = known_pxe_uuids[uuid]
if __name__ == '__main__':
start_detection()
while True:
eventlet.sleep(30)
eventlet.sleep(30)
@@ -43,9 +43,6 @@ class NodeHandler(generic.NodeHandler):
# TODO(jjohnson2): probe serial number and uuid
def config(self, nodename, reset=False):
self._bmcconfig(nodename, reset)
def _bmcconfig(self, nodename, reset=False):
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
@@ -91,8 +88,7 @@ class NodeHandler(generic.NodeHandler):
if ('secret.hardwaremanagementuser' not in cd or
'secret.hardwaremanagementpassword' not in cd):
raise exc.TargetEndpointBadCredentials(
'secret.hardwaremanagementuser and/or '
'secret.hardwaremanagementpassword was not configured')
'Missing user and/or password')
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
@@ -154,4 +150,4 @@ class NodeHandler(generic.NodeHandler):
raise
if reset:
ic.reset_bmc()
return ic
return
@@ -19,31 +19,21 @@ webclient = eventlet.import_patched('pyghmi.util.webclient')
class NodeHandler(object):
https_supported = True
is_enclosure = False
devname = ''
maxmacs = 2 # reasonable default, allowing for common scenario of
# shared nic in theory, but blocking enclosure managers
# and uplink ports
def __init__(self, info, configmanager):
self._certfailreason = None
self._fp = None
self.info = info
self.configmanager = configmanager
targsa = [None]
targsa = None
# first let us prefer LLA if possible, since that's most stable
for sa in info['addresses']:
if sa[0].startswith('fe80'):
targsa = sa
break
else:
if info.get('addresses', False):
targsa = info['addresses'][0]
targsa = info['addresses'][0]
self.ipaddr = targsa[0]
def scan(self):
# Do completely passive things to enhance data.
# Probe is permitted to for example attempt a login
# scan *only* does what it can without a login attempt
return
def probe(self):
@@ -54,10 +44,9 @@ class NodeHandler(object):
def preconfig(self):
return
def discoverable_by_switch(self, macs):
# Given the number of macs sharing the port, is this handler
# appropriate?
return macs <= self.maxmacs
@property
def discoverable_by_switch(self):
return True
def _savecert(self, certificate):
self._fp = certificate
@@ -15,71 +15,32 @@
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):
devname = 'IMM'
def scan(self):
slpattrs = self.info.get('attributes', {})
self.isdense = False
try:
ff = slpattrs.get('enclosure-form-factor', [''])[0]
except IndexError:
return
wronguuid = slpattrs.get('node-uuid', [''])[0]
if wronguuid:
# we need to fix the first three portions of the uuid
uuidprefix = wronguuid.split('-')[:3]
uuidprefix = struct.pack(
'<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \
uuidprefix[12:16]
self.info['uuid'] = uuidprefix + '-' + '-'.join(
wronguuid.split('-')[3:])
self.info['uuid'] = string.lower(self.info['uuid'])
if ff not in ('dense-computing', 'BC2'):
# do not probe unless it's a dense platform
return
self.isdense = True
slot = int(slpattrs.get('slot', ['0'])[0])
if slot != 0:
self.info['enclosure.bay'] = slot
def probe(self):
if self.info.get('enclosure.bay', 0) == 0:
self.scan()
if self.info.get('enclosure.bay', 0) != 0:
# scan has already populated info
return
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff != 'dense-computing':
return
try:
# we are a dense platform, but the SLP data did not give us slot
# attempt to probe using IPMI
ipmicmd = self._get_ipmicmd()
guiddata = ipmicmd.xraw_command(netfn=6, command=8)
self.info['uuid'] = pygutil.decode_wireformat_uuid(
guiddata['data']).lower()
guiddata['data'])
ipmicmd.oem_init()
bayid = ipmicmd._oem.immhandler.get_property(
'/v2/cmm/sp/7')
if not bayid:
return
self.info['enclosure.bay'] = int(bayid)
smmid = ipmicmd._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
if not smmid:
return
smmid = smmid.lower().replace(' ', '')
smmid = '{0}-{1}-{2}-{3}-{4}'.format(smmid[:8], smmid[8:12],
smmid[12:16], smmid[16:20],
smmid[20:])
self.info['enclosure.uuid'] = smmid
self.info['enclosure.type'] = 'smm'
#
self.info['enclosure.bay'] = bayid
# enclosure.bay only happens for Flex, nextscale doesn't do it
# this way
except pygexc.IpmiException as ie:
print(repr(ie))
raise
# TODO(jjohnson2): web based init config for future prevalidated cert scheme
# def config(self, nodename):
# return
@@ -13,28 +13,10 @@
# limitations under the License.
import confluent.discovery.handlers.bmc as bmchandler
import struct
def fixuuid(baduuid):
# SMM dumps it out in hex
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).lower()
class NodeHandler(bmchandler.NodeHandler):
is_enclosure = True
devname = 'SMM'
maxmacs = 5 # support an enclosure, but try to avoid catching daisy chain
def scan(self):
# the UUID is in a weird order, fix it up to match
# ipmi return and property value
uuid = self.info.get('attributes', {}).get('uuid', None)
if uuid:
uuid = fixuuid(uuid[0])
self.info['uuid'] = uuid
def config(self, nodename):
# SMM for now has to reset to assure configuration applies
@@ -12,36 +12,41 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.discovery.handlers.imm as immhandler
import confluent.discovery.handlers.bmc as bmchandler
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.oem.lenovo.imm as imm
import pyghmi.ipmi.private.util as pygutil
class NodeHandler(immhandler.NodeHandler):
class NodeHandler(bmchandler.NodeHandler):
devname = 'XCC'
def preconfig(self):
ff = None
def probe(self):
try:
ff = self.info['attributes']['enclosure-form-factor']
except KeyError:
try:
# an XCC should always have that set, this is sign of
# a bug, try to reset the BMC as a workaround
ipmicmd = self._get_ipmicmd()
ipmicmd.reset_bmc()
return "XCC with address {0} did not have attributes " \
"declared, attempting to correct with " \
"XCC reset".format(self.ipaddr)
except pygexc.IpmiException as e:
if (e.ipmicode != 193 and
'Unauthorized name' not in str(e) and
'Incorrect password' not in str(e)):
# raise an issue if anything other than to be expected
raise
if ff not in ('dense-computing', [u'dense-computing']):
return
ipmicmd = self._get_ipmicmd()
guiddata = ipmicmd.xraw_command(netfn=6, command=8)
self.info['uuid'] = pygutil.decode_wireformat_uuid(
guiddata['data'])
ipmicmd.oem_init()
bayid = ipmicmd._oem.immhandler.get_property(
'/v2/cmm/sp/7')
if not bayid:
return
self.info['enclosure.bay'] = bayid
smmid = ipmicmd._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
if not smmid:
return
smmid = smmid.lower().replace(' ', '')
smmid = '{0}-{1}-{2}-{3}-{4}'.format(smmid[:8], smmid[8:12],
smmid[12:16], smmid[16:20],
smmid[20:])
self.info['enclosure.uuid'] = smmid
self.info['enclosure.type'] = 'smm'
except pygexc.IpmiException as ie:
print(repr(ie))
raise
def preconfig(self):
# attempt to enable SMM
#it's normal to get a 'not supported' (193) for systems without an SMM
ipmicmd = None
@@ -49,8 +54,7 @@ class NodeHandler(immhandler.NodeHandler):
ipmicmd = self._get_ipmicmd()
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
except pygexc.IpmiException as e:
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
'Incorrect password' not in str(e)):
if e.ipmicode != 193:
# raise an issue if anything other than to be expected
raise
#TODO: decide how to clean out if important
@@ -58,30 +62,6 @@ class NodeHandler(immhandler.NodeHandler):
#if ipmicmd:
# ipmicmd.ipmi_session.logout()
def config(self, nodename, reset=False):
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
ic = self._bmcconfig(nodename)
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
return
# Ok, we can get the enclosure uuid now..
ic.oem_init()
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
if enclosureuuid:
enclosureuuid = imm.fixup_uuid(enclosureuuid).lower()
em = self.configmanager.get_node_attributes(nodename,
'enclosure.manager')
em = em.get(nodename, {}).get('enclosure.manager', {}).get(
'value', None)
# ok, set the uuid of the manager...
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):
@@ -37,7 +37,7 @@ def decode_uuid(rawguid):
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
return '{0:08X}-{1:04X}-{2:04X}-{3:04X}-{4:04X}{5:08X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2]).lower()
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2])
def find_info_in_options(rq, optidx):
@@ -92,7 +92,7 @@ def snoop(handler):
rq = bytearray(rq)
if rq[0] == 1: # Boot request
addrlen = rq[2]
if addrlen > 16 or addrlen == 0:
if addrlen > 16: # max address size in bootp is 16 bytes
continue
netaddr = rq[28:28+addrlen]
netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr])
@@ -28,9 +28,6 @@ _slp_services = set([
'service:management-hardware.IBM:integrated-management-module2',
'service:lenovo-smm',
'service:management-hardware.Lenovo:lenovo-xclarity-controller',
'service:management-hardware.IBM:chassis-management-module',
'service:management-hardware.Lenovo:chassis-management-module',
'service:io-device.Lenovo:management-module',
])
# SLP has a lot of ambition that was unfulfilled in practice.
@@ -41,12 +38,6 @@ srvreqfooter = b'\x00\x07DEFAULT\x00\x00\x00\x00'
# which is defined in RFC 3059, used to indicate support for that capability
attrlistext = b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00'
try:
IPPROTO_IPV6 = socket.IPPROTO_IPV6
except AttributeError:
IPPROTO_IPV6 = 41 # Assume Windows value if socket is missing it
def _parse_slp_header(packet):
packet = bytearray(packet)
@@ -206,7 +197,7 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
v6addrs.append(('ff02::1:' + v6hash, 427, 0, 0))
for idx in util.list_interface_indexes():
# IPv6 multicast is by index, so lead with that
net.setsockopt(IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, idx)
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, idx)
for sa in v6addrs:
try:
net.sendto(data, sa)
@@ -222,13 +213,7 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
bcast = i4['broadcast']
net4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
socket.inet_aton(addr))
try:
net4.sendto(data, ('239.255.255.253', 427))
except socket.error as se:
# On occasion, multicasting may be disabled
# tolerate this scenario and move on
if se.errno != 101:
raise
net4.sendto(data, ('239.255.255.253', 427))
net4.sendto(data, (bcast, 427))
@@ -279,7 +264,7 @@ def _parse_attrlist(attrstr):
'{4:04X}{5:08X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0],
bebytes[1], bebytes[2]
).lower()
)
attribs[attrname].append(val)
attrstr = attrstr[attrstr.index(')'):]
elif attrstr[0] == ',':
@@ -393,14 +378,14 @@ def snoop(handler):
"""
active_scan(handler)
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
slpg = socket.inet_pton(socket.AF_INET6, 'ff01::123')
slpg2 = socket.inet_pton(socket.AF_INET6, 'ff02::123')
for i6idx in util.list_interface_indexes():
mreq = slpg + struct.pack('=I', i6idx)
net.setsockopt(IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
mreq = slpg2 + struct.pack('=I', i6idx)
net.setsockopt(IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
net.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -450,15 +435,8 @@ def snoop(handler):
# ignore for now
known_peers.discard(peer)
continue
# we want to prioritize the very well known services
svcs = []
for svc in q:
if svc in _slp_services:
svcs.insert(0, svc)
else:
svcs.append(svc)
peerbymacaddress[mac] = {
'services': svcs,
'services': q,
'addresses': [peer],
}
newmacs.add(mac)
@@ -484,7 +462,7 @@ def active_scan(handler):
handler(scanned)
def scan(srvtypes=_slp_services, addresses=None, localonly=False):
def scan(srvtypes=_slp_services, addresses=None):
"""Find targets providing matching requested srvtypes
This is a generator that will iterate over respondants to the SrvType
@@ -499,15 +477,13 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
"""
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# increase RCVBUF to max, mitigate chance of
# TODO: increase RCVBUF to max, mitigate chance of
# failure due to full buffer.
net.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216)
net4.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216)
# SLP is very poor at scanning large counts and managing it, so we
# must make the best of it
# Some platforms/config default to IPV6ONLY, we are doing IPv4
# too, so force it
#net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
# we are going to do broadcast, so allow that...
initxid = random.randint(0, 32768)
xididx = 0
@@ -526,12 +502,6 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
_grab_rsps((net, net4), rsps, 1, xidmap)
# now to analyze and flesh out the responses
for id in rsps:
if localonly:
for addr in rsps[id]['addresses']:
if 'fe80' in addr[0]:
break
else:
continue
_add_attributes(rsps[id])
del rsps[id]['payload']
del rsps[id]['function']
@@ -542,4 +512,4 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
if __name__ == '__main__':
def testsnoop(a):
print(repr(a))
snoop(testsnoop)
snoop(testsnoop)
+14 -21
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2017 Lenovo
# Copyright 2015 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,86 +21,79 @@ import json
class ConfluentException(Exception):
apierrorcode = 500
_apierrorstr = 'Unexpected Error'
apierrorstr = 'Unexpected Error'
def get_error_body(self):
errstr = ' - '.join((self._apierrorstr, str(self)))
errstr = ' - '.join((self.apierrorstr, str(self)))
return json.dumps({'error': errstr })
@property
def apierrorstr(self):
if str(self):
return self._apierrorstr + ' - ' + str(self)
return self._apierrorstr
class NotFoundException(ConfluentException):
# Something that could be construed as a name was not found
# basically, picture an http error code 404
apierrorcode = 404
_apierrorstr = 'Target not found'
apierrorstr = 'Request path not recognized'
class InvalidArgumentException(ConfluentException):
# Something from the remote client wasn't correct
# like http code 400
apierrorcode = 400
_apierrorstr = 'Bad Request'
apierrorstr = 'Bad Request'
class TargetEndpointUnreachable(ConfluentException):
# A target system was unavailable. For example, a BMC
# was unreachable. http code 504
apierrorcode = 504
_apierrorstr = 'Unreachable Target'
apierrorstr = 'Unreachable Target'
class TargetEndpointBadCredentials(ConfluentException):
# target was reachable, but authentication/authorization
# failed
apierrorcode = 502
_apierrorstr = 'Bad Credentials'
apierrorstr = 'Bad Credentials'
class LockedCredentials(ConfluentException):
# A request was performed that required a credential, but the credential
# store is locked
_apierrorstr = 'Credential store locked'
apierrorstr = 'Credential store locked'
class ForbiddenRequest(ConfluentException):
# The client request is not allowed by authorization engine
apierrorcode = 403
_apierrorstr = 'Forbidden'
apierrorstr = 'Forbidden'
class NotImplementedException(ConfluentException):
# The current configuration/plugin is unable to perform
# the requested task. http code 501
apierrorcode = 501
_apierrorstr = '501 - Not Implemented'
apierrorstr = '501 - Not Implemented'
class GlobalConfigError(ConfluentException):
# The configuration in the global config file is not right
_apierrorstr = 'Global configuration contains an error'
apierrorstr = 'Global configuration contains an error'
class TargetResourceUnavailable(ConfluentException):
# This is meant for scenarios like asking to read a sensor that is
# currently unavailable. This may be a persistent or transient state
apierrocode = 503
_apierrorstr = 'Target Resource Unavailable'
apierrorstr = 'Target Resource Unavailable'
class PubkeyInvalid(ConfluentException):
apierrorcode = 502
_apierrorstr = '502 - Invalid certificate or key on target'
apierrorstr = '502 - Invalid certificate or key on target'
def __init__(self, text, certificate, fingerprint, attribname, event):
super(PubkeyInvalid, self).__init__(self, text)
self.fingerprint = fingerprint
self.attrname = attribname
bodydata = {'message': text,
'event': event,
'fingerprint': fingerprint,
@@ -113,7 +106,7 @@ class PubkeyInvalid(ConfluentException):
class LoggedOut(ConfluentException):
apierrorcode = 401
_apierrorstr = '401 - Logged out'
apierrorstr = '401 - Logged out'
def get_error_body(self):
return '{"loggedout": 1}'
@@ -1,131 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# provide managing firmware update process and firmware repository if/when
# the time comes
import confluent.exceptions as exc
import confluent.messages as msg
import eventlet
updatesbytarget = {}
uploadsbytarget = {}
updatepool = eventlet.greenpool.GreenPool(256)
def execupdate(handler, filename, updateobj, type):
try:
if type == 'firmware':
completion = handler(filename, progress=updateobj.handle_progress,
bank=updateobj.bank)
else:
completion = handler(filename, progress=updateobj.handle_progress)
if completion is None:
completion = 'complete'
updateobj.handle_progress({'phase': completion, 'progress': 100.0})
except exc.PubkeyInvalid as pi:
errstr = 'Certificate mismatch detected, does not match value in ' \
'attribute {0}'.format(pi.attrname)
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': errstr})
except Exception as e:
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': str(e)})
class Updater(object):
def __init__(self, node, handler, filename, tenant=None, name=None,
bank=None, type='firmware'):
self.bank = bank
self.node = node
self.phase = 'initializing'
self.detail = ''
self.percent = 0.0
self.updateproc = updatepool.spawn(execupdate, handler, filename,
self, type)
if type == 'firmware':
myparty = updatesbytarget
elif type == 'mediaupload':
myparty = uploadsbytarget
if (node, tenant) not in myparty:
myparty[(node, tenant)] = {}
if name is None:
name = 1
while '{0}'.format(name) in myparty[(node, tenant)]:
name += 1
self.name = '{0}'.format(name)
myparty[(node, tenant)][self.name] = self
def handle_progress(self, progress):
self.phase = progress.get('phase', 'unknown')
self.percent = float(progress.get('progress', 100.0))
self.detail = progress.get('detail', '')
def cancel(self):
self.updateproc.kill()
@property
def progress(self):
return {'phase': self.phase, 'progress': self.percent,
'detail': self.detail}
def remove_updates(nodes, tenant, element, type='firmware'):
if len(element) < 5 and element[:2] != ['media', 'uploads']:
raise exc.InvalidArgumentException()
upid = element[-1]
if type == 'firmware':
myparty = updatesbytarget
else:
myparty = uploadsbytarget
for node in nodes:
try:
upd = myparty[(node, tenant)][upid]
except KeyError:
raise exc.NotFoundException('No active update matches request')
upd.cancel()
del myparty[(node, tenant)][upid]
yield msg.DeletedResource(
'nodes/{0}/inventory/firmware/updates/active/{1}'.format(
node, upid))
def list_updates(nodes, tenant, element, type='firmware'):
showmode = False
if type == 'mediaupload':
myparty = uploadsbytarget
verb = 'upload'
else:
myparty = updatesbytarget
verb = 'update'
if type == 'firmware':
specificlen = 4
else:
specificlen = 2
if len(element) > specificlen:
showmode = True
upid = element[-1]
for node in nodes:
if showmode:
try:
updater = myparty[(node, tenant)][upid]
except KeyError:
raise exc.NotFoundException(
'No matching {0} process found'.format(verb))
yield msg.KeyValueData(updater.progress, name=node)
else:
for updateid in myparty.get((node, tenant), {}):
yield msg.ChildCollection(updateid)
-119
View File
@@ -1,119 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#This handles port forwarding for web interfaces on management devices
#It will also hijack port 3900 and do best effort..
import eventlet
import eventlet.green.select as select
import eventlet.green.socket as socket
forwardersbyclient = {}
relaysbysession = {}
sessionsbyip = {}
ipsbysession = {}
sockhandler = {}
vidtargetbypeer = {}
vidforwarder = None
def handle_connection(incoming, outgoing):
while True:
r, _, _ = select.select((incoming, outgoing), (), (), 60)
for mysock in r:
data = mysock.recv(32768)
if not data:
return
if mysock == incoming:
outgoing.sendall(data)
elif mysock == outgoing:
incoming.sendall(data)
def forward_port(sock, target, clientip, sessionid):
while True:
conn, cli = sock.accept()
if cli[0] != clientip:
conn.close()
continue
try:
client = socket.create_connection((target, 443))
except Exception:
conn.close()
continue
if sessionid not in relaysbysession:
relaysbysession[sessionid] = {}
relaysbysession[sessionid][eventlet.spawn(
handle_connection, conn, client)] = conn
def forward_video():
sock = eventlet.listen(('::', 3900, 0, 0), family=socket.AF_INET6)
while True:
conn, cli = sock.accept()
if cli[0] not in vidtargetbypeer or not sessionsbyip.get(cli[0], None):
conn.close()
continue
try:
vidclient = socket.create_connection((vidtargetbypeer[cli[0]],
3900))
except Exception:
conn.close()
continue
eventlet.spawn_n(handle_connection, conn, vidclient)
def close_session(sessionid):
for addr in forwardersbyclient.get(sessionid, []):
killsock = forwardersbyclient[sessionid][addr]
sockhandler[killsock].kill()
del sockhandler[killsock]
killsock.close()
if sessionid in forwardersbyclient:
del forwardersbyclient[sessionid]
for clip in ipsbysession.get(sessionid, ()):
sessionsbyip[clip].discard(sessionid)
if sessionid in ipsbysession:
del ipsbysession[sessionid]
for relay in list(relaysbysession.get(sessionid, ())):
conn = relaysbysession[sessionid][relay]
relay.kill()
conn.close()
if sessionid in relaysbysession:
del relaysbysession[sessionid]
def get_port(addr, clientip, sessionid):
global vidforwarder
if socket.getaddrinfo(clientip, 0)[0][0] == socket.AF_INET:
clientip = '::ffff:' + clientip
if sessionid not in ipsbysession:
ipsbysession[sessionid] = set([])
if clientip not in sessionsbyip:
sessionsbyip[clientip] = set([])
sessionsbyip[clientip].add(sessionid)
ipsbysession[sessionid].add(clientip)
if sessionid not in forwardersbyclient:
forwardersbyclient[sessionid] = {}
if addr not in forwardersbyclient[sessionid]:
newsock = eventlet.listen(('::', 0, 0, 0),
family=socket.AF_INET6)
forwardersbyclient[sessionid][addr] = newsock
sockhandler[newsock] = eventlet.spawn(forward_port, newsock, addr,
clientip, sessionid)
if not vidforwarder:
vidforwarder = eventlet.spawn(forward_video)
vidtargetbypeer[clientip] = addr
return forwardersbyclient[sessionid][addr].getsockname()[1]
+2 -29
View File
@@ -21,7 +21,6 @@ import Cookie
import confluent.auth as auth
import confluent.config.attributes as attribs
import confluent.consoleserver as consoleserver
import confluent.forwarder as forwarder
import confluent.exceptions as exc
import confluent.log as log
import confluent.messages
@@ -152,7 +151,6 @@ def _sessioncleaner():
if httpsessions[session]['expiry'] < currtime:
targsessions.append(session)
for session in targsessions:
forwarder.close_session(session)
del httpsessions[session]
targsessions = []
for session in consolesessions:
@@ -217,17 +215,11 @@ def _should_skip_authlog(env):
return True
return False
def _csrf_exempt(path):
# first a get of info to get CSRF key, also '/forward/web' to enable
# the popup ability to just forward
return path == '/sessions/current/info' or path.endswith('/forward/web')
def _csrf_valid(env, session):
# This could be simplified into a statement, but this is more readable
# to have it broken out
if env['REQUEST_METHOD'] == 'GET' and _csrf_exempt(env['PATH_INFO']):
if (env['REQUEST_METHOD'] == 'GET' and
env['PATH_INFO'] == '/sessions/current/info'):
# Provide a web client a safe hook to request the CSRF token
# This means that we consider GET of /sessions/current/info to be
# a safe thing to inflict via CSRF, since CORS should prevent
@@ -284,7 +276,6 @@ def _authorize_request(env, operation):
targets.append(mythread)
for mythread in targets:
eventlet.greenthread.kill(mythread)
forwarder.close_session(sessionid)
del httpsessions[sessionid]
return ('logout',)
httpsessions[sessionid]['expiry'] = time.time() + 90
@@ -455,24 +446,6 @@ def resourcehandler_backend(env, start_response):
start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr),
headers)
yield e.get_error_body()
elif (env['PATH_INFO'].endswith('/forward/web') and
env['PATH_INFO'].startswith('/nodes/')):
prefix, _, _ = env['PATH_INFO'].partition('/forward/web')
_, _, nodename = prefix.rpartition('/')
hm = cfgmgr.get_node_attributes(nodename, 'hardwaremanagement.manager')
targip = hm.get(nodename, {}).get(
'hardwaremanagement.manager', {}).get('value', None)
if not targip:
start_response('404 Not Found', headers)
yield 'No hardwaremanagemnet.manager defined for node'
return
funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'],
authorized['sessionid'])
host = env['HTTP_X_FORWARDED_HOST']
url = 'https://{0}:{1}/'.format(host, funport)
start_response('302', [('Location', url)])
yield 'Our princess is in another castle!'
return
elif (operation == 'create' and ('/console/session' in env['PATH_INFO'] or
'/shell/sessions/' in env['PATH_INFO'])):
#hard bake JSON into this path, do not support other incarnations
+9 -20
View File
@@ -379,7 +379,7 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
def _sizeRoll(self):
self.close()
for i in range(self.backupCount - 1, 0, -1):
for i in range(self.sizeRollingCount, 0, -1):
sbfn = "%s.%d" % (self.binpath, i)
dbfn = "%s.%d" % (self.binpath, i + 1)
stfn = "%s.%d" % (self.textpath, i)
@@ -392,6 +392,8 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
if os.path.exists(dtfn):
os.remove(dtfn)
os.rename(stfn, dtfn)
# size rolling happens, add statistics count
self.sizeRollingCount += 1
dbfn = self.binpath + ".1"
dtfn = self.textpath + ".1"
if os.path.exists(dbfn):
@@ -402,18 +404,8 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
os.rename(self.binpath, dbfn)
if os.path.exists(self.textpath):
os.rename(self.textpath, dtfn)
self._deleteFilesForSizeRolling()
return dbfn, dtfn
def _deleteFilesForSizeRolling(self):
for i in range(self.sizeRollingCount, self.backupCount -1, -1):
dbfn = "%s.%d" % (self.binpath, i)
dtfn = "%s.%d" % (self.textpath, i)
if os.path.exists(dbfn):
os.remove(dbfn)
if os.path.exists(dtfn):
os.remove(dtfn)
def _timeRoll(self):
self.close()
# get the time that this sequence started at and make it a TimeTuple
@@ -432,7 +424,8 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
addend = -3600
timeTuple = time.localtime(t + addend)
for i in range(self.backupCount - 1, 0, -1):
# if size rolling files exist
for i in range(self.sizeRollingCount, 0, -1):
sbfn = "%s.%d" % ( self.binpath, i)
dbfn = "%s.%s.%d" % (
self.binpath, time.strftime(self.suffix, timeTuple),i)
@@ -448,17 +441,19 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
os.remove(dtfn)
os.rename(stfn, dtfn)
# As time rolling happens, reset statistics count
self.sizeRollingCount = 0
dbfn = self.binpath + "." + time.strftime(self.suffix, timeTuple)
odbfn = dbfn
dtfn = self.textpath + "." + time.strftime(self.suffix, timeTuple)
odtfn = dtfn
append=1
while os.path.exists(dbfn):
dbfn = odbfn + '.{0}'.format(append)
dbfn = odbfn + '.{}'.format(append)
append += 1
append=1
while os.path.exists(dtfn):
dtfn = odtfn + '.{0}'.format(append)
dtfn = odtfn + '.{}'.format(append)
append += 1
if os.path.exists(self.binpath):
os.rename(self.binpath, dbfn)
@@ -545,12 +540,6 @@ class Logger(object):
tstamp = entry[1]
data = entry[2]
evtdata = entry[3]
if len(data) > 65535:
# our max log entry is 65k, take only the first 65k and put
# rest back on as a continuation
entry[2] = data[65535:]
self.logentries.appendleft(entry)
data = data[:65535]
textdate = ''
if self.isconsole and ltype != 2:
textdate = time.strftime(
+5 -11
View File
@@ -163,7 +163,7 @@ def dumptrace(signalname, frame):
continue
if not o:
continue
ht.write('Thread trace: ({0})\n'.format(id(o)))
ht.write('Thread trace:\n')
ht.write(''.join(traceback.format_stack(o.gr_frame)))
ht.close()
@@ -204,10 +204,7 @@ def setlimits():
def run():
setlimits()
try:
signal.signal(signal.SIGUSR1, dumptrace)
except AttributeError:
pass # silly windows
signal.signal(signal.SIGUSR1, dumptrace)
if havefcntl:
_checkpidfile()
conf.init_config()
@@ -234,12 +231,9 @@ def run():
os.remove('/var/run/confluent/dbg.sock')
except OSError:
pass # We are not expecting the file to exist
try:
dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
family=socket.AF_UNIX)
eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
except AttributeError:
pass # Windows...
dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
family=socket.AF_UNIX)
eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
os.umask(oumask)
http_bind_host, http_bind_port = _get_connector_config('http')
sock_bind_host, sock_bind_port = _get_connector_config('socket')
+36 -129
View File
@@ -19,7 +19,6 @@
# Things are defined here to 'encourage' developers to coordinate information
# format. This is also how different data formats are supported
import confluent.exceptions as exc
import confluent.config.configmanager as cfm
from copy import deepcopy
from datetime import datetime
import json
@@ -35,7 +34,7 @@ valid_health_values = set([
def _htmlify_structure(indict):
ret = "<ul>"
if isinstance(indict, dict):
for key in sorted(indict):
for key in indict.iterkeys():
ret += "<li>{0}: ".format(key)
if type(indict[key]) in (str, unicode, float, int):
ret += str(indict[key])
@@ -77,7 +76,7 @@ class ConfluentMessage(object):
datasource = self.kvpairs
else:
datasource = {'databynode': self.kvpairs}
jsonsnippet = json.dumps(datasource, sort_keys=True, separators=(',', ':'))[1:-1]
jsonsnippet = json.dumps(datasource, separators=(',', ':'))[1:-1]
return jsonsnippet
def raw(self):
@@ -139,10 +138,7 @@ class ConfluentMessage(object):
'<input type="checkbox" name="restexplorerhonorkey" '
'value="{1}">\r').format(valtype, key, self.desc)
return snippet
if (isinstance(val, bool) or isinstance(val, str) or
isinstance(val, unicode)):
value = str(val)
elif val is not None and 'value' in val:
if val is not None and 'value' in val:
value = val['value']
if 'inheritedfrom' in val:
notes.append('Inherited from %s' % val['inheritedfrom'])
@@ -166,7 +162,7 @@ class ConfluentMessage(object):
notes.append('Inherited from %s' % val['inheritedfrom'])
value = '********'
if self.readonly:
snippet += "{0}: {1}<br>".format(key, value)
snippet += "{0}: {1}".format(key, value)
else:
snippet += (key + ":" +
'<input type="{0}" name="{1}" value="{2}" '
@@ -247,25 +243,13 @@ class DeletedResource(ConfluentMessage):
def __init__(self, resource):
self.kvpairs = {'deleted': resource}
def strip_node(self, node):
pass
class CreatedResource(ConfluentMessage):
notnode = True
readonly = True
def __init__(self, resource):
self.kvpairs = {'created': resource}
def strip_node(self, node):
pass
class AssignedResource(ConfluentMessage):
notnode = True
readonly = True
def __init__(self, resource):
self.kvpairs = {'assigned': resource}
@@ -367,13 +351,9 @@ class ChildCollection(LinkRelation):
# InputMCI
# InputDomainName
# InputNTPServer
def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
configmanager=None):
def get_input_message(path, operation, inputdata, nodes=None, multinode=False):
if path[0] == 'power' and path[1] == 'state' and operation != 'retrieve':
return InputPowerMessage(path, nodes, inputdata)
elif (path in (['power', 'reseat'], ['_enclosure', 'reseat_bay']) and
operation != 'retrieve'):
return InputReseatMessage(path, nodes, inputdata)
elif path == ['attributes', 'expression']:
return InputExpression(path, inputdata, nodes)
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
@@ -399,8 +379,7 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
return InputMCI(path, nodes, inputdata)
elif (path[:4] == ['configuration', 'management_controller',
'net_interfaces', 'management'] and operation != 'retrieve'):
return InputNetworkConfiguration(path, nodes, inputdata,
configmanager)
return InputNetworkConfiguration(path, nodes, inputdata)
elif (path[:3] == ['configuration', 'management_controller', 'domain_name']
and operation != 'retrieve'):
return InputDomainName(path, nodes, inputdata)
@@ -410,41 +389,9 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
elif (path[:4] == ['configuration', 'management_controller', 'ntp',
'servers'] and operation != 'retrieve' and len(path) == 5):
return InputNTPServer(path, nodes, inputdata)
elif (path[:3] == ['configuration', 'system', 'all'] and
operation != 'retrieve'):
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
elif 'inventory/firmware/updates/active' in '/'.join(path) and inputdata:
return InputFirmwareUpdate(path, nodes, inputdata)
elif '/'.join(path).startswith('media/detach'):
return DetachMedia(path, nodes, inputdata)
elif '/'.join(path).startswith('media/') and inputdata:
return InputMedia(path, nodes, inputdata)
elif inputdata:
raise exc.InvalidArgumentException(
'No known input handler for request')
raise exc.InvalidArgumentException()
class InputFirmwareUpdate(ConfluentMessage):
def __init__(self, path, nodes, inputdata):
self.filename = inputdata.get('filename', inputdata.get('url', None))
self.bank = inputdata.get('bank', None)
self.nodes = nodes
class InputMedia(InputFirmwareUpdate):
# Use InputFirmwareUpdate
pass
class DetachMedia(ConfluentMessage):
def __init__(self, path, nodes, inputdata):
if 'detachall' not in inputdata:
raise exc.InvalidArgumentException('Currently only supporting'
'{"detachall": 1}')
class Media(ConfluentMessage):
def __init__(self, node, media):
self.kvpairs = {node: {'name': media.name, 'url': media.url}}
class InputAlertData(ConfluentMessage):
@@ -473,13 +420,23 @@ class InputExpression(ConfluentMessage):
# so that it can make it intact to the pertinent configmanager function
def __init__(self, path, inputdata, nodes=None):
self.nodeattribs = {}
nestedmode = False
if not inputdata:
raise exc.InvalidArgumentException('no request data provided')
if nodes is None:
self.attribs = inputdata
return
for node in nodes:
self.nodeattribs[node] = inputdata
if node in inputdata:
nestedmode = True
self.nodeattribs[node] = inputdata[node]
if nestedmode:
for key in inputdata:
if key not in nodes:
raise exc.InvalidArgumentException
else:
for node in nodes:
self.nodeattribs[node] = inputdata
def get_attributes(self, node):
if node not in self.nodeattribs:
@@ -487,27 +444,6 @@ class InputExpression(ConfluentMessage):
nodeattr = deepcopy(self.nodeattribs[node])
return nodeattr
class InputConfigChangeSet(InputExpression):
# For now, this is identical to InputExpression, later it may
# internalize formula expansion, but not now..
def __init__(self, path, inputdata, nodes=None, configmanager=None):
self.cfm = configmanager
super(InputConfigChangeSet, self).__init__(path, inputdata, nodes)
def get_attributes(self, node):
attrs = super(InputConfigChangeSet, self).get_attributes(node)
endattrs = {}
for attr in attrs:
origval = attrs[attr]
if isinstance(origval, str) or isinstance(origval, unicode):
origval = {'expression': origval}
if 'expression' not in origval:
endattrs[attr] = attrs[attr]
else:
endattrs[attr] = list(self.cfm.expand_attrib_expression(
[node], attrs[attr]))[0][1]
return endattrs
class InputAttributes(ConfluentMessage):
# This is particularly designed for attributes, where a simple string
@@ -515,13 +451,14 @@ class InputAttributes(ConfluentMessage):
# preserve the client provided expression for posterity, rather than
# immediate consumption.
# for things like node configuration or similar, a different class is
# appropriate since it needs to immediately expand an expression.
# appropriate since it nedes to immediately expand an expression.
# with that class, the 'InputExpression' and calling code in attributes.py
# might be deprecated in favor of the generic expression expander
# and a small function in attributes.py to reflect the expansion back
# to the client
def __init__(self, path, inputdata, nodes=None):
self.nodeattribs = {}
nestedmode = False
if not inputdata:
raise exc.InvalidArgumentException('no request data provided')
if nodes is None:
@@ -546,7 +483,16 @@ class InputAttributes(ConfluentMessage):
'expression': self.attribs[attrib]}
return
for node in nodes:
self.nodeattribs[node] = inputdata
if node in inputdata:
nestedmode = True
self.nodeattribs[node] = inputdata[node]
if nestedmode:
for key in inputdata:
if key not in nodes:
raise exc.InvalidArgumentException
else:
for node in nodes:
self.nodeattribs[node] = inputdata
def get_attributes(self, node):
if node not in self.nodeattribs:
@@ -583,6 +529,7 @@ class InputCredential(ConfluentMessage):
def __init__(self, path, inputdata, nodes=None):
self.credentials = {}
nestedmode = False
if not inputdata:
raise exc.InvalidArgumentException('no request data provided')
@@ -657,7 +604,7 @@ class ConfluentInputMessage(ConfluentMessage):
if self.keyname not in datum:
raise exc.InvalidArgumentException(
'missing {0} argument'.format(self.keyname))
elif not self.is_valid_key(datum[self.keyname]):
elif datum[self.keyname] not in self.valid_values:
raise exc.InvalidArgumentException(
datum[self.keyname] + ' is not one of ' +
','.join(self.valid_values))
@@ -667,15 +614,13 @@ class ConfluentInputMessage(ConfluentMessage):
if self.keyname not in datum:
raise exc.InvalidArgumentException(
'missing {0} argument'.format(self.keyname))
elif not self.is_valid_key(datum[self.keyname]):
elif datum[self.keyname] not in self.valid_values:
raise exc.InvalidArgumentException(datum[self.keyname] +
' is not one of ' +
','.join(self.valid_values))
for node in nodes:
self.inputbynode[node] = datum[self.keyname]
def is_valid_key(self, key):
return key in self.valid_values
class InputIdentifyMessage(ConfluentInputMessage):
valid_values = set([
@@ -699,16 +644,6 @@ class InputPowerMessage(ConfluentInputMessage):
def powerstate(self, node):
return self.inputbynode[node]
class InputReseatMessage(ConfluentInputMessage):
valid_values = set([
'reseat',
])
keyname = 'reseat'
def is_valid_key(self, key):
return key in self.valid_values or isinstance(key, int)
class InputBMCReset(ConfluentInputMessage):
valid_values = set([
@@ -740,7 +675,7 @@ class InputMCI(ConfluentInputMessage):
class InputNetworkConfiguration(ConfluentInputMessage):
def __init__(self, path, nodes, inputdata, configmanager=None):
def __init__(self, path, nodes, inputdata):
self.inputbynode = {}
self.stripped = False
if not inputdata:
@@ -764,27 +699,8 @@ class InputNetworkConfiguration(ConfluentInputMessage):
if nodes is None:
raise exc.InvalidArgumentException(
'This only supports per-node input')
nodeattrmap = {}
for attr in inputdata:
try:
if inputdata[attr] is not None:
inputdata[attr].format()
except (KeyError, IndexError):
nodeattrmap[attr] = {}
for expanded in configmanager.expand_attrib_expression(
nodes, inputdata[attr]):
node, value = expanded
nodeattrmap[attr][node] = value
if not nodeattrmap:
for node in nodes:
self.inputbynode[node] = inputdata
return
# an expression was encountered
for node in nodes:
self.inputbynode[node] = deepcopy(inputdata)
for attr in self.inputbynode[node]:
if attr in nodeattrmap:
self.inputbynode[node][attr] = nodeattrmap[attr][node]
self.inputbynode[node] = inputdata
def netconfig(self, node):
return self.inputbynode[node]
@@ -939,13 +855,6 @@ class IdentifyState(ConfluentChoiceMessage):
keyname = 'identify'
class ReseatResult(ConfluentChoiceMessage):
valid_values = set([
'success',
])
keyname = 'reseat'
class PowerState(ConfluentChoiceMessage):
valid_values = set([
'on',
@@ -1261,7 +1170,7 @@ class Attributes(ConfluentMessage):
self.desc = desc
nkv = {}
self.notnode = name is None
for key in kv:
for key in kv.iterkeys():
if type(kv[key]) in (str, unicode):
nkv[key] = {'value': kv[key]}
else:
@@ -1273,8 +1182,6 @@ class Attributes(ConfluentMessage):
name: nkv
}
class ConfigSet(Attributes):
pass
class ListAttributes(ConfluentMessage):
def __init__(self, name=None, kv=None, desc=''):
+1 -1
View File
@@ -83,7 +83,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None):
cfgdata['prefix'] = prefixlen
for setting in nodenetattribs:
gw = nodenetattribs[setting].get('value', None)
if gw is None or not gw:
if gw is None:
continue
if ip_on_same_subnet(ip, gw, prefixlen):
cfgdata['ipv4_gateway'] = gw
+14 -229
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016, 2017 Lenovo
# Copyright 2016 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -30,24 +30,13 @@
# Provides support for viewing and processing lldp data for switches
if __name__ == '__main__':
import sys
import confluent.config.configmanager as cfm
import base64
import confluent.exceptions as exc
import confluent.log as log
import confluent.messages as msg
import confluent.snmputil as snmp
import confluent.networking.netutil as netutil
import confluent.util as util
import eventlet
from eventlet.greenpool import GreenPool
import eventlet.semaphore
import re
# The interesting OIDs are:
# lldpLocChassisId - to cross reference (1.0.8802.1.1.2.1.3.2.0)
# lldpLocPortId - for cross referencing.. (1.0.8802.1.1.2.1.3.7.1.3)
# 1.0.8802.1.1.2.1.3.7.1.4 - Lookup of LLDP index id to description
# Yet another fun fact, the LLDP port index frequent
# does *not* map to ifName, like a sane
@@ -81,11 +70,6 @@ import re
# # 1.0.8802.1.1.2.1.4.1.1.10 - SysDesc - good stuff
_neighdata = {}
_neighbypeerid = {}
_updatelocks = {}
_chassisidbyswitch = {}
def lenovoname(idx, desc):
if desc.isdigit():
return 'Ethernet' + str(idx)
@@ -95,166 +79,48 @@ nameoverrides = [
(re.compile('20301\..*'), lenovoname),
]
# Lenovo chassis id rule is match only first 5 bytes for a match.....
def _api_sanitize_string(source):
source = source.strip()
return source.replace(':', '-').replace('/', '-')
def close_enough(fuzz, literal):
if fuzz == literal:
return True
fuzz = '^' + fuzz.replace('-', '[/: -]') + '$'
matcher = re.compile(fuzz)
return bool(matcher.match(literal))
def _lldpdesc_to_ifname(switchid, idx, desc):
for tform in nameoverrides:
if tform[0].match(switchid):
desc = tform[1](idx, desc)
return desc.strip().strip('\x00')
return desc
def _dump_neighbordatum(info):
return [msg.KeyValueData(info)]
def b64tohex(b64str):
bd = base64.b64decode(b64str)
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
def get_fingerprint(switch, port, configmanager, portmatch):
update_switch_data(switch, configmanager)
for neigh in _neighbypeerid:
info = _neighbypeerid[neigh]
if neigh == '!!vintage' or info.get('switch', None) != switch:
continue
if 'peersha256fingerprint' not in info:
continue
if info.get('switch', None) != switch:
continue
if portmatch(info.get('port'), port):
return ('sha256$' + b64tohex(info['peersha256fingerprint']),
info.get('verified', False))
return None, False
def _extract_extended_desc(info, source, integritychecked):
source = str(source)
info['verified'] = bool(integritychecked)
if source.startswith('Lenovo SMM;'):
info['peerdescription'] = 'Lenovo SMM'
if ';S2=' in source:
info['peersha256fingerprint'] = source.replace('Lenovo SMM;S2=',
'')
else:
info['peerdescription'] = source
def sanitize(val):
val = str(val)
for x in val.strip('\x00'):
if ord(x) < 32 or ord(x) > 128:
val = ':'.join(['{0:02x}'.format(ord(x)) for x in str(val)])
break
return val
def _init_lldp(data, iname, idx, idxtoportid, switch):
if iname not in data:
data[iname] = {'port': iname, 'portid': str(idxtoportid[idx]),
'chassisid': str(_chassisidbyswitch[switch])}
def _extract_neighbor_data_b(args):
"""Build LLDP data about elements connected to switch
args are carried as a tuple, because of eventlet convenience
"""
switch, password, user, force = args
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
now = util.monotonic_time()
if vintage > (now - 60) and not force:
return
switch, password, user = args
conn = snmp.Session(switch, password, user)
sid = None
lldpdata = {'!!vintage': now}
lldpdata = {}
for sysid in conn.walk('1.3.6.1.2.1.1.2'):
sid = str(sysid[1][6:])
idxtoifname = {}
idxtoportid = {}
_chassisidbyswitch[switch] = list(conn.walk('1.0.8802.1.1.2.1.3.2'))[0][1]
for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.3'):
idx = oidindex[0][-1]
idxtoportid[idx] = sanitize(oidindex[1])
for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.4'):
idx = oidindex[0][-1]
idxtoifname[idx] = _lldpdesc_to_ifname(sid, idx, str(oidindex[1]))
for remotedesc in conn.walk('1.0.8802.1.1.2.1.4.1.1.10'):
iname = idxtoifname[remotedesc[0][-2]]
_init_lldp(lldpdata, iname, remotedesc[0][-2], idxtoportid, switch)
_extract_extended_desc(lldpdata[iname], remotedesc[1], user)
lldpdata[iname] = {'description': str(remotedesc[1])}
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.9'):
iname = idxtoifname[remotename[0][-2]]
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
lldpdata[iname]['peername'] = str(remotename[1])
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.7'):
iname = idxtoifname[remotename[0][-2]]
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
lldpdata[iname]['peerportid'] = sanitize(remotename[1])
if iname not in lldpdata:
lldpdata[iname] = {}
lldpdata[iname]['name'] = str(remotename[1])
for remoteid in conn.walk('1.0.8802.1.1.2.1.4.1.1.5'):
iname = idxtoifname[remoteid[0][-2]]
_init_lldp(lldpdata, iname, remoteid[0][-2], idxtoportid, switch)
lldpdata[iname]['peerchassisid'] = sanitize(remoteid[1])
for entry in lldpdata:
if entry == '!!vintage':
continue
entry = lldpdata[entry]
entry['switch'] = switch
peerid = '{0}.{1}'.format(
entry.get('peerchassisid', '').replace(':', '-').replace('/', '-'),
entry.get('peerportid', '').replace(':', '-').replace('/', '-'))
entry['peerid'] = peerid
_neighbypeerid[peerid] = entry
_neighdata[switch] = lldpdata
def update_switch_data(switch, configmanager, force=False):
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
_extract_neighbor_data(switchcreds + (force,))
return _neighdata.get(switch, {})
def update_neighbors(configmanager, force=False):
return _update_neighbors_backend(configmanager, force)
def _update_neighbors_backend(configmanager, force):
global _neighdata
global _neighbypeerid
vintage = _neighdata.get('!!vintage', 0)
now = util.monotonic_time()
if vintage > (now - 60) and not force:
return
_neighdata = {'!!vintage': now}
_neighbypeerid = {'!!vintage': now}
switches = netutil.list_switches(configmanager)
switchcreds = netutil.get_switchcreds(configmanager, switches)
switchcreds = [ x + (force,) for x in switchcreds]
pool = GreenPool(64)
for ans in pool.imap(_extract_neighbor_data, switchcreds):
yield ans
if iname not in lldpdata:
lldpdata[iname] = {}
lldpdata[iname]['chassisid'] = str(remoteid[1])
print(repr(lldpdata))
def _extract_neighbor_data(args):
# single switch neighbor data update
switch = args[0]
if switch not in _updatelocks:
_updatelocks[switch] = eventlet.semaphore.Semaphore()
if _updatelocks[switch].locked():
while _updatelocks[switch].locked():
eventlet.sleep(1)
return
try:
with _updatelocks[switch]:
_extract_neighbor_data_b(args)
_extract_neighbor_data_b(args)
except Exception:
log.logtrace()
@@ -262,85 +128,4 @@ if __name__ == '__main__':
# a quick one-shot test, args are switch and snmpv1 string for now
# (should do three argument form for snmpv3 test
import sys
_extract_neighbor_data((sys.argv[1], sys.argv[2], None, True))
print(repr(_neighdata))
multi_selectors = set(['by-switch', 'by-peername', 'by-peerportid',
'by-peerchassisid', 'by-chassisid', 'by-port',
'by-portid'])
single_selectors = set(['by-peerid'])
def _parameterize_path(pathcomponents):
listrequested = False
childcoll = True
if len(pathcomponents) % 2 == 1:
listrequested = pathcomponents[-1]
pathcomponents = pathcomponents[:-1]
pathit = iter(pathcomponents)
keyparams = {}
validselectors = multi_selectors | single_selectors
for key, val in zip(pathit, pathit):
if key not in validselectors:
raise exc.NotFoundException('{0} is not valid here'.format(key))
keyparams[key] = val
validselectors.discard(key)
if key == 'by-switch':
validselectors.add('by-port')
if key in single_selectors:
childcoll = False
validselectors = set([])
return validselectors, keyparams, listrequested, childcoll
def list_info(parms, requestedparameter):
#{u'by-switch': u'r8e1', u'by-port': u'e'}
#by-peerport
suffix = '/' if requestedparameter in multi_selectors else ''
results = set([])
requestedparameter = requestedparameter.replace('by-', '')
for info in _neighbypeerid:
if info == '!!vintage':
continue
info = _neighbypeerid[info]
for mk in parms:
mk = mk.replace('by-', '')
if mk not in info:
continue
if (not close_enough(parms['by-' + mk], info[mk]) or
requestedparameter not in info):
break
else:
candidate = info[requestedparameter]
candidate = candidate.strip()
if candidate != '':
results.add(_api_sanitize_string(candidate))
return [msg.ChildCollection(x + suffix) for x in util.natural_sort(results)]
def _handle_neighbor_query(pathcomponents, configmanager):
choices, parms, listrequested, childcoll = _parameterize_path(
pathcomponents)
if not childcoll: # this means it's a single entry with by-peerid
# guaranteed
if (parms['by-peerid'] not in _neighbypeerid and
_neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60):
list(update_neighbors(configmanager))
if parms['by-peerid'] not in _neighbypeerid:
raise exc.NotFoundException('No matching peer known')
return _dump_neighbordatum(_neighbypeerid[parms['by-peerid']])
if not listrequested: # the query is for currently valid choices
return [msg.ChildCollection(x + '/') for x in sorted(list(choices))]
if listrequested not in multi_selectors | single_selectors:
raise exc.NotFoundException('{0} is not found'.format(listrequested))
if 'by-switch' in parms:
update_switch_data(parms['by-switch'], configmanager)
else:
list(update_neighbors(configmanager))
return list_info(parms, listrequested)
def _list_interfaces(switchname, configmanager):
switchcreds = get_switchcreds(configmanager, (switchname,))
switchcreds = switchcreds[0]
conn = snmp.Session(*switchcreds)
ifnames = netutil.get_portnamemap(conn)
return util.natural_sort(ifnames.values())
_extract_neighbor_data((sys.argv[1], sys.argv[2]))
+55 -68
View File
@@ -30,8 +30,6 @@
# this module will provide mac to switch and full 'ifName' label
# This functionality is restricted to the null tenant
from confluent.networking.lldp import _handle_neighbor_query, get_fingerprint
from confluent.networking.netutil import get_switchcreds, list_switches, get_portnamemap
if __name__ == '__main__':
import sys
@@ -50,7 +48,6 @@ _macmap = {}
_macsbyswitch = {}
_nodesbymac = {}
_switchportmap = {}
_neighdata = {}
vintage = None
@@ -182,7 +179,20 @@ def _map_switch_backend(args):
except ValueError:
# ifidx might be '', skip in such a case
continue
ifnamemap = get_portnamemap(conn)
ifnamemap = {}
havenames = False
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
ifidx, ifname = vb
if not ifname:
continue
havenames = True
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
if not havenames:
for vb in conn.walk( '1.3.6.1.2.1.2.2.1.2'):
ifidx, ifname = vb
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
maccounts = {}
bridgetoifvalid = False
for mac in mactobridge:
@@ -229,34 +239,29 @@ def _map_switch_backend(args):
_macsbyswitch[switch][ifname] = [mac]
nodename = _nodelookup(switch, ifname)
if nodename is not None:
if mac in _nodesbymac and _nodesbymac[mac][0] != nodename:
if mac in _nodesbymac and _nodesbymac[mac] != nodename:
# For example, listed on both a real edge port
# and by accident a trunk port
log.log({'error': '{0} and {1} described by ambiguous'
' switch topology values'.format(
nodename, _nodesbymac[mac][0])})
_nodesbymac[mac] = (None, None)
' switch topology values'.format(nodename,
_nodesbymac[mac]
)})
_nodesbymac[mac] = None
else:
_nodesbymac[mac] = (nodename, maccounts[ifname])
_nodesbymac[mac] = nodename
switchbackoff = 30
def find_nodeinfo_by_mac(mac, configmanager):
def find_node_by_mac(mac, configmanager):
now = util.monotonic_time()
if vintage and (now - vintage) < 90 and mac in _nodesbymac:
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
return _nodesbymac[mac]
# do not actually sweep switches more than once every 30 seconds
# however, if there is an update in progress, wait on it
for _ in update_macmap(configmanager,
vintage and (now - vintage) < switchbackoff):
for _ in update_macmap(configmanager, vintage and (now - vintage) < 30):
if mac in _nodesbymac:
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
return _nodesbymac[mac]
# If update_mac bailed out, still check one last time
if mac in _nodesbymac:
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
return None, {'maccount': 0}
return _nodesbymac.get(mac, None)
mapupdating = eventlet.semaphore.Semaphore()
@@ -287,20 +292,16 @@ def update_macmap(configmanager, impatient=False):
eventlet.spawn_n(_finish_update, completions)
raise
def _finish_update(completions):
for _ in completions:
pass
def _full_updatemacmap(configmanager):
global vintage
global _macmap
global _nodesbymac
global _switchportmap
global _macsbyswitch
global switchbackoff
start = util.monotonic_time()
with mapupdating:
vintage = util.monotonic_time()
# Clear all existing entries
@@ -311,7 +312,6 @@ def _full_updatemacmap(configmanager):
if configmanager.tenant is not None:
raise exc.ForbiddenRequest(
'Network topology not available to tenants')
# here's a list of switches... need to add nodes that are switches
nodelocations = configmanager.get_node_attributes(
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
switches = set([])
@@ -340,18 +340,28 @@ def _full_updatemacmap(configmanager):
_switchportmap[curswitch][portname] = None
else:
_switchportmap[curswitch][portname] = node
switchauth = get_switchcreds(configmanager, switches)
pool = GreenPool(64)
switchcfg = configmanager.get_node_attributes(
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
'secret.hardwaremanagementpassword'), decrypt=True)
switchauth = []
for switch in switches:
if not switch:
continue
switchparms = switchcfg.get(switch, {})
user = None
password = switchparms.get(
'secret.snmpcommunity', {}).get('value', None)
if not password:
password = switchparms.get(
'secret.hardwaremanagementpassword', {}).get('value',
'public')
user = switchparms.get(
'secret.hardwaremanagementuser', {}).get('value', None)
switchauth.append((switch, password, user))
pool = GreenPool()
for ans in pool.imap(_map_switch, switchauth):
vintage = util.monotonic_time()
yield ans
endtime = util.monotonic_time()
duration = endtime - start
duration = duration * 15 # wait 15 times as long as it takes to walk
# avoid spending a large portion of the time hitting switches with snmp
# requests
if duration > switchbackoff:
switchbackoff = duration
def _dump_locations(info, macaddr, nodename=None):
@@ -368,7 +378,7 @@ def _dump_locations(info, macaddr, nodename=None):
def handle_api_request(configmanager, inputdata, operation, pathcomponents):
if operation == 'retrieve':
return handle_read_api_request(pathcomponents, configmanager)
return handle_read_api_request(pathcomponents)
if (operation in ('update', 'create') and
pathcomponents == ['networking', 'macs', 'rescan']):
if inputdata != {'rescan': 'start'}:
@@ -380,44 +390,20 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
operation, '/'.join(pathcomponents)))
def get_node_fingerprints(nodename, configmanager):
cfg = configmanager.get_node_attributes(nodename, ['net*.switch',
'net*.switchport'])
for attrkey in cfg[nodename]:
if attrkey.endswith('switch'):
switch = cfg[nodename][attrkey]['value']
port = cfg[nodename][attrkey + 'port']['value']
yield get_fingerprint(switch, port, configmanager,
_namesmatch)
def handle_read_api_request(pathcomponents, configmanager):
def handle_read_api_request(pathcomponents):
# TODO(jjohnson2): discovery core.py api handler design, apply it here
# to make this a less tangled mess as it gets extended
if len(pathcomponents) == 1:
return [msg.ChildCollection('macs/'),
msg.ChildCollection('neighbors/')]
elif pathcomponents[1] == 'neighbors':
if len(pathcomponents) == 3 and pathcomponents[-1] == 'by-switch':
return [msg.ChildCollection(x + '/')
for x in list_switches(configmanager)]
else:
return _handle_neighbor_query(pathcomponents[2:], configmanager)
return [msg.ChildCollection('macs/')]
elif len(pathcomponents) == 2:
if pathcomponents[-1] == 'macs':
return [msg.ChildCollection(x) for x in (# 'by-node/',
'by-mac/', 'by-switch/',
'rescan')]
elif pathcomponents[-1] == 'neighbors':
return [msg.ChildCollection('by-switch/')]
else:
raise exc.NotFoundException(
'Unknown networking resource {0}'.format(pathcomponents[-1]))
return [msg.ChildCollection(x) for x in (# 'by-node/',
'by-mac/', 'by-switch/',
'rescan')]
if False and pathcomponents[2] == 'by-node':
# TODO: should be list of node names, and then under that 'by-mac'
if len(pathcomponents) == 3:
return [msg.ChildCollection(x.replace(':', '-'))
for x in util.natural_sort(list(_nodesbymac))]
for x in sorted(list(_nodesbymac))]
elif len(pathcomponents) == 4:
macaddr = pathcomponents[-1].replace('-', ':')
return dump_macinfo(macaddr)
@@ -430,7 +416,8 @@ def handle_read_api_request(pathcomponents, configmanager):
elif pathcomponents[2] == 'by-switch':
if len(pathcomponents) == 3:
return [msg.ChildCollection(x + '/')
for x in list_switches(configmanager)]
for x in sorted(list(_macsbyswitch))]
if len(pathcomponents) == 4:
return [msg.ChildCollection('by-port/')]
if len(pathcomponents) == 5:
@@ -439,7 +426,7 @@ def handle_read_api_request(pathcomponents, configmanager):
raise exc.NotFoundException(
'No known macs for switch {0}'.format(switchname))
return [msg.ChildCollection(x.replace('/', '-') + '/')
for x in util.natural_sort(list(_macsbyswitch[switchname]))]
for x in sorted(list(_macsbyswitch[switchname]))]
if len(pathcomponents) == 6:
return [msg.ChildCollection('by-mac/')]
if len(pathcomponents) == 7:
@@ -468,7 +455,7 @@ def dump_macinfo(macaddr):
raise exc.NotFoundException(
'{0} not found in mac table of '
'any known switches'.format(macaddr))
return _dump_locations(info, macaddr, _nodesbymac.get(macaddr, (None,))[0])
return _dump_locations(info, macaddr, _nodesbymac.get(macaddr, None))
def rescan(cfg):
@@ -1,74 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.util as util
def get_switchcreds(configmanager, switches):
switchcfg = configmanager.get_node_attributes(
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
'secret.hardwaremanagementpassword'), decrypt=True)
switchauth = []
for switch in switches:
if not switch:
continue
switchparms = switchcfg.get(switch, {})
user = None
password = switchparms.get(
'secret.snmpcommunity', {}).get('value', None)
if not password:
password = switchparms.get(
'secret.hardwaremanagementpassword', {}).get('value',
'public')
user = switchparms.get(
'secret.hardwaremanagementuser', {}).get('value', None)
if not user:
user = None
switchauth.append((switch, password, user))
return switchauth
def list_switches(configmanager):
nodelocations = configmanager.get_node_attributes(
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
switches = set([])
for node in nodelocations:
cfg = nodelocations[node]
for attr in cfg:
if not attr.endswith('.switch') or 'value' not in cfg[attr]:
continue
curswitch = cfg[attr].get('value', None)
if not curswitch:
continue
switches.add(curswitch)
return util.natural_sort(switches)
def get_portnamemap(conn):
ifnamemap = {}
havenames = False
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
ifidx, ifname = vb
if not ifname:
continue
havenames = True
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
if not havenames:
for vb in conn.walk('1.3.6.1.2.1.2.2.1.2'):
ifidx, ifname = vb
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
return ifnamemap
-41
View File
@@ -38,9 +38,6 @@ _parser = pp.nestedExpr(content=_grammar)
_numextractor = pp.OneOrMore(pp.Word(pp.alphas + '-') | pp.Word(pp.nums))
numregex = re.compile('([0-9]+)')
lastnoderange = None
def humanify_nodename(nodename):
"""Analyzes nodename in a human way to enable natural sort
@@ -51,42 +48,6 @@ def humanify_nodename(nodename):
for text in re.split(numregex, nodename)]
class ReverseNodeRange(object):
"""Abbreviate a set of nodes to a shorter noderange representation
:param nodes: List of nodes as a list, tuple, etc.
:param config: Config manager
"""
def __init__(self, nodes, config):
self.cfm = config
self.nodes = set(nodes)
@property
def noderange(self):
subsetgroups = []
for group in self.cfm.get_groups(sizesort=True):
if lastnoderange:
for nr in lastnoderange:
if lastnoderange[nr] - self.nodes:
continue
if self.nodes - lastnoderange[nr]:
continue
return nr
nl = set(
self.cfm.get_nodegroup_attributes(group).get('nodes', []))
if len(nl) > len(self.nodes) or not nl:
continue
if not nl - self.nodes:
subsetgroups.append(group)
self.nodes -= nl
if not self.nodes:
break
return ','.join(sorted(subsetgroups) + sorted(self.nodes))
# TODO: pagination operators <pp.nums and >pp.nums for begin and end respective
class NodeRange(object):
"""Iterate over a noderange
@@ -96,7 +57,6 @@ class NodeRange(object):
"""
def __init__(self, noderange, config=None):
global lastnoderange
self.beginpage = None
self.endpage = None
self.cfm = config
@@ -110,7 +70,6 @@ class NodeRange(object):
self._noderange = set(self.cfm.list_nodes())
else:
self._noderange = self._evaluate(elements)
lastnoderange = {noderange: set(self._noderange)}
@property
def nodes(self):
@@ -1,5 +1,4 @@
# Copyright 2014 IBM Corporation
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,7 +15,6 @@
import confluent.exceptions as exc
import confluent.messages as msg
import confluent.config.attributes as allattributes
import confluent.util as util
def retrieve(nodes, element, configmanager, inputdata):
@@ -28,13 +26,7 @@ def retrieve(nodes, element, configmanager, inputdata):
def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
try:
grpcfg = configmanager.get_nodegroup_attributes(nodegroup)
except KeyError:
if not configmanager.is_nodegroup(nodegroup):
raise exc.NotFoundException(
'Invalid nodegroup: {0} not found'.format(nodegroup))
raise
grpcfg = configmanager.get_nodegroup_attributes(nodegroup)
if element == 'all':
theattrs = set(allattributes.node).union(set(grpcfg))
theattrs.add('nodes')
@@ -176,16 +168,8 @@ def _expand_expression(nodes, configmanager, inputdata):
expression = expression['expression']
if type(expression) is dict:
expression = expression['expression']
pernodeexpressions = {}
try:
for expanded in configmanager.expand_attrib_expression(nodes,
expression):
pernodeexpressions[expanded[0]] = expanded[1]
for node in util.natural_sort(pernodeexpressions):
yield msg.KeyValueData({'value': pernodeexpressions[node]}, node)
except ValueError as e:
raise exc.InvalidArgumentException(str(e))
for expanded in configmanager.expand_attrib_expression(nodes, expression):
yield msg.KeyValueData({'value': expanded[1]}, expanded[0])
def create(nodes, element, configmanager, inputdata):
@@ -1,38 +0,0 @@
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.core as core
import confluent.messages as msg
import pyghmi.exceptions as pygexc
def update(nodes, element, configmanager, inputdata):
emebs = configmanager.get_node_attributes(
nodes, (u'enclosure.manager', u'enclosure.bay'))
for node in nodes:
try:
em = emebs[node]['enclosure.manager']['value']
eb = emebs[node]['enclosure.bay']['value']
except KeyError:
yield msg.ConfluentNodeError(
node,
'Reseat is only supported on servers in an enclosure, and '
'with enclosure.manager and enclosure.bay defined')
continue
try:
for rsp in core.handle_path(
'/nodes/{0}/_enclosure/reseat_bay'.format(em),
'update', configmanager,
inputdata={'reseat': int(eb)}):
yield rsp
except pygexc.UnsupportedFunctionality as uf:
yield msg.ConfluentNodeError(node, str(uf))
@@ -1,5 +1,5 @@
# Copyright 2014 IBM Corporation
# Copyright 2015-2017 Lenovo
# Copyright 2015-2016 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
import atexit
import confluent.exceptions as exc
import confluent.firmwaremanager as firmwaremanager
import confluent.interface.console as conapi
import confluent.messages as msg
import confluent.util as util
@@ -30,30 +29,9 @@ import pyghmi.exceptions as pygexc
console = eventlet.import_patched('pyghmi.ipmi.console')
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
import socket
import ssl
# There is something not right with the RLocks used in pyghmi when
# eventlet comes into play. It seems like sometimes on acquire,
# it calls _get_ident and it isn't the id(greenlet) and so
# a thread deadlocks itself due to identity crisis?
# However, since we are not really threaded, the operations being protected
# are not actually dangerously multiplexed... so we can replace with
# a null context manager for now
class NullLock(object):
def donothing(self, *args, **kwargs):
return 1
__enter__ = donothing
__exit__ = donothing
acquire = donothing
release = donothing
console.session.select = eventlet.green.select
console.session.threading = eventlet.green.threading
console.session.WAITING_SESSIONS = NullLock()
console.session.KEEPALIVE_SESSIONS = NullLock()
console.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
@@ -70,8 +48,7 @@ _ipmiwaiters = []
sensor_categories = {
'temperature': frozenset(['Temperature']),
'energy': frozenset(['Energy']),
'power': frozenset(['Power', 'Current']),
'power': frozenset(['Current', 'Battery']),
'fans': frozenset(['Fan', 'Cooling Device']),
}
@@ -136,7 +113,6 @@ class IpmiCommandWrapper(ipmicommand.Command):
'secret.hardwaremanagementpassword', 'secret.ipmikg',
'hardwaremanagement.manager'), self._attribschanged)
super(self.__class__, self).__init__(**kwargs)
self.setup_confluent_keyhandler()
def setup_confluent_keyhandler(self):
self.register_key_handler(util.TLSCertVerifier(
@@ -269,7 +245,6 @@ class IpmiConsole(conapi.Console):
password=self.password,
kg=self.kg, force=True,
iohandler=self.handle_data)
self.solconnection.outputlock = NullLock()
while not self.solconnection.connected and not self.broken:
w = eventlet.event.Event()
_ipmiwaiters.append(w)
@@ -318,8 +293,6 @@ def perform_requests(operator, nodes, element, cfg, inputdata):
datum = resultdata.get(timeout=10)
while datum:
if datum != 'Done':
if isinstance(datum, Exception):
raise datum
yield datum
datum = resultdata.get_nowait()
except queue.Empty:
@@ -351,19 +324,9 @@ def perform_request(operator, node, element,
raise
except exc.TargetEndpointUnreachable as tu:
results.put(msg.ConfluentTargetTimeout(node, str(tu)))
except ssl.SSLEOFError:
results.put(msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
'the target BMC'))
except exc.PubkeyInvalid:
results.put(msg.ConfluentNodeError(
node,
'Mismatch detected between target certificate fingerprint '
'and pubkeys.tls_hardwaremanager attribute'))
except pygexc.InvalidParameterValue as e:
results.put(msg.ConfluentNodeError(node, str(e)))
except Exception as e:
results.put(e)
results.put(msg.ConfluentNodeError(
node, 'IPMI PluginException (see stderr log): ' + str(e)))
raise
finally:
results.put('Done')
@@ -387,7 +350,6 @@ class IpmiHandler(object):
connparams = get_conn_params(node, self.cfg)
self.ipmicmd = None
self.inputdata = inputdata
self.tenant = cfg.tenant
tenant = cfg.tenant
if ((node, tenant) not in persistent_ipmicmds or
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged):
@@ -401,7 +363,6 @@ class IpmiHandler(object):
userid=connparams['username'],
password=connparams['passphrase'], kg=connparams['kg'],
port=connparams['port'], onlogon=self.logged)
ipmisess = persistent_ipmicmds[(node, tenant)].ipmi_session
begin = util.monotonic_time()
while ((not (self.broken or self.loggedin)) and
@@ -427,6 +388,7 @@ class IpmiHandler(object):
else:
self.ipmicmd = ipmicmd
self.loggedin = True
self.ipmicmd.setup_confluent_keyhandler()
def handle_request(self):
if self.broken:
@@ -445,8 +407,6 @@ class IpmiHandler(object):
raise Exception(self.error)
if self.element == ['power', 'state']:
self.power()
elif self.element == ['_enclosure', 'reseat_bay']:
self.reseat_bay()
elif self.element == ['boot', 'nextdevice']:
self.bootdevice()
elif self.element == ['health', 'hardware']:
@@ -457,18 +417,8 @@ class IpmiHandler(object):
self.handle_sensors()
elif self.element[0] == 'configuration':
self.handle_configuration()
elif self.element[:3] == ['inventory', 'firmware', 'updates']:
self.handle_update()
elif self.element[0] == 'inventory':
self.handle_inventory()
elif self.element == ['media', 'attach']:
self.handle_attach_media()
elif self.element == ['media', 'detach']:
self.handle_detach_media()
elif self.element == ['media', 'uploads']:
self.handle_media_upload()
elif self.element == ['media', 'current']:
self.handle_list_media()
elif self.element == ['events', 'hardware', 'log']:
self.do_eventlog()
elif self.element == ['events', 'hardware', 'decode']:
@@ -478,35 +428,6 @@ class IpmiHandler(object):
else:
raise Exception('Not Implemented')
def handle_update(self):
u = firmwaremanager.Updater(self.node, self.ipmicmd.update_firmware,
self.inputdata.filename, self.tenant,
bank=self.inputdata.bank)
self.output.put(
msg.CreatedResource(
'nodes/{0}/inventory/firmware/updates/active/{1}'.format(
self.node, u.name)))
def handle_media_upload(self):
u = firmwaremanager.Updater(self.node, self.ipmicmd.upload_media,
self.inputdata.filename, self.tenant,
type='mediaupload')
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def handle_attach_media(self):
try:
self.ipmicmd.attach_remote_media(self.inputdata.filename)
except pygexc.UnsupportedFunctionality as uf:
self.output.put(msg.ConfluentNodeError(self.node, str(uf)))
def handle_detach_media(self):
self.ipmicmd.detach_remote_media()
def handle_list_media(self):
for media in self.ipmicmd.list_media():
self.output.put(msg.Media(self.node, media))
def handle_configuration(self):
if self.element[1:3] == ['management_controller', 'alerts']:
return self.handle_alerts()
@@ -522,8 +443,6 @@ class IpmiHandler(object):
return self.handle_domain_name()
elif self.element[1:3] == ['management_controller', 'ntp']:
return self.handle_ntp()
elif self.element[1:3] == ['system', 'all']:
return self.handle_sysconfig()
raise Exception('Not implemented')
def decode_alert(self):
@@ -746,27 +665,10 @@ class IpmiHandler(object):
def read_firmware(self, component):
items = []
errorneeded = False
try:
complist = () if component == 'all' else (component,)
for id, data in self.ipmicmd.get_firmware(complist):
if (component in ('core', 'all') or
component == simplify_name(id)):
items.append({id: data})
except ssl.SSLEOFError:
errorneeded = msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
'the target BMC while trying to read extended '
'information')
except exc.PubkeyInvalid:
errorneeded = msg.ConfluentNodeError(
self.node,
'Extended information unavailable, mismatch detected between '
'target certificate fingerprint and '
'pubkeys.tls_hardwaremanager attribute')
for id, data in self.ipmicmd.get_firmware():
if component == 'all' or component == simplify_name(id):
items.append({id: data})
self.output.put(msg.Firmware(items, self.node))
if errorneeded:
self.output.put(errorneeded)
def handle_inventory(self):
if self.element[1] == 'firmware':
@@ -794,47 +696,32 @@ class IpmiHandler(object):
self.output.put(msg.LEDStatus(led_categories, self.node))
def read_inventory(self, component):
errorneeded = False
try:
invitems = []
if component == 'all':
for invdata in self.ipmicmd.get_inventory():
if invdata[1] is None:
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata[1])
newinf = {'present': True, 'information': invdata[1]}
newinf['name'] = invdata[0]
invitems.append(newinf)
else:
self.make_inventory_map()
compname = self.invmap.get(component, None)
if compname is None:
self.output.put(msg.ConfluentTargetNotFound())
return
invdata = self.ipmicmd.get_inventory_of_component(compname)
if invdata is None:
invitems = []
if component == 'all':
for invdata in self.ipmicmd.get_inventory():
if invdata[1] is None:
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata)
newinf = {'present': True, 'information': invdata}
newinf['name'] = compname
sanitize_invdata(invdata[1])
newinf = {'present': True, 'information': invdata[1]}
newinf['name'] = invdata[0]
invitems.append(newinf)
except ssl.SSLEOFError:
errorneeded = msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
'the target BMC while trying to read extended '
'information')
except exc.PubkeyInvalid:
errorneeded = msg.ConfluentNodeError(
self.node,
'Extended information unavailable, mismatch detected between '
'target certificate fingerprint and '
'pubkeys.tls_hardwaremanager attribute')
else:
self.make_inventory_map()
compname = self.invmap.get(component, None)
if compname is None:
self.output.put(msg.ConfluentTargetNotFound())
return
invdata = self.ipmicmd.get_inventory_of_component(compname)
if invdata is None:
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata)
newinf = {'present': True, 'information': invdata}
newinf['name'] = compname
invitems.append(newinf)
newinvdata = {'inventory': invitems}
self.output.put(msg.KeyValueData(newinvdata, self.node))
if errorneeded:
self.output.put(errorneeded)
def handle_sensors(self):
if self.element[-1] == '':
@@ -889,14 +776,6 @@ class IpmiHandler(object):
else:
raise exc.InvalidArgumentException('health is read-only')
def reseat_bay(self):
bay = self.inputdata.inputbynode[self.node]
try:
self.ipmicmd.reseat_bay(bay)
self.output.put(msg.ReseatResult(self.node, 'success'))
except pygexc.UnsupportedFunctionality as uf:
self.output.put(uf)
def bootdevice(self):
if 'read' == self.op:
bootdev = self.ipmicmd.get_bootdev()
@@ -996,14 +875,6 @@ class IpmiHandler(object):
self.ipmicmd.set_domain_name(dn)
return
def handle_sysconfig(self):
if 'read' == self.op:
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration()))
elif 'update' == self.op:
self.ipmicmd.set_system_configuration(
self.inputdata.get_attributes(self.node))
def handle_ntp(self):
if self.element[3] == 'enabled':
if 'read' == self.op:
@@ -1092,22 +963,9 @@ def update(nodes, element, configmanager, inputdata):
def retrieve(nodes, element, configmanager, inputdata):
initthread()
if '/'.join(element).startswith('inventory/firmware/updates/active'):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element)
elif '/'.join(element).startswith('media/uploads'):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element, 'mediaupload')
else:
return perform_requests('read', nodes, element, configmanager, inputdata)
return perform_requests('read', nodes, element, configmanager, inputdata)
def delete(nodes, element, configmanager, inputdata):
initthread()
if '/'.join(element).startswith('inventory/firmware/updates/active'):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element)
elif '/'.join(element).startswith('media/uploads'):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element, type='mediaupload')
return perform_requests(
'delete', nodes, element, configmanager, inputdata)
+2 -3
View File
@@ -59,9 +59,8 @@ class Session(object):
# SNMP v2c
self.authdata = snmp.CommunityData(secret, mpModel=1)
else:
self.authdata = snmp.UsmUserData(
username, authKey=secret, privKey=secret,
authProtocol=snmp.usmHMACSHAAuthProtocol)
self.authdata = snmp.UsmUserData(username, authKey=secret,
privKey=secret)
self.eng = snmp.SnmpEngine()
def walk(self, oid):
+10 -50
View File
@@ -22,8 +22,6 @@ import confluent.log as log
import hashlib
import netifaces
import os
import re
import socket
import struct
@@ -33,22 +31,13 @@ def list_interface_indexes():
# For now be linux specific
try:
for iface in os.listdir('/sys/class/net/'):
if not os.path.exists('/sys/class/net/{0}/ifindex'.format(iface)):
continue
ifile = open('/sys/class/net/{0}/ifindex'.format(iface), 'r')
intidx = int(ifile.read())
ifile.close()
yield intidx
except (IOError, OSError):
# Probably situation is non-Linux, just do limited support for
# such platforms until other people come along
for iface in netifaces.interfaces():
addrinfo = netifaces.ifaddresses(iface).get(socket.AF_INET6, [])
for addr in addrinfo:
v6addr = addr.get('addr', '').partition('%')[2]
if v6addr:
yield(int(v6addr))
break
# such platforms until other people come alonge
return
@@ -100,11 +89,9 @@ def monotonic_time():
def get_fingerprint(certificate, algo='sha512'):
if algo == 'sha256':
return 'sha256$' + hashlib.sha256(certificate).hexdigest()
elif algo == 'sha512':
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
raise Exception('Unsupported fingerprint algorithm ' + algo)
if algo != 'sha512':
raise Exception("TODO: Non-sha512")
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
def cert_matches(fingerprint, certificate):
@@ -112,8 +99,8 @@ def cert_matches(fingerprint, certificate):
return False
algo, _, fp = fingerprint.partition('$')
newfp = None
if algo in ('sha512', 'sha256'):
newfp = get_fingerprint(certificate, algo)
if algo == 'sha512':
newfp = get_fingerprint(certificate)
return newfp and fingerprint == newfp
@@ -124,11 +111,11 @@ class TLSCertVerifier(object):
self.fieldname = fieldname
def verify_cert(self, certificate):
fingerprint = get_fingerprint(certificate)
storedprint = self.cfm.get_node_attributes(self.node, (self.fieldname,)
)
if (self.fieldname not in storedprint[self.node] or
storedprint[self.node][self.fieldname]['value'] == ''):
# no stored value, check policy for next action
if self.fieldname not in storedprint[self.node]: # no stored value, check
# policy for next action
newpolicy = self.cfm.get_node_attributes(self.node,
('pubkeys.addpolicy',))
if ('pubkeys.addpolicy' in newpolicy[self.node] and
@@ -136,46 +123,19 @@ class TLSCertVerifier(object):
newpolicy[self.node]['pubkeys.addpolicy']['value'] == 'manual'):
# manual policy means always raise unless a match is set
# manually
fingerprint = get_fingerprint(certificate, 'sha256')
raise cexc.PubkeyInvalid('New certificate detected',
certificate, fingerprint,
self.fieldname, 'newkey')
# since the policy is not manual, go ahead and add new key
# after logging to audit log
fingerprint = get_fingerprint(certificate, 'sha256')
auditlog = log.Logger('audit')
auditlog.log({'node': self.node, 'event': 'certautoadd',
'fingerprint': fingerprint})
self.cfm.set_node_attributes(
{self.node: {self.fieldname: fingerprint}})
return True
elif cert_matches(storedprint[self.node][self.fieldname]['value'],
certificate):
elif storedprint[self.node][self.fieldname]['value'] == fingerprint:
return True
fingerprint = get_fingerprint(certificate, 'sha256')
raise cexc.PubkeyInvalid(
'Mismatched certificate detected', certificate, fingerprint,
self.fieldname, 'mismatch')
numregex = re.compile('([0-9]+)')
def naturalize_string(key):
"""Analyzes string in a human way to enable natural sort
:param nodename: The node name to analyze
:returns: A structure that can be consumed by 'sorted'
"""
return [int(text) if text.isdigit() else text.lower()
for text in re.split(numregex, key)]
def natural_sort(iterable):
"""Return a sort using natural sort if possible
:param iterable:
:return:
"""
try:
return sorted(iterable, key=naturalize_string)
except TypeError:
# The natural sort attempt failed, fallback to ascii sort
return sorted(iterable)
+1 -6
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-crypto >= 2.6.1, confluent_client, pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor
Requires: python-pyghmi, python-eventlet, python-greenlet, python-crypto >= 2.6.1, confluent_client, pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1, python-pysnmp, python-pyte
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
Url: http://xcat.sf.net/
@@ -33,12 +33,7 @@ done
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
cat INSTALLED_FILES
%triggerin -- python-pyghmi
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
true
%post
sysctl -p /usr/lib/sysctl.d/confluent.conf >& /dev/null
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
true
+1 -20
View File
@@ -19,28 +19,12 @@
# Note that this script has a high chance of breaking confluent, so
# do not be surprised if confluent crashes as you exit...
import atexit
import os
import select
import socket
import readline
import sys
import threading
historypath = os.path.expanduser("~/.confluentdbghistory")
def save_history():
import readline
try:
readline.write_history_file(historypath)
except:
pass
if os.path.exists(historypath):
readline.set_history_length(1000)
readline.read_history_file(historypath)
atexit.register(save_history)
readline.parse_and_bind('tab: complete')
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
conn.connect('/var/run/confluent/dbg.sock')
@@ -61,10 +45,7 @@ class GetInput(threading.Thread):
inputthread = GetInput()
inputthread.start()
while True:
try:
r, _, _ = select.select((conn,), (), (), 0.1)
except select.error:
continue
r, _, _ = select.select((conn,), (), (), 0.1)
if conn in r:
sys.stdout.write(conn.recv(1))
if pendingoutput is not None:
@@ -27,7 +27,7 @@ with open(sys.argv[1]) as tracefile:
if currtrace not in threadtraces:
threadtraces[currtrace] = 0
threadtraces[currtrace] += 1
currtrace = ''
currtrace = line
elif currtrace is not None:
currtrace += line + '\n'
for trace in sorted(threadtraces, key=lambda x: threadtraces[x]):
-1
View File
@@ -20,7 +20,6 @@ setup(
'pyghmi>=0.6.5'],
scripts=['bin/confluent', 'bin/confluentdbutil'],
data_files=[('/etc/init.d', ['sysvinit/confluent']),
('/usr/lib/sysctl.d', ['sysctl/confluent.conf']),
('/usr/lib/systemd/system', ['systemd/confluent.service']),
('/opt/confluent/lib/python/confluent/plugins/console/', [])],
-2
View File
@@ -1,2 +0,0 @@
# Increase available receive buffers for discovery scans
net.core.rmem_max = 2097152
@@ -7,7 +7,6 @@ Type=forking
PIDFile=/var/run/confluent/pid
ExecStart=/opt/confluent/bin/confluent
ExecStop=/opt/confluent/bin/confetty shutdown /
Restart=on-failure
[Install]
WantedBy=multi-user.target