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

Compare commits

..

130 Commits

Author SHA1 Message Date
Jarrod Johnson cbee00d3bb Fix initial collective join
Initial collective join combined
with the orderly collective startup
hit a chicken and egg problem.

Disable initting on first enrollment
to let enrollment drive
that specific initialization to
restore behavior.
2021-07-19 10:26:05 -04:00
Jarrod Johnson 14bb841b2d Fix nodebmcreset partial success
The error handling is improved, to
catch both noderange global issues as well
as partial completions.
2021-06-08 09:13:56 -04:00
Jarrod Johnson 03a75ec67d Error if noderange includes too many )
By default, pyparsing consumes only as much of the
input as matches the grammar. Tell it to consume all
of the noderange and error if there's more string than
matches our grammar.
2021-06-04 14:21:45 -04:00
Jarrod Johnson 269300119a Skip newer agent behaviors with incompatible old ssh
The previous attempt to support older ssh failed to completely
enact old behavior when needed.
2021-06-04 13:41:47 -04:00
Jarrod Johnson a2edf46b8a Improve ctrl-c and other behaviors of osdeploy import
More reliably delete an import attempt to avoid odd behaviors.
2021-05-27 16:10:55 -04:00
Jarrod Johnson 74e011e964 Implement node name subtitution in nodeshell
If wanting to ssh to 'altenative interfaces', provide -s to facilitate that
behavior, with added flexibility
compared to previous incarnations.
2021-05-25 15:46:49 -04:00
Jarrod Johnson 66372bea0c Fix hang on ctrl-e,c,p,o
Leave the function to
consume termdata if data
is available.
2021-05-25 14:27:37 -04:00
Jarrod Johnson d250a3e850 Fix typo in confetty 2021-05-25 11:58:01 -04:00
Jarrod Johnson 49b69a2e19 Explicitly provide content-length 0 on 204
For some vintages of eventlet+apache, this is required
to avoid invalid responses from the server.
2021-05-25 10:19:11 -04:00
Jarrod Johnson 93d4847763 Conditionally apply agent to sshutil
Older OSes (RHEL7/SLES12) cannot
do ssh-keygen with an agent.

Degrade to classic confluent behavior when that happens.
2021-05-18 12:28:42 -04:00
Jarrod Johnson b283b50cc6 Improve error on nodeconfig parse errors
The current error is vague and confusing, clarify with
more data showing what went wrong.
2021-05-18 10:34:09 -04:00
Jarrod Johnson 1f320af8e5 Add error message for long profile names
If a profile name pushes the filename field of dhcp
beyond what it can support, log an event and
do not offer a corrupted
dhcp offer packet.
2021-05-18 09:44:27 -04:00
Jarrod Johnson 5fea265dc8 Change CSV to have unix style line endings
The excel dialog has nice minimal quoting, but DOS line endings.

Unix dialog has good line endings, but excessive quoting.

Create a hybrid dialect and use it for nodesensors output.
2021-05-13 10:39:03 -04:00
Jarrod Johnson b74d648e7f Fix refactored inline command run
This addresses the mistake in
refactoring the inline command
shortcut
2021-05-12 13:49:06 -04:00
Jarrod Johnson 380f21853c Merge branch 'master' into 3.2 2021-05-11 08:10:41 -04:00
Jarrod Johnson 9900d76b0c Merge remote-tracking branch 'lenovo/master' 2021-05-11 08:09:57 -04:00
Jarrod Johnson aa7701ea3c More usefully indicate unfound interfaces
Rather than no data, provide
not found error when requesting a currently unsupported name.
2021-05-11 08:02:19 -04:00
Jarrod Johnson 3044c662ad Merge pull request #55 from vmaneagit/patch-45
Update nodesupport.ronn
2021-05-11 07:59:24 -04:00
Jarrod Johnson 7e02d1d008 Merge pull request #57 from vmaneagit/patch-43
Update collective.ronn
2021-05-11 07:56:55 -04:00
Jarrod Johnson f321e06e6e Merge pull request #51 from vmaneagit/patch-49
Update nodersync.ronn
2021-05-11 07:50:47 -04:00
Jarrod Johnson 5c0af9ebc7 Merge pull request #50 from vmaneagit/patch-50
Update nodersync.ronn
2021-05-11 07:50:18 -04:00
Jarrod Johnson c146b50b5b Merge pull request #49 from vmaneagit/patch-51
Update nodereseat.ronn
2021-05-11 07:49:37 -04:00
Jarrod Johnson cae8b2d0f8 Merge pull request #48 from vmaneagit/patch-52
Update noderemove.ronn
2021-05-11 07:49:24 -04:00
Jarrod Johnson 2664adefa4 Merge pull request #47 from vmaneagit/patch-53
Update nodepower.ronn
2021-05-11 07:49:10 -04:00
Jarrod Johnson c5f03ee62d Merge pull request #46 from vmaneagit/patch-54
Update nodelicense.ronn
2021-05-11 07:49:00 -04:00
Jarrod Johnson a932e91b17 Merge pull request #45 from vmaneagit/patch-56
Update nodegroupattrib.ronn.tmpl
2021-05-11 07:48:45 -04:00
Jarrod Johnson 1c00aa3c4e Merge pull request #44 from vmaneagit/patch-57
Update confetty.ronn
2021-05-11 07:48:35 -04:00
Jarrod Johnson bd889f4c78 Merge pull request #43 from vmaneagit/patch-58
Update collate.ronn
2021-05-11 07:48:17 -04:00
Jarrod Johnson 409a0796a1 Merge pull request #42 from vmaneagit/patch-59
Update nodesupport.ronn
2021-05-11 07:48:04 -04:00
Jarrod Johnson 70ffef6b06 Merge pull request #41 from vmaneagit/patch-55
Update nodelicense.ronn
2021-05-11 07:47:53 -04:00
Jarrod Johnson 77a043faa6 Change apiclient to append if outputting to existing file 2021-05-05 17:45:39 -04:00
Jarrod Johnson d69eb6ef12 Change apiclient to append if outputting to existing file 2021-05-05 17:45:04 -04:00
Jarrod Johnson 984c3044fe Invalidate sealed token on new token
If a new token grant occurs, do not
retain stale token sealed, as it can be misleading.
2021-05-05 09:18:28 -04:00
Jarrod Johnson 8543129fb9 Invalidate sealed token on new token
If a new token grant occurs, do not
retain stale token sealed, as it can be misleading.
2021-05-05 09:17:42 -04:00
Jarrod Johnson 257edff3a7 Redo ssh certs on firstboot for SUSE
For whatever reason, SUSE loses the certificate during firstboot.
2021-05-03 16:47:24 -04:00
Jarrod Johnson 763feda63b Fix missed references to old variable names 2021-05-03 15:54:39 -04:00
Jarrod Johnson 508e5b4a46 Fix suse run_remote_parts
Behave more consistently
2021-05-03 15:36:27 -04:00
Jarrod Johnson 7676c4fd26 Restore EL8 treatment
The changes caused EL8 to lose some required special treatment.
2021-05-03 15:18:33 -04:00
Jarrod Johnson c3b954a743 Move suse15 to log like rhel 2021-05-03 14:50:04 -04:00
Jarrod Johnson a94a341582 Fix ansible path compatibility. 2021-05-03 14:31:28 -04:00
Jarrod Johnson 08dfd55ea0 Rename variables in the ubuntu initramfs portion 2021-05-03 13:28:28 -04:00
Jarrod Johnson cf6975ded3 Fix python2/3 handling in el8 pre.sh 2021-05-03 13:11:52 -04:00
Jarrod Johnson d17b1d060c Prepend confluent_ to vars and switch to explicitly requesting directory
The 'profile' variable notably induces dracut to be excruciatingly slow,
mitigate chance by putting confluent before apikey, mgr, and profile.

Further, it has been requested to have the scripts use same name on
server for directory moving forward. Implement this request while
allowing existing OS profiles to keep working.
2021-05-03 12:48:08 -04:00
Jarrod Johnson fcb4b33e8e Include stderr in the script execution logs 2021-05-03 08:57:59 -04:00
Jarrod Johnson 121e0727cf Add Rocky linux recognition 2021-05-03 08:36:57 -04:00
Jarrod Johnson b5b9631b3f Correct typo in pre script 2021-04-30 16:27:16 -04:00
Jarrod Johnson c09f96b8c9 Remove pivot_root from genesis
It is not used (if we did use something, it would be switch_root).
2021-04-30 16:20:58 -04:00
Jarrod Johnson 196ed31bee Retry node api token retrieval, with message 2021-04-30 15:17:02 -04:00
Jarrod Johnson bb05f72583 Add source_remote_parts
This function will enable a script to invoke multiple
pieces in same variable/function context
2021-04-30 14:33:02 -04:00
Jarrod Johnson 68b4f83627 Have firstboot script be logged better
Also show on screen firstboot script execution.
2021-04-30 14:08:42 -04:00
Jarrod Johnson 672bc4f754 Amend postscript logging strategy
This adjusts to avoid hangs in
anaconda
2021-04-30 13:38:31 -04:00
Jarrod Johnson 4df3fda8ea Change logging/display for RH oses
The pre and post phases are
desirable to see both in logs and consoles.

Unfortunately, anaconda cheaps out by using communicate(), which means
the logs are unwritten until the end.

Change by using bash's redirectiors
to create a dynamically updated logfile
and tail -f that.
2021-04-30 09:39:00 -04:00
Jarrod Johnson f32619aa3e Allow firstboot to reboot in RH postscripts
Sometimes firstboot scripts want to reboot.

In such a case, start firstboot once, and if reboot prevents completion,
then next boot will updatestatus, but not run firstboot scripts and
deregister itself.
2021-04-29 09:20:24 -04:00
Jarrod Johnson dd7b9f0244 Bump genesis version 2021-04-28 16:44:24 -04:00
Jarrod Johnson def534e73f Fixes for boot by DHCP and by MAC 2021-04-28 16:43:11 -04:00
Jarrod Johnson 2cb641e734 Fix PXE based on mac
We normally use UUID, on a broken platform with bad UUID,
user may need to use hwaddr.  This was supposed to work, but
didn't. Fix it to work correctly.
2021-04-28 15:36:12 -04:00
Jarrod Johnson ca78b664c9 Get rid of minor warning about RSAAuthentication 2021-04-28 14:06:57 -04:00
Jarrod Johnson 0e55d390e3 Fix up post output to console 2021-04-28 13:46:26 -04:00
Jarrod Johnson 002146b2f7 Try to output pre/post content to console during install 2021-04-28 13:27:34 -04:00
Jarrod Johnson 917a51a406 Error if bind to privileged port fails 2021-04-28 08:37:23 -04:00
Jarrod Johnson d3fa08d78b Amend syncfiles to handle directory targets better
Rather than using symlinks for directories, recursively recreate tree
and symlink only the non-directory
entries.

This improves mixing and matching
files and directories to target a directory.
2021-04-27 12:48:27 -04:00
Jarrod Johnson 38eb88249e Permit wildcard in syncfiles
This was a supported and used feature in xCAT
syncfiles, carry it into confluent syncfiles.
2021-04-27 09:12:11 -04:00
Jarrod Johnson c333c3eb9c Fix missing data on XCC discovery
The XCC scan now calls the
imm scan to fill out the lost data.
2021-04-26 08:57:52 -04:00
Jarrod Johnson f32a9a2f08 Rework inline command handling
Previously, if hotkey entry
had text data come in, it
would corrupt the state of
the client.

Minimize the corruption and request the server to pause.
2021-04-23 14:22:24 -04:00
Jarrod Johnson f584b9bc11 Protect against binary sealed data
It was detected that binary sealed data
could happen. Ensure that it is str
before passing to configuration.
2021-04-23 14:17:54 -04:00
Jarrod Johnson affbbcc89b Fix syncfiles for el7 2021-04-23 12:27:42 -04:00
Jarrod Johnson 45cc4b7788 Add documentation syncfiles examples
Also, check to make sure a syncfiles has actual
work to do before triggering a greenthread and
the client to start polling.
2021-04-23 11:09:35 -04:00
Jarrod Johnson 945f108548 Fix inconsistencies between el7 and el8
While there are still differences, minimize
the ones that aren't for specific reasons
2021-04-23 09:08:50 -04:00
Jarrod Johnson 1f9c440525 Handle non-utf8 console data
Sometimes console uses non-utf8.

Fallback to cp437 when utf8 fails us.
2021-04-22 15:14:52 -04:00
Jarrod Johnson 8397c5defc Only close datfile if datfile is a file
If going by filename alone, there will be
no file handle to close. Correct by not
attempting a close in such a case.
2021-04-22 13:55:10 -04:00
Jarrod Johnson b7af6b5c27 Add model name to discovery info
Sometimes the model name is
useful criteria for evaluating systems,
and the model number isn't
quite that handy.

For XCC, we can provide this data too. Provide it in xcc scan
method and then offer it up to clients.
2021-04-22 13:38:51 -04:00
Jarrod Johnson c38aac88df Avoid overwriting customized installdisk or partitioning 2021-04-22 12:12:58 -04:00
Jarrod Johnson 6d1348f5fa Add pre.d to EL8 profiles 2021-04-22 12:08:25 -04:00
Jarrod Johnson c05f18d865 Remove abortive custom names for pre.sh.custom 2021-04-22 11:48:05 -04:00
Jarrod Johnson bf00c75f43 Specify supported protocols to be confluent.console
Eventlet will return no supported protocols if
none are specified. Firefox doesn't care, but
chrome rejects such a reply.
2021-04-22 07:44:18 -04:00
Jarrod Johnson 2e04647c95 Add xz to confluent genesis
xz provides better compression than gzip, handy
to have as an option.
2021-04-21 16:59:33 -04:00
Jarrod Johnson 479ddd582e Induce python2 to unicode
eventlet will treat str as binary,
correct by forcing unicode.

For python3, it is a no-op, but
python2 needs it to pass the 'text_type' check in eventlet.
2021-04-21 15:33:27 -04:00
Jarrod Johnson d82690f0d9 Fix python3-ism
python2 doesn't understand how to return
an iterator from within an iterator.
2021-04-21 13:31:11 -04:00
Jarrod Johnson 34cfd99fd8 Periodic reassimilation
Have leader periodically try to
assimilate offline members.

This will recover from some scenarios
where a rogue non-quorum collective
or a stray non-collective state
persists.
2021-04-21 10:30:54 -04:00
Jarrod Johnson 9009f63b2d Implement websocket based terminal access 2021-04-20 15:49:07 -04:00
Jarrod Johnson ab6bf82da6 Begin draft work to support websocket
First use case will be consoles.

Then we can consider the async case
for smoother async operation as well
though that is less critical.
2021-04-19 17:04:26 -04:00
Jarrod Johnson 0fae615756 Have nodediscover properly set exitcode
It was not propogating error to the caller in script context
2021-04-19 08:58:08 -04:00
Jarrod Johnson 37dca72579 Allow managercandidates to limit switch interrogation
If a switch is a node and has denoted allowed managers,
do not interrogate that switch if this member is not
permitted.
2021-04-16 14:19:51 -04:00
Jarrod Johnson 1aea406852 Restore error handling to SNMP
Properly carry and reconstitute exceptions
used to drive the specific errors.
2021-04-16 14:09:44 -04:00
Jarrod Johnson 374b87e2d7 Offload macmap SNMP activity to an auxillary process
This doesn't make the code more efficient, but it
keeps it from slowing down the main process
and allows it to leverage an additional core
to do the work.

Still needs work to restore the error reporting.
2021-04-15 17:22:15 -04:00
Jarrod Johnson 3469c8ab2b Fix affluent communication
Affluent would hang on error, particularly
certificate problem.  Fix by catching and being
explicit about the error, and generically
fixing the arguments for 'get' to have
the inntended timeout.
2021-04-14 15:51:18 -04:00
Jarrod Johnson e08ca5fb7e Have confetty show correct trace more easily
In some scenarios, the quitconfetty will overwrite the
stack trace.

Get the relevant trace before trying the quitconfetty, then print
the saved trace.
2021-04-14 10:10:44 -04:00
Jarrod Johnson 23dffe882e Do a keepalive to track idle, but alive collective
When relaying a command, use a keepalive to indicate that
the connection is alive, just not sending data.
2021-04-09 17:27:00 -04:00
Jarrod Johnson b6fb91b228 Have follower thread avoid killing self 2021-04-09 15:36:35 -04:00
Jarrod Johnson ac4d1fa8e1 More collective startup/recovery cleanup
If assimilated during startup, stop doing startup.
2021-04-09 13:32:32 -04:00
Jarrod Johnson 1d902c4d90 Backoff if trying to find a currently live server 2021-04-09 11:49:56 -04:00
Jarrod Johnson f11687998f Fix typo in cosmetic message 2021-04-08 20:48:46 -04:00
Jarrod Johnson 153956b2cd Further refine collective startup behavior 2021-04-08 18:26:59 -04:00
Jarrod Johnson 64d5081be3 Fix collective retry logic
It erroneously treated a thread as a bool, need
to check if None to know if it is scheduled.
2021-04-08 15:56:33 -04:00
Jarrod Johnson 8d16b412ae Further refine collective start process
Serialize assimilation, do not induce activity that may have been
aborted by an earlier chain.

Further, accelerate initial startup by making potential timeouts
occur concurrently, rather than sequentially.
2021-04-08 13:44:20 -04:00
Jarrod Johnson c5ec34d5a5 Actually hook in new assimilation behavior
Additionally, use collective member name as
tiebreaker if txcount and follower count is
identical.
2021-04-07 13:15:36 -04:00
Jarrod Johnson 6a4086679c Rework collective assimilation logic
Followers will only depart if their current leader
is assimilated.

Leaders with quorum will refuse assimilation and instruct
member trying to assimilate to join it.

