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

Compare commits

...

222 Commits

Author SHA1 Message Date
Jarrod Johnson 75082c4418 Move up creation of ca.pem 2023-08-03 14:03:36 -04:00
Jarrod Johnson 9a1c9eb43f Improve ssh concurrency on websocket
ssh module was pausing input for the
entire websocket while doing the simple 'write' operation.

Change to background the actual
logon processing,
rather than blocking what should be a fairly trivial write operation.
2023-08-03 09:56:36 -04:00
Jarrod Johnson 89bd798f8b Increasing time again, outlet count didn't factor 2023-08-02 15:20:29 -04:00
Jarrod Johnson bf10e58f00 Bump version
With recent collective changes, bump the version to block connection with
older collective members until upgraded.
2023-08-02 13:43:41 -04:00
Jarrod Johnson cbf2cdcdc5 Scale timeout with number of outlets
Delta PDUs seem to serialize outlet operation.
2023-08-01 16:08:51 -04:00
Jarrod Johnson 987587aaf8 Allow custom auth file to define valid roles 2023-07-26 16:37:55 -04:00
Jarrod Johnson ad25c31d3f Correct error in check_for_yaml function in auth 2023-07-26 16:15:36 -04:00
Jarrod Johnson b1018d648e Hook loading of /etc/confluent/authorization.yaml
This should permit custom roles to be defined.
2023-07-26 16:05:29 -04:00
Jarrod Johnson a18d0f10b7 Clean up stale lvm devices files
During cloning, stale lvm devices file may cause
ugly output.  Remove stale files before proceeding down steps
that trigger lvm warnings.
2023-07-26 07:54:41 -04:00
Jarrod Johnson 65ac3de21b Take EL9 version of image2disk for improvements with parted supervision 2023-07-26 07:26:49 -04:00
Jarrod Johnson c9d9a3cc54 Add EL9 cloning support
Main difference from EL8 is different grub strategy.  To cope, capture
the uuid of new /boot and manipulate the
stub grub.cfg with the right uuid.

In EL8, the efi has the main grub, EL9 changes to have /boot host the 'real' grub,
causing us to have to modify the stub grub.
2023-07-25 17:09:37 -04:00
Jarrod Johnson 957b979dde Reorder imports in configmanager to mitigate circular import 2023-07-24 13:38:44 -04:00
Jarrod Johnson 48c4a2e062 Have reconnects use new TCP connections
Current code was trying to reuse connections that would be useless,
explicitly go to new TCP connections for reconnection.
2023-07-24 12:31:32 -04:00
Jarrod Johnson 285a159ba5 Implement a number of improvements for collective
For one, remove 'non-voting' members from being leaders.
Large number of leader candidates create long delays for
converging on a valid organization.  Further, some treat 'non-voting'
more roughly, inducing the worst case convergence scenario of unclean
shutdown of leader.
Convergence now happens fairly quickly for collectives with large
number of non-voting members.

During initial DB transfer, the leader would be tied up unreasonably
long handling the jsonification of a large configuration.  Offload to a worker
process to allow the leader to continue operation while this intensive, rare
operation occurs.

Reliably run a reassimilation procedure for the lifetime of the leader.
This allows orphaned members to be prompted to join the correct leader.

Serialize the onboarding of a connecting member, and have redundancy more gracefully
paused. This avoids excessive waiting in lock and more deterministic timing
with respect to timeout expectations by the connecting system.
2023-07-24 11:11:39 -04:00
Jarrod Johnson 8ea2ba046e Sort the IP addresses in nodediscover for consistent UI 2023-07-19 16:16:27 -04:00
Jarrod Johnson f16daa44dd Handle older python with addrinfo
While newer python omits '%',
older python includes.  Change to handle
either form.
2023-07-19 16:04:25 -04:00
Jarrod Johnson 03fce4f762 Adjust sys.path logic in macmap and osimage
Ensure the sys.path logic
is before attempting
the relevant imports.
2023-07-19 12:15:01 -04:00
Jarrod Johnson 809099c8f8 Specifically wait for /dev/disk/by-label
When udev is populating the disk hierarchy, it can be a long time
before the 'by-label' is specifically ready.

Wait for that specific entry to come along before continuing to
check if there's an identity image.
2023-07-07 15:32:47 -04:00
Jarrod Johnson a875f0d3e1 Correct syntax error in selfservice 2023-07-06 14:04:49 -04:00
Jarrod Johnson fcc62b7cb7 Improve confluent scanning on newer python
Newer python changed the format of peer[0], adapt to the change
to use the socket info if not available in the peer[0] string.
2023-07-05 16:34:37 -04:00
Jarrod Johnson 11cb47c03e Use natural sort on some self service calls 2023-07-05 16:18:49 -04:00
Jarrod Johnson afc78513a1 Merge pull request #90 from sjtstg/ansible-fixes
Ansible fixes
2023-07-05 16:15:30 -04:00
Simon Thompson 45f22c3e31 when host is not in hosts file, group_vars is not loaded, change logic to emulate host being in hosts file with no groups to ensure group_vars is loaded which permits use of dynamic grouping in plays 2023-07-05 21:25:25 +02:00
Simon Thompson d0f978548e sort directory listing to make ansible plays and script order deterministic 2023-07-05 21:23:55 +02:00
Jarrod Johnson 052d16aa49 Avoid modifying PYTHONPATH
Since there's a possibility of cross-python invocation,
avoid modifying PYTHONPATH
2023-07-05 11:40:00 -04:00
Jarrod Johnson 7ddd2c2e6e Correct mistakes in shebang adaptive behavior 2023-07-05 11:14:32 -04:00
Jarrod Johnson 9186c8142c Fix incorrect reference in the smm validation by certificate 2023-06-28 13:13:26 -04:00
Jarrod Johnson c07248d58e Fix outlet control on Geist PDUs. 2023-06-27 08:04:16 -04:00
Jarrod Johnson 013de4f881 Adapt to ansible python runtime
Unfortunately, python being python, ansible may elect to install under a
different python runtime than confluent.

In such a case, hope that the ansible python can work.

Remove eventlet as a hard requirement, as
that is unlikely to be in ansible python.

This leaves msgpack, which is unavoidable, for now. But if it's in ansible python,
it's fine.
2023-06-26 15:41:44 -04:00
Jarrod Johnson 795b4c513d Rework for connected temperature sensors
PDUs behave differently when a sensor module is
connected. Handle
that difference and
provide the relevant data.
2023-06-26 12:32:45 -04:00
Jarrod Johnson 4056264991 Correct keyname from previous fix 2023-06-23 17:01:50 -04:00
Jarrod Johnson 77c8e93a8d Fix terminal actions on shared websocket
The full complement of functionality was not
interpreted in shared context
2023-06-23 16:33:30 -04:00
Jarrod Johnson 1695e222ec Rename command to nodeapply, by popular demand 2023-06-22 14:16:54 -04:00
Jarrod Johnson b2a793568a Merge pull request #125 from tkucherera-lenovo/master
change timestamp log option from -d to -T
2023-06-22 14:14:59 -04:00
Jarrod Johnson 3ebd43fe0c Bump genesis base version 2023-06-15 09:35:37 -04:00
Jarrod Johnson 2e20e0303f Fix libbpf link 2023-06-15 09:22:52 -04:00
Jarrod Johnson da2df63498 Update location of krb5 2023-06-15 09:20:37 -04:00
Jarrod Johnson 896a4ff1ff Fix spec format 2023-06-14 16:05:08 -04:00
Jarrod Johnson a14effa4f2 Undo change
Realized we need to install related rpm for efivar
2023-06-14 15:57:27 -04:00
Jarrod Johnson 66c79a5f42 Fixup handling of efivar license 2023-06-14 15:52:52 -04:00
Jarrod Johnson 389c37659e Refresh more licensing material in genesis rpm 2023-06-14 15:47:17 -04:00
tkucherera 89af8661da change timestamp log option from -d to -T 2023-06-12 10:51:29 -04:00
Jarrod Johnson 41437c511b Fix when networkmanager breaks one of our connections
This usually happens with ipv6 link local used
in initramfs stage, after networkmanager may
mutilate the ipv6 connectivity.
2023-06-12 08:49:13 -04:00
Jarrod Johnson 75f0aaeee9 Allow session id through header
This permits a client to exert finer grained control
over the session id
than provided by cookie.
2023-06-08 11:30:32 -04:00
Jarrod Johnson 68f9688292 Drop SLP packets with error
Some implementations send
error for services they do not provide.
2023-06-08 08:02:59 -04:00
Jarrod Johnson 67204f79a1 Fix bug where dispatching a request could block all other activity 2023-06-02 11:06:44 -04:00
Jarrod Johnson e9dd7da775 Fix syntax error in el7 identity image support 2023-06-02 08:55:27 -04:00
Jarrod Johnson 8f1be7c905 Fix Python2 compatibility issue with port forwarder 2023-06-01 16:50:06 -04:00
Jarrod Johnson bc684b82b4 Implement identity image based deployment for EL7
Though older, it is still popularly used.
2023-06-01 16:49:17 -04:00
Jarrod Johnson 5e72d40c03 Fix compatibility issue with python2 2023-06-01 13:31:32 -04:00
Jarrod Johnson 7ab2aff7f2 Restore syntax of EL7 deployment
Inadvertent incomplete checkin
broke syntax of EL7, fix to have it
work as-is for now.
2023-06-01 09:20:17 -04:00
Jarrod Johnson c3fc931ba4 Fix python2 compatibility issue for port forwarding
The measure for predictable port numbering
was done with python3 in mind, without consideration
of python2.
2023-06-01 09:18:36 -04:00
Jarrod Johnson 2c61cee72d Merge pull request #124 from Tkucherera/nodeconsole_timestamp
Nodeconsole timestamp
2023-06-01 09:08:33 -04:00
Tinashe 190ec2473b dump log to stfout with timestamps 2023-05-31 17:39:53 -04:00
Tkucherera 9ebb8f27ec Merge branch 'xcat2:master' into master 2023-05-31 17:34:52 -04:00
Jarrod Johnson 0db7d4f9d2 Stall readback of PDU after outlet control
There is some lag after requesting an outlet change and
the outlet state actually changing.
2023-05-26 10:43:04 -04:00
Jarrod Johnson 5d1315098f Enhance and extend check of node relations 2023-05-25 11:14:58 -04:00
Jarrod Johnson fc29ddb913 Improve routed EL8 deployment and add debug shell on tty2 2023-05-24 11:02:23 -04:00
Jarrod Johnson b06b77f5a5 Fix issues with confluent media deployment on multi-nics in routed env 2023-05-23 17:22:38 -04:00
Jarrod Johnson 97a5bf0806 Correct typo in suse 15 diskless boot 2023-05-19 14:50:04 -04:00
Jarrod Johnson 36e61f301a Add a nodefreshen command
This aims to provide the same shortcuts as 'updatenode'
2023-05-18 15:03:29 -04:00
Jarrod Johnson e476c7b9e6 Avoid deprecationwarning in python 3.10+
Newer python is not backwards compatible with
a constant.  Attempt to use new
constant and fallback to teh old one
if unavilable.
2023-05-18 09:20:52 -04:00
Jarrod Johnson a523df80e8 Handle newer SuSE
After SP4, suse drops mkinird requirement.

Switch to dracut -f in such a scenario.
2023-05-16 14:51:27 -04:00
Jarrod Johnson 8f4982e850 Fix build mistake in the cooltera plugin 2023-05-15 08:30:44 -04:00
Jarrod Johnson a167d19441 Add module for cooltera nodesensors 2023-05-10 13:24:20 -04:00
Jarrod Johnson 2237e0e173 Add a retry mechanism for connecting socket
Timing is sensitive during the handshake, catch and retry
if it should fail.
2023-05-10 11:12:29 -04:00
Jarrod Johnson b08b62614a Have proxydhcp recover from faults 2023-05-10 08:44:31 -04:00
Jarrod Johnson e93e4abd83 Add clear error when base profile doesn't exist
If the user specifies a wrong '-b',
indicate clearly that is the case,
and do so early.
2023-05-10 08:40:45 -04:00
Jarrod Johnson 430b24081c Have apiclient timeout if IO hangs up 2023-05-09 16:04:24 -04:00
Jarrod Johnson ec54ecc9cd Fix tiling with different gravity
In some environments, the coordinates may be reported in a
different format.  Handle the different context.
2023-04-28 09:20:31 -04:00
Jarrod Johnson f787461759 Merge pull request #123 from Tkucherera/tmux_reuse
Tmux reuse
2023-04-28 09:18:44 -04:00
Jarrod Johnson 7312d8e092 Provide commented hints for mdns integration
mdns protocol is not currently used, but
put in breadcrumbs for
bringing it alive if
we have an openbmc handler.
2023-04-27 15:51:28 -04:00
Tkucherera 884a6be68c Wkill like command (#1)
* wkill like command fix

* tmux session resuse

---------

Co-authored-by: Tinashe <tkucherera@lenovo.com>
2023-04-27 15:46:40 -04:00
Tinashe a35a7e74c3 tmux session resuse 2023-04-27 15:39:32 -04:00
Jarrod Johnson ffdf799713 Advance mdns support
At this point, the protocol handler
seems up to the task of describing openbmcs
2023-04-27 08:59:39 -04:00
Jarrod Johnson b9d0da0416 Correct mistake in the gathering of valid nodenames 2023-04-26 15:37:08 -04:00
Jarrod Johnson 3fd930137d Begin work on mdns protocol support 2023-04-21 16:14:25 -04:00
Tinashe 71a263366d wkill like command fix 2023-04-21 10:22:10 -04:00
Jarrod Johnson 7b150773df Extend multi-nic guessing to Ubuntu 20/22 2023-04-20 15:45:32 -04:00
Jarrod Johnson 53f4665358 Correct issues in Ubuntu 18 deployment 2023-04-20 14:48:39 -04:00
Jarrod Johnson e5fff430be Explicitly clear logonwaiters on discard of a session
When a session is being discarded, it
may inadvertently hold onto logonwaiters.

The IPMI session may be reused, but the context is new and those
dead contexts can consume memory unreasonably.
2023-04-19 10:22:33 -04:00
Jarrod Johnson 55da173b27 Correct missing parameter in Geist
Geist support was missing a variable in the refactor.
2023-04-19 10:20:34 -04:00
Jarrod Johnson 55a796af82 Correct some errors in the Ubuntu 18 support
An ommited end clause as well
as some bashisms.
2023-04-12 13:23:39 -04:00
Jarrod Johnson 123d190f12 Extend network and deployment server checking for Ubuntu 18.04
When doing network evaluation in ambiguous contexts,
spend some time checking each nic and each candidate target ip address.

This may take longer, but options are limited in a routed context.
2023-04-12 13:04:08 -04:00
Jarrod Johnson 305a3a06d2 Ensure $HOME is set during firstboot
systemd tends not to set environment variables.  However some firstboot
scripts generally expect $HOME to be correct.
2023-04-12 11:22:27 -04:00
Jarrod Johnson 024677d7de Try EL7 to build the old addons 2023-04-07 16:50:20 -04:00
Jarrod Johnson fb31a68818 Add profile.yaml to ubuntu18.04 stock profile 2023-04-07 16:26:32 -04:00
Jarrod Johnson 526a248963 Add ubuntu18.04 to build script in other location 2023-04-07 16:17:37 -04:00
Jarrod Johnson 86d9d6fc2f Add ubuntu18.04 to build 2023-04-07 15:46:29 -04:00
Jarrod Johnson 2bdc53722b Further advance ubuntu 18.04 support 2023-04-07 15:45:31 -04:00
Jarrod Johnson 25b0f45e6f Merge remote-tracking branch 'lenovo/master' 2023-04-07 13:53:44 -04:00
Jarrod Johnson f5b3682c08 Merge pull request #89 from erderial/patch-8
added functionality to check for net.*switch
2023-04-07 13:52:01 -04:00
Jarrod Johnson b7ea27b939 Advance draft Ubuntu 18 support
Get most things automated for the targeted use case.

Still need to handle partitioning and end of install question, add firstboot and add
staged and complete step.
2023-04-06 16:07:55 -04:00
Jarrod Johnson 1fff15677d Add el9bin correctly into build process 2023-04-04 11:55:23 -04:00
Jarrod Johnson 556d858d33 Address extraneous errors during build 2023-04-04 11:27:50 -04:00
Jarrod Johnson 0a02b010c9 Fix esxi build for new build strategy 2023-04-04 11:24:10 -04:00
Jarrod Johnson 08e19fbf6e Provide a sample for fuse3 urlmount
When the time comes to transition, this will be useful.

The challenge is that existing images will likely have a 'distribution'
with fuse2 libs, so this isn't a transition to make lightly.

Definitely should be done for new distributions at least, current
distributions pending further investigation
2023-04-04 10:39:12 -04:00
Jarrod Johnson 2d16d5558c Rework to build everything under el8 or el9 containers specifically
Make the build host less critical of an implementation detail
2023-04-04 10:19:13 -04:00
Jarrod Johnson 7f15e4af27 Change back to fedora
We need fuse-devel for now, since urlmount is written against older fuse
2023-04-04 09:44:04 -04:00
Jarrod Johnson 34313f4041 Change to EL9 for the new-wave util builds 2023-04-04 09:32:25 -04:00
erderial 9bb402a1b8 Update confluent_selfcheck 2023-04-03 10:27:07 +03:00
erderial 13d4c57ee2 changes done as per request 2023-03-31 19:32:43 +03:00
erderial 88c47c9254 added functionality to check for net.*switch
added functionality to check for net.*switch
2023-03-31 16:43:15 +03:00
Jarrod Johnson 181aa876a7 Draft initramfs addons for Ubuntu 18
For older Ubuntu 18, the automated installer is debian-installer.  Derive from the
prototype debian work, with same limitation about more limited network bringup that
we are stuck with from Debian limitations.
2023-03-30 16:36:40 -04:00
Jarrod Johnson a0dbb90c77 Handle mismatched alpha and numeric in hyphenated string
If 123abc-abc123 were attempted, it would produce
a strange error
when it encounters a string compare to number.

Detect the scenario and treat it like similar situations.
2023-03-30 14:21:22 -04:00
Jarrod Johnson cb129789b8 Properly recognize old ubuntu server install media
Older media designed around
debian installer requires
different handling.
2023-03-30 08:33:18 -04:00
Jarrod Johnson 7ac778106e Also skip over failure to broadcast SSDP 2023-03-29 16:55:12 -04:00
Jarrod Johnson f808c542e4 Ignore forbidden transmits on multicast 2023-03-29 16:53:40 -04:00
Jarrod Johnson 2a3bc61be3 Avoid overwriting groups on a redefine
When a node is being redefined, do
not populate groups.

That is only needed when truly defining for first time.
2023-03-29 09:28:52 -04:00
Jarrod Johnson 73fb54ccbc Add more manual licenses to the kernel 2023-03-28 17:02:53 -04:00
Jarrod Johnson 578bd9702a Sample script to install a cert into an XCC 2023-03-24 15:56:26 -04:00
Jarrod Johnson ca5f19a4f1 Sample script for getting a CSR from an XCC 2023-03-24 15:46:21 -04:00
Jarrod Johnson 717df579aa Add firmware to Geist support 2023-03-17 16:02:55 -04:00
Jarrod Johnson 70b973b1cf Refactor measurements in Geist
Use common code
for sensor translation.
2023-03-17 15:34:26 -04:00
Jarrod Johnson 48696ef63d Begin adding sensors to Geist PDUs 2023-03-17 14:37:03 -04:00
Jarrod Johnson ff025989c6 Restore spacing around float in nodesensors
The managed formatting needs
to be padded in the non-csv output.
2023-03-16 17:29:36 -04:00
Jarrod Johnson 872718c658 Add notation about alternate location of ansible plays
Make it known that plays may now be private.
2023-03-16 15:19:15 -04:00
Jarrod Johnson 47af869360 Allow ansible plays to be private
Since the node need not have access,
the ansible plays may be made private instead.

This enables potentially sensitive plays
to be protected.
2023-03-16 15:15:46 -04:00
Jarrod Johnson f256b1cd4e Handle more complex ansible configuration
Some plays expect the
inventory from /etc/ansible, detect
if that inventory *could* apply, use if it can.

ansible users generally anticipate leveraging the implicit '.' in the role
path,
chdir to meet that expectation.

'become' in the play without 'become_user' can upset ansible runtime.

Since we are already root, we will just ignore the implicit 'become', since we already
match that behavior.
2023-03-16 13:24:46 -04:00
Jarrod Johnson bfe03e6e01 Fix erronously dragging in CIDR to IPv4 configuration 2023-03-14 16:55:20 -04:00
Jarrod Johnson 8dc3dfd20c Limit TRYNIC to only one NIC in ubuntu deployment 2023-03-14 15:36:24 -04:00
Jarrod Johnson 0cc588f8e9 Carry nic detection through to bottom of Ubuntu 2023-03-14 15:24:37 -04:00
Jarrod Johnson e12578b6ac Use media net config when media deployed 2023-03-14 13:32:20 -04:00
Jarrod Johnson 0366bbd26f Place apikey where expected 2023-03-13 17:00:35 -04:00
Jarrod Johnson 5f4ab5ff80 Fix path to hmacfile
Since we are running
outside of chroot, need to adjust the $()
substitution.
2023-03-13 16:21:25 -04:00
Jarrod Johnson 88a6bccf12 Use b64 alphabet for b64
While POSIX hates +, b64 needs +
2023-03-13 16:20:17 -04:00
Jarrod Johnson 52c3e9d515 Do not care about port, let kernel decide 2023-03-13 11:19:11 -04:00
Jarrod Johnson 50a4ee2c6d Have a quick sample script to get LLA of USB nic peer 2023-03-13 11:15:31 -04:00
Jarrod Johnson 15ff24fccd Correct syntax errors in auth.py 2023-03-10 16:38:47 -05:00
Jarrod Johnson d9cc9112f4 Add identity image deployment to Ubuntu
Support the api arm token
and media network
config for Ubuntu.
2023-03-10 16:23:48 -05:00
Jarrod Johnson 5418d9ea97 Place a common script for setupssh 2023-03-10 16:05:29 -05:00
Jarrod Johnson 2aeade1e74 Merge pull request #88 from erderial/patch-6
updated with custom yaml file for auth
2023-03-09 15:48:12 -05:00
erderial 85f9dc12fb Update auth.py 2023-03-09 22:38:37 +02:00
erderial 56dea2422a Update auth.py 2023-03-07 21:08:01 +02:00
Jarrod Johnson 8b89232922 Do not get collective member when collective doesn't exist 2023-03-06 16:59:07 -05:00
Jarrod Johnson 22c464e092 Only add self to collective if self not yet in collective
Previously, it was safe to just do all the time, but now it may lose
the role.
2023-03-06 16:49:03 -05:00
Jarrod Johnson 4d9b11bc55 Fix quorum when there is no collective yet 2023-03-06 16:38:09 -05:00
Jarrod Johnson baa365fcac Implement non-voting collective members
Provide for applications
where only a small subset of collective
members should be
considered to count
toward whether the collective
can proceed.

Commonly, 'service' nodes may
be numerous to do work, but may all want to go offline
during a maintenance window.
2023-03-06 11:56:15 -05:00
Jarrod Johnson a385b1e93d Try strategy to have confignet run
confignet is special, it is designed
to work when networking
isn't right.  So have it run during firstboot
in case post fouled up
the network for firstboot.
2023-02-28 12:12:36 -05:00
Jarrod Johnson 733b6853dd Up newly added interfaces as a matter of course 2023-02-28 12:04:20 -05:00
Jarrod Johnson b4182cd4b5 Fix formation of error message
Use format to take in the parameters regardless of type
2023-02-27 14:55:01 -05:00
erderial cf2f5aac7b Merge branch 'lenovo:master' into patch-6 2023-02-27 18:27:39 +02:00
Jarrod Johnson 9f7e53701e Avoid latching onto USB nic in a vswitch as 'the nic'
In esxi, some builds may have USB nic brought up in a vswitch.

Detect and avoid that scenario.
2023-02-27 10:43:40 -05:00
erderial 3b9f9abdd9 Merge pull request #2 from erderial/erderial-patch-1
updated auth.py with some changes
2023-02-27 17:36:20 +02:00
erderial b800aa032e updated auth.py with some changes
updated auth.py with some changes. Need to add the check_for_yaml() function to main.py as well
2023-02-27 17:10:17 +02:00
Jarrod Johnson 70d8a1059c Consistently treat bytes as bytes in ssh
In Python3 systems,
there would be confusion
about bytes versus str.

Fix this so that ssh can work more consistently.
2023-02-24 15:47:20 -05:00
Jarrod Johnson 59b07665ab Modify float formatting again
Make sure at least one decimal is in a float.

Maximum precision of 5 past.
2023-02-24 12:03:43 -05:00
Jarrod Johnson 5ea214a726 Use eventlet subprocess
sshutil uses eventlet subprocess,
making calledprocesserror
hard to catch.

Adjust to consistently use same
subprocesss module.
2023-02-22 16:34:13 -05:00
Jarrod Johnson b99034f539 Improve reliability of collective join
While servicing an enrollment,
there's a window for a collective
member to be 'defined' but not
yet active, meaning quorum may transiently be lost as multiple enrollments progress.

Serialize enrollments by holding the enrollment process open.

Also, there is a chance that a transient transfer error may occur during loading
of the DB.  In such a case, restart
the connection rather thn aborting.
2023-02-22 16:11:38 -05:00
Jarrod Johnson 6df2e822a5 Correct api call in discovery 2023-02-22 09:34:32 -05:00
Jarrod Johnson 2379f6f90f Change nodesensors format of float
Floats are either unnecessarily long
in normal output, or too unconstrained in CSV output.

Normalize to as many digits as 'makes sense' up to 5 digits.

5 miight seem a bit much, but one common metric is kWh, which may need
that precision over short intervals.
2023-02-22 08:41:46 -05:00
erderial 8940247164 updated with custom yaml file for auth
Added 2 new function to check if the custom yaml file exists - /etc/confluent/authorize.yaml - and one to update the _allowbyrole and _deniedbyrole vars accordingly.
2023-02-21 15:13:17 +02:00
Jarrod Johnson 77ba0acee6 Merge pull request #122 from Tkucherera/nodeconsole-kill
nodeconsole <noderange> kill: added functionality for closing open win…
2023-02-16 16:37:07 -05:00
Tinashe b2c773bb84 nodeconsole <noderange> kill:added functionality for closing open windowed consoles 2023-02-16 15:54:21 -05:00
Jarrod Johnson 241800b1c9 Restore filename-only import
The open file handle as implemented
could not pass to the subprocess.

Rather than figure out how to open
and pass the filehandle,
simply let the subprocess
independently open the file
if it isn't passed.
2023-02-16 09:13:05 -05:00
Jarrod Johnson abc639e32b Preferentially support HTTPS on Eaton PDU
While Eaton does not do HTTPS by default,
it can be configured to do so.

Support when available.

Mitigate downgrade attack by
stickying the cert fingerprint.
If fingerprint is present, then refuse
to even think about port 80.
2023-02-15 17:03:35 -05:00
Jarrod Johnson 90af99e864 Add more clear error on syncfile mistake
If a bad node was included in
a syncfile, the error was highly misleading.

Provide a more clear indicaiton of the problem on failure.
2023-02-14 14:53:40 -05:00
Jarrod Johnson 09ce824c85 Fix bad lookup attempts on slashed addr
While this should in theory be
harmless, it exacerbates some
DNS setups that would look
up the normal result quickly,
but would stall on
a bad lookup.
2023-02-14 14:53:40 -05:00
Jarrod Johnson 9c1e7a7142 Allow interfaces to supersede default
In some scenarios, the 'default'
interface is overlapped by another connection, either
identical or as a superset in a bond.

Whittle down the default
interface if superseded
to mitigate duplicate interface setup.
2023-02-14 14:53:40 -05:00
Jarrod Johnson 36195198a6 Add fallback for newer msgpack
Newer msgpack refuses the encoding argument, use raw=False instead.

Further, newer msgpack refuses to accept int as key by default.
Opt into it as the risk is hash collision due to msgpack int being used directly, and
we aren't dealing with untrusted
peer (we only talk to ourselves).
2023-02-14 14:53:40 -05:00
Jarrod Johnson 3798a33213 Merge pull request #121 from Tkucherera/nodeconsole
nodeconsole documentation: passthrough options
2023-02-14 08:45:43 -05:00
Tinashe 251b307bd7 nodeconsole documentation: passthrough options 2023-02-14 08:27:25 -05:00
Jarrod Johnson bb7a72db65 Fix for ipv6 deployment
Need to avoid double-bracketing of the server and also disable globbing
so curl does not mistake the ip address for a glob attempt.
2023-02-13 09:36:42 -05:00
Jarrod Johnson fcde113e08 Add a check of dns.domain to selfcheck for node 2023-02-08 14:45:16 -05:00
Jarrod Johnson a02f617b3d Add DDR5 dimm to nodeinventory CLI output 2023-02-07 14:01:18 -05:00
Jarrod Johnson 7f1ac92fc9 Store mgr from confluent= specificate 2023-02-01 16:51:21 -05:00
Jarrod Johnson 8cf97833ab Fixes for certificate directed discovery 2023-02-01 13:09:40 -05:00
Jarrod Johnson 3e747069d9 Try to get verified bay from SMMs
With V3 systems, we can now ask
the SMMs for the certificates
and use that for a verified
measurement, regardless of
whether the XCC is returning
the correct bay number.
2023-02-01 12:57:27 -05:00
Jarrod Johnson c687da4d5f Tweak architecture override on import 2023-01-31 15:57:41 -05:00
Jarrod Johnson 340ccc422c Specify check for arch override of addons.cpio
For now, keep using x86_64 as
default, but allow overrides
for other architectures.

One day it may be cleaner to move all addons.cpio to
arch specific subdirs.
2023-01-31 15:27:45 -05:00
Jarrod Johnson 2c3afac576 Restructure aarch64 addons
Avoid tripping over current copy over, prepare
for smarter selection by architecture.
2023-01-31 15:10:49 -05:00
Jarrod Johnson 8e1cc63ac0 Correct spelling of keyword argument in ipmi 2023-01-31 15:00:22 -05:00
Jarrod Johnson dc6c7c1acc Make sure both el8 and el9 binaries are packed 2023-01-31 13:29:24 -05:00
Jarrod Johnson 5c309db47c Further ARMv8 support
Handle aarch64 differences in
at least some distributions.
2023-01-31 11:20:40 -05:00
Jarrod Johnson 976e9ef563 Bump version on genesis 2023-01-31 09:10:23 -05:00
Jarrod Johnson 0efd2a4d74 Fix the amended license gathering 2023-01-31 08:58:56 -05:00
Jarrod Johnson 424830471d Note how to fetch srpms associated with genesis 2023-01-31 08:54:03 -05:00
Jarrod Johnson 23f33a8420 Revamp license gathering for genesis 2023-01-31 08:52:32 -05:00
Jarrod Johnson 521a58c1d9 Have a utility to generate NOTICE from tmux
tmux basically defers to the c files, so
generate a NOTICE file from c files.
2023-01-31 08:37:42 -05:00
Jarrod Johnson ce375a1162 Merge pull request #120 from Tkucherera/master
Adding missing imports
2023-01-30 14:48:05 -05:00
Tkucherera caee136012 Merge branch 'xcat2:master' into master 2023-01-30 14:15:53 -05:00
Tinashe 2e283f3442 nodeconsole: missing imports time and socket 2023-01-30 14:13:44 -05:00
Jarrod Johnson 2b01d9fbfa Properly store all candidate host ip addresses
This is needed to ensure that mis-detected primary ip
falls through to another viable ip
2023-01-30 12:40:40 -05:00
Jarrod Johnson 627bc9ffe3 Modify pkglist for aarch64 2023-01-27 12:14:37 -05:00
Jarrod Johnson 3e71e103b1 Fix unpacking of el8 and el9 built sources 2023-01-27 10:47:27 -05:00
Jarrod Johnson a90cd8515e Tweak osdeploy for ARM setup 2023-01-27 10:43:29 -05:00
Jarrod Johnson 284d042afe Merge pull request #119 from Tkucherera/master
nodeconsole windowed and tiled functionality
2023-01-27 09:33:45 -05:00
Tinashe 2cc134adeb nodeconsole: allow for passthrough args 2023-01-27 09:30:46 -05:00
Jarrod Johnson 02e242ec4e Restore link local cert in apiclient 2023-01-27 09:13:47 -05:00
Jarrod Johnson 1777223232 Fixes for osdeploy arm ipxe init 2023-01-27 08:40:31 -05:00
Jarrod Johnson 648290ffbc Begin implementing aarch64 deploy support 2023-01-27 08:00:38 -05:00
Tinashe c9b72225a9 nodeeventlog timeframe documentation 2023-01-26 10:37:30 -05:00
Tinashe 3433635e9b nodeeventlog: timeframe option 2023-01-26 10:16:33 -05:00
Tkucherera 7a6c4fc5b2 Merge branch 'xcat2:master' into master 2023-01-26 10:05:04 -05:00
Tinashe f176a836ae nodeecentlog: add timeframe option 2023-01-26 10:01:02 -05:00
Jarrod Johnson ce324e90f7 Draft spec to generate addons-aarch64 files 2023-01-25 12:54:03 -05:00
Tinashe 23ea53ab55 console geometry 100x31 2023-01-24 11:18:21 -05:00
Jarrod Johnson d14d28caf8 Confirm TLS connectivity when scanning hosts
In certain environments, Confluent may have an IP address that
is fake, but then there is elsewhere with that same IP for real.

To mitigate this, follow up basic connectivity with proof of having
an associated certificate.
2023-01-24 08:22:00 -05:00
Jarrod Johnson 0008998680 Add api method to request all mac data
This will provide easy way for
client to get FDB data, potentially
for use in conjunction with discovery data.

For now, leave LLDP out, as that isn't currently cached
at the confluent layer.
2023-01-23 13:37:29 -05:00
Jarrod Johnson 2e059b5887 Make an API for getting full discovery data in one fetch
This makes for faster nodediscover being possible, also
makes web management of the data easier
2023-01-23 11:47:33 -05:00
Jarrod Johnson 792e6472e4 Fix IPv6 addresses_match
fe80:: could be submitted during
collective startup, handle that problem appropriately.
2023-01-23 11:24:25 -05:00
Tinashe b965f9b758 nodeconsole windowed and tiled functionality 2023-01-20 16:41:56 -05:00
Jarrod Johnson a522e17a63 Merge pull request #118 from Tkucherera/master
nodeeventlog man page -l option
2023-01-20 14:49:11 -05:00
Tinashe 9f3b934ea4 nodeeventlog man page -l option 2023-01-20 14:24:32 -05:00
Jarrod Johnson 680ca2c4a2 Merge pull request #117 from Tkucherera/master
nodeeventlog: return last n entries
2023-01-20 12:51:16 -05:00
Tinashe c3d0d255d3 nodeeventlog: -l return last n lines for each node 2023-01-20 12:13:15 -05:00
Tinashe 46d0a8d222 nodeeventlog: return last n entries 2023-01-20 10:09:52 -05:00
Jarrod Johnson 75f020f53c Have apiarmed continuous be properly respected for shared secret
Remote media was erroneously being invalidated, despite user opting
out of the strict security.
2023-01-19 14:54:18 -05:00
Jarrod Johnson c09e8448c2 Change to POSIX compliant range
POSIX allows ., but does not allow +.  This was a problem with EL 8.4 libxcrypt,
though is not a problem otherwise.
2023-01-19 14:53:35 -05:00
Jarrod Johnson 01f939b871 Have SuSE path also not be bothered by inability to restart web service 2023-01-18 08:50:30 -05:00
Jarrod Johnson 1f23750356 Add affluent detection to confluent
Affluent agent will now have an SSDP
response.  Add support for at
least recognizing and presenting
this in the discovery data.
2023-01-17 15:11:12 -05:00
Jarrod Johnson d1265af828 Handle more errors
subprocess may throw other errors that aren't calledprocesserrors,
in newer python versions.  Handle the case more broadly.
2023-01-17 10:04:10 -05:00
Jarrod Johnson 0929f059e2 Increase track size in dir2img
Larger images still run afoul of track limits
in mtools.  Make tracks 4 times as big
to lower number of required tracks.
2023-01-17 10:02:51 -05:00
Jarrod Johnson 40c3f2da53 Actually display deployment state, when available 2023-01-13 13:02:17 -05:00
Jarrod Johnson 51e53405d8 Add attributes for profiles to report state
Profiles may want to report things
like success and error
2023-01-13 12:54:21 -05:00
Jarrod Johnson d644d34b60 Add '-s' to nodeinventory
This allows a quick command to
get attributes into confluent
for manually added nodes,
without having to go through 'discovery' process.
2023-01-13 12:29:36 -05:00
Jarrod Johnson 7f31ae5b57 Fix syntax error 2023-01-13 11:15:51 -05:00
Jarrod Johnson a09e1a3f8b Handle IPv6 not set on IPMI nodes 2023-01-13 11:07:13 -05:00
Jarrod Johnson 50c073670d Explicitly declare Textmode during autoconsole
This enables a workable console during text install,
while also allowing graphical to run
2023-01-13 10:54:29 -05:00
Jarrod Johnson bc452b9b9a Restore role-less group
If a group is missing a role,
coerce it to administrator
2023-01-13 10:01:52 -05:00
Jarrod Johnson 453d1f9ceb Add IPv6 configuration support
For redfish and IPMI devices,
support new IPv6 static configuration
controls
2023-01-13 10:01:28 -05:00
Jarrod Johnson feed125c86 Fix restoration of old confluent db
Old confluent DB may have None in role. This is no longer
allowed.  Restore such entries by coercing them to 'Administrator'
which is how old confluent treated such users.
2023-01-12 08:38:55 -05:00
119 changed files with 4297 additions and 499 deletions
+4 -4
View File
@@ -21,17 +21,17 @@ def create_image(directory, image, label=None):
currsz = (currsz // 512 +1) * 512
datasz += currsz
datasz += ents * 32768
datasz = datasz // 4096 + 1
datasz = datasz // 16384 + 1
with open(image, 'wb') as imgfile:
imgfile.seek(datasz * 4096 - 1)
imgfile.seek(datasz * 16384 - 1)
imgfile.write(b'\x00')
if label:
subprocess.check_call(['mformat', '-i', image, '-v', label,
'-r', '16', '-d', '1', '-t', str(datasz),
'-s', '4','-h', '2', '::'])
'-s', '16','-h', '2', '::'])
else:
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
str(datasz), '-s', '4','-h', '2', '::'])
str(datasz), '-s', '16','-h', '2', '::'])
# Some clustered filesystems will have the lock from mformat
# linger after close (mformat doesn't unlock)
# do a blocking wait for shared lock and then explicitly
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-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.
from collections import deque
import optparse
import os
import select
import signal
import subprocess
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run():
global devnull
devnull = open(os.devnull, 'rb')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of commands to run at a time')
argparser.add_option('-k', '--security', action='store_true',
help='Update SSH setup')
argparser.add_option('-F', '--sync', action='store_true',
help='Run the syncfiles associated with the currently completed OS profile on the noderange')
argparser.add_option('-P', '--scripts',
help='Re-run specified scripts, with full path under scripts, e.g. post.d/first,firstboot.d/second')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
#argparser.disable_interspersed_args()
(options, args) = argparser.parse_args()
if len(args) < 1:
argparser.print_help()
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
currprocs = 0
all = set([])
pipedesc = {}
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
nodemap = {}
cmdparms = []
nodes = []
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
break
node = res['item']['href'][:-1]
nodes.append(node)
cmdstorun = []
if options.security:
cmdstorun.append(['run_remote', 'setupssh'])
if options.sync:
cmdstorun.append(['run_remote_python', 'syncfileclient'])
if options.scripts:
for script in options.scripts.split(','):
cmdstorun.append(['run_remote', script])
if not cmdstorun:
argparser.print_help()
sys.exit(1)
idxbynode = {}
cmdvbase = ['bash', '/etc/confluent/functions']
for sshnode in nodes:
idxbynode[sshnode] = 1
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[0]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy, _, _ = select.select(all, [], [], 10)
while all:
pernodeout = {}
for r in rdy:
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0)[0]:
data = r.readline()
if data:
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = []
pernodeout[node].append(data)
else:
data = client.stringify(data)
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.flush()
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
r.close()
if desc['type'] == 'stdout':
if idxbynode[node] < len(cmdstorun):
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[idxbynode[node]]
idxbynode[node] += 1
run_cmdv(node, cmdv, all, pipedesc)
elif pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
line = line.lstrip('\x08')
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy, _, _ = select.select(all, [], [], 10)
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
'type': 'stderr'}
all.add(nopen.stdout)
all.add(nopen.stderr)
if __name__ == '__main__':
run()
+6
View File
@@ -90,6 +90,12 @@ cfgpaths = {
'bmc.ipv4_gateway': (
'configuration/management_controller/net_interfaces/management',
'ipv4_gateway'),
'bmc.static_ipv6_addresses': (
'configuration/management_controller/net_interfaces/management',
'static_v6_addresses'),
'bmc.static_ipv6_gateway': (
'configuration/management_controller/net_interfaces/management',
'static_v6_gateway'),
'bmc.hostname': (
'configuration/management_controller/hostname', 'hostname'),
}
+206 -7
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/libexec/platform-python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
@@ -23,13 +23,17 @@ path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
import confluent.logreader as logreader
import time
import socket
import re
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] node",
usage="Usage: %prog [options] <noderange> [kill][-- [passthroughoptions]]",
epilog="Command sequences are available while connected to a console, hit "
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
"For example, ctrl-'e', then 'c', then '.' will exit the current "
@@ -38,6 +42,10 @@ argparser.add_option('-t', '--tile', action='store_true', default=False,
help='Tile console windows in the terminal')
argparser.add_option('-l', '--log', action='store_true', default=False,
help='Enter log replay mode instead of showing a live console')
argparser.add_option('-T', '--Timestamp', action='store_true', default=False,
help= 'Dump log in stdout with timestamps')
argparser.add_option('-w','--windowed', action='store_true', default=False,
help='Open terminal windows for each node. The '
'environment variable NODECONSOLE_WINDOWED_COMMAND '
@@ -58,10 +66,26 @@ argparser.add_option('-w','--windowed', action='store_true', default=False,
'--shell-type login". If the NODECONSOLE_WINDOWED_COMMAND '
'environment variable isn\'t set, xterm will be used by'
'default.')
(options, args) = argparser.parse_args()
pass_through_args = []
killcon = False
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'kill':
killcon = True
pass_through_args = args[1:]
args = args[:1]
except IndexError:
argparser.print_help()
sys.exit(1)
if len(args) != 1:
argparser.print_help()
sys.exit(1)
if options.log:
logname = args[0]
if not os.path.exists(logname) and logname[0] != '/':
@@ -71,14 +95,105 @@ if options.log:
sys.exit(1)
logreader.replay_to_console(logname)
sys.exit(0)
#added functionality for wcons
if options.windowed:
if options.Timestamp:
logname = args[0]
if not os.path.exists(logname) and logname[0] != '/':
logname = os.path.join('/var/log/confluent/consoles', logname)
if not os.path.exists(logname):
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
sys.exit(1)
logreader.dump_to_console(logname)
sys.exit(0)
def kill(noderange):
sess = client.Command()
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
if not envstring:
envlist=["xterm", "-e"]
envstring = 'xterm'
nodes = []
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
node = res.get('item', {}).get('href', '/').replace('/', '')
if not node:
sys.stderr.write(res.get('error', repr(res)) + '\n')
sys.exit(1)
nodes.append(node)
for node in nodes:
s=socket.socket(socket.AF_UNIX)
winid=None
try:
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
wintr=win.communicate()[0]
for line in wintr.decode('utf-8').split('\n'):
if 'console: {0}'.format(node) in line or 'confetty' in line:
win_obj = [ele for ele in line.split(' ') if ele.strip()]
winid = win_obj[0]
except:
print("Error: cannot retrieve window id of node {}".format(node))
if winid:
ps_data=subprocess.Popen(['xkill', '-id', winid ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sys.exit(0)
def handle_geometry(envlist, sizegeometry, side_pad=0, top_pad=0, first=False):
if '-geometry' in envlist:
g_index = envlist.index('-geometry')
elif '-g' in envlist:
g_index = envlist.index('-g')
else:
g_index = 0
if g_index:
if first:
envlist[g_index+1] = '{0}+{1}+{2}'.format(envlist[g_index+1],side_pad, top_pad)
else:
envlist[g_index+1] = '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad)
else:
envlist.insert(1, '-geometry')
envlist.insert(2, '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad))
g_index = 1
return envlist
# add funcltionality to close/kill all open consoles
if killcon:
kill(noderange)
#added functionality for wcons
if options.windowed:
result=subprocess.Popen(['xwininfo', '-root'], stdout=subprocess.PIPE)
rootinfo=result.communicate()[0]
result.wait()
for line in rootinfo.decode('utf-8').split('\n'):
if 'Width' in line:
screenwidth = int(line.split(':')[1])
if 'Height' in line:
screenheight = int(line.split(':')[1])
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
if not envstring:
sizegeometry='100x31'
corrected_x, corrected_y = (13,84)
envlist = handle_geometry(['xterm'] + pass_through_args + ['-e'],sizegeometry, first=True)
#envlist=['xterm', '-bg', 'black', '-fg', 'white', '-geometry', '{sizegeometry}+0+0'.format(sizegeometry=sizegeometry), '-e']
else:
envlist=os.environ.get('NODECONSOLE_WINDOWED_COMMAND').split(' ')
if envlist[0] == 'xterm':
if '-geometry' in envlist:
g_index = envlist.index('-geometry')
elif '-g' in envlist:
g_index = envlist.index('-g')
else:
g_index = 0
if g_index:
envlist[g_index+1] = envlist[g_index+1] + '+0+0'
else:
envlist.insert(1, '-geometry')
envlist.insert(2, '100x31+0+0')
g_index = 1
nodes = []
sess = client.Command()
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
@@ -87,9 +202,87 @@ if options.windowed:
sys.stderr.write(res.get('error', repr(res)) + '\n')
sys.exit(1)
nodes.append(node)
if options.tile and not envlist[0] == 'xterm':
sys.stderr.write('[ERROR] UNSUPPORTED OPTIONS. \nWindowed and tiled consoles are only supported when using xterm \n')
sys.exit(1)
firstnode=nodes[0]
nodes.pop(0)
with open(os.devnull, 'wb') as devnull:
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode), '-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode) ] , stdin=devnull)
time.sleep(2)
s=socket.socket(socket.AF_UNIX)
winid=''
try:
s.connect('/tmp/controlpath-{firstnode}'.format(firstnode=firstnode))
s.recv(64)
s.send(b'GETWINID')
winid=s.recv(64).decode('utf-8')
except:
time.sleep(2)
# try to get id of first panel/xterm window using name
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
wintr=win.communicate()[0]
for line in wintr.decode('utf-8').split('\n'):
if 'console: {firstnode}'.format(firstnode=firstnode) in line or 'confetty' in line:
win_obj = [ele for ele in line.split(' ') if ele.strip()]
winid = win_obj[0]
if winid:
firstnode_window=subprocess.Popen(['xwininfo', '-id', '{winid}'.format(winid=winid)], stdout=subprocess.PIPE)
xinfo=firstnode_window.communicate()[0]
xinfl = xinfo.decode('utf-8').split('\n')
for line in xinfl:
if 'Absolute upper-left X:' in line:
side_pad = int(line.split(':')[1])
elif 'Absolute upper-left Y:' in line:
top_pad = int(line.split(':')[1])
elif 'Width:' in line:
window_width = int(line.split(':')[1])
elif 'Height' in line:
window_height = int(line.split(':')[1])
elif '-geometry' in line:
l = re.split(' |x|-|\+', line)
l_nosp = [ele for ele in l if ele.strip()]
wmxo = int(l_nosp[1])
wmyo = int(l_nosp[2])
sizegeometry = str(wmxo) + 'x' + str(wmyo)
else:
pass
window_width += side_pad*2
window_height += side_pad+top_pad
screenwidth -= wmxo
screenheight -= wmyo
currx = window_width
curry = 0
maxcol = int(screenwidth/window_width)
for node in sortutil.natural_sort(nodes):
if options.tile and envlist[0] == 'xterm':
corrected_x = currx
corrected_y = curry
xgeometry = '{0}+{1}+{2}'.format(sizegeometry, corrected_x, corrected_y)
currx += window_width
if currx >= screenwidth:
currx=0
curry += window_height
if curry > screenheight:
curry =top_pad
if not envstring:
envlist= handle_geometry(envlist, sizegeometry, corrected_x, corrected_y)
else:
if g_index:
envlist[g_index+1] = xgeometry
elif envlist[0] == 'xterm':
envlist=handle_geometry(envlist, sizegeometry, side_pad, top_pad)
side_pad+=(side_pad+1)
top_pad+=(top_pad+30)
else:
pass
with open(os.devnull, 'wb') as devnull:
subprocess.Popen(envlist + [confettypath, '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)], stdin=devnull)
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node), '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
sys.exit(0)
#end of wcons
if options.tile:
@@ -103,8 +296,13 @@ if options.tile:
sys.exit(1)
nodes.append(node)
initial = True
in_tmux = False
pane = 0
sessname = 'nodeconsole_{0}'.format(os.getpid())
if os.environ.get("TMUX"):
initial = False
in_tmux = True
subprocess.call(['tmux', 'rename-session', sessname])
for node in sortutil.natural_sort(nodes):
panename = '{0}:{1}'.format(sessname, pane)
if initial:
@@ -125,7 +323,8 @@ if options.tile:
pane += 1
subprocess.call(['tmux', 'select-pane', '-t', sessname])
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
if not in_tmux:
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
else:
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
+14 -3
View File
@@ -49,7 +49,7 @@ def armonce(nr, cli):
def setpending(nr, profile, cli):
args = {'deployment.pendingprofile': profile}
args = {'deployment.pendingprofile': profile, 'deployment.state': '', 'deployment.state_detail': ''}
if not profile.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
args['deployment.profile'] = ''
@@ -132,7 +132,7 @@ def main(args):
if node not in databynode:
databynode[node] = {}
for attr in dbn[node]:
if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile'):
if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile', 'deployment.state', 'deployment.state_detail'):
databynode[node][attr] = dbn[node][attr].get('value', '')
for node in sortutil.natural_sort(databynode):
profile = databynode[node].get('deployment.pendingprofile', '')
@@ -153,7 +153,18 @@ def main(args):
armed = ' (node authentication armed)'
else:
armed = ''
print('{0}: {1}{2}'.format(node, profile, armed))
stateinfo = ''
deploymentstate = databynode[node].get('deployment.state', '')
if deploymentstate:
statedetails = databynode[node].get('deployment.state_detail', '')
if statedetails:
stateinfo = '{}: {}'.format(deploymentstate, statedetails)
else:
stateinfo = deploymentstate
if stateinfo:
print('{0}: {1} ({2})'.format(node, profile, stateinfo))
else:
print('{0}: {1}{2}'.format(node, profile, armed))
sys.exit(0)
if args.network and not args.prepareonly:
return rc
+64 -5
View File
@@ -17,6 +17,7 @@
import codecs
from datetime import datetime as dt
from datetime import timedelta
import optparse
import os
import signal
@@ -41,7 +42,18 @@ argparser = optparse.OptionParser(
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to clear if clearing log, '
'prompting if over the threshold')
'prompting if over the threshold')
argparser.add_option('-l', '--lines', type='int',
help='return the last <n> entries '
'for each node in the eventlog. '
)
argparser.add_option('-t', '--timeframe', type='string',
help='return entries within a specified timeframe '
'for each node in the eventlog. This will return '
'entries from the last hours or days. '
'1h would be one hour, 4d would be four days. '
'format <num>h or <num>d'
)
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -89,12 +101,34 @@ def format_event(evt):
msg = ''
return ' '.join(retparts) + msg
if deletemode:
func = session.delete
session.stop_if_noderange_over(noderange, options.maxnodes)
else:
func = session.read
if options.timeframe:
try:
delta = int(options.timeframe[:-1])
except ValueError:
argparser.print_help()
sys.exit(1)
if options.timeframe[-1].lower() == 'd':
tdelta = timedelta(days=delta)
elif options.timeframe[-1].lower() == 'h':
tdelta = timedelta(hours=delta)
else:
argparser.print_help()
sys.exit(1)
timeframe = dt.now() - tdelta
event_dict = {}
nodes = []
for res in session.read('/noderange/{0}/nodes/'.format(args[0])):
node = res.get('item', {}).get('href', '/').replace('/', '')
nodes.append(node)
event_dict[node] = []
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
@@ -107,6 +141,31 @@ for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
sys.stderr.write('{0}: {1}\n'.format(node, thisdata['error']))
exitcode |= 1
if 'events' in thisdata:
evtdata = thisdata['events']
for evt in evtdata:
print('{0}: {1}'.format(node, format_event(evt)))
evtdata = thisdata['events']
if options.lines:
event_dict[node].extend(evtdata)
else:
for evt in evtdata:
if options.timeframe:
# check if line is in timeframe
if 'timestamp' in evt and evt['timestamp'] is not None:
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
if display > timeframe:
print('{0}: {1}'.format(node, format_event(evt)))
else:
print('{0}: {1}'.format(node, format_event(evt)))
if options.lines:
for node in nodes:
evtdata_list = event_dict[node]
if len(evtdata_list) != 0:
if len(evtdata_list) > options.lines:
evtdata_list = evtdata_list[-abs(options.lines):]
for evt in evtdata_list:
if options.timeframe:
if 'timestamp' in evt and evt['timestamp'] is not None:
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
if display > timeframe:
print('{0}: {1}'.format(node, format_event(evt)))
else:
print('{0}: {1}'.format(node, format_event(evt)))
+32 -4
View File
@@ -53,6 +53,8 @@ def print_mem_info(node, prefix, meminfo):
memdescfmt += '3-{1} '
elif 'DDR4' in meminfo['memory_type']:
memdescfmt += '4-{1} '
elif 'DDR5' in meminfo['memory_type']:
memdescfmt += '5-{1} '
elif 'DCPMM' in meminfo['memory_type']:
memdescfmt = '{0}GB {1} '
meminfo['module_type'] = 'DCPMM'
@@ -99,6 +101,7 @@ usedprefixes = set([])
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
argparser.add_option('-j', '--json', action='store_true', help='Output JSON')
argparser.add_option('-s', '--store', action='store_true', help='Store serial, model, and uuid into id.serial, id.model, and id.uuid')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -126,17 +129,36 @@ if len(args) > 1:
try:
if options.json:
databynode = {}
if options.store and len(args) <= 1:
url = '/noderange/{0}/inventory/hardware/all/system'
pushattribs = {}
session = client.Command()
for res in session.read(url.format(noderange)):
printerror(res)
if 'databynode' not in res:
continue
for node in res['databynode']:
if options.store and node not in pushattribs:
pushattribs[node] = {}
printerror(res['databynode'][node], node)
if 'inventory' not in res['databynode'][node]:
continue
for inv in res['databynode'][node]['inventory']:
prefix = inv['name']
if options.store and prefix == 'System':
currinfo = inv.get('information', {})
curruuid = currinfo.get('UUID', '')
if curruuid:
curruuid = curruuid.lower()
pushattribs[node]['id.uuid'] = curruuid
currserial = currinfo.get('Serial Number', '')
if currserial:
currserial = currserial.strip()
pushattribs[node]['id.serial'] = currserial
currmodelnum = currinfo.get('Model', '')
if currmodelnum:
currmodelnum = currmodelnum.strip()
pushattribs[node]['id.model'] = currmodelnum
idx = 2
while (node, prefix) in usedprefixes:
prefix = '{0} {1}'.format(inv['name'], idx)
@@ -148,7 +170,7 @@ try:
if node not in databynode:
databynode[node] = {}
databynode[node][prefix] = inv
else:
elif not options.store:
print('{0}: {1}: Not Present'.format(node, prefix))
continue
info = inv['information']
@@ -179,12 +201,18 @@ try:
databynode[node] = {}
databynode[node][prefix] = inv
break
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
pretty(datum),
info[datum]))
elif not options.store:
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
pretty(datum),
info[datum]))
if options.json:
print(json.dumps(databynode, sort_keys=True, indent=4,
separators=(',', ': ')))
if pushattribs:
for node in pushattribs:
for rsp in session.update('/nodes/{0}/attributes/current'.format(node), pushattribs[node]):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
+9 -1
View File
@@ -37,6 +37,12 @@ class hybridcsv(csv.excel):
lineterminator = '\n'
def floatformat(num):
fm = u'{:.5f}'.format(num).rstrip('0')
if fm[-1:] == u'.':
return fm + u'0'
return fm
csv.register_dialect('hybrid', hybridcsv)
import confluent.client as client
@@ -135,7 +141,7 @@ def sensorpass(showout=True, appendtime=False):
if sensedata['value'] is None:
showval = ''
elif isinstance(sensedata['value'], float):
showval = u' {0:.5f} '.format(sensedata['value'])
showval = u' {0} '.format(floatformat(sensedata['value']))
else:
showval = u' {0} '.format(sensedata['value'])
if sensedata['units'] not in (None, u''):
@@ -191,6 +197,8 @@ def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
datum = ','.join([datum, healthstates])
else:
datum = healthstates
if isinstance(datum, float):
datum = floatformat(datum)
rowdata.append(datum)
except KeyError:
rowdata.append('N/A')
+42
View File
@@ -296,5 +296,47 @@ def replay_to_console(txtfile):
sys.exit(1)
_replay_to_console(txtfile, binfile)
def dump_to_console(txtfile):
if os.path.exists(txtfile + '.cbl'):
binfile = txtfile + '.cbl'
elif '.' not in txtfile:
if '/' not in txtfile:
txtfile = os.getcwd() + '/' + txtfile
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(txtfile + '.cbl'))
sys.exit(1)
else:
fileparts = txtfile.split('.')
prefix = '.'.join(fileparts[:-1])
binfile = prefix + '.cbl.' + fileparts[-1]
if not os.path.exists(binfile):
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(binfile))
sys.exit(1)
replay = LogReplay(txtfile, binfile)
quitit = False
writeout('\x1b[2J\x1b[;H')
prepend = ''
try:
while not quitit:
newdata, delay = replay.get_output(False)
if newdata:
if prepend:
newdata = prepend + newdata
prepend = b''
writeout(newdata.replace(b'\r\n', '\r\n[ {0} ] '.format(time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))).encode('utf8')))
newdata = ''
try:
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.flush()
else:
sys.stdout.flush()
except IOError:
pass
else:
break
except Exception:
writeout('\x1b[m\x1b[?25h\n')
raise
writeout('\x1b[m\x1b[?25h\n')
if __name__ == '__main__':
replay_to_console(sys.argv[1])
+20 -1
View File
@@ -2,7 +2,7 @@ nodeconsole(8) -- Open a console to a confluent node
=====================================================
## SYNOPSIS
`nodeconsole [options] <noderange>`
`nodeconsole [options] <noderange> [kill][-- [passthroughoptions]]`
## DESCRIPTION
@@ -16,6 +16,9 @@ will initiate an automatic retry interval that is randomized between 2 and 4 min
The reopen escape sequence below requests an immediate retry, as does connecting
a new session.
When a windowed console is open the `nodeconsole <noderange> kill` command will kill the
console process which will result in the console window closing.
## OPTIONS
* `-t`, `--tile`:
@@ -26,6 +29,11 @@ a new session.
Perform a log reply on the current, local log in /var/log/confluent/consoles.
If in collective mode, this only makes sense to use on the current collective
manager at this time.
* `-T`, `--Timestamp`:
Dump the log with Timpstamps on the current, local log in /var/log/confluent/consoles.
If in collective mode, this only makes sense to use on the current collective
manager at this time.
* `-w`, `--windowed`:
Open terminal windows for each node. The
@@ -86,3 +94,14 @@ keystroke will be interpreted as a command. The following commands are availabl
Get a list of supported commands
* `<ent>`:
Hit enter to skip entering a command at the escape prompt.
## PASSTHROUGH OPTIONS
While opening a windowed console with xterm or any other console of choice. The
nodeconsole command gives capality to specify passthrough options targeted at
the console. All options after the -- will be parsed the console program. For
example, opening a windowed console using xterm with a black background.
`nodeconconsole -w n1 -- -bg black`
@@ -15,6 +15,15 @@ noderange.
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to clear if clearing log, prompting if
over the threshold
* `-l LINES`, `--lines=LINES`:
return the last <n> entries for each node in the eventlog.
* `-t TIMEFRAME`, `--timeframe=TIMEFRAME`:
return entries within a specified timeframe for each node's event log.
This will return entries from the last <n> hours or days. 1h would be
entries from with the last one hour.
* `-h`, `--help`:
Show help message and exit
+14 -3
View File
@@ -11,13 +11,24 @@ mv confluent_osdeploy.tar.xz ~/rpmbuild/SOURCES/
cd -
mkdir -p el9bin/opt/confluent/bin
mkdir -p el9bin/stateless-bin
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t fedorabuilder make -C /buildutils
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t fedorabuild make -C /buildutils
cd utils
mv confluent_imginfo copernicus clortho autocons ../el9bin/opt/confluent/bin
mv start_root urlmount ../el9bin/stateless-bin/
cp confluent_imginfo copernicus clortho autocons ../el9bin/opt/confluent/bin
cp start_root urlmount ../el9bin/stateless-bin/
make clean
cd ..
mkdir -p el8bin/opt/confluent/bin
mkdir -p el8bin/stateless-bin
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t el7build make -C /buildutils
cd utils
cp confluent_imginfo copernicus clortho autocons ../el8bin/opt/confluent/bin
cp start_root urlmount ../el8bin/stateless-bin/
make clean
cd ..
tar Jcvf confluent_el9bin.tar.xz el9bin/
tar Jcvf confluent_el8bin.tar.xz el8bin/
mv confluent_el8bin.tar.xz ~/rpmbuild/SOURCES/
mv confluent_el9bin.tar.xz ~/rpmbuild/SOURCES/
rm -rf el9bin
rm -rf el8bin
rpmbuild -ba confluent_osdeploy.spec
+32
View File
@@ -0,0 +1,32 @@
VERSION=`git describe|cut -d- -f 1`
NUMCOMMITS=`git describe|cut -d- -f 2`
if [ "$NUMCOMMITS" != "$VERSION" ]; then
VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3`
fi
sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy-aarch64.spec.tmpl > confluent_osdeploy-aarch64.spec
cd ..
cp ../LICENSE .
tar Jcvf confluent_osdeploy.tar.xz confluent_osdeploy
mv confluent_osdeploy.tar.xz ~/rpmbuild/SOURCES/
cd -
mkdir -p el9bin/opt/confluent/bin
mkdir -p el9bin/stateless-bin
mkdir -p el8bin/opt/confluent/bin
mkdir -p el8bin/stateless-bin
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t el9builder make -C /buildutils
cd utils
mv confluent_imginfo copernicus clortho autocons ../el9bin/opt/confluent/bin
mv start_root urlmount ../el9bin/stateless-bin/
cd ..
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t el8builder make -C /buildutils
cd utils
mv confluent_imginfo copernicus clortho autocons ../el8bin/opt/confluent/bin
mv start_root urlmount ../el8bin/stateless-bin/
cd ..
tar Jcvf confluent_el9bin.tar.xz el9bin/
tar Jcvf confluent_el8bin.tar.xz el8bin/
mv confluent_el8bin.tar.xz ~/rpmbuild/SOURCES/
mv confluent_el9bin.tar.xz ~/rpmbuild/SOURCES/
rm -rf el9bin
rm -rf el8bin
rpmbuild -ba confluent_osdeploy-aarch64.spec
@@ -21,6 +21,10 @@ class InvalidApiKey(Exception):
def msg_align(len):
return (len + 3) & ~3
try:
_protoparm = ssl.PROTOCOL_TLS_CLIENT
except AttributeError:
_protoparm = ssl.PROTOCOL_SSLv23
cryptname = ctypes.util.find_library('crypt')
if not cryptname:
if os.path.exists('/lib64/libcrypt.so.2') or os.path.exists('/usr/lib64/libcrypt.so.2'):
@@ -253,6 +257,7 @@ class HTTPSClient(client.HTTPConnection, object):
self.stdheaders['CONFLUENT_NODENAME'] = node
if line.startswith('MANAGER:') and not host:
host = line.split(' ')[1]
self.hosts.append(host)
if not plainhost:
plainhost = host
if line.startswith('EXTMGRINFO:'):
@@ -304,6 +309,10 @@ class HTTPSClient(client.HTTPConnection, object):
def check_connections(self):
foundsrv = None
hosts = self.hosts
ctx = ssl.SSLContext(_protoparm)
ctx.load_verify_locations('/etc/confluent/ca.pem')
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.check_hostname = True
for timeo in (0.1, 5):
for host in hosts:
try:
@@ -311,10 +320,16 @@ class HTTPSClient(client.HTTPConnection, object):
psock = socket.socket(addrinf[0])
psock.settimeout(timeo)
psock.connect(addrinf[4])
chost = host.split('%', 1)[0]
ctx.wrap_socket(psock, server_hostname=chost)
foundsrv = host
psock.close()
break
except OSError:
except (OSError, socket.error):
continue
except ssl.SSLError:
continue
except ssl.CertificateError:
continue
else:
continue
@@ -342,11 +357,12 @@ class HTTPSClient(client.HTTPConnection, object):
raise Exception('Unable to reach any hosts')
return foundsrv
def connect(self):
def connect(self, tries=3):
addrinf = socket.getaddrinfo(self.host, self.port)[0]
psock = socket.socket(addrinf[0])
psock.settimeout(15)
psock.connect(addrinf[4])
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ctx = ssl.SSLContext(_protoparm)
ctx.load_verify_locations('/etc/confluent/ca.pem')
host = self.host.split('%', 1)[0]
if '[' not in host and ':' in host:
@@ -364,6 +380,10 @@ class HTTPSClient(client.HTTPConnection, object):
self.errout.write(errmsg)
self.errout.flush()
sys.exit(1)
except socket.timeout:
if not tries:
raise
return self.connect(tries=tries-1)
def grab_url(self, url, data=None, returnrsp=False):
return self.grab_url_with_status(url, data, returnrsp)[1]
@@ -296,6 +296,10 @@ class NetworkManager(object):
subprocess.check_call(['nmcli', 'c', 'u', u])
else:
subprocess.check_call(['nmcli', 'c', 'add', 'type', self.devtypes[iname], 'con-name', cname, 'connection.interface-name', iname] + cargs)
self.read_connections()
u = self.uuidbyname.get(cname, None)
if u:
subprocess.check_call(['nmcli', 'c', 'u', u])
@@ -312,7 +316,10 @@ if __name__ == '__main__':
srvs, _ = apiclient.scan_confluents()
doneidxs = set([])
for srv in srvs:
s = socket.create_connection((srv, 443))
try:
s = socket.create_connection((srv, 443))
except socket.error:
continue
myname = s.getsockname()
s.close()
if len(myname) == 4:
@@ -344,6 +351,13 @@ if __name__ == '__main__':
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
doneidxs.add(curridx)
if 'default' in netname_to_interfaces:
for netn in netname_to_interfaces:
if netn == 'default':
continue
netname_to_interfaces['default']['interfaces'] -= netname_to_interfaces[netn]['interfaces']
if not netname_to_interfaces['default']['interfaces']:
del netname_to_interfaces['default']
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes)
@@ -0,0 +1,31 @@
[ -f /lib/confluent/functions ] && . /lib/confluent/functions
[ -f /etc/confluent/functions ] && . /etc/confluent/functions
[ -f /opt/confluent/bin/apiclient ] && confapiclient=/opt/confluent/bin/apiclient
[ -f /etc/confluent/apiclient ] && confapiclient=/etc/confluent/apiclient
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
rm $certfile
confluentpython $confapiclient /confluent-api/self/sshcert $pubkey -o $certfile
done
TMPDIR=$(mktemp -d)
cd $TMPDIR
confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs.tgz
tar xf initramfs.tgz
for ca in ssh/*.ca; do
LINE=$(cat $ca)
cp -af /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts.new
grep -v "$LINE" /etc/ssh/ssh_known_hosts > /etc/ssh/ssh_known_hosts.new
echo '@cert-authority *' $LINE >> /etc/ssh/ssh_known_hosts.new
mv /etc/ssh/ssh_known_hosts.new /etc/ssh/ssh_known_hosts
done
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
cp -af /root/.ssh/authorized_keys /root/.ssh/authorized_keys.new
grep -v "$LINE" /root/.ssh/authorized_keys > /root/.ssh/authorized_keys.new
echo "$LINE" >> /root/.ssh/authorized_keys.new
mv /root/.ssh/authorized_keys.new /root/.ssh/authorized_keys
done
confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' > /etc/ssh/shosts.equiv
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
@@ -0,0 +1,91 @@
Name: confluent_osdeploy-aarch64
Version: #VERSION#
Release: 1
Summary: OS Deployment support for confluent
License: Apache2
URL: https://hpc.lenovo.com/
Source0: confluent_osdeploy.tar.xz
Source1: confluent_el9bin.tar.xz
Source2: confluent_el8bin.tar.xz
BuildArch: noarch
Requires: confluent_ipxe mtools tar
BuildRoot: /tmp
%description
This contains support utilities for enabling deployment of aarch64 architecture systems
%define debug_package %{nil}
%prep
%setup -n confluent_osdeploy -a 2 -a 1
%build
mkdir -p opt/confluent/bin
mkdir -p stateless-bin
cp -a el8bin/* .
ln -s el8 el9
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 coreos el9; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
else
cp -a ../opt .
fi
cp -a ../${os}/initramfs/* .
cp -a ../common/initramfs/* .
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
cd ..
done
for os in el7 el8 suse15 el9 ubuntu20.04; do
mkdir ${os}disklessout
cd ${os}disklessout
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
else
cp -a ../opt .
fi
cp -a ../${os}-diskless/initramfs/* .
cp -a ../common/initramfs/* .
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/stateless-bin/* opt/confluent/bin
else
cp -a ../stateless-bin/* opt/confluent/bin
fi
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
cd ..
done
mkdir esxi7out
cd esxi7out
cp -a ../opt .
cp -a ../esxi7/initramfs/* .
cp -a ../common/initramfs/* .
chmod +x bin/* opt/confluent/bin/*
tar zcvf ../addons.tgz *
mv ../addons.tgz .
cd ..
cp -a esxi7out esxi6out
cp -a esxi7 esxi6
cp -a esxi7out esxi8out
cp -a esxi7 esxi8
%install
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
#cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu22.04 esxi6 esxi7 esxi8 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
if [ -d ${os}disklessout ]; then
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs/aarch64/
cp ${os}disklessout/addons.* %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs/aarch64/
fi
done
find %{buildroot}/opt/confluent/lib/osdeploy/ -name .gitignore -exec rm -f {} +
%files
/opt/confluent/lib/osdeploy
#%license /opt/confluent/share/licenses/confluent_osdeploy/LICENSE
+13 -14
View File
@@ -7,6 +7,7 @@ License: Apache2
URL: https://hpc.lenovo.com/
Source0: confluent_osdeploy.tar.xz
Source1: confluent_el9bin.tar.xz
Source2: confluent_el8bin.tar.xz
BuildArch: noarch
Requires: confluent_ipxe mtools tar
BuildRoot: /tmp
@@ -18,24 +19,22 @@ This contains support utilities for enabling deployment of x86_64 architecture s
%define debug_package %{nil}
%prep
%setup -n confluent_osdeploy -a 1
%setup -n confluent_osdeploy -a 2 -a 1
%build
mkdir -p opt/confluent/bin
mkdir -p stateless-bin
cd utils
make all
cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
cp start_root urlmount ../stateless-bin/
cd ..
#cd utils
#make all
#cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
#cp start_root urlmount ../stateless-bin/
#cd ..
ln -s el8 el9
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 coreos el9; do
for os in rhvh4 el7 genesis el8 suse15 ubuntu18.04 ubuntu20.04 ubuntu22.04 coreos el9; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
else
cp -a ../opt .
cp -a ../el8bin/opt .
fi
cp -a ../${os}/initramfs/* .
cp -a ../common/initramfs/* .
@@ -49,14 +48,14 @@ for os in el7 el8 suse15 el9 ubuntu20.04; do
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
else
cp -a ../opt .
cp -a ../el8bin/opt .
fi
cp -a ../${os}-diskless/initramfs/* .
cp -a ../common/initramfs/* .
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/stateless-bin/* opt/confluent/bin
else
cp -a ../stateless-bin/* opt/confluent/bin
cp -a ../el8bin/stateless-bin/* opt/confluent/bin
fi
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
@@ -64,7 +63,7 @@ for os in el7 el8 suse15 el9 ubuntu20.04; do
done
mkdir esxi7out
cd esxi7out
cp -a ../opt .
cp -a ../el8bin/opt .
cp -a ../esxi7/initramfs/* .
cp -a ../common/initramfs/* .
chmod +x bin/* opt/confluent/bin/*
@@ -79,7 +78,7 @@ cp -a esxi7 esxi8
%install
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu22.04 esxi6 esxi7 esxi8 coreos; do
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu18.04 ubuntu22.04 esxi6 esxi7 esxi8 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
@@ -69,7 +69,7 @@ if [ "$v4cfgmeth" = "static" ]; then
for namesrv in "$namesrvs"; do
setdebopt netcfg/get_nameservers $namesrv string
done
elif [ "$vpcfgmeth" = "dhcp" ]; then
elif [ "$v4cfgmeth" = "dhcp" ]; then
setdebopt netcfg/disable_dhcp false boolean
setdebopt netcfg/confirm_static false boolean
fi
@@ -5,6 +5,8 @@
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -7,41 +7,111 @@ if [ -f /tmp/dd_disk ]; then
fi
done
fi
oum=$(umask)
umask 0077
mkdir -p /etc/confluent
cat /tls/*.pem > /etc/confluent/ca.pem
TRIES=0
echo -n > /etc/confluent/confluent.info
cd /sys/class/net
while ! awk -F'|' '{print $3}' /etc/confluent/confluent.info |grep 1 >& /dev/null && [ "$TRIES" -lt 60 ]; do
TRIES=$((TRIES + 1))
for currif in *; do
ip link set $currif up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
cat /tls/*.pem > /etc/confluent/ca.pem
TRIES=5
while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do
sleep 2
TRIES=$((TRIES - 1))
done
cd /
grep ^EXTMGRINFO: /etc/confluent/confluent.info || return 0 # Do absolutely nothing if no data at all yet
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=/tmp/idntmnt
mkdir -p $tmnt
tcfg=/tmp/idnttmp
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/, /^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
nodename=$(grep ^nodename: cnflnt.yml|awk '{print $2}')
echo "NODENAME: "$nodename > /etc/confluent/confluent.info
for dsrv in $deploysrvs; do
echo 'MANAGER: '$dsrv >> /etc/confluent/confluent.info
done
sed -n '/^net_cfgs:/, /^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/, /^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
for i in /sys/class/net/*; do
ip link set $(basename $i) down
udevadm info $i | grep ID_NET_DRIVER=cdc_ether > /dev/null && continue
ip link set $(basename $i) up
done
usedhcp=0
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK| awk '{print $2}' | sed -e 's/:$//'); do
if [ "$autoconfigmethod" = "dhcp" ]; then
usedhcp=1
else
v4addr=$(grep ^ipv4_address: $tcfg)
v4addr=${v4addr#ipv4_address: }
v4plen=${v4addr#*/}
v4addr=${v4addr%/*}
v4gw=$(grep ^ipv4_gateway: $tcfg)
v4gw=${v4gw#ipv4_gateway: }
ip addr add dev $NICGUESS $v4addr/$v4plen
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
TESTSRV=$(python /opt/confluent/bin/apiclient -c 2> /dev/null)
if [ ! -z "$v4gw" ]; then
ip route del default via $v4gw
fi
ip -4 addr flush dev $NICGUESS
if [ ! -z "$TESTSRV" ]; then
mgr=$TESTSRV
ifname=$NICGUESS
break
fi
fi
done
fi
TRIES=0
if [ "$usedhcp" = 1 ]; then
echo ip=$ifname:dhcp >> /etc/cmdline.d/01-confluent.conf
elif [ -z "$ifname" ]; then
cd /sys/class/net
while ! awk -F'|' '{print $3}' /etc/confluent/confluent.info |grep 1 >& /dev/null && [ "$TRIES" -lt 60 ]; do
TRIES=$((TRIES + 1))
for currif in *; do
ip link set $currif up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
done
cd /
grep ^EXTMGRINFO: /etc/confluent/confluent.info || return 0 # Do absolutely nothing if no data at all yet
echo -n "" > /etc/cmdline.d/01-confluent.conf
else
echo -n ip=$v4addr::$v4gw:$v4nm:$hostname:$ifname:none > /etc/cmdline.d/01-confluent.conf
fi
echo -n "" > /tmp/confluent.initq
# restart cmdline
echo -n "" > /etc/cmdline.d/01-confluent.conf
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
#TODO: blkid --label <whatever> to find mounted api
oum=$(umask)
python /opt/confluent/bin/apiclient /confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
echo $ifname > /tmp/net.ifaces
if [ -z "$ifname" ]; then
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
if [ ! -z "$ifname" ]; then
echo $ifname > /tmp/net.ifaces
fi
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
hostname=$nodename
if [ ! -z "$dnsdomain" ] && [ "$dnsdomain" != "null" ]; then
hostname=$hostname.$dnsdomain
fi
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg)
mgr=${mgr#deploy_server: }
if [ -z "$mgr" ]; then
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg)
mgr=${mgr#deploy_server: }
fi
profilename=$(grep ^profile: /etc/confluent/confluent.deploycfg)
profilename=${profilename#profile: }
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg)
@@ -65,21 +135,23 @@ kickstart=$proto://$mgr/confluent-public/os/$profilename/kickstart
root=anaconda-net:$proto://$mgr/confluent-public/os/$profilename/distribution
export kickstart
export root
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
echo ip=$ifname:dhcp >> /etc/cmdline.d/01-confluent.conf
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
if [ -z "$autoconfigmethod" ]; then
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
echo ip=$ifname:dhcp >> /etc/cmdline.d/01-confluent.conf
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#ipv4_netmask: }
echo ip=$v4addr::$v4gw:$v4nm:$hostname:$ifname:none >> /etc/cmdline.d/01-confluent.conf
fi
v4nm=$(grep ipv4_netmask: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#ipv4_netmask: }
echo ip=$v4addr::$v4gw:$v4nm:$hostname:$ifname:none >> /etc/cmdline.d/01-confluent.conf
fi
nameserversec=0
while read -r entry; do
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,4 +1,6 @@
#!/bin/sh
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
# This script is executed on the first boot after install has
# completed. It is best to edit the middle of the file as
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -5,6 +5,8 @@
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -11,6 +11,8 @@ import struct
import sys
import subprocess
bootuuid = None
def get_next_part_meta(img, imgsize):
if img.tell() == imgsize:
return None
@@ -53,10 +55,13 @@ class PartedRunner():
def __init__(self, disk):
self.disk = disk
def run(self, command):
def run(self, command, check=True):
command = command.split()
command = ['parted', '-a', 'optimal', '-s', self.disk] + command
return subprocess.check_output(command).decode('utf8')
if check:
return subprocess.check_output(command).decode('utf8')
else:
return subprocess.run(command, stdout=subprocess.PIPE).stdout.decode('utf8')
def fixup(rootdir, vols):
devbymount = {}
@@ -125,9 +130,14 @@ def fixup(rootdir, vols):
sys.stdout.flush()
for metafs in ('proc', 'sys', 'dev'):
subprocess.check_call(['mount', '-o', 'bind', '/{}'.format(metafs), os.path.join(rootdir, metafs)])
with open(os.path.join(rootdir, 'etc/sysconfig/grub')) as defgrubin:
if os.path.exists(os.path.join(rootdir, 'etc/lvm/devices/system.devices')):
os.remove(os.path.join(rootdir, 'etc/lvm/devices/system.devices'))
grubsyscfg = os.path.join(rootdir, 'etc/sysconfig/grub')
if not os.path.exists(grubsyscfg):
grubsyscfg = os.path.join(rootdir, 'etc/default/grub')
with open(grubsyscfg) as defgrubin:
defgrub = defgrubin.read().split('\n')
with open(os.path.join(rootdir, 'etc/sysconfig/grub'), 'w') as defgrubout:
with open(grubsyscfg, 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
@@ -136,8 +146,34 @@ def fixup(rootdir, vols):
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/')
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/').replace('//', '/')
grubcfg = grubcfg.split('\n')
if not grubcfg[-1]:
grubcfg = grubcfg[:-1]
if len(grubcfg) == 1:
grubcfg = grubcfg[0]
else:
for gcfg in grubcfg:
rgcfg = os.path.join(rootdir, gcfg[1:]) # gcfg has a leading / to get rid of
if os.stat(rgcfg).st_size > 256:
grubcfg = gcfg
else:
with open(rgcfg, 'r') as gin:
tgrubcfg = gin.read()
tgrubcfg = tgrubcfg.split('\n')
if 'search --no-floppy --fs-uuid --set=dev' in tgrubcfg[0]:
tgrubcfg[0] = 'search --no-floppy --fs-uuid --set=dev ' + bootuuid
with open(rgcfg, 'w') as gout:
for gcline in tgrubcfg:
gout.write(gcline)
gout.write('\n')
try:
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
except Exception as e:
print(repr(e))
print(rootdir)
print(grubcfg)
time.sleep(86400)
newroot = None
with open('/etc/shadow') as shadowin:
shents = shadowin.read().split('\n')
@@ -184,6 +220,7 @@ def had_swap():
return False
def install_to_disk(imgpath):
global bootuuid
lvmvols = {}
deftotsize = 0
mintotsize = 0
@@ -224,7 +261,7 @@ def install_to_disk(imgpath):
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
parted = PartedRunner(instdisk)
dinfo = parted.run('unit s print')
dinfo = parted.run('unit s print', check=False)
dinfo = dinfo.split('\n')
sectors = 0
sectorsize = 0
@@ -366,6 +403,14 @@ def install_to_disk(imgpath):
sys.stdout.write('\x1b[1K\rDone writing {0}'.format(vol['mount']))
sys.stdout.write('\n')
sys.stdout.flush()
if vol['mount'] == '/boot':
tbootuuid = subprocess.check_output(['blkid', vol['targetdisk']])
if b'UUID="' in tbootuuid:
bootuuid = tbootuuid.split(b'UUID="', 1)[1].split(b'"')[0].decode('utf8')
subprocess.check_call(['umount', '/run/imginst/targ'])
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
@@ -373,4 +418,4 @@ def install_to_disk(imgpath):
if __name__ == '__main__':
install_to_disk(os.environ['mountsrc'])
install_to_disk(os.environ['mountsrc'])
@@ -31,6 +31,9 @@ if [ ! -f /sysroot/tmp/installdisk ]; then
fi
lvm vgchange -a n
udevadm control -e
if [ -f /sysroot/etc/lvm/devices/system.devices ]; then
rm /sysroot/etc/lvm/devices/system.devices
fi
chroot /sysroot /usr/lib/systemd/systemd-udevd --daemon
chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python image2disk.py"
echo "Port 22" >> /etc/ssh/sshd_config
@@ -1,6 +1,7 @@
#!/bin/sh
[ -e /tmp/confluent.initq ] && return 0
. /lib/dracut-lib.sh
setsid sh -c 'exec bash <> /dev/tty2 >&0 2>&1' &
udevadm trigger
udevadm trigger --type=devices --action=add
udevadm settle
@@ -40,7 +41,7 @@ mkdir -p /etc/confluent
echo -n > /etc/confluent/confluent.info
umask $oum
TRIES=5
while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do
while [ ! -e /dev/disk/by-label ] && [ $TRIES -gt 0 ]; do
sleep 2
TRIES=$((TRIES - 1))
done
@@ -55,21 +56,49 @@ if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
sed -n '/^net_cfgs:/, /^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/, /^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
/usr/libexec/nm-initrd-generator ip=:dhcp
else
v4addr=$(grep ^ipv4_address: $tcfg)
v4addr=${v4addr#ipv4_address: }
v4addr=${v4addr%/*}
v4gw=$(grep ^ipv4_gateway: $tcfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
for i in /sys/class/net/*; do
ip link set $(basename $i) down
udevadm info $i | grep ID_NET_DRIVER=cdc_ether > /dev/null && continue
ip link set $(basename $i) up
done
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK| awk '{print $2}' | sed -e 's/:$//'); do
if [ "$autoconfigmethod" = "dhcp" ]; then
/usr/libexec/nm-initrd-generator ip=$NICGUESS:dhcp
else
v4addr=$(grep ^ipv4_address: $tcfg)
v4addr=${v4addr#ipv4_address: }
v4plen=${v4addr#*/}
v4addr=${v4addr%/*}
v4gw=$(grep ^ipv4_gateway: $tcfg)
v4gw=${v4gw#ipv4_gateway: }
ip addr add dev $NICGUESS $v4addr/$v4plen
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
DETECTED=0
for dsrv in $deploysrvs; do
if curl --capath /tls/ -s --connect-timeout 3 https://$dsrv/confluent-public/ > /dev/null; then
rm /run/NetworkManager/system-connections/*
/usr/libexec/nm-initrd-generator ip=$v4addr::$v4gw:$v4nm:$hostname:$NICGUESS:none
DETECTED=1
ifname=$NICGUESS
break
fi
done
if [ ! -z "$v4gw" ]; then
ip route del default via $v4gw
fi
ip addr flush dev $NICGUESS
if [ $DETECTED = 1 ]; then
break
fi
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
/usr/libexec/nm-initrd-generator ip=$v4addr::$v4gw:$v4nm:$hostname::none
fi
done
NetworkManager --configure-and-quit=initrd --no-daemon
hmackeyfile=/tmp/cnflnthmackeytmp
echo -n $(grep ^apitoken: cnflnt.yml|awk '{print $2}') > $hmackeyfile
@@ -85,10 +114,10 @@ if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
echo 'MANAGER: '$deploysrv >> /etc/confluent/confluent.info
done
for deployer in $deploysrvs; do
if curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" -d@$passcrypt -k https://$deployer/confluent-api/self/registerapikey; then
if curl --capath /tls/ -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" -d@$passcrypt -k https://$deployer/confluent-api/self/registerapikey; then
cp $passfile /etc/confluent/confluent.apikey
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$deployer/confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
curl --capath /tls/ -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$deployer/confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg)
confluent_profile=${confluent_profile#profile: }
mgr=$deployer
@@ -100,7 +129,9 @@ cd /sys/class/net
if ! grep MANAGER: /etc/confluent/confluent.info; then
confluentsrv=$(getarg confluent)
if [ ! -z "$confluentsrv" ]; then
mgr=$confluentsrv
if [[ "$confluentsrv" = *":"* ]]; then
mgr="[$mgr]"
confluenthttpsrv=[$confluentsrv]
/usr/libexec/nm-initrd-generator ip=:dhcp6
else
@@ -112,7 +143,7 @@ if ! grep MANAGER: /etc/confluent/confluent.info; then
for mac in $(ip -br link|grep -v LOOPBACK|awk '{print $3}'); do
myids=$myids"/mac="$mac
done
myname=$(curl -sH "CONFLUENT_IDS: $myids" https://$confluenthttpsrv/confluent-api/self/whoami)
myname=$(curl --capath /tls/ -sH "CONFLUENT_IDS: $myids" https://$confluenthttpsrv/confluent-api/self/whoami)
if [ ! -z "$myname" ]; then
echo NODENAME: $myname > /etc/confluent/confluent.info
echo MANAGER: $confluentsrv >> /etc/confluent/confluent.info
@@ -146,15 +177,17 @@ fi
while ! confluentpython /opt/confluent/bin/apiclient $errout /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg; do
sleep 10
done
ifidx=$(cat /tmp/confluent.ifidx)
if [ ! -z "$ifidx" ]; then
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
ifname=${ifname%@*}
echo $ifname > /tmp/net.ifaces
else
ip -br a|grep UP|awk '{print $1}' > /tmp/net.ifaces
ifname=$(cat /tmp/net.ifaces)
ifidx=$(cat /tmp/confluent.ifidx 2> /dev/null)
if [ -z "$ifname" ]; then
if [ ! -z "$ifidx" ]; then
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
ifname=${ifname%@*}
echo $ifname > /tmp/net.ifaces
else
ip -br a|grep UP|awk '{print $1}' > /tmp/net.ifaces
ifname=$(cat /tmp/net.ifaces)
fi
fi
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -2,6 +2,10 @@
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 && \
ln -s $1/EFI/BOOT/BOOTX64.EFI $1/EFI/BOOT/grubx64.efi $2/boot/efi/boot/
mkdir -p $2/boot/efi/boot
if [ -e $1/EFI/BOOT/BOOTAA64.EFI ]; then
ln -s $1/EFI/BOOT/BOOTAA64.EFI $1/EFI/BOOT/grubaa64.efi $2/boot/efi/boot/
else
ln -s $1/EFI/BOOT/BOOTX64.EFI $1/EFI/BOOT/grubx64.efi $2/boot/efi/boot/
fi
@@ -1,4 +1,6 @@
#!/bin/sh
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
# This script is executed on the first boot after install has
# completed. It is best to edit the middle of the file as
@@ -33,16 +35,21 @@ export nodename confluent_mgr confluent_profile
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
chmod 600 /var/log/confluent/confluent-firstboot.log
if [ ! -f /etc/confluent/firstboot.ran ]; then
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
confluentpython /root/confignet
rm /root/confignet
fi
while ! ping -c 1 $confluent_pingtarget >& /dev/null; do
sleep 1
done
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
run_remote_python confignet
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
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -47,5 +47,8 @@ run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
cd /root
fetch_remote confignet
cd -
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -5,6 +5,8 @@
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -11,6 +11,8 @@ import struct
import sys
import subprocess
bootuuid = None
def get_next_part_meta(img, imgsize):
if img.tell() == imgsize:
return None
@@ -53,10 +55,13 @@ class PartedRunner():
def __init__(self, disk):
self.disk = disk
def run(self, command):
def run(self, command, check=True):
command = command.split()
command = ['parted', '-a', 'optimal', '-s', self.disk] + command
return subprocess.check_output(command).decode('utf8')
if check:
return subprocess.check_output(command).decode('utf8')
else:
return subprocess.run(command, stdout=subprocess.PIPE).stdout.decode('utf8')
def fixup(rootdir, vols):
devbymount = {}
@@ -125,9 +130,14 @@ def fixup(rootdir, vols):
sys.stdout.flush()
for metafs in ('proc', 'sys', 'dev'):
subprocess.check_call(['mount', '-o', 'bind', '/{}'.format(metafs), os.path.join(rootdir, metafs)])
with open(os.path.join(rootdir, 'etc/sysconfig/grub')) as defgrubin:
if os.path.exists(os.path.join(rootdir, 'etc/lvm/devices/system.devices')):
os.remove(os.path.join(rootdir, 'etc/lvm/devices/system.devices'))
grubsyscfg = os.path.join(rootdir, 'etc/sysconfig/grub')
if not os.path.exists(grubsyscfg):
grubsyscfg = os.path.join(rootdir, 'etc/default/grub')
with open(grubsyscfg) as defgrubin:
defgrub = defgrubin.read().split('\n')
with open(os.path.join(rootdir, 'etc/sysconfig/grub'), 'w') as defgrubout:
with open(grubsyscfg, 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
@@ -136,8 +146,34 @@ def fixup(rootdir, vols):
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/')
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/').replace('//', '/')
grubcfg = grubcfg.split('\n')
if not grubcfg[-1]:
grubcfg = grubcfg[:-1]
if len(grubcfg) == 1:
grubcfg = grubcfg[0]
else:
for gcfg in grubcfg:
rgcfg = os.path.join(rootdir, gcfg[1:]) # gcfg has a leading / to get rid of
if os.stat(rgcfg).st_size > 256:
grubcfg = gcfg
else:
with open(rgcfg, 'r') as gin:
tgrubcfg = gin.read()
tgrubcfg = tgrubcfg.split('\n')
if 'search --no-floppy --fs-uuid --set=dev' in tgrubcfg[0]:
tgrubcfg[0] = 'search --no-floppy --fs-uuid --set=dev ' + bootuuid
with open(rgcfg, 'w') as gout:
for gcline in tgrubcfg:
gout.write(gcline)
gout.write('\n')
try:
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
except Exception as e:
print(repr(e))
print(rootdir)
print(grubcfg)
time.sleep(86400)
newroot = None
with open('/etc/shadow') as shadowin:
shents = shadowin.read().split('\n')
@@ -184,6 +220,7 @@ def had_swap():
return False
def install_to_disk(imgpath):
global bootuuid
lvmvols = {}
deftotsize = 0
mintotsize = 0
@@ -224,7 +261,7 @@ def install_to_disk(imgpath):
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
parted = PartedRunner(instdisk)
dinfo = parted.run('unit s print')
dinfo = parted.run('unit s print', check=False)
dinfo = dinfo.split('\n')
sectors = 0
sectorsize = 0
@@ -366,6 +403,14 @@ def install_to_disk(imgpath):
sys.stdout.write('\x1b[1K\rDone writing {0}'.format(vol['mount']))
sys.stdout.write('\n')
sys.stdout.flush()
if vol['mount'] == '/boot':
tbootuuid = subprocess.check_output(['blkid', vol['targetdisk']])
if b'UUID="' in tbootuuid:
bootuuid = tbootuuid.split(b'UUID="', 1)[1].split(b'"')[0].decode('utf8')
subprocess.check_call(['umount', '/run/imginst/targ'])
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
@@ -373,4 +418,4 @@ def install_to_disk(imgpath):
if __name__ == '__main__':
install_to_disk(os.environ['mountsrc'])
install_to_disk(os.environ['mountsrc'])
@@ -31,6 +31,9 @@ if [ ! -f /sysroot/tmp/installdisk ]; then
fi
lvm vgchange -a n
udevadm control -e
if [ -f /sysroot/etc/lvm/devices/system.devices ]; then
rm /sysroot/etc/lvm/devices/system.devices
fi
chroot /sysroot /usr/lib/systemd/systemd-udevd --daemon
chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python image2disk.py"
echo "Port 22" >> /etc/ssh/sshd_config
@@ -94,6 +94,7 @@ while ! grep NODENAME /etc/confluent/confluent.info; do
fi
done
node=$(grep NODENAME: /etc/confluent/confluent.info|head -n 1|awk '{print $2}')
cat /tls/*.pem > /etc/confluent/ca.pem
mgr=$(python /opt/confluent/bin/apiclient -c)
APIKEY=$(cat /etc/confluent/confluent.apikey)
@@ -108,7 +109,6 @@ while [ -z "$APIKEY" ]; do
(/clortho $node $mgr || /opt/confluent/bin/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: //')
/opt/confluent/bin/apiclient /confluent-public/os/$profile/kickstart > /etc/confluent/ks.cfg
@@ -33,6 +33,8 @@ for info in vswinfo.split('\n'):
upinfo = uplinkmatch.match(info)
if upinfo:
vmnic = upinfo.group(1)
if vmnic and 'vusb0' not in vmnic:
break
try:
with open('/tmp/confluentident/cnflnt.jsn') as identin:
identcfg = json.load(identin)
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -161,7 +161,7 @@ else
echo -e "BOOTPROTO='static'\nSTARTMODE='auto'" >> /run/confluent/ifcfg-$ifname
echo "IPADDR='$v4addr/$v4nm'" >> /run/confluent/ifcfg-$ifname
if [ ! -z "$v4gw" ]; then
echo defafult $v4gw - $ifname > /run/confluent/ifroute-$ifname
echo default $v4gw - $ifname > /run/confluent/ifroute-$ifname
fi
fi
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -77,6 +77,7 @@ if [ "$textconsole" = "true" ] && ! grep console= /proc/cmdline > /dev/null && [
sed -e s'/$/ 'console=${autocons#*/dev/}/ /proc/cmdline > /etc/fakecmdline
mount -o bind /etc/fakecmdline /proc/cmdline
echo "ConsoleDevice: ${autocons%,*}" >> /etc/linuxrc.d/01-confluent
echo "Textmode: 1" >> /etc/linuxrc.d/01-confluent
fi
tz=$(grep timezone: /etc/confluent/confluent.deploycfg | awk '{print $2}')
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,4 +1,6 @@
#!/bin/bash
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
# This script runs at the end of the final boot, updating status
exec >> /var/log/confluent/confluent-firstboot.log
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,6 +1,10 @@
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
appropriate phase of the install process.
Alternatively, plays may be placed in /var/lib/confluent/private/os/<profilename>/ansible/<directory>.
This prevents public clients from being able to read the plays, which is not necessary for them to function,
and may protect them from divulging material contained in the plays or associated roles.
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
requesting the playbooks be executed.
@@ -1,4 +1,6 @@
#!/bin/bash
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
# This script runs at the end of the final boot, updating status
exec >> /var/log/confluent/confluent-firstboot.log
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -0,0 +1,205 @@
reverse_uuid() {
echo $1 | head -c 8 | tail -c 2
echo $1 | head -c 6 | tail -c 2
echo $1 | head -c 4 | tail -c 2
echo $1 | head -c 2 | tail -c 2
echo $1 | head -c 13 | tail -c 2
echo $1 | head -c 11 | tail -c 2
echo $1 | head -c 18 | tail -c 2
echo $1 | head -c 16 | tail -c 2
echo $1 | tail -c 18 | sed -e s/-//
}
setdebopt() {
debconf-set $1 $2
echo d-i $1 $3 $2 >> /preseed.cfg
}
mkdir -p /etc/confluent
for i in /sys/class/net/*; do
ip link set $(basename $i) up
done
TRIES=5
while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do
sleep 2
TRIES=$((TRIES - 1))
done
for i in /sys/class/net/*; do
ip link set $(basename $i) down
udevadm info $i | grep ID_NET_DRIVER=cdc_ether > /dev/null && continue
ip link set $(basename $i) up
done
cp -a /tls/* /etc/ssl/certs/
mkdir -p /etc/confluent
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=$(mktemp -d)
tcfg=$(mktemp)
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/,/^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
nodename=$(grep ^nodename: cnflnt.yml|cut -f 2 -d ' ')
echo NODENAME: $nodename > /etc/confluent/confluent.info
sed -n '/^net_cfgs:/,/^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/,/^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "static" ]; then
setdebopt netcfg/disable_dhcp true boolean
v4addr=$(grep ^ipv4_address: $tcfg|cut -d: -f 2|sed -e 's/ //')
v4gw=$(grep ^ipv4_gateway: $tcfg|cut -d: -f 2| sed -e 's/ //')
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^ipv4_netmask: $tcfg|cut -d: -f 2|sed -e 's/ //')
setdebopt netcfg/get_netmask $v4nm string
setdebopt netcfg/get_ipaddress ${v4addr%/*} string
setdebopt netcfg/confirm_static true boolean
if [ ! -z "$v4gw" ]; then
setdebopt netcfg/get_gateway $v4gw string
fi
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do
ip addr add dev $NICGUESS $v4addr
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
for dsrv in $deploysrvs; do
if wget https://$dsrv/confluent-public/ --tries=1 --timeout=1 -O /dev/null > /dev/null 2>&1; then
deploysrvs=$dsrv
NIC=$NICGUESS
setdebopt netcfg/choose_interface $NIC select
break
fi
done
if [ -z "$NIC" ]; then
ip -4 a flush dev $NICGUESS
else
break
fi
done
#TODO: nameservers
elif [ "$v4cfgmeth" = "dhcp" ]; then
setdebopt netcfg/disable_dhcp false boolean
setdebopt netcfg/confirm_static false boolean
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do
udhcpc $NICGUESS
done
for dsrv in $deploysrvs; do
if wget https://$dsrv/confluent-public/ --tries=1 --timeout=1 -O /dev/null > /dev/null 2>&1; then
deploysrvs=$dsrv
fi
done
fi
mgr=$deploysrvs
ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac
hmackeyfile=/tmp/cnflnthmackeytmp
passfile=/tmp/cnflnttmppassfile
passcrypt=/tmp/cnflntcryptfile
hmacfile=/tmp/cnflnthmacfile
echo -n $(grep ^apitoken: cnflnt.yml|cut -d ' ' -f 2) > $hmackeyfile
/opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" --post-file=$passcrypt https://$mgr/confluent-api/self/registerapikey -O - --quiet
cp $passfile /etc/confluent/confluent.apikey
nic=$NIC
else
dhuuid=$(reverse_uuid $(cat /sys/devices/virtual/dmi/id/product_uuid))
dhcpid=$(mktemp)
mkdir -p /etc/confluent
cp /tls/* /etc/ssl/certs/
cat /tls/*.pem >> /etc/confluent/ca.pem
for nic in $(ip link | grep mtu|grep -v LOOPBACK|cut -d: -f 2|sed -e 's/ //'); do
ip link set $nic up
done
for nic in $(ip link | grep mtu|grep -v LOOPBACK|grep LOWER_UP|cut -d: -f 2|sed -e 's/ //'); do
if udhcpc -i $nic -p $dhcpid -t 2 -T 2 -n -x 93:0007 -x 97:00$dhuuid -q; then
/opt/confluent/bin/copernicus > /etc/confluent/confluent.info
if grep ^MANAGER:.*\\. /etc/confluent/confluent.info ; then
break
fi
fi
ip -4 flush dev $nic
done
mgr=$(grep ^MANAGER:.*\\. /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //')
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //')
/opt/confluent/bin/clortho $nodename $mgr > /etc/confluent/confluent.apikey
fi
apikey=$(cat /etc/confluent/confluent.apikey)
cd /etc/confluent
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/deploycfg
cd -
predir=$(mktemp -d)
cd $predir
cp /etc/confluent/deploycfg /etc/confluent/confluent.deploycfg
profile=$(grep ^profile: /etc/confluent/deploycfg|cut -d ' ' -f 2)
namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //')
for namesrv in "$namesrvs"; do
setdebopt netcfg/get_nameservers $namesrv string
done
rootpass=$(grep ^rootpassword: /etc/confluent/deploycfg|cut -d ' ' -f 2|sed -e 's/ //')
if [ "$rootpass" = null ] || [ -z "$rootpass" ]; then
setdebopt passwd/root-login false boolean
else
setdebopt passwd/root-login true boolean
setdebopt passwd/root-password-crypted $rootpass string
fi
setdebopt time/zone $(grep ^timezone: /etc/confluent/deploycfg|cut -d ' ' -f 2|sed -e 's/ //') string
ntpsrvs=$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //')
for ntpsrv in "$ntpsrvs"; do
setdebopt clock-setup/ntp true boolean
setdebopt clock-setup/ntp-server $ntpsrv string
done
setdebopt debian-installer/locale $(grep ^locale: /etc/confluent/deploycfg|cut -d ' ' -f 2) select
domainname=$(grep ^dnsdomain: /etc/confluent/deploycfg|cut -d ' ' -f 2)
if [ ! -z "$domainname" ] && [ "$domainname" != "null" ]; then
setdebopt netcfg/get_domain $domainname string
fi
wget https://$mgr/confluent-public/os/$profile/scripts/pre.sh
chmod u+x pre.sh
wget https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh
chmod u+x prechroot.sh
wget https://$mgr/confluent-public/os/$profile/scripts/post.sh
chmod u+x post.sh
wget https://$mgr/confluent-public/os/$profile/preseed.cfg
cat preseed.cfg >> /preseed.cfg
echo $mgr > /etc/confluent/deployer
setdebopt auto-install/enable true boolean
setdebopt partman/early_command $predir/pre.sh string
setdebopt preseed/late_command $predir/prechroot.sh string
mv $predir/post.sh /tmp/
cd -
ip -4 a flush dev $nic
setdebopt netcfg/choose_interface $nic select
setdebopt netcfg/get_hostname $nodename string
setdebopt mirror/protocol https string
setdebopt mirror/country manual string
setdebopt mirror/https/hostname $mgr string
setdebopt mirror/https/directory /confluent-public/os/$profile/distribution string
setdebopt mirror/protocol https string
setdebopt mirror/https/proxy "" string
setdebopt apt-setup/security_host $mgr string
if [ ! -e /dev/disk/by-label/CNFLNT_IDNT ]; then
v4cfgmeth=$(grep ipv4_method: /etc/confluent/deploycfg |cut -d: -f 2|sed -e 's/ //')
if [ "$v4cfgmeth" = "static" ]; then
setdebopt netcfg/disable_dhcp true boolean
v4addr=$(grep ^ipv4_address: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //')
v4gw=$(grep ^ipv4_gateway: /etc/confluent/deploycfg|cut -d: -f 2| sed -e 's/ //')
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^ipv4_netmask: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //')
setdebopt netcfg/get_netmask $v4nm string
setdebopt netcfg/get_ipaddress $v4addr string
setdebopt netcfg/confirm_static true boolean
if [ ! -z "$v4gw" ]; then
setdebopt netcfg/get_gateway $v4gw string
fi
namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //')
for namesrv in "$namesrvs"; do
setdebopt netcfg/get_nameservers $namesrv string
done
elif [ "$vpcfgmeth" = "dhcp" ]; then
setdebopt netcfg/disable_dhcp false boolean
setdebopt netcfg/confirm_static false boolean
fi
fi
@@ -0,0 +1,7 @@
#!/bin/sh
sed -i 's/label: ubuntu/label: Ubuntu/' $2/profile.yaml && \
ln -s $1/install/hwe-netboot/ubuntu-installer/amd64/linux $2/boot/kernel && \
ln -s $1/install/hwe-netboot/ubuntu-installer/amd64/initrd.gz $2/boot/initramfs/distribution && \
mkdir -p $2/boot/efi/boot && \
ln -s $1/EFI/BOOT/* $2/boot/efi/boot
@@ -0,0 +1,21 @@
d-i anna/choose_modules string openssh-server-udeb
d-i partman-auto/method string regular
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-auto/expert_recipe_file string /tmp/partitionfile
d-i partman/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i passwd/make-user boolean false
d-i clock-setup/utc boolean true
d-i apt-setup/multiverse boolean false
d-i apt-setup/universe boolean false
d-i apt-setup/backports boolean false
d-i apt-setup/updates boolean false
d-i grub-installer/only_debian boolean true
tasksel tasksel/first multiselect standard
d-i pkgsel/include string openssh-server curl
d-i pkgsel/update-policy select none
d-i pkgsel/updatedb boolean false
d-i finish-install/reboot_in_progress note
@@ -0,0 +1,3 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet osprofile=%%PROFILE%%
#installedargs: example # These arguments would be added to the installed system
@@ -0,0 +1,11 @@
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,22 @@
#!/bin/bash
echo "Confluent first boot is running"
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
#cp -a /etc/confluent/ssh/* /etc/ssh/
#systemctl restart sshd
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ ! -z "$rootpw" -a "$rootpw" != "null" ]; then
echo root:$rootpw | chpasswd -e
fi
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
source /etc/confluent/functions
run_remote_parts firstboot.d
run_remote_config firstboot.d
systemctl disable firstboot
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -0,0 +1,208 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
elif [ -x /usr/bin/python3 ]; then
/usr/bin/python3 $*
elif [ -x /usr/bin/python ]; then
/usr/bin/python $*
elif [ -x /usr/bin/python2 ]; then
/usr/bin/python2 $*
fi
}
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [[ "$confluent_mgr" == *"%"* ]]; then
confluent_mgr=""
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if ! test_mgr $confluent_mgr; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [[ "$confluent_mgr" = *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
fi
if ! test_mgr $confluent_mgr; then
BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
for confluent_mgr in $BESTMGRS $OKMGRS; do
if [[ $confluent_mgr == *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
if test_mgr $confluent_mgr; then
break
fi
done
fi
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
curlargs=""
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -gf -sS $curlargs https://$whost/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 /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
unsettmpdir=0
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
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
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
rm -rf $confluentscripttmpdir
return $retcode
}
run_remote() {
requestedcmd="'$*'"
unsettmpdir=0
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 /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
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
if [ -x /usr/bin/chcon ]; then
chcon system_u:object_r:bin_t:s0 $cmd
fi
shift
./$cmd $*
retcode=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
return $retcode
}
run_remote_python() {
echo
set_confluent_vars
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
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
confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
#If invoked as a command, use the arguments to actually run a function
(return 0 2>/dev/null) || $1 "${@:2}"
@@ -0,0 +1,67 @@
#!/bin/bash
mkdir -p /run/sshd
mkdir -p /root/.ssh
cat /tmp/ssh/*pubkey >> /root/.ssh/authorized_keys
cat /tmp/ssh/*.ca | sed -e s/^/'@cert-authority * '/ >> /etc/ssh/ssh_known_hosts
chmod 700 /etc/confluent
chmod go-rwx /etc/confluent/*
sshconf=/etc/ssh/ssh_config
if [ -d /etc/ssh/ssh_config.d/ ]; then
sshconf=/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | awk '{print $2}')
mkdir -p /opt/confluent/bin
python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
chmod +x /opt/confluent/bin/firstboot.sh
python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
systemctl enable firstboot
python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
source /etc/confluent/functions
python3 /opt/confluent/bin/apiclient /confluent-api/self/nodelist | sed -e s/'^- //' > /tmp/allnodes
cp /tmp/allnodes /root/.shosts
cp /tmp/allnodes /etc/ssh/shosts.equiv
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
ntps=$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|sed 1d|sed '$d' | sed -e 's/^- //' | paste -sd ' ')
sed -i "s/#NTP=/NTP=$ntps/" /etc/systemd/timesyncd.conf
fi
textcons=$(grep ^textconsole: /etc/confluent/confluent.deploycfg |awk '{print $2}')
updategrub=0
if [ "$textcons" = "true" ] && ! grep console= /proc/cmdline > /dev/null; then
cons=""
if [ -f /tmp/autocons.info ]; then
cons=$(cat /tmp/autocons.info)
fi
if [ ! -z "$cons" ]; then
sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 console='${cons#/dev/}'"/' /etc/default/grub
updategrub=1
fi
fi
kargs=$(python3 /opt/confluent/bin/apiclient /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}"'"/' /etc/default/grub
fi
if [ 1 = $updategrub ]; then
update-grub
fi
if [ -e /sys/firmware/efi ]; then
bootnum=$(efibootmgr | grep ubuntu | sed -e 's/ .*//' -e 's/\*//' -e s/Boot//)
if [ ! -z "$bootnum" ]; then
currboot=$(efibootmgr | grep ^BootOrder: | awk '{print $2}')
nextboot=$(echo $currboot| awk -F, '{print $1}')
[ "$nextboot" = "$bootnum" ] || efibootmgr -o $bootnum,$currboot
efibootmgr -D
fi
fi
run_remote_python syncfileclient
run_remote_parts post.d
run_remote_config post
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged'
@@ -0,0 +1,87 @@
#!/bin/sh
## Use the following option to add additional boot parameters for the
## installed system (if supported by the bootloader installer).
## Note: options passed to the installer will be added automatically.
#d-i debian-installer/add-kernel-opts string [from profile.yaml]
deploycfg=/etc/confluent/confluent.deploycfg
mgr=$(cat /etc/confluent/deployer)
cryptboot=$(grep encryptboot: $deploycfg|sed -e 's/^encryptboot: //')
if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "null" ]; then
echo "****Encrypted boot requested, but not implemented for this OS, halting install" > /dev/console
[ -f '/tmp/autoconsdev' ] && (echo "****Encryptod boot requested, but not implemented for this OS,halting install" >> $(cat /tmp/autoconsdev))
while :; do sleep 86400; done
fi
cat > /usr/lib/live-installer.d/confluent-certs << EOF
#!/bin/sh
cp /tls/* /target/etc/ssl/certs/
cat /tls/*.pem >> /target/etc/ssl/certs/ca-certificates.crt
EOF
chmod a+x /usr/lib/live-installer.d/confluent-certs
mkdir -p /.ssh/
cat /ssh/*pubkey > /.ssh/authorized_keys
mkdir -p /etc/ssh
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|cut -d ' ' -f 2)
apikey=$(cat /etc/confluent/confluent.apikey)
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=$(echo $pubkey | sed -e s/.pub/-cert.pub/)
keyfile=${pubkey%.pub}
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" --post-file=$pubkey https://$mgr/confluent-api/self/sshcert -O $certfile --quiet
echo HostKey $keyfile >> /etc/ssh/sshd_config
echo HostCertificate $certfile >> /etc/ssh/sshd_config
done
if [ -e /tmp/installdisk ]; then
instdisk=$(cat /tmp/installdisk)
else
for blockdev in $(ls /sys/class/block/); do
shortname=$(basename $blockdev)
if [ "$shortname" != "${shortname%loop*}" ]; then
continue
fi
udevadm info --query=property /dev/$shortname |grep DEVTYPE=disk > /dev/null || continue # ignore partitions
udevadm info --query=property /dev/$shortname |grep DM_NAME > /dev/null && continue # not a real disk
sz=$(cat /sys/block/$shortname/size 2> /dev/null)
[ -z "$sz" ] && continue
[ $sz -lt 1048576 ] && continue # Too small
[ -z "$firstdisk" ] && firstdisk=$shortname
if udevadm info --query=property /dev/$shortname|grep ID_MODEL=| sed -e s/' '/_/g | grep -iE '(thinksystem_m.2|m.2_nvme_2-bay_raid_kit)' > /dev/null; then
instdisk=$shortname
break
fi
if udevadm info --query=property /dev/$shortname|grep MD_CONTAINER=imsm; then
sraid=$sortname
else
drv=$(udevadm info -a /dev/sdb|grep DRIVERS==|grep -Ev '""|"sd"' | sed -e s/.*=// -e s/'"'//g)
if [ "ahci" = "$drv" -a -z "$onbdisk" ]; then
onbdisk=$shortname
elif [ "megaraid" = "$drv" -a -z "$rdisk" ]; then
rdisk=$shortname
fi
fi
done
fi
if [ -z "$instdisk" ]; then
if [ ! -z "$sraid"]; then
instdisk=$sraid
elif [ ! -z "$onbdisk" ]; then
instdisk=$onbdisk
elif [ ! -z "$rdisk" ]; then
instdisk=$rdisk
else
instdisk=$firstdisk
fi
fi
if [ ! -z "$instdisk" ]; then
debconf-set partman-auto/disk /dev/$instdisk
fi
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config
echo IgnoreRhosts no >> /etc/ssh/sshd_config
echo sshd:x:1:1::/run/sshd:/bin/false >> /etc/passwd
/usr/sbin/sshd
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/nodelist -O /tmp/allnodes --quiet
#kill -HUP $(ps | grep -v grep | grep /usr/sbin/sshd | sed -e 's/^ *//'|cut -d ' ' -f 1)
#curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /tmp/getinstalldisk
#python3 /tmp/getinstalldisk
#sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml
@@ -0,0 +1,17 @@
#!/bin/sh
mount -o bind /sys /target/sys
mount -o bind /dev /target/dev
mount -o bind /dev/pts /target/dev/pts
mount -o bind /proc /target/proc
mount -o bind /dev/pts /target/dev/pts
mount -o bind /run /target/run
cp -a /etc/confluent /target/etc/confluent
cp -a /opt/confluent /target/opt/confluent
mv /tmp/post.sh /target/tmp/
cp -a /ssh /tls /target/tmp
cat /tls/*.pem >> /target/etc/confluent/ca.pem
cp -a /etc/ssh/ssh_host_* /target/etc/ssh/
grep HostCertificate /etc/ssh/sshd_config >> /target/etc/ssh/sshd_config
echo Port 2222 >> /etc/ssh/sshd_config
kill -HUP $(ps |grep -v grep|grep sshd|grep /usr|sed -e s/' root.*//')
chroot /target bash /tmp/post.sh
@@ -5,10 +5,70 @@ done
mkdir -p /custom-installation
cp -a /opt/confluent /custom-installation
touch /custom-installation/confluent/confluent.info
while ! grep NODENAME /custom-installation/confluent/confluent.info; do
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
TRIES=5
while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do
sleep 2
TRIES=$((TRIES - 1))
done
MGR="[$(grep MANAGER: /custom-installation/confluent/confluent.info | head -n 1 | awk '{print $2}')]"
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=/tmp/idntmnt
mkdir -p /tmp/identdata/
mkdir -p $tmnt
tcfg=/tmp/idnttmp
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cp -a $tmnt/* /tmp/identdata/
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/,/^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
sed -n '/^net_cfgs:/,/^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/,/^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
. /scripts/functions
if [ "$autoconfigmethod" = "static" ]; then
MYIP=$(grep ^ipv4_address: $tcfg | awk '{print $2}'|sed -e s'!/.*!!')
v4addr=$(grep ^ipv4_address: $tcfg|cut -d: -f 2|sed -e 's/ //')
MYGW=$(grep ^ipv4_gateway: $tcfg | awk '{print $2}')
if [ "$MYGW" = "null" ]; then
MYGW=""
fi
MYNM=$(grep ^ipv4_netmask: $tcfg | awk '{print $2}')
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do
ip addr add dev $NICGUESS $v4addr
if [ ! -z "$MYGW" ]; then
ip route add default via $MYGW
fi
for dsrv in $deploysrvs; do
if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
deploysrvs=$dsrv
NIC=$NICGUESS
break
fi
done
if [ -z "$NIC" ]; then
ip -4 a flush dev $NICGUESS
else
break
fi
done
ipconfig -d $MYIP::$MYGW:$MYNM::$NIC
echo $NIC > /tmp/autodetectnic
else
configure_networking
fi
MGR=$deploysrvs
NODENAME=$(grep ^nodename: /tmp/idntmnt/cnflnt.yml | awk '{print $2}')
echo "NODENAME: $NODENAME" >> /custom-installation/confluent/confluent.info
echo "MANAGER: $MGR" >> /custom-installation/confluent/confluent.info
echo "EXTMGRINFO: $MGR||1" >> /custom-installation/confluent/confluent.info
hmackeyfile=/tmp/cnflnthmackeytmp
echo -n $(grep ^apitoken: cnflnt.yml|awk '{print $2}') > $hmackeyfile
cd -
umount $tmnt
else
while ! grep NODENAME /custom-installation/confluent/confluent.info; do
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
done
MGR="[$(grep MANAGER: /custom-installation/confluent/confluent.info | head -n 1 | awk '{print $2}')]"
fi
osprofile=$(sed -e 's/.*osprofile=//' -e 's/ .*//' /proc/cmdline)
cat /proc/cmdline > /custom-installation/confluent/cmdline.orig
if [ -f /custom-installation/autocons.info ]; then
@@ -1,5 +1,8 @@
#!/bin/bash
echo "Confluent first boot is running"
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
seems a potentially relevant thing to put i... by Jarrod Johnson
cp -a /etc/confluent/ssh/* /etc/ssh/
systemctl restart sshd
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
@@ -11,32 +11,48 @@ MGR=$(grep ^MANAGER: /custom-installation/confluent/confluent.info|head -n 1| aw
MGTIFACE=$(grep $MGR /custom-installation/confluent/confluent.info | grep ^EXTMGRINFO: | head -n 1 | awk -F'|' '{print $2}')
oum=$(umask)
umask 077
chroot . custom-installation/confluent/bin/clortho $NODENAME $MGR > /root/custom-installation/confluent/confluent.apikey
MGR=[$MGR]
deploycfg=/root/custom-installation/confluent/confluent.deploycfg
netcfgfile=$deploycfg
if [ -e /tmp/cnflnthmackeytmp ]; then
netcfgfile=/tmp/idnttmp
hmackeyfile=/tmp/cnflnthmackeytmp
#echo -n $(grep ^apitoken: /tmp/identdata/cnflnt.yml|awk '{print $2}') > $hmackeyfile
passfile=/tmp/cnflnttmppassfile
passcrypt=/tmp/cnflntcryptfile
hmacfile=/tmp/cnflnthmacfile
chroot . ln -sf /custom-installation/confluent/bin/clortho custom-installation/confluent/bin/genpasshmac
cp $hmackeyfile tmp
chroot . custom-installation/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
chroot . curl -f -H "CONFLUENT_NODENAME: $NODENAME" -H "CONFLUENT_CRYPTHMAC: $(cat /root/$hmacfile)" -d @/tmp/cnflntcryptfile https://$MGR/confluent-api/self/registerapikey
cp /root/$passfile /root/custom-installation/confluent/confluent.apikey
DEVICE=$(cat /tmp/autodetectnic)
else
chroot . custom-installation/confluent/bin/clortho $NODENAME $MGR > /root/custom-installation/confluent/confluent.apikey
MGR=[$MGR]
nic=$(grep ^MANAGER /custom-installation/confluent/confluent.info|grep fe80::|sed -e s/.*%//|head -n 1)
nic=$(ip link |grep ^$nic:|awk '{print $2}')
DEVICE=${nic%:}
fi
if [ -z "$MGTIFACE" ]; then
chroot . usr/bin/curl -f -H "CONFLUENT_NODENAME: $NODENAME" -H "CONFLUENT_APIKEY: $(cat /root//custom-installation/confluent/confluent.apikey)" https://${MGR}/confluent-api/self/deploycfg > $deploycfg
else
chroot . usr/bin/curl -f -H "CONFLUENT_MGTIFACE: $MGTIFACE" -H "CONFLUENT_NODENAME: $NODENAME" -H "CONFLUENT_APIKEY: $(cat /root//custom-installation/confluent/confluent.apikey)" https://${MGR}/confluent-api/self/deploycfg > $deploycfg
fi
umask $oum
nic=$(grep ^MANAGER /custom-installation/confluent/confluent.info|grep fe80::|sed -e s/.*%//|head -n 1)
nic=$(ip link |grep ^$nic:|awk '{print $2}')
DEVICE=${nic%:}
ipv4m=$(grep ^ipv4_method $deploycfg|awk '{print$2}')
ipv4m=$(grep ^ipv4_method $netcfgfile|awk '{print$2}')
. /scripts/functions
if [ "$ipv4m" = "dhcp" ]; then
IP=dhcp
configure_networking
elif [ "$ipv4m" = "static" ]; then
v4addr=$(grep ^ipv4_address: $deploycfg)
v4addr=$(grep ^ipv4_address: $netcfgfile | sed -e 's!/.*!!')
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: $deploycfg)
v4gw=$(grep ^ipv4_gateway: $netcfgfile)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: $deploycfg)
v4nm=$(grep ipv4_netmask: $netcfgfile)
v4nm=${v4nm#ipv4_netmask: }
dnsdomain=$(grep ^dnsdomain: $deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
@@ -5,10 +5,70 @@ done
mkdir -p /custom-installation
cp -a /opt/confluent /custom-installation
touch /custom-installation/confluent/confluent.info
while ! grep NODENAME /custom-installation/confluent/confluent.info; do
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
TRIES=5
while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do
sleep 2
TRIES=$((TRIES - 1))
done
MGR="[$(grep MANAGER: /custom-installation/confluent/confluent.info | head -n 1 | awk '{print $2}')]"
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=/tmp/idntmnt
mkdir -p /tmp/identdata/
mkdir -p $tmnt
tcfg=/tmp/idnttmp
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cp -a $tmnt/* /tmp/identdata/
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/,/^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
sed -n '/^net_cfgs:/,/^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/,/^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
. /scripts/functions
if [ "$autoconfigmethod" = "static" ]; then
MYIP=$(grep ^ipv4_address: $tcfg | awk '{print $2}'|sed -e s'!/.*!!')
v4addr=$(grep ^ipv4_address: $tcfg|cut -d: -f 2|sed -e 's/ //')
MYGW=$(grep ^ipv4_gateway: $tcfg | awk '{print $2}')
if [ "$MYGW" = "null" ]; then
MYGW=""
fi
MYNM=$(grep ^ipv4_netmask: $tcfg | awk '{print $2}')
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do
ip addr add dev $NICGUESS $v4addr
if [ ! -z "$MYGW" ]; then
ip route add default via $MYGW
fi
for dsrv in $deploysrvs; do
if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
deploysrvs=$dsrv
NIC=$NICGUESS
break
fi
done
if [ -z "$NIC" ]; then
ip -4 a flush dev $NICGUESS
else
break
fi
done
ipconfig -d $MYIP::$MYGW:$MYNM::$NIC
echo $NIC > /tmp/autodetectnic
else
configure_networking
fi
MGR=$deploysrvs
NODENAME=$(grep ^nodename: /tmp/idntmnt/cnflnt.yml | awk '{print $2}')
echo "NODENAME: $NODENAME" >> /custom-installation/confluent/confluent.info
echo "MANAGER: $MGR" >> /custom-installation/confluent/confluent.info
echo "EXTMGRINFO: $MGR||1" >> /custom-installation/confluent/confluent.info
hmackeyfile=/tmp/cnflnthmackeytmp
echo -n $(grep ^apitoken: cnflnt.yml|awk '{print $2}') > $hmackeyfile
cd -
umount $tmnt
else
while ! grep NODENAME /custom-installation/confluent/confluent.info; do
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
done
MGR="[$(grep MANAGER: /custom-installation/confluent/confluent.info | head -n 1 | awk '{print $2}')]"
fi
osprofile=$(sed -e 's/.*osprofile=//' -e 's/ .*//' /proc/cmdline)
cat /proc/cmdline > /custom-installation/confluent/cmdline.orig
if [ -f /custom-installation/autocons.info ]; then
@@ -1,5 +1,8 @@
#!/bin/bash
echo "Confluent first boot is running"
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
seems a potentially relevant thing to put i... by Jarrod Johnson
cp -a /etc/confluent/ssh/* /etc/ssh/
systemctl restart sshd
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
@@ -1,10 +1,10 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -s https://${whost}/confluent-api/ > /dev/null; then
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
@@ -63,10 +63,10 @@ fetch_remote() {
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
@@ -174,10 +174,10 @@ run_remote_python() {
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]]; then
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
+6 -5
View File
@@ -18,7 +18,8 @@
#define MAXPACKET 1024
static const char cryptalpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char cryptalpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";
static const char b64alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
unsigned char* genpasswd(int len) {
unsigned char * passwd;
@@ -56,10 +57,10 @@ char * b64e(uint8_t * data, uint32_t datalen) {
currchunk[1] = remaining > 1 ? currptr[1] : 0;
currchunk[2] = remaining > 2 ? currptr[2] : 0;
currptr += 3;
currout[0] = cryptalpha[currchunk[0] >> 2];
currout[1] = cryptalpha[(currchunk[0] << 4 | currchunk[1] >> 4) & 0x3f];
currout[2] = remaining > 1 ? cryptalpha[(currchunk[1] << 2 | currchunk[2] >> 6) & 0x3f] : '=';
currout[3] = remaining > 2 ? cryptalpha[currchunk[2] & 0x3f] : '=';
currout[0] = b64alpha[currchunk[0] >> 2];
currout[1] = b64alpha[(currchunk[0] << 4 | currchunk[1] >> 4) & 0x3f];
currout[2] = remaining > 1 ? b64alpha[(currchunk[1] << 2 | currchunk[2] >> 6) & 0x3f] : '=';
currout[3] = remaining > 2 ? b64alpha[currchunk[2] & 0x3f] : '=';
remaining -= 3;
currout += 4;
}
+348
View File
@@ -0,0 +1,348 @@
/*
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2021 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.
*/
#define FUSE_USE_VERSION 30
#include <fuse3/fuse.h>
#include <curl/curl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <pthread.h>
#include <unistd.h>
CURL *curl;
char curlerror[CURL_ERROR_SIZE];
curl_off_t filesize;
typedef struct downloadbuffer {
char *response;
size_t completed;
size_t total;
} downloadbuffer;
#define MAX_FILE_LEN 1024
#define MAX_URL_PATHS 512
static char filename[MAX_FILE_LEN + 1];
static int urlidx, newidx;
static char* urls[MAX_URL_PATHS + 1];
void *http_rechecker(void *argp) {
CURL *checkurl;
int tmpidx, tmpval;
tmpidx = open("/dev/urandom", O_RDONLY);
if (tmpidx <= 0 || read(tmpidx, (char*)&tmpval, 4) < 0)
tmpval = time(NULL) & 0xffffffff;
if (tmpidx >= 0)
close(tmpidx);
srand(tmpval);
checkurl = curl_easy_init();
if (curl_easy_setopt(checkurl, CURLOPT_ERRORBUFFER, curlerror) != CURLE_OK) {
fprintf(stderr, "Error buffer\n");
exit(1);
}
//We want to consider error conditions fatal, rather than
//passing error text as data
if (curl_easy_setopt(checkurl, CURLOPT_FAILONERROR, 1L) != CURLE_OK) {
fprintf(stderr, "Fail on error\n");
exit(1);
}
if (curl_easy_setopt(checkurl, CURLOPT_TIMEOUT, 10L) != CURLE_OK) {
fprintf(stderr, "Error setting timeout\n");
exit(1);
}
if (curl_easy_setopt(checkurl, CURLOPT_NOBODY, 1) != CURLE_OK) {
fprintf(stderr, "Error setting nobody\n");
exit(1);
}
while (1) {
sleep(25 + tmpval % 10); // Spread out retries across systems
tmpidx = 0;
while (tmpidx < urlidx && tmpidx < newidx && urls[tmpidx] != NULL) {
if (curl_easy_setopt(checkurl, CURLOPT_URL, urls[tmpidx]) != CURLE_OK) {
tmpidx++;
continue;
}
if (curl_easy_perform(checkurl) == CURLE_OK)
newidx = tmpidx;
else
tmpidx++;
}
}
}
static int http_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags frf)
{
if (strcmp(path, "/") != 0) // We don't support subdirs
return -ENOENT;
filler(buf, ".", NULL, 0, 0);
filler(buf, "..", NULL, 0, 0);
filler(buf, filename + 1, NULL, 0, 0);
return 0;
}
size_t fill_buffer(char *data, size_t size, size_t nmemb, downloadbuffer *userdata) {
size_t amount;
amount = size * nmemb;
if (userdata->total < amount + userdata->completed) return 0;
memcpy(&(userdata->response[userdata->completed]), data, amount);
userdata->completed += amount;
return amount;
}
static int http_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi) {
char headbuffer[512];
double dldbl = 0.0;
int startidx;
int reconnecting = 0;
FILE* fd;
startidx = urlidx;
memset(buf, 0, size);
curl_off_t downloaded;
//Would be needed for multithread, however preferring to conserve
//filehandles rather than go multithread
// Some comparisons showed that the threaded performance boost doesn't
// do even offset the overhead of the new curl handles, so better
// to use single threaded curl overall for now
//CURL *tmpcurl = curl_easy_duphandle(curl);
downloadbuffer dlbuf;
dlbuf.response = buf;
dlbuf.completed = 0;
dlbuf.total = size;
fd = NULL;
if (strcmp(path, filename) != 0) return -ENOENT;
memset(headbuffer, 0, 512);
if (offset >= filesize) return 0;
if (offset + size - 1 >= filesize) size = filesize - offset - 1;
snprintf(headbuffer, 512, "%ld-%ld", offset, offset + size - 1);
if (curl_easy_setopt(curl, CURLOPT_RANGE, headbuffer) != CURLE_OK) {
fprintf(stderr, "Error setting range\n");
exit(1);
}
if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&dlbuf) != CURLE_OK) {
fprintf(stderr, "Error setting writedata\n");
exit(1);
}
if (newidx < MAX_URL_PATHS) {
reconnecting = 1;
urlidx = newidx;
newidx = MAX_URL_PATHS;
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<5>urlmount: Connecting to %s\n", urls[urlidx]);
fclose(fd);
// if fail, carry on and take the error in curl_easy_perform instead
if (curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]) != CURLE_OK) {}
}
while (curl_easy_perform(curl) != CURLE_OK) {
reconnecting = 1;
fd = fopen("/dev/kmsg", "w+");
dlbuf.completed = 0;
fprintf(fd, "<4>urlmount: error while communicating with %s: %s\n", urls[urlidx], curlerror);
fclose(fd);
urlidx++;
if (urlidx > MAX_URL_PATHS)
urlidx = 0;
if (urls[urlidx] == NULL)
urlidx = 0;
if (urlidx == startidx) {
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<1>urlmount: All connections to source are down\n");
fclose(fd);
sleep(10);
}
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<5>urlmount: Connecting to %s\n", urls[urlidx]);
fclose(fd);
if (urlidx > MAX_URL_PATHS) {
fprintf(stderr, "Maximum url path exceeded\n");
exit(1);
}
// ignore, let the curl_easy_perform get the error
if (curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]) != CURLE_OK) {}
}
if (reconnecting) {
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<5>urlmount: Successfully connected to %s\n", urls[urlidx]);
fclose(fd);
}
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &dldbl);
downloaded = round(dldbl);
//Would be needed for multithread
//curl_easy_cleanup(tmpcurl);
return downloaded;
}
static int http_open(const char *path, struct fuse_file_info *fi) {
if (strcmp(path, filename) != 0)
return -ENOENT;
if ((fi->flags & 3) != O_RDONLY)
return -EACCES;
return 0;
}
static void* http_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
// Because we fork, we need to redo curl
// or else suffer the wrath of NSS TLS
pthread_t tid;
curl_global_init(CURL_GLOBAL_DEFAULT);
pthread_create(&tid, NULL, http_rechecker, NULL);
curl = curl_easy_init();
if (curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerror) != CURLE_OK) {
fprintf(stderr, "Failure initializing libcurl error buffor\n");
exit(1);
}
//We want to consider error conditions fatal, rather than
//passing error text as data
if (curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L) != CURLE_OK) {
fprintf(stderr, "Failure initializing libcurl failonerror\n");
exit(1);
}
if (curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L) != CURLE_OK) {
fprintf(stderr, "Failure initializing libcurl timeout\n");
exit(1);
}
if (curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]) != CURLE_OK) {
fprintf(stderr, "Failure initializing url\n");
exit(1);
}
if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fill_buffer) != CURLE_OK) {
fprintf(stderr, "Failure initializing libcurl fill buffer\n");
exit(1);
}
return NULL;
}
static int http_getattr(const char *path, struct stat *st, struct fuse_file_info *ffi) {
memset(st, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
st->st_mode = S_IFDIR | 0555;
st->st_nlink = 2;
} else if (strcmp(path, filename) == 0) {
st->st_mode = S_IFREG | 0444;
st->st_nlink = 1;
st->st_size = filesize; // TODO: fix with curl HEAD
} else
return -ENOENT;
return 0;
}
static const struct fuse_operations http_ops = {
.getattr = http_getattr,
.readdir = http_readdir,
.read = http_read,
.open = http_open,
.init = http_init,
};
int main(int argc, char* argv[]) {
char *tmp;
double fsize;
unsigned int i;
int j;
j = 0;
memset(urls, 0, 32*sizeof(char*));
urlidx = 0;
newidx = MAX_URL_PATHS;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerror) != CURLE_OK) {
fprintf(stderr, "Unable to set error buffer\n");
exit(1);
}
//We want to consider error conditions fatal, rather than
//passing error text as data
if (curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L) != CURLE_OK) {
fprintf(stderr, "Unable to set fail on error\n");
exit(1);
}
if (curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L) != CURLE_OK) {
fprintf(stderr, "Unable to setup curl timeout\n");
exit(1);
}
memset(filename, 0, MAX_FILE_LEN);
for (i=0; i < argc; i++) {
if (strstr(argv[i], ":") > 0) {
if (j < MAX_URL_PATHS) {
urls[j] = argv[i];
tmp = strrchr(urls[j++], '/');
strncpy(filename, tmp, MAX_FILE_LEN);
}
//Request single threaded mode, as curl would need more
// filehandles for multithread
argv[i] = "-s";
}
}
if (filename[0] == 0) {
fprintf(stderr, "No URL given in arguments\n");
exit(1);
}
for (i=0; urls[i] != NULL; i++) {
printf("Registering mount path: %s\n", urls[i]);
}
j = urlidx;
printf("Connecting to %s\n", urls[urlidx]);
if (curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]) != CURLE_OK) {
fprintf(stderr, "Unable to set url\n");
}
if (curl_easy_setopt(curl, CURLOPT_NOBODY, 1) != CURLE_OK) {
fprintf(stderr, "Failure setting no body\n");
}
while (curl_easy_perform(curl) != CURLE_OK) {
fprintf(stderr, "urlmount: error while communicating with %s: %s\n", urls[urlidx++], curlerror);
if (urls[urlidx] == NULL)
urlidx = 0;
if (urlidx == j) {
fprintf(stderr, "urlmount: Unable to reach any target url, aborting\n");
exit(1);
}
printf("Connecting to %s\n", urls[urlidx]);
if (curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]) != CURLE_OK) {
fprintf(stderr, "Unable to set url\n");
}
}
printf("Successfully connected to %s\n", urls[urlidx]);
if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &fsize) != CURLE_OK) {
fprintf(stderr, "Failed getting content length\n");
exit(1);
}
filesize = round(fsize);
if (curl_easy_setopt(curl, CURLOPT_NOBODY, 0) != CURLE_OK) {
fprintf(stderr, "Failed setting nobody\n");
exit(1);
}
if (filesize < 1) {
fprintf(stderr, "Unable to reach designated URL\n");
exit(1);
}
if (!curl) {
fprintf(stderr, "Unable to initialize CURL!\n");
exit(1);
}
curl_easy_cleanup(curl);
curl_global_cleanup();
fuse_main(argc, argv, &http_ops, NULL);
}
+11 -6
View File
@@ -52,11 +52,12 @@ def make_certificate():
os.umask(umask)
def show_invitation(name):
def show_invitation(name, nonvoting=False):
if not os.path.exists('/etc/confluent/srvcert.pem'):
make_certificate()
s = client.Command().connection
tlvdata.send(s, {'collective': {'operation': 'invite', 'name': name}})
role = 'nonvoting' if nonvoting else None
tlvdata.send(s, {'collective': {'operation': 'invite', 'name': name, 'role': role}})
invite = tlvdata.recv(s)['collective']
if 'error' in invite:
sys.stderr.write(invite['error'] + '\n')
@@ -104,16 +105,19 @@ def show_collective():
return
if 'quorum' in res['collective']:
print('Quorum: {0}'.format(res['collective']['quorum']))
print('Leader: {0}'.format(res['collective']['leader']))
leadernonvoting = res['collective']['leader'] in res['collective'].get('nonvoting', ())
print('Leader: {0}{1}'.format(res['collective']['leader'], ' (non-voting)' if leadernonvoting else ''))
if 'active' in res['collective']:
if res['collective']['active']:
print('Active collective members:')
for member in sortutil.natural_sort(res['collective']['active']):
print(' {0}'.format(member))
nonvoter = member in res['collective'].get('nonvoting', ())
print(' {0}{1}'.format(member, ' (nonvoting)' if nonvoter else ''))
if res['collective']['offline']:
print('Offline collective members:')
for member in sortutil.natural_sort(res['collective']['offline']):
print(' {0}'.format(member))
nonvoter = member in res['collective'].get('nonvoting', ())
print(' {0}{1}'.format(member, ' (nonvoting)' if nonvoter else ''))
else:
print('Run collective show on leader for more data')
@@ -128,6 +132,7 @@ def main():
'collective member. Run collective invite -h for more information')
ic.add_argument('name', help='Name of server to invite to join the '
'collective')
ic.add_argument('-n', help='Join as a non-voting member, do not have this member contribute to quorum', action='store_true')
dc = sp.add_parser('delete', help='Delete a member of a collective')
dc.add_argument('name', help='Name of server to delete from collective')
jc = sp.add_parser('join', help='Join a collective. Run collective join -h for more information')
@@ -138,7 +143,7 @@ def main():
if cmdset.command == 'gencert':
make_certificate()
elif cmdset.command == 'invite':
show_invitation(cmdset.name)
show_invitation(cmdset.name, cmdset.n)
elif cmdset.command == 'join':
join_collective(cmdset.server, cmdset.i)
elif cmdset.command == 'show':
+16 -1
View File
@@ -15,7 +15,7 @@ import confluent.sshutil as sshutil
import confluent.certutil as certutil
import confluent.client as client
import confluent.config.configmanager as configmanager
import subprocess
import eventlet.green.subprocess as subprocess
import tempfile
import shutil
import eventlet.green.socket as socket
@@ -237,11 +237,17 @@ if __name__ == '__main__':
allok = True
uuidok = False
macok = False
valid_nodes = [node['item']['href'][:-1] for node in sess.read('/nodes/')] #get all valid nodes
for rsp in sess.read(f'/nodes/{args.node}/attributes/all'):
if rsp.get('errorcode', None) == 404:
emprint(f'There is no node named "{args.node}"')
allok = False
uuidok = True # not really, but suppress the spurious error
dnsdomain = rsp.get('dns.domain', {}).get('value', '')
if ',' in dnsdomain or ' ' in dnsdomain:
allok = False
emprint(f'{args.node} has a dns.domain that appears to be a search instead of singular domain')
uuidok = True # not really, but suppress the spurious error
uuid = rsp.get('id.uuid', {}).get('value', None)
if uuid:
uuidok = True
@@ -254,6 +260,15 @@ if __name__ == '__main__':
mac = rsp[key].get('value', None)
if mac:
macok = True
#adding new code to check if the response is something like net.<something>switch
for key in rsp:
if ((key.startswith('net.') and key.endswith('switch'))
or (key.startswith('power.') and key.endswith('pdu'))
or (key.startswith('enclosure.') and key.endswith('manager'))
):
switch_value = rsp[key].get('value',None)
if switch_value and switch_value not in valid_nodes:
emprint(f'{switch_value} is not a valid node name (as referenced by attribute "{key}" of node {args.node}).')
if not uuidok and not macok:
allok = False
emprint(f'{args.node} does not have a uuid or mac address defined in id.uuid or net.*hwaddr, deployment will not work')
+12 -2
View File
@@ -217,14 +217,24 @@ def install_tftp_content():
else:
emprint(
'Detected {0} as tftp directory, but unable to determine tftp service, ensure that a tftp server is installed and enabled manually'.format(tftplocation))
otftplocation = tftplocation
tftplocation = '{0}/confluent/x86_64'.format(tftplocation)
try:
os.makedirs(tftplocation)
except OSError as e:
if e.errno != 17:
raise
armtftplocation = '{0}/confluent/aarch64'.format(otftplocation)
try:
os.makedirs(armtftplocation)
except OSError as e:
if e.errno != 17:
raise
shutil.copy('/opt/confluent/lib/ipxe/ipxe.efi', tftplocation)
shutil.copy('/opt/confluent/lib/ipxe/ipxe.kkpxe', tftplocation)
if os.path.exists('/opt/confluent/lib/ipxe/ipxe-aarch64.efi'):
shutil.copy('/opt/confluent/lib/ipxe/ipxe-aarch64.efi', os.path.join(armtftplocation, 'ipxe.efi'))
def initialize(cmdset):
@@ -285,13 +295,13 @@ def initialize(cmdset):
try:
subprocess.check_call(['systemctl', 'try-restart', 'httpd'])
print('HTTP server has been restarted if it was running')
except subprocess.CalledProcessError:
except Exception:
emprint('New HTTPS certificates generated, restart the web server manually')
elif os.path.exists('/usr/lib/systemd/system/apache2.service'):
try:
subprocess.check_call(['systemctl', 'try-restart', 'apache2'])
print('HTTP server has been restarted if it was running')
except subprocess.CalledProcessError:
except Exception:
emprint('New HTTPS certificates generated, restart the web server manually')
else:
emprint('New HTTPS certificates generated, restart the web server manually')
+42
View File
@@ -41,6 +41,7 @@ try:
except ImportError:
pass
import time
import yaml
_pamservice = 'confluent'
_passcache = {}
@@ -115,6 +116,47 @@ class PromptsNeeded(Exception):
def __init__(self, prompts):
self.prompts = prompts
#add function to change _allowedbyrole and _deniedbyrole vars.
def add_roles(_allowed,_denied):
# function to parse the roles and the files. If there are modifications to be done to the roles, items will be added to dictionaries.
# If there are no moodifications done to one of the roles, it continues to the next
# Opening YAML file and reading the custom roles
with open("/etc/confluent/authorization.yaml","r") as stream:
loaded_file = yaml.safe_load(stream)
try:
allowed_loaded = loaded_file["allowedbyrole"]
for role in allowed_loaded:
if role not in configmanager._validroles:
configmanager._validroles.append(role)
except:
pass
try:
denied_loaded = loaded_file["deniedbyrole"]
except:
pass
try:
_allowed.update(allowed_loaded)
except NameError:
pass
try:
_denied.update(denied_loaded)
except NameError:
pass
return
def check_for_yaml():
#checking if the file exists
if os.path.exists("/etc/confluent/authorization.yaml"):
add_roles(_allowedbyrole,_deniedbyrole)
return "Custom auth. file detected in /etc/confluent, updated roles accordingly"
else:
return "No custom auth. file. Continuing as normal"
def _get_usertenant(name, tenant=False):
"""_get_usertenant
@@ -22,12 +22,13 @@ import hmac
import os
pending_invites = {}
def create_server_invitation(servername):
def create_server_invitation(servername, role):
servername = servername.encode('utf-8')
randbytes = (3 - ((len(servername) + 2) % 3)) % 3 + 64
invitation = os.urandom(randbytes)
pending_invites[servername] = invitation
return base64.b64encode(servername + b'@' + invitation)
pending_invites[servername] = {'invitation': invitation, 'role': role}
invite = servername + b'@' + invitation
return base64.b64encode(invite)
def create_client_proof(invitation, mycert, peercert):
return hmac.new(invitation, peercert + mycert, hashlib.sha256).digest()
@@ -42,6 +43,8 @@ def check_client_proof(servername, mycert, peercert, proof):
if servername not in pending_invites:
return False
invitation = pending_invites[servername]
role = invitation['role']
invitation = invitation['invitation']
validproof = hmac.new(invitation, mycert + peercert, hashlib.sha256
).digest()
if proof == validproof:
@@ -54,7 +57,7 @@ def check_client_proof(servername, mycert, peercert, proof):
# Now to generate an answer...., reverse the cert order so our answer
# is different, but still proving things
return hmac.new(invitation, peercert + mycert, hashlib.sha256
).digest()
).digest(), role
# The given proof did not verify the invitation
return False
return False, None
+104 -45
View File
@@ -61,14 +61,20 @@ class ContextBool(object):
connecting = ContextBool()
leader_init = ContextBool()
enrolling = ContextBool()
def connect_to_leader(cert=None, name=None, leader=None, remote=None):
def connect_to_leader(cert=None, name=None, leader=None, remote=None, isretry=False):
global currentleader
global follower
ocert = cert
oname = name
oleader = leader
oremote = remote
if leader is None:
leader = currentleader
log.log({'info': 'Attempting connection to leader {0}'.format(leader),
'subsystem': 'collective'})
if not isretry:
log.log({'info': 'Attempting connection to leader {0}'.format(leader),
'subsystem': 'collective'})
try:
remote = connect_to_collective(cert, leader, remote)
except Exception as e:
@@ -79,9 +85,11 @@ def connect_to_leader(cert=None, name=None, leader=None, remote=None):
with connecting:
with cfm._initlock:
banner = tlvdata.recv(remote) # the banner
if not banner:
return
vers = banner.split()[2]
if vers not in (b'v2', b'v3'):
raise Exception('This instance only supports protocol 2 or 3, synchronize versions between collective members')
if vers != b'v4':
raise Exception('This instance only supports protocol 4, synchronize versions between collective members')
tlvdata.recv(remote) # authpassed... 0..
if name is None:
name = get_myname()
@@ -98,7 +106,12 @@ def connect_to_leader(cert=None, name=None, leader=None, remote=None):
'{0}'.format(leader),
'subsystem': 'collective'})
return False
if 'waitinline' in keydata:
eventlet.sleep(0.3)
return connect_to_leader(cert, name, leader, None, isretry=True)
if 'leader' in keydata:
if keydata['leader'] == None:
return None
log.log(
{'info': 'Prospective leader {0} has redirected this '
'member to {1}'.format(leader, keydata['leader']),
@@ -138,7 +151,9 @@ def connect_to_leader(cert=None, name=None, leader=None, remote=None):
remote.close()
except Exception:
pass
raise Exception("Error doing initial DB transfer")
log.log({'error': 'Retrying connection, error during initial sync', 'subsystem': 'collective'})
return connect_to_leader(ocert, oname, oleader, None)
raise Exception("Error doing initial DB transfer") # bad ssl write retry
dbjson += ndata
cfm.clear_configuration()
try:
@@ -146,7 +161,7 @@ def connect_to_leader(cert=None, name=None, leader=None, remote=None):
for c in colldata:
cfm._true_add_collective_member(c, colldata[c]['address'],
colldata[c]['fingerprint'],
sync=False)
sync=False, role=colldata[c].get('role', None))
for globvar in globaldata:
cfm.set_global(globvar, globaldata[globvar], False)
cfm._txcount = dbi.get('txcount', 0)
@@ -264,7 +279,11 @@ def handle_connection(connection, cert, request, local=False):
return
if follower is not None:
linfo = cfm.get_collective_member_by_address(currentleader)
remote = socket.create_connection((currentleader, 13001), 15)
try:
remote = socket.create_connection((currentleader, 13001), 15)
except Exception:
cfm.stop_following()
return
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
@@ -321,7 +340,8 @@ def handle_connection(connection, cert, request, local=False):
#TODO(jjohnson2): Cannot do the invitation if not the head node, the certificate hand-carrying
#can't work in such a case.
name = request['name']
invitation = invites.create_server_invitation(name)
role = request.get('role', '')
invitation = invites.create_server_invitation(name, role)
tlvdata.send(connection,
{'collective': {'invitation': invitation}})
connection.close()
@@ -390,34 +410,44 @@ def handle_connection(connection, cert, request, local=False):
eventlet.spawn_n(connect_to_leader, rsp['collective'][
'fingerprint'], name)
if 'enroll' == operation:
#TODO(jjohnson2): error appropriately when asked to enroll, but the master is elsewhere
mycert = util.get_certificate_from_file('/etc/confluent/srvcert.pem')
proof = base64.b64decode(request['hmac'])
myrsp = invites.check_client_proof(request['name'], mycert,
cert, proof)
if not myrsp:
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)
cfm.add_collective_member(get_myname(),
connection.getsockname()[0], myfprint)
cfm.add_collective_member(request['name'],
connection.getpeername()[0], fprint)
myleader = get_leader(connection)
ldrfprint = cfm.get_collective_member_by_address(
myleader)['fingerprint']
tlvdata.send(connection,
{'collective': {'approval': myrsp,
'fingerprint': ldrfprint,
'leader': get_leader(connection)}})
with enrolling:
cfm.check_quorum()
mycert = util.get_certificate_from_file('/etc/confluent/srvcert.pem')
proof = base64.b64decode(request['hmac'])
myrsp, role = invites.check_client_proof(request['name'], mycert,
cert, proof)
if not myrsp:
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)
iam = cfm.get_collective_member(get_myname())
if not iam:
cfm.add_collective_member(get_myname(),
connection.getsockname()[0], myfprint)
cfm.add_collective_member(request['name'],
connection.getpeername()[0], fprint, role)
myleader = get_leader(connection)
ldrfprint = cfm.get_collective_member_by_address(
myleader)['fingerprint']
tlvdata.send(connection,
{'collective': {'approval': myrsp,
'fingerprint': ldrfprint,
'leader': get_leader(connection)}})
havequorum = False
while not havequorum:
try:
cfm.check_quorum()
havequorum = True
except exc.DegradedCollective:
eventlet.sleep(0.1)
if 'assimilate' == operation:
drone = request['name']
droneinfo = cfm.get_collective_member(drone)
@@ -519,6 +549,11 @@ def handle_connection(connection, cert, request, local=False):
'backoff': True})
connection.close()
return
if leader_init.active:
tlvdata.send(connection, {'error': 'Servicing a connection',
'waitinline': True})
connection.close()
return
if myself != get_leader(connection):
tlvdata.send(
connection,
@@ -534,7 +569,7 @@ def handle_connection(connection, cert, request, local=False):
'txcount': cfm._txcount})
log.log({'info': 'Connecting to leader due to superior '
'transaction count', 'subsystem': 'collective'})
connection.close()
connection.close() # well this won't work
if not connect_to_leader(
None, None, connection.getpeername()[0]):
if retrythread is None:
@@ -551,9 +586,15 @@ def handle_connection(connection, cert, request, local=False):
tlvdata.send(connection, cfm._cfgstore['collective'])
tlvdata.send(connection, {'confluent_uuid': cfm.get_global('confluent_uuid')}) # cfm.get_globals())
cfgdata = cfm.ConfigManager(None)._dump_to_json()
tlvdata.send(connection, {'txcount': cfm._txcount,
'dbsize': len(cfgdata)})
connection.sendall(cfgdata)
try:
tlvdata.send(connection, {'txcount': cfm._txcount,
'dbsize': len(cfgdata)})
connection.sendall(cfgdata)
except Exception:
try:
connection.close()
finally:
return None
#tlvdata.send(connection, {'tenants': 0}) # skip the tenants for now,
# so far unused anyway
connection.settimeout(90)
@@ -575,9 +616,12 @@ def populate_collinfo(collinfo):
activemembers = set(cfm.cfgstreams)
activemembers.add(iam)
collinfo['offline'] = []
collinfo['nonvoting'] = []
for member in cfm.list_collective():
if member not in activemembers:
collinfo['offline'].append(member)
if cfm.get_collective_member(member).get('role', None) == 'nonvoting':
collinfo['nonvoting'].append(member)
def try_assimilate(drone, followcount, remote):
@@ -639,7 +683,11 @@ def get_leader(connection):
def retire_as_leader(newleader=None):
global currentleader
global reassimilate
cfm.stop_leading(newleader)
if reassimilate is not None:
reassimilate.kill()
reassimilate = None
currentleader = None
def become_leader(connection):
@@ -647,6 +695,10 @@ def become_leader(connection):
global follower
global retrythread
global reassimilate
if cfm.get_collective_member(get_myname()).get('role', None) == 'nonvoting':
log.log({'info': 'Refraining from being leader of collective (nonvoting)',
'subsystem': 'collective'})
return False
log.log({'info': 'Becoming leader of collective',
'subsystem': 'collective'})
if follower is not None:
@@ -658,15 +710,20 @@ def become_leader(connection):
retrythread = None
currentleader = connection.getsockname()[0]
skipaddr = connection.getpeername()[0]
if reassimilate is not None:
reassimilate.kill()
reassimilate = eventlet.spawn(reassimilate_missing)
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():
while True:
try:
_assimilate_missing()
except Exception as e:
cfm.logException()
eventlet.sleep(30)
def _assimilate_missing(skipaddr=None):
@@ -780,6 +837,8 @@ def start_collective():
for member in sorted(list(cfm.list_collective())):
if member == myname:
continue
if cfm.get_collective_member(member).get('role', None) == 'nonvoting':
continue
if cfm.cfgleader is None:
cfm.stop_following(True)
ldrcandidate = cfm.get_collective_member(member)['address']
@@ -233,6 +233,12 @@ node = {
'a stateless profile would be both after first boot.')
},
'deployment.state': {
'description': ('Profiles may push more specific state, for example, it may set the state to "failed" or "succeded"'),
},
'deployment.state_detail': {
'description': ('Detailed state information as reported by an OS profile, when available'),
},
'deployment.useinsecureprotocols': {
'description': ('What phase(s) of boot are permitted to use insecure protocols '
'(TFTP and HTTP without TLS. By default, only HTTPS is used. However '
@@ -59,13 +59,21 @@ except ModuleNotFoundError:
import ast
import base64
from binascii import hexlify
import os
import sys
if __name__ == '__main__':
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', '..'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.config.attributes as allattributes
import confluent.config.conf as conf
import confluent.log
import confluent.noderange as noderange
import confluent.util
import confluent.netutil as netutil
import confluent.exceptions as exc
import confluent.log
import copy
import crypt
try:
@@ -77,18 +85,18 @@ import eventlet
import eventlet.event as event
import eventlet.green.select as select
import eventlet.green.threading as gthread
import eventlet.green.subprocess as subprocess
import fnmatch
import hashlib
import json
import msgpack
import operator
import os
import random
import re
import string
import struct
import sys
import threading
import time
import traceback
try:
unicode
@@ -119,7 +127,7 @@ _attraliases = {
'bmcpass': 'secret.hardwaremanagementpassword',
'switchpass': 'secret.hardwaremanagementpassword',
}
_validroles = ('Administrator', 'Operator', 'Monitor', 'Stub')
_validroles = ['Administrator', 'Operator', 'Monitor', 'Stub']
membership_callback = None
@@ -320,7 +328,7 @@ def _rpc_set_group_attributes(tenant, attribmap, autocreate):
def check_quorum():
if isinstance(cfgleader, bool):
raise exc.DegradedCollective()
if (not cfgleader) and len(cfgstreams) < (len(_cfgstore.get('collective', {})) // 2):
if (not cfgleader) and (not has_quorum()):
# the leader counts in addition to registered streams
raise exc.DegradedCollective()
if cfgleader and not _hasquorum:
@@ -348,9 +356,9 @@ def exec_on_followers(fnname, *args):
pushes = eventlet.GreenPool()
# Check health of collective prior to attempting
for _ in pushes.starmap(
_push_rpc, [(cfgstreams[s], b'') for s in cfgstreams]):
_push_rpc, [(cfgstreams[s]['stream'], b'') for s in cfgstreams]):
pass
if len(cfgstreams) < (len(_cfgstore['collective']) // 2):
if not has_quorum():
# the leader counts in addition to registered streams
raise exc.DegradedCollective()
exec_on_followers_unconditional(fnname, *args)
@@ -363,7 +371,7 @@ def exec_on_followers_unconditional(fnname, *args):
payload = msgpack.packb({'function': fnname, 'args': args,
'txcount': _txcount}, use_bin_type=False)
for _ in pushes.starmap(
_push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]):
_push_rpc, [(cfgstreams[s]['stream'], payload) for s in cfgstreams]):
pass
@@ -421,7 +429,10 @@ def _push_rpc(stream, payload):
pass
if membership_callback:
membership_callback()
stream.close()
try:
stream.close()
except Exception:
pass
def decrypt_value(cryptvalue,
@@ -615,6 +626,40 @@ def set_global(globalname, value, sync=True):
if sync:
ConfigManager._bg_sync_to_file()
mycachedname = [None, 0]
def get_myname():
if mycachedname[1] > time.time() - 15:
return mycachedname[0]
try:
with open('/etc/confluent/cfg/myname', 'r') as f:
mycachedname[0] = f.read().strip()
mycachedname[1] = time.time()
return mycachedname[0]
except IOError:
myname = socket.gethostname().split('.')[0]
with open('/etc/confluent/cfg/myname', 'w') as f:
f.write(myname)
mycachedname[0] = myname
mycachedname[1] = time.time()
return myname
def has_quorum():
voters = 0
for follower in cfgstreams:
if cfgstreams[follower].get('role', None) != 'nonvoting':
voters += 1
iam = get_collective_member(get_myname())
myrole = None
if iam:
myrole = iam.get('role', None)
if myrole != 'nonvoting':
voters += 1
allvoters = 0
for ghost in list_collective():
if get_collective_member(ghost).get('role', None) != 'nonvoting':
allvoters += 1
return voters > allvoters // 2
cfgstreams = {}
def relay_slaved_requests(name, listener):
global cfgleader
@@ -622,21 +667,21 @@ def relay_slaved_requests(name, listener):
pushes = eventlet.GreenPool()
if name not in _followerlocks:
_followerlocks[name] = gthread.RLock()
meminfo = get_collective_member(name)
with _followerlocks[name]:
try:
stop_following()
if name in cfgstreams:
try:
cfgstreams[name].close()
cfgstreams[name]['stream'].close()
except Exception:
pass
del cfgstreams[name]
if membership_callback:
membership_callback()
cfgstreams[name] = listener
cfgstreams[name] = {'stream': listener, 'role': meminfo.get('role', None)}
lh = StreamHandler(listener)
_hasquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
_hasquorum = has_quorum()
_newquorum = None
while _hasquorum != _newquorum:
if _newquorum is not None:
@@ -644,10 +689,9 @@ def relay_slaved_requests(name, listener):
payload = msgpack.packb({'quorum': _hasquorum}, use_bin_type=False)
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
[(cfgstreams[s]['stream'], payload) for s in cfgstreams]):
pass
_newquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
_newquorum = has_quorum()
_hasquorum = _newquorum
if _hasquorum and _pending_collective_updates:
apply_pending_collective_updates()
@@ -656,7 +700,9 @@ def relay_slaved_requests(name, listener):
if name not in cfgstreams:
raise Exception("Unexpected loss of node in followers: " + name)
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
if sz == 0:
_push_rpc(listener, b'')
else:
rpc = b''
while len(rpc) < sz:
nrpc = listener.recv(sz - len(rpc))
@@ -693,12 +739,11 @@ def relay_slaved_requests(name, listener):
except KeyError:
pass # May have already been closed/deleted...
if cfgstreams:
_hasquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
_hasquorum = has_quorum()
payload = msgpack.packb({'quorum': _hasquorum}, use_bin_type=False)
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
[(cfgstreams[s]['stream'], payload) for s in cfgstreams]):
pass
if membership_callback:
membership_callback()
@@ -707,6 +752,16 @@ def relay_slaved_requests(name, listener):
return False
return True
lastheartbeat = None
def check_leader():
_push_rpc(cfgleader, b'')
tries = 0
while tries < 30:
eventlet.sleep(0.1)
tries += 1
if lastheartbeat and lastheartbeat > (confluent.util.monotonic_time() - 3):
return True
raise Exception("Leader has disappeared")
class StreamHandler(object):
def __init__(self, sock):
@@ -728,10 +783,13 @@ class StreamHandler(object):
res = _push_rpc(self.sock, b'') # nulls are a keepalive
if not res:
return None
#TODO: this test can work fine even if the other end is
# gone, go to a more affirmative test to more quickly
# detect outage to peer
self.keepalive = confluent.util.monotonic_time() + 20
self.expiry = confluent.util.monotonic_time() + 60
msg = self.sock.recv(8)
except Exception:
except Exception as e:
msg = None
return msg
@@ -756,8 +814,8 @@ def stop_leading(newleader=None):
for stream in list(cfgstreams):
try:
if rpcpayload is not None:
_push_rpc(cfgstreams[stream], rpcpayload)
cfgstreams[stream].close()
_push_rpc(cfgstreams[stream]['stream'], rpcpayload)
cfgstreams[stream]['stream'].close()
except Exception:
pass
try:
@@ -825,6 +883,7 @@ cfgleader = None
def follow_channel(channel):
global _txcount
global _hasquorum
global lastheartbeat
try:
stop_leading()
stop_following(channel)
@@ -832,7 +891,9 @@ def follow_channel(channel):
msg = lh.get_next_msg()
while msg:
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
if sz == 0:
lastheartbeat = confluent.util.monotonic_time()
else:
rpc = b''
while len(rpc) < sz:
nrpc = channel.recv(sz - len(rpc))
@@ -876,12 +937,12 @@ def follow_channel(channel):
return {}
def add_collective_member(name, address, fingerprint):
def add_collective_member(name, address, fingerprint, role=None):
if cfgleader:
return exec_on_leader('add_collective_member', name, address, fingerprint)
return exec_on_leader('add_collective_member', name, address, fingerprint, role)
if cfgstreams:
exec_on_followers('_true_add_collective_member', name, address, fingerprint)
_true_add_collective_member(name, address, fingerprint)
exec_on_followers('_true_add_collective_member', name, address, fingerprint, True, role)
_true_add_collective_member(name, address, fingerprint, role=role)
def del_collective_member(name):
if cfgleader and not isinstance(cfgleader, bool):
@@ -934,7 +995,7 @@ def apply_pending_collective_updates():
del _pending_collective_updates[name]
def _true_add_collective_member(name, address, fingerprint, sync=True):
def _true_add_collective_member(name, address, fingerprint, sync=True, role=None):
name = confluent.util.stringify(name)
if _cfgstore is None:
init(not sync) # use not sync to avoid read from disk
@@ -942,6 +1003,8 @@ def _true_add_collective_member(name, address, fingerprint, sync=True):
_cfgstore['collective'] = {}
_cfgstore['collective'][name] = {'name': name, 'address': address,
'fingerprint': fingerprint}
if role:
_cfgstore['collective'][name]['role'] = role
with _dirtylock:
if 'collectivedirty' not in _cfgstore:
_cfgstore['collectivedirty'] = set([])
@@ -1515,6 +1578,8 @@ class ConfigManager(object):
groupname = confluent.util.stringify(groupname)
if groupname in self._cfgstore['usergroups']:
raise Exception("Duplicate groupname requested")
if role is None:
role = 'Administrator'
for candrole in _validroles:
if candrole.lower().startswith(role.lower()):
role = candrole
@@ -1625,6 +1690,8 @@ class ConfigManager(object):
name = confluent.util.stringify(name)
if name in self._cfgstore['users']:
raise Exception("Duplicate username requested")
if role is None:
role = 'Administrator'
for candrole in _validroles:
if candrole.lower().startswith(role.lower()):
role = candrole
@@ -2162,9 +2229,6 @@ class ConfigManager(object):
self._bg_sync_to_file()
def add_node_attributes(self, attribmap):
for node in attribmap:
if 'groups' not in attribmap[node]:
attribmap[node]['groups'] = []
self.set_node_attributes(attribmap, autocreate=True)
def rename_nodes(self, renamemap):
@@ -2296,6 +2360,8 @@ class ConfigManager(object):
'"{0}" is not a valid node name'.format(node))
if autocreate is False and node not in self._cfgstore['nodes']:
raise ValueError("node {0} does not exist".format(node))
if 'groups' not in attribmap[node] and node not in self._cfgstore['nodes']:
attribmap[node]['groups'] = []
for attrname in list(attribmap[node]):
if attrname in _attraliases:
truename = _attraliases[attrname]
@@ -2502,6 +2568,15 @@ class ConfigManager(object):
data.
"""
with open(os.devnull, 'w+') as devnull:
worker = subprocess.Popen(
[sys.executable, __file__, '-r' if redact else ''],
stdin=devnull, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = worker.communicate()
return stdout
def _real_dump_to_json(self, redact=None):
dumpdata = {}
for confarea in _config_areas:
if confarea not in self._cfgstore:
@@ -2833,9 +2908,9 @@ def dump_db_to_directory(location, password, redact=None, skipkeys=False):
with open(os.path.join(location, 'keys.json'), 'w') as cfgfile:
cfgfile.write(_dump_keys(password))
cfgfile.write('\n')
with open(os.path.join(location, 'main.json'), 'w') as cfgfile:
with open(os.path.join(location, 'main.json'), 'wb') as cfgfile:
cfgfile.write(ConfigManager(tenant=None)._dump_to_json(redact=redact))
cfgfile.write('\n')
cfgfile.write(b'\n')
if 'collective' in _cfgstore:
with open(os.path.join(location, 'collective.json'), 'w') as cfgfile:
cfgfile.write(json.dumps(_cfgstore['collective']))
@@ -2876,6 +2951,11 @@ def init(stateless=False):
_cfgstore = {}
if __name__ == '__main__':
redact=None
if '-r' in sys.argv:
redact=True
sys.stdout.write(ConfigManager(None)._real_dump_to_json(redact))
# some unit tests worth implementing:
# set group attribute on lower priority group, result is that node should not
# change
+1 -1
View File
@@ -66,7 +66,7 @@ import eventlet.semaphore as semaphore
import itertools
import msgpack
import os
import socket
import eventlet.green.socket as socket
import struct
import sys
+4 -4
View File
@@ -79,14 +79,14 @@ class CredServer(object):
hmackey = hmackey.get(nodename, {}).get('secret.selfapiarmtoken', {}).get('value', None)
elif tlv[1]:
client.recv(tlv[1])
apimats = self.cfm.get_node_attributes(nodename,
['deployment.apiarmed', 'deployment.sealedapikey'])
apiarmed = apimats.get(nodename, {}).get('deployment.apiarmed', {}).get(
'value', None)
if not hmackey:
if not address_is_somewhat_trusted(peer[0], nodename, self.cfm):
client.close()
return
apimats = self.cfm.get_node_attributes(nodename,
['deployment.apiarmed', 'deployment.sealedapikey'])
apiarmed = apimats.get(nodename, {}).get('deployment.apiarmed', {}).get(
'value', None)
if not apiarmed:
if apimats.get(nodename, {}).get(
'deployment.sealedapikey', {}).get('value', None):
@@ -66,6 +66,7 @@ import confluent.config.configmanager as cfm
import confluent.collective.manager as collective
import confluent.discovery.protocols.pxe as pxe
import confluent.discovery.protocols.ssdp as ssdp
#import confluent.discovery.protocols.mdns as mdns
import confluent.discovery.protocols.slp as slp
import confluent.discovery.handlers.imm as imm
import confluent.discovery.handlers.cpstorage as cpstorage
@@ -116,6 +117,8 @@ nodehandlers = {
'pxe-client': pxeh,
'onie-switch': None,
'cumulus-switch': None,
'affluent-switch': None,
#'openbmc': None,
'service:io-device.Lenovo:management-module': None,
'service:thinkagile-storage': cpstorage,
'service:lenovo-tsm': tsm,
@@ -127,7 +130,9 @@ servicenames = {
'cumulus-switch': 'cumulus-switch',
'service:lenovo-smm': 'lenovo-smm',
'service:lenovo-smm2': 'lenovo-smm2',
'affluent-switch': 'affluent-switch',
'lenovo-xcc': 'lenovo-xcc',
#'openbmc': 'openbmc',
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
'service:io-device.Lenovo:management-module': 'lenovo-switch',
'service:thinkagile-storage': 'thinkagile-storagebmc',
@@ -140,6 +145,7 @@ servicebyname = {
'cumulus-switch': 'cumulus-switch',
'lenovo-smm': 'service:lenovo-smm',
'lenovo-smm2': 'service:lenovo-smm2',
'affluent-switch': 'affluent-switch',
'lenovo-xcc': 'lenovo-xcc',
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
'lenovo-switch': 'service:io-device.Lenovo:management-module',
@@ -223,6 +229,7 @@ def _printable_ip(sa):
def send_discovery_datum(info):
addresses = info.get('addresses', [])
addresses = util.natural_sort(addresses)
if info['handler'] == pxeh:
enrich_pxe_info(info)
yield msg.KeyValueData({'nodename': info.get('nodename', '')})
@@ -356,6 +363,14 @@ def show_info(mac):
for i in send_discovery_datum(known_info[mac]):
yield i
def dump_discovery():
infobymac = {}
for mac in known_info:
infobymac[mac] = {}
for i in send_discovery_datum(known_info[mac]):
for kn in i.kvpairs:
infobymac[mac][kn] = i.kvpairs[kn]
yield msg.KeyValueData(infobymac)
list_info = {
'by-node': list_matching_nodes,
@@ -599,11 +614,14 @@ def handle_read_api_request(pathcomponents):
# starting at 2 are parameters to previous index
if pathcomponents == ['discovery', 'rescan']:
return (msg.KeyValueData({'scanning': bool(scanner)}),)
if pathcomponents == ['discovery', 'alldata']:
return dump_discovery()
subcats, queryparms, indexof, coll = _parameterize_path(pathcomponents[1:])
if len(pathcomponents) == 1:
dirlist = [msg.ChildCollection(x + '/') for x in sorted(list(subcats))]
dirlist.append(msg.ChildCollection('rescan'))
dirlist.append(msg.ChildCollection('autosense'))
dirlist.append(msg.ChildCollection('alldata'))
dirlist.append(msg.ChildCollection('subscriptions/'))
return dirlist
if not coll:
@@ -1143,6 +1161,38 @@ def get_nodename_from_enclosures(cfg, info):
return nodename
def search_smms_by_cert(currsmm, cert, cfg):
neighs = []
cv = util.TLSCertVerifier(
cfg, currsmm, 'pubkeys.tls_hardwaremanager').verify_cert
try:
cd = cfg.get_node_attributes(currsmm, ['hardwaremanagement.manager',
'pubkeys.tls_hardwaremanager'])
smmaddr = cd.get(currsmm, {}).get('hardwaremanagement.manager', {}).get('value', None)
wc = webclient.SecureHTTPConnection(currsmm, verifycallback=cv)
neighs = wc.grab_json_response('/scripts/neighdata.json')
except Exception:
return None
for neigh in neighs:
fprint = neigh.get('sha384', None)
if fprint and fprint.endswith('AA=='):
fprint = fprint[:-4]
if fprint and util.cert_matches(fprint, cert):
port = neigh.get('port', None)
if port is not None:
bay = port + 1
nl = list(
cfg.filter_node_attributes('enclosure.manager=' + currsmm))
nl = list(
cfg.filter_node_attributes('enclosure.bay={}'.format(bay), nl))
if len(nl) == 1:
return currsmm, bay, nl[0]
return currsmm, bay, None
exnl = list(cfg.filter_node_attributes('enclosure.extends=' + currsmm))
if len(exnl) == 1:
return search_smms_by_cert(exnl[0], cert, cfg)
def eval_node(cfg, handler, info, nodename, manual=False):
try:
handler.probe() # unicast interrogation as possible to get more data
@@ -1177,6 +1227,14 @@ def eval_node(cfg, handler, info, nodename, manual=False):
# The specified node is an enclosure (has nodes mapped to it), but
# what we are talking to is *not* an enclosure
# might be ambiguous, need to match chassis-uuid as well..
match = search_smms_by_cert(nodename, handler.https_cert, cfg)
if match:
info['verfied'] = True
info['enclosure.bay'] = match[1]
if match[2]:
if not discover_node(cfg, handler, info, match[2], manual):
pending_nodes[match[2]] = info
return
if 'enclosure.bay' not in info:
unknown_info[info['hwaddr']] = info
info['discostatus'] = 'unidentified'
@@ -1577,6 +1635,7 @@ def stop_autosense():
def start_autosense():
autosensors.add(eventlet.spawn(slp.snoop, safe_detected, slp))
#autosensors.add(eventlet.spawn(mdns.snoop, safe_detected, mdns))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected, pxe, get_node_guess_by_uuid))
remotescan()
@@ -0,0 +1,449 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2023 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.
# Documented somewhat at
# http://buildingskb.schneider-electric.com/view.php?AID=15197
# Here is the payload of an SSDP 'announce', sent to the multicast v4/v6 1900
# NOTIFY * HTTP/1.1
# HOST: 239.255.255.250:1900
# CACHE-CONTROL: max-age=1800
# AL: https://172.30.254.151:8080/redfish/v1
# SERVER: Linux/3.14.28-ltsi Redfish/1.0
# NT: urn:dmtf-org:service:redfish-rest:1
# USN: uuid:00000000-0000-0000-0005-000000000001::urn:dmtf-org:service:redfish-rest:1
# NTS: ssdp:alive
import confluent.config.configmanager as cfm
import confluent.collective.manager as collective
import confluent.neighutil as neighutil
import confluent.noderange as noderange
import confluent.util as util
import confluent.log as log
import confluent.netutil as netutil
import eventlet
import eventlet.green.select as select
import eventlet.green.socket as socket
import eventlet.greenpool as gp
import os
import time
import struct
import traceback
webclient = eventlet.import_patched('pyghmi.util.webclient')
mcastv4addr = '224.0.0.251'
mcastv6addr = 'ff02::fb'
mdns6mcast = socket.inet_pton(socket.AF_INET6, mcastv6addr)
def name_to_qname(name):
nameparts = name.split('.')
qname = b''
for namepart in name.split('.'):
namepart = namepart.encode('utf8')
qname += bytes(bytearray([len(namepart)])) + namepart
qname += b'\x00'
return qname
PTR = 12
SRV = 33
def _makequery(name):
return struct.pack('!HHHHHH',
0, # transaction id
0, # flags, stdard query
1, # query count
0, # answers
0, # authorities
0) + \
name_to_qname(name) + \
struct.pack('!HH',
PTR,
(1 << 15) | 1) # Unicast response
#listsrvs = _makequery('_services._dns-sd._udp.local') # to get all possible services
listobmccons = _makequery('_obmc_console._tcp.local')
def _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler):
if mac in peerbymacaddress and peer not in peerbymacaddress[mac]['addresses']:
peerbymacaddress[mac]['addresses'].append(peer)
else:
sdata = _mdns_to_dict(rsp)
if not sdata:
return 0
peerdata = {
'hwaddr': mac,
'addresses': [peer],
'services': ['openbmc'],
'urls': '/redfish/v1/'
}
if sdata.get('ttl', 0) == 0:
if byehandler:
eventlet.spawn_n(check_fish_handler, byehandler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer)
return 1
if handler:
eventlet.spawn_n(check_fish_handler, handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer)
return 2
def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer):
retdata = check_fish(('/redfish/v1/', peerdata))
if retdata:
known_peers.add(peer)
newmacs.add(mac)
peerbymacaddress[mac] = retdata
machandlers[mac] = handler
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
"""Watch for unsolicited mDNS answers
The handler shall be called on any service coming online.
byehandler is called whenever a system advertises that it is departing.
If no byehandler is specified, byebye messages are ignored. The handler is
given (as possible), the mac address, a list of viable sockaddrs to reference
the peer, and the notification type (e.g.
'urn:dmtf-org:service:redfish-rest:1'
:param handler: A handler for online notifications from network
:param byehandler: Optional handler for devices going off the network
"""
# Normally, I like using v6/v4 agnostic socket. However, since we are
# dabbling in multicast wizardry here, such sockets can cause big problems,
# so we will have two distinct sockets
# TTL=0 is a wthdrawal, otherwise an announce
tracelog = log.Logger('trace')
net4, net6 = get_sockets()
net6.bind(('', 5353))
net4.bind(('', 5353))
try:
active_scan(handler, protocol)
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
known_peers = set([])
recent_peers = set([])
for ifidx in util.list_interface_indexes():
v6grp = mdns6mcast + struct.pack('=I', ifidx)
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, v6grp)
for i4 in util.list_ips():
mdns4mcast = socket.inet_pton(socket.AF_INET, mcastv4addr) + \
socket.inet_aton(i4['addr'])
try:
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
mdns4mcast)
except socket.error as e:
if e.errno != 98:
# errno 98 can happen if aliased, skip for now
raise
peerbymacaddress = {}
while True:
try:
newmacs = set([])
deferrednotifies = []
machandlers = {}
r = select.select((net4, net6), (), (), 60)
if r:
r = r[0]
recent_peers = set([])
while r and len(deferrednotifies) < 256:
for s in r:
(rsp, peer) = s.recvfrom(9000)
if peer in recent_peers:
continue
mac = neighutil.get_hwaddr(peer[0])
if not mac:
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
try:
s.sendto(b'\x00', probepeer)
except Exception:
continue
deferrednotifies.append((peer, rsp))
datum = _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
if datum == 2:
recent_peers.add(peer)
r = select.select((net4, net6), (), (), 1.5)
if r:
r = r[0]
if deferrednotifies:
eventlet.sleep(2.2)
for peerrsp in deferrednotifies:
peer, rsp = peerrsp
mac = neighutil.get_hwaddr(peer[0])
if not mac:
continue
_process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
for mac in newmacs:
thehandler = machandlers.get(mac, None)
if thehandler:
thehandler(peerbymacaddress[mac])
except Exception:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def _get_svrip(peerdata):
for addr in peerdata['addresses']:
if addr[0].startswith('fe80::'):
if '%' not in addr[0]:
return addr[0] + '%{0}'.format(addr[3])
return addr[0]
return peerdata['addresses'][0][0]
def get_sockets():
net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
net6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return net4, net6
def active_scan(handler, protocol=None):
net4, net6 = get_sockets()
for idx in util.list_interface_indexes():
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF,
idx)
try:
net6.sendto(listobmccons, (mcastv6addr, 5353, 0, 0))
except socket.error:
# ignore interfaces without ipv6 multicast causing error
pass
for i4 in util.list_ips():
if 'broadcast' not in i4:
continue
addr = i4['addr']
net4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
socket.inet_aton(addr))
try:
net4.sendto(listobmccons, (mcastv4addr, 5353))
except socket.error as se:
if se.errno != 101 and se.errno != 1:
raise
deadline = util.monotonic_time() + 2
r, _, _ = select.select((net4, net6), (), (), 2)
peerdata = {}
deferparse = []
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
if not neighutil.get_hwaddr(peer[0]):
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
try:
s.sendto(b'\x00', probepeer)
except Exception:
continue
deferparse.append((rsp, peer))
continue
_parse_mdns(peer, rsp, peerdata, '_obmc_console._tcp.local')
timeout = deadline - util.monotonic_time()
if timeout < 0:
timeout = 0
r, _, _ = select.select((net4, net6), (), (), timeout)
if deferparse:
eventlet.sleep(2.2)
for dp in deferparse:
rsp, peer = dp
_parse_mdns(peer, rsp, peerdata, '_obmc_console._tcp.local')
querypool = gp.GreenPool()
pooltargs = []
for nid in peerdata:
if '/redfish/v1/' not in peerdata[nid].get('urls', ()) and '/redfish/v1' not in peerdata[nid].get('urls', ()):
continue
pooltargs.append(('/redfish/v1/', peerdata[nid]))
# For now, don't interrogate generic redfish bmcs
# This is due to a need to deduplicate from some supported SLP
# targets (IMM, TSM, others)
# activate this else once the core filters/merges duplicate uuid
# or we drop support for those devices
#else:
# pooltargs.append(('/redfish/v1/', peerdata[nid]))
for pi in querypool.imap(check_fish, pooltargs):
if pi is not None:
handler(pi)
def check_fish(urldata, port=443, verifycallback=None):
if not verifycallback:
verifycallback = lambda x: True
url, data = urldata
try:
wc = webclient.SecureHTTPConnection(_get_svrip(data), port, verifycallback=verifycallback, timeout=1.5)
peerinfo = wc.grab_json_response(url)
except socket.error:
return None
if url == '/DeviceDescription.json':
try:
peerinfo = peerinfo[0]
myuuid = peerinfo['node-uuid'].lower()
if '-' not in myuuid:
myuuid = '-'.join([myuuid[:8], myuuid[8:12], myuuid[12:16], myuuid[16:20], myuuid[20:]])
data['uuid'] = myuuid
data['attributes'] = peerinfo
data['services'] = ['lenovo-xcc']
return data
except (IndexError, KeyError):
return None
url = '/redfish/v1/'
peerinfo = wc.grab_json_response('/redfish/v1/')
if url == '/redfish/v1/':
if 'UUID' in peerinfo:
if 'services' not in data:
data['services'] = ['service:redfish-bmc']
else:
data['services'].append('service:redfish-bmc')
data['uuid'] = peerinfo['UUID'].lower()
return data
return None
def extract_qname(view, reply):
name = ''
idx = 1
if isinstance(view[0], int):
currlen = view[0]
else:
currlen = ord(view[0])
while currlen:
if currlen == 192:
name += extract_qname(reply[view[1]:], reply)[1] + '.'
view = view[2:]
idx += 1
if name:
return idx, name[:-1]
return idx, ''
else:
name += view[1:currlen + 1].tobytes().decode('utf8') + '.'
view = view[currlen + 1:]
idx += currlen + 1
if not view:
break # some contexts don't null terminate
if isinstance(view[0], int):
currlen = view[0]
else:
currlen = ord(view[0])
if name:
return idx, name[:-1]
return idx, ''
def _mdns_to_dict(rsp):
txid, flags, quests, answers, arr, morerr = struct.unpack('!HHHHHH', rsp[:12])
rv = memoryview(rsp[12:])
rspv = memoryview(rsp)
retval = {}
while quests:
idx, name = extract_qname(rv, rspv)
rv = rv[idx:]
typ, dclass = struct.unpack('!HH', rv[:4])
quests -= 1
rv = rv[4:]
while answers:
idx, name = extract_qname(rv, rspv)
rv = rv[idx:]
typ, dclass, ttl, dlen = struct.unpack('!HHIH', rv[:10])
if 0 and typ == 12: # PTR, we don't need for now...
adata = extract_qname(rv[10:], rspv)
if 'ptrs' not in retval:
retval['ptrs'] = [{'name': adata, 'ttl': ttl}]
else:
retval['ptrs'].append({'name': adata, 'ttl': ttl})
if typ == 33:
portnum = struct.unpack('!H', rv[14:16])[0]
retval['protoname'] = name.split('.', 1)[1]
retval['portnumber'] = portnum
retval['ttl'] = ttl
rv = rv[dlen + 10:]
answers -= 1
return retval
def _parse_mdns(peer, rsp, peerdata, srvname):
parsed = _mdns_to_dict(rsp)
if not parsed:
return
if parsed.get('ttl', 0) == 0:
return
nid = peer[0]
mac = neighutil.get_hwaddr(peer[0])
if mac:
nid = mac
if nid in peerdata:
peerdatum = peerdata[nid]
if peer not in peerdatum['addresses']:
peerdatum['addresses'].append(peer)
else:
peerdatum = {
'addresses': [peer],
'hwaddr': mac,
'services': [srvname]
}
if srvname == '_obmc_console._tcp.local':
peerdatum['services'] = ['openbmc']
peerdatum['urls'] = ['/redfish/v1/']
peerdata[nid] = peerdatum
def _parse_ssdp(peer, rsp, peerdata):
nid = peer[0]
mac = None
mac = neighutil.get_hwaddr(peer[0])
if mac:
nid = mac
headlines = rsp.split(b'\r\n')
try:
_, code, _ = headlines[0].split(b' ', 2)
except ValueError:
return
if code == b'200':
if nid in peerdata:
peerdatum = peerdata[nid]
if peer not in peerdatum['addresses']:
peerdatum['addresses'].append(peer)
else:
peerdatum = {
'addresses': [peer],
'hwaddr': mac,
}
peerdata[nid] = peerdatum
for headline in headlines[1:]:
if not headline:
continue
header, _, value = headline.partition(b':')
header = header.strip().decode('utf8')
value = value.strip().decode('utf8')
if header == 'AL' or header == 'LOCATION':
value = value[value.index('://')+3:]
value = value[value.index('/'):]
if 'urls' not in peerdatum:
peerdatum['urls'] = [value]
elif value not in peerdatum['urls']:
peerdatum['urls'].append(value)
elif header == 'ST':
if 'services' not in peerdatum:
peerdatum['services'] = [value]
elif value not in peerdatum['services']:
peerdatum['services'].append(value)
elif header == 'USN':
peerdatum['usn'] = value
elif header == 'MODELNAME':
peerdatum['modelname'] = value
from pprint import pprint
if __name__ == '__main__':
def printit(rsp):
print(repr(rsp))
snoop(pprint)
@@ -297,90 +297,96 @@ def proxydhcp(handler, nodeguess):
msg.msg_namelen = ctypes.sizeof(clientaddr)
cfg = cfm.ConfigManager(None)
while True:
ready = select.select([net4011], [], [], None)
if not ready or not ready[0]:
continue
i = recvmsg(net4011.fileno(), ctypes.pointer(msg), 0)
#nb, client = net4011.recvfrom_into(rq)
if i < 240:
continue
rqv = memoryview(rq)[:i]
client = (ipfromint(clientaddr.sin_addr.s_addr), socket.htons(clientaddr.sin_port))
_, level, typ = struct.unpack('QII', cmsgarr[:16])
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
idx, recv = struct.unpack('II', cmsgarr[16:24])
recv = ipfromint(recv)
try:
optidx = rqv.tobytes().index(b'\x63\x82\x53\x63') + 4
except ValueError:
continue
hwlen = rq[2]
opts, disco = opts_to_dict(rq, optidx, 3)
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']]
elif disco.get('uuid', None) in uuidmap:
node = uuidmap[disco['uuid']]
myipn = myipbypeer.get(rqv[28:28+hwlen].tobytes(), None)
skiplogging = True
netaddr = disco['hwaddr']
if time.time() > ignoredisco.get(netaddr, 0) + 90:
skiplogging = False
ignoredisco[netaddr] = time.time()
if not myipn:
info = {'hwaddr': netaddr, 'uuid': disco['uuid'],
'architecture': disco['arch'],
'netinfo': {'ifidx': idx, 'recvip': recv},
'services': ('pxe-client',)}
if not skiplogging:
handler(info)
if not node:
if not myipn and not skiplogging:
log.log(
{'info': 'No node matches boot attempt from uuid {0} or hardware address {1}'.format(
disco.get('uuid', 'unknown'), disco.get('hwaddr', 'unknown')
)})
continue
profile = None
if not myipn:
myipn = socket.inet_aton(recv)
profile = get_deployment_profile(node, cfg)
if profile:
log.log({
'info': 'Offering proxyDHCP boot from {0} to {1} ({2})'.format(recv, node, client[0])})
else:
ready = select.select([net4011], [], [], None)
if not ready or not ready[0]:
continue
i = recvmsg(net4011.fileno(), ctypes.pointer(msg), 0)
#nb, client = net4011.recvfrom_into(rq)
if i < 240:
continue
rqv = memoryview(rq)[:i]
client = (ipfromint(clientaddr.sin_addr.s_addr), socket.htons(clientaddr.sin_port))
_, level, typ = struct.unpack('QII', cmsgarr[:16])
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
idx, recv = struct.unpack('II', cmsgarr[16:24])
recv = ipfromint(recv)
try:
optidx = rqv.tobytes().index(b'\x63\x82\x53\x63') + 4
except ValueError:
continue
hwlen = rq[2]
opts, disco = opts_to_dict(rq, optidx, 3)
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']]
elif disco.get('uuid', None) in uuidmap:
node = uuidmap[disco['uuid']]
myipn = myipbypeer.get(rqv[28:28+hwlen].tobytes(), None)
skiplogging = True
netaddr = disco['hwaddr']
if time.time() > ignoredisco.get(netaddr, 0) + 90:
skiplogging = False
ignoredisco[netaddr] = time.time()
if not myipn:
info = {'hwaddr': netaddr, 'uuid': disco['uuid'],
'architecture': disco['arch'],
'netinfo': {'ifidx': idx, 'recvip': recv},
'services': ('pxe-client',)}
if not skiplogging:
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP reply'.format(node)})
handler(info)
if not node:
if not myipn and not skiplogging:
log.log(
{'info': 'No node matches boot attempt from uuid {0} or hardware address {1}'.format(
disco.get('uuid', 'unknown'), disco.get('hwaddr', 'unknown')
)})
continue
if opts.get(77, None) == b'iPXE':
if not profile:
profile = None
if not myipn:
myipn = socket.inet_aton(recv)
profile = get_deployment_profile(node, cfg)
if not profile:
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP reply'.format(node)})
if profile:
log.log({
'info': 'Offering proxyDHCP boot from {0} to {1} ({2})'.format(recv, node, client[0])})
else:
if not skiplogging:
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP reply'.format(node)})
continue
if opts.get(77, None) == b'iPXE':
if not profile:
profile = get_deployment_profile(node, cfg)
if not profile:
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP reply'.format(node)})
continue
myip = socket.inet_ntoa(myipn)
bootfile = 'http://{0}/confluent-public/os/{1}/boot.ipxe'.format(myip, profile).encode('utf8')
elif disco['arch'] == 'uefi-x64':
bootfile = b'confluent/x86_64/ipxe.efi'
elif disco['arch'] == 'bios-x86':
bootfile = b'confluent/x86_64/ipxe.kkpxe'
elif disco['arch'] == 'uefi-aarch64':
bootfile = b'confluent/aarch64/ipxe.efi'
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
myip = socket.inet_ntoa(myipn)
bootfile = 'http://{0}/confluent-public/os/{1}/boot.ipxe'.format(myip, profile).encode('utf8')
elif disco['arch'] == 'uefi-x64':
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
rpv[240:243] = b'\x35\x01\x05'
rpv[243:249] = b'\x36\x04' + myipn
rpv[20:24] = myipn
rpv[249:268] = b'\x61\x11' + opts[97]
rpv[268:280] = b'\x3c\x09PXEClient\xff'
net4011.sendto(rpv[:281], client)
rpv[:240] = rqv[:240].tobytes()
rpv[0:1] = b'\x02'
rpv[108:108 + len(bootfile)] = bootfile
rpv[240:243] = b'\x35\x01\x05'
rpv[243:249] = b'\x36\x04' + myipn
rpv[20:24] = myipn
rpv[249:268] = b'\x61\x11' + opts[97]
rpv[268:280] = b'\x3c\x09PXEClient\xff'
net4011.sendto(rpv[:281], client)
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def start_proxydhcp(handler, nodeguess=None):
@@ -63,6 +63,9 @@ def _parse_slp_header(packet):
bytes(b'\x00' + packet[7:14]))
parsed['lang'] = packet[14:14 + langlen].decode('utf-8')
parsed['payload'] = packet[14 + langlen:]
errcode = struct.unpack('!H', packet[14 + langlen:16 + langlen])[0]
if errcode != 0:
return None
if offset:
parsed['offset'] = 14 + langlen
parsed['extoffset'] = offset
@@ -58,7 +58,7 @@ smsg = ('M-SEARCH * HTTP/1.1\r\n'
def active_scan(handler, protocol=None):
known_peers = set([])
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1']):
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1', 'urn::service:affluent']):
for addr in scanned['addresses']:
if addr in known_peers:
break
@@ -258,10 +258,15 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
seconds = int(currtime)
msecs = int(currtime * 1000 % 1000)
reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}\r\nCURRTIME: {1}\r\nCURRMSECS: {2}\r\n'.format(node, seconds, msecs)
if '%' in peer[0]:
iface = socket.getaddrinfo(peer[0], 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][-1]
reply += 'MGTIFACE: {0}\r\n'.format(
peer[0].split('%', 1)[1])
theip = peer[0].split('%', 1)[0]
if netutil.ip_on_same_subnet(theip, 'fe80::', 64):
if '%' in peer[0]:
ifidx = peer[0].split('%', 1)[1]
iface = socket.getaddrinfo(peer[0], 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][-1]
else:
ifidx = '{}'.format(peer[-1])
iface = peer[-1]
reply += 'MGTIFACE: {0}\r\n'.format(ifidx)
ncfg = netutil.get_nic_config(
cfg, node, ifidx=iface)
if ncfg.get('matchesnodename', None):
@@ -345,12 +350,15 @@ def _find_service(service, target):
try:
net4.sendto(msg, (mcastv4addr, 1900))
except socket.error as se:
if se.errno != 101:
if se.errno != 101 and se.errno != 1:
raise
msg = smsg.format(bcast, service)
if not isinstance(msg, bytes):
msg = msg.encode('utf8')
net4.sendto(msg, (bcast, 1900))
try:
net4.sendto(msg, (bcast, 1900))
except socket.error:
pass
# SSDP by spec encourages responses to spread out over a 3 second interval
# hence we must be a bit more patient
deadline = util.monotonic_time() + 4
@@ -381,6 +389,24 @@ def _find_service(service, target):
querypool = gp.GreenPool()
pooltargs = []
for nid in peerdata:
if peerdata[nid].get('services', [None])[0] == 'urn::service:affluent:1':
peerdata[nid]['attributes'] = {
'type': 'affluent-switch',
}
peerdata[nid]['services'] = ['affluent-switch']
mya = peerdata[nid]['attributes']
usn = peerdata[nid]['usn']
idinfo = usn.split('::')
for idi in idinfo:
key, val = idi.split(':', 1)
if key == 'uuid':
peerdata[nid]['uuid'] = val
elif key == 'serial':
mya['enclosure-serial-number'] = [val]
elif key == 'model':
mya['enclosure-machinetype-model'] = [val]
yield peerdata[nid]
continue
if '/redfish/v1/' not in peerdata[nid].get('urls', ()) and '/redfish/v1' not in peerdata[nid].get('urls', ()):
continue
if '/DeviceDescription.json' in peerdata[nid]['urls']:
@@ -466,6 +492,10 @@ def _parse_ssdp(peer, rsp, peerdata):
peerdatum['services'] = [value]
elif value not in peerdatum['services']:
peerdatum['services'].append(value)
elif header == 'USN':
peerdatum['usn'] = value
elif header == 'MODELNAME':
peerdatum['modelname'] = value
+1 -1
View File
@@ -119,7 +119,7 @@ def get_port(addr, clientip, sessionid):
newsock.bind(('::', newport, 0, 0))
newsock.listen(50)
break
except OSError as e:
except (socket.error, OSError) as e:
if e.errno == 98:
newport += 1
continue
+18 -5
View File
@@ -288,12 +288,16 @@ def _authorize_request(env, operation, reqbody):
authdata = auth.authorize(name, element=element, operation=operation)
else:
element = None
if (not authdata) and 'HTTP_COOKIE' in env:
cidx = (env['HTTP_COOKIE']).find('confluentsessionid=')
if cidx >= 0:
sessionid = env['HTTP_COOKIE'][cidx+19:cidx+51]
sessid = sessionid
if not authdata:
if 'HTTP_CONFLUENTSESSION' in env:
sessionid = env['HTTP_CONFLUENTSESSION']
sessid = sessionid
elif 'HTTP_COOKIE' in env:
cidx = (env['HTTP_COOKIE']).find('confluentsessionid=')
if cidx >= 0:
sessionid = env['HTTP_COOKIE'][cidx+19:cidx+51]
sessid = sessionid
if sessionid:
if sessionid in httpsessions:
if _csrf_valid(env, httpsessions[sessionid]):
if env['PATH_INFO'] == '/sessions/current/logout':
@@ -483,6 +487,8 @@ def wsock_handler(ws):
elif clientmsg[0] == '!':
msg = json.loads(clientmsg[1:])
action = msg.get('operation', None)
if not action:
action = msg.get('action', None)
targ = msg.get('target', None)
if targ:
authdata = auth.authorize(name, targ, operation=action)
@@ -520,6 +526,13 @@ def wsock_handler(ws):
datacallback=datacallback,
width=width, height=height)
myconsoles[clientsessid] = consession
elif action == 'resize':
clientsessid = '{0}'.format(msg['sessid'])
myconsoles[clientsessid].resize(
width=msg['width'], height=msg['height'])
if action == 'break':
clientsessid = '{0}'.format(msg['sessid'])
myconsoles[clientsessid].send_break()
elif action == 'stop':
sessid = '{0}'.format(msg.get('sessid', None))
if sessid in myconsoles:
+1
View File
@@ -306,6 +306,7 @@ def run(args):
except AttributeError:
pass # Windows...
os.umask(oumask)
auth.check_for_yaml()
collective.startup()
consoleserver.initialize()
http_bind_host, http_bind_port = _get_connector_config('http')
+4 -1
View File
@@ -1682,16 +1682,19 @@ class NetworkConfiguration(ConfluentMessage):
desc = 'Network configuration'
def __init__(self, name=None, ipv4addr=None, ipv4gateway=None,
ipv4cfgmethod=None, hwaddr=None):
ipv4cfgmethod=None, hwaddr=None, staticv6addrs=(), staticv6gateway=None):
self.myargs = (name, ipv4addr, ipv4gateway, ipv4cfgmethod, hwaddr)
self.notnode = name is None
self.stripped = False
v6addrs = ','.join(staticv6addrs)
kvpairs = {
'ipv4_address': {'value': ipv4addr},
'ipv4_gateway': {'value': ipv4gateway},
'ipv4_configuration': {'value': ipv4cfgmethod},
'hw_addr': {'value': hwaddr},
'static_v6_addresses': {'value': v6addrs},
'static_v6_gateway': {'value': staticv6gateway}
}
if self.notnode:
self.kvpairs = kvpairs
+5 -2
View File
@@ -198,8 +198,9 @@ class NetManager(object):
ipv4addr = attribs.get('ipv4_address', None)
if ipv4addr:
try:
for ai in socket.getaddrinfo(ipv4addr, 0, socket.AF_INET, socket.SOCK_STREAM):
ipv4addr = ai[-1][0]
luaddr = ipv4addr.split('/', 1)[0]
for ai in socket.getaddrinfo(luaddr, 0, socket.AF_INET, socket.SOCK_STREAM):
ipv4addr.replace(luaddr, ai[-1][0])
except socket.gaierror:
pass
else:
@@ -661,6 +662,8 @@ def addresses_match(addr1, addr2):
:param addr2:
:return: True if the given addresses refer to the same thing
"""
if '%' in addr1 or '%' in addr2:
return False
for addrinfo in socket.getaddrinfo(addr1, 0, 0, socket.SOCK_STREAM):
rootaddr1 = socket.inet_pton(addrinfo[0], addrinfo[4][0])
if addrinfo[0] == socket.AF_INET6 and rootaddr1[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
@@ -31,7 +31,14 @@
# this module will provide mac to switch and full 'ifName' label
# This functionality is restricted to the null tenant
import os
import sys
if __name__ == '__main__':
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', '..'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.config.configmanager as cfm
import confluent.snmputil as snmp
@@ -43,8 +50,6 @@ 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
@@ -208,7 +213,6 @@ def _offload_map_switch(switch, password, user):
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)
@@ -220,7 +224,10 @@ def _start_offloader():
def _recv_offload():
upacker = msgpack.Unpacker(encoding='utf8')
try:
upacker = msgpack.Unpacker(encoding='utf8')
except TypeError:
upacker = msgpack.Unpacker(raw=False, strict_map_key=False)
instream = _offloader.stdout.fileno()
while True:
select.select([_offloader.stdout], [], [])
@@ -601,7 +608,7 @@ def handle_read_api_request(pathcomponents, configmanager):
elif len(pathcomponents) == 2:
if pathcomponents[-1] == 'macs':
return [msg.ChildCollection(x) for x in (# 'by-node/',
'by-mac/', 'by-switch/',
'alldata', 'by-mac/', 'by-switch/',
'rescan')]
elif pathcomponents[-1] == 'neighbors':
return [msg.ChildCollection('by-switch/')]
@@ -616,6 +623,8 @@ def handle_read_api_request(pathcomponents, configmanager):
elif len(pathcomponents) == 4:
macaddr = pathcomponents[-1].replace('-', ':')
return dump_macinfo(macaddr)
elif pathcomponents[2] == 'alldata':
return [msg.KeyValueData(_apimacmap)]
elif pathcomponents[2] == 'by-mac':
if len(pathcomponents) == 3:
return [msg.ChildCollection(x.replace(':', '-'))
@@ -687,7 +696,10 @@ def rescan(cfg):
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == '-o':
upacker = msgpack.Unpacker(encoding='utf8')
try:
upacker = msgpack.Unpacker(encoding='utf8')
except TypeError:
upacker = msgpack.Unpacker(raw=False, strict_map_key=False)
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
@@ -712,4 +724,4 @@ if __name__ == '__main__':
print("Mac to location lookup table: -------------------")
print(repr(_macmap))
print("switch to fdb lookup table: -------------------")
print(repr(_macsbyswitch))
print(repr(_macsbyswitch))
+1 -1
View File
@@ -186,7 +186,7 @@ class NodeRange(object):
for idx in range(len(leftbits)):
if leftbits[idx] == rightbits[idx]:
finalfmt += leftbits[idx]
elif leftbits[idx][0] in pp.alphas:
elif leftbits[idx][0] in pp.alphas or rightbits[idx][0] in pp.alphas:
# if string portion unequal, not going to work
return self.failorreturn(seqrange)
else:
+40 -9
View File
@@ -1,6 +1,4 @@
#!/usr/bin/python
import confluent.exceptions as exc
import confluent.messages as msg
import eventlet
import eventlet.green.select as select
import eventlet.green.subprocess as subprocess
@@ -14,6 +12,14 @@ import shutil
import sys
import time
import yaml
if __name__ == '__main__':
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.exceptions as exc
import confluent.messages as msg
COPY = 1
EXTRACT = 2
@@ -376,6 +382,20 @@ def check_ubuntu(isoinfo):
if not isinstance(arch, str):
arch = arch.decode('utf8')
major = '.'.join(ver.split('.', 2)[:2])
if 'install/hwe-netboot/ubuntu-installer/amd64/linux' in isoinfo[0]:
# debian-installer style amd64
return {
'name': 'ubuntu-{0}-{1}'.format(ver, arch),
'method': EXTRACT,
'category': 'ubuntu{0}'.format(major)}
elif 'efi/boot/bootaa64.efi' in isoinfo[0]:
exlist = ['casper/vmlinuz', 'casper/initrd',
'efi/boot/bootaa64.efi', 'efi/boot/grubaa64.efi'
]
else:
exlist = ['casper/vmlinuz', 'casper/initrd',
'efi/boot/bootx64.efi', 'efi/boot/grubx64.efi'
]
return {'name': 'ubuntu-{0}-{1}'.format(ver, arch),
'method': EXTRACT|COPY,
'extractlist': ['casper/vmlinuz', 'casper/initrd',
@@ -765,10 +785,15 @@ def generate_stock_profiles(defprofile, distpath, targpath, osname,
yout.write('# This manifest enables rebase to know original source of profile data and if any customizations have been done\n')
manifestdata = {'distdir': srcname, 'disthashes': hmap}
yout.write(yaml.dump(manifestdata, default_flow_style=False))
for initrd in os.listdir('{0}/initramfs'.format(defprofile)):
fullpath = '{0}/initramfs/{1}'.format(defprofile, initrd)
initrds = ['{0}/initramfs/{1}'.format(defprofile, initrd) for initrd in os.listdir('{0}/initramfs'.format(defprofile))]
if os.path.exists('{0}/initramfs/{1}'.format(defprofile, arch)):
initrds.extend(['{0}/initramfs/{1}/{2}'.format(defprofile, arch, initrd) for initrd in os.listdir('{0}/initramfs/{1}'.format(defprofile, arch))])
for fullpath in initrds:
initrd = os.path.basename(fullpath)
if os.path.isdir(fullpath):
continue
if os.path.exists('{0}/boot/initramfs/{1}'.format(dirname, initrd)):
os.remove('{0}/boot/initramfs/{1}'.format(dirname, initrd))
os.symlink(fullpath,
'{0}/boot/initramfs/{1}'.format(dirname, initrd))
os.symlink(
@@ -792,11 +817,17 @@ class MediaImporter(object):
raise Exception('`osdeploy initialize` must be executed before importing any media')
self.profiles = []
medfile = None
self.medfile = None
if cfm and media in cfm.clientfiles:
medfile = cfm.clientfiles[media]
self.medfile = cfm.clientfiles[media]
medfile = self.medfile
else:
medfile = open(media, 'rb')
identity = fingerprint(medfile)
try:
identity = fingerprint(medfile)
finally:
if not self.medfile:
medfile.close()
if not identity:
raise exc.InvalidArgumentException('Unsupported Media')
self.percent = 0.0
@@ -824,7 +855,6 @@ class MediaImporter(object):
del importing[importkey]
raise Exception('{0} already exists'.format(self.targpath))
self.filename = os.path.abspath(media)
self.medfile = medfile
self.error = ''
self.importer = eventlet.spawn(self.importmedia)
@@ -837,8 +867,8 @@ class MediaImporter(object):
return {'phase': self.phase, 'progress': self.percent, 'profiles': self.profiles, 'error': self.error}
def importmedia(self):
os.environ['PYTHONPATH'] = ':'.join(sys.path)
os.environ['CONFLUENT_MEDIAFD'] = '{0}'.format(self.medfile.fileno())
if self.medfile:
os.environ['CONFLUENT_MEDIAFD'] = '{0}'.format(self.medfile.fileno())
with open(os.devnull, 'w') as devnull:
self.worker = subprocess.Popen(
[sys.executable, __file__, self.filename, '-b'],
@@ -911,3 +941,4 @@ if __name__ == '__main__':
sys.exit(import_image(sys.argv[1], callback=printit, backend=True, mfd=mfd))
else:
sys.exit(import_image(sys.argv[1], callback=printit))
@@ -49,7 +49,8 @@ class WebClient(object):
results.put(msg.ConfluentTargetInvalidCredentials(self.node, 'Unable to authenticate'))
return {}
elif status != 200:
results.put(msg.ConfluentNodeError(self.node, 'Unknown error: ' + rsp + ' while retrieving ' + url))
#must be str not bytes
results.put(msg.ConfluentNodeError(self.node, 'Unknown error: {} while retrieving {}'.format(rsp, url)))
return {}
return rsp
@@ -192,4 +193,4 @@ def retrieve_health(configmanager, creds, node, results, element):
hinfo = wc.fetch('/affluent/health', results)
if hinfo:
results.put(msg.HealthSummary(hinfo.get('health', 'unknown'), name=node))
results.put(msg.SensorReadings(hinfo.get('sensors', []), name=node))
results.put(msg.SensorReadings(hinfo.get('sensors', []), name=node))
@@ -0,0 +1,266 @@
# Copyright 2022 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.
from xml.etree.ElementTree import fromstring as rfromstring
import pyghmi.util.webclient as wc
import confluent.util as util
import confluent.messages as msg
import confluent.exceptions as exc
import eventlet.green.time as time
import eventlet.green.socket as socket
import eventlet.greenpool as greenpool
import eventlet
try:
import Cookie
httplib = eventlet.import_patched('httplib')
except ImportError:
httplib = eventlet.import_patched('http.client')
import http.cookies as Cookie
sensorsbymodel = {
'FS1350': ['alarms', 'dt', 'duty', 'dw', 'mode', 'p3state', 'primflow', 'ps1', 'ps1a', 'ps1b', 'ps2', 'ps3', 'ps4', 'ps5a', 'ps5b', 'ps5c', 'pumpspeed1', 'pumpspeed2', 'pumpspeed3', 'rh', 'sdp', 'secflow', 'setpoint', 't1', 't2', 't2a', 't2b', 't2c', 't3', 't3', 't4', 't5', 'valve', 'valve2'],
'FS600': ['alarms', 'dt', 'duty', 'dw', 'mode', 'p3state', 'pdp', 'primflow', 'ps1', 'ps1a', 'ps1b', 'ps2', 'ps3', 'ps4', 'ps5a', 'ps5b', 'pumpspeed1', 'pumpspeed2', 'rh', 'sdp', 'secflow', 'setpoint', 't1', 't2', 't2a', 't2b', 't2c', 't3', 't3', 't4', 't5', 'valve'],
'RM100': ['alarms', 'dt', 'duty', 'dw', 'mode', 'p3state', 'primflow', 'ps1', 'ps1a', 'ps2', 'ps3', 'pumpspeed1', 'pumpspeed2', 'rh', 'sdp', 'secflow', 'setpoint', 't1', 't2', 't2a', 't2b', 't2c', 't3', 't3', 't4', 't5', 'valve'],
}
_thesensors = {
'RM100': {
't1': ('Primary loop supply temperature', 'degC'),
't2': ('Secondary loop supply temperature', 'degC'),
't4': ('Secondary loop return temperature', 'degC'),
't3': ('Ambient air temperature', 'degC'),
't5': ('Primary loop return temperature', 'degC'),
'rh': ('Relative Humidity', '%'),
'dw': ('Dewpoint', 'degC'),
'pumpspeed1': ('Pump 1 Speed', '%'),
'pumpspeed2': ('Pump 2 Speed', '%'),
'alarms': ('Number of active alarms', ''),
'primflow': ('Input flow rate', 'l/m'),
'secflow': ('Output flow rate', 'l/m'),
'ps1': ('Secondary loop return pressure', 'bar'),
'ps3': ('Secondary loop supply pressure', 'bar'),
},
'FS600': {
't1': ('Primary loop supply temperature', 'degC'),
't2': ('Secondary loop supply temperature', 'degC'),
't4': ('Secondary loop return temperature', 'degC'),
't5': ('Primary loop return temperature', 'degC'),
't3': ('Ambient air temperature', 'degC'),
'rh': ('Relative Humidity', '%'),
'dw': ('Dewpoint', 'degC'),
'pumpspeed1': ('Pump 1 Speed', '%'),
'pumpspeed2': ('Pump 2 Speed', '%'),
'alarms': ('Number of active alarms', ''),
'primflow': ('Input flow rate', 'l/m'),
'secflow': ('Output flow rate', 'l/m'),
'ps1': ('Secondary loop return pressure', 'bar'),
'ps3': ('Primary loop supply pressure', 'bar'),
'ps2': ('Secondary loop supply pressure', 'bar'),
},
'FS1350': {
't1': ('Primary loop supply temperature', 'degC'),
't2': ('Secondary loop supply temperature', 'degC'),
't4': ('Secondary loop return temperature', 'degC'),
't5': ('Primary loop return temperature', 'degC'),
't3': ('Ambient air temperature', 'degC'),
'rh': ('Relative Humidity', '%'),
'dw': ('Dewpoint', 'degC'),
'pumpspeed1': ('Pump 1 Speed', '%'),
'pumpspeed2': ('Pump 2 Speed', '%'),
'pumpspeed3': ('Pump 2 Speed', '%'),
'alarms': ('Number of active alarms', ''),
'primflow': ('Input flow rate', 'l/m'),
'secflow': ('Output flow rate', 'l/m'),
'ps1': ('Secondary loop return pressure', 'bar'),
'ps3': ('Primary loop supply pressure', 'bar'),
'ps2': ('Secondary loop supply pressure', 'bar'),
'ps4': ('Primary loop return pressure', 'bar'),
},
}
def fromstring(inputdata):
if isinstance(inputdata, bytes):
cmpstr = b'!entity'
else:
cmpstr = '!entity'
if cmpstr in inputdata.lower():
raise Exception('!ENTITY not supported in this interface')
# The measures above should filter out the risky facets of xml
# We don't need sophisticated feature support
return rfromstring(inputdata) # nosec
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
pdupool = greenpool.GreenPool(128)
class WebResponse(httplib.HTTPResponse):
def _check_close(self):
return True
class WebConnection(wc.SecureHTTPConnection):
response_class = WebResponse
def __init__(self, host, secure, verifycallback):
if secure:
port = 443
else:
port = 80
wc.SecureHTTPConnection.__init__(self, host, port, verifycallback=verifycallback)
self.secure = secure
self.cookies = {}
def connect(self):
if self.secure:
return super(WebConnection, self).connect()
addrinfo = socket.getaddrinfo(self.host, self.port)[0]
# workaround problems of too large mtu, moderately frequent occurance
# in this space
plainsock = socket.socket(addrinfo[0])
plainsock.settimeout(self.mytimeout)
try:
plainsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
except socket.error:
pass
plainsock.connect(addrinfo[4])
self.sock = plainsock
def getresponse(self):
try:
rsp = super(WebConnection, self).getresponse()
try:
hdrs = [x.split(':', 1) for x in rsp.msg.headers]
except AttributeError:
hdrs = rsp.msg.items()
for hdr in hdrs:
if hdr[0] == 'Set-Cookie':
c = Cookie.BaseCookie(hdr[1])
for k in c:
self.cookies[k] = c[k].value
except httplib.BadStatusLine:
self.broken = True
raise
return rsp
def request(self, method, url, body=None):
headers = {}
if body:
headers['Content-Length'] = len(body)
cookies = []
for cookie in self.cookies:
cookies.append('{0}={1}'.format(cookie, self.cookies[cookie]))
headers['Cookie'] = ';'.join(cookies)
headers['Host'] = 'pdu.cluster.net'
headers['Accept'] = '*/*'
headers['Accept-Language'] = 'en-US,en;q=0.9'
headers['Connection'] = 'close'
headers['Referer'] = 'http://pdu.cluster.net/setting_admin.htm'
return super(WebConnection, self).request(method, url, body, headers)
def grab_response(self, url, body=None, method=None):
if method is None:
method = 'GET' if body is None else 'POST'
if body:
self.request(method, url, body)
else:
self.request(method, url)
rsp = self.getresponse()
body = rsp.read()
return body, rsp.status
class CoolteraClient(object):
def __init__(self, cdu, configmanager):
self.node = cdu
self.configmanager = configmanager
self._wc = None
@property
def wc(self):
if self._wc:
return self._wc
targcfg = self.configmanager.get_node_attributes(self.node,
['hardwaremanagement.manager'],
decrypt=True)
targcfg = targcfg.get(self.node, {})
target = targcfg.get(
'hardwaremanagement.manager', {}).get('value', None)
if not target:
target = self.node
target = target.split('/', 1)[0]
cv = util.TLSCertVerifier(
self.configmanager, self.node,
'pubkeys.tls_hardwaremanager').verify_cert
self._wc = WebConnection(target, secure=True, verifycallback=cv)
return self._wc
def xml2stateinfo(statdata):
statdata = fromstring(statdata)
stateinfo = []
sensornames = sorted([x.tag for x in statdata])
themodel = None
for model in sensorsbymodel:
if sensorsbymodel[model] == sensornames:
themodel = model
break
thesensors = _thesensors[themodel]
#['mode', 't1', 't2a', 't2b', 't2c', 't2', 't5', 't3', 't4', 'dw', 't3', 'rh', 'setpoint', 'secflow', 'primflow', 'ps1', 'ps1a', 'ps1b', 'ps2', 'ps3', 'ps4', 'ps5a', 'ps5b', 'ps5c', 'sdp', 'valve', 'valve2', 'pumpspeed1', 'pumpspeed2', 'pumpspeed3', 'alarms', 'dt', 'p3state', 'duty']
for tagname in thesensors:
label, units = thesensors[tagname]
val = statdata.find(tagname).text.replace(units, '').strip()
stateinfo.append({
'name': label,
'value': val,
'units': units.replace('degC', '°C'),
'type': 'Temperature',
})
return stateinfo
_sensors_by_node = {}
def read_sensors(element, node, configmanager):
category, name = element[-2:]
justnames = False
if len(element) == 3:
# just get names
category = name
name = 'all'
justnames = True
for sensor in sensors:
yield msg.ChildCollection(simplify_name(sensors[sensor][0]))
return
if category in ('leds, fans'):
return
sn = _sensors_by_node.get(node, None)
if not sn or sn[1] < time.time():
cc = CoolteraClient(node, configmanager)
cc.wc.request('GET', '/status.xml')
rsp = cc.wc.getresponse()
statdata = rsp.read()
statinfo = xml2stateinfo(statdata)
_sensors_by_node[node] = (statinfo, time.time() + 1)
sn = _sensors_by_node.get(node, None)
if sn:
yield msg.SensorReadings(sn[0], name=node)
def retrieve(nodes, element, configmanager, inputdata):
if element[0] == 'sensors':
gp = greenpool.GreenPile(pdupool)
for node in nodes:
gp.spawn(read_sensors, element, node, configmanager)
for rsp in gp:
for datum in rsp:
yield datum
return
@@ -199,11 +199,12 @@ def update(nodes, element, configmanager, inputdata):
if 'outlets' not in element:
yield msg.ConfluentResourceUnavailable(node, 'Not implemented')
return
timeout = 4
for node in nodes:
gc = PDUClient(node, configmanager)
newstate = inputdata.powerstate(node)
gc.set_outlet(element[-1], newstate)
gc.logout()
eventlet.sleep(2)
eventlet.sleep(timeout)
for res in retrieve(nodes, element, configmanager, inputdata):
yield res

Some files were not shown because too many files have changed in this diff Show More