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

Compare commits

..

127 Commits

Author SHA1 Message Date
Jarrod Johnson c8745292bf Handle broader memory information being returned from confluent 2025-12-04 13:28:45 -05:00
Jarrod Johnson 75e7b9040b Add more imgutil documentation 2025-12-04 13:27:48 -05:00
Jarrod Johnson d2d77ab1d5 Do not let diskless.conf persist into EL9 diskless images
It fouls run of kdump building the kdump image.
2025-11-07 13:23:44 -05:00
Jarrod Johnson ce8531375a Update to handle newer XCC2 firmware 2025-10-31 09:46:23 -04:00
Jarrod Johnson 762adb882a Track client address on checkin
When doing DHCP deployment in particular, it's good to track what the actual ip was.
2025-10-21 13:04:30 -04:00
Jarrod Johnson 36687069aa Fix ESXi8 deployment
The changes for getinstalldisk assumed functionality
in ESXi9.  Target older
functional level for our purposes.

Also expand the fallback to cover cases where the disk interrogation fails.
2025-10-21 11:11:52 -04:00
Jarrod Johnson 11ff2dabfc Clean up kickstart networking
Try to apply hostname through localcli, since
hostname is unsupported through net if dhcp.

Also more affirimatively indicate dhcp.
2025-10-17 10:00:38 -04:00
Jarrod Johnson f9351484a4 Add fallback if getinstalldisk detects no preferred disks 2025-10-17 09:32:33 -04:00
Jarrod Johnson b22c17208a Stop preferring HWE for now
The HWE has some missing hardware support, ironically...
2025-10-16 18:30:46 -04:00
Jarrod Johnson 4982ac1a17 Bump genesis version 2025-10-15 16:51:21 -04:00
Jarrod Johnson a43d7e11e2 Implement an esxi getinstalldisk 2025-10-15 10:43:36 -04:00
Jarrod Johnson c5896c056e Add facility to manage BMC CA certs
For redfish at least, we can manage
some BMC CA certificate trust stores.
2025-10-14 14:30:27 -04:00
Jarrod Johnson a73dced80b Fix configbmc on XCC3
IPv4 based configbmc is now fixed for multi-nic XCC3 configurations.
2025-10-14 13:19:28 -04:00
Jarrod Johnson b6188683b8 Redirect 'xcc' to 'xcc3' for bmcconfig -c 2025-10-14 10:22:15 -04:00
Jarrod Johnson 50243b67d5 Add a more helpful error when bmc not set
When doing a configbmc, need to present
a more reasonable message about lack of address.
2025-10-08 14:20:44 -04:00
Jarrod Johnson 7cdfcd4913 Implement support for multi-manager XCC managed systems 2025-10-08 09:42:17 -04:00
Jarrod Johnson 179ad4e196 Fix IPv6 redfish config fetch for select targets
Not all targets offer up static gateway
2025-10-08 09:39:17 -04:00
Jarrod Johnson be2ae57a38 Skip the '[' when evaluating partial ESC for terminator 2025-10-06 17:34:17 -04:00
Jarrod Johnson f34395648e Add vlan_id management to redfish and ipmi plugins 2025-10-06 17:28:55 -04:00
Jarrod Johnson 3f5d96788e Fix handling of split SGR payloads
Surprisingly frequently, the firmware stacks split right after the \x1b byte in
sending data down. Defer a dangling partial sequence until more data
comes in that should make it complete.
2025-10-06 13:12:43 -04:00
Jarrod Johnson 17866d7657 Change to only force intense if bg == fg
In the interest of interfering with terminal behavior as little as possible,
only apply the forced intensity if the background and foreground color are
identical and would make it otherwise literally impossible to read
when working as designed.
2025-10-06 12:22:21 -04:00
Jarrod Johnson a1144fd49a Auto-intense color
Terminals seem to expect 'bold or intensity' to imply intense color.

There are certain terminals that steadfastly refuse to do bold and intense. So implement the logic on behalf of
the remote terminal.

Commonly, UEFI setup menus request bold white text on white background. This fixes such menus to be readable by explicitly requesting intense white foreground rather than normal background. For example, the kitty terminal has no 'intense on bold feature.
2025-10-06 10:48:35 -04:00
Jarrod Johnson c472d96406 Add '-r' to nodedeploy
This allows a shorthand to request a redeploy of whatever the most appropriate profile is.
2025-10-06 08:24:17 -04:00
Jarrod Johnson 02791418bc Support attribute expansion in filenames
For nodemedia, nodelicense, and nodefirmware, support
for expressions in filenames was
fouled when pass by
filehandle was added.

Restore this by adding all the files matching an expression.
2025-10-02 15:45:48 -04:00
Jarrod Johnson 2d29813320 Store device for future use in ubuntu deployment 2025-10-02 14:28:46 -04:00
Jarrod Johnson a9d15de156 Rework Ubuntu identity image DHCP bringup
The stock Ubuntu approach was inadequate.  It would DHCP out every nic and take the fastest result, and no going back.

Now the CDC nic can frequently win that race.

First, rmmod cdc_ether, as a scenario that is completely right out.

But beyond that, let Ubuntu have one shot at multi-nic bringup.  Beyond that, maintain a list of all link-up devices.

If the check should fail, then start doing one nic at a time, cycling through them.

Also, the openssl s_client timeout is painfully slow, use subshell and kill to speed up things.
2025-10-02 10:55:43 -04:00
Jarrod Johnson a4ba92a2e7 Retry network bringup
ESXi may be slow in being ready for network bringup. Workaround
by retrying.
2025-10-01 13:08:17 -04:00
Jarrod Johnson 6938bba2d3 Have confignet pause until connectivity restored
If we are reconfiguring network for a diskless node, wait for
things to settle back in before continuing.
2025-09-26 13:42:29 -04:00
Jarrod Johnson 871685ea20 Correct missing closure of if 2025-09-25 15:49:25 -04:00
Jarrod Johnson a480cc73df Add connectivity check to esxi ident bringup
If using the identity image bringup
with dhcp, be more careful about waiting
for connectivity before proceeding.
2025-09-25 15:29:33 -04:00
Jarrod Johnson 39eb32df38 Test connection on net cfg apply
When network configuration is applied, wait until we
can reach the deployment server again before exiting.

This should make us more robust against various potential delays after
changing the nature of network interfaces.
2025-09-25 15:18:18 -04:00
Jarrod Johnson 3505fe36e6 Remove hiddenmenu
This no longer applies to most grub2
2025-09-25 14:07:27 -04:00
Jarrod Johnson 29accaa494 Change grub to not prompt
Sometimes grub can get stuck unexpectedly waiting for interaction. Try to get away from this by default
by setting the timeout to 0.
2025-09-25 10:09:25 -04:00
Jarrod Johnson f66093680b Attempt to loop on reconfiguring networking
This may induce DHCP to be retried
2025-09-25 10:08:05 -04:00
Jarrod Johnson 97d4015b09 Handle memory inventory without type indicated 2025-09-22 15:21:53 -04:00
Jarrod Johnson 184132c398 Fix collective manager candidates not in nodelist
For switch operations, need to carry over the same logic as other evaluations.
2025-09-19 19:41:54 -04:00
Jarrod Johnson ac7fdb3ef7 Enhance message for enclosure based discovery
If nodes are accidentally omitted, but present, provide a hint that may clarify
the situation.
2025-09-19 15:46:18 -04:00
Jarrod Johnson d7879bad5b Improve robustness of Ubuntu net bringup
If using DHCP, have the loop to validate connectivity repeat.
2025-09-19 15:44:55 -04:00
Jarrod Johnson 8911193aca Implement a test with retry for basic communication
confuesbox is likely to be a very early utility, and the relevant network is at high risk of being merely 'partially' up.
2025-09-19 11:50:12 -04:00
Jarrod Johnson e7e8daafea Merge pull request #198 from henglikuang/logdirectory
An effort was made at one point to have log directory configurable, but no way was actually done
to make it accessible.

This corrects that.
2025-09-18 15:19:07 -04:00
Jarrod Johnson 3f9a13ed6f Ensure certfile is blanked before writing to it 2025-09-18 15:14:39 -04:00
Jarrod Johnson 500cdf7535 Change boot.img to boot.iso for Windows
Some things expect an iso to be named as such. This drives different handling, but
there's little choice in the matter.
2025-09-18 08:55:09 -04:00
Jarrod Johnson 22c8921455 Place identity files loose in directory as well
Some OS deployment mechanism may wish to convey the identity information more loosely. For those, it's convenient if the files are loose instead
of needing extraction from a VFAT image.
2025-09-17 09:25:40 -04:00
Jarrod Johnson ebcf7d7bf8 Refresh genesis build version 2025-09-15 11:21:03 -04:00
Jarrod Johnson 7a2cb80f6a Make hmac import optional
Some environments do not have this module
2025-09-12 16:57:37 -04:00
Jarrod Johnson dd2b7be2ca Bump genesis version 2025-09-12 16:15:00 -04:00
Jarrod Johnson 678bd6052a Correct path to util-linux in genesis build 2025-09-12 15:59:48 -04:00
Jarrod Johnson cb5fcf077a Fix incorrect character in release filename 2025-09-12 08:50:32 -04:00
Jarrod Johnson 5f26fb73e6 Enable apiclient to be more self-sufficient
Provide a totally 'clortho' and 'copernicus' free behavior.

This allows some flows to skip the cpio addons to go straight to python.

Some scenarios demand the utilities (initramfs) and others are more awkward with the utilities,
so we enable both.
2025-09-09 16:47:44 -04:00
Jarrod Johnson c9ca199b16 Fix preference of netplan
If netplan and nmcli both exist, the intent was to prefer netplan.

However, there was a mistake that caused nmcli to be the most preferred.
2025-09-09 11:17:26 -04:00
Jarrod Johnson 8109adaabf Add BFB recognition to osimage parsing
Recognize BFB embedded OS as a potential osdeploy target.

This is toward the end of identifying the appropriate 'addons.cpio' for setting up for a bf.cfg driven bfb install.

For now, it is disabled until companion os category exists.
2025-09-04 15:23:03 -04:00
Jarrod Johnson 29c6ce230f Tolerate updateboot failure on first import 2025-09-04 10:21:01 -04:00
Jarrod Johnson 87a6891eff Include boot filename in ARM case
ARM PXE solutions often fail to properly implement
PXE, workaround by going ahead and including the boot filename.
2025-09-03 09:09:21 -04:00
Jarrod Johnson a112297e60 Detect ESXi editions for more specific fingerprinting 2025-09-02 10:19:41 -04:00
Jarrod Johnson c567bfbd17 Add sysctl tune check to selfcheck
Apart frem the gc_thresh indirect check, perform other checks.

For now, just highlight that tcp_sack being disabled can really
mess with BMC connections.  Since the management node may have high speed and the BMC may be behind a 100MBit link, SACK
is needed to overcome the massive loss and
induce TCP to rate limit appropriately.
2025-09-02 08:53:55 -04:00
Jarrod Johnson 6d2146f252 Provide more category based firmware query
Some platforms can have a very slow category,
like disks. Give CLI a way to ask for the desired
categories and a chance to optimize away the uninteresting.
2025-08-29 17:12:36 -04:00
Jarrod Johnson 5045b46014 Switch to ISO based boot for windows
Windows boot loader can be easily confused by a plurality
of vfat volumes, coddle it by giving it an ISO image for now.
2025-08-28 15:14:02 -04:00
Jarrod Johnson 5905510a32 Move tmp script execution out of /tmp
Some environments want noexec on /tmp, this will work in such environments.
2025-08-28 08:34:07 -04:00
Jarrod Johnson f321f56109 Make more windows content executable
Other files use the executable
bit as an indication of whether to run or not.
2025-08-28 08:08:30 -04:00
Jarrod Johnson 9defc47474 Give pycdlib a duped filehandle
Attempts to share the filehandle resulted in race conditions around closing,
dedicate a dupe filehandle to pycdlib to avoid the conflict.
2025-08-27 12:29:19 -04:00
Jarrod Johnson 595b628e08 Validate that the agent socket actually works
If agent is 'kill -9', then recover
from that by reaping the now dead socket.
2025-08-26 14:00:36 -04:00
Jarrod Johnson 710b24e9f5 Recover from dead ssh agent
If the ssh-agent is gone, for whatever reason, restart it.
2025-08-26 11:10:43 -04:00
Jarrod Johnson c26fba74e7 Fix issues with EL10 installtodisk 2025-08-26 09:52:21 -04:00
Jarrod Johnson a01eb64adc Remove disused function from confluent2ansible 2025-08-26 09:50:25 -04:00
Jarrod Johnson ac8179b867 Amend swraid example script 2025-08-26 09:49:28 -04:00
Jarrod Johnson 87990c72c3 Make EL10 diskless consistent with EL9 2025-08-26 09:06:28 -04:00
Jarrod Johnson a6a57e8590 Fix ssh operation during install in installtodisk for el9 2025-08-26 08:59:10 -04:00
Jarrod Johnson 6be98c7e60 Fix leaking ssh-agent processes in selfcheck 2025-08-26 08:44:42 -04:00
Jarrod Johnson 1a64768fca Carry forward EL* installtodisk to EL9 2025-08-26 08:08:24 -04:00
Jarrod Johnson 157641e37a Fixup imported windows media
Samba by default needs executable bit on files for them to be executable by windows.

Only give executable bits to .exe files that are PE32, mitigating the chance the executable bit could mean anything for Linux.
It could still mean something with binfmt misc hooks, but that shouldn't be done much.
2025-08-25 08:59:53 -04:00
Jarrod Johnson 63bbe53448 Address numerous issues with 'installtodisk' for el8
Add missing pre.d directory to let user know they can use such scripts

Preserve console directievs from kernelargs into installed system

Retry umount during image2disk, if processes have the filesystem busy.

Fix DNS behavior during post phase of installtodisk

Invoke confignet properly during firstboot to set up additional interfaces.

Have sshd run during the install from '/sysroot', for convenience

Fix some cosmetic error output for setupssh
2025-08-22 08:39:40 -04:00
Jarrod Johnson ec3fcee7d7 Implement updateboot for windows profiles 2025-08-20 20:15:05 -04:00
Jarrod Johnson b2b2b5710b Fix up monolithing sshd_config for platforms that need it 2025-08-20 11:26:41 -04:00
Jarrod Johnson b32ded9c6a Fix skipping of quotation in grub config 2025-08-20 10:31:59 -04:00
Jarrod Johnson 75c228dae4 Fix syntax mistake 2025-08-20 10:10:01 -04:00
Jarrod Johnson afd2b6c219 Add storage drivers to imgutil for el8 diskless 2025-08-20 10:04:13 -04:00
Jarrod Johnson 9a85b9ee94 Fixes for installtodisk for diskless for el9 2025-08-20 09:55:26 -04:00
Jarrod Johnson c9c5165245 Fix syntax error in commit 2025-08-20 08:53:55 -04:00
Jarrod Johnson d4e91b1c7e Back port installtodisk to el8 diskless 2025-08-20 08:49:11 -04:00
Jarrod Johnson 98e78dd43c Reduce size of presumed diskless image for installtodisk
A diskless image is likely to be significantly smaller, have it support install
to smaller disks.
2025-08-18 16:34:26 -04:00
Jarrod Johnson e7606e69bd Wait for redfish account service to be ready before modifying users
If an XCC is booting, it may appear before it's ready to use redfish to manage user accounts.  Handle this by delaying the discovery until
the service is ready.
2025-08-15 10:18:49 -04:00
Jarrod Johnson 580c451945 Explicitly restart sshd on completion
Some changes from setupssh may require sshd restart, perform it
if running with the sshd unit running.
2025-08-14 07:24:28 -04:00
Jarrod Johnson a71804a13b Fix suse/ubuntu paths for apache config 2025-08-12 11:47:39 -04:00
Jarrod Johnson dbda4f45a1 Add 'proxmox' to valid values for console options 2025-08-12 10:49:01 -04:00
Jarrod Johnson 5ac0cccc4d Update proxmoxve for trixie
Have the proxmox post script adaptive between Debian 12 or 13
2025-08-11 17:11:18 -04:00
Jarrod Johnson 465e985cc7 Extract and use HWE by default for Ubuntu 24.04 2025-08-11 13:55:36 -04:00
Jarrod Johnson 836b629986 Remove unused json import from apiclient 2025-08-11 12:31:44 -04:00
Jarrod Johnson 58b6a2d317 Fix python2 incompatibility with apiclient 2025-08-11 12:20:10 -04:00
Jarrod Johnson fc6c1495d3 Change comment to not have unicode
Some environments don't like the UTF
2025-08-11 12:10:33 -04:00
Jarrod Johnson c9b9275bb1 Support clear of custom attributes
middle name attributes and custom attributes should
be checked for explicitly during a clear.
2025-08-11 11:46:35 -04:00
Jarrod Johnson c0a99f63a5 Correct typo in rpm spec changes 2025-08-11 10:31:55 -04:00
Jarrod Johnson 51afcc68a7 Add debian 13 support
Debian 13 changes the installer initramfs
to have lib be under usr, compensate.
2025-08-11 10:14:05 -04:00
Jarrod Johnson 902ff43a9b Extend certificate management
Provide checks for nginx config and apache configuration, perhaps even concurrently.

Latch on the first match, since we are taking care of IP based SANs and subsequent server/virtualhost sections are irrelevant.

Latch onto a chain file, if indicated in the apache configuration, placing our CA in the chain.

For nginx, put our CA in the cert, since nginx
uses the 'certificate' file as the chain.

In this scenario, a cross-signed CA cert is possible.
2025-08-08 17:27:26 -04:00
Jarrod Johnson e01701bcf1 Speed up log initialization
Cache the directory list over a few seconds
to avoid excessive filesystem calls.

Also switchg to a more potent regex to avoid wasting time on timestamped files.
2025-08-08 15:51:45 -04:00
Jarrod Johnson a1cf8023c6 Clarify that nodesensors csv may have a different order
CSV must keep sensors grouped and consistent, so it waits until it gathers
all results to sort.

Normally, it presents the results as quickly as the API provides it.

This results in some different ordering for the "fast as API provides it"
versus "delayed to group the values together" behavior.
2025-08-08 14:30:30 -04:00
Jarrod Johnson 960a890530 Fix discovery mac deletion to also clean associated node records 2025-08-07 15:15:14 -04:00
Jarrod Johnson d43de05b09 Present error when user tries to operate undefined PDU outlets 2025-08-07 14:56:12 -04:00
Jarrod Johnson 36ce0922fc Implement openEuler scripted install
openEuler 24.03 seems closest to el8, so start with that baseline.

Modify el8 handling to recognize a totally missing CA bundle and assume
the original el8 location.

Have osimage recognize openEuler media by presence of openEuler release file.
2025-08-06 14:56:12 -04:00
Jarrod Johnson aafa65274c Do not trip over .discinfo file contents 2025-08-06 14:27:05 -04:00
Jarrod Johnson 63bb5f4d1b Have routed diskless retry checking for interface
If the network interface is slow to link up, wait
until something comes up, ignoring infiniband for now.
2025-08-06 14:15:43 -04:00
Jarrod Johnson d99689f84b Have confluent2ansible support amending inventory
Rather than writing from scratch each time, parse the
existing file if present and merge new content without
perturbing existing.
2025-08-06 11:56:44 -04:00
Hengli Kuang 816f3be2ed Configure the logdirectory from the configuration file
- Set the log directory using the configuration manager
- Add _get_logdirectory function to retrieve the log directory path
- Update _redirectoutput to use the new log directory setting
2025-08-06 04:31:38 -04:00
Jarrod Johnson bab169269c Fix variable mistake in error handling 2025-08-05 08:39:18 -04:00
Jarrod Johnson 85ddf528a2 Add Confluent UUID to identity image
The UUID and tls material usually comes from site, but some
OSes may depend entirely on the identity image, so
make the UUID available that way as well.
2025-08-01 15:53:38 -04:00
Jarrod Johnson 8cfbf40a2e Implement capture of extra volume groups
Provide a mechanism to capture non-boot hosted volume groups
and map them to specified disks via a mapping.
2025-08-01 12:02:04 -04:00
Jarrod Johnson 48a0c21300 Refine getinstalldisk
Reduce obvious output about skipped devices.