Leaders without quorum will either follow the assimilation leader
or refuse, depending on who has highest transaction count, and if
a tie, which has the larger set of followers
2021-04-07 13:05:02 -04:00
Jarrod Johnson 3d2422ded3 Coerce iterator to list for length check 2021-04-07 08:40:18 -04:00
Jarrod Johnson 7d0f47bbcb Correct syntax for measuring collective size 2021-04-07 08:31:29 -04:00
Jarrod Johnson 9a779f2dd2 Improve collective startup behavior
Have confluent focus on establishing quorum before
initiating headless console and discovery activity.
2021-04-07 07:58:10 -04:00
Jarrod Johnson 4724d9e45b Eliminate one source of stale data leaking
If nodes are swapped, then clearing the attributes
did not clear some mappings.
2021-04-06 18:19:13 -04:00
Jarrod Johnson 8404ddf3a2 Fix import of sortutil 2021-04-06 10:43:37 -04:00
Jarrod Johnson 872a13589c Sort collective members
Improve consistency of output by sorting the output.
2021-04-06 09:35:34 -04:00
Jarrod Johnson 433a22e6e7 Finish rename of variable 2021-04-06 08:22:51 -04:00
Jarrod Johnson 1f238a3f8b Fix variable that was not declared after refactor 2021-04-06 08:20:10 -04:00
Jarrod Johnson 670fc87e1d Address several collective issues
When a stream has been deleted from cfgstreams, contiue exception
handling since the desired result.

For connections to a manager, institute a 15 second socket level timeout.
This avoids an abandoned server conversation from locking a colleective member startup.

When scheduling the failover check, first block any redundant attempts to schedule.

Wrap the collective startup in an exception
handler, so that a retry is
more well guaranteed.
2021-04-05 16:39:41 -04:00
Jarrod Johnson 3be3d4a588 Always send null byte out stdout
If the buffer doesn't exist or if it does,
either way make sure to write out a null
byte to let the caller know that it
is done.
2021-04-02 14:01:25 -04:00
Jarrod Johnson 14d13749ad Guarantee consoleserver init before use
During a restart, a client may aggressively trigger
console reconnect before the consoleserver starts.

Make sure that the daemon is running and globals
ready before API could possible ask for console.
2021-04-02 13:57:45 -04:00
Jarrod Johnson 92a10ede11 Pull in tmt fixes from Martin Hughes fork
Fix SGR being applied and fix default fg/bg color situation.
2021-04-02 13:13:19 -04:00
Jarrod Johnson d9051e80d3 Fix console buffer interaction
In some environments, the read method on the pipe object fails
to work, os.read should be the same, but seems to work better and
is happy to perform the opportunisticly large reads I want.
2021-04-02 12:29:41 -04:00
Jarrod Johnson 2d43fac1b5 Add a contingency for temporary read error 2021-04-02 11:54:38 -04:00
Jarrod Johnson 4a0d419f85 Improve behavior on python2 systems 2021-04-02 11:47:14 -04:00
Jarrod Johnson 7fb5f9756d Explictly request newer C version
C99 is needed for tmt, might as well go GNU11
2021-04-02 10:36:56 -04:00
Jarrod Johnson b482410072 Have server pull in vtbufferd 2021-04-01 17:31:52 -04:00
Jarrod Johnson 4d47f58e53 Make tar creation less verbose 2021-04-01 17:22:10 -04:00
Jarrod Johnson c5bb55e8f2 Fix name in the rpm spec 2021-04-01 17:21:33 -04:00
Jarrod Johnson 2320718618 Fix build issues with the vtbuffer daemon 2021-04-01 17:17:34 -04:00
vmaneagit 75e13239c2 Update nodesupport.ronn 2021-03-22 13:40:49 +02:00
vmaneagit c88d8d047a Update collate.ronn
OPTIONS chapter has been updated to match the output from confetty -h
2021-03-22 13:38:13 +02:00
vmaneagit 304fbba2bb Update confetty.ronn
OPTIONS chapter has been updated to match the output from confetty -h
2021-03-22 13:31:54 +02:00
vmaneagit d2f35ccbde Update nodegroupattrib.ronn.tmpl
Changes made for Option chapter:
 - added -h command
2021-03-22 12:40:46 +02:00
vmaneagit 273c32d4a7 Update nodelicense.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Update typo in Description chapter: "Using delete will remove the specified license name from  th eBMC."
2021-03-22 12:38:55 +02:00
vmaneagit 1616999cb9 Update nodelicense.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-22 12:36:24 +02:00
vmaneagit 195db5c6e8 Update nodepower.ronn
Changes made for Option chapter:
 - added -m command
2021-03-22 12:34:23 +02:00
vmaneagit fa0e216c7e Update noderemove.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-22 12:29:35 +02:00
vmaneagit 1605169b92 Update nodereseat.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-22 12:28:17 +02:00
vmaneagit 7fa0016536 Update nodersync.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-22 12:26:51 +02:00
vmaneagit b4d26c3c4e Update nodersync.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-22 12:21:28 +02:00
vmaneagit 7470d9b55c Update nodesupport.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-22 12:06:57 +02:00
vmaneagit aaf1fb8c94 Update collective.ronn
Changes made for Option chapter:
 - added -h command
2021-03-22 12:01:24 +02:00
98 changed files with 1916 additions and 598 deletions
+98 -88
View File
@@ -663,6 +663,28 @@ def get_session_node(shellargs):
return None
def run_inline_command(path, arg, completion, **kwargs):
tlvdata.send(session.connection, {'operation': 'pause',
'path': currconsole})
buffdata = ''
while select.select((session.connection,), (), (), 0)[0]:
buffdata += consume_termdata(session.connection, bufferonly=True)
rc = session.simple_noderange_command(consolename, path, arg, **kwargs)
tlvdata.send(session.connection, {'operation': 'resume',
'path': currconsole})
sys.stdout.write(completion)
sys.stdout.flush()
if buffdata:
try:
sys.stdout.write(buffdata)
except UnicodeEncodeError:
sys.stdout.buffer.write(buffdata.encode('utf8'))
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in buffdata:
sys.stdout.write(d)
return rc
def conserver_command(filehandle, localcommand):
# x - conserver has that as 'show baud', I am inclined to replace that with
# 'request exclusive'
@@ -698,13 +720,13 @@ def conserver_command(filehandle, localcommand):
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
if localcommand[1] == 'o': # off
print("powering off...")
session.simple_noderange_command(consolename, '/power/state', 'off')
print("complete]\r")
sys.stdout.write("powering off...")
sys.stdout.flush()
run_inline_command('/power/state', 'off', 'complete]')
elif localcommand[1] == 's': # shutdown
print("shutting down...")
session.simple_noderange_command(consolename, '/power/state', 'shutdown')
print("complete]\r")
sys.stdout.write("shutting down...")
sys.stdout.flush()
run_inline_command('/power/state', 'shutdown', 'complete]')
elif localcommand[1] == 'b': # boot
cmdlen += 1
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
@@ -715,59 +737,46 @@ def conserver_command(filehandle, localcommand):
bootmode = 'uefi'
bootdev = 'setup'
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
if rc:
print("Error]\r")
else:
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
rc = run_inline_command('/power/state', 'boot', 'complete]')
if rc:
print("Error]\r")
else:
print("complete]\r")
print("Error]")
elif localcommand[2] == 'n': # boot to network
print("booting to network...")
sys.stdout.write("booting to network...")
sys.stdout.flush()
bootmode = 'uefi'
bootdev = 'network'
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
if rc:
print("Error]\r")
else:
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
rc = run_inline_command('/power/state', 'boot', 'complete]')
if rc:
print("Error]\r")
else:
print("complete]\r")
elif localcommand[2] == '\x0d': # boot to default
print("booting to default...")
bootmode = 'uefi'
bootdev = 'default'
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
if rc:
print("Error]\r")
else:
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
rc = run_inline_command('/power/state', 'boot', 'complete]')
if rc:
print("Error]\r")
else:
print("complete]\r")
else:
print("Unknown boot state.]\r")
else:
print("Unknown power state.]\r")
check_power_state()
elif localcommand[0] == 'r':
sys.stdout.write('\x1b7\x1b[999;999H\x1b[6n')
@@ -929,65 +938,7 @@ def main():
rdylist = ()
for fh in rdylist:
if fh == session.connection:
# this only should get called in the
# case of a console session
# each command should slurp up all relevant
# recv potential
#fh.read()
try:
data = tlvdata.recv(fh)
except Exception:
data = None
if type(data) == dict:
updatestatus(data)
continue
if data is not None:
data = client.stringify(data)
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
try:
sys.stdout.write(data)
except UnicodeEncodeError:
sys.stdout.buffer.write(data.encode('utf8'))
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in data:
sys.stdout.write(d)
now = time.time()
if ('showtime' not in laststate or
(now // 60) != laststate['showtime'] // 60):
# don't bother churning if minute does not change
laststate['showtime'] = now
updatestatus()
try:
sys.stdout.flush()
except Exception:
# EWOULDBLOCK causes this to raise, ignore
# this scenario comfortable that it
# will come out soon enough
pass
else:
deadline = 5
connected = False
while not connected and deadline > 0:
try:
server_connect()
connected = True
except Exception:
pass
if not connected:
time.sleep(1)
deadline -=1
if connected:
do_command(
"start /nodes/%s/console/session skipreplay=True" % consolename,
netserver)
else:
doexit = True
inconsole = False
sys.stdout.write("\r\n[remote disconnected]\r\n")
break
consume_termdata(fh)
else:
try:
myinput = fh.read()
@@ -1013,6 +964,64 @@ def main():
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
def consume_termdata(fh, bufferonly=False):
global clearpowermessage
try:
data = tlvdata.recv(fh)
except Exception:
data = None
if type(data) == dict:
updatestatus(data)
return
if data is not None:
data = client.stringify(data)
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
if bufferonly:
return data
try:
sys.stdout.write(data)
except UnicodeEncodeError:
sys.stdout.buffer.write(data.encode('utf8'))
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in data:
sys.stdout.write(d)
now = time.time()
if ('showtime' not in laststate or
(now // 60) != laststate['showtime'] // 60):
# don't bother churning if minute does not change
laststate['showtime'] = now
updatestatus()
try:
sys.stdout.flush()
except Exception:
# EWOULDBLOCK causes this to raise, ignore
# this scenario comfortable that it
# will come out soon enough
pass
else:
deadline = 5
connected = False
while not connected and deadline > 0:
try:
server_connect()
connected = True
except Exception:
pass
if not connected:
time.sleep(1)
deadline -=1
if connected:
do_command(
"start /nodes/%s/console/session skipreplay=True" % consolename,
netserver)
else:
doexit = True
inconsole = False
sys.stdout.write("\r\n[remote disconnected]\r\n")
if __name__ == '__main__':
errcode = 0
deadline = 0
@@ -1024,11 +1033,12 @@ if __name__ == '__main__':
errcode = e.errorcode
except Exception as e:
import traceback
excinfo = traceback.print_exc()
try:
quitconfetty()
except Exception:
pass
traceback.print_exc()
print(excinfo)
finally:
if deadline and os.times()[4] < deadline:
sys.stderr.write('[Exited early, hit enter to continue]')
+2 -2
View File
@@ -47,14 +47,14 @@ exitcode = 0
errorNodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
if success != 0:
sys.exit(success)
# Determine which nodes were successful and print them
allNodes = set([])
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
if 'error' in node and success != 0:
sys.exit(success)
allNodes.add(node['item']['href'].replace("/", ""))
goodNodes = allNodes - errorNodes
+2 -2
View File
@@ -159,7 +159,7 @@ def parse_config_line(arguments, single=False):
if setmode is None:
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
bailout('Cannot do set and query in same command: Query detected but "{0}" appears to be set'.format(param))
if '=' in param:
key, _, value = param.partition('=')
_assign_value()
@@ -171,7 +171,7 @@ def parse_config_line(arguments, single=False):
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
bailout('Cannot do set and query in same command: Set mode detected but "{0}" appears to be a query'.format(param))
if '.' not in param:
if param == 'bmc':
printallbmc = True
+12
View File
@@ -34,6 +34,7 @@ defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
columnmapping = {
'Node': 'nodename',
'Model': 'modelnumber',
'Model Name': 'modelname',
'Serial': 'serialnumber',
'UUID': 'uuid',
'Type': 'types',
@@ -148,6 +149,7 @@ unique_fields = frozenset(['serial', 'mac', 'uuid'])
def import_csv(options, session):
nodedata = []
unique_data = {}
exitcode = 0
with open(options.importfile, 'r') as datasrc:
records = csv.reader(datasrc)
fields = process_header(next(records))
@@ -188,6 +190,7 @@ def import_csv(options, session):
for res in session.create('/nodes/', datum):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
continue
elif 'created' in res:
print('Defined ' + res['created'])
@@ -198,11 +201,14 @@ def import_csv(options, session):
{'node': nodename}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
continue
elif 'assigned' in res:
print('Discovered ' + res['assigned'])
else:
print(repr(res))
if exitcode:
sys.exit(exitcode)
def list_discovery(options, session):
@@ -291,12 +297,18 @@ def assign_discovery(options, session, needid=True):
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
exitcode = 0
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']))
elif 'error' in res:
sys.stderr.write('Error: {0}\n'.format(res['error']))
exitcode |= res.get('errorcode', 1)
else:
print(repr(res))
if exitcode:
sys.exit(exitcode)
def blocking_scan(session):
list(session.update('/discovery/rescan', {'rescan': 'start'}))
+8 -1
View File
@@ -32,6 +32,13 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
class hybridcsv(csv.excel):
lineterminator = '\n'
csv.register_dialect('hybrid', hybridcsv)
import confluent.client as client
sensorcollections = {
@@ -209,7 +216,7 @@ def main():
headernames.append(headername)
if options.csv:
linebyline = False
csvwriter = csv.writer(sys.stdout)
csvwriter = csv.writer(sys.stdout, dialect='hybrid')
if options.interval:
csvwriter.writerow(['time', 'node'] + headernames)
else:
+18 -2
View File
@@ -48,6 +48,8 @@ def run():
help='Do not prefix output with node names')
argparser.add_option('-p', '--port', type='int', default=0,
help='Specify a custom port for ssh')
argparser.add_option('-s', '--substitutename',
help='Use a different name other than the nodename for ssh')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
@@ -71,6 +73,19 @@ def run():
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
nodemap = {}
if options.substitutename:
subname = options.substitutename
if '{' not in subname:
subname = '{node}' + subname
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': subname}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
nodemap[node] = ex[node]['value']
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
@@ -79,12 +94,13 @@ def run():
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value']
sshnode = nodemap.get(node, node)
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
if options.port:
cmdv = ['ssh', '-p', '{0}'.format(options.port), node, cmd]
cmdv = ['ssh', '-p', '{0}'.format(options.port), sshnode, cmd]
else:
cmdv = ['ssh', node, cmd]
cmdv = ['ssh', sshnode, cmd]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
+9 -6
View File
@@ -22,7 +22,7 @@ node and group names sorted alphanumerically.
output suggests a large number of nodes are unreachable, abbreviate
showing 'rack1' as being unreachable may make more obvious a possible cause.
* `-b`, `--base`:
* `-b BASE`, `--base=BASE`:
Use given node as reference for comparison when using
-d, instead of using the most common result
@@ -44,16 +44,19 @@ node and group names sorted alphanumerically.
* `-c`, `--count`:
Print a count of the number of nodes in an output group under the
noderange.
noderange.
* `-r`, `--reverse`:
Rather than starting with most common to least common, start with
the least common and print the most common last.
* `-r`, `--reverse`:
Rather than starting with most common to least common, start with
the least common and print the most common last.
* `-l`, `--log`:
* `-l LOG`, `--log=LOG`:
Save output per node to individual log files, replacing {node} in the name
with the nodename of each
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
* Organizing power state of multiple nodes:
`# nodepower n1-n12 | collate`
+3
View File
@@ -37,6 +37,9 @@ on circumstances.
* `-i`:
Provide the token as an argument rather than interactively.
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
* Inviting a server called mgt2:
+6 -3
View File
@@ -17,15 +17,18 @@ the various function specific commands.
## OPTIONS
* `-s`, `--server`:
* `-s SERVER:PORT`, `--server=SERVER:PORT`:
Confluent instance to connect to
* `-c`, `--control`:
* `-c PATH`, `--control=PATH`:
Path to offer terminal control
* `-m`, `--mintime`:
* `-m MINTIME`, `--mintime=MINTIME`:
Minimum time to run or else pause for input (used to
keep a terminal from closing quickly on error)
* `-h`, `--help`:
Show help message and exit
## COMMANDS
@@ -36,6 +36,9 @@ the attributes are set on the node versus a group to which a node belongs.
* `-p`, `--prompt`:
Prompt for attribute values interactively
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
+5 -2
View File
@@ -9,7 +9,7 @@ nodelicense(8) -- Manage license keys on BMC
`nodelicense` manages license keys on supported BMCs. Without an argument, the command
lists currently installed license. Using `delete` will remove the specified license name
from th eBMC. The `save` subcommand will take the passed directory (which may be in the form
from the BMC. The `save` subcommand will take the passed directory (which may be in the form
of /path/to/{node}/ to have the node name substituted for each node) and back up installed licenses
to that directory. The `install` command will take the specified filename and install. The filename
argument may be of the form xcc_fod_0034_7X21{id.serial}.key to have the serial number substituted
@@ -17,5 +17,8 @@ to allow unique licenses to be specified in a single command.
## OPTIONS
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to delete licenses from, prompting if over the threshold
* `-h`, `--help`:
Show help message and exit
+4
View File
@@ -31,6 +31,10 @@ off will not react to this request.
* `-p`, '--showprevious':
Show previous power state for all directives that may change power state.
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to change power state, prompting if
over the threshold
* `-h`, `--help`:
Show help message and exit
+4 -2
View File
@@ -11,9 +11,12 @@ noderemove(8) -- Remove nodes from the confluent management service
## OPTIONS
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to delete, prompting if over the
threshold
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
@@ -27,4 +30,3 @@ noderemove(8) -- Remove nodes from the confluent management service
`r2u3: deleted`
`r2u2: deleted`
`r2u1: deleted`
+4 -1
View File
@@ -13,8 +13,11 @@ the chassis and putting it back in, but without actually having to do so.
## OPTIONS
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to reseat, prompting if over the threshold
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
+3 -1
View File
@@ -17,7 +17,9 @@ noderange. This will present progress as percentage for all nodes.
exceeds the count, then excess nodes will wait until one of the
active count completes.
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to run rsync to, prompting if over the
threshold
* `-h`, `--help`:
Show help message and exit
+8 -1
View File
@@ -26,7 +26,14 @@ as stderr, unlike psh which combines all stdout and stderr into stdout.
* `-p PORT`, `--port=PORT`
Specify a custom port for ssh
* `-s SUBSTITUTION`, `--substitutename=SUBSTITITUTION`
Specify a substitution name instead of the nodename. If no {} are in the substitution,
it is considered to be an append. For example, '-s -ib' would produce 'node1-ib' from 'node1'.
Full expression syntax is supported, in which case the substitution is considered to be the entire
new name. {node}-ib would be equivalent to -ib. For example, nodeshell -s {bmc} node1
would ssh to the BMC instead of the node.
## EXAMPLES
* Running `echo hi` on for nodes:
+5 -2
View File
@@ -21,9 +21,12 @@ specific suffixes added to any file produced.
## OPTIONS
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to download diagnostic data from, prompting
if over the threshold
if over the threshold
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
@@ -143,7 +143,7 @@ if __name__ == '__main__':
except ValueError:
data = None
if outbin:
with open(outbin, 'wb') as outf:
with open(outbin, 'ab+') as outf:
reader = HTTPSClient(json=json).grab_url(
sys.argv[1], data, returnrsp=True)
chunk = reader.read(16384)
@@ -0,0 +1,25 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
running content under scripts/ which ask much less of the deployment server
Here is an example of what a playbook would look like broadly:
- name: Example
gather_facts: no
tasks:
- name: Example1
lineinfile:
path: /etc/hosts
line: 1.2.3.4 test1
create: yes
- name: Example2
lineinfile:
path: /etc/hosts
line: 1.2.3.5 test2
create: yes
@@ -0,0 +1,25 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
running content under scripts/ which ask much less of the deployment server
Here is an example of what a playbook would look like broadly:
- name: Example
gather_facts: no
tasks:
- name: Example1
lineinfile:
path: /etc/hosts
line: 1.2.3.4 test1
create: yes
- name: Example2
lineinfile:
path: /etc/hosts
line: 1.2.3.5 test2
create: yes
@@ -1,5 +1,5 @@
#!/bin/sh
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/' $2/profile.yaml
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/; s/oraclelinux/Oracle Linux/; s/alma/AlmaLinux/' $2/profile.yaml
ln -s $1/images/pxeboot/vmlinuz $2/boot/kernel && \
ln -s $1/images/pxeboot/initrd.img $2/boot/initramfs/distribution
mkdir -p $2/boot/efi/boot && \
@@ -33,14 +33,15 @@
reboot
%packages
#-kernel-uek # This can opt out of the UEK for the relevant distribution
chrony
rsync
python
tar
pciutils
%include /tmp/addonpackages
%end
# A kickstart.custom file is provided to enable easily adding
# kickstart content without modifying the stock file.
# While the stock file may be safely modified, using the .custom
@@ -49,32 +50,33 @@ pciutils
%include /tmp/kickstart.custom
%pre
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/preinst.sh
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/preinst.sh
. /tmp/preinst.sh
%end
%post --nochroot
mkdir -p /mnt/sysimage/etc/confluent
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh > /tmp/postinst.sh
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/prechroot.sh > /tmp/postinst.sh
. /tmp/postinst.sh
# Hook firstboot.sh
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/sysimage/etc/confluent/firstboot.sh
chmod +x /mnt/sysimage/etc/confluent/firstboot.sh
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
mkdir -p /mnt/sysimage/opt/confluent/bin
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /mnt/sysimage/opt/confluent/bin/firstboot.sh
chmod +x /mnt/sysimage/opt/confluent/bin/firstboot.sh
%end
%post
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
systemctl enable firstboot
chgrp ssh_keys /etc/ssh/ssh*key
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /etc/confluent/firstboot.sh
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/postinst.sh
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /opt/confluent/bin/firstboot.sh
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/postinst.sh
. /tmp/postinst.sh
%end
@@ -1,3 +1,3 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet
kernelargs: quiet # These arguments are passed to the installer
#installedargs: example # These arguments would be added to the installed system
@@ -0,0 +1,414 @@
# 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 argparse
import ctypes
import fcntl
import json
from select import select
import socket
import struct
import os
import subprocess
import sys
import time
if os.path.exists('/opt/confluent/bin/apiclient'):
apiclient = '/opt/confluent/bin/apiclient'
elif os.path.exists('/etc/confluent/apiclient'):
apiclient = '/etc/confluent/apiclient'
class IpmiMsg(ctypes.Structure):
_fields_ = [('netfn', ctypes.c_ubyte),
('cmd', ctypes.c_ubyte),
('data_len', ctypes.c_short),
('data', ctypes.POINTER(ctypes.c_ubyte))]
class IpmiSystemInterfaceAddr(ctypes.Structure):
_fields_ = [('addr_type', ctypes.c_int),
('channel', ctypes.c_short),
('lun', ctypes.c_ubyte)]
class IpmiRecv(ctypes.Structure):
_fields_ = [('recv_type', ctypes.c_int),
('addr', ctypes.POINTER(IpmiSystemInterfaceAddr)),
('addr_len', ctypes.c_uint),
('msgid', ctypes.c_long),
('msg', IpmiMsg)]
class IpmiReq(ctypes.Structure):
_fields_ = [('addr', ctypes.POINTER(IpmiSystemInterfaceAddr)),
('addr_len', ctypes.c_uint),
('msgid', ctypes.c_long),
('msg', IpmiMsg)]
_IONONE = 0
_IOWRITE = 1
_IOREAD = 2
IPMICTL_SET_MY_ADDRESS_CMD = (
_IOREAD << 30 | ctypes.sizeof(ctypes.c_uint) << 16
| ord('i') << 8 | 17) # from ipmi.h
IPMICTL_SEND_COMMAND = (
_IOREAD << 30 | ctypes.sizeof(IpmiReq) << 16
| ord('i') << 8 | 13) # from ipmi.h
# next is really IPMICTL_RECEIVE_MSG_TRUNC, but will only use that
IPMICTL_RECV = (
(_IOWRITE | _IOREAD) << 30 | ctypes.sizeof(IpmiRecv) << 16
| ord('i') << 8 | 11) # from ipmi.h
BMC_SLAVE_ADDR = ctypes.c_uint(0x20)
CURRCHAN = 0xf
ADDRTYPE = 0xc
class Session(object):
def __init__(self, devnode='/dev/ipmi0'):
"""Create a local session inband
:param: devnode: The path to the ipmi device
"""
self.ipmidev = open(devnode, 'r+')
fcntl.ioctl(self.ipmidev, IPMICTL_SET_MY_ADDRESS_CMD, BMC_SLAVE_ADDR)
# the interface is initted, create some reusable memory for our session
self.databuffer = ctypes.create_string_buffer(4096)
self.req = IpmiReq()
self.rsp = IpmiRecv()
self.addr = IpmiSystemInterfaceAddr()
self.req.msg.data = ctypes.cast(
ctypes.addressof(self.databuffer),
ctypes.POINTER(ctypes.c_ubyte))
self.rsp.msg.data = self.req.msg.data
self.userid = None
self.password = None
def await_reply(self):
rd, _, _ = select((self.ipmidev,), (), (), 1)
while not rd:
rd, _, _ = select((self.ipmidev,), (), (), 1)
def pause(self, seconds):
time.sleep(seconds)
@property
def parsed_rsp(self):
response = {'netfn': self.rsp.msg.netfn, 'command': self.rsp.msg.cmd,
'code': bytearray(self.databuffer.raw)[0],
'data': bytearray(
self.databuffer.raw[1:self.rsp.msg.data_len])}
return response
def await_config(s, bmccfg, channel):
vlan = bmccfg.get('bmcvlan', None)
ipv4 = bmccfg.get('bmcipv4', None)
prefix = bmccfg.get('prefixv4', None)
gw = bmccfg.get('bmcgw', None)
def raw_command(self,
netfn,
command,
data=(),
bridge_request=None,
retry=True,
delay_xmit=None,
timeout=None,
waitall=False, rslun=0):
self.addr.channel = CURRCHAN
self.addr.addr_type = ADDRTYPE
self.addr.lun = rslun
self.req.addr_len = ctypes.sizeof(IpmiSystemInterfaceAddr)
self.req.addr = ctypes.pointer(self.addr)
self.req.msg.netfn = netfn
self.req.msg.cmd = command
if data:
data = memoryview(bytearray(data))
try:
self.databuffer[:len(data)] = data[:len(data)]
except ValueError:
self.databuffer[:len(data)] = data[:len(data)].tobytes()
self.req.msg.data_len = len(data)
fcntl.ioctl(self.ipmidev, IPMICTL_SEND_COMMAND, self.req)
self.await_reply()
self.rsp.msg.data_len = 4096
self.rsp.addr = ctypes.pointer(self.addr)
self.rsp.addr_len = ctypes.sizeof(IpmiSystemInterfaceAddr)
fcntl.ioctl(self.ipmidev, IPMICTL_RECV, self.rsp)
return self.parsed_rsp
def _is_tsm(model):
return model in ('7y00', '7z01', '7y98', '7y99')
def set_port(s, port, vendor, model):
oport = port
if vendor not in ('IBM', 'Lenovo'):
raise Exception('{0} not implemented'.format(vendor))
if _is_tsm(model):
return set_port_tsm(s, port, model)
else:
set_port_xcc(s, port, model)
return 1
def set_port_tsm(s, port, model):
oport = port
sys.stdout.write('Setting TSM port to "{}"...'.format(oport))
sys.stdout.flush()
if port == 'ocp':
s.raw_command(0x32, 0x71, b'\x00\x01\x00')
elif port == 'dedicated':
s.raw_command(0x32, 0x71, b'\x00\x00\x00')
else:
raise Exception("Unsupported port for TSM")
timer = 15
while timer:
timer = timer - 1
time.sleep(1.0)
sys.stdout.write('.')
sys.stdout.flush()
if port == 'ocp':
iface = 0
s.raw_command(0x32, 0x71, b'\x00\x00\x03')
elif port == 'dedicated':
iface = 1
s.raw_command(0x32, 0x71, b'\x00\x01\x03')
rsp = s.raw_command(0x32, 0x72, bytearray([4, iface, 0]))
print('Complete')
return int(rsp['data'][0])
def set_port_xcc(s, port, model):
oport = port
if port.lower() == 'dedicated':
port = b'\x01'
elif port.lower() in ('ml2', 'ocp'):
port = b'\x02\x00'
elif port.lower() == 'lom':
if model == '7x58':
port = b'\x00\x02'
else:
port = b'\x00\x00'
else:
port = port.split(' ')
port = bytes(bytearray([int(x) for x in port]))
currport = bytes(s.raw_command(0xc, 2, b'\x01\xc0\x00\x00')['data'][1:])
if port == currport:
sys.stdout.write('XCC port already set to "{}"\n'.format(oport))
return
sys.stdout.write('Setting XCC port to "{}"...'.format(oport))
sys.stdout.flush()
s.raw_command(0xc, 1, b'\x01\xc0' + port)
tries = 60
while currport != port and tries:
tries -= 1
time.sleep(0.5)
sys.stdout.write('.')
sys.stdout.flush()
currport = bytes(s.raw_command(0xc, 2, b'\x01\xc0\x00\x00')['data'][1:])
if not tries:
raise Exception('Timeout attempting to set port')
sys.stdout.write('Complete\n')
def check_vlan(s, vlan, channel):
if vlan == 'off':
vlan = b'\x00\x00'
else:
vlan = int(vlan)
if vlan:
vlan = vlan | 32768
vlan = struct.pack('<H', vlan)
currvlan = bytes(s.raw_command(0xc, 2, bytearray([channel, 0x14 ,0, 0]))['data'][1:])
if bytearray(currvlan)[1] & 0b10000000 == 0:
currvlan = b'\x00\x00'
return currvlan == vlan
def set_vlan(s, vlan, channel):
ovlan = vlan
if vlan == 'off':
vlan = b'\x00\x00'
else:
vlan = int(vlan)
if vlan:
vlan = vlan | 32768
vlan = struct.pack('<H', vlan)
if check_vlan(s, ovlan, channel):
sys.stdout.write('VLAN already configured to "{0}"\n'.format(ovlan))
return False
rsp = s.raw_command(0xc, 1, bytearray([channel, 0x14]) + vlan)
if rsp.get('code', 1) == 0:
print('VLAN configured to "{}"'.format(ovlan))
else:
print('Error setting vlan: ' + repr(rsp))
return True
def get_lan_channel(s):
for chan in range(1, 16):
rsp = s.raw_command(6, 0x42, bytearray([chan]))['data']
if not rsp:
continue
medtype = int(rsp[1]) & 0b1111111
if medtype not in (4, 6):
continue
rsp = s.raw_command(0xc, 2, bytearray([2, chan, 5, 0, 0]))
if rsp.get('code', 1) == 0:
return chan
return 1
def check_ipv4(s, ipaddr, channel):
ipaddr = bytearray(socket.inet_aton(ipaddr))
rsp = s.raw_command(0xc, 2, bytearray([channel, 3, 0, 0]))['data'][-4:]
return rsp == ipaddr
def set_ipv4(s, ipaddr, channel):
oipaddr = ipaddr
ipaddr = bytearray(socket.inet_aton(ipaddr))
if check_ipv4(s, oipaddr, channel):
print('IP Address already set to {}'.format(oipaddr))
return False
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
if rsp != 1:
sys.stdout.write("Changing configuration to static...")
sys.stdout.flush()
resp = s.raw_command(0xc, 1, bytearray([channel, 4, 1]))
tries = 0
while rsp != 1 and tries < 30:
sys.stdout.write('.')
sys.stdout.flush()
tries += 1
time.sleep(0.5)
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
sys.stdout.write('Complete\n')
sys.stdout.flush()
print('Setting IP to {}'.format(oipaddr))
s.raw_command(0xc, 1, bytearray([channel, 3]) + ipaddr)
return True
def check_subnet(s, prefix, channel):
prefix = int(prefix)
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
rsp = s.raw_command(0xc, 2, bytearray([channel, 6, 0, 0]))['data'][-4:]
return rsp == mask
def set_subnet(s, prefix, channel):
oprefix = prefix
prefix = int(prefix)
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
if check_subnet(s, prefix, channel):
print('Subnet Mask already set to /{}'.format(oprefix))
return False
print('Setting subnet mask to /{}'.format(oprefix))
s.raw_command(0xc, 1, bytearray([channel, 6]) + mask)
return True
def check_gateway(s, gw, channel):
gw = bytearray(socket.inet_aton(gw))
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
return rsp == gw
def set_gateway(s, gw, channel):
ogw = gw
gw = bytearray(socket.inet_aton(gw))
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
if check_gateway(s, ogw, channel):
print('Gateway already set to {}'.format(ogw))
return False
print('Setting gateway to {}'.format(ogw))
s.raw_command(0xc, 1, bytearray([channel, 12]) + gw)
return True
def dotwait():
sys.stdout.write('.')
sys.stdout.flush()
time.sleep(0.5)
def main():
a = argparse.ArgumentParser(description='Locally configure a BMC device')
a.add_argument('-v', help='vlan id or off to disable vlan tagging')
a.add_argument('-p', help='Which port to use (dedicated, lom, ocp, ml2)')
a.add_argument('-i', help='JSON configuration file to read for '
'configuration')
a.add_argument('-c', help='Use Confluent API to direct BMC configuration',
action='store_true')
args = a.parse_args()
if args.i:
bmccfg = json.load(open(args.i))
elif args.c:
bmccfgsrc = subprocess.check_output(
[sys.executable, apiclient, '/confluent-api/self/bmcconfig', '-j'])
bmccfg = json.loads(bmccfgsrc)
else:
bmccfg = {}
if args.p is not None:
bmccfg['bmcport'] = args.p
if args.v is not None:
bmccfg['bmcvlan'] = args.v
vendor = open('/sys/devices/virtual/dmi/id/sys_vendor').read()
vendor = vendor.strip()
try:
model = open('/sys/devices/virtual/dmi/id/product_sku').read().strip()
if model == 'none':
raise Exception('No SKU')
except Exception:
model = open('/sys/devices/virtual/dmi/id/product_name').read()
if vendor in ('Lenovo', 'IBM'):
if '[' in model and ']' in model:
model = model.split('[')[1].split(']')[0]
model = model[:4].lower()
s = Session('/dev/ipmi0')
if not bmccfg:
print("No BMC configuration specified, exiting.")
return
if bmccfg.get('bmcport', None):
channel = set_port(s, bmccfg['bmcport'], vendor, model)
else:
channel = get_lan_channel(s)
awaitvlan = False
awaitip = False
awaitprefix = False
awaitgw = False
if bmccfg.get('bmcvlan', None):
awaitvlan = set_vlan(s, bmccfg['bmcvlan'], channel)
if bmccfg.get('bmcipv4', None):
awaitip = set_ipv4(s, bmccfg['bmcipv4'], channel)
if bmccfg.get('prefixv4', None):
awaitprefix = set_subnet(s, bmccfg['prefixv4'], channel)
if bmccfg.get('bmcgw', None):
awaitgw = set_gateway(s, bmccfg['bmcgw'], channel)
sys.stdout.write('Waiting for changes to take effect...')
sys.stdout.flush()
while awaitvlan and not check_vlan(s, bmccfg['bmcvlan'], channel):
dotwait()
while awaitip and not check_ipv4(s, bmccfg['bmcipv4'], channel):
dotwait()
while awaitprefix and not check_subnet(s, bmccfg['prefixv4'], channel):
dotwait()
while awaitprefix and not check_gateway(s, bmccfg['bmcgw'], channel):
dotwait()
sys.stdout.write('done\n')
sys.stdout.flush()
#await_config(s, bmccfg, channel)
if __name__ == '__main__':
main()
@@ -4,7 +4,7 @@ Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/etc/confluent/firstboot.sh
ExecStart=/opt/confluent/bin/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -6,17 +6,31 @@
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
export nodename mgr profile
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
logshowpid=$!
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
run_remote firstboot.custom
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot.d
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot.d
fi
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
systemctl disable firstboot
rm /etc/systemd/system/firstboot.service
rm /etc/confluent/firstboot.ran
kill $logshowpid
@@ -1,37 +1,73 @@
function set_confluent_vars() {
if [ -z "$mgr" ]; then
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$profile" ]; then
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
run_remote() {
requestedcmd="'$*'"
curlargs=""
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source $cmd
cd - > /dev/null
return $retcode
}
run_remote() {
requestedcmd="'$*'"
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
@@ -41,7 +77,9 @@ run_remote() {
shift
./$cmd $*
retcode=$?
echo "$requestedcmd exited with code $retcode"
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
return $retcode
}
@@ -53,12 +91,12 @@ run_remote_python() {
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$mgr/confluent-public/os/$profile/scripts/
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -70,3 +108,26 @@ run_remote_python() {
cd - > /dev/null
return $retcode
}
run_remote_config() {
echo
set_confluent_vars
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
else
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/status -w 204
fi
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
@@ -2,6 +2,6 @@
# This is a convenient place to keep customizations separate from modifying the stock scripts
# While modification of the stock scripts is fine, it may be easier to rebase to a newer
# stock profile if the '.custom' files are used.
# An example for installing OFED for infiniband follows (see the file for more detail):
#run_remote infiniband/mofed.post
@@ -1,20 +1,22 @@
#!/bin/sh
# need to copy over ssh key info
mkdir -p /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
tail -f /var/log/confluent/confluent-post.log > /dev/tty &
logshowpid=$!
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
chmod 700 /etc/confluent
chmod og-rwx /etc/confluent/*
export mgr profile nodename
export confluent_mgr confluent_profile nodename
. /etc/confluent/functions
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
if [ -f /tmp/cryptboot ]; then
run_remote tpm_luks.sh
fi
# By default, the install repository is ignored, change
# this by manually adding local repositories
@@ -37,3 +39,11 @@ run_remote_python syncfileclient
# run_remote example.sh
# run_remote_python example.py
run_remote post.custom
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -11,4 +11,3 @@
#Some addons improve efficiency by adding dependencies during install
#here is an example for adding OFED install prereqs to the install
#run_remote infiniband/mofed.pre
@@ -7,7 +7,14 @@
# If you want to use a more custom partition plan, the easiest
# method is to edit the kicktstart file and comment out or
# delete %include /tmp/partitioning
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
TMUX= tmux a <> $consoledev >&0 2>&1 &
fi
exec >> /tmp/confluent-pre.log
exec 2>> /tmp/confluent-pre.log
tail -f /tmp/confluent-pre.log > /dev/tty &
logshowpid=$!
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
locale=$(grep ^locale: /etc/confluent/confluent.deploycfg)
locale=${locale#locale: }
@@ -28,7 +35,7 @@ if [ "$rootpw" = null ]; then
else
echo "rootpw --iscrypted $rootpw" > /tmp/rootpw
fi
curl -f https://$mgr/confluent-public/os/$profile/profile.yaml > /tmp/instprofile.yaml
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/profile.yaml > /tmp/instprofile.yaml
blargs=$(grep ^installedargs: /tmp/instprofile.yaml | sed -e 's/#.*//' -e 's/^installedargs: //')
if [ ! -z "$blargs" ]; then
blargs=' --append="'$blargs'"'
@@ -43,33 +50,33 @@ if [ ! -z "$blargs" ]; then
echo "bootloader $blargs" > /tmp/grubpw
fi
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host_*_key.pub; do
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
echo HostCertificate $certfile >> /etc/ssh/sshd_config.anaconda
done
/usr/sbin/sshd -f /etc/ssh/sshd_config.anaconda
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
TMUX= tmux a <> $consoledev >&0 2>&1 &
fi
touch /tmp/addonpackages
cryptboot=$(grep ^encryptboot: /etc/confluent/confluent.deploycfg | awk '{print $2}')
LUKSPARTY=''
touch /tmp/addonpackages
if [ "$cryptboot" == "tpm2" ]; then
LUKSPARTY="--encrypted --passphrase=$(cat /etc/confluent/confluent.apikey)"
echo $cryptboot >> /tmp/cryptboot
fi
export mgr profile nodename
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /tmp/functions
export confluent_mgr confluent_profile nodename
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
run_remote_python getinstalldisk
if [ -e /tmp/installdisk ]; then
run_remote pre.custom
run_remote_parts pre.d
if [ ! -e /tmp/installdisk ]; then
run_remote_python getinstalldisk
fi
if [ -e /tmp/installdisk -a ! -e /tmp/partitioning ]; then
echo clearpart --all --initlabel >> /tmp/partitioning
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
echo autopart --nohome $LUKSPARTY >> /tmp/partitioning
fi
python /etc/confluent/apiclient /confluent-public/os/$profile/kickstart.custom -o /tmp/kickstart.custom
run_remote pre.custom
python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
kill $logshowpid
@@ -8,11 +8,20 @@
# for customization, which will run in a more normal mechanism
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export mgr profile nodename
export confluent_mgr confluent_profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
chmod -R og-rwx /mnt/sysimage/etc/confluent
cp /tmp/functions /mnt/sysimage/etc/confluent/
. /tmp/functions
cp /tmp/cryptboot /mnt/sysimage/tmp/
if [ -f /tmp/cryptboot ]; then
cp /tmp/cryptboot /mnt/sysimage/tmp/
fi
mkdir -p /mnt/sysimage/var/log/confluent
mv /tmp/confluent-pre.log /mnt/sysimage/var/log/confluent
echo Port 2222 >> /etc/ssh/sshd_config.anaconda
echo Match LocalPort 22 >> /etc/ssh/sshd_config.anaconda
echo " ChrootDirectory /mnt/sysimage" >> /etc/ssh/sshd_config.anaconda
kill -HUP $(cat /run/sshd.pid)
# Preserve the ssh setup work done for the installer
# by copying into the target system and setting up
@@ -18,6 +18,6 @@ chmod 700 /mnt/sysimage/root/.ssh/
cp /root/.ssh/authorized_keys /mnt/sysimage/root/.ssh/
chmod 600 /mnt/sysimage/root/.ssh/authorized_keys
cp /etc/ssh/ssh_known_hosts /mnt/sysimage/etc/ssh/
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
cp /tmp/allnodes /mnt/sysimage/etc/ssh/shosts.equiv
cp /tmp/allnodes /mnt/sysimage/root/.shosts
@@ -4,11 +4,17 @@ import tempfile
import json
import os
import shutil
from importlib.machinery import SourceFileLoader
try:
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
except FileNotFoundError:
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
from importlib.machinery import SourceFileLoader
def load_source(mod, path):
return SourceFileLoader(mod, path).load_module()
except ImportError:
from imp import load_source
try:
apiclient = load_source('apiclient', '/opt/confluent/bin/apiclient')
except IOError:
apiclient = load_source('apiclient', '/etc/confluent/apiclient')
def partitionhostsline(line):
@@ -216,4 +222,4 @@ def synchronize():
if __name__ == '__main__':
synchronize()
synchronize()
@@ -0,0 +1,13 @@
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# Entries under MERGE will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -52,23 +52,23 @@ pciutils
%include /tmp/kickstart.custom
%pre
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/preinst.sh
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/preinst.sh
. /tmp/preinst.sh
%end
%post --nochroot
mkdir -p /mnt/sysimage/etc/confluent
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh > /tmp/postinst.sh
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/prechroot.sh > /tmp/postinst.sh
. /tmp/postinst.sh
# Hook firstboot.sh
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
mkdir -p /mnt/sysimage/opt/confluent/bin
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/sysimage/opt/confluent/bin/firstboot.sh
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /mnt/sysimage/opt/confluent/bin/firstboot.sh
chmod +x /mnt/sysimage/opt/confluent/bin/firstboot.sh
%end
@@ -77,8 +77,8 @@ cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
systemctl enable firstboot
chgrp ssh_keys /etc/ssh/ssh*key
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /opt/confluent/bin/firstboot.sh
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/postinst.sh
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/postinst.sh
. /tmp/postinst.sh
%end
@@ -6,22 +6,31 @@
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
export nodename mgr profile
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
logshowpid=$!
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot.d
fi
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
systemctl disable firstboot
rm /etc/systemd/system/firstboot.service
rm /etc/confluent/firstboot.ran
kill $logshowpid
@@ -1,44 +1,73 @@
function set_confluent_vars() {
if [ -z "$mgr" ]; then
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$profile" ]; then
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
run_remote_parts() {
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1.d/$script
done
}
run_remote() {
requestedcmd="'$*'"
curlargs=""
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source $cmd
cd - > /dev/null
return $retcode
}
run_remote() {
requestedcmd="'$*'"
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
@@ -48,7 +77,9 @@ run_remote() {
shift
./$cmd $*
retcode=$?
echo "$requestedcmd exited with code $retcode"
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
return $retcode
}
@@ -60,14 +91,18 @@ run_remote_python() {
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$mgr/confluent-public/os/$profile/scripts/
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
/usr/libexec/platform-python $*
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
else
/usr/bin/python $*
fi
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
@@ -82,9 +117,14 @@ run_remote_config() {
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
else
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/status -w 204
fi
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
@@ -9,12 +9,11 @@ if lspci -d 15b3:: -n |grep 15b3 > /dev/null; then
# two lines to use the .iso instead of the tgz packaging
#fetch_remote infiniband/mofed.iso
#mkdir MLNX_OFED
#mount -o loop ofed.iso MLNX_OFED
#mount -o loop infiniband/mofed.iso MLNX_OFED
fetch_remote infiniband/mofed.tgz
tar xf infiniband/mofed.tgz
# The rest is common between tar and iso
cd MLNX_OFED*
mount -o loop ofed
./mlnxofedinstall --force
fi
@@ -1,11 +1,15 @@
#!/bin/sh
# need to copy over ssh key info
mkdir -p /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
tail -f /var/log/confluent/confluent-post.log > /dev/tty &
logshowpid=$!
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
chmod 700 /etc/confluent
chmod og-rwx /etc/confluent/*
export mgr profile nodename
export confluent_mgr confluent_profile nodename
. /etc/confluent/functions
@@ -37,8 +41,9 @@ run_remote_python syncfileclient
run_remote post.custom
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
run_remote_parts post
run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
run_remote_config post.d
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -7,7 +7,14 @@
# If you want to use a more custom partition plan, the easiest
# method is to edit the kicktstart file and comment out or
# delete %include /tmp/partitioning
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
TMUX= tmux a <> $consoledev >&0 2>&1 &
fi
exec >> /tmp/confluent-pre.log
exec 2>> /tmp/confluent-pre.log
tail -f /tmp/confluent-pre.log > /dev/tty &
logshowpid=$!
/usr/libexec/platform-python /etc/confluent/apiclient >& /dev/null
nicname=$(ip link|grep ^$(cat /tmp/confluent.ifidx): | awk '{print $2}' | awk -F: '{print $1}')
nmcli c u $nicname
@@ -34,7 +41,7 @@ if [ "$rootpw" = null ]; then
else
echo "rootpw --iscrypted $rootpw" > /tmp/rootpw
fi
curl -f https://$mgr/confluent-public/os/$profile/profile.yaml > /tmp/instprofile.yaml
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/profile.yaml > /tmp/instprofile.yaml
blargs=$(grep ^installedargs: /tmp/instprofile.yaml | sed -e 's/#.*//' -e 's/^installedargs: //')
if [ ! -z "$blargs" ]; then
blargs=' --append="'$blargs'"'
@@ -48,16 +55,13 @@ fi
if [ ! -z "$blargs" ]; then
echo "bootloader $blargs" > /tmp/grubpw
fi
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
echo HostCertificate $certfile >> /etc/ssh/sshd_config.anaconda
done
/usr/sbin/sshd -f /etc/ssh/sshd_config.anaconda
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
tmux a <> $consoledev >&0 2>&1 &
fi
cryptboot=$(grep ^encryptboot: /etc/confluent/confluent.deploycfg | awk '{print $2}')
LUKSPARTY=''
touch /tmp/cryptpkglist
@@ -65,18 +69,29 @@ touch /tmp/addonpackages
if [ "$cryptboot" == "tpm2" ]; then
LUKSPARTY="--encrypted --passphrase=$(cat /etc/confluent/confluent.apikey)"
echo $cryptboot >> /tmp/cryptboot
echo clevis-dracut >> /tmp/cryptpkglist
echo clevis-dracut >> /tmp/cryptpkglist
fi
export mgr profile nodename
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /tmp/functions
export confluent_mgr confluent_profile nodename
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
run_remote_python getinstalldisk
if [ -e /tmp/installdisk ]; then
run_remote pre.custom
run_remote_parts pre.d
if [ ! -e /tmp/installdisk ]; then
run_remote_python getinstalldisk
fi
if [ -e /tmp/installdisk -a ! -e /tmp/partitioning ]; then
echo clearpart --all --initlabel >> /tmp/partitioning
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
echo autopart --nohome $LUKSPARTY >> /tmp/partitioning
fi
curl -f https://$mgr/confluent-public/os/$profile/kickstart.custom > /tmp/kickstart.custom
run_remote pre.custom
if [ -e /usr/libexec/platform-python ]; then
python=/usr/libexec/platform-python
elif [ -e /usr/bin/python3 ]; then
python=/usr/byn/python3
else
python=/usr/bin/python
fi
$python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
kill $logshowpid
@@ -8,12 +8,16 @@
# for customization, which will run in a more normal mechanism
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export mgr profile nodename
export confluent_mgr confluent_profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
chmod -R og-rwx /mnt/sysimage/etc/confluent
cp /tmp/functions /mnt/sysimage/etc/confluent/
. /tmp/functions
cp /tmp/cryptboot /mnt/sysimage/tmp/
if [ -f /tmp/cryptboot ]; then
cp /tmp/cryptboot /mnt/sysimage/tmp/
fi
mkdir -p /mnt/sysimage/var/log/confluent
mv /tmp/confluent-pre.log /mnt/sysimage/var/log/confluent
echo Port 2222 >> /etc/ssh/sshd_config.anaconda
echo Match LocalPort 22 >> /etc/ssh/sshd_config.anaconda
echo " ChrootDirectory /mnt/sysimage" >> /etc/ssh/sshd_config.anaconda
@@ -18,6 +18,6 @@ chmod 700 /mnt/sysimage/root/.ssh/
cp /root/.ssh/authorized_keys /mnt/sysimage/root/.ssh/
chmod 600 /mnt/sysimage/root/.ssh/authorized_keys
cp /etc/ssh/ssh_known_hosts /mnt/sysimage/etc/ssh/
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
cp /tmp/allnodes /mnt/sysimage/etc/ssh/shosts.equiv
cp /tmp/allnodes /mnt/sysimage/root/.shosts
@@ -0,0 +1,13 @@
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# Entries under MERGE will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -28,6 +28,19 @@ node=$(grep NODENAME: /etc/confluent/confluent.info|head -n 1|awk '{print $2}')
mgr=$(grep MANAGER: /etc/confluent/confluent.info|head -n 1|awk '{print $2}')
cp /opt/confluent/bin/clortho /clortho
/clortho $node $mgr > /etc/confluent/confluent.apikey
APIKEY=$(cat /etc/confluent/confluent.apikey)
while [ -z "$APIKEY" ]; do
if [ ! -f /var/run/vmware/show-esx-shell-login ]; then
chvt 1
sleep 1
chvt 2
/etc/init.d/ESXShell start
fi
echo "Deployment server failed to grant node deployment token, retrying token retrieval"
echo "A debug session has been made available on Alt-F1"
/clortho $node $mgr > /etc/confluent/confluent.apikey
APIKEY=$(cat /etc/confluent/confluent.apikey)
done
cat /tls/*.pem > /etc/confluent/ca.pem
/opt/confluent/bin/apiclient /confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg.new
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg.new | sed -e 's/^profile: //')
@@ -59,6 +59,7 @@ hostname $nodename
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
@@ -80,7 +81,7 @@ else
ip route add default via $v4gw
fi
fi
echo -n "Initialiing ssh..."
echo -n "Initializing ssh..."
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
@@ -92,11 +93,11 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do
echo HostKey $privfile >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | awk '{print $2}')
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | awk '{print $2}')
export profile mgr
echo "Running https://$mgr/confluent-public/os/$profile/scripts/onboot.sh"
/usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-public/os/$profile/scripts/functions > /tmp/functions
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | awk '{print $2}')
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | awk '{print $2}')
export confluent_profile confluent_mgr
echo "Running https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.sh"
/usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
run_remote onboot.sh
while :; do
@@ -1,9 +1,9 @@
function set_confluent_vars() {
if [ -z "$mgr" ]; then
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$profile" ]; then
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
@@ -12,7 +12,7 @@ fetch_remote() {
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -25,11 +25,11 @@ run_remote() {
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
@@ -51,11 +51,11 @@ run_remote_python() {
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$mgr/confluent-public/os/$profile/scripts/
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
/usr/libexec/platform-python $*
retcode=$?
@@ -72,7 +72,7 @@ run_remote_config() {
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
@@ -103,10 +103,10 @@ dynamic behavior and replace with static configuration.
<source>
<![CDATA[
#!/bin/sh
mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl $proto://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/pre.sh
curl $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/pre.sh
. /tmp/pre.sh
]]>
</source>
@@ -119,13 +119,13 @@ curl $proto://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/pre.sh
<source>
<![CDATA[
#!/bin/sh
mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl $proto://$mgr/confluent-public/os/$profile/scripts/prechroot.sh > /tmp/prechroot.sh
curl $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/prechroot.sh > /tmp/prechroot.sh
. /tmp/prechroot.sh
curl -f $proto://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/etc/confluent/firstboot.sh
curl -f $proto://$mgr/confluent-public/os/$profile/scripts/post.sh > /mnt/etc/confluent/post.sh
curl -f $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /mnt/etc/confluent/firstboot.sh
curl -f $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /mnt/etc/confluent/post.sh
chmod +x /mnt/etc/confluent/firstboot.sh
chmod +x /mnt/etc/confluent/post.sh
cp /mnt/etc/confluent/post.sh /mnt/var/adm/autoinstall/scripts/
@@ -1,20 +1,27 @@
#!/bin/sh
#!/bin/bash
# This script runs at the end of the final boot, updating status
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
. /etc/confluent/functions
for i in /etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname
done
systemctl restart sshd
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot
run_remote_parts firstboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot
run_remote_config firstboot.d
curl --capath /etc/confluent/tls -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -f -X POST -d "status: complete" https://$mgr/confluent-api/self/updatestatus
curl --capath /etc/confluent/tls -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -f -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -1,12 +1,23 @@
function set_confluent_vars() {
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
run_remote() {
set_confluent_vars
requestedcmd="'$*'"
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
curl -f -sS https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
mkdir -p $(dirname $1)
curl -f -sS https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
@@ -18,14 +29,41 @@ run_remote() {
return $retcode
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(/usr/bin/python3 $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(/usr/bin/python3 $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
unset confluentscripttmpdir
}
run_remote_python() {
echo
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$mgr/confluent-public/os/$profile/scripts/
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
curl -f -sS https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
mkdir -p $(dirname $1)
curl -f -sS https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
python3 $*
retcode=$?
@@ -42,7 +80,7 @@ run_remote_config() {
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
python3 $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
python3 $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# This script runs at the end of install in the installed system
# but still under the installer kernel.
@@ -9,16 +9,18 @@
# If there are dependencies on the kernel (drivers or special filesystems)
# then firstboot.sh would be the script to customize.
mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
chmod 700 /etc/confluent
chmod og-rwx /etc/confluent/*
export mgr profile nodename
export confluent_mgr confluent_profile nodename
. /etc/confluent/functions
# This will induce server side processing of the syncfile contents if
@@ -28,10 +30,10 @@ run_remote_python syncfileclient
run_remote post.custom
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
run_remote_parts post
run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post
run_remote_config post.d
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -1,8 +1,10 @@
#!/bin/sh
#!/bin/bash
# This script runs before the installer executes, and sets up ssh during install as well
# as rewriting the autoyast file with any substitutions prior to it being evaluated for real
exec >> /tmp/confluent-pre.log
exec 2>> /tmp/confluent-pre.log
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
rootpw=$(grep rootpassword: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
if [ "$rootpw" = "null" ]; then
@@ -21,12 +23,12 @@ cat /ssh/*pubkey > ~/.ssh/authorized_keys 2>/dev/null
ssh-keygen -A
for i in /etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$mgr/confluent-api/self/sshcert > $certname
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname
echo HostKey ${i%.pub} >> /etc/ssh/sshd_config
echo HostCertificate $certname >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /tmp/functions
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
ntpcfg=""
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
@@ -4,10 +4,10 @@
# is still running, with the to-be-booted system mounted in /mnt
# carry over deployment configuration and api key for OS install action
mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export mgr profile nodename
export confluent_mgr confluent_profile nodename
mkdir -p /mnt/etc/confluent
chmod 700 /mnt/etc/confluent
cp /tmp/functions /mnt/etc/confluent/
@@ -27,4 +27,6 @@ echo Port 2222 >> /etc/ssh/sshd_config
echo Match LocalPort 22 >> /etc/ssh/sshd_config
echo " ChrootDirectory /mnt" >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
mkdir -p /mnt/var/log/confluent
cp /tmp/confluent*log /mnt/var/log/confluent
@@ -28,7 +28,7 @@ echo /usr/lib/ssh/ssh-keysign root:root 4711 >> /mnt/etc/permissions.local
chmod 4711 /mnt/usr/lib/ssh/ssh-keysign
# Download list of nodes from confluent, and put it into shosts.equiv (for most users) and .shosts (for root)
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
cp /tmp/allnodes /mnt/root/.shosts
cp /tmp/allnodes /mnt/etc/ssh/shosts.equiv
@@ -1,7 +1,7 @@
#!/bin/bash
deploycfg=/custom-installation/confluent/confluent.deploycfg
mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
profile=$(grep ^profile: $deploycfg|awk '{print $2}')
export deploycfg mgr profile
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/post.sh
confluent_mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: $deploycfg|awk '{print $2}')
export deploycfg confluent_mgr confluent_profile
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/post.sh
. /tmp/post.sh
@@ -1,6 +1,6 @@
#!/bin/bash
deploycfg=/custom-installation/confluent/confluent.deploycfg
mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
profile=$(grep ^profile: $deploycfg|awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/pre.sh
confluent_mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: $deploycfg|awk '{print $2}')
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/pre.sh
. /tmp/pre.sh
@@ -7,12 +7,12 @@ if [ ! -z "$rootpw" -a "$rootpw" != "null" ]; then
echo root:$rootpw | chpasswd -e
fi
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
hostnamectl set-hostname $(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
touch /etc/cloud/cloud-init.disabled
source /etc/confluent/functions
run_remote_parts firstboot
run_remote_config firstboot
curl --capath /etc/confluent/tls -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -X POST -d "status: complete" https://$mgr/confluent-api/self/updatestatus
run_remote_parts firstboot.d
run_remote_config firstboot.d
curl --capath /etc/confluent/tls -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -1,9 +1,9 @@
function set_confluent_vars() {
if [ -z "$mgr" ]; then
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$profile" ]; then
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
@@ -13,14 +13,14 @@ fetch_remote() {
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
run_remote_parts() {
scriptlist=$(python3 /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1.d/$script
run_remote $1/$script
done
}
@@ -33,12 +33,12 @@ run_remote() {
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
@@ -60,12 +60,12 @@ run_remote_python() {
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$mgr/confluent-public/os/$profile/scripts/
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
python3 $*
retcode=$?
@@ -82,7 +82,7 @@ run_remote_config() {
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
python3 $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
python3 $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
@@ -20,8 +20,8 @@ echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /target/etc/confluent/firstboot.sh
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /target/etc/confluent/functions
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /target/etc/confluent/firstboot.sh
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /target/etc/confluent/functions
source /target/etc/confluent/functions
chmod +x /target/etc/confluent/firstboot.sh
cp /tmp/allnodes /target/root/.shosts
@@ -42,7 +42,7 @@ if [ "$textcons" = "true" ] && ! grep console= /proc/cmdline > /dev/null; then
updategrub=1
fi
fi
kargs=$(curl https://$mgr/confluent-public/os/$profile/profile.yaml | grep ^installedargs: | sed -e 's/#.*//')
kargs=$(curl https://$confluent_mgr/confluent-public/os/$confluent_profile/profile.yaml | grep ^installedargs: | sed -e 's/#.*//')
if [ ! -z "$kargs" ]; then
sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 '"${kargs}"'"/' /target/etc/default/grub
fi
@@ -66,7 +66,7 @@ kill -HUP $(cat /run/sshd.pid)
cat /target/etc/confluent/tls/*.pem > /target/etc/confluent/ca.pem
cat /target/etc/confluent/tls/*.pem > /etc/confluent/ca.pem
chroot /target bash -c "source /etc/confluent/functions; run_remote_python syncfileclient"
chroot /target bash -c "source /etc/confluent/functions; run_remote_parts post"
chroot /target bash -c "source /etc/confluent/functions; run_remote_parts post.d"
source /target/etc/confluent/functions
run_remote_config post
@@ -15,7 +15,7 @@ apikey=$(cat /custom-installation/confluent/confluent.apikey)
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
keyfile=${pubkey%.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
echo HostKey $keyfile >> /etc/ssh/sshd_config.d/confluent.conf
echo HostCertificate $certfile >> /etc/ssh/sshd_config.d/confluent.conf
done
@@ -23,7 +23,7 @@ echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/confluent.conf
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/confluent.conf
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/confluent.conf
systemctl restart sshd
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
curl -f https://$mgr/confluent-public/os/$profile/scripts/getinstalldisk > /custom-installation/getinstalldisk
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /custom-installation/getinstalldisk
python3 /custom-installation/getinstalldisk
sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml
+8 -2
View File
@@ -82,9 +82,15 @@ int main(int argc, char* argv[]) {
if (sock < 0) continue;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
if (curr->ai_family == AF_INET) {
bind(sock, (struct sockaddr*)&net4bind, sizeof(struct sockaddr_in));
if (bind(sock, (struct sockaddr*)&net4bind, sizeof(struct sockaddr_in)) < 0) {
fprintf(stderr, "Unable to bind port 302\n");
exit(1);
}
} else if (curr->ai_family == AF_INET6) {
bind(sock, (struct sockaddr*)&net6bind, sizeof(struct sockaddr_in6));
if (bind(sock, (struct sockaddr*)&net6bind, sizeof(struct sockaddr_in6)) < 0) {
fprintf(stderr, "Unable to bind port 302\n");
exit(1);
}
} else {
continue;
}
+3 -2
View File
@@ -15,6 +15,7 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
import confluent.tlvdata as tlvdata
try:
@@ -107,11 +108,11 @@ def show_collective():
if 'active' in res['collective']:
if res['collective']['active']:
print('Active collective members:')
for member in res['collective']['active']:
for member in sortutil.natural_sort(res['collective']['active']):
print(' {0}'.format(member))
if res['collective']['offline']:
print('Offline collective members:')
for member in res['collective']['offline']:
for member in sortutil.natural_sort(res['collective']['offline']):
print(' {0}'.format(member))
else:
print('Run collective show on leader for more data')
+18 -16
View File
@@ -361,22 +361,24 @@ def osimport(imagefile):
print('Importing from {0} to {1}'.format(imagefile, rsp['target']))
else:
print(repr(rsp))
while importing:
for rsp in c.read('/deployment/importing/{0}'.format(shortname)):
if 'progress' in rsp:
sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'],
rsp['progress']))
if rsp['phase'] == 'complete':
importing = False
sys.stdout.write('\n')
for profile in rsp['profiles']:
print('Deployment profile created: {0}'.format(profile))
sys.stdout.flush()
else:
print(repr(rsp))
time.sleep(0.5)
if shortname:
list(c.delete('/deployment/importing/{0}'.format(shortname)))
try:
while importing:
for rsp in c.read('/deployment/importing/{0}'.format(shortname)):
if 'progress' in rsp:
sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'],
rsp['progress']))
if rsp['phase'] == 'complete':
importing = False
sys.stdout.write('\n')
for profile in rsp['profiles']:
print('Deployment profile created: {0}'.format(profile))
sys.stdout.flush()
else:
print(repr(rsp))
time.sleep(0.5)
finally:
if shortname:
list(c.delete('/deployment/importing/{0}'.format(shortname)))
if __name__ == '__main__':
main(sys.argv)
+202 -66
View File
@@ -23,9 +23,11 @@ import confluent.noderange as noderange
import confluent.tlvdata as tlvdata
import confluent.util as util
import eventlet
import eventlet.greenpool as greenpool
import eventlet.green.socket as socket
import eventlet.green.ssl as ssl
import eventlet.green.threading as threading
import confluent.sortutil as sortutil
import greenlet
import random
import time
@@ -41,6 +43,8 @@ currentleader = None
follower = None
retrythread = None
failovercheck = None
initting = True
reassimilate = None
class ContextBool(object):
def __init__(self):
@@ -58,7 +62,7 @@ class ContextBool(object):
connecting = ContextBool()
leader_init = ContextBool()
def connect_to_leader(cert=None, name=None, leader=None):
def connect_to_leader(cert=None, name=None, leader=None, remote=None):
global currentleader
global follower
if leader is None:
@@ -66,7 +70,7 @@ def connect_to_leader(cert=None, name=None, leader=None):
log.log({'info': 'Attempting connection to leader {0}'.format(leader),
'subsystem': 'collective'})
try:
remote = connect_to_collective(cert, leader)
remote = connect_to_collective(cert, leader, remote)
except socket.error as e:
log.log({'error': 'Collective connection attempt to {0} failed: {1}'
''.format(leader, str(e)),
@@ -116,7 +120,7 @@ def connect_to_leader(cert=None, name=None, leader=None):
follower.kill()
cfm.stop_following()
follower = None
if follower:
if follower is not None:
follower.kill()
cfm.stop_following()
follower = None
@@ -156,15 +160,20 @@ def connect_to_leader(cert=None, name=None, leader=None):
raise
currentleader = leader
#spawn this as a thread...
remote.settimeout(90)
follower = eventlet.spawn(follow_leader, remote, leader)
return True
def follow_leader(remote, leader):
global currentleader
global retrythread
global follower
cleanexit = False
newleader = None
try:
cfm.follow_channel(remote)
exitcause = cfm.follow_channel(remote)
newleader = exitcause.get('newleader', None)
except greenlet.GreenletExit:
cleanexit = True
finally:
@@ -172,20 +181,39 @@ def follow_leader(remote, leader):
log.log({'info': 'Previous following cleanly closed',
'subsystem': 'collective'})
return
if newleader:
log.log(
{'info': 'Previous leader directed us to join new leader {}'.format(newleader)})
if connect_to_leader(None, get_myname(), newleader):
return
log.log({'info': 'Current leader ({0}) has disappeared, restarting '
'collective membership'.format(leader), 'subsystem': 'collective'})
# The leader has folded, time to startup again...
follower = None
cfm.stop_following()
currentleader = None
eventlet.spawn_n(start_collective)
if retrythread is None: # start a recovery
retrythread = eventlet.spawn_after(
random.random(), start_collective)
def create_connection(member):
remote = None
try:
remote = socket.create_connection((member, 13001), 2)
remote.settimeout(15)
# TLS cert validation is custom and will not pass normal CA vetting
# to override completely in the right place requires enormous effort, so just defer until after connect
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE, keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
except Exception as e:
return member, e
return member, remote
def connect_to_collective(cert, member):
remote = socket.create_connection((member, 13001))
# TLS cert validation is custom and will not pass normal CA vetting
# to override completely in the right place requires enormous effort, so just defer until after connect
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE, keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
def connect_to_collective(cert, member, remote=None):
if remote is None:
_, remote = create_connection(member)
if isinstance(remote, Exception):
raise remote
if cert:
fprint = cert
else:
@@ -216,6 +244,8 @@ def get_myname():
def handle_connection(connection, cert, request, local=False):
global currentleader
global retrythread
global initting
connection.settimeout(5)
operation = request['operation']
if cert:
cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
@@ -229,9 +259,9 @@ def handle_connection(connection, cert, request, local=False):
'enabled on this '
'system'}})
return
if follower:
if follower is not None:
linfo = cfm.get_collective_member_by_address(currentleader)
remote = socket.create_connection((currentleader, 13001))
remote = socket.create_connection((currentleader, 13001), 15)
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
@@ -307,7 +337,7 @@ def handle_connection(connection, cert, request, local=False):
return
host = request['server']
try:
remote = socket.create_connection((host, 13001))
remote = socket.create_connection((host, 13001), 15)
# This isn't what it looks like. We do CERT_NONE to disable
# openssl verification, but then use the invitation as a
# shared secret to validate the certs as part of the join
@@ -366,6 +396,11 @@ def handle_connection(connection, cert, request, local=False):
tlvdata.send(connection, {'error': 'Invalid token'})
connection.close()
return
if not list(cfm.list_collective()):
# First enrollment of a collective, since the collective doesn't
# quite exist, then set initting false to let the enrollment action
# drive this particular initialization
initting = False
myrsp = base64.b64encode(myrsp)
fprint = util.get_fingerprint(cert)
myfprint = util.get_fingerprint(mycert)
@@ -399,6 +434,36 @@ def handle_connection(connection, cert, request, local=False):
'transaction count',
'txcount': cfm._txcount,})
return
if cfm.cfgstreams and request['txcount'] == cfm._txcount:
try:
cfm.check_quorum()
tlvdata.send(connection,
{'error': 'Refusing to be assimilated as I am a leader with quorum',
'txcount': cfm._txcount,})
return
except exc.DegradedCollective:
followcount = request.get('followcount', None)
myfollowcount = len(list(cfm.cfgstreams))
if followcount is not None:
if followcount < myfollowcount:
tlvdata.send(connection,
{'error': 'Refusing to be assimilated by leader with fewer followers',
'txcount': cfm._txcount,})
return
elif followcount == myfollowcount:
myname = sortutil.naturalize_string(get_myname())
if myname < sortutil.naturalize_string(request['name']):
tlvdata.send(connection,
{'error': 'Refusing, my name is better',
'txcount': cfm._txcount,})
return
if follower is not None and not follower.dead:
tlvdata.send(
connection,
{'error': 'Already following, assimilate leader first',
'leader': currentleader})
connection.close()
return
if connecting.active:
# don't try to connect while actively already trying to connect
tlvdata.send(connection, {'status': 0})
@@ -413,10 +478,15 @@ def handle_connection(connection, cert, request, local=False):
return
log.log({'info': 'Connecting in response to assimilation',
'subsystem': 'collective'})
eventlet.spawn_n(connect_to_leader, None, None,
leader=connection.getpeername()[0])
if cfm.cfgstreams:
retire_as_leader(connection.getpeername())
tlvdata.send(connection, {'status': 0})
newleader = connection.getpeername()[0]
connection.close()
if not connect_to_leader(None, None, leader=newleader):
if retrythread is None:
retrythread = eventlet.spawn_after(random.random(),
start_collective)
if 'getinfo' == operation:
drone = request['name']
droneinfo = cfm.get_collective_member(drone)
@@ -441,7 +511,7 @@ def handle_connection(connection, cert, request, local=False):
connection.close()
return
myself = connection.getsockname()[0]
if connecting.active:
if connecting.active or initting:
tlvdata.send(connection, {'error': 'Connecting right now',
'backoff': True})
connection.close()
@@ -460,12 +530,15 @@ def handle_connection(connection, cert, request, local=False):
'should assimilate me, connecting..',
'txcount': cfm._txcount})
log.log({'info': 'Connecting to leader due to superior '
'transaction count', 'subsystem': collective})
eventlet.spawn_n(connect_to_leader, None, None,
connection.getpeername()[0])
'transaction count', 'subsystem': 'collective'})
connection.close()
if not connect_to_leader(
None, None, connection.getpeername()[0]):
if retrythread is None:
retrythread = eventlet.spawn_after(5 + random.random(),
start_collective)
return
if retrythread:
if retrythread is not None:
retrythread.cancel()
retrythread = None
with leader_init:
@@ -480,10 +553,13 @@ def handle_connection(connection, cert, request, local=False):
connection.sendall(cfgdata)
#tlvdata.send(connection, {'tenants': 0}) # skip the tenants for now,
# so far unused anyway
connection.settimeout(90)
if not cfm.relay_slaved_requests(drone, connection):
if not retrythread: # start a recovery if everyone else seems
log.log({'info': 'All clients have disconnected, starting recovery process',
'subsystem': 'collective'})
if retrythread is None: # start a recovery if everyone else seems
# to have disappeared
retrythread = eventlet.spawn_after(30 + random.random(),
retrythread = eventlet.spawn_after(5 + random.random(),
start_collective)
# ok, we have a connecting member whose certificate checks out
# He needs to bootstrap his configuration and subscribe it to updates
@@ -501,15 +577,17 @@ def populate_collinfo(collinfo):
collinfo['offline'].append(member)
def try_assimilate(drone):
def try_assimilate(drone, followcount, remote):
global retrythread
try:
remote = connect_to_collective(None, drone)
remote = connect_to_collective(None, drone, remote)
except socket.error:
# Oh well, unable to connect, hopefully the rest will be
# in order
return
tlvdata.send(remote, {'collective': {'operation': 'assimilate',
'name': get_myname(),
'followcount': followcount,
'txcount': cfm._txcount}})
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
@@ -520,24 +598,33 @@ def try_assimilate(drone):
'No answer from {0} while trying to assimilate'.format(
drone),
'subsystem': 'collective'})
return
return True
if 'txcount' in answer:
log.log({'info': 'Deferring to {0} due to transaction count'.format(
log.log({'info': 'Deferring to {0} due to target being a better leader'.format(
drone), 'subsystem': 'collective'})
connect_to_leader(None, None, leader=remote.getpeername()[0])
return
retire_as_leader(drone)
if not connect_to_leader(None, None, leader=remote.getpeername()[0]):
if retrythread is None:
retrythread = eventlet.spawn_after(random.random(),
start_collective)
return False
if 'leader' in answer:
# Will wait for leader to see about assimilation
return True
if 'error' in answer:
log.log({
'error': 'Error encountered while attempting to '
'assimilate {0}: {1}'.format(drone, answer['error']),
'subsystem': 'collective'})
return
return True
log.log({'info': 'Assimilated {0} into collective'.format(drone),
'subsystem': 'collective'})
return True
def get_leader(connection):
if currentleader is None or connection.getpeername()[0] == currentleader:
# cancel retry if a retry is pending
if currentleader is None:
msg = 'Becoming leader as no leader known'
else:
@@ -547,36 +634,62 @@ def get_leader(connection):
become_leader(connection)
return currentleader
def retire_as_leader():
def retire_as_leader(newleader=None):
global currentleader
cfm.stop_leading()
cfm.stop_leading(newleader)
currentleader = None
def become_leader(connection):
global currentleader
global follower
global retrythread
global reassimilate
log.log({'info': 'Becoming leader of collective',
'subsystem': 'collective'})
if follower:
if follower is not None:
follower.kill()
cfm.stop_following()
follower = None
if retrythread:
if retrythread is not None:
retrythread.cancel()
retrythread = None
currentleader = connection.getsockname()[0]
skipaddr = connection.getpeername()[0]
if _assimilate_missing(skipaddr):
schedule_rebalance()
if reassimilate is not None:
reassimilate.kill()
reassimilate = eventlet.spawn(reassimilate_missing)
def reassimilate_missing():
eventlet.sleep(30)
while cfm.cfgstreams and _assimilate_missing():
eventlet.sleep(30)
def _assimilate_missing(skipaddr=None):
connecto = []
myname = get_myname()
skipem = set(cfm.cfgstreams)
numfollowers = len(skipem)
skipem.add(currentleader)
skipem.add(skipaddr)
if skipaddr is not None:
skipem.add(skipaddr)
for member in cfm.list_collective():
dronecandidate = cfm.get_collective_member(member)['address']
if dronecandidate in skipem or member == myname:
if dronecandidate in skipem or member == myname or member in skipem:
continue
eventlet.spawn_n(try_assimilate, dronecandidate)
schedule_rebalance()
connecto.append(dronecandidate)
if not connecto:
return True
conpool = greenpool.GreenPool(64)
connections = conpool.imap(create_connection, connecto)
for ent in connections:
member, remote = ent
if isinstance(remote, Exception):
continue
if not try_assimilate(member, numfollowers, remote):
return False
return True
def startup():
@@ -589,6 +702,11 @@ def startup():
def check_managers():
global failovercheck
if not follower:
try:
cfm.check_quorum()
except exc.DegradedCollective:
failovercheck = None
return
c = cfm.ConfigManager(None)
collinfo = {}
populate_collinfo(collinfo)
@@ -624,44 +742,62 @@ def check_managers():
continue
c.set_node_attributes({node: {'collective.manager': {'value': targets[0]}}})
availmanagers[targets[0]] += 1
_assimilate_missing()
failovercheck = None
def schedule_rebalance():
global failovercheck
if not failovercheck:
failovercheck = True
failovercheck = eventlet.spawn_after(10, check_managers)
def start_collective():
cfm.membership_callback = schedule_rebalance
global follower
global retrythread
if follower:
follower.kill()
cfm.stop_following()
follower = None
global initting
initting = True
retrythread = None
try:
if cfm.cfgstreams:
cfm.check_quorum()
# Do not start if we have quorum and are leader
cfm.membership_callback = schedule_rebalance
if follower is not None:
initting = False
return
except exc.DegradedCollective:
try:
if cfm.cfgstreams:
cfm.check_quorum()
# Do not start if we have quorum and are leader
return
except exc.DegradedCollective:
pass
if leader_init.active: # do not start trying to connect if we are
# xmitting data to a follower
return
myname = get_myname()
connecto = []
for member in sorted(list(cfm.list_collective())):
if member == myname:
continue
if cfm.cfgleader is None:
cfm.stop_following(True)
ldrcandidate = cfm.get_collective_member(member)['address']
connecto.append(ldrcandidate)
conpool = greenpool.GreenPool(64)
connections = conpool.imap(create_connection, connecto)
for ent in connections:
member, remote = ent
if isinstance(remote, Exception):
continue
if follower is None:
log.log({'info': 'Performing startup attempt to {0}'.format(
member), 'subsystem': 'collective'})
if not connect_to_leader(name=myname, leader=member, remote=remote):
remote.close()
else:
remote.close()
except Exception as e:
pass
if leader_init.active: # do not start trying to connect if we are
# xmitting data to a follower
return
myname = get_myname()
for member in sorted(list(cfm.list_collective())):
if member == myname:
continue
if cfm.cfgleader is None:
cfm.stop_following(True)
ldrcandidate = cfm.get_collective_member(member)['address']
log.log({'info': 'Performing startup attempt to {0}'.format(
ldrcandidate), 'subsystem': 'collective'})
if connect_to_leader(name=myname, leader=ldrcandidate):
break
else:
retrythread = eventlet.spawn_after(30 + random.random(),
start_collective)
finally:
if retrythread is None and follower is None:
retrythread = eventlet.spawn_after(5 + random.random(),
start_collective)
initting = False
@@ -409,7 +409,10 @@ def _push_rpc(stream, payload):
return True
except Exception:
logException()
del cfgstreams[stream]
try:
del cfgstreams[stream]
except KeyError:
pass
if membership_callback:
membership_callback()
stream.close()
@@ -736,9 +739,14 @@ def stop_following(replacement=None):
pass
cfgleader = replacement
def stop_leading():
def stop_leading(newleader=None):
rpcpayload = None
if newleader is not None:
rpcpayload = msgpack.packb({'newleader': newleader}, use_bin_type=False)
for stream in list(cfgstreams):
try:
if rpcpayload is not None:
_push_rpc(cfgstreams[stream], rpcpayload)
cfgstreams[stream].close()
except Exception:
pass
@@ -824,6 +832,8 @@ def follow_channel(channel):
rpc = msgpack.unpackb(rpc, raw=False)
if 'txcount' in rpc:
_txcount = rpc['txcount']
if 'newleader' in rpc:
return rpc
if 'function' in rpc:
if not (rpc['function'].startswith('_true') or rpc['function'].startswith('_rpc')):
raise Exception("Received unsupported function call: {0}".format(rpc['function']))
@@ -853,6 +863,7 @@ def follow_channel(channel):
stop_following(None)
else:
stop_following(True)
return {}
def add_collective_member(name, address, fingerprint):
+11 -6
View File
@@ -66,19 +66,22 @@ def get_buffer_output(nodename):
instream = _bufferdaemon.stdout
if not isinstance(nodename, bytes):
nodename = nodename.encode('utf8')
outdata = b''
outdata = bytearray()
with _bufferlock:
out.write(struct.pack('I', len(nodename)))
out.write(nodename)
out.flush()
select.select((instream,), (), (), 30)
while not outdata or outdata[-1]:
chunk = instream.read(128)
try:
chunk = os.read(instream.fileno(), 128)
except IOError:
chunk = None
if chunk:
outdata += chunk
outdata.extend(chunk)
else:
select.select((instream,), (), (), 0)
return outdata[:-1]
return bytes(outdata[:-1])
def send_output(nodename, output):
@@ -583,18 +586,20 @@ def _start_tenant_sessions(cfm):
cfm.watch_nodecollection(_nodechange)
def start_console_sessions():
def initialize():
global _tracelog
global _bufferdaemon
global _bufferlock
_bufferlock = semaphore.Semaphore()
_tracelog = log.Logger('trace')
_bufferdaemon = subprocess.Popen(
['/opt/confluent/bin/vtbufferd'], stdin=subprocess.PIPE,
['/opt/confluent/bin/vtbufferd'], bufsize=0, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
fl = fcntl.fcntl(_bufferdaemon.stdout.fileno(), fcntl.F_GETFL)
fcntl.fcntl(_bufferdaemon.stdout.fileno(),
fcntl.F_SETFL, fl | os.O_NONBLOCK)
def start_console_sessions():
configmodule.hook_new_configmanagers(_start_tenant_sessions)
+17 -2
View File
@@ -57,9 +57,11 @@ except ImportError:
# Only required for collective mode
crypto = None
import confluent.util as util
import eventlet
import eventlet.greenpool as greenpool
import eventlet.green.ssl as ssl
import eventlet.queue as queue
import eventlet.semaphore as semaphore
import itertools
import msgpack
import os
@@ -745,6 +747,12 @@ def abbreviate_noderange(configmanager, inputdata, operation):
return (msg.KeyValueData({'noderange': noderange.ReverseNodeRange(inputdata['nodes'], configmanager).noderange}),)
def _keepalivefn(connection, xmitlock):
while True:
eventlet.sleep(30)
with xmitlock:
connection.sendall(b'\x00\x00\x00\x00\x00\x00\x00\x01\x00')
def handle_dispatch(connection, cert, dispatch, peername):
cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
if not util.cert_matches(
@@ -757,6 +765,8 @@ def handle_dispatch(connection, cert, dispatch, peername):
# under 0x20 or so.
connection.close()
return
xmitlock = semaphore.Semaphore()
keepalive = eventlet.spawn(_keepalivefn, connection, xmitlock)
dispatch = msgpack.unpackb(dispatch[2:], raw=False)
configmanager = cfm.ConfigManager(dispatch['tenant'])
nodes = dispatch['nodes']
@@ -796,9 +806,12 @@ def handle_dispatch(connection, cert, dispatch, peername):
configmanager=configmanager,
inputdata=inputdata))
for res in itertools.chain(*passvalues):
_forward_rsp(connection, res)
with xmitlock:
_forward_rsp(connection, res)
except Exception as res:
_forward_rsp(connection, res)
with xmitlock:
_forward_rsp(connection, res)
keepalive.kill()
connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00')
@@ -1104,6 +1117,8 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
a['name']))
return
rsp += nrsp
if rsp == b'\x00':
continue
try:
rsp = msg.msg_deserialize(rsp)
except Exception:
+1 -1
View File
@@ -88,7 +88,7 @@ class CredServer(object):
client.close()
return
echotoken = util.stringify(client.recv(tlv[1]))
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.apiarmed': ''}}
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.sealedapikey': '', 'deployment.apiarmed': ''}}
if apiarmed == 'continuous':
del cfgupdate[nodename]['deployment.apiarmed']
self.cfm.set_node_attributes(cfgupdate)
+20 -19
View File
@@ -1,4 +1,4 @@
# Copyright 2016-2017 Lenovo
# Copyright 2016-2021 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -252,6 +252,8 @@ def send_discovery_datum(info):
yield msg.KeyValueData({'lowest_u': info['u']})
if 'hostname' in info:
yield msg.KeyValueData({'hostname': info['hostname']})
if 'modelname' in info:
yield msg.KeyValueData({'modelname': info['modelname']})
def _info_matches(info, criteria):
@@ -910,8 +912,11 @@ def get_nodename_from_chained_smms(cfg, handler, info):
nodename = newnodename
return nodename
def get_node_by_uuid(uuid):
return nodes_by_uuid.get(uuid, None)
def get_node_by_uuid_or_mac(uuidormac):
node = pxe.macmap.get(uuidormac, None)
if node is not None:
return node
return nodes_by_uuid.get(uuidormac, None)
def get_nodename_from_enclosures(cfg, info):
nodename = None
@@ -1303,7 +1308,7 @@ def start_detection():
if rechecker is None:
rechecktime = util.monotonic_time() + 900
rechecker = eventlet.spawn_after(900, _periodic_recheck, cfg)
eventlet.spawn_n(ssdp.snoop, None, None, ssdp, get_node_by_uuid)
eventlet.spawn_n(ssdp.snoop, None, None, ssdp, get_node_by_uuid_or_mac)
def stop_autosense():
for watcher in list(autosensors):
@@ -1322,42 +1327,38 @@ known_pxe_uuids = {}
def _map_unique_ids(nodes=None):
global nodes_by_uuid
global nodes_by_fprint
global known_pxe_uuids
# Map current known ids based on uuid and fingperprints for fast lookup
cfg = cfm.ConfigManager(None)
if nodes is None:
nodes_by_uuid = {}
nodes_by_fprint = {}
known_pxe_uuids = {}
nodes = cfg.list_nodes()
bigmap = cfg.get_node_attributes(nodes,
('id.uuid',
'pubkeys.tls_hardwaremanager'))
uuid_by_nodes = {}
fprint_by_nodes = {}
for uuid in nodes_by_uuid:
if not uuid_is_valid(uuid):
continue
for uuid in list(nodes_by_uuid):
node = nodes_by_uuid[uuid]
if node in bigmap:
uuid_by_nodes[node] = uuid
for fprint in nodes_by_fprint:
del nodes_by_uuid[uuid]
for uuid in list(known_pxe_uuids):
node = known_pxe_uuids[uuid]
if node in bigmap:
del known_pxe_uuids[uuid]
for fprint in list(nodes_by_fprint):
node = nodes_by_fprint[fprint]
if node in bigmap:
fprint_by_nodes[node] = fprint
del nodes_by_fprint[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', '').lower()
if uuid_is_valid(uuid):
nodes_by_uuid[uuid] = node
known_pxe_uuids[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:
nodes_by_uuid[uuid] = known_pxe_uuids[uuid]
if __name__ == '__main__':
@@ -63,6 +63,15 @@ class NodeHandler(immhandler.NodeHandler):
# This is not adequate for being satisfied
return bool(info.get('attributes', {}))
def scan(self):
c = webclient.SecureHTTPConnection(self.ipaddr, 443,
verifycallback=self.validate_cert)
i = c.grab_json_response('/api/providers/logoninfo')
modelname = i.get('items', [{}])[0].get('machine_name', None)
if modelname:
self.info['modelname'] = modelname
super(NodeHandler, self).scan()
def preconfig(self, possiblenode):
self.tmpnodename = possiblenode
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
@@ -252,8 +252,9 @@ def proxydhcp():
optidx = rq.index(b'\x63\x82\x53\x63') + 4
except ValueError:
continue
hwlen = rq[2]
opts, disco = opts_to_dict(rq, optidx, 3)
disco['uuid']
disco['hwaddr'] = ':'.join(['{0:02x}'.format(x) for x in rq[28:28+hwlen]])
node = None
if disco.get('hwaddr', None) in macmap:
node = macmap[disco['hwaddr']]
@@ -261,7 +262,7 @@ def proxydhcp():
node = uuidmap[disco['uuid']]
if not node:
continue
hwlen = rq[2]
myipn = myipbypeer.get(rqv[28:28+hwlen].tobytes(), None)
if not myipn:
continue
@@ -275,6 +276,13 @@ def proxydhcp():
bootfile = b'confluent/x86_64/ipxe.efi'
elif disco['arch'] == 'bios-x86':
bootfile = b'confluent/x86_64/ipxe.kkpxe'
if len(bootfile) > 127:
log.log(
{'info': 'Boot offer cannot be made to {0} as the '
'profile name "{1}" is {2} characters longer than is supported '
'for this boot method.'.format(
node, profile, len(bootfile) - 127)})
continue
rpv[:240] = rqv[:240].tobytes()
rpv[0:1] = b'\x02'
rpv[108:108 + len(bootfile)] = bootfile
@@ -484,6 +492,13 @@ def check_reply(node, info, packet, sock, cfg, reqview):
)
if not isinstance(bootfile, bytes):
bootfile = bootfile.encode('utf8')
if len(bootfile) > 127:
log.log(
{'info': 'Boot offer cannot be made to {0} as the '
'profile name "{1}" is {2} characters longer than is supported '
'for this boot method.'.format(
node, profile, len(bootfile) - 127)})
return
repview[108:108 + len(bootfile)] = bootfile
repview[20:24] = myipn
gateway = None
@@ -180,11 +180,14 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
except Exception:
continue
for query in headline[-1].split('/'):
node = None
if query.startswith('uuid='):
curruuid = query.split('=', 1)[1].lower()
node = uuidlookup(curruuid)
if not node:
break
elif query.startswith('mac='):
currmac = query.split('=', 1)[1].lower()
node = uuidlookup(currmac)
if node:
# Do not bother replying to a node that
# we have no deployment activity
# planned for
+3 -2
View File
@@ -25,9 +25,10 @@ def deserialize_exc(msg):
return Exception(excd[1])
if excd[0] not in globals():
return Exception('Cannot deserialize: {0}'.format(repr(excd)))
if not issubclass(excd[0], ConfluentException):
classn = globals()[excd[0]]
if not issubclass(classn, ConfluentException):
return Exception('Cannot deserialize: {0}'.format(repr(excd)))
return globals(excd[0])(*excd[1])
return classn(*excd[1])
class ConfluentException(Exception):
apierrorcode = 500
+88
View File
@@ -47,6 +47,7 @@ try:
import urlparse
except ModuleNotFoundError:
import urllib.parse as urlparse
import eventlet.websocket
import eventlet.wsgi
#scgi = eventlet.import_patched('flup.server.scgi')
tlvdata = confluent.tlvdata
@@ -345,6 +346,7 @@ def _authorize_request(env, operation):
auditlog.log(auditmsg)
if 'csrftoken' in httpsessions[sessid]:
authinfo['authtoken'] = httpsessions[sessid]['csrftoken']
httpsessions[sessid]['cfgmgr'] = authdata[1]
return authinfo
elif authdata is None:
return {'code': 401}
@@ -379,9 +381,93 @@ def _assign_consessionid(consolesession):
'expiry': time.time() + 60}
return sessid
@eventlet.websocket.WebSocketWSGI.configured(
supported_protocols=['confluent.console'])
def wsock_handler(ws):
sessid = ws.wait()
sessid = sessid.replace('ConfluentSessionId:', '')
sessid = sessid[:-1]
currsess = httpsessions.get(sessid, None)
if not currsess:
return
authtoken = ws.wait()
authtoken = authtoken.replace('ConfluentAuthToken:', '')
authtoken = authtoken[:-1]
if currsess['csrftoken'] != authtoken:
return
mythreadid = greenlet.getcurrent()
httpsessions[sessid]['inflight'].add(mythreadid)
name = httpsessions[sessid]['name']
authdata = auth.authorize(name, ws.path)
if not authdata:
return
if '/console/session' in ws.path or '/shell/sessions/' in ws.path:
#hard bake JSON into this path, do not support other incarnations
if '/console/session' in ws.path:
prefix, _, _ = ws.path.partition('/console/session')
shellsession = False
elif '/shell/sessions/' in ws.path:
prefix, _, _ = ws.path.partition('/shell/sessions')
shellsession = True
_, _, nodename = prefix.rpartition('/')
geom = ws.wait()
geom = geom[1:]
geom = json.loads(geom)
width = geom['width']
height = geom['height']
skipreplay = geom.get('skipreplay', False)
cfgmgr = httpsessions[sessid]['cfgmgr']
username = httpsessions[sessid]['name']
def datacallback(data):
if isinstance(data, dict):
data = json.dumps(data)
ws.send(u'!' + data)
else:
try:
data = data.decode('utf8')
except UnicodeDecodeError:
data = data.decode('cp437')
ws.send(u' ' + data)
try:
if shellsession:
consession = shellserver.ShellSession(
node=nodename, configmanager=cfgmgr,
username=username, skipreplay=skipreplay,
datacallback=datacallback, width=width, height=height
)
else:
consession = consoleserver.ConsoleSession(
node=nodename, configmanager=cfgmgr,
username=username, skipreplay=skipreplay,
datacallback=datacallback, width=width, height=height
)
except exc.NotFoundException:
return
clientmsg = ws.wait()
try:
while clientmsg is not None:
if clientmsg[0] == ' ':
consession.write(clientmsg[1:])
elif clientmsg[0] == '!':
cmd = json.loads(clientmsg[1:])
action = cmd.get('action', None)
if action == 'break':
consession.send_break()
elif action == 'resize':
consession.resize(
width=cmd['width'], height=cmd['height'])
elif clientmsg[0] == '?':
ws.send(u'?')
clientmsg = ws.wait()
finally:
consession.destroy()
def resourcehandler(env, start_response):
try:
if 'HTTP_SEC_WEBSOCKET_VERSION' in env:
for rsp in wsock_handler(env, start_response):
yield rsp
for rsp in resourcehandler_backend(env, start_response):
yield rsp
except Exception as e:
@@ -632,6 +718,8 @@ def resourcehandler_backend(env, start_response):
sessinfo = {'username': authorized['username']}
if 'authtoken' in authorized:
sessinfo['authtoken'] = authorized['authtoken']
if 'sessionid' in authorized:
sessinfo['sessionid'] = authorized['sessionid']
tlvdata.unicode_dictvalues(sessinfo)
yield json.dumps(sessinfo)
return
+15 -6
View File
@@ -244,7 +244,7 @@ def run(args):
_updatepidfile()
signal.signal(signal.SIGINT, terminate)
signal.signal(signal.SIGTERM, terminate)
collective.startup()
atexit.register(doexit)
if dbgif:
oumask = os.umask(0o077)
try:
@@ -258,18 +258,27 @@ def run(args):
except AttributeError:
pass # Windows...
os.umask(oumask)
collective.startup()
consoleserver.initialize()
http_bind_host, http_bind_port = _get_connector_config('http')
sock_bind_host, sock_bind_port = _get_connector_config('socket')
webservice = httpapi.HttpApi(http_bind_host, http_bind_port)
webservice.start()
disco.start_detection()
pxe.start_proxydhcp()
try:
sockservice = sockapi.SockApi(sock_bind_host, sock_bind_port)
sockservice.start()
except NameError:
pass
atexit.register(doexit)
webservice = httpapi.HttpApi(http_bind_host, http_bind_port)
webservice.start()
while len(list(configmanager.list_collective())) >= 2:
# If in a collective, stall automatic startup activity
# until we establish quorum
try:
configmanager.check_quorum()
break
except Exception:
eventlet.sleep(0.5)
disco.start_detection()
pxe.start_proxydhcp()
eventlet.sleep(1)
consoleserver.start_console_sessions()
while 1:
+172 -67
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2019 Lenovo
# Copyright 2016-2021 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -30,21 +30,32 @@
# 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
import eventlet.green.socket as socket
if __name__ == '__main__':
import sys
import confluent.config.configmanager as cfm
import confluent.snmputil as snmp
from confluent.networking.lldp import _handle_neighbor_query, get_fingerprint
from confluent.networking.netutil import get_switchcreds, list_switches, get_portnamemap
import eventlet.green.select as select
import eventlet.green.socket as socket
import os
import sys
import confluent.exceptions as exc
import confluent.log as log
import confluent.messages as msg
import confluent.snmputil as snmp
import confluent.util as util
from eventlet.greenpool import GreenPool
import eventlet.green.subprocess as subprocess
import fcntl
import eventlet
import eventlet.semaphore
import msgpack
import random
import re
webclient = eventlet.import_patched('pyghmi.util.webclient')
@@ -56,6 +67,8 @@ _apimacmap = {}
_macsbyswitch = {}
_nodesbymac = {}
_switchportmap = {}
_offloadevts = {}
_offloader = None
vintage = None
@@ -134,8 +147,8 @@ def _nodelookup(switch, ifname):
def _affluent_map_switch(args):
switch, password, user, cfm = args
kv = util.TLSCertVerifier(cfm, switch,
switch, password, user, cfgm = args
kv = util.TLSCertVerifier(cfgm, switch,
'pubkeys.tls_hardwaremanager').verify_cert
wc = webclient.SecureHTTPConnection(
switch, 443, verifycallback=kv, timeout=5)
@@ -164,6 +177,53 @@ def _affluent_map_switch(args):
else:
_nodesbymac[mac] = (nodename, nummacs)
def _offload_map_switch(switch, password, user):
if _offloader is None:
_start_offloader()
evtid = random.randint(0, 4294967295)
while evtid in _offloadevts:
evtid = random.randint(0, 4294967295)
_offloadevts[evtid] = eventlet.Event()
_offloader.stdin.write(msgpack.packb((evtid, switch, password, user),
use_bin_type=True))
_offloader.stdin.flush()
result = _offloadevts[evtid].wait()
del _offloadevts[evtid]
if len(result) == 2:
if result[0] == 1:
raise exc.deserialize_exc(result[1])
elif result[0] == 2:
raise Exception(result[1])
return result
def _start_offloader():
global _offloader
os.environ['PYTHONPATH'] = ':'.join(sys.path)
_offloader = subprocess.Popen(
[sys.executable, __file__, '-o'], bufsize=0, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
fl = fcntl.fcntl(_offloader.stdout.fileno(), fcntl.F_GETFL)
fcntl.fcntl(_offloader.stdout.fileno(),
fcntl.F_SETFL, fl | os.O_NONBLOCK)
eventlet.spawn_n(_recv_offload)
eventlet.sleep(0)
def _recv_offload():
upacker = msgpack.Unpacker(encoding='utf8')
instream = _offloader.stdout.fileno()
while True:
select.select([_offloader.stdout], [], [])
upacker.feed(os.read(instream, 128))
for result in upacker:
if result[0] not in _offloadevts:
print("Uh oh, unexpected event id... " + repr(result))
continue
_offloadevts[result[0]].send(result[1:])
eventlet.sleep(0)
def _map_switch_backend(args):
"""Manipulate portions of mac address map relevant to a given switch
@@ -183,7 +243,7 @@ def _map_switch_backend(args):
#
global _macmap
if len(args) == 4:
switch, password, user, cfm = args
switch, password, user, _ = args # 4th arg is for affluent only
if not user:
user = None
else:
@@ -194,6 +254,91 @@ def _map_switch_backend(args):
return _affluent_map_switch(args)
except Exception:
pass
mactobridge, ifnamemap, bridgetoifmap = _offload_map_switch(
switch, password, user)
maccounts = {}
bridgetoifvalid = False
for mac in mactobridge:
try:
ifname = ifnamemap[bridgetoifmap[mactobridge[mac]]]
bridgetoifvalid = True
except KeyError:
continue
if ifname not in maccounts:
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
if not bridgetoifvalid:
bridgetoifmap = {}
# Not a single mac address resolved to an interface index, chances are
# that the switch is broken, and the mactobridge is reporting ifidx
# instead of bridge port index
# try again, skipping the bridgetoifmap lookup
for mac in mactobridge:
try:
ifname = ifnamemap[mactobridge[mac]]
bridgetoifmap[mactobridge[mac]] = mactobridge[mac]
except KeyError:
continue
if ifname not in maccounts:
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
newmacs = {}
noaffluent.add(switch)
for mac in mactobridge:
# We want to merge it so that when a mac appears in multiple
# places, it is captured.
try:
ifname = ifnamemap[bridgetoifmap[mactobridge[mac]]]
except KeyError:
continue
if mac in _macmap:
_macmap[mac].append((switch, ifname, maccounts[ifname]))
else:
_macmap[mac] = [(switch, ifname, maccounts[ifname])]
if ifname in newmacs:
newmacs[ifname].append(mac)
else:
newmacs[ifname] = [mac]
nodename = _nodelookup(switch, ifname)
if nodename is not None:
if mac in _nodesbymac and _nodesbymac[mac][0] != 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)
else:
_nodesbymac[mac] = (nodename, maccounts[ifname])
_macsbyswitch[switch] = newmacs
def _snmp_map_switch_relay(rqid, switch, password, user):
try:
res = _snmp_map_switch(switch, password, user)
payload = msgpack.packb((rqid,) + res, use_bin_type=True)
try:
sys.stdout.buffer.write(payload)
except AttributeError:
sys.stdout.write(payload)
except exc.ConfluentException as e:
nestedexc = e.serialize()
payload = msgpack.packb((rqid, 1, nestedexc), use_bin_type=True)
try:
sys.stdout.buffer.write(payload)
except AttributeError:
sys.stdout.write(payload)
except Exception as e:
payload = msgpack.packb((rqid, 2, str(e)), use_bin_type=True)
try:
sys.stdout.buffer.write(payload)
except AttributeError:
sys.stdout.write(payload)
finally:
sys.stdout.flush()
def _snmp_map_switch(switch, password, user):
haveqbridge = False
mactobridge = {}
conn = snmp.Session(switch, password, user)
@@ -203,12 +348,12 @@ def _map_switch_backend(args):
oid, bridgeport = vb
if not bridgeport:
continue
oid = str(oid).rsplit('.', 6) # if 7, then oid[1] would be vlan id
oid = str(oid).rsplit('.', 6)
# if 7, then oid[1] would be vlan id
macaddr = '{0:02x}:{1:02x}:{2:02x}:{3:02x}:{4:02x}:{5:02x}'.format(
*([int(x) for x in oid[-6:]])
)
mactobridge[macaddr] = int(bridgeport)
noaffluent.add(switch)
if not haveqbridge:
for vb in conn.walk('1.3.6.1.2.1.17.4.3.1.2'):
oid, bridgeport = vb
@@ -250,62 +395,8 @@ def _map_switch_backend(args):
except ValueError:
# ifidx might be '', skip in such a case
continue
maccounts = {}
bridgetoifvalid = False
for mac in mactobridge:
try:
ifname = ifnamemap[bridgetoifmap[mactobridge[mac]]]
bridgetoifvalid = True
except KeyError:
continue
if ifname not in maccounts:
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
if not bridgetoifvalid:
bridgetoifmap = {}
# Not a single mac address resolved to an interface index, chances are
# that the switch is broken, and the mactobridge is reporting ifidx
# instead of bridge port index
# try again, skipping the bridgetoifmap lookup
for mac in mactobridge:
try:
ifname = ifnamemap[mactobridge[mac]]
bridgetoifmap[mactobridge[mac]] = mactobridge[mac]
except KeyError:
continue
if ifname not in maccounts:
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
newmacs = {}
for mac in mactobridge:
# We want to merge it so that when a mac appears in multiple
# places, it is captured.
try:
ifname = ifnamemap[bridgetoifmap[mactobridge[mac]]]
except KeyError:
continue
if mac in _macmap:
_macmap[mac].append((switch, ifname, maccounts[ifname]))
else:
_macmap[mac] = [(switch, ifname, maccounts[ifname])]
if ifname in newmacs:
newmacs[ifname].append(mac)
else:
newmacs[ifname] = [mac]
nodename = _nodelookup(switch, ifname)
if nodename is not None:
if mac in _nodesbymac and _nodesbymac[mac][0] != 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)
else:
_nodesbymac[mac] = (nodename, maccounts[ifname])
_macsbyswitch[switch] = newmacs
#OFFLOAD: end of need to offload?
return mactobridge,ifnamemap,bridgetoifmap
switchbackoff = 30
@@ -572,6 +663,20 @@ def rescan(cfg):
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == '-o':
upacker = msgpack.Unpacker(encoding='utf8')
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
while True:
r = select.select([sys.stdin], [], [])
try:
upacker.feed(sys.stdin.buffer.read())
except AttributeError:
upacker.feed(sys.stdin.read())
for cmd in upacker:
eventlet.spawn_n(_snmp_map_switch_relay, *cmd)
sys.exit(0)
cg = cfm.ConfigManager(None)
for res in update_macmap(cg):
print("map has updated")
@@ -14,16 +14,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.util as util
import confluent.noderange as noderange
import confluent.collective.manager as collective
def get_switchcreds(configmanager, switches):
switchcfg = configmanager.get_node_attributes(
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
'secret.hardwaremanagementpassword'), decrypt=True)
'secret.hardwaremanagementpassword',
'collective.managercandidates'), decrypt=True)
switchauth = []
for switch in switches:
if not switch:
continue
candmgrs = switchcfg.get(switch, {}).get('collective.managercandidates', {}).get('value', None)
if candmgrs:
candmgrs = noderange.NodeRange(candmgrs, configmanager).nodes
if collective.get_myname() not in candmgrs:
continue
switchparms = switchcfg.get(switch, {})
user = None
password = switchparms.get(
+1 -1
View File
@@ -106,7 +106,7 @@ class NodeRange(object):
self.endpage = None
self.cfm = config
try:
elements = _parser.parseString("(" + noderange + ")").asList()[0]
elements = _parser.parseString("(" + noderange + ")", parseAll=True).asList()[0]
except pp.ParseException as pe:
raise Exception("Invalid syntax")
if noderange[0] in ('<', '>'):
+21
View File
@@ -238,6 +238,26 @@ def extract_file(archfile, flags=0, callback=lambda x: None, imginfo=(), extract
return pctdone
def check_rocky(isoinfo):
ver = None
arch = None
cat = None
for entry in isoinfo[0]:
if 'rocky-release-8' in entry:
ver = entry.split('-')[2]
arch = entry.split('.')[-2]
cat = 'el8'
break
else:
return None
if arch == 'noarch' and '.discinfo' in isoinfo[1]:
prodinfo = isoinfo[1]['.discinfo']
arch = prodinfo.split(b'\n')[2]
if not isinstance(arch, str):
arch = arch.decode('utf-8')
return {'name': 'rocky-{0}-{1}'.format(ver, arch), 'method': EXTRACT, 'category': cat}
def check_alma(isoinfo):
ver = None
arch = None
@@ -649,6 +669,7 @@ class MediaImporter(object):
targpath += '/' + identity['subname']
self.targpath = '/var/lib/confluent/distributions/' + targpath
if os.path.exists(self.targpath):
del importing[importkey]
raise Exception('{0} already exists'.format(self.targpath))
self.filename = os.path.abspath(media)
self.medfile = medfile
@@ -36,7 +36,14 @@ class WebClient(object):
self.wc.set_basic_credentials(creds[node]['secret.hardwaremanagementuser']['value'], creds[node]['secret.hardwaremanagementpassword']['value'])
def fetch(self, url, results):
rsp, status = self.wc.grab_json_response_with_status(url)
try:
rsp, status = self.wc.grab_json_response_with_status(url)
except exc.PubkeyInvalid:
results.put(msg.ConfluentNodeError(self.node,
'Mismatch detected between '
'target certificate fingerprint and '
'pubkeys.tls_hardwaremanager attribute'))
return {}
if status == 401:
results.put(msg.ConfluentTargetInvalidCredentials(self.node, 'Unable to authenticate'))
return {}
@@ -91,7 +98,7 @@ def retrieve(nodes, element, configmanager, inputdata):
return
while workers:
try:
datum = results.get(10)
datum = results.get(block=True, timeout=10)
while datum:
if datum:
yield datum
@@ -749,6 +749,10 @@ class IpmiHandler(object):
self.node, 'Invalid prefix length given'))
else:
raise
elif len(self.element) == 4 and self.element[-1] != 'management':
self.output.put(
msg.ConfluentTargetNotFound(self.node,
'Interface not found'))
def handle_users(self):
# Create user
@@ -1539,7 +1543,8 @@ class IpmiHandler(object):
try:
self.ipmicmd.apply_license(filename, data=datfile)
finally:
datfile.close()
if datfile is not None:
datfile.close()
if len(self.element) == 3:
self.output.put(msg.ChildCollection('all'))
i = 1
@@ -608,6 +608,10 @@ class IpmiHandler(object):
self.node, 'Invalid prefix length given'))
else:
raise
elif len(self.element) == 4 and self.element[-1] != 'management':
self.output.put(
msg.ConfluentTargetNotFound(self.node,
'Interface not found'))
def handle_users(self):
# Create user
@@ -1384,7 +1388,8 @@ class IpmiHandler(object):
try:
self.ipmicmd.apply_license(filename, data=datfile)
finally:
datfile.close()
if datfile is not None:
datfile.close()
if len(self.element) == 3:
self.output.put(msg.ChildCollection('all'))
i = 1
+13 -5
View File
@@ -259,6 +259,8 @@ def handle_request(env, start_response):
start_response('500 Error', (('Content-Type', 'text/plain'),))
yield 'No pending profile detected, unable to accept status update'
elif env['PATH_INFO'] == '/self/saveapikey' and reqbody:
if not isinstance(reqbody, str):
reqbody = reqbody.decode('utf8')
cfg.set_node_attributes({
nodename: {'deployment.sealedapikey': {'value': reqbody}}})
start_response('200 OK', ())
@@ -267,10 +269,13 @@ def handle_request(env, start_response):
scriptcat = env['PATH_INFO'].replace('/self/remoteconfig/', '')
slist, profile = get_scriptlist(
scriptcat, cfg, nodename,
'/var/lib/confluent/public/os/{0}/ansible/{1}.d/')
'/var/lib/confluent/public/os/{0}/ansible/{1}')
playlist = []
dirname = '/var/lib/confluent/public/os/{0}/ansible/{1}.d/'.format(
dirname = '/var/lib/confluent/public/os/{0}/ansible/{1}/'.format(
profile, scriptcat)
if not os.path.isdir(dirname):
dirname = '/var/lib/confluent/public/os/{0}/ansible/{1}.d/'.format(
profile, scriptcat)
for filename in slist:
if filename.endswith('.yaml') or filename.endswith('.yml'):
playlist.append(os.path.join(dirname, filename))
@@ -297,7 +302,7 @@ def handle_request(env, start_response):
elif env['PATH_INFO'].startswith('/self/remoteconfig/status'):
rst = runansible.running_status.get(nodename, None)
if not rst:
start_response('204 Not Running', ())
start_response('204 Not Running', (('Content-Length', '0'),))
yield ''
return
start_response('200 OK', ())
@@ -309,7 +314,7 @@ def handle_request(env, start_response):
scriptcat = env['PATH_INFO'].replace('/self/scriptlist/', '')
slist, _ = get_scriptlist(
scriptcat, cfg, nodename,
'/var/lib/confluent/public/os/{0}/scripts/{1}.d/')
'/var/lib/confluent/public/os/{0}/scripts/{1}')
if slist:
start_response('200 OK', (('Content-Type', 'application/yaml'),))
yield yaml.safe_dump(util.natural_sort(slist), default_flow_style=False)
@@ -335,8 +340,11 @@ def get_scriptlist(scriptcat, cfg, nodename, pathtemplate):
profile = deployinfo.get(
'deployment.profile', {}).get('value', '')
slist = []
target = pathtemplate.format(profile, scriptcat)
if not os.path.isdir(target) and os.path.isdir(target + '.d'):
target = target + '.d'
try:
slist = os.listdir(pathtemplate.format(profile, scriptcat))
slist = os.listdir(target)
except OSError:
pass
return slist, profile
+7 -1
View File
@@ -103,7 +103,7 @@ class ClientConsole(object):
self.xmit = True
for datum in self.pendingdata:
send_data(self.client, datum)
self.pendingdata = None
self.pendingdata = []
def send_data(connection, data):
@@ -321,6 +321,12 @@ def term_interact(authdata, authname, ccons, cfm, connection, consession,
elif data['operation'] == 'reopen':
consession.reopen()
continue
elif data['operation'] == 'pause':
ccons.xmit = False
continue
elif data['operation'] == 'resume':
ccons.xmit = True
continue
elif data['operation'] == 'resize':
consession.resize(width=data['width'], height=data['height'])
continue
+21 -2
View File
@@ -11,6 +11,16 @@ import tempfile
agent_pid = None
ready_keys = {}
_sshver = None
def sshver():
global _sshver
if _sshver is None:
p = subprocess.Popen(['ssh', '-V'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
_, output = p.communicate()
_sshver = float(output.split()[0].split(b'_')[1].split(b'p')[0])
return _sshver
def normalize_uid():
curruid = os.geteuid()
@@ -23,6 +33,8 @@ def normalize_uid():
def assure_agent():
if sshver() <= 7.6:
return False
global agent_pid
if agent_pid is None:
sai = subprocess.check_output(['ssh-agent'])
@@ -39,8 +51,11 @@ def assure_agent():
if k == 'SSH_AGENT_PID':
agent_pid = v
os.environ[k] = v
return True
def get_passphrase():
if sshver() <= 7.6:
return ''
# convert the master key to base64
# for use in ssh passphrase context
if cfm._masterkey is None:
@@ -76,9 +91,11 @@ def initialize_ca():
def prep_ssh_key(keyname):
assure_agent()
if keyname in ready_keys:
return
if not assure_agent():
ready_keys[keyname] = 1
return
tmpdir = tempfile.mkdtemp()
try:
askpass = os.path.join(tmpdir, 'askpass.sh')
@@ -106,8 +123,10 @@ def sign_host_key(pubkey, nodename, principals=()):
principals = set(principals)
principals.add(nodename)
principals = ','.join(sorted(principals))
flags = '-Us' if sshver() > 7.6 else '-s'
keyname = '/etc/confluent/ssh/ca.pub' if flags == '-Us' else '/etc/confluent/ssh/ca'
subprocess.check_call(
['ssh-keygen', '-Us', '/etc/confluent/ssh/ca.pub', '-I', nodename,
['ssh-keygen', flags, keyname, '-I', nodename,
'-n', principals, '-h', pkeyname])
certname = pkeyname.replace('.pub', '-cert.pub')
with open(certname) as cert:
+28 -16
View File
@@ -45,7 +45,7 @@ class SyncList(object):
ent = ent[:cmtidx]
except ValueError:
pass
for special in '!@$%^&*()|{}':
for special in '!@$%^&()|{}':
if special in ent:
raise Exception(
'Special character "{}" reserved for future use'.format(special))
@@ -65,15 +65,14 @@ class SyncList(object):
v = v.strip()
else:
k = ent
v = ent
v = None
currmap[k] = v
def sync_list_to_node(synclist, node, suffixes):
def sync_list_to_node(sl, node, suffixes):
targdir = tempfile.mkdtemp('.syncto{}'.format(node))
output = ''
try:
sl = SyncList(synclist)
for ent in sl.replacemap:
stage_ent(sl.replacemap, ent, targdir)
if 'append' in suffixes:
@@ -107,22 +106,32 @@ def stage_ent(currmap, ent, targdir):
everyfent.extend(os.path.dirname(tmpent))
if not everyfent:
raise Exception('No matching files for "{}"'.format(ent))
if dst is None: # this is to indicate source and destination as one
dst = os.path.dirname(everyfent[0]) + '/'
while dst and dst[0] == '/':
dst = dst[1:]
if len(everyfent) > 1 and dst[-1] != '/':
raise Exception(
'Multiple files match {}, {} needs a trailing slash to indicate a directory'.format(ent, dst))
fulltarg = os.path.join(targdir, dst)
if dst[-1] == '/' or len(everyfent) > 1 or os.path.isdir(everyfent[0]):
# target *must* be a directory
fulltargdir = fulltarg
else:
fulltargdir = os.path.join(targdir, os.path.dirname(dst))
mkdirp(fulltargdir)
for targ in everyfent:
if fulltargdir == fulltarg:
os.symlink(
targ, os.path.join(
fulltargdir, os.path.basename(targ)))
mkpathorlink(targ, fulltarg)
def mkpathorlink(source, destination):
if os.path.isdir(source):
mkdirp(destination)
for ent in os.listdir(source):
currsrc = os.path.join(source, ent)
currdst = os.path.join(destination, ent)
mkpathorlink(currsrc, currdst)
else:
if destination[-1] == '/':
mkdirp(destination)
destination = os.path.join(destination, os.path.basename(source))
else:
os.symlink(targ, fulltarg)
mkdirp(os.path.dirname(destination))
os.symlink(source, destination)
syncrunners = {}
@@ -144,8 +153,11 @@ def start_syncfiles(nodename, cfg, suffixes):
synclist = '/var/lib/confluent/public/os/{}/syncfiles'.format(profile)
if not os.path.exists(synclist):
return '200 OK' # not running
sl = SyncList(synclist)
if not (sl.appendmap or sl.mergemap or sl.replacemap):
return '200 OK' # the synclist has no actual entries
syncrunners[nodename] = eventlet.spawn(
sync_list_to_node, synclist, nodename, suffixes)
sync_list_to_node, sl, nodename, suffixes)
return '202 Queued' # backgrounded
def get_syncresult(nodename):
@@ -12,6 +12,7 @@ Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Requires: confluent_vtbufferd
%if "%{dist}" == ".el8"
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client python3-msgpack python3-libarchive-c python3-yaml
%else
+1 -1
View File
@@ -1,5 +1,5 @@
vtbufferd: vtbufferd.c tmt.c
gcc -O3 -o vtbufferd vtbufferd.c tmt.c
gcc -O3 --std=gnu11 -o vtbufferd vtbufferd.c tmt.c
clean:
rm vtbufferd
Regular → Executable
+2 -2
View File
@@ -6,13 +6,13 @@ fi
mkdir -p dist/confluent_vtbufferd-$VERSION
cp *.c *.h Makefile dist/confluent_vtbufferd-$VERSION
cd dist
tar czvf confluent_vtbufferd-$VERSION.tar.gz confluent_vtbufferd-$VERSION
tar czf confluent_vtbufferd-$VERSION.tar.gz confluent_vtbufferd-$VERSION
cd -
cp dist/confluent_vtbufferd-$VERSION.tar.gz ~/rpmbuild/SOURCES
sed -e 's/#VERSION#/'$VERSION/ confluent_vtbufferd.spec.tmpl > ~/rpmbuild/SPECS/confluent_vtbufferd.spec
rpmbuild -ba ~/rpmbuild/SPECS/confluent_vtbufferd.spec 2> /dev/null |grep ^Wrote:
if [ $? -ne 0 ]; then
echo "[ERROR] rpmbuild returned non-zero, run: rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec"
echo "[ERROR] rpmbuild returned non-zero, run: rpmbuild -ba ~/rpmbuild/SPECS/confluent_vtbufferd.spec"
exit 1
else
# Clean up the generated files in this directory
@@ -42,3 +42,6 @@ make
%install
mkdir -p $RPM_BUILD_ROOT/opt/confluent/bin
cp vtbufferd $RPM_BUILD_ROOT/opt/confluent/bin/
%files
/opt/confluent/bin/vtbufferd
+8 -7
View File
@@ -214,13 +214,13 @@ HANDLER(sgr)
#define FGBG(c) *(P0(i) < 40? &vt->attrs.fg : &vt->attrs.bg) = c
for (size_t i = 0; i < vt->npar; i++) switch (P0(i)){
case 0: vt->attrs = defattrs; break;
case 1: case 22: vt->attrs.bold = P0(0) < 20; break;
case 2: case 23: vt->attrs.dim = P0(0) < 20; break;
case 4: case 24: vt->attrs.underline = P0(0) < 20; break;
case 5: case 25: vt->attrs.blink = P0(0) < 20; break;
case 7: case 27: vt->attrs.reverse = P0(0) < 20; break;
case 8: case 28: vt->attrs.invisible = P0(0) < 20; break;
case 10: case 11: vt->acs = P0(0) > 10; break;
case 1: case 22: vt->attrs.bold = P0(i) < 20; break;
case 2: case 23: vt->attrs.dim = P0(i) < 20; break;
case 4: case 24: vt->attrs.underline = P0(i) < 20; break;
case 5: case 25: vt->attrs.blink = P0(i) < 20; break;
case 7: case 27: vt->attrs.reverse = P0(i) < 20; break;
case 8: case 28: vt->attrs.invisible = P0(i) < 20; break;
case 10: case 11: vt->acs = P0(i) > 10; break;
case 30: case 40: FGBG(TMT_COLOR_BLACK); break;
case 31: case 41: FGBG(TMT_COLOR_RED); break;
case 32: case 42: FGBG(TMT_COLOR_GREEN); break;
@@ -366,6 +366,7 @@ tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
vt->decchars = L" ◆▒\t\f\r\n°±\n\v┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£•";
vt->cb = cb;
vt->p = p;
vt->attrs = vt->oldattrs = defattrs;
if (!tmt_resize(vt, nline, ncol)) return tmt_close(vt), NULL;
return vt;
+4 -6
View File
@@ -158,10 +158,6 @@ void dump_vt(TMT* outvt) {
fflush(stdout);
wprintf(L"\x1b[%ld;%ldH", curs->r + 1, curs->c + 1);
fflush(stdout);
idx = write(1, "\x00", 1);
if (idx < 0) {
return;
}
}
int main(int argc, char* argv[]) {
@@ -201,9 +197,11 @@ int main(int argc, char* argv[]) {
if (cmd < 0)
continue;
outvt = get_termentbyname(cmdbuf);
if (outvt != NULL) {
if (outvt != NULL)
dump_vt(outvt);
}
length = write(1, "\x00", 1);
if (length < 0)
continue;
}
}
}
+3 -2
View File
@@ -2,7 +2,7 @@
dracut_install /usr/bin/clevis-encrypt-tpm2 /usr/bin/clevis-decrypt-tpm2 /usr/bin/jose /usr/bin/mktemp
dracut_install /lib64/libtss2-tcti-device.so.0
dracut_install /usr/bin/tpm2_create /usr/bin/tpm2_pcrread /usr/bin/tpm2_createpolicy /usr/bin/tpm2_createprimary /usr/bin/tpm2_load /usr/bin/tpm2_unseal
dracut_install curl openssl tar ipmitool cpio gzip lsmod ethtool
dracut_install curl openssl tar ipmitool cpio xz gzip lsmod ethtool
dracut_install modprobe touch echo cut wc bash netstat uniq grep ip hostname
dracut_install awk egrep dirname bc expr sort
dracut_install ssh sshd vi reboot lspci parted tmux mkfs mkfs.ext4 mkfs.xfs xfs_db mkswap
@@ -17,7 +17,7 @@ dracut_install dmidecode /usr/lib64/libstdc++.so.6
dracut_install ps free find
inst /bin/bash /bin/sh
#inst_hook cmdline 10 $moddir/cmdline.sh # moved to addons.cpio
dracut_install killall chown chroot dd expr kill parted rsync shutdown sort blockdev findfs insmod lvm pivot_root
dracut_install killall chown chroot dd expr kill parted rsync shutdown sort blockdev findfs insmod lvm
dracut_install /etc/udev/hwdb.bin
dracut_install /usr/share/hwdata/pci.ids
dracut_install ibstat ibstatus
@@ -26,5 +26,6 @@ dracut_install /usr/lib/udev/rules.d/10-dm.rules /usr/sbin/dmsetup /usr/lib/udev
#dracut_install /usr/lib/opa-fm/bin/opafmd
#dracut_install /usr/sbin/opensm /usr/libexec/opensm-launch
dracut_install /usr/lib64/libibverbs/libhfi1verbs-rdmav25.so /etc/libibverbs.d/hfi1verbs.driver /etc/libibverbs.d/mlx4.driver /etc/libibverbs.d/mlx5.driver /usr/lib64/libibverbs/libmlx4-rdmav25.so /usr/lib64/libibverbs/libmlx5-rdmav25.so
inst /usr/lib/dracut/modules.d/40network/net-lib.sh /lib/net-lib.sh
#dracut_install mstflint This would have mlx update support, but it incurs gigantic dependencies, libicudata
+1 -1
View File
@@ -1,6 +1,6 @@
%define arch x86_64
Version: 3.2.0
Release: 3
Release: 5
Name: confluent-genesis-%{arch}
BuildArch: noarch
Summary: Genesis servicing image for confluent