Rule out any read-only device.

Amend minimum size to 2GB.

Among same priority devices, select the smallest target.
2025-08-01 09:00:25 -04:00
Jarrod Johnson 2c43055aec Amend handling of malformed bytes in output
Additionally, fix handling of mixed numeric/string in the attribute handler.
2025-07-31 08:44:58 -04:00
Jarrod Johnson 16a1c4d598 Pull mtu into the netcfg api 2025-07-29 15:45:41 -04:00
Jarrod Johnson 97e4d7c3d0 Skip sr0 if attempted 2025-07-29 15:33:05 -04:00
Jarrod Johnson cfa16237e1 Dismantle initramfs by default in EL9/EL10 diskless
If debugssh is not requsted, the initramfs is not needed.

Do not unshare the mount namespace, leave the kernel relevant namespace 'normal'

Remove some initramfs content to free up some memory.
2025-07-29 14:26:43 -04:00
Jarrod Johnson 7066f85520 Merge remote-tracking branch 'xcat' 2025-07-29 07:03:22 -04:00
Jarrod Johnson 8c6f36adf3 Fixes for ansible running
Newer ansible requires deferred plugin initialization explicitly,
support either ansible norm.

Reliably mark a play as complete even if facing a very early init
problem, e.g. if ansible shebang points to a non-existant python.

Fix incorrect association of stderr to tasks, and present
stderr to the client exactly once.
2025-07-28 15:55:16 -04:00
Jarrod Johnson 33cee4174e Merge pull request #196 from Obihoernchen/doclink
Update documentation link
2025-07-25 15:03:49 -04:00
Jarrod Johnson 47710756a5 Implement mtu for netplan backend 2025-07-25 15:01:26 -04:00
Jarrod Johnson 21429c6d7d Implement mtu for networkmanager backend 2025-07-25 14:48:03 -04:00
Jarrod Johnson ff0c11e919 Add mtu to net attributes 2025-07-25 14:47:46 -04:00
Jarrod Johnson bf209a8009 Explicitly recognize fe80::/64 as 'local'
For various reasons, it is likely/expected for fe80:: to fail the 'local'
check. This is fine in most contexts, except for credserver and ssdp.

ssdp already special cased fe80::, so special case fe80:: in credserver
as well.
2025-07-24 16:20:12 -04:00
Jarrod Johnson 6ec072be9d Fix EL10 for ARM nodes 2025-07-22 14:25:39 -04:00
Jarrod Johnson 79e44e420a Fix console handling for vcenter and proxmox plugins 2025-07-22 09:26:50 -04:00
Markus Hilger 5a0b2468f6 Update documentation link 2025-07-11 03:08:50 +02:00
Jarrod Johnson 13d9fe2712 Revert useinsecureprotocols to any group value after adoption 2025-07-02 09:55:24 -04:00
Jarrod Johnson 5028ed9f07 Avoid set changed during iteration 2025-07-01 15:45:24 -04:00
Jarrod Johnson 05dbbd6ce0 Explicitly check root user keys
Replace simple existence check
with a check that assures the content also matches.
2025-06-25 16:10:26 -04:00
Jarrod Johnson 61749c3649 Support older Pillow libraries
Some distributions bundle older pillow, and we have
a way to support them.
2025-06-25 13:47:29 -04:00
Jarrod Johnson 1f3b84cc9d Implement Windows iso extraction
If a user has custom
windows categories, match
them to media import.

To do this, we needed to go to pycdlib, as libarchive can't
do UDF-only isos.

For now, this has no progress indication, but does extract it similar
to most Linux distributions are done.
2025-06-24 16:32:31 -04:00
Jarrod Johnson ac42c1b4c7 Skip IPv6 lookups on IPv4 context
For DHCPv4 context, it makes no sense to bother with IPv6 addresses that cannot be used.
2025-06-24 13:40:40 -04:00
Jarrod Johnson e489d2d532 Fix setupssh behavior on nearly full /tmp/ 2025-06-18 08:55:13 -04:00
106 changed files with 2184 additions and 490 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ If you're coming from xCAT, check out [this comparison](https://hpc.lenovo.com/u
# Documentation
Confluent documentation is hosted on hpc.lenovo.com: https://hpc.lenovo.com/users/documentation/
Confluent documentation is hosted on: https://xcat2.github.io/confluent-docs/
# Download
+61 -1
View File
@@ -45,6 +45,7 @@ import math
import getpass
import optparse
import os
import re
import select
import shlex
import signal
@@ -969,8 +970,15 @@ def main():
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
sgr_re = re.compile(r'(\x1b\[[0-9;]*m)')
sgr_parameters_re = re.compile(r'\x1b\[([0-9;]*)m')
fgcolor = None
bgcolor = None
fgshifted = False
pendseq = ''
def consume_termdata(fh, bufferonly=False):
global clearpowermessage
global fgcolor, bgcolor, fgshifted, pendseq
try:
data = tlvdata.recv(fh)
except Exception:
@@ -979,7 +987,59 @@ def consume_termdata(fh, bufferonly=False):
updatestatus(data)
return ''
if data is not None:
data = client.stringify(data)
indata = pendseq + client.stringify(data)
pendseq = ''
data = ''
for segment in sgr_re.split(indata):
if sgr_re.match(segment): # it is an sgr, analyze, maybe replace
params = []
for parameters in sgr_parameters_re.findall(segment):
for param in parameters.split(';'):
params.append(param)
if param == '0':
fgcolor = None
bgcolor = None
try:
ival = int(param)
except ValueError:
continue
if 40 <= ival <= 47 or 100 <= ival <= 107:
bgcolor = ival
if 30 <= ival <= 37 or 90 <= ival <= 97:
fgcolor = ival
if bgcolor is not None:
fgindicated = False
for idx, param in enumerate(params):
try:
ival = int(param)
except ValueError:
continue
if 30 <= ival <= 37 and (bgcolor % 10 == ival % 10):
fgindicated = True
fgshifted = True
ival += 60
params[idx] = str(ival)
if not fgindicated and fgcolor is not None:
if bgcolor and (bgcolor % 10) == (fgcolor % 10):
fgshifted = True
params.append(str((fgcolor % 10) + 90))
elif fgshifted:
params.append(str(fgcolor))
segment = '\x1b[' + ';'.join(str(p) for p in params) + 'm'
data += segment
# defer any partial ansi escape sequence for a later pass
escidx = segment.rfind('\x1b[')
if escidx >= 0:
for chr in segment[escidx + 2:]:
if 0x40 <= ord(chr) <= 0x7e:
break
else:
# incomplete escape sequence, don't print it yet
data = data[:-len(segment) + escidx]
pendseq = segment[escidx:]
if not pendseq and segment and segment[-1] == '\x1b':
data = data[:-1]
pendseq = '\x1b'
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
+42 -25
View File
@@ -1,8 +1,9 @@
#!/usr/bin/python2
#!/usr/bin/python3
import optparse
import signal
import sys
import os
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
@@ -17,18 +18,16 @@ import confluent.client as client
import confluent.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts
\n ''')
usage='''\n %prog noderange -o ansible.hosts -a
''')
argparser.add_option('-o', '--output',
help='Ansible hosts file')
help='Writes an Ansible hosts file')
argparser.add_option('-a', '--append', action='store_true',
help='Appends to existing hosts file')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -45,24 +44,42 @@ def main():
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
nodesbygroup = {}
with open(options.output, 'w') as importfile:
needempty = False
for node in sortutil.natural_sort(databynode):
data = databynode[node]
if not data.get('groups', []):
importfile.write(node + '\n')
needempty = True
for g in data.get('groups', []):
if g not in nodesbygroup:
nodesbygroup[g] = set([node])
for node in sortutil.natural_sort(databynode):
data = databynode[node]
groups = data.get('groups', [])
if not groups:
nodesbygroup.setdefault('', set()).add(node.strip().lower())
else:
for g in groups:
nodesbygroup.setdefault(g, set()).add(node.strip().lower())
existing_data = {}
if options.append and os.path.exists(options.output):
current_group = ''
with open(options.output, 'r') as f:
for line in f:
line = line.strip().lower()
if not line:
continue
if line.startswith('[') and line.endswith(']'):
current_group = line[1:-1]
existing_data.setdefault(current_group, set())
else:
nodesbygroup[g].add(node)
if needempty:
importfile.write('\n')
for group in sortutil.natural_sort(nodesbygroup):
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(nodesbygroup[group]):
existing_data.setdefault(current_group, set()).add(line)
for group, nodes in nodesbygroup.items():
nodes = {n.strip().lower() for n in nodes}
current_nodes = existing_data.get(group, set())
new_nodes = nodes - current_nodes
if new_nodes:
existing_data.setdefault(group, set()).update(nodes)
with open(options.output, 'w') as importfile:
for group in sortutil.natural_sort(existing_data.keys()):
if group:
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(existing_data[group]):
importfile.write('{0}\n'.format(node))
importfile.write('\n')
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/python3
import os
import sys
from cryptography import x509
from cryptography.hazmat.primitives import hashes
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
def removebmccacert(noderange, certid, cmd):
for res in cmd.delete(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities/{certid}'):
print(repr(res))
def listbmccacerts(noderange, cmd):
certids = []
for res in cmd.read(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities'):
certids.append(res.get('item', {}).get('href', ''))
for certid in certids:
for res in cmd.read(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities/{certid}'):
for node in res.get('databynode', {}):
certdata = res['databynode'][node].get('pem', {}).get('value', '')
summary = ''
if not certdata:
continue
san = res['databynode'][node].get('san', {}).get('value', '')
if san:
summary += f" SANs: {san}"
subject = res['databynode'][node].get('subject', {}).get('value', '')
if subject:
summary = subject
try:
cert = x509.load_pem_x509_certificate(certdata.encode())
sha256 = cert.fingerprint(hashes.SHA256()).hex().upper()
except Exception as e:
print(f"Error processing certificate for {node}: {e}", file=sys.stderr)
continue
summary += f" (SHA256={sha256})"
print(f"{node}: {certid}: {summary}")
def installbmccacert(noderange, certfile, cmd):
if certfile:
try:
with open(certfile, 'r') as f:
certdata = f.read()
except Exception as e:
print(f"Error reading certificate file: {e}", file=sys.stderr)
sys.exit(1)
# Simple validation: check if it starts and ends with the correct PEM markers
if not (certdata.startswith("-----BEGIN CERTIFICATE-----") and certdata.strip().endswith("-----END CERTIFICATE-----")):
print("Invalid certificate format. Must be a PEM encoded certificate.", file=sys.stderr)
sys.exit(1)
payload = {'pem': certdata}
for res in cmd.update(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities', payload):
print(repr(res))
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Node certificate utility')
parser.add_argument('noderange', help='Node range to operate on')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# installbmccacert subcommand
install_parser = subparsers.add_parser('installbmccacert', help='Install BMC CA certificate')
install_parser.add_argument('filename', help='Certificate file to install')
remove_parser = subparsers.add_parser('removebmccacert', help='Remove BMC CA certificate')
remove_parser.add_argument('id', help='Certificate id to remove')
list_parser = subparsers.add_parser('listbmccacerts', help='List BMC CA certificates')
args = parser.parse_args()
c = client.Command()
if args.command == 'installbmccacert':
installbmccacert(args.noderange, args.filename, c)
elif args.command == 'removebmccacert':
removebmccacert(args.noderange, args.id, c)
elif args.command == 'listbmccacerts':
listbmccacerts(args.noderange, c)
else:
parser.print_help()
sys.exit(1)
+8 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
# Copyright 2025 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -96,6 +96,12 @@ cfgpaths = {
'bmc.static_ipv6_gateway': (
'configuration/management_controller/net_interfaces/management',
'static_v6_gateway'),
'bmc.vlan_id': (
'configuration/management_controller/net_interfaces/management',
'vlan_id'),
'bmc.mac_address': (
'configuration/management_controller/net_interfaces/management',
'hw_addr'),
'bmc.hostname': (
'configuration/management_controller/hostname', 'hostname'),
}
+9 -7
View File
@@ -38,7 +38,7 @@ import termios
import fcntl
import confluent.screensqueeze as sq
try:
from PIL import Image, ImageDraw
from PIL import Image, ImageDraw, ImageFont
except ImportError:
Image = None
@@ -227,22 +227,24 @@ def draw_text(text, width, height):
nd = ImageDraw.Draw(nerr)
for txtpiece in text.split('\n'):
fntsize = 8
while nd.textlength(txtpiece, font_size=fntsize) < int(imgwidth * 0.90):
txtfont = ImageFont.truetype('DejaVuSans.ttf', size=fntsize)
while nd.textlength(txtpiece, font=txtfont) < int(imgwidth * 0.90):
fntsize += 1
txtfont = ImageFont.truetype('DejaVuSans.ttf', size=fntsize)
fntsize -= 1
if fntsize < maxfntsize:
maxfntsize = fntsize
hmargin = int(imgwidth * 0.05)
vmargin = int(imgheight * 0.10)
nd.text((hmargin, vmargin), text, font_size=maxfntsize)
nd.rectangle((0, 0, nerr.width - 1, nerr.height -1), outline='white', width=1)
nd.text((hmargin, vmargin), text, font=txtfont)
nd.rectangle((0, 0, nerr.width - 1, nerr.height -1), outline='white')
outfile = io.BytesIO()
nerr.save(outfile, format='PNG')
data = base64.b64encode(outfile.getbuffer())
draw_image(data, width, height, doscale=False)
else:
sys.stdout.write(text)
cursor_left(len(txt))
cursor_left(len(text))
def draw_image(data, width, height, doscale=True):
imageformat = os.environ.get('CONFLUENT_IMAGE_PROTOCOL', 'kitty')
@@ -270,8 +272,8 @@ def draw_image(data, width, height, doscale=True):
rzheight = imgheight
img = img.resize((rzwidth, rzheight))
nd = ImageDraw.Draw(nimg)
nd.rectangle((1, 1, rzwidth + 2, rzheight + 2), outline='black', width=1)
nd.rectangle((0, 0, rzwidth + 3, rzheight + 3), outline='white', width=1)
nd.rectangle((1, 1, rzwidth + 2, rzheight + 2), outline='black')
nd.rectangle((0, 0, rzwidth + 3, rzheight + 3), outline='white')
nimg.paste(img, box=(2, 2))
outfile = io.BytesIO()
nimg.save(outfile, format='PNG')
+62 -21
View File
@@ -48,7 +48,18 @@ def armonce(nr, cli):
pass
def setpending(nr, profile, cli):
def setpending(nr, profile, profilebynodes, cli):
if profilebynodes:
for node in sortutil.natural_sort(profilebynodes):
prof = profilebynodes[node]
args = {'deployment.pendingprofile': prof, 'deployment.state': '', 'deployment.state_detail': ''}
if not prof.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
args['deployment.profile'] = ''
for rsp in cli.update('/nodes/{0}/attributes/current'.format(node),
args):
pass
return
args = {'deployment.pendingprofile': profile, 'deployment.state': '', 'deployment.state_detail': ''}
if not profile.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
@@ -69,6 +80,7 @@ def main(args):
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true')
ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
ap.add_argument('-r', '--redeploy', help='Redeploy nodes with the current or pending profile', action='store_true')
ap.add_argument('noderange', help='Set of nodes to deploy')
ap.add_argument('profile', nargs='?', help='Profile name to deploy')
args, extra = ap.parse_known_args(args)
@@ -78,7 +90,7 @@ def main(args):
if args.profile and not args.network:
sys.stderr.write('-n is a required argument currently to perform an install, optionally with -p\n')
return 1
if not args.profile and args.network:
if not args.profile and args.network and not args.redeploy:
sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\n')
return 1
if args.clear and args.profile:
@@ -96,27 +108,38 @@ def main(args):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
sys.exit(1)
profilebynode = {}
if args.clear:
cleararm(args.noderange, c)
clearpending(args.noderange, c)
elif args.profile:
profnames = []
for prof in c.read('/deployment/profiles/'):
profname = prof.get('item', {}).get('href', None)
if profname:
profname = profname.replace('/', '')
profnames.append(profname)
if profname == args.profile:
break
else:
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
if profnames:
sys.stderr.write('The following profiles are available:\n')
for profname in profnames:
sys.stderr.write(' ' + profname + '\n')
else:
sys.stderr.write('No deployment profiles available, try osdeploy import or imgutil capture\n')
sys.exit(1)
elif args.redeploy:
hadpending = {}
for rsp in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
for node in rsp.get('databynode', {}):
nodeinfo = rsp['databynode'][node]
for attr in nodeinfo:
if attr == 'deployment.pendingprofile':
curr = nodeinfo[attr].get('value', '')
if curr:
hadpending[node] = True
profilebynode[node] = curr
if attr == 'deployment.stagedprofile':
curr = nodeinfo[attr].get('value', '')
if curr and node not in hadpending:
profilebynode[node] = curr
if attr == 'deployment.profile':
curr = nodeinfo[attr].get('value', '')
if curr and node not in profilebynode:
profilebynode[node] = curr
for lockinfo in c.read('/noderange/{0}/deployment/lock'.format(args.noderange)):
for node in lockinfo.get('databynode', {}):
lockstate = lockinfo['databynode'][node]['lock']['value']
if lockstate == 'locked':
lockednodes.append(node)
if args.profile and profilebynode:
sys.stderr.write('The -r/--redeploy option cannot be used with a profile, it redeploys the current or pending profile\n')
return 1
if args.profile or profilebynode:
lockednodes = []
for lockinfo in c.read('/noderange/{0}/deployment/lock'.format(args.noderange)):
for node in lockinfo.get('databynode', {}):
@@ -127,8 +150,26 @@ def main(args):
sys.stderr.write('Requested noderange has nodes with locked deployment: ' + ','.join(lockednodes))
sys.stderr.write('\n')
sys.exit(1)
if args.profile:
profnames = []
for prof in c.read('/deployment/profiles/'):
profname = prof.get('item', {}).get('href', None)
if profname:
profname = profname.replace('/', '')
profnames.append(profname)
if profname == args.profile:
break
else:
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
if profnames:
sys.stderr.write('The following profiles are available:\n')
for profname in profnames:
sys.stderr.write(' ' + profname + '\n')
else:
sys.stderr.write('No deployment profiles available, try osdeploy import or imgutil capture\n')
sys.exit(1)
armonce(args.noderange, c)
setpending(args.noderange, args.profile, c)
setpending(args.noderange, args.profile, profilebynode, c)
else:
databynode = {}
for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
+25 -8
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -114,11 +114,24 @@ def update_firmware(session, filename):
upargs['bank'] = 'backup'
noderrs = {}
if session.unixdomain:
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
filesbynode = {}
for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': filename}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
filesbynode[node] = ex[node]['value']
if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
filesbynode[node] = filesbynode[node].encode('utf-8')
for node in filesbynode:
endfilename = filesbynode[node]
of = open(endfilename, 'rb')
try:
session.add_file(endfilename, of.fileno(), 'rb')
except Exception:
pass
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
@@ -153,9 +166,13 @@ def show_firmware(session):
firmware_shown = False
nodes_matched = False
for component in components:
category = 'all'
if component in ('adapters', 'disks', 'misc', 'core'):
category = component
component = 'all'
for res in session.read(
'/noderange/{0}/inventory/firmware/all/{1}'.format(
noderange, component)):
'/noderange/{0}/inventory/firmware/{2}/{1}'.format(
noderange, component, category)):
nodes_matched = True
exitcode |= client.printerror(res)
if 'databynode' not in res:
+10 -3
View File
@@ -49,7 +49,9 @@ def pretty(text):
def print_mem_info(node, prefix, meminfo):
memdescfmt = '{0}GB PC'
if meminfo['memory_type'] == 'DDR3 SDRAM':
if meminfo['memory_type'] is None:
memdescfmt = '{0}GB '
elif meminfo['memory_type'] == 'DDR3 SDRAM':
memdescfmt += '3-{1} '
elif 'DDR4' in meminfo['memory_type']:
memdescfmt += '4-{1} '
@@ -58,16 +60,21 @@ def print_mem_info(node, prefix, meminfo):
elif 'DCPMM' in meminfo['memory_type']:
memdescfmt = '{0}GB {1} '
meminfo['module_type'] = 'DCPMM'
elif meminfo['memory_type'] == 'HBM':
memdescfmt = '{0}GB HBM '
else:
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
return
if meminfo.get('ecc', False):
memdescfmt += 'ECC '
capacity = meminfo['capacity_mb'] / 1024
modtype = meminfo.get('module_type', None)
if modtype:
memdescfmt += modtype
memdesc = memdescfmt.format(capacity, meminfo['speed'])
if meminfo.get('capacity_mb', None):
capacity = meminfo['capacity_mb'] // 1024
memdesc = memdescfmt.format(capacity, meminfo['speed'])
else:
memdesc = 'Unspecified Module'
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
print('{0}: {1} manufacturer: {2}'.format(
node, prefix, meminfo['manufacturer']))
+20 -6
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
@@ -65,16 +65,30 @@ client.check_globbing(noderange)
def install_license(session, filename):
global exitcode
resource = '/noderange/{0}/configuration/' \
'management_controller/licenses/'.format(noderange)
filename = os.path.abspath(filename)
instargs = {'filename': filename}
if session.unixdomain:
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
filesbynode = {}
for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': filename}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
filesbynode[node] = ex[node]['value']
if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
filesbynode[node] = filesbynode[node].encode('utf-8')
for node in filesbynode:
endfilename = filesbynode[node]
of = open(endfilename, 'rb')
try:
session.add_file(endfilename, of.fileno(), 'rb')
except Exception:
pass
for res in session.create(resource, instargs):
for node in res.get('databynode', []):
if 'error' in res['databynode'][node]:
+25 -10
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
@@ -101,22 +101,37 @@ def detach_media(noderange, media):
def upload_media(noderange, media):
global exitcode
if not os.path.exists(media):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
media))
sys.exit(404)
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
filename = os.path.abspath(media)
resource = '/noderange/{0}/media/uploads/'.format(noderange)
filename = os.path.abspath(filename)
upargs = {'filename': filename}
noderrs = {}
if session.unixdomain:
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
filesbynode = {}
for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': filename}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
filesbynode[node] = ex[node]['value']
if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
filesbynode[node] = filesbynode[node].encode('utf-8')
for node in filesbynode:
endfilename = filesbynode[node]
if not os.path.exists(endfilename):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
endfilename))
sys.exit(404)
of = open(endfilename, 'rb')
try:
session.add_file(endfilename, of.fileno(), 'rb')
except Exception:
pass
nodeurls = {}
for res in session.create(resource, upargs):
if 'created' not in res:
+3 -3
View File
@@ -57,7 +57,7 @@ def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
# current python version
if isinstance(instr, bytes) and not isinstance(instr, str):
return instr.decode('utf-8')
return instr.decode('utf-8', errors='replace')
elif not isinstance(instr, bytes) and not isinstance(instr, str):
return instr.encode('utf-8')
return instr
@@ -464,8 +464,8 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
def _sort_attrib(k):
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
return k[1]['sortid']
return k[0]
return sortutil.naturalize_string('{}'.format(k[1]['sortid']))
return sortutil.naturalize_string(k[0])
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
exitcode = 0
+139
View File
@@ -0,0 +1,139 @@
# imgutil(1) -- Work with confluent OS cloning and diskless images
## SYNOPSIS
`imgutil` `build` [<options>] <scratchdir>
`imgutil` `exec` [<options>] <scratchdir> [<cmd>...]
`imgutil` `unpack` <profilename> <scratchdir>
`imgutil` `pack` [<options>] <scratchdir> <profilename>
`imgutil` `capture` <node> <profilename>
## DESCRIPTION
**imgutil** is a utility for creating, managing, and deploying OS images for diskless boot and system cloning in a Confluent environment. It supports building images from scratch, capturing images from running systems, and packing/unpacking diskless profiles.
## COMMANDS
* `build`:
Build a new diskless image from scratch in the specified scratch directory.
* `exec`:
Start the specified scratch directory as a container and optionally run a command inside it.
* `unpack`:
Unpack a diskless image profile to a scratch directory for modification.
* `pack`:
Pack a scratch directory into a diskless profile that can be deployed.
* `capture`:
Capture an image for cloning from a running system.
## BUILD OPTIONS
* `-r`, `--addrepos` <repository>:
Repositories to add in addition to the main source. May be specified multiple times.
* `-p`, `--packagelist` <file>:
Filename of package list to replace default pkglist.
* `-a`, `--addpackagelist` <file>:
A list of additional packages to include. May be specified multiple times.
* `-s`, `--source` <directory>:
Directory to pull installation from, typically a subdirectory of `/var/lib/confluent/distributions`. By default, the repositories for the build system are used. For Ubuntu, this is not supported; the build system repositories are always used.
* `-y`, `--non-interactive`:
Avoid prompting for confirmation.
* `-v`, `--volume` <mount>:
Directory to make available in the build environment. `-v /` will cause it to be mounted in image as `/run/external/`. `-v /:/run/root` will override the target to be `/run/root`. Something like `/var/lib/repository:-` will cause it to mount to the identical path inside the image. May be specified multiple times.
* <scratchdir>:
Directory to build new diskless root in.
## EXEC OPTIONS
* `-v`, `--volume` <mount>:
Directory to make available in the build environment. `-v /` will cause it to be mounted in image as `/run/external/`. `-v /:/run/root` will override the target to be `/run/root`. May be specified multiple times.
* <scratchdir>:
Directory of an unpacked diskless root.
* <cmd>:
Optional command to run (defaults to a shell).
## UNPACK OPTIONS
* <profilename>:
The diskless OS profile to unpack.
* <scratchdir>:
Directory to extract diskless root to.
## PACK OPTIONS
* `-b`, `--baseprofile` <profile>:
Profile to copy extra info from. For example, to make a new version of an existing profile, reference the previous one as baseprofile.
* `-u`, `--unencrypted`:
Pack an unencrypted image rather than encrypting.
* <scratchdir>:
Directory containing diskless root.
* <profilename>:
The desired diskless OS profile name to pack the root into.
## CAPTURE OPTIONS
* <node>:
Node to capture image from.
* <profilename>:
Profile name for captured image.
## EXAMPLES
Build a diskless image from a distribution:
imgutil build -s alma-9.6-x86_64 /tmp/myimage
Execute a shell in an unpacked image:
imgutil exec /tmp/myimage
Execute a specific command in an image:
imgutil exec /tmp/myimage /bin/rpm -qa
Unpack an existing profile for modification:
imgutil unpack myprofile /tmp/myimage
Pack a modified image into a new profile:
imgutil pack /tmp/myimage myprofile-v2
Capture an image from a running node:
imgutil capture node01 production-image
## FILES
* `/var/lib/confluent/public/os/`:
Default location for OS profiles.
* `/var/lib/confluent/private/os/`:
Location for encrypted image keys and private data.
* `/var/lib/confluent/distributions/`:
Default location for installation sources.
## SEE ALSO
osdeploy(8)
## AUTHOR
Written for the Confluent project.
+3 -1
View File
@@ -19,7 +19,9 @@ interval of 1 second is used.
## OPTIONS
* `-c`, `--csv`:
Organize output into CSV format, one sensor per column.
Organize output into CSV format, one sensor per column. Note that while normally nodesensors reports
sensors in order as returned by server, CSV output enforces consistency by sorting after receiving
the results, which may have a different ordering than non-CSV usage of nodesensors.
* `-i`, `--interval`=**SECONDS**:
Repeat data gathering waiting, waiting the specified time between samples. Unless `-n` is
@@ -3,6 +3,7 @@ try:
import http.client as client
except ImportError:
import httplib as client
import base64
import ctypes
import ctypes.util
import glob
@@ -15,7 +16,13 @@ import sys
import struct
import time
import re
import json
import hashlib
try:
import json
import hmac
except ImportError:
json = None
hmac = None
class InvalidApiKey(Exception):
pass
@@ -73,7 +80,7 @@ def get_my_addresses():
return addrs
def scan_confluents():
def scan_confluents(confuuid=None):
srvs = {}
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
@@ -85,12 +92,13 @@ def scan_confluents():
s4.bind(('0.0.0.0', 1900))
doneidxs = set([])
msg = 'M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:'
with open('/etc/confluent/confluent.deploycfg') as dcfg:
for line in dcfg.read().split('\n'):
if line.startswith('confluent_uuid:'):
confluentuuid = line.split(': ')[1]
msg += '/confluentuuid=' + confluentuuid
break
if not confuuid:
with open('/etc/confluent/confluent.deploycfg') as dcfg:
for line in dcfg.read().split('\n'):
if line.startswith('confluent_uuid:'):
confluentuuid = line.split(': ')[1]
msg += '/confluentuuid=' + confluentuuid
break
try:
with open('/sys/devices/virtual/dmi/id/product_uuid') as uuidin:
msg += '/uuid=' + uuidin.read().strip()
@@ -127,6 +135,7 @@ def scan_confluents():
srvlist = []
if r:
r = r[0]
nodename = None
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
@@ -134,6 +143,7 @@ def scan_confluents():
current = None
for line in rsp:
if line.startswith(b'NODENAME: '):
nodename = line.replace(b'NODENAME: ', b'').strip().decode('utf8')
current = {}
elif line.startswith(b'DEFAULTNET: 1'):
current['isdefault'] = True
@@ -149,16 +159,32 @@ def scan_confluents():
r = select.select((s4, s6), (), (), 2)
if r:
r = r[0]
if not os.path.exists('/etc/confluent/confluent.info'):
with open('/etc/confluent/confluent.info', 'w+') as cinfo:
if nodename:
cinfo.write('NODENAME: {0}\n'.format(nodename))
for srv in srvlist:
cinfo.write('MANAGER: {0}\n'.format(srv))
return srvlist, srvs
def get_net_apikey(nodename, mgr):
def get_net_apikey(nodename, mgr, hmackey=None, confuuid=None):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./'
newpass = ''.join([alpha[x >> 2] for x in bytearray(os.urandom(32))])
salt = '$5$' + ''.join([alpha[x >> 2] for x in bytearray(os.urandom(8))])
newpass = newpass.encode('utf8')
salt = salt.encode('utf8')
crypted = c_crypt(newpass, salt)
if hmackey:
hmacvalue = hmac.new(hmackey.encode('utf8'), crypted, hashlib.sha256).digest()
hmacvalue = base64.b64encode(hmacvalue).decode('utf8')
client = HTTPSClient(host=mgr, phmac=hmacvalue, nodename=nodename, confuuid=confuuid)
try:
status, rsp = client.grab_url_with_status('/confluent-api/self/registerapikey', data=crypted, returnrsp=True)
if status == 200:
return newpass.decode('utf8')
except Exception:
pass
for addrinfo in socket.getaddrinfo(mgr, 13001, 0, socket.SOCK_STREAM):
try:
clisock = socket.socket(addrinfo[0], addrinfo[1])
@@ -196,7 +222,7 @@ def get_net_apikey(nodename, mgr):
return ''
def get_apikey(nodename, hosts, errout=None):
def get_apikey(nodename, hosts, errout=None, hmackey=None, confuuid=None):
apikey = ""
if os.path.exists('/etc/confluent/confluent.apikey'):
apikey = open('/etc/confluent/confluent.apikey').read().strip()
@@ -205,16 +231,16 @@ def get_apikey(nodename, hosts, errout=None):
while not apikey:
for host in hosts:
try:
apikey = get_net_apikey(nodename, host)
apikey = get_net_apikey(nodename, host, hmackey=hmackey, confuuid=confuuid)
except OSError:
apikey = None
if apikey:
break
else:
srvlist, _ = scan_confluents()
srvlist, _ = scan_confluents(confuuid=confuuid)
for host in srvlist:
try:
apikey = get_net_apikey(nodename, host)
apikey = get_net_apikey(nodename, host, hmackey=hmackey, confuuid=confuuid)
except OSError:
apikey = None
if apikey:
@@ -232,35 +258,43 @@ def get_apikey(nodename, hosts, errout=None):
return apikey
class HTTPSClient(client.HTTPConnection, object):
def __init__(self, usejson=False, port=443, host=None, errout=None, phmac=None, checkonly=False):
def __init__(self, usejson=False, port=443, host=None, errout=None, phmac=None, checkonly=False, hmackey=None, nodename=None, confuuid=None):
self.ignorehosts = set([])
self.phmac = phmac
self.hmackey = hmackey
self.confuuid = confuuid
self.errout = None
self.stdheaders = {}
if nodename:
self.stdheaders['CONFLUENT_NODENAME'] = nodename
if errout:
self.errout = open(errout, 'w')
self.errout.flush()
self.stdheaders = {}
mgtiface = None
if usejson:
self.stdheaders['ACCEPT'] = 'application/json'
if host:
self.hosts = [host]
with open('/etc/confluent/confluent.info') as cinfo:
info = cinfo.read().split('\n')
for line in info:
if line.startswith('NODENAME:'):
node = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = node
if not nodename:
with open('/etc/confluent/confluent.info') as cinfo:
info = cinfo.read().split('\n')
for line in info:
if line.startswith('NODENAME:'):
nodename = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = nodename
else:
self.hosts = []
info = open('/etc/confluent/confluent.info').read().split('\n')
try:
info = open('/etc/confluent/confluent.info').read().split('\n')
except Exception:
info = []
havedefault = '0'
plainhost = ''
for line in info:
host = ''
if line.startswith('NODENAME:'):
node = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = node
nodename = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = nodename
if line.startswith('MANAGER:') and not host:
host = line.split(' ')[1]
self.hosts.append(host)
@@ -295,15 +329,14 @@ class HTTPSClient(client.HTTPConnection, object):
if plainhost and not self.hosts:
self.hosts.append(plainhost)
if self.phmac:
with open(phmac, 'r') as hmacin:
self.stdheaders['CONFLUENT_CRYPTHMAC'] = hmacin.read()
self.stdheaders['CONFLUENT_CRYPTHMAC'] = self.phmac
elif not checkonly:
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, self.hosts, errout=self.errout)
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(nodename, self.hosts, errout=self.errout, hmackey=hmackey, confuuid=self.confuuid)
if mgtiface:
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
self.port = port
self.host = None
self.node = node
self.node = nodename
host = self.check_connections()
client.HTTPConnection.__init__(self, host, port)
self.connect()
@@ -343,7 +376,7 @@ class HTTPSClient(client.HTTPConnection, object):
continue
break
if not foundsrv:
srvlist, srvs = scan_confluents()
srvlist, srvs = scan_confluents(self.confuuid)
hosts = []
for srv in srvlist:
if srvs[srv].get('isdefault', False):
@@ -417,7 +450,7 @@ class HTTPSClient(client.HTTPConnection, object):
with open('/etc/confluent/confluent.apikey', 'w+') as akfile:
akfile.write('')
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(
self.node, [self.host], errout=self.errout)
self.node, [self.host], errout=self.errout, hmackey=self.hmackey, confuuid=self.confuuid)
if rsp.status == 503: # confluent is down, but the server running confluent is otherwise up
authed = False
self.ignorehosts.add(self.host)
@@ -464,7 +497,7 @@ def get_available_nics():
parts = re.split(r'\s{2,}', line.strip())
if len(parts) >= 5:
nic_name = parts[0]
nic_status = parts[4] # "Link Status" este al 5-lea câmp
nic_status = parts[4] # "Link Status" is the 5th field
available_nics[nic_name] = nic_status
return available_nics
@@ -509,7 +542,7 @@ if __name__ == '__main__':
try:
fix_vswitch()
except Exception as e:
print(f"fix_vswitch() error: {e}")
print("fix_vswitch() error: {}".format(e))
sys.argv.remove('-f')
sys.exit(0)
usejson = False
@@ -546,8 +579,24 @@ if __name__ == '__main__':
phmac = sys.argv.index('-p')
sys.argv.pop(phmac)
phmac = sys.argv.pop(phmac)
with open(phmac, 'r') as hmacin:
phmac = hmacin.read()
except ValueError:
phmac = None
try:
identfile = sys.argv.index('-i')
sys.argv.pop(identfile)
identfile = sys.argv.pop(identfile)
with open(identfile) as idin:
data = idin.read()
identinfo = json.loads(data)
nodename = identinfo.get('nodename', None)
hmackey = identinfo.get('apitoken', None)
confuuid = identinfo.get('confluent_uuid', None)
except ValueError:
hmackey = None
nodename = None
confuuid = None
try:
checkonly = False
idxit = sys.argv.index('-c')
@@ -559,7 +608,7 @@ if __name__ == '__main__':
data = open(sys.argv[-1]).read()
if outbin:
with open(outbin, 'ab+') as outf:
reader = HTTPSClient(usejson=usejson, errout=errout).grab_url(
reader = HTTPSClient(usejson=usejson, errout=errout, hmackey=hmackey, nodename=nodename, confuuid=confuuid).grab_url(
sys.argv[1], data, returnrsp=True)
chunk = reader.read(16384)
while chunk:
@@ -567,7 +616,7 @@ if __name__ == '__main__':
chunk = reader.read(16384)
sys.exit(0)
mclient = HTTPSClient(usejson, errout=errout, phmac=phmac, checkonly=checkonly)
mclient = HTTPSClient(usejson, errout=errout, phmac=phmac, checkonly=checkonly, hmackey=hmackey, nodename=nodename, confuuid=confuuid)
if waitfor:
status = 201
while status != waitfor:
@@ -164,6 +164,9 @@ class NetplanManager(object):
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('mtu', None):
devdict = self.getcfgarrpath([devname])
devdict['mtu'] = int(stgs['mtu'])
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
@@ -381,6 +384,8 @@ class NetworkManager(object):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if stgs.get('mtu', None):
cmdargs['802-3-ethernet.mtu'] = stgs['mtu']
dnsips = self.deploycfg.get('nameservers', [])
if not dnsips:
dnsips = []
@@ -406,7 +411,7 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(cmdargs[arg]))
if stgs['team_mode'] in self.bondtypes:
stgs['team_mode'] = self.bondtypes[stgs['team_mode']]
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'miimon=100,mode={}'.format(stgs['team_mode'])] + cargs)
@@ -441,7 +446,7 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(cmdargs[arg]))
if u:
subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
@@ -455,6 +460,9 @@ class NetworkManager(object):
if __name__ == '__main__':
checktarg = None
if '-c' in sys.argv:
checktarg = sys.argv[sys.argv.index('-c') + 1]
havefirewall = subprocess.call(['systemctl', 'status', 'firewalld'])
havefirewall = havefirewall == 0
if havefirewall:
@@ -540,7 +548,7 @@ if __name__ == '__main__':
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
if os.path.exists('/usr/bin/nmcli'):
elif os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes, dc)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
@@ -562,4 +570,27 @@ if __name__ == '__main__':
if havefirewall:
subprocess.check_call(['systemctl', 'start', 'firewalld'])
await_tentative()
maxwait = 10
while maxwait:
try:
tclient = apiclient.HTTPSClient(checkonly=True)
tclient.check_connections()
break
except Exception:
maxwait -= 1
time.sleep(1)
maxwait = 10
if checktarg:
while maxwait:
try:
addrinf = socket.getaddrinfo(checktarg, 443)[0]
psock = socket.socket(addrinf[0], socket.SOCK_STREAM)
psock.settimeout(10)
psock.connect(addrinf[4])
psock.close()
break
except Exception:
maxwait -= 1
time.sleep(1)
@@ -7,7 +7,7 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do
continue
fi
certfile=${pubkey/.pub/-cert.pub}
rm $certfile
echo -n > $certfile
confluentpython $confapiclient /confluent-api/self/sshcert $pubkey -o $certfile
done
if [ -d /etc/ssh/sshd_config.d/ -a ! -e /etc/ssh/sshd_config.d/90-confluent.conf ]; then
@@ -17,6 +17,13 @@ if [ -d /etc/ssh/sshd_config.d/ -a ! -e /etc/ssh/sshd_config.d/90-confluent.conf
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/90-confluent.conf
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/90-confluent.conf
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/90-confluent.conf
elif [ ! -d /etc/ssh/sshd_config.d/ ] && ! grep HostCertificate /etc/ssh/sshd_config > /dev/null; then
for cert in /etc/ssh/ssh*-cert.pub; do
echo HostCertificate $cert >> /etc/ssh/sshd_config
done
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config
echo IgnoreRhosts no >> /etc/ssh/sshd_config
fi
TMPDIR=$(mktemp -d)
@@ -25,13 +32,20 @@ confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs
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
if [ -z "$LINE" ]; then continue; fi
if [ -f /etc/ssh/ssh_known_hosts ]; then
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
fi
echo '@cert-authority *' $LINE >> /etc/ssh/ssh_known_hosts.new
mv /etc/ssh/ssh_known_hosts.new /etc/ssh/ssh_known_hosts
done
mkdir -p /root/.ssh/
chmod 700 /root/.ssh/
touch /root/.ssh/authorized_keys
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
if [ -z "$LINE" ]; then continue; fi
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
@@ -41,3 +55,4 @@ confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' >
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
systemctl try-restart sshd
@@ -27,7 +27,12 @@ mkdir -p stateless-bin
cp -a el8bin/* .
ln -s el8 el9
ln -s el8 el10
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
mv el10/initramfs/usr el10/initramfs/var
cp -a debian debian13
mkdir -p debian13/initramfs/usr
mv debian13/initramfs/lib debian13/initramfs/usr/
mv el10/initramfs/usr el10/initramfs/var
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
@@ -81,7 +86,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 el10 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
for os in rhvh4 el7 el8 el9 el10 debian debian13 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.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
@@ -29,8 +29,11 @@ This contains support utilities for enabling deployment of x86_64 architecture s
#cd ..
ln -s el8 el9
cp -a el8 el10
cp -a debian debian13
mkdir -p debian13/initramfs/usr
mv debian13/initramfs/lib debian13/initramfs/usr/
mv el10/initramfs/usr el10/initramfs/var
for os in rhvh4 el7 genesis el8 suse15 debian ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
@@ -86,7 +89,7 @@ cp -a esxi7 esxi9
%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 el10 genesis suse15 ubuntu20.04 debian ubuntu18.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 esxi9 coreos; do
for os in rhvh4 el7 el8 el9 el10 genesis suse15 ubuntu20.04 debian debian13 ubuntu18.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 esxi9 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
@@ -164,6 +164,9 @@ class NetplanManager(object):
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('mtu', None):
devdict = self.getcfgarrpath([devname])
devdict['mtu'] = int(stgs['mtu'])
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
@@ -381,6 +384,8 @@ class NetworkManager(object):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if stgs.get('mtu', None):
cmdargs['802-3-ethernet.mtu'] = stgs['mtu']
dnsips = self.deploycfg.get('nameservers', [])
if not dnsips:
dnsips = []
@@ -406,10 +411,10 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(cmdargs[arg]))
if stgs['team_mode'] in self.bondtypes:
stgs['team_mode'] = self.bondtypes[stgs['team_mode']]
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'mode={}'.format(stgs['team_mode'])] + cargs)
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'miimon=100,mode={}'.format(stgs['team_mode'])] + cargs)
for iface in cfg['interfaces']:
self.add_team_member(cname, iface)
subprocess.check_call(['nmcli', 'c', 'u', cname])
@@ -441,7 +446,7 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append(cmdargs[arg])
cargs.append('{}'.format(cmdargs[arg]))
if u:
subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,12 +2,18 @@
# This script would run in post.d
#
export DEBIAN_FRONTEND=noninteractive
echo "deb [arch=amd64] http://download.proxmox.com/debian/pve bookworm pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
wget https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg
sum=$(sha512sum /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg)
if [ "$sum" -ne "7da6fe34168adc6e479327ba517796d4702fa2f8b4f0a9833f5ea6e6b48f6507a6da403a274fe201595edc86a84463d50383d07f64bdde2e3658108db7d6dc87" ]; then
codename=$(grep ^VERSION_CODENAME /etc/os-release | cut -d= -f2)
echo "deb [arch=amd64] http://download.proxmox.com/debian/pve $codename pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
wget https://enterprise.proxmox.com/debian/proxmox-release-$codename.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg
sum=$(sha512sum /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg)
if [ $codename == "bookworm" ]; then
expectedsum=7da6fe34168adc6e479327ba517796d4702fa2f8b4f0a9833f5ea6e6b48f6507a6da403a274fe201595edc86a84463d50383d07f64bdde2e3658108db7d6dc87
elif [ $codename == "trixie" ]; then
expectedsum=8678f2327c49276615288d7ca11e7d296bc8a2b96946fe565a9c81e533f9b15a5dbbad210a0ad5cd46d361ff1d3c4bac55844bc296beefa4f88b86e44e69fa51
fi
if [ "$sum" -ne "$expectedsum" ]; then
echo "Mismatch in fingerprint!"
rm /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg
rm /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg
exit 1
fi
apt-get update && apt-get -y full-upgrade < /dev/null
@@ -7,7 +7,6 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do
continue
fi
certfile=${pubkey/.pub/-cert.pub}
rm $certfile
confluentpython $confapiclient /confluent-api/self/sshcert $pubkey -o $certfile
done
if [ -d /etc/ssh/sshd_config.d/ -a ! -e /etc/ssh/sshd_config.d/90-confluent.conf ]; then
@@ -25,6 +24,7 @@ confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs
tar xf initramfs.tgz
for ca in ssh/*.ca; do
LINE=$(cat $ca)
if [ -z "$LINE" ]; then continue; fi
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
@@ -32,6 +32,7 @@ for ca in ssh/*.ca; do
done
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
if [ -z "$LINE" ]; then continue; fi
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
@@ -41,3 +42,4 @@ confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' >
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
systemctl try-restart sshd
@@ -129,4 +129,10 @@ ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
mv /lib/firmware /lib/firmware-ramfs
ln -s /sysroot/lib/firmware /lib/firmware
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
exec /opt/confluent/bin/start_root
if grep debugssh /proc/cmdline >& /dev/null; then
exec /opt/confluent/bin/start_root
else
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
fi
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -107,7 +107,11 @@ if [ ! -z "$confluentsrv" ]; then
/usr/libexec/nm-initrd-generator ip=:dhcp6
else
confluenthttpsrv=$confluentsrv
ifname=$(ip -br link|grep LOWER_UP|grep -v UNKNOWN|head -n 1|awk '{print $1}')
ifname=""
while [ -z "$ifname" ]; do
ifname=$(ip -br link|grep LOWER_UP|grep -v ib|grep -v UNKNOWN|head -n 1|awk '{print $1}')
sleep 0.5
done
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
while ! ip -br addr show dev $ifname | grep \\. > /dev/null; do
@@ -25,7 +25,8 @@ if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
confluentpython /root/confignet
rm /root/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
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -56,7 +56,11 @@ def get_image_metadata(imgpath):
for md in get_multipart_image_meta(img):
yield md
else:
raise Exception('Installation from single part image not supported')
yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 4294967296, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760}
yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 536870912, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656}
yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 33554432, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960}
#raise Exception('Installation from single part image not supported')
class PartedRunner():
def __init__(self, disk):
@@ -75,8 +79,17 @@ def fixup(rootdir, vols):
for vol in vols:
devbymount[vol['mount']] = vol['targetdisk']
fstabfile = os.path.join(rootdir, 'etc/fstab')
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
if os.path.exists(fstabfile):
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
else:
# fabricate a reference fstab
fstab = [
"#ORIGFSTAB#/dev/mapper/root# / xfs defaults 0 0",
"#ORIGFSTAB#UUID=aaf9e0f9-aa4d-4d74-9e75-3537620cfe23# /boot xfs defaults 0 0",
"#ORIGFSTAB#UUID=C21D-B881# /boot/efi vfat umask=0077,shortname=winnt 0 2",
"#ORIGFSTAB#/dev/mapper/swap# none swap defaults 0 0",
]
while not fstab[0]:
fstab = fstab[1:]
if os.path.exists(os.path.join(rootdir, '.autorelabel')):
@@ -126,8 +139,10 @@ def fixup(rootdir, vols):
newcfg = ifcfg.split('/')[-1]
newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg))
shutil.copy2(ifcfg, newcfg)
shutil.rmtree(os.path.join(rootdir, 'etc/confluent/'))
shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent'))
rootconfluentdir = os.path.join(rootdir, 'etc/confluent/')
if os.path.exists(rootconfluentdir):
shutil.rmtree(rootconfluentdir)
shutil.copytree('/etc/confluent', rootconfluentdir)
if policy:
sys.stdout.write('Applying SELinux labeling...')
sys.stdout.flush()
@@ -142,14 +157,41 @@ def fixup(rootdir, vols):
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')
currcmdline = []
with open('/proc/cmdline') as cmdlinein:
cmdline = cmdlinein.read().strip()
for arg in cmdline.split():
if arg.startswith('console='):
currcmdline.append(arg)
elif arg == 'quiet':
currcmdline.append(arg)
currcmdlinestr = ' '.join(currcmdline)
if os.path.exists(grubsyscfg):
with open(grubsyscfg) as defgrubin:
defgrub = defgrubin.read().split('\n')
else:
defgrub = [
'GRUB_TIMEOUT=5',
'GRUB_DISTRIBUTOR="$(sed ' + "'s, release .*$,,g'" + ' /etc/system-release)"',
'GRUB_DEFAULT=saved',
'GRUB_DISABLE_SUBMENU=true',
'GRUB_TERMINAL=""',
'GRUB_SERIAL_COMMAND=""',
'GRUB_CMDLINE_LINUX="{} crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"'.format(currcmdlinestr),
'GRUB_DISABLE_RECOVERY="true"',
'GRUB_ENABLE_BLSCFG=true',
]
if not os.path.exists(os.path.join(rootdir, "etc/kernel/cmdline")):
with open(os.path.join(rootdir, "etc/kernel/cmdline"), "w") as cmdlineout:
cmdlineout.write("{} root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root".format(currcmdlinestr))
with open(grubsyscfg, 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
for ent in gline:
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
if ent.endswith('"'):
newline.append('"')
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
@@ -159,6 +201,12 @@ def fixup(rootdir, vols):
grubcfg = grubcfg[:-1]
if len(grubcfg) == 1:
grubcfg = grubcfg[0]
elif not grubcfg:
grubcfg = '/boot/grub2/grub.cfg'
paths = glob.glob(os.path.join(rootdir, 'boot/efi/EFI/*'))
for path in paths:
with open(os.path.join(path, 'grub.cfg'), 'w') as stubgrubout:
stubgrubout.write("search --no-floppy --root-dev-only --fs-uuid --set=dev " + bootuuid + "\nset prefix=($dev)/grub2\nexport $prefix\nconfigfile $prefix/grub.cfg\n")
else:
for gcfg in grubcfg:
rgcfg = os.path.join(rootdir, gcfg[1:]) # gcfg has a leading / to get rid of
@@ -214,10 +262,18 @@ def fixup(rootdir, vols):
shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip()
shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\')
subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum])
try:
os.makedirs(os.path.join(rootdir, 'opt/confluent/bin'))
except Exception:
pass
shutil.copy2('/opt/confluent/bin/apiclient', os.path.join(rootdir, 'opt/confluent/bin/apiclient'))
#other network interfaces
def had_swap():
if not os.path.exists('/etc/fstab'):
# diskless source, assume swap
return True
with open('/etc/fstab') as tabfile:
tabs = tabfile.read().split('\n')
for tab in tabs:
@@ -362,6 +418,8 @@ def install_to_disk(imgpath):
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ'])
source = vol['mount'].replace('/', '_')
source = '/run/imginst/sources/' + source
if not os.path.exists(source):
source = '/run/imginst/sources/_' + vol['mount']
blankfsstat = os.statvfs('/run/imginst/targ')
blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize
sys.stdout.write('\nWriting {0}: '.format(vol['mount']))
@@ -419,8 +477,14 @@ def install_to_disk(imgpath):
subprocess.check_call(['umount', '/run/imginst/targ'])
while True:
try:
subprocess.check_call(['umount', '/run/imginst/targ'])
except subprocess.CalledProcessError:
print("Failed to unmount /run/imginst/targ, retrying")
time.sleep(1)
else:
break
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
@@ -5,6 +5,7 @@
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /dev/pts /sysroot/dev/pts
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
@@ -21,8 +22,14 @@ else
done
fi
cd /sysroot/run
cp /run/sshd.pid /tmp/dbgssh.pid
chroot /sysroot/ bash -c "/usr/sbin/sshd"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
for nameserver in $(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //'); do
echo "nameserver $nameserver" >> /sysroot/etc/resolv.conf
done
#chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python confignet"
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
@@ -39,7 +46,10 @@ chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python imag
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
kill $(cat /sysroot/var/run/sshd.pid)
kill -HUP $(cat /tmp/dbgssh.pid)
cp /sysroot/etc/pki/ca-trust/source/anchors/* /sysroot/run/imginst/targ/etc/pki/ca-trust/source/anchors/
chroot /sysroot/run/imginst/targ update-ca-trust
chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh"
chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)"
@@ -59,7 +59,7 @@ rpm --import /etc/pki/rpm-gpg/*
run_remote_python add_local_repositories
run_remote_python syncfileclient
run_remote_python confignet
run_remote_python confignet -c $confluent_mgr
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
@@ -2,13 +2,17 @@
# This script is executed 'chrooted' into a cloned disk target before rebooting
#
if [ -f /etc/dracut.conf.d/diskless.conf ]; then
rm /etc/dracut.conf.d/diskless.conf
fi
for kver in /lib/modules/*; do kver=$(basename $kver); kernel-install add $kver /boot/vmlinuz-$kver; done
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}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
run_remote setupssh
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
@@ -33,6 +37,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
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -3,10 +3,12 @@ echo -n "" >> /tmp/net.ifaces
echo -n "" > /tmp/01-autocons.devnode
BUNDLENAME=/etc/pki/tls/certs/ca-bundle.crt
if [ ! -e "$BUNDLENAME" ]; then
BUNDLENAME=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
mkdir -p /etc/pki/tls/certs
ln -s $BUNDLENAME /etc/pki/tls/certs/ca-bundle.crt
fi
if [ -e /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem ]; then
BUNDLENAME=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
ln -s $BUNDLENAME /etc/pki/tls/certs/ca-bundle.crt
fi
fi
cat /tls/*.0 >> $BUNDLENAME
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -31,8 +31,10 @@ 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
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
confluentpython /root/confignet
rm /root/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
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -11,6 +11,10 @@ import struct
import sys
import subprocess
import traceback
try:
import yaml
except ImportError:
yaml = None
bootuuid = None
vgname = 'localstorage'
@@ -66,9 +70,9 @@ def get_image_metadata(imgpath):
yield md
else:
# plausible filesystem structure to apply to a nominally "diskless" image
yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 39513563136, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760}
yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 232316928, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656}
yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 7835648, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960}
yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 4294967296, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760}
yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 536870912, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656}
yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 33554432, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960}
#raise Exception('Installation from single part image not supported')
class PartedRunner():
@@ -166,6 +170,15 @@ def fixup(rootdir, vols):
grubsyscfg = os.path.join(rootdir, 'etc/sysconfig/grub')
if not os.path.exists(grubsyscfg):
grubsyscfg = os.path.join(rootdir, 'etc/default/grub')
currcmdline = []
with open('/proc/cmdline') as cmdlinein:
cmdline = cmdlinein.read().strip()
for arg in cmdline.split():
if arg.startswith('console='):
currcmdline.append(arg)
elif arg == 'quiet':
currcmdline.append(arg)
currcmdlinestr = ' '.join(currcmdline)
kcmdline = os.path.join(rootdir, 'etc/kernel/cmdline')
if os.path.exists(kcmdline):
with open(kcmdline) as kcmdlinein:
@@ -177,8 +190,10 @@ def fixup(rootdir, vols):
elif ent.startswith('root='):
newkcmdlineent.append('root={}'.format(newrootdev))
elif ent.startswith('rd.lvm.lv='):
ent = convert_lv(ent)
if ent:
nent = convert_lv(ent)
if nent:
newkcmdlineent.append(ent)
else:
newkcmdlineent.append(ent)
else:
newkcmdlineent.append(ent)
@@ -200,8 +215,10 @@ def fixup(rootdir, vols):
elif cfgpart.startswith('resume='):
newcfgparts.append('resume={}'.format(newswapdev))
elif cfgpart.startswith('rd.lvm.lv='):
cfgpart = convert_lv(cfgpart)
if cfgpart:
ncfgpart = convert_lv(cfgpart)
if ncfgpart:
newcfgparts.append(ncfgpart)
else:
newcfgparts.append(cfgpart)
else:
newcfgparts.append(cfgpart)
@@ -217,13 +234,13 @@ def fixup(rootdir, vols):
'GRUB_DISABLE_SUBMENU=true',
'GRUB_TERMINAL=""',
'GRUB_SERIAL_COMMAND=""',
'GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"',
'GRUB_CMDLINE_LINUX="{}crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"'.format(currcmdlinestr),
'GRUB_DISABLE_RECOVERY="true"',
'GRUB_ENABLE_BLSCFG=true',
]
if not os.path.exists(os.path.join(rootdir, "etc/kernel/cmdline")):
with open(os.path.join(rootdir, "etc/kernel/cmdline"), "w") as cmdlineout:
cmdlineout.write("root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root")
cmdlineout.write("{} root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root".format(currcmdlinestr))
with open(grubsyscfg, 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
@@ -234,11 +251,11 @@ def fixup(rootdir, vols):
elif ent.startswith('root='):
newline.append('root={}'.format(newrootdev))
elif ent.startswith('rd.lvm.lv='):
ent = convert_lv(ent)
if ent:
nent = convert_lv(ent)
if nent:
newline.append(nent)
else:
newline.append(ent)
elif '""' in ent:
newline.append('""')
else:
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
@@ -301,8 +318,8 @@ def fixup(rootdir, vols):
for vol in vols:
if vol['mount'] == '/boot/efi':
targdev = vol['targetdisk']
partnum = re.search('(\d+)$', targdev).group(1)
targblock = re.search('(.*)\d+$', targdev).group(1)
partnum = re.search(r'(\d+)$', targdev).group(1)
targblock = re.search(r'(.*)\d+$', targdev).group(1)
if targblock:
if targblock.endswith('p') and 'nvme' in targblock:
targblock = targblock[:-1]
@@ -334,13 +351,16 @@ def had_swap():
newrootdev = None
newswapdev = None
vgmap = None
def install_to_disk(imgpath):
global vgmap
global bootuuid
global newrootdev
global newswapdev
global vgname
global oldvgname
lvmvols = {}
vgmap = {}
deftotsize = 0
mintotsize = 0
deflvmsize = 0
@@ -365,24 +385,30 @@ def install_to_disk(imgpath):
mintotsize = swapsize
for fs in get_image_metadata(imgpath):
allvols.append(fs)
deftotsize += fs['initsize']
mintotsize += fs['minsize']
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
if fs['device'].startswith('/dev/mapper'):
oldvgname = fs['device'].rsplit('/', 1)[-1]
odevname = fs['device'].rsplit('/', 1)[-1]
# if node has - then /dev/mapper will double up the hypen
if '_' in oldvgname and '-' in oldvgname.split('_', 1)[-1]:
oldvgname = oldvgname.rsplit('-', 1)[0].replace('--', '-')
if '_' in odevname and '-' in odevname.split('_', 1)[-1]:
oldvgname = odevname.rsplit('-', 1)[0].replace('--', '-')
osname = oldvgname.split('_')[0]
nodename = socket.gethostname().split('.')[0]
vgname = '{}_{}'.format(osname, nodename)
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
elif '-' in odevname: # unique one
vgmap[odevname] = odevname.split('-')[0]
lvmvols[odevname] = fs
continue
lvmvols[odevname] = fs
deflvmsize += fs['initsize']
minlvmsize += fs['minsize']
else:
plainvols[int(re.search('(\d+)$', fs['device'])[0])] = fs
plainvols[int(re.search(r'(\d+)$', fs['device'])[0])] = fs
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
deftotsize += fs['initsize']
mintotsize += fs['minsize']
with open('/tmp/installdisk') as diskin:
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
@@ -440,6 +466,28 @@ def install_to_disk(imgpath):
lvmpart = get_partname(instdisk, volidx + 1)
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
subprocess.check_call(['vgcreate', vgname, lvmpart])
vgroupmap = {}
if yaml and vgmap:
with open('/tmp/volumegroupmap.yml') as mapin:
vgroupmap = yaml.safe_load(mapin)
donedisks = {}
for morevolname in vgmap:
morevg = vgmap[morevolname]
if morevg not in vgroupmap:
raise Exception("No mapping defined to create volume group {}".format(morevg))
targdisk = vgroupmap[morevg]
if targdisk not in donedisks:
moreparted = PartedRunner(targdisk)
moreparted.run('mklabel gpt')
moreparted.run('mkpart lvm 0% 100%')
morelvmpart = get_partname(targdisk, 1)
subprocess.check_call(['pvcreate', '-ff', '-y', morelvmpart])
subprocess.check_call(['vgcreate', morevg, morelvmpart])
donedisks[targdisk] = 1
morelvname = morevolname.split('-', 1)[1]
subprocess.check_call(['lvcreate', '-L', '{}b'.format(lvmvols[morevolname]['initsize']), '-y', '-n', morelvname, morevg])
lvmvols[morevolname]['targetdisk'] = '/dev/{}/{}'.format(morevg, morelvname)
vginfo = subprocess.check_output(['vgdisplay', vgname, '--units', 'b']).decode('utf8')
vginfo = vginfo.split('\n')
pesize = 0
@@ -452,6 +500,9 @@ def install_to_disk(imgpath):
pes = int(infline[4])
takeaway = swapsize // pesize
for volidx in lvmvols:
if volidx in vgmap:
# was handled previously
continue
vol = lvmvols[volidx]
if vol is biggestfs:
continue
@@ -460,6 +511,10 @@ def install_to_disk(imgpath):
biggestextents = pes - takeaway
for volidx in lvmvols:
vol = lvmvols[volidx]
if volidx in vgmap:
# was handled previously
continue
if vol is biggestfs:
extents = biggestextents
else:
@@ -546,7 +601,13 @@ def install_to_disk(imgpath):
subprocess.check_call(['umount', '/run/imginst/targ'])
while True:
try:
subprocess.check_call(['umount', '/run/imginst/targ'])
break
except subprocess.CalledProcessError:
print("Failed to unmount /run/imginst/targ, retrying")
time.sleep(1)
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
@@ -129,5 +129,11 @@ mv /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs
ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
mv /lib/firmware /lib/firmware-ramfs
ln -s /sysroot/lib/firmware /lib/firmware
rm -f /sysroot/etc/dracut.conf.d/diskless.conf # remove diskless dracut from runtime, to make kdump happier
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
exec /opt/confluent/bin/start_root
if grep debugssh /proc/cmdline >& /dev/null; then
exec /opt/confluent/bin/start_root
else
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
fi
@@ -5,8 +5,12 @@
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /dev/pts /sysroot/dev/pts
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
mount -t efivarfs none /sysroot/sys/firmware/efi/efivars
if [ ! -f /tmp/mountparts.sh ]; then
@@ -21,8 +25,16 @@ else
done
fi
cd /sysroot/run
[ -f /run/sshd.pid ] &&
cp /run/sshd.pid /tmp/dbgssh.pid
chmod 0600 /sysroot/etc/ssh/ssh*key
chroot /sysroot/ bash -c "/usr/sbin/sshd"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
for nameserver in $(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //'); do
echo "nameserver $nameserver" >> /sysroot/etc/resolv.conf
done
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
@@ -40,7 +52,8 @@ chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python imag
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
kill $(cat /sysroot/var/run/sshd.pid)
[ -f /tmp/dbgssh.pid ] && kill -HUP $(cat /tmp/dbgssh.pid)
cp /sysroot/etc/pki/ca-trust/source/anchors/* /sysroot/run/imginst/targ/etc/pki/ca-trust/source/anchors/
chroot /sysroot/run/imginst/targ update-ca-trust
@@ -53,7 +53,7 @@ rpm --import /etc/pki/rpm-gpg/*
run_remote_python add_local_repositories
run_remote_python syncfileclient
run_remote_python confignet
run_remote_python confignet -c $confluent_mgr
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
@@ -43,7 +43,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
# rebuild initrd, pick up new drivers if needed
dracut -f /boot/initramfs-$(uname -r).img $(uname -r)
@@ -62,8 +62,8 @@ if [ -e /tmp/confluentident/cnflnt.yml ]; then
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
localcli network ip interface ipv4 set -i vmk0 -I $v4addr -N $v4nm -g $v4gw -t static
localcli network ip route ipv4 add -n default -g $v4gw
while ! localcli network ip interface ipv4 set -i vmk0 -I $v4addr -N $v4nm -g $v4gw -t static; do echo "Retrying..."; sleep 5; done
while ! localcli network ip route ipv4 add -n default -g $v4gw; do sleep 1; done
fi
hmackeyfile=$(mktemp)
echo -n $(grep ^apitoken: /tmp/confluentident/cnflnt.yml|awk '{print $2}') > $hmackeyfile
@@ -73,6 +73,20 @@ if [ -e /tmp/confluentident/cnflnt.yml ]; then
hmacfile=$(mktemp)
ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac
/opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
echo -n 'Checking connectivity to server: '
maxwait=30
while ! /opt/confluent/bin/apiclient -c >& /dev/null; do
echo -n '.'
sleep 1
maxwait=$((maxwait - 1))
if [ $maxwait -le 0 ]; then
echo "Unable to contact deployment server, verify network connectivity"
echo "A debug session has been made available on Alt-F1"
sleep 30
maxwait=30
fi
done
echo
echo -n 'Registering new API key with deployment server: '
/opt/confluent/bin/apiclient -p $hmacfile /confluent-api/self/registerapikey $passcrypt
echo
@@ -1,6 +1,5 @@
accepteula
clearpart --firstdisk --overwritevmfs
install --firstdisk --overwritevmfs
%include /tmp/storagecfg
%include /tmp/ksnet
%include /tmp/rootpw
reboot
@@ -0,0 +1,148 @@
#!/usr/bin/python3
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname, devinfo):
self.name = devname
self.path = '/dev/' + devname
self.wwn = None
self.model = devinfo.get('model', 'Unknown')
self.driver = devinfo.get('adapter_driver', 'Unknown')
self.size = devinfo.get('size', 0) # in MiB
if not devinfo.get('is_local', False):
raise SilentException("Not local")
if devinfo.get('is_removable', False):
raise SilentException("Removable")
if devinfo.get('is_usb', False):
raise SilentException("USB device")
if devinfo.get('type', '').lower() in ('cd-rom',):
raise SilentException("CD-ROM device")
if self.size < 2048:
raise SilentException("Too small")
@property
def priority(self):
if self.model.lower() in ('m.2 nvme 2-bay raid kit', 'thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
return 0
if self.driver == 'vmw_ahci':
return 2
if self.driver == 'nvme_pcie':
return 3
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def list_disks():
current_dev = None
disks = {}
devlist = subprocess.check_output(['localcli', 'storage', 'core', 'device', 'list'])
if not isinstance(devlist, str):
devlist = devlist.decode('utf8')
devbyadp = {}
for line in devlist.split('\n'):
if not line.strip():
continue
if not line.startswith(' '):
current_dev = line.rsplit(':', 1)[0]
if current_dev not in disks:
disks[current_dev] = {}
elif current_dev:
if ' Model:' in line:
disks[current_dev]['model'] = ' '.join(line.split()[1:])
elif ' Driver:' in line:
disks[current_dev]['driver'] = ' '.join(line.split()[1:])
elif ' Is Local:' in line:
disks[current_dev]['is_local'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Is Removable:' in line:
disks[current_dev]['is_removable'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Size:' in line: # in MiB
disks[current_dev]['size'] = int(line.split()[1])
elif ' Is SSD:' in line:
disks[current_dev]['is_ssd'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Is USB:' in line:
disks[current_dev]['is_usb'] = ' '.join(line.split()[2:]).lower() == 'true'
elif ' Is Removable:' in line:
disks[current_dev]['is_removable'] = ' '.join(line.split()[2:]).lower() == 'true'
elif 'Device Type:' in line:
disks[current_dev]['type'] = ' '.join(line.split()[2:])
for dev in disks:
pathlist = subprocess.check_output(['localcli', 'storage', 'core', 'path', 'list', '--device', dev])
if not isinstance(pathlist, str):
pathlist = pathlist.decode('utf8')
for line in pathlist.split('\n'):
if not line.strip():
continue
if not line.startswith(' '):
continue
if ' Adapter Identifier:' in line:
adpname = ' '.join(line.split()[2:])
disks[dev]['adapter_id'] = adpname
elif ' Adapter:' in line:
adp = ' '.join(line.split()[1:])
disks[dev]['adapter'] = adp
devbyadp.setdefault(adp, []).append(dev)
adapterlist = subprocess.check_output(['localcli', 'storage', 'core', 'adapter', 'list'])
if not isinstance(adapterlist, str):
adapterlist = adapterlist.decode('utf8')
driverbyadp = {}
linenum = 0
for line in adapterlist.split('\n'):
linenum += 1
if not line.strip():
continue
if linenum < 3:
continue
parts = line.split()
if len(parts) < 2:
continue
adp = parts[0]
driver = parts[1]
driverbyadp[adp] = driver
for adp in devbyadp:
driver = driverbyadp.get(adp, 'Unknown')
for dev in devbyadp[adp]:
disks[dev]['adapter_driver'] = driver
return disks
def main():
disks = []
try:
alldisks = list_disks()
except Exception as e:
print("Error listing disks: {0}".format(str(e)))
alldisks = {}
for disk in alldisks:
try:
disks.append(DiskInfo(disk, alldisks[disk]))
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
with open('/tmp/storagecfg', 'w') as sc:
sc.write(f'clearpart --all --drives={nd[0]} --overwritevmfs\n')
sc.write(f'install --drive={nd[0]} --overwritevmfs\n')
else:
with open('/tmp/storagecfg', 'w') as sc:
sc.write(f'clearpart --firstdisk --overwritevmfs\n')
sc.write(f'install --firstdisk --overwritevmfs\n')
if __name__ == '__main__':
main()
@@ -45,7 +45,10 @@ try:
cfg['ipv4_gateway'] = ncfg['ipv4_gateway']
except Exception:
pass
netline = 'network --hostname={0} --bootproto={1}'.format(nodename, cfg['ipv4_method'])
if cfg['ipv4_method'] == 'static':
netline = 'network --hostname={0} --bootproto={1}'.format(nodename, cfg['ipv4_method'])
else:
netline = 'network --bootproto=dhcp'
if vmnic:
netline += ' --device={0}'.format(vmnic)
if cfg['ipv4_method'] == 'static':
@@ -1,9 +1,12 @@
#!/bin/sh
mv /etc/confluent/confluent.deploycfg /etc/confluent/confluent.newdeploycfg
/opt/confluent/bin/apiclient /confluent-public/os/$profile/scripts/makeksnet >> /tmp/makeksnet
/opt/confluent/bin/apiclient /confluent-public/os/$profile/scripts/getinstalldisk >> /tmp/getinstalldisk
mv /etc/confluent/confluent.newdeploycfg /etc/confluent/confluent.deploycfg
chmod +x /tmp/makeksnet
/tmp/makeksnet > /tmp/ksnet
localcli system hostname set --host $node
python3 /tmp/getinstalldisk
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
echo rootpw --iscrypted $rootpw > /tmp/rootpw
export BOOT_CMDLINE=ks=/etc/confluent/ks.cfg
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -52,13 +52,40 @@ while ! grep NODENAME /custom-installation/confluent/confluent.info; do
ipconfig -d $MYIP::$MYGW:$MYNM::$NIC
echo $NIC > /tmp/autodetectnic
else
configure_networking
for dsrv in $deploysrvs; do
if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
deploysrvs=$dsrv
break
rmmod cdc_ether 2> /dev/null
while [ ! -f /run/confirmednic ]; do
ALLNETDEVS=$(ip a|grep LOWER_UP|grep MULTICAST|awk '{print $2}'|sed -e s/://)
rm -rf /run/net* /run/dhcpcd /var/lib/dhcpcd
for dev in $(ip a|grep MULTICAST|awk '{print $2}'|sed -e s/://); do
ip a flush $dev
echo 1 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode
echo 0 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode
done
unset DEVICE DEVICE6 IP IP6 dev
[ -z "$1" ] || DEVICE=$1
shift
configure_networking
echo $DEVICE > /tmp/autodetectnic
for dsrv in $deploysrvs; do
(if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
echo $dsrv > /run/confirmednic
break
fi) &
chkpid=$!
( sleep 10 && kill $chkpid ) &
timeoutpid=$!
wait $chkpid
kill $timeoutpid 2> /dev/null
unset chkpid timeoutpid
done
if [ ! -f /run/confirmednic ]; then
echo "No connectivity to deployment servers, retrying..."
[ -z "$1" ] && set -- $ALLNETDEVS
fi
done
deploysrvs=$(cat /run/confirmednic)
rm /run/confirmednic
fi
MGR=$deploysrvs
NODENAME=$(grep ^nodename: /tmp/idntmnt/cnflnt.yml | awk '{print $2}')
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -53,13 +53,40 @@ while ! grep NODENAME /custom-installation/confluent/confluent.info; do
ipconfig -d $MYIP::$MYGW:$MYNM::$NIC
echo $NIC > /tmp/autodetectnic
else
configure_networking
for dsrv in $deploysrvs; do
if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
deploysrvs=$dsrv
break
rmmod cdc_ether 2> /dev/null
while [ ! -f /run/confirmednic ]; do
ALLNETDEVS=$(ip a|grep LOWER_UP|grep MULTICAST|awk '{print $2}'|sed -e s/://)
rm -rf /run/net* /run/dhcpcd /var/lib/dhcpcd
for dev in $(ip a|grep MULTICAST|awk '{print $2}'|sed -e s/://); do
ip a flush $dev
echo 1 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode
echo 0 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode
done
unset DEVICE DEVICE6 IP IP6 dev
[ -z "$1" ] || DEVICE=$1
shift
configure_networking
echo $DEVICE > /tmp/autodetectnic
for dsrv in $deploysrvs; do
(if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then
echo $dsrv > /run/confirmednic
break
fi) &
chkpid=$!
( sleep 10 && kill $chkpid ) &
timeoutpid=$!
wait $chkpid
kill $timeoutpid 2> /dev/null
unset chkpid timeoutpid
done
if [ ! -f /run/confirmednic ]; then
echo "No connectivity to deployment servers, retrying..."
[ -z "$1" ] && set -- $ALLNETDEVS
fi
done
deploysrvs=$(cat /run/confirmednic)
rm /run/confirmednic
fi
MGR=$deploysrvs
NODENAME=$(grep ^nodename: /tmp/idntmnt/cnflnt.yml | awk '{print $2}')
@@ -1,14 +1,23 @@
#!/bin/sh
sed -i 's/label: ubuntu/label: Ubuntu/' $2/profile.yaml && \
ln -s $1/casper/vmlinuz $2/boot/kernel && \
ln -s $1/casper/initrd $2/boot/initramfs/distribution && \
mkdir -p $2/boot/efi/boot && \
#!/bin/bash
set -e
sed -i 's/label: ubuntu/label: Ubuntu/' $2/profile.yaml
#if [ -e $1/casper/hwe-vmlinuz ]; then
# ln -s $1/casper/hwe-vmlinuz $2/boot/kernel
#else
ln -s $1/casper/vmlinuz $2/boot/kernel
#fi
#if [ -e $1/casper/hwe-initrd ]; then
# ln -s $1/casper/hwe-initrd $2/boot/initramfs/distribution
#else
ln -s $1/casper/initrd $2/boot/initramfs/distribution
#fi
mkdir -p $2/boot/efi/boot
if [ -d $1/EFI/boot/ ]; then
ln -s $1/EFI/boot/* $2/boot/efi/boot
elif [ -d $1/efi/boot/ ]; then
ln -s $1/efi/boot/* $2/boot/efi/boot
else
echo "Unrecogrized boot contents in media" >&2
echo "Unrecognized boot contents in media" >&2
exit 1
fi
@@ -10,6 +10,13 @@ function test_mgr() {
return 1
}
function initconfluentscriptstmp() {
if [ -z "$confluentscripttmpdir" ]; then
mkdir -p /opt/confluent/tmpexec
confluentscripttmpdir=$(mktemp -d /opt/confluent/tmpexec/confluentscripts.XXXXXXXXX)
fi
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
@@ -72,7 +79,8 @@ fetch_remote() {
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -86,7 +94,8 @@ source_remote_parts() {
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
@@ -105,10 +114,7 @@ source_remote() {
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
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -135,9 +141,9 @@ run_remote() {
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
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
@@ -170,7 +176,8 @@ run_remote_python() {
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
@@ -2,6 +2,9 @@
import subprocess
import os
class SilentException(Exception):
pass
class DiskInfo(object):
def __init__(self, devname):
if devname.startswith('nvme') and 'c' in devname:
@@ -24,9 +27,11 @@ class DiskInfo(object):
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
if v == 'partition':
raise SilentException('Partition')
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
raise SilentException('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
@@ -50,13 +55,17 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
elif k == 'ATTRS{subsystype}':
self.subsystype = v.replace('"', '')
elif k == 'ATTR{ro}' and v == '"1"':
raise Exception("Device is read-only")
if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm':
raise Exception("No driver detected")
if self.driver == 'sr':
raise Exception('cd/dvd')
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
if int(self.size) < 2147483648:
raise Exception("Device too small for install ({}MiB)".format(int(self.size)/1024/1024))
@property
def priority(self):
@@ -89,9 +98,11 @@ def main():
try:
disk = DiskInfo(disk)
disks.append(disk)
except SilentException:
pass
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
nd = [x.name for x in sorted(disks, key=lambda x: [x.priority, x.size])]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
@@ -2,20 +2,23 @@ package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"os"
"net"
"net/http"
"crypto/x509"
"crypto/tls"
"os"
"strings"
"errors"
"time"
)
type ApiClient struct {
server string
server string
urlserver string
apikey string
nodename string
apikey string
nodename string
webclient *http.Client
}
@@ -24,7 +27,7 @@ func NewApiClient(cafile string, keyfile string, nodename string, server string)
if err != nil {
return nil, err
}
cacerts := x509.NewCertPool()
cacerts := x509.NewCertPool()
cacerts.AppendCertsFromPEM(currcacerts)
apikey := []byte("")
if keyfile != "" {
@@ -32,7 +35,7 @@ func NewApiClient(cafile string, keyfile string, nodename string, server string)
if err != nil {
return nil, err
}
if apikey[len(apikey) - 1] == 0xa {
if apikey[len(apikey)-1] == 0xa {
apikey = apikey[:len(apikey)-1]
}
}
@@ -40,7 +43,9 @@ func NewApiClient(cafile string, keyfile string, nodename string, server string)
cinfo, err := os.ReadFile("/etc/confluent/confliuent.info")
if err != nil {
nodename, err = os.Hostname()
if err != nil { return nil, err }
if err != nil {
return nil, err
}
}
cinfolines := bytes.Split(cinfo, []byte("\n"))
if bytes.Contains(cinfolines[0], []byte("NODENAME")) {
@@ -48,6 +53,20 @@ func NewApiClient(cafile string, keyfile string, nodename string, server string)
nodename = string(cnodebytes[0])
}
}
// Test connectivity with up to 3 retries
var conn net.Conn
for i := 0; i < 3; i++ {
conn, err = net.Dial("tcp", net.JoinHostPort(server, "443"))
if err == nil {
conn.Close()
break
}
time.Sleep(5 * time.Second)
fmt.Print("Connection attempt failed, retrying...\n")
if i == 2 {
return nil, fmt.Errorf("failed to connect after 3 attempts: %v", err)
}
}
urlserver := server
if strings.Contains(server, ":") {
if strings.Contains(server, "%") && !strings.Contains(server, "%25") {
@@ -58,10 +77,11 @@ func NewApiClient(cafile string, keyfile string, nodename string, server string)
server = server[:strings.Index(server, "%")]
}
}
webclient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: cacerts,
RootCAs: cacerts,
ServerName: server,
},
},
@@ -70,34 +90,42 @@ func NewApiClient(cafile string, keyfile string, nodename string, server string)
return &vc, nil
}
func (apiclient *ApiClient) RegisterKey(crypted string, hmac string) (error) {
func (apiclient *ApiClient) RegisterKey(crypted string, hmac string) error {
cryptbytes := []byte(crypted)
cryptbuffer := bytes.NewBuffer(cryptbytes)
_, err := apiclient.request("/confluent-api/self/registerapikey", "", cryptbuffer, "", hmac)
return err
}
func (apiclient *ApiClient) Fetch(url string, outputfile string, mime string, body io.Reader) (error) {
func (apiclient *ApiClient) Fetch(url string, outputfile string, mime string, body io.Reader) error {
outp, err := os.Create(outputfile)
if err != nil { return err }
if err != nil {
return err
}
defer outp.Close()
rsp, err := apiclient.request(url, mime, body, "", "")
if err != nil { return err }
if err != nil {
return err
}
_, err = io.Copy(outp, rsp)
return err
}
func (apiclient *ApiClient) GrabText(url string, mime string, body io.Reader) (string, error){
func (apiclient *ApiClient) GrabText(url string, mime string, body io.Reader) (string, error) {
rsp, err := apiclient.request(url, mime, body, "", "")
if err != nil { return "", err }
if err != nil {
return "", err
}
rspdata, err := io.ReadAll(rsp)
if err != nil { return "", err }
if err != nil {
return "", err
}
rsptxt := string(rspdata)
return rsptxt, nil
}
func (apiclient *ApiClient) request(url string, mime string, body io.Reader, method string, hmac string) (io.ReadCloser, error) {
if ! strings.Contains(url, "https://") {
if !strings.Contains(url, "https://") {
url = fmt.Sprintf("https://%s%s", apiclient.urlserver, url)
}
if method == "" {
@@ -114,8 +142,12 @@ func (apiclient *ApiClient) request(url string, mime string, body io.Reader, met
} else {
rq, err = http.NewRequest(method, url, body)
}
if err != nil { return nil, err }
if (mime != "") { rq.Header.Set("Accept", mime) }
if err != nil {
return nil, err
}
if mime != "" {
rq.Header.Set("Accept", mime)
}
rq.Header.Set("CONFLUENT_NODENAME", apiclient.nodename)
if len(hmac) > 0 {
rq.Header.Set("CONFLUENT_CRYPTHMAC", hmac)
@@ -124,11 +156,12 @@ func (apiclient *ApiClient) request(url string, mime string, body io.Reader, met
rq.Header.Set("CONFLUENT_APIKEY", apiclient.apikey)
}
rsp, err := apiclient.webclient.Do(rq)
if err != nil { return nil, err }
if err != nil {
return nil, err
}
if rsp.StatusCode >= 300 {
err = errors.New(rsp.Status)
return nil, err
}
return rsp.Body, err
}
+4 -1
View File
@@ -3,8 +3,11 @@
#include <sys/mount.h>
#define __USE_GNU
#include <sched.h>
#include <string.h>
int main(int argc, char* argv[]) {
unshare(CLONE_NEWNS);
if (argc < 2 || strcmp(argv[1], "-s")) {
unshare(CLONE_NEWNS);
}
mount("/dev", "/sysroot/dev", NULL, MS_MOVE, NULL);
mount("/proc", "/sysroot/proc", NULL, MS_MOVE, NULL);
mount("/sys", "/sysroot/sys", NULL, MS_MOVE, NULL);
+37 -2
View File
@@ -27,6 +27,16 @@ import signal
import confluent.collective.manager as collective
import confluent.noderange as noderange
def check_sysctl_tuning():
with open('/proc/sys/net/ipv4/tcp_sack', 'r') as f:
value = f.read().strip()
if value == '1':
print('OK')
return
else:
emprint('TCP SACK is disabled, network operations to BMCs may be particularly impacted, including firmware updates and virtual media')
def check_neigh_overflow():
dmesgout = subprocess.check_output(['dmesg'])
if b'_cache: neighbor table overflow!' in subprocess.check_output(['dmesg']):
@@ -216,6 +226,8 @@ if __name__ == '__main__':
emprint('ARP/Neighbor table problem detected, evaluate and increase net.ipv*.neigh.default.gc_thresh*')
else:
print('OK')
fprint('Checking sysctl tunables: ')
check_sysctl_tuning()
fprint('TFTP Status: ')
if tftp_works():
print('OK')
@@ -223,7 +235,26 @@ if __name__ == '__main__':
emprint('TFTP failure, PXE will not work, though media and HTTP boot can still work. (Example resolution: osdeploy initialize -p)')
fprint('SSH root user public key: ')
if glob.glob('/var/lib/confluent/public/site/ssh/*.rootpubkey'):
print('OK')
if not glob.glob('/root/.ssh/id_*.pub'):
emprint('No SSH keys for root user, passwordless SSH from managers to nodes may not work (Example resolution: osdeploy initialize -u)')
for userpub in glob.glob('/root/.ssh/id_*.pub'):
with open(userpub) as f:
pubkey = f.read().strip()
for sitepubkey in glob.glob('/var/lib/confluent/public/site/ssh/*.rootpubkey'):
with open(sitepubkey) as sf:
spubkey = sf.read().strip()
for keyline in spubkey.split('\n'):
if keyline == pubkey:
print('OK')
break
else:
continue
break
else:
continue
break
else:
emprint('No matching public key found for root user (Example resolution: osdeploy initialize -u)')
else:
emprint('No trusted ssh keys for root user, passwordless SSH from managers to nodes may not work (Example resolution: osdeploy initialize -u)')
if sshutil.sshver() > 7.6:
@@ -254,6 +285,8 @@ if __name__ == '__main__':
emprint('Permissions incorrect on /etc/confluent/ssh/automation (Example resolution: chmod 600 /etc/confluent/ssh/automation)')
else:
emprint('Failed to load confluent automation key, syncfiles and profile ansible plays will not work (Example resolution: osdeploy initialize -a)')
if sshutil.agent_pid:
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
sys.exit(0)
fprint('Checking for blocked insecure boot: ')
if insecure_boot_attempts():
@@ -402,7 +435,9 @@ if __name__ == '__main__':
else:
emprint('Unknown error attempting confluent automation ssh:')
sys.stderr.buffer.write(srun.stderr)
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
if sshutil.agent_pid:
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
sys.exit(0)
else:
print("Skipping node checks, no node specified (Example: confluent_selfcheck -n n1)")
# possible checks:
+97 -10
View File
@@ -53,22 +53,61 @@ def get_ip_addresses():
def check_apache_config(path):
keypath = None
certpath = None
chainpath = None
with open(path, 'r') as openf:
webconf = openf.read()
insection = False
# we always manipulate the first VirtualHost section
# since we are managing IP based SANs, then SNI
# can never match anything but the first VirtualHost
for line in webconf.split('\n'):
line = line.strip()
line = line.split('#')[0]
if line.startswith('SSLCertificateFile'):
_, certpath = line.split(None, 1)
if line.startswith('SSLCertificateKeyFile'):
if not certpath and line.startswith('SSLCertificateFile'):
insection = True
if not certpath:
_, certpath = line.split(None, 1)
if not keypath and line.startswith('SSLCertificateKeyFile'):
insection = True
_, keypath = line.split(None, 1)
if not chainpath and line.startswith('SSLCertificateChainFile'):
insection = True
_, chainpath = line.split(None, 1)
if insection and line.startswith('</VirtualHost>'):
break
return keypath, certpath, chainpath
def check_nginx_config(path):
keypath = None
certpath = None
# again, we only care about the first server section
# since IP won't trigger SNI matches down the configuration
with open(path, 'r') as openf:
webconf = openf.read()
for line in webconf.split('\n'):
if keypath and certpath:
break
line = line.strip()
line = line.split('#')[0]
for segment in line.split(';'):
if not certpath and segment.startswith('ssl_certificate'):
_, certpath = segment.split(None, 1)
if not keypath and segment.startswith('ssl_certificate_key'):
_, keypath = segment.split(None, 1)
if keypath:
keypath = keypath.strip('"')
if certpath:
certpath = certpath.strip('"')
return keypath, certpath
def get_certificate_paths():
keypath = None
certpath = None
chainpath = None
ngkeypath = None
ngbundlepath = None
if os.path.exists('/etc/httpd/conf.d/ssl.conf'): # redhat way
keypath, certpath = check_apache_config('/etc/httpd/conf.d/ssl.conf')
keypath, certpath, chainpath = check_apache_config('/etc/httpd/conf.d/ssl.conf')
if not keypath and os.path.exists('/etc/apache2'): # suse way
for currpath, _, files in os.walk('/etc/apache2'):
for fname in files:
@@ -77,11 +116,32 @@ def get_certificate_paths():
kploc = check_apache_config(os.path.join(currpath,
fname))
if keypath and kploc[0] and keypath != kploc[0]:
return None, None # Ambiguous...
return {'error': 'Ambiguous...'}
if kploc[0]:
keypath, certpath = kploc
return keypath, certpath
keypath, certpath, chainpath = kploc
if os.path.exists('/etc/nginx'): # nginx way
for currpath, _, files in os.walk('/etc/nginx'):
if ngkeypath:
break
for fname in files:
if not fname.endswith('.conf'):
continue
ngkeypath, ngbundlepath = check_nginx_config(os.path.join(currpath,
fname))
if ngkeypath:
break
tlsmateriallocation = {}
if keypath:
tlsmateriallocation.setdefault('keys', []).append(keypath)
if ngkeypath:
tlsmateriallocation.setdefault('keys', []).append(ngkeypath)
if certpath:
tlsmateriallocation.setdefault('certs', []).append(certpath)
if chainpath:
tlsmateriallocation.setdefault('chains', []).append(chainpath)
if ngbundlepath:
tlsmateriallocation.setdefault('bundles', []).append(ngbundlepath)
return tlsmateriallocation
def assure_tls_ca():
keyout, certout = ('/etc/confluent/tls/cakey.pem', '/etc/confluent/tls/cacert.pem')
@@ -208,8 +268,12 @@ def create_simple_ca(keyout, certout):
def create_certificate(keyout=None, certout=None, csrout=None):
if not keyout:
keyout, certout = get_certificate_paths()
if not keyout:
tlsmateriallocation = get_certificate_paths()
keyout = tlsmateriallocation.get('keys', [None])[0]
certout = tlsmateriallocation.get('certs', [None])[0]
if not certout:
certout = tlsmateriallocation.get('bundles', [None])[0]
if not keyout or not certout:
raise Exception('Unable to locate TLS certificate path automatically')
assure_tls_ca()
shortname = socket.gethostname().split('.')[0]
@@ -291,6 +355,29 @@ def create_certificate(keyout=None, certout=None, csrout=None):
'-startdate', '19700101010101Z', '-enddate', '21000101010101Z',
'-extfile', extconfig
])
for keycopy in tlsmateriallocation.get('keys', []):
if keycopy != keyout:
shutil.copy2(keyout, keycopy)
for certcopy in tlsmateriallocation.get('certs', []):
if certcopy != certout:
shutil.copy2(certout, certcopy)
cacert = None
with open('/etc/confluent/tls/cacert.pem', 'rb') as cacertfile:
cacert = cacertfile.read()
for bundlecopy in tlsmateriallocation.get('bundles', []):
if bundlecopy != certout:
shutil.copy2(certout, bundlecopy)
with open(bundlecopy, 'ab') as bundlefile:
bundlefile.write(b'\n')
bundlefile.write(cacert)
for chaincopy in tlsmateriallocation.get('chains', []):
if chaincopy != certout:
with open(chaincopy, 'wb') as chainfile:
chainfile.write(cacert)
else:
with open(chaincopy, 'ab') as chainfile:
chainfile.write(b'\n')
chainfile.write(cacert)
finally:
os.remove(tmpconfig)
if needcsr:
@@ -215,6 +215,9 @@ node = {
'Using this requires that collective members be '
'defined as nodes for noderange expansion')
},
'deployment.client_ip': {
'description': ('Client IP used when most recently reporting state.')
},
'deployment.lock': {
'description': ('Indicates whether deployment actions should be impeded. '
'If locked, it indicates that a pending profile should not be applied. '
@@ -376,7 +379,7 @@ node = {
'the managed node. If not specified, then console '
'is disabled. "ipmi" should be specified for most '
'systems if console is desired.'),
'validvalues': ('ssh', 'ipmi', 'openbmc', 'tsmsol', 'vcenter'),
'validvalues': ('ssh', 'ipmi', 'openbmc', 'tsmsol', 'vcenter', 'proxmox'),
},
# 'virtualization.host': {
# 'description': ('Hypervisor where this node does/should reside'),
@@ -478,6 +481,9 @@ node = {
'This would be the default name per the deployed OS and can be a comma delimited list to denote members of '
'a team or a single interface for VLAN/PKEY connections.'
},
'net.mtu': {
'description': 'MTU to apply to this connection',
},
'net.vlan_id': {
'description': 'Ethernet VLAN or InfiniBand PKEY to use for this connection. '
'Specify the parent device using net.interface_names.'
+20
View File
@@ -300,6 +300,10 @@ def _init_core():
'default': 'ipmi',
}),
},
'certificate_authorities': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
@@ -498,6 +502,22 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'core': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'adapters': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'disks': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'misc': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'updatestatus': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
+2
View File
@@ -43,6 +43,8 @@ libc = ctypes.CDLL(ctypes.util.find_library('c'))
def address_is_somewhat_trusted(address, nodename, cfm):
if netutil.ip_on_same_subnet(address.split('%')[0], 'fe80::', 64):
return True
if netutil.address_is_local(address):
return True
authnets = cfm.get_node_attributes(nodename, 'trusted.subnets')
+9 -2
View File
@@ -316,6 +316,8 @@ def list_matching_nodes(criteria):
retnodes = []
for node in known_nodes:
for mac in known_nodes[node]:
if mac not in known_info:
continue
info = known_info[mac]
if _info_matches(info, criteria):
retnodes.append(node)
@@ -613,7 +615,11 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
return [msg.AssignedResource(inputdata['node'])]
elif operation == 'delete':
mac = _get_mac_from_query(pathcomponents)
del known_info[mac]
for node in known_nodes:
if mac in known_nodes[node]:
del known_nodes[node][mac]
if mac in known_info:
del known_info[mac]
return [msg.DeletedResource(mac)]
raise exc.NotImplementedException(
'Unable to {0} to {1}'.format(operation, '/'.join(pathcomponents)))
@@ -1356,7 +1362,8 @@ def eval_node(cfg, handler, info, nodename, manual=False):
errorstr = 'The detected node {0} was detected using switch, ' \
'however the relevant port has too many macs learned ' \
'for this type of device ({1}) to be discovered by ' \
'switch.'.format(nodename, handler.devname)
'switch. If this should be an enclosure, make sure there are ' \
'defined nodes for the enclosure'.format(nodename, handler.devname)
log.log({'error': errorstr})
return
if not discover_node(cfg, handler, info, nodename, manual):
@@ -68,14 +68,19 @@ class NodeHandler(generic.NodeHandler):
self._srvroot = srvroot
return self._srvroot
def get_manager_url(self, wc):
mgrs = self.srvroot(wc).get('Managers', {}).get('@odata.id', None)
if not mgrs:
raise Exception("No Managers resource on BMC")
rsp = wc.grab_json_response(mgrs)
if len(rsp.get('Members', [])) != 1:
raise Exception("Can not handle multiple Managers")
mgrurl = rsp['Members'][0]['@odata.id']
return mgrurl
def mgrinfo(self, wc):
if not self._mgrinfo:
mgrs = self.srvroot(wc)['Managers']['@odata.id']
rsp = wc.grab_json_response(mgrs)
if len(rsp['Members']) != 1:
raise Exception("Can not handle multiple Managers")
mgrurl = rsp['Members'][0]['@odata.id']
self._mgrinfo = wc.grab_json_response(mgrurl)
self._mgrinfo = wc.grab_json_response(self.get_manager_url(wc))
return self._mgrinfo
@@ -281,7 +286,7 @@ class NodeHandler(generic.NodeHandler):
compip = compip.split('%')[0]
ipkey = 'IPv6Addresses'
else:
ipkey = 'IPv6Addresses'
ipkey = 'IPv4Addresses'
actualnic = None
for curractnic in actualnics:
currnicinfo = wc.grab_json_response(curractnic)
@@ -15,6 +15,7 @@
import base64
import codecs
import confluent.discovery.handlers.imm as immhandler
import confluent.discovery.handlers.xcc3 as xcc3handler
import confluent.exceptions as exc
import confluent.netutil as netutil
import confluent.util as util
@@ -477,12 +478,19 @@ class NodeHandler(immhandler.NodeHandler):
tmpaccount = None
while status != 200:
tries -= 1
rsp, status = wc.grab_json_response_with_status(
'/redfish/v1/AccountService/Accounts/{0}'.format(uid))
if status >= 500:
if tries < 0:
raise Exception('Redfish account management failure')
eventlet.sleep(30)
continue
rsp, status = wc.grab_json_response_with_status(
'/redfish/v1/AccountService/Accounts/{0}'.format(uid),
{'UserName': username}, method='PATCH')
if status != 200:
rsp = json.loads(rsp)
if rsp.get('error', {}).get('code', 'Unknown') in ('Base.1.8.GeneralError', 'Base.1.12.GeneralError', 'Base.1.14.GeneralError', 'Base.1.18.GeneralError'):
if rsp.get('error', {}).get('code', 'Unknown') in ('Base.1.8.GeneralError', 'Base.1.12.GeneralError', 'Base.1.14.GeneralError', 'Base.1.18.GeneralError', 'Base.1.21.GeneralError'):
if tries:
eventlet.sleep(4)
elif tmpaccount:
@@ -514,7 +522,7 @@ class NodeHandler(immhandler.NodeHandler):
if userent['users_user_name'] == user:
curruser = userent
break
if curruser.get('users_pass_is_sha256', 0):
if curruser and curruser.get('users_pass_is_sha256', 0):
self._wc = None
wc = self.wc
nwc = wc.dupe()
@@ -708,6 +716,13 @@ def remote_nodecfg(nodename, cfm):
raise Exception('Cannot remote configure a system without known '
'address')
info = {'addresses': [ipaddr]}
nh = NodeHandler(info, cfm)
ipaddr = ipaddr[0]
wc = webclient.SecureHTTPConnection(
ipaddr, 443, verifycallback=lambda x: True)
rsp = wc.grab_json_response('/DeviceDescription.json')
if isinstance(rsp, list):
nh = NodeHandler(info, cfm)
else:
nh = xcc3handler.NodeHandler(info, cfm)
nh.config(nodename)
@@ -29,6 +29,9 @@ class NodeHandler(redfishbmc.NodeHandler):
def get_firmware_default_account_info(self):
return ('USERID', 'PASSW0RD')
def get_manager_url(self, wc):
return '/redfish/v1/Managers/1'
def scan(self):
ip, port = self.get_web_port_and_ip()
c = webclient.SecureHTTPConnection(ip, port,
@@ -844,7 +844,7 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=N
log.log({'error': 'Unable to serve {0} due to duplicated address between node and interface index "{}"'.format(node, info['netinfo']['ifidx'])})
return
can302 = True
if httpboot:
if isboot and httpboot:
proto = 'https' if insecuremode == 'never' else 'http'
bootfile = '{0}://{1}/confluent-public/os/{2}/boot.img'.format(
proto, myipn, profile
@@ -865,13 +865,16 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=N
node, profile, len(bootfile) - 127)})
return
repview[108:108 + len(bootfile)] = bootfile
elif info.get('architecture', None) == 'uefi-aarch64' and packet.get(77, None) == b'iPXE':
if not profile:
profile, stgprofile = get_deployment_profile(node, cfg)
if not profile:
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP eply'.format(node)})
return
bootfile = 'http://{0}/confluent-public/os/{1}/boot.ipxe'.format(myipn, profile).encode('utf8')
elif isboot and info.get('architecture', None) == 'uefi-aarch64':
if packet.get(77, None) == b'iPXE':
if not profile:
profile, stgprofile = get_deployment_profile(node, cfg)
if not profile:
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP eply'.format(node)})
return
bootfile = 'http://{0}/confluent-public/os/{1}/boot.ipxe'.format(myipn, profile).encode('utf8')
else:
bootfile = b'confluent/aarch64/ipxe.efi'
repview[108:108 + len(bootfile)] = bootfile
myip = myipn
myipn = socket.inet_aton(myipn)
@@ -316,7 +316,7 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
if not mac:
continue
_process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
for mac in newmacs:
for mac in list(newmacs):
thehandler = machandlers.get(mac, None)
if thehandler:
thehandler(peerbymacaddress[mac])
+6 -2
View File
@@ -386,14 +386,18 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
f['time'] in t_list[:-(self.backupCount - 1)]]
return result
dirContents = {}
def initSizeRollingCount(self):
"""
Init the max number of log files for current time.
"""
dirName, baseName = os.path.split(self.textpath)
prefix = baseName + "."
filePaths = glob.glob(os.path.join(dirName, "%s*" % prefix))
fileNames = [os.path.split(f)[1] for f in filePaths]
if dirName not in self.dirContents or self.dirContents[dirName][1] < time.time():
self.dirContents[dirName] = (os.listdir(dirName), time.time() + 5)
matchexp = re.compile(f'^{prefix}\.\d+$')
fileNames = [f for f in self.dirContents[dirName][0]
if matchexp.match(f)]
plen = len(prefix)
for fileName in fileNames:
suffix = fileName[plen:]
+4
View File
@@ -94,6 +94,7 @@ def _daemonize():
def _redirectoutput():
os.umask(63)
configmanager.set_global('logdirectory', _get_logdirectory())
sys.stdout = log.Logger('stdout', buffered=False)
sys.stderr = log.Logger('stderr', buffered=False)
@@ -340,3 +341,6 @@ def _get_connector_config(session):
host = conf.get_option(session, 'bindhost')
port = conf.get_int_option(session, 'bindport')
return (host, port)
def _get_logdirectory():
return conf.get_option('globals', 'logdirectory')
+24 -3
View File
@@ -517,6 +517,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
path[:4] == ['configuration', 'management_controller', 'alerts',
'destinations'] and operation != 'retrieve'):
return InputAlertDestination(path, nodes, inputdata, multinode)
elif len(path) == 3 and path[:3] == ['configuration', 'management_controller', 'certificate_authorities'] and operation not in ('retrieve', 'delete'):
return InputCertificateAuthority(path, nodes, inputdata)
elif path == ['identify'] and operation != 'retrieve':
return InputIdentifyMessage(path, nodes, inputdata)
elif path == ['events', 'hardware', 'decode']:
@@ -955,6 +957,16 @@ class ConfluentInputMessage(ConfluentMessage):
return key in self.valid_values
class InputCertificateAuthority(ConfluentInputMessage):
keyname = 'pem'
# anything is valid, since it is a blob of text
def get_pem(self, node):
return self.inputbynode[node]
def is_valid_key(self, key):
return key.strip().startswith('-----BEGIN') and '-----END' in key
class InputIdentImage(ConfluentInputMessage):
keyname = 'ident_image'
valid_values = ['create']
@@ -1148,6 +1160,9 @@ class InputNetworkConfiguration(ConfluentInputMessage):
if 'ipv4_gateway' not in inputdata:
inputdata['ipv4_gateway'] = None
if 'vlan_id' not in inputdata:
inputdata['vlan_id'] = None
if 'ipv4_configuration' in inputdata and inputdata['ipv4_configuration']:
if inputdata['ipv4_configuration'].lower() not in ['dhcp','static']:
raise exc.InvalidArgumentException(
@@ -1342,6 +1357,11 @@ class ReseatResult(ConfluentChoiceMessage):
keyname = 'reseat'
class CertificateAuthority(ConfluentMessage):
def __init__(self, node, pem, subject, san):
self.myargs = (node, pem, subject, san)
self.kvpairs = {node: {'pem': {'value': pem}, 'subject': {'value': subject}, 'san': {'value': san}}}
class PowerState(ConfluentChoiceMessage):
valid_values = set([
'on',
@@ -1736,8 +1756,8 @@ class NetworkConfiguration(ConfluentMessage):
desc = 'Network configuration'
def __init__(self, name=None, ipv4addr=None, ipv4gateway=None,
ipv4cfgmethod=None, hwaddr=None, staticv6addrs=(), staticv6gateway=None):
self.myargs = (name, ipv4addr, ipv4gateway, ipv4cfgmethod, hwaddr)
ipv4cfgmethod=None, hwaddr=None, staticv6addrs=(), staticv6gateway=None, vlan_id=None):
self.myargs = (name, ipv4addr, ipv4gateway, ipv4cfgmethod, hwaddr, staticv6addrs, staticv6gateway, vlan_id)
self.notnode = name is None
self.stripped = False
v6addrs = ','.join(staticv6addrs)
@@ -1748,7 +1768,8 @@ class NetworkConfiguration(ConfluentMessage):
'ipv4_configuration': {'value': ipv4cfgmethod},
'hw_addr': {'value': hwaddr},
'static_v6_addresses': {'value': v6addrs},
'static_v6_gateway': {'value': staticv6gateway}
'static_v6_gateway': {'value': staticv6gateway},
'vlan_id': {'value': vlan_id}
}
if self.notnode:
self.kvpairs = kvpairs
+16 -9
View File
@@ -243,6 +243,9 @@ class NetManager(object):
vlanid = attribs.get('vlan_id', None)
if vlanid:
myattribs['vlan_id'] = vlanid
mtuinfo = attribs.get('mtu', None)
if mtuinfo:
myattribs['mtu'] = int(mtuinfo)
teammod = attribs.get('team_mode', None)
if teammod:
myattribs['team_mode'] = teammod
@@ -497,6 +500,8 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
#TODO(jjohnson2): ip address, prefix length, mac address,
# join a bond/bridge, vlan configs, etc.
# also other nic criteria, physical location, driver and index...
if not onlyfamily:
onlyfamily = 0
clientfam = None
clientipn = None
serverfam = None
@@ -527,11 +532,13 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
bmc6 = None
if bmc:
try:
bmc4 = socket.getaddrinfo(bmc, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
if onlyfamily in (0, socket.AF_INET):
bmc4 = socket.getaddrinfo(bmc, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
except Exception:
pass
try:
bmc6 = socket.getaddrinfo(bmc, 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][0]
if onlyfamily in (0, socket.AF_INET6):
bmc6 = socket.getaddrinfo(bmc, 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][0]
except Exception:
pass
cfgbyname = {}
@@ -555,8 +562,6 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
'ipv6_method': None,
}
myaddrs = []
if onlyfamily is None:
onlyfamily = 0
if ifidx is not None:
dhcprequested = False
myaddrs = get_my_addresses(ifidx, family=onlyfamily)
@@ -591,13 +596,15 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
ipbynodename = None
ip6bynodename = None
try:
for addr in socket.getaddrinfo(node, 0, socket.AF_INET, socket.SOCK_DGRAM):
ipbynodename = addr[-1][0]
if onlyfamily in (socket.AF_INET, 0):
for addr in socket.getaddrinfo(node, 0, socket.AF_INET, socket.SOCK_DGRAM):
ipbynodename = addr[-1][0]
except socket.gaierror:
pass
try:
for addr in socket.getaddrinfo(node, 0, socket.AF_INET6, socket.SOCK_DGRAM):
ip6bynodename = addr[-1][0]
if onlyfamily in (socket.AF_INET6, 0):
for addr in socket.getaddrinfo(node, 0, socket.AF_INET6, socket.SOCK_DGRAM):
ip6bynodename = addr[-1][0]
except socket.gaierror:
pass
if myaddrs:
@@ -753,7 +760,7 @@ def get_addresses_by_serverip(serverip):
elif ':' in serverip:
fam = socket.AF_INET6
else:
raise ValueError('"{0}" is not a valid ip argument')
raise ValueError('"{0}" is not a valid ip argument'.format(serverip))
ipbytes = socket.inet_pton(fam, serverip)
if ipbytes[:8] == b'\xfe\x80\x00\x00\x00\x00\x00\x00':
myaddrs = get_my_addresses(matchlla=ipbytes)
@@ -535,7 +535,10 @@ def _full_updatemacmap(configmanager):
if incollective:
candmgrs = cfg.get('collective.managercandidates', {}).get('value', None)
if candmgrs:
candmgrs = noderange.NodeRange(candmgrs, configmanager).nodes
try:
candmgrs = noderange.NodeRange(candmgrs, configmanager).nodes
except Exception:
candmgrs = noderange.NodeRange(candmgrs).nodes
if mycollectivename not in candmgrs:
# do not think about trying to find nodes that we aren't possibly
# supposed to be a manager for in a collective
@@ -29,7 +29,10 @@ def get_switchcreds(configmanager, switches):
continue
candmgrs = switchcfg.get(switch, {}).get('collective.managercandidates', {}).get('value', None)
if candmgrs:
candmgrs = noderange.NodeRange(candmgrs, configmanager).nodes
try:
candmgrs = noderange.NodeRange(candmgrs, configmanager).nodes
except Exception:
candmgrs = noderange.NodeRange(candmgrs).nodes
if collective.get_myname() not in candmgrs:
continue
switchparms = switchcfg.get(switch, {})
@@ -81,4 +84,4 @@ def get_portnamemap(conn):
ifidx, ifname = vb
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
return ifnamemap
return ifnamemap
+231 -14
View File
@@ -9,6 +9,11 @@ logging.getLogger('libarchive').addHandler(logging.NullHandler())
import libarchive
import hashlib
import os
try:
from io import BytesIO
import pycdlib
except ImportError:
pycdlib = None
import shutil
import sys
import time
@@ -24,12 +29,14 @@ import confluent.messages as msg
COPY = 1
EXTRACT = 2
EXTRACTUDF = 4
READFILES = set([
'.disk/info',
'media.1/products',
'media.2/products',
'.DISCINFO',
'.discinfo',
'ISOLINUX.CFG',
'zipl.prm',
'sources/idwbinfo.txt',
])
@@ -65,7 +72,7 @@ def symlink(src, targ):
raise
def update_boot(profilename):
def update_boot(profilename, initialimport=False):
if profilename.startswith('/var/lib/confluent/public'):
profiledir = profilename
else:
@@ -82,6 +89,21 @@ def update_boot(profilename):
update_boot_linux(profiledir, profile, label)
elif ostype == 'esxi':
update_boot_esxi(profiledir, profile, label)
elif ostype == 'windows':
update_boot_windows(profiledir, profile, label, initialimport)
def update_boot_windows(profiledir, profile, label, initialimport):
profname = os.path.basename(profiledir)
try:
subprocess.check_call(
['/usr/bin/genisoimage', '-o',
'{0}/boot.iso'.format(profiledir), '-udf', '-b', 'dvd/etfsboot.com',
'-no-emul-boot', '-eltorito-alt-boot', '-eltorito-boot',
'dvd/efisys_noprompt.bin', '{0}/boot'.format(profiledir)], preexec_fn=relax_umask)
except Exception:
if initialimport:
return
raise
def update_boot_esxi(profiledir, profile, label):
profname = os.path.basename(profiledir)
@@ -188,7 +210,7 @@ def update_boot_linux(profiledir, profile, label):
needefi = True
lincmd = 'linuxefi' if needefi else 'linux'
initrdcmd = 'initrdefi' if needefi else 'initrd'
grubcfg = "set timeout=5\nmenuentry '"
grubcfg = "set timeout=0\nmenuentry '"
grubcfg += label
grubcfg += "' {\n " + lincmd + " /kernel " + kernelargs + "\n"
initrds = []
@@ -244,8 +266,13 @@ def extract_entries(entries, flags=0, callback=None, totalsize=None, extractlist
for entry in entries:
if str(entry).endswith('TRANS.TBL'):
continue
if extractlist and str(entry).lower() not in extractlist:
continue
if extractlist:
normname = str(entry).lower()
for extent in extractlist:
if fnmatch(normname, extent):
break
else:
continue
write_header(write_p, entry._entry_p)
read_p = entry._archive_p
while 1:
@@ -268,8 +295,33 @@ def extract_entries(entries, flags=0, callback=None, totalsize=None, extractlist
return float(sizedone) / float(totalsize)
def extract_file(archfile, flags=0, callback=lambda x: None, imginfo=(), extractlist=None):
def extract_udf(archfile, callback=lambda x: None):
"""Extracts a UDF archive from a file into the current directory."""
dfd = os.dup(archfile.fileno())
os.lseek(dfd, 0, 0)
fp = os.fdopen(dfd, 'rb')
udf = pycdlib.PyCdlib()
udf.open_fp(fp)
for dirent in udf.walk(udf_path='/'):
for filent in dirent[2]:
currfile = os.path.join(dirent[0], filent)
relfile = currfile
if currfile[0] == '/':
relfile = currfile[1:]
targfile = os.path.join('.', relfile)
if os.path.exists(targfile):
os.unlink(targfile)
os.makedirs(os.path.dirname(targfile), exist_ok=True)
udf.get_file_from_iso(targfile, udf_path=currfile)
udf.close()
fp.close()
return True
def extract_file(archfile, flags=0, callback=lambda x: None, imginfo=(), extractlist=None, method=EXTRACT):
"""Extracts an archive from a file into the current directory."""
if EXTRACTUDF & method:
return extract_udf(archfile, callback)
totalsize = 0
for img in imginfo:
if not imginfo[img]:
@@ -287,6 +339,16 @@ def extract_file(archfile, flags=0, callback=lambda x: None, imginfo=(), extract
return pctdone
def check_openeuler(isoinfo):
for entry in isoinfo[0]:
if 'openEuler-release-24.03' in entry:
ver = entry.split('-')[2]
arch = entry.split('.')[-2]
cat = 'el9'
break
else:
return None
return {'name': 'openeuler-{0}-{1}'.format(ver, arch), 'method': EXTRACT, 'category': cat}
def check_rocky(isoinfo):
ver = None
arch = None
@@ -327,6 +389,8 @@ def check_fedora(isoinfo):
prodlines = prodinfo.split(b'\n')
if len(prodlines) < 3:
return None
if not prodlines[1].split():
return None
prod = prodlines[1].split()[0]
if prod != b'Fedora':
return None
@@ -437,9 +501,24 @@ def check_esxi(isoinfo):
_, version = line.split(b' ', 1)
if not isinstance(version, str):
version = version.decode('utf8')
edition = ''
if isesxi and version:
if 'ISOLINUX.CFG' in isoinfo[1]:
for line in isoinfo[1]['ISOLINUX.CFG'].split(b'\n'):
if line.startswith(b'MENU TITLE'):
words = line.split()
if len(words) > 2:
edition = words[2].decode('utf8')
break
if edition:
for vnd in ('LNV', 'LVO', 'LVN'):
if edition.startswith(vnd):
edition = '_' + edition.split('-', 1)[1].strip()
break
else:
edition = ''
return {
'name': 'esxi-{0}'.format(version),
'name': 'esxi-{0}{1}'.format(version, edition),
'method': EXTRACT,
'category': 'esxi{0}'.format(version.split('.', 1)[0])
}
@@ -467,10 +546,15 @@ def check_debian(isoinfo):
raise Exception("Unsupported debian architecture {}".format(arch))
arch = 'x86_64'
name = 'debian-{0}-{1}'.format(version, arch)
major = int(major)
if major > 12:
category = 'debian13'
else:
category = 'debian'
return {
'name': name,
'method': EXTRACT,
'category': 'debian',
'category': category,
}
@@ -505,11 +589,11 @@ def check_ubuntu(isoinfo):
'method': EXTRACT,
'category': 'ubuntu{0}'.format(major)}
elif 'efi/boot/bootaa64.efi' in isoinfo[0]:
exlist = ['casper/vmlinuz', 'casper/initrd',
exlist = ['casper/*vmlinuz', 'casper/*initrd',
'efi/boot/bootaa64.efi', 'efi/boot/grubaa64.efi'
]
else:
exlist = ['casper/vmlinuz', 'casper/initrd',
exlist = ['casper/*vmlinuz', 'casper/*initrd',
'efi/boot/bootx64.efi', 'efi/boot/grubx64.efi'
]
return {'name': 'ubuntu-{0}-{1}'.format(ver, arch),
@@ -587,6 +671,33 @@ def fixup_coreos(targpath):
bootimg.write(b'\x01')
def is_windows_executable(filename):
with open(filename, 'rb') as f:
header = f.read(2)
if header == b'MZ':
# seems to be DOS, but let's also make sure it is PE32
f.seek(0x3c)
pe_offset = f.read(4)
offset = int.from_bytes(pe_offset, byteorder='little')
f.seek(offset)
pe_header = f.read(4)
if pe_header == b'PE\x00\x00':
return True
return False
def fixup_windows(targpath):
# windows needs the executable file to be executable, which samba
# manifests as following the executable bit
for root, _, files in os.walk(targpath):
for fname in files:
for ext in ('.exe', '.dll', '.sys', '.mui', '.efi'):
if fname.endswith(ext):
fpath = os.path.join(root, fname)
if is_windows_executable(fpath):
st = os.stat(fpath)
os.chmod(fpath, st.st_mode | 0o111)
def check_coreos(isoinfo):
arch = 'x86_64' # TODO: would check magic of vmlinuz to see which arch
if 'zipl.prm' in isoinfo[1]:
@@ -604,7 +715,28 @@ def check_coreos(isoinfo):
'method': EXTRACT, 'category': 'coreos'}
def check_windows(isoinfo):
idwbinfo = isoinfo[1].get('sources/idwbinfo.txt', b'')
idwbinfo = idwbinfo.decode()
idwbinfo = idwbinfo.split('\n')
version = ''
for line in idwbinfo:
if 'BuildBranch=' in line:
branch = line.strip().split('=')[1]
if branch == 'rs5_release':
version = '2019'
elif branch == 'fe_release':
version = '2022'
elif branch == 'ge_release':
version = '2025'
category = f'windows{version}'
if version:
defprofile = '/opt/confluent/lib/osdeploy/{0}'.format(category)
if not os.path.exists(defprofile):
return None
return {'name': 'windows-{0}-x86_64'.format(version), 'method': EXTRACTUDF, 'category': category}
return None
def check_rhel(isoinfo):
ver = None
arch = None
@@ -657,27 +789,106 @@ def check_rhel(isoinfo):
major = ver.split('.', 1)[0]
return {'name': 'rhel-{0}-{1}'.format(ver, arch), 'method': EXTRACT, 'category': 'el{0}'.format(major)}
def fingerprint_initramfs(archive):
curroffset = archive.tell()
dfd = os.dup(archive.fileno())
os.lseek(dfd, curroffset, 0)
try:
with libarchive.fd_reader(dfd) as reader:
for ent in reader:
if str(ent) == 'usr/lib/initrd-release':
osrelcontents = b''
for block in ent.get_blocks():
osrelcontents += bytes(block)
osrelease = osrelcontents.decode('utf-8').strip()
osid = ''
osver = ''
for line in osrelease.split('\n'):
if line.startswith('ID='):
osid = line.split('=', 1)[1].strip().strip('"')
if line.startswith('VERSION_ID='):
osver = line.split('=', 1)[1].strip().strip('"')
if osid and osver:
return (osid, osver)
finally:
os.close(dfd)
return None
def scan_iso(archive):
scanudf = False
filesizes = {}
filecontents = {}
dfd = os.dup(archive.fileno())
os.lseek(dfd, 0, 0)
try:
with libarchive.fd_reader(dfd) as reader:
with libarchive.fd_reader(dfd, ) as reader:
for ent in reader:
if str(ent).endswith('TRANS.TBL'):
continue
eventlet.sleep(0)
filesizes[str(ent)] = ent.size
if str(ent) == 'README.TXT':
readmecontents = b''
for block in ent.get_blocks():
readmecontents += bytes(block)
if b'ISO-13346' in readmecontents:
scanudf = True
if str(ent) in READFILES:
filecontents[str(ent)] = b''
for block in ent.get_blocks():
filecontents[str(ent)] += bytes(block)
if scanudf:
ndfd = os.dup(archive.fileno())
os.lseek(ndfd, 0, 0)
return scan_udf(ndfd)
finally:
os.close(dfd)
return filesizes, filecontents
def scan_udf(dfd):
fp = os.fdopen(dfd, 'rb')
iso = pycdlib.PyCdlib()
iso.open_fp(fp)
imginfo = {}
try:
extracted = BytesIO()
iso.get_file_from_iso_fp(extracted, udf_path='/sources/idwbinfo.txt')
idwbinfo = extracted.getvalue()
imginfo = {'sources/idwbinfo.txt': idwbinfo}
except Exception:
pass
finally:
iso.close()
fp.close()
return {}, imginfo
def parse_bfb(archive):
currtype = 0
# we want to find the initramfs image (id 63) and dig around to see the OS version
while currtype != 63:
currhdr = archive.read(24)
if currhdr[:5] != b'Bf\x02\x13!':
return None
currsize = int.from_bytes(currhdr[8:12], byteorder='little')
# currsize needs to be rounded up to nearest 8 byte boundary
if currsize % 8:
currsize += 8 - (currsize % 8)
currtype = currhdr[7]
if currtype == 63:
ossig = fingerprint_initramfs(archive)
if ossig:
osinfo = {
'name': f'bluefield_{ossig[0]}-{ossig[1]}-aarch64',
'method': COPY,
'category': f'bluefield_{ossig[0]}{ossig[1]}'
}
if os.path.exists(f'/opt/confluent/lib/osdeploy/{osinfo["category"]}'):
return osinfo
else:
archive.seek(currsize, os.SEEK_CUR)
return None
def fingerprint(archive):
archive.seek(0)
@@ -693,6 +904,12 @@ def fingerprint(archive):
if name:
return name, isoinfo[0], fun.replace('check_', '')
return None
elif header[:4] == b'Bf\x02\x13':
# BFB payload for Bluefield
archive.seek(0)
imginfo = parse_bfb(archive)
if imginfo:
return imginfo, None, 'bluefield'
else:
sum = hashlib.sha256(header)
if sum.digest() in HEADERSUMS:
@@ -736,9 +953,9 @@ def import_image(filename, callback, backend=False, mfd=None, custtargpath=None,
print('Importing OS to ' + targpath + ':')
callback({'progress': 0.0})
pct = 0.0
if EXTRACT & identity['method']:
if EXTRACT & identity['method'] or EXTRACTUDF & identity['method']:
pct = extract_file(archive, callback=callback, imginfo=imginfo,
extractlist=identity.get('extractlist', None))
extractlist=identity.get('extractlist', None), method=identity['method'])
if COPY & identity['method']:
basename = identity.get('copyto', os.path.basename(filename))
targiso = os.path.join(targpath, basename)
@@ -935,7 +1152,7 @@ def generate_stock_profiles(defprofile, distpath, targpath, osname,
subprocess.check_call(
['sh', '{0}/initprofile.sh'.format(dirname),
targpath, dirname])
bootupdates.append(eventlet.spawn(update_boot, dirname))
bootupdates.append(eventlet.spawn(update_boot, dirname, True))
profilelist.append(profname)
for upd in bootupdates:
upd.wait()
@@ -319,6 +319,12 @@ def update_nodes(nodes, element, configmanager, inputdata):
if fnmatch(candattrib, attrib):
clearattribs.append(candattrib)
foundattrib = True
currnodeattrs = configmanager.get_node_attributes(node, attrib)
for matchattrib in currnodeattrs.get(node, {}):
if matchattrib != attrib:
continue
clearattribs.append(matchattrib)
foundattrib = True
if not foundattrib:
raise exc.InvalidArgumentException("No attribute matches '" + attrib + "' (try wildcard if trying to clear a group)")
elif '*' in attrib:
@@ -21,6 +21,7 @@
import confluent.messages as msg
import confluent.netutil as netutil
import eventlet.green.subprocess as subprocess
import confluent.config.configmanager as cfm
import os
import shutil
import tempfile
@@ -51,6 +52,7 @@ def create_ident_image(node, configmanager):
# It would be a reasonable enhancement to list all collective server addresses
# restricted by 'managercandidates'
ident['deploy_servers'] = []
ident['confluent_uuid'] = cfm.get_global('confluent_uuid')
for myaddr in netutil.get_my_addresses():
myaddr = socket.inet_ntop(myaddr[0], myaddr[1])
ident['deploy_servers'].append(myaddr)
@@ -60,6 +62,9 @@ def create_ident_image(node, configmanager):
with open(os.path.join(tmpd, 'cnflnt.jsn'), 'w') as jsonout:
json.dump(ident, jsonout)
shutil.copytree('/var/lib/confluent/public/site/tls', os.path.join(tmpd, 'tls'))
mkdirp('/var/lib/confluent/private/identity_files/')
shutil.copy(os.path.join(tmpd, 'cnflnt.yml'), '/var/lib/confluent/private/identity_files/{0}.yml'.format(node))
shutil.copy(os.path.join(tmpd, 'cnflnt.jsn'), '/var/lib/confluent/private/identity_files/{0}.json'.format(node))
mkdirp('/var/lib/confluent/private/identity_images/')
imgname = '/var/lib/confluent/private/identity_images/{0}.img'.format(node)
if os.path.exists(imgname):
@@ -773,6 +773,7 @@ class IpmiHandler(object):
hwaddr=lancfg['mac_address'],
staticv6addrs=v6cfg.get('static_addrs', ''),
staticv6gateway=v6cfg.get('static_gateway', ''),
vlan_id=lancfg.get('vlan_id', None)
))
elif self.op == 'update':
config = self.inputdata.netconfig(self.node)
@@ -780,7 +781,8 @@ class IpmiHandler(object):
self.ipmicmd.set_net_configuration(
ipv4_address=config['ipv4_address'],
ipv4_configuration=config['ipv4_configuration'],
ipv4_gateway=config['ipv4_gateway'])
ipv4_gateway=config['ipv4_gateway'],
vlan_id=config.get('vlan_id', None))
v6addrs = config.get('static_v6_addresses', None)
if v6addrs is not None:
v6addrs = v6addrs.split(',')
@@ -973,12 +975,12 @@ class IpmiHandler(object):
for id, data in self.ipmicmd.get_firmware():
self.output.put(msg.ChildCollection(simplify_name(id)))
def read_firmware(self, component):
def read_firmware(self, component, category):
items = []
errorneeded = False
try:
complist = () if component == 'all' else (component,)
for id, data in self.ipmicmd.get_firmware(complist):
for id, data in self.ipmicmd.get_firmware(complist, category):
if (component in ('core', 'all') or
component == simplify_name(id) or
match_aliases(component, simplify_name(id))):
@@ -1014,7 +1016,7 @@ class IpmiHandler(object):
if len(self.element) == 3:
return self.list_firmware()
elif len(self.element) == 4:
return self.read_firmware(self.element[-1])
return self.read_firmware(self.element[-1], self.element[-2])
elif self.element[1] == 'hardware':
if len(self.element) == 3: # list things in inventory
return self.list_inventory()
@@ -42,6 +42,8 @@ def retrieve(nodes, element, configmanager, inputdata):
inletname = element[-1]
outlets = get_outlets(nodes, emebs, inletname)
for node in outlets:
if not outlets[node]:
yield msg.ConfluentTargetNotFound(node, 'No matching inlets defined for node in "power.*" attributes')
for pgroup in outlets[node]:
pdu = outlets[node][pgroup]['pdu']
outlet = outlets[node][pgroup]['outlet']
@@ -109,6 +111,8 @@ def update(nodes, element, configmanager, inputdata):
gp = greenpool.GreenPool(64)
outlets = get_outlets(nodes, emebs, inletname)
for node in outlets:
if not outlets[node]:
yield msg.ConfluentTargetNotFound(node, 'No matching inlets defined for node in "power.*" attributes')
for pgroup in outlets[node]:
pdu = outlets[node][pgroup]['pdu']
outlet = outlets[node][pgroup]['outlet']
@@ -454,8 +454,8 @@ def create(nodes, element, configmanager, inputdata):
yield msg.ChildCollection(url)
return
serialdata = clientsbynode[node].get_vm_serial(node)
return PmxConsole(serialdata, node, configmanager, clientsbynode[node])
yield PmxConsole(serialdata, node, configmanager, clientsbynode[node])
return
if __name__ == '__main__':
@@ -526,6 +526,8 @@ class IpmiHandler(object):
def handle_configuration(self):
if self.element[1:3] == ['management_controller', 'alerts']:
return self.handle_alerts()
elif self.element[1:3] == ['management_controller', 'certificate_authorities']:
return self.handle_cert_authorities()
elif self.element[1:3] == ['management_controller', 'users']:
return self.handle_users()
elif self.element[1:3] == ['management_controller', 'net_interfaces']:
@@ -576,6 +578,28 @@ class IpmiHandler(object):
self.pyghmi_event_to_confluent(event)
self.output.put(msg.EventCollection((event,), name=self.node))
def handle_cert_authorities(self):
if len(self.element) == 3:
if self.op == 'read':
for cert in self.ipmicmd.get_trusted_cas():
self.output.put(msg.ChildCollection(cert['id']))
elif self.op == 'update':
cert = self.inputdata.get_pem(self.node)
self.ipmicmd.add_trusted_ca(cert)
elif len(self.element) == 4:
certid = self.element[-1]
if self.op == 'read':
for certdata in self.ipmicmd.get_trusted_cas():
if certdata['id'] == certid:
self.output.put(msg.CertificateAuthority(
pem=certdata['pem'],
node=self.node,
subject=certdata['subject'],
san=certdata.get('san', None)))
elif self.op == 'delete':
self.ipmicmd.del_trusted_ca(certid)
return
def handle_alerts(self):
if self.element[3] == 'destinations':
if len(self.element) == 4:
@@ -632,7 +656,8 @@ class IpmiHandler(object):
ipv4cfgmethod=lancfg['ipv4_configuration'],
hwaddr=lancfg['mac_address'],
staticv6addrs=v6cfg['static_addrs'],
staticv6gateway=v6cfg['static_gateway']
staticv6gateway=v6cfg.get('static_gateway', None),
vlan_id=lancfg.get('vlan_id', None)
))
elif self.op == 'update':
config = self.inputdata.netconfig(self.node)
@@ -640,7 +665,8 @@ class IpmiHandler(object):
self.ipmicmd.set_net_configuration(
ipv4_address=config['ipv4_address'],
ipv4_configuration=config['ipv4_configuration'],
ipv4_gateway=config['ipv4_gateway'])
ipv4_gateway=config['ipv4_gateway'],
vlan_id=config.get('vlan_id', None))
v6addrs = config.get('static_v6_addresses', None)
if v6addrs is not None:
v6addrs = v6addrs.split(',')
@@ -830,12 +856,12 @@ class IpmiHandler(object):
for id, data in self.ipmicmd.get_firmware():
self.output.put(msg.ChildCollection(simplify_name(id)))
def read_firmware(self, component):
def read_firmware(self, component, category):
items = []
errorneeded = False
try:
complist = () if component == 'all' else (component,)
for id, data in self.ipmicmd.get_firmware(complist):
for id, data in self.ipmicmd.get_firmware(complist, category):
if (component in ('core', 'all') or
component == simplify_name(id) or
match_aliases(component, simplify_name(id))):
@@ -871,7 +897,7 @@ class IpmiHandler(object):
if len(self.element) == 3:
return self.list_firmware()
elif len(self.element) == 4:
return self.read_firmware(self.element[-1])
return self.read_firmware(self.element[-1], self.element[-2])
elif self.element[1] == 'hardware':
if len(self.element) == 3: # list things in inventory
return self.list_inventory()
@@ -372,7 +372,8 @@ def create(nodes, element, configmanager, inputdata):
clientsbynode = prep_vcsa_clients(nodes, configmanager)
for node in nodes:
serialdata = clientsbynode[node].get_vm_serial(node)
return VmConsole(serialdata['server'], serialdata['port'], serialdata['tls'], configmanager)
yield VmConsole(serialdata['server'], serialdata['port'], serialdata['tls'], configmanager)
return
+37 -30
View File
@@ -48,6 +48,7 @@ class PlayRunner(object):
return avail
def dump_text(self):
stderr = self.stderr
retinfo = self.dump_dict()
textout = ''
for result in retinfo['results']:
@@ -64,9 +65,9 @@ class PlayRunner(object):
else:
textout += result['state'] + '\n'
textout += '\n'
if self.stderr:
textout += "ERRORS **********************************\n"
textout += self.stderr
if stderr:
textout += "ERRORS **********************************\n"
textout += stderr
return textout
def dump_json(self):
@@ -80,32 +81,34 @@ class PlayRunner(object):
def _really_run_playbooks(self):
global anspypath
mypath = anspypath
if not mypath:
ansloc = shutil.which('ansible')
if ansloc:
with open(ansloc, 'r') as onsop:
shebang = onsop.readline()
anspypath = shebang.strip().replace('#!', '')
mypath = anspypath
if not mypath:
mypath = sys.executable
with open(os.devnull, 'w+') as devnull:
targnodes = ','.join(self.nodes)
for playfilename in self.playfiles:
worker = subprocess.Popen(
[mypath, __file__, targnodes, playfilename],
stdin=devnull, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stder = worker.communicate()
self.stderr += stder.decode('utf8')
current = memoryview(stdout)
while len(current):
sz = struct.unpack('=q', current[:8])[0]
result = msgpack.unpackb(current[8:8+sz], raw=False)
self.results.append(result)
current = current[8+sz:]
self.complete = True
try:
mypath = anspypath
if not mypath:
ansloc = shutil.which('ansible')
if ansloc:
with open(ansloc, 'r') as onsop:
shebang = onsop.readline()
anspypath = shebang.strip().replace('#!', '')
mypath = anspypath
if not mypath:
mypath = sys.executable
with open(os.devnull, 'w+') as devnull:
targnodes = ','.join(self.nodes)
for playfilename in self.playfiles:
worker = subprocess.Popen(
[mypath, __file__, targnodes, playfilename],
stdin=devnull, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stder = worker.communicate()
self.stderr += stder.decode('utf8')
current = memoryview(stdout)
while len(current):
sz = struct.unpack('=q', current[:8])[0]
result = msgpack.unpackb(current[8:8+sz], raw=False)
self.results.append(result)
current = current[8+sz:]
finally:
self.complete = True
def run_playbooks(playfiles, nodes):
@@ -143,6 +146,7 @@ if __name__ == '__main__':
from ansible import context
from ansible.module_utils.common.collections import ImmutableDict
from ansible.plugins.callback import CallbackBase
import ansible.plugins.loader
import yaml
class ResultsCollector(CallbackBase):
@@ -161,7 +165,10 @@ if __name__ == '__main__':
become=None, become_method=None, become_user=None, check=False,
diff=False, verbosity=0, remote_user='root')
try:
ansible.plugins.loader.init_plugin_loader()
except AttributeError:
pass
loader = DataLoader()
invman = None
if os.path.exists('/etc/ansible/hosts'):
@@ -261,6 +261,10 @@ def handle_request(env, start_response):
res['bmcvlan'] = vlan
bmcaddr = hmattr.get('hardwaremanagement.manager', {}).get('value',
None)
if not bmcaddr:
start_response('500 Internal Server Error', [])
yield 'Missing value in hardwaremanagement.manager'
return
bmcaddr = bmcaddr.split('/', 1)[0]
bmcaddr = socket.getaddrinfo(bmcaddr, 0)[0]
bmcaddr = bmcaddr[-1][0]
@@ -462,6 +466,9 @@ def handle_request(env, start_response):
statusstr = update.get('state', None)
statusdetail = update.get('state_detail', None)
didstateupdate = False
if statusstr or 'status' in update:
cfg.set_node_attributes({nodename: {
'deployment.client_ip': {'value': clientip}}})
if statusstr:
cfg.set_node_attributes({nodename: {'deployment.state': statusstr}})
didstateupdate = True
+18 -2
View File
@@ -5,9 +5,10 @@ import confluent.config.configmanager as cfm
import confluent.collective.manager as collective
import confluent.util as util
import eventlet.green.subprocess as subprocess
import eventlet.green.socket as socket
import eventlet
import glob
import os
import eventlet.green.os as os
import shutil
import tempfile
@@ -34,6 +35,7 @@ def normalize_uid():
return curruid
agent_starting = False
def assure_agent():
global agent_starting
global agent_pid
@@ -54,7 +56,7 @@ def assure_agent():
k = k.decode('utf8')
v = v.decode('utf8')
if k == 'SSH_AGENT_PID':
agent_pid = v
agent_pid = int(v)
os.environ[k] = v
finally:
agent_starting = False
@@ -113,9 +115,23 @@ def initialize_ca():
adding_key = False
def prep_ssh_key(keyname):
global adding_key
global agent_pid
while adding_key:
eventlet.sleep(0.1)
adding_key = True
if agent_pid:
if os.path.exists(os.environ['SSH_AUTH_SOCK']):
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(os.environ['SSH_AUTH_SOCK'])
except Exception:
os.unlink(os.environ['SSH_AUTH_SOCK'])
os.rmdir(os.path.dirname(os.environ['SSH_AUTH_SOCK']))
finally:
sock.close()
if not os.path.exists(os.environ['SSH_AUTH_SOCK']):
agent_pid = None
ready_keys.clear()
if keyname in ready_keys:
adding_key = False
return
+1 -1
View File
@@ -26,7 +26,7 @@ dracut_install poweroff date /etc/nsswitch.conf /etc/services /etc/protocols
dracut_install /usr/share/terminfo/x/xterm /usr/share/terminfo/l/linux /usr/share/terminfo/v/vt100 /usr/share/terminfo/x/xterm-color /usr/share/terminfo/s/screen /usr/share/terminfo/x/xterm-256color /usr/share/terminfo/p/putty-256color /usr/share/terminfo/p/putty /usr/share/terminfo/d/dumb
dracut_install chmod whoami head tail basename ping tr /usr/share/hwdata/usb.ids
if [ -e /etc/redhat-release ]; then
dracut_install /etc/redhat_release
dracut_install /etc/redhat-release
fi
dracut_install dmidecode /usr/$IMPLIBDIR/libstdc++.so.6
dracut_install ps free find

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