mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9ba385996 | |||
| 29a9d417d6 | |||
| 7347125b6f | |||
| 66347a937e | |||
| ee01dfd05e | |||
| 611e1f5bc3 | |||
| 7622145fea | |||
| 678bd53857 | |||
| 3295778566 | |||
| b3acf011bd | |||
| dc4cafc29f | |||
| 3340585fb4 | |||
| 4456767122 | |||
| 556bb1d0ff | |||
| a201e9886e | |||
| f82993efe7 | |||
| f8366a50ef | |||
| d369dcac55 | |||
| 3b2d92a219 | |||
| ffacb66c62 | |||
| 2073f421da | |||
| ed2eed66dc | |||
| d8f9b6c8e6 | |||
| d9a18a7bf6 | |||
| 439a930188 | |||
| 00b2afd42b | |||
| 90c2a4cf73 | |||
| c691dc7159 | |||
| a7c188b812 | |||
| 5ba43ecaa0 | |||
| 2f53d3bde6 | |||
| 9fe9e8672a | |||
| 0fe60175f3 | |||
| d411041243 | |||
| cc101d12bc | |||
| 2f08ee81f2 | |||
| 1e9231eea6 | |||
| 24cb05e535 | |||
| 72b95abb4f | |||
| 57a170d0d8 | |||
| daeabc6fe5 | |||
| 28a8f6f0d6 | |||
| 7542897b43 | |||
| c69952265f | |||
| 01cc86fa55 | |||
| d6e3c7e837 | |||
| dcb6aeca65 | |||
| 7bc76b62e6 | |||
| db313628c5 | |||
| f260323d2f | |||
| d60bc7f524 | |||
| ff0d4cdadf | |||
| db6475c4da | |||
| 6d27e8a009 | |||
| f363796439 | |||
| dec118a985 | |||
| 38eb0d7b10 | |||
| ae338daa43 | |||
| 6ad3f0d70c | |||
| c0b9bb3ab1 | |||
| b32755b0d3 | |||
| c54ac530e1 | |||
| 8990622470 | |||
| 93a35d7e77 | |||
| c49b2fd8ab | |||
| 3ce2a5bc26 | |||
| 69d984b9dc | |||
| a123165712 | |||
| b91b10552c | |||
| 779b07d2c2 | |||
| df73c14475 | |||
| f78b301143 | |||
| 9b00fe5521 | |||
| 13a6444541 | |||
| 52db46be93 | |||
| 550dfbf6a0 | |||
| e0951b11a6 | |||
| 1a87701fee | |||
| e185f2224f | |||
| 69beaad3c9 | |||
| 74dda48513 | |||
| 08b2e1d008 | |||
| 582842aec8 | |||
| 63307c331e | |||
| 318608cde3 | |||
| ef7d2414ad | |||
| 722a0b874a | |||
| 1deb76989e | |||
| 480d399f44 | |||
| 07369667f7 | |||
| e1d4b72f32 | |||
| 86783a2f12 | |||
| 99063eb049 | |||
| 0975bd9e62 | |||
| 3058dd4141 | |||
| 21c9158491 | |||
| e6c19388a2 | |||
| 048780e16d | |||
| 61d7a49163 | |||
| f8b8ce3847 | |||
| a0a5887214 | |||
| ccaf22f44f | |||
| 72c4868073 | |||
| afb6356f9d | |||
| 6e6ac67b3d | |||
| 99d10896e8 | |||
| 488f23e3ed | |||
| 6ca62cbb35 | |||
| 45bc9788b4 | |||
| 289c31e7ac | |||
| 1a684f2012 | |||
| a4229fc58d | |||
| 31c1a865dc | |||
| ff84fcf6e9 | |||
| 56dfb6dc6b | |||
| d7577a04a7 | |||
| b72d6c9cfc | |||
| 523c93dfc3 | |||
| c8745292bf | |||
| 75e7b9040b | |||
| 04e983a2d3 | |||
| 2464e0ff4f | |||
| c196bf9d55 | |||
| 12d886a4f6 | |||
| 6a26ece782 | |||
| 3cbac38d57 | |||
| 224f349053 | |||
| 9d361d376d | |||
| ec39de3df0 | |||
| a3b768c70f | |||
| 4f75d4942b | |||
| 4d2f36917c | |||
| a2a50d34d1 | |||
| 041008a524 | |||
| 5923feaa18 | |||
| 73216fc062 | |||
| 100944490c | |||
| 61b07e0af4 | |||
| 53760ab5dd | |||
| d3e7a49f92 | |||
| 1f688ead28 | |||
| d20c5ac6eb | |||
| 4484216198 | |||
| e1efd6a9c5 | |||
| 58d5209595 | |||
| 53c918042a | |||
| 9148a841b5 | |||
| 6ebb6de107 | |||
| d2d77ab1d5 | |||
| 20292cdfd0 | |||
| b07da455c2 | |||
| cc9a81103b | |||
| 21155d2091 | |||
| 6c0d7ea60e | |||
| 174d204607 | |||
| 2826abb7ab | |||
| 5adb5fa780 | |||
| 5de063212f | |||
| 073f6d1389 | |||
| f755ba9f91 | |||
| cf8c01ef13 | |||
| ce8531375a | |||
| 8b12047ae0 | |||
| f0a779764d | |||
| 0ad7e99efe | |||
| 24a76612ae | |||
| 6c9c58f464 | |||
| 3125f4171b | |||
| 762adb882a | |||
| 36687069aa | |||
| 11ff2dabfc | |||
| f9351484a4 | |||
| b22c17208a | |||
| 4982ac1a17 | |||
| a43d7e11e2 | |||
| c5896c056e | |||
| a73dced80b | |||
| b6188683b8 | |||
| 50243b67d5 | |||
| 7cdfcd4913 | |||
| 179ad4e196 | |||
| be2ae57a38 | |||
| f34395648e | |||
| 3f5d96788e | |||
| 17866d7657 | |||
| a1144fd49a | |||
| c472d96406 | |||
| 02791418bc | |||
| 2d29813320 | |||
| a9d15de156 | |||
| a4ba92a2e7 | |||
| 6938bba2d3 | |||
| 871685ea20 | |||
| a480cc73df | |||
| 39eb32df38 | |||
| 3505fe36e6 | |||
| 29accaa494 | |||
| f66093680b | |||
| 97d4015b09 | |||
| 184132c398 | |||
| ac7fdb3ef7 | |||
| d7879bad5b | |||
| 8911193aca | |||
| e7e8daafea | |||
| 3f9a13ed6f | |||
| 500cdf7535 | |||
| 22c8921455 | |||
| ebcf7d7bf8 | |||
| 7a2cb80f6a | |||
| dd2b7be2ca | |||
| 678bd6052a | |||
| cb5fcf077a | |||
| 5f26fb73e6 | |||
| c9ca199b16 | |||
| 8109adaabf | |||
| 29c6ce230f | |||
| 87a6891eff | |||
| a112297e60 | |||
| c567bfbd17 | |||
| 6d2146f252 | |||
| 5045b46014 | |||
| 5905510a32 | |||
| f321f56109 | |||
| 816f3be2ed |
@@ -0,0 +1,8 @@
|
||||
FROM almalinux:10
|
||||
RUN ["yum", "-y","update"]
|
||||
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-XML-LibXML", "pinentry-tty", "rpm-sign", "git", "golang"]
|
||||
ADD rpmmacro /root/.rpmmacros
|
||||
ADD buildpackages.sh /bin/
|
||||
#VOLUME ["/rpms", "/srpms"]
|
||||
CMD ["/bin/bash","/bin/buildpackages.sh"]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
for package in /srpms/*; do
|
||||
rpmbuild --rebuild $package
|
||||
done
|
||||
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
%_gpg_digest_algo sha256
|
||||
%_gpg_name Lenovo Scalable Infrastructure
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
FROM almalinux:8
|
||||
RUN ["yum", "-y","update"]
|
||||
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-Net-DNS", "perl-DB_File", "perl-XML-LibXML", "rpm-sign", "git", "fuse-devel","libcurl-devel"]
|
||||
ADD rpmmacro /root/.rpmmacros
|
||||
ADD buildpackages.sh /bin/
|
||||
#VOLUME ["/rpms", "/srpms"]
|
||||
CMD ["/bin/bash","/bin/buildpackages.sh"]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
for package in /srpms/*; do
|
||||
rpmbuild --rebuild $package
|
||||
done
|
||||
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%_gpg_digest_algo sha256
|
||||
%_gpg_name Lenovo Scalable Infrastructure
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM almalinux:9
|
||||
RUN ["yum", "-y","update"]
|
||||
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-Net-DNS", "perl-DB_File", "perl-XML-LibXML", "pinentry-tty", "rpm-sign", "epel-release", "git"]
|
||||
RUN ["crb", "enable"]
|
||||
RUN ["yum", "-y","install","fuse-devel","libcurl-devel"]
|
||||
ADD rpmmacro /root/.rpmmacros
|
||||
ADD buildpackages.sh /bin/
|
||||
#VOLUME ["/rpms", "/srpms"]
|
||||
CMD ["/bin/bash","/bin/buildpackages.sh"]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
for package in /srpms/*; do
|
||||
rpmbuild --rebuild $package
|
||||
done
|
||||
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%_gpg_digest_algo sha256
|
||||
%_gpg_name Lenovo Scalable Infrastructure
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM ubuntu:noble
|
||||
ADD stdeb.patch /tmp/
|
||||
ADD buildapt.sh /bin/
|
||||
ADD distributions.tmpl /bin/
|
||||
RUN ["apt-get", "update"]
|
||||
RUN ["apt-get", "install", "-y", "reprepro", "python3-stdeb", "gnupg-agent", "devscripts", "debhelper", "libsoap-lite-perl", "libdbi-perl", "quilt", "git", "python3-pyparsing", "python3-dnspython", "python3-eventlet", "python3-netifaces", "python3-paramiko", "dh-python", "libjson-perl", "ronn", "alien", "gcc", "make"]
|
||||
RUN ["mkdir", "-p", "/sources/git/"]
|
||||
RUN ["mkdir", "-p", "/debs/"]
|
||||
RUN ["mkdir", "-p", "/apt/"]
|
||||
RUN ["bash", "-c", "patch -p1 < /tmp/stdeb.patch"]
|
||||
CMD ["/bin/bash", "/bin/buildapt.sh"]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#cp -a /sources/git /tmp
|
||||
for builder in $(find /sources/git -name builddeb); do
|
||||
cd $(dirname $builder)
|
||||
./builddeb /debs/
|
||||
done
|
||||
cp /prebuilt/* /debs/
|
||||
cp /osd/*.deb /debs/
|
||||
mkdir -p /apt/conf/
|
||||
CODENAME=$(grep VERSION_CODENAME= /etc/os-release | sed -e 's/.*=//')
|
||||
if [ -z "$CODENAME" ]; then
|
||||
CODENAME=$(grep VERSION= /etc/os-release | sed -e 's/.*(//' -e 's/).*//')
|
||||
fi
|
||||
if ! grep $CODENAME /apt/conf/distributions; then
|
||||
sed -e s/#CODENAME#/$CODENAME/ /bin/distributions.tmpl >> /apt/conf/distributions
|
||||
fi
|
||||
cd /apt/
|
||||
reprepro includedeb $CODENAME /debs/*.deb
|
||||
for dsc in /debs/*.dsc; do
|
||||
reprepro includedsc $CODENAME $dsc
|
||||
done
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Origin: Lenovo HPC Packages
|
||||
Label: Lenovo HPC Packages
|
||||
Codename: #CODENAME#
|
||||
Architectures: amd64 source
|
||||
Components: main
|
||||
Description: Lenovo HPC Packages
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
diff -urN t/usr/lib/python3/dist-packages/stdeb/cli_runner.py t.patch/usr/lib/python3/dist-packages/stdeb/cli_runner.py
|
||||
--- t/usr/lib/python3/dist-packages/stdeb/cli_runner.py 2024-06-11 18:30:13.930328999 +0000
|
||||
+++ t.patch/usr/lib/python3/dist-packages/stdeb/cli_runner.py 2024-06-11 18:32:05.392731405 +0000
|
||||
@@ -8,7 +8,7 @@
|
||||
from ConfigParser import SafeConfigParser # noqa: F401
|
||||
except ImportError:
|
||||
# python 3.x
|
||||
- from configparser import SafeConfigParser # noqa: F401
|
||||
+ from configparser import ConfigParser # noqa: F401
|
||||
from distutils.util import strtobool
|
||||
from distutils.fancy_getopt import FancyGetopt, translate_longopt
|
||||
from stdeb.util import stdeb_cmdline_opts, stdeb_cmd_bool_opts
|
||||
diff -urN t/usr/lib/python3/dist-packages/stdeb/util.py t.patch/usr/lib/python3/dist-packages/stdeb/util.py
|
||||
--- t/usr/lib/python3/dist-packages/stdeb/util.py 2024-06-11 18:32:53.864776149 +0000
|
||||
+++ t.patch/usr/lib/python3/dist-packages/stdeb/util.py 2024-06-11 18:33:02.063952870 +0000
|
||||
@@ -730,7 +730,7 @@
|
||||
example.
|
||||
"""
|
||||
|
||||
- cfg = ConfigParser.SafeConfigParser()
|
||||
+ cfg = ConfigParser.ConfigParser()
|
||||
cfg.read(cfg_files)
|
||||
if cfg.has_section(module_name):
|
||||
section_items = cfg.items(module_name)
|
||||
@@ -801,7 +801,7 @@
|
||||
if len(cfg_files):
|
||||
check_cfg_files(cfg_files, module_name)
|
||||
|
||||
- cfg = ConfigParser.SafeConfigParser(cfg_defaults)
|
||||
+ cfg = ConfigParser.ConfigParser(cfg_defaults)
|
||||
for cfg_file in cfg_files:
|
||||
with codecs.open(cfg_file, mode='r', encoding='utf-8') as fd:
|
||||
cfg.readfp(fd)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
cd ~/confluent
|
||||
git pull
|
||||
rm ~/rpmbuild/RPMS/noarch/*osdeploy*
|
||||
rm ~/rpmbuild/SRPMS/*osdeploy*
|
||||
sh confluent_osdeploy/buildrpm-aarch64
|
||||
mkdir -p $HOME/el9/
|
||||
mkdir -p $HOME/el10/
|
||||
podman run --rm -it -v $HOME:/build el9build bash /build/confluent/confluent_vtbufferd/buildrpm /build/el9/
|
||||
|
||||
@@ -45,6 +45,7 @@ import math
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import shlex
|
||||
import signal
|
||||
@@ -132,6 +133,8 @@ def print_help():
|
||||
|
||||
def updatestatus(stateinfo={}):
|
||||
global powerstate, powertime, clearpowermessage
|
||||
if opts.headless:
|
||||
return
|
||||
status = consolename
|
||||
info = []
|
||||
for statekey in stateinfo:
|
||||
@@ -454,8 +457,10 @@ def do_command(command, server):
|
||||
currconsole = targpath
|
||||
startrequest = {'operation': 'start', 'path': targpath,
|
||||
'parameters': {}}
|
||||
height, width = struct.unpack(
|
||||
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
|
||||
height, width = 31, 100
|
||||
if not opts.headless:
|
||||
height, width = struct.unpack(
|
||||
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
|
||||
startrequest['parameters']['width'] = width
|
||||
startrequest['parameters']['height'] = height
|
||||
for param in argv[2:]:
|
||||
@@ -623,17 +628,19 @@ def startconsole(nodename):
|
||||
signal.signal(signal.SIGWINCH, do_resize)
|
||||
didconsole = True
|
||||
consolename = nodename
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
|
||||
if not opts.headless:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
|
||||
inconsole = True
|
||||
check_automation('') # give any leading 'sends' a chance
|
||||
|
||||
|
||||
def quitconfetty(code=0, fullexit=False, fixterm=True):
|
||||
global inconsole
|
||||
global currconsole
|
||||
global didconsole
|
||||
if fixterm or didconsole:
|
||||
if (fixterm or didconsole) and not opts.headless:
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl & ~os.O_NONBLOCK)
|
||||
if oldtcattr is not None:
|
||||
@@ -843,6 +850,20 @@ def check_escape_seq(currinput, filehandle):
|
||||
currinput += filehandle.read()
|
||||
return currinput
|
||||
|
||||
automation_directives = []
|
||||
current_automation_directive = None
|
||||
automation_map = {
|
||||
'<up>': '\x1b[A',
|
||||
'<down>': '\x1b[B',
|
||||
'<right>': '\x1b[C',
|
||||
'<left>': '\x1b[D',
|
||||
'<enter>': '\r',
|
||||
'<esc>': '\x1b',
|
||||
'<tab>': '\t',
|
||||
}
|
||||
|
||||
|
||||
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option("-s", "--server", dest="netserver",
|
||||
help="Confluent instance to connect to",
|
||||
@@ -850,12 +871,64 @@ parser.add_option("-s", "--server", dest="netserver",
|
||||
parser.add_option("-c", "--control", dest="controlpath",
|
||||
help="Path to offer terminal control",
|
||||
metavar="PATH")
|
||||
parser.add_option('-a', '--automation', type='string', default=None,
|
||||
help='Specify an automation script to run', metavar='SCRIPT')
|
||||
parser.add_option('-e', '--headless', action='store_true', default=False,
|
||||
help='Run in headless mode, which is designed for use with '
|
||||
'automation scripts and disables interactive features')
|
||||
parser.add_option(
|
||||
'-m', '--mintime', default=0,
|
||||
help='Minimum time to run or else pause for input (used to keep a '
|
||||
'terminal from closing quickly on error)')
|
||||
opts, shellargs = parser.parse_args()
|
||||
|
||||
def parse_automation_script(script, session_node):
|
||||
global current_automation_directive
|
||||
for line in script.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if line.startswith('exit'):
|
||||
automation_directives.append(('exit', None))
|
||||
continue
|
||||
if ' ' not in line:
|
||||
sys.stderr.write("Invalid line in automation script: %s\n" % line)
|
||||
continue
|
||||
directive, arg = line.split(' ', 1)
|
||||
directive = directive.strip().lower()
|
||||
if directive not in ('expect', 'send', 'forget'):
|
||||
sys.stderr.write("Unknown directive in automation script: %s\n" % directive)
|
||||
continue
|
||||
arg = arg.strip()
|
||||
origarg = arg
|
||||
if arg[0] not in ('"', "'"):
|
||||
arg = '"' + arg + '"'
|
||||
if arg[0] == "'" and arg[-1] == "'":
|
||||
# do not process '<>' sequences in single quotes
|
||||
arg = arg[1:-1]
|
||||
arg = bytes(arg, "utf-8").decode("unicode_escape")
|
||||
elif arg[0] == '"' and arg[-1] == '"':
|
||||
arg = bytes(arg[1:-1], "utf-8").decode("unicode_escape")
|
||||
for key, value in automation_map.items():
|
||||
arg = arg.replace(key, value)
|
||||
arg = re.sub(r'<env:(\w+)>', lambda m: os.environ[m.group(1)], arg)
|
||||
if '{' in arg: # support confluent expressions
|
||||
for res in session.create('/nodes/{0}/attributes/expression'.format(session_node),
|
||||
{'expression': arg}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
sys.exit(1)
|
||||
if 'value' in res:
|
||||
arg = res['value']
|
||||
automation_directives.append((directive, arg, origarg))
|
||||
if automation_directives:
|
||||
current_automation_directive = automation_directives.pop(0)
|
||||
if opts.headless and current_automation_directive[0] == 'expect':
|
||||
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\r\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
|
||||
username = None
|
||||
passphrase = None
|
||||
def server_connect():
|
||||
@@ -908,6 +981,7 @@ def main():
|
||||
# sys.stdout.write('\x1b[H\x1b[J')
|
||||
# sys.stdout.flush()
|
||||
global powerstate, powertime, clearpowermessage
|
||||
|
||||
|
||||
|
||||
if sys.stdout.isatty():
|
||||
@@ -924,6 +998,9 @@ def main():
|
||||
targ, session_node = get_session_node(shellargs)
|
||||
if session_node is not None:
|
||||
consoleonly = True
|
||||
if opts.automation:
|
||||
with open(opts.automation) as f:
|
||||
parse_automation_script(f.read(), session_node)
|
||||
do_command("start %s" % targ, netserver)
|
||||
doexit = True
|
||||
elif shellargs:
|
||||
@@ -935,9 +1012,13 @@ def main():
|
||||
|
||||
while inconsole or not doexit:
|
||||
if inconsole:
|
||||
if opts.headless:
|
||||
handles = [session.connection]
|
||||
else:
|
||||
handles = (sys.stdin, session.connection)
|
||||
try:
|
||||
rdylist, _, _ = select.select(
|
||||
(sys.stdin, session.connection), (), (), 10)
|
||||
handles, (), (), 10)
|
||||
except select.error:
|
||||
rdylist = ()
|
||||
for fh in rdylist:
|
||||
@@ -969,8 +1050,81 @@ 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 = ''
|
||||
automation_check = ''
|
||||
|
||||
def check_automation(data):
|
||||
global automation_check
|
||||
global current_automation_directive
|
||||
if type(data) != str:
|
||||
data = data.decode('utf-8', errors='ignore')
|
||||
while current_automation_directive:
|
||||
if current_automation_directive[0] == 'forget' and data:
|
||||
current_automation_directive = None
|
||||
automation_check = ''
|
||||
if automation_directives:
|
||||
current_automation_directive = automation_directives.pop(0)
|
||||
if opts.headless and current_automation_directive[0] == 'expect':
|
||||
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\n')
|
||||
sys.stdout.flush()
|
||||
return
|
||||
if current_automation_directive[0] == 'expect':
|
||||
expected = current_automation_directive[1]
|
||||
combined = automation_check + data
|
||||
if expected and expected in combined:
|
||||
data = data[combined.rindex(expected) + len(expected):]
|
||||
|
||||
if opts.headless:
|
||||
sys.stdout.write(f'Detected {repr(current_automation_directive[2])}\r\n')
|
||||
sys.stdout.flush()
|
||||
current_automation_directive = None
|
||||
automation_check = ''
|
||||
if automation_directives:
|
||||
current_automation_directive = automation_directives.pop(0)
|
||||
if opts.headless and current_automation_directive[0] == 'expect':
|
||||
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\r\n')
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
# Check if there's potential start of expected data in the incoming data
|
||||
combined = automation_check + data
|
||||
automation_check = ''
|
||||
for i in range(1, min(len(expected), len(combined)) + 1):
|
||||
if expected.startswith(combined[-i:]):
|
||||
automation_check = combined[-i:]
|
||||
return # wait for next check
|
||||
elif current_automation_directive[0] == 'send':
|
||||
data = ''
|
||||
automation_check = ''
|
||||
if opts.headless:
|
||||
sys.stdout.write(f'Sending {repr(current_automation_directive[2])}\r\n')
|
||||
sys.stdout.flush()
|
||||
if current_automation_directive[1]:
|
||||
tlvdata.send(session.connection, current_automation_directive[1])
|
||||
current_automation_directive = None
|
||||
if automation_directives:
|
||||
current_automation_directive = automation_directives.pop(0)
|
||||
if opts.headless and current_automation_directive[0] == 'expect':
|
||||
sys.stdout.write(f'Expecting {repr(current_automation_directive[2])}\r\n')
|
||||
sys.stdout.flush()
|
||||
elif current_automation_directive[0] == 'exit':
|
||||
if opts.headless:
|
||||
sys.stdout.write('Automation completed\r\n')
|
||||
sys.stdout.flush()
|
||||
return True
|
||||
if opts.headless and not current_automation_directive:
|
||||
sys.stdout.write('Automation completed\r\n')
|
||||
sys.stdout.flush()
|
||||
return True
|
||||
return False
|
||||
|
||||
def consume_termdata(fh, bufferonly=False):
|
||||
global clearpowermessage
|
||||
global fgcolor, bgcolor, fgshifted, pendseq
|
||||
try:
|
||||
data = tlvdata.recv(fh)
|
||||
except Exception:
|
||||
@@ -979,7 +1133,64 @@ def consume_termdata(fh, bufferonly=False):
|
||||
updatestatus(data)
|
||||
return ''
|
||||
if data is not None:
|
||||
data = client.stringify(data)
|
||||
shouldexit = check_automation(data)
|
||||
if opts.headless:
|
||||
if shouldexit:
|
||||
quitconfetty(fullexit=True)
|
||||
return ''
|
||||
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
|
||||
@@ -1006,6 +1217,8 @@ def consume_termdata(fh, bufferonly=False):
|
||||
# this scenario comfortable that it
|
||||
# will come out soon enough
|
||||
pass
|
||||
if shouldexit:
|
||||
quitconfetty(fullexit=True)
|
||||
else:
|
||||
deadline = 5
|
||||
connected = False
|
||||
|
||||
@@ -36,6 +36,36 @@ import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
devnull = None
|
||||
|
||||
def run_automation(noderange, category, c):
|
||||
automationbynode = {}
|
||||
for res in c.update('/noderange/{0}/deployment/remote_config/run'.format(noderange), {
|
||||
'category': category,
|
||||
}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
if 'created' in res:
|
||||
nodename = res['created'].split('/')[2]
|
||||
automationbynode[nodename] = res['created']
|
||||
while automationbynode:
|
||||
for node in list(automationbynode):
|
||||
for res in c.read(automationbynode[node]):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
for result in res.get('results', []):
|
||||
sys.stdout.write('{0}: Task [{1}] {2}\n'.format(
|
||||
node, result['task_name'], result['state']))
|
||||
for warning in result.get('warnings', []):
|
||||
sys.stderr.write('{0}: [WARNING] {1}\n'.format(node, warning))
|
||||
if 'errorinfo' in result:
|
||||
for errorline in result['errorinfo'].splitlines():
|
||||
sys.stderr.write('{0}: [ERROR] {1}\n'.format(node, errorline))
|
||||
if res.get('complete', False):
|
||||
del automationbynode[node]
|
||||
sys.stdout.write('{0}: Automation complete\n'.format(node))
|
||||
|
||||
|
||||
def run():
|
||||
global devnull
|
||||
devnull = open(os.devnull, 'rb')
|
||||
@@ -51,6 +81,8 @@ def run():
|
||||
help='Run the syncfiles associated with the currently completed OS profile on the noderange')
|
||||
argparser.add_option('-P', '--scripts',
|
||||
help='Re-run specified scripts, with full path under scripts, e.g. post.d/first,firstboot.d/second')
|
||||
argparser.add_option('-A', '--automation',
|
||||
help='Run the automation scripts associated with the current OS profile on the noderange, specifying category (onboot.d/firstboot.d/post.d)')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
@@ -74,16 +106,13 @@ def run():
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
if options.automation:
|
||||
run_automation(args[0], options.automation, c)
|
||||
|
||||
nodemap = {}
|
||||
cmdparms = []
|
||||
nodes = []
|
||||
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
break
|
||||
node = res['item']['href'][:-1]
|
||||
nodes.append(node)
|
||||
|
||||
|
||||
cmdstorun = []
|
||||
if options.security:
|
||||
@@ -94,8 +123,17 @@ def run():
|
||||
for script in options.scripts.split(','):
|
||||
cmdstorun.append(['run_remote', script])
|
||||
if not cmdstorun:
|
||||
if options.automation:
|
||||
sys.exit(0)
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
break
|
||||
node = res['item']['href'][:-1]
|
||||
nodes.append(node)
|
||||
idxbynode = {}
|
||||
cmdvbase = ['bash', '/etc/confluent/functions']
|
||||
for sshnode in nodes:
|
||||
@@ -145,7 +183,7 @@ def run():
|
||||
run_cmdv(node, cmdv, all, poller, pipedesc)
|
||||
elif pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, poller. pipedesc)
|
||||
run_cmdv(node, cmdv, all, poller, pipedesc)
|
||||
singlepoller.close()
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/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')
|
||||
|
||||
sign_bmc_parser = subparsers.add_parser('signbmccert', help='Sign BMC certificate')
|
||||
sign_bmc_parser.add_argument('--days', type=int, help='Number of days the certificate is valid for')
|
||||
sign_bmc_parser.add_argument('--added-names', type=str, help='Additional names to include in the certificate')
|
||||
|
||||
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)
|
||||
elif args.command == 'signbmccert':
|
||||
payload = {}
|
||||
if args.days is not None:
|
||||
payload['days'] = args.days
|
||||
else:
|
||||
print("Error: --days is required for signbmccert", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if args.added_names:
|
||||
payload['added_names'] = args.added_names
|
||||
for res in c.update(f'/noderange/{args.noderange}/configuration/management_controller/certificate/sign', payload):
|
||||
print(repr(res))
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
@@ -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.
|
||||
@@ -62,7 +62,7 @@ argparser.add_option('-e', '--extra', dest='extra',
|
||||
'to be extra configuration')
|
||||
argparser.add_option('-x', '--exclude', dest='exclude',
|
||||
action='store_true', default=False,
|
||||
help='Treat positional arguments as items to not '
|
||||
help='Treat named settings as items to not '
|
||||
'examine, compare, or restore default')
|
||||
argparser.add_option('-a', '--advanced', dest='advanced',
|
||||
action='store_true', default=False,
|
||||
@@ -73,7 +73,7 @@ argparser.add_option('-r', '--restoredefault', default=False,
|
||||
dest='restoredefault', metavar="COMPONENT",
|
||||
help='Restore the configuration of the node '
|
||||
'to factory default for given component. '
|
||||
'Currently only uefi is supported')
|
||||
'Currently "uefi" and "bmc" are supported components')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure, '
|
||||
@@ -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'),
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import base64
|
||||
import optparse
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -62,8 +63,13 @@ argparser = optparse.OptionParser(
|
||||
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
|
||||
"For example, ctrl-'e', then 'c', then '.' will exit the current "
|
||||
"console")
|
||||
argparser.add_option('-a', '--automation', type='string', default=None,
|
||||
help='Specify an automation script')
|
||||
argparser.add_option('-t', '--tile', action='store_true', default=False,
|
||||
help='Tile console windows in the terminal')
|
||||
argparser.add_option('-e', '--headless', action='store_true', default=False,
|
||||
help='Run in headless mode, which is designed for use with '
|
||||
'automation scripts and disables interactive features')
|
||||
argparser.add_option('-l', '--log', action='store_true', default=False,
|
||||
help='Enter log replay mode instead of showing a live console')
|
||||
|
||||
@@ -98,6 +104,12 @@ argparser.add_option('-w','--windowed', action='store_true', default=False,
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
automation_args = []
|
||||
if options.automation:
|
||||
automation_args = ['-a', options.automation]
|
||||
if options.headless:
|
||||
automation_args += ['--headless']
|
||||
|
||||
oldtcattr = None
|
||||
oldfl = None
|
||||
|
||||
@@ -655,7 +667,7 @@ if options.windowed:
|
||||
firstnode=nodes[0]
|
||||
nodes.pop(0)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode), '-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode) ] , stdin=devnull)
|
||||
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode)] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode)] , stdin=devnull)
|
||||
time.sleep(2)
|
||||
s=socket.socket(socket.AF_UNIX)
|
||||
winid=''
|
||||
@@ -727,7 +739,7 @@ if options.windowed:
|
||||
else:
|
||||
pass
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node), '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
|
||||
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node)] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
|
||||
sys.exit(0)
|
||||
#end of wcons
|
||||
if options.tile:
|
||||
@@ -752,18 +764,18 @@ if options.tile:
|
||||
panename = '{0}:{1}'.format(sessname, pane)
|
||||
if initial:
|
||||
initial = False
|
||||
confetty_cmd = [confettypath] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(node)]
|
||||
subprocess.call(
|
||||
['tmux', 'new-session', '-d', '-s',
|
||||
sessname, '-x', '800', '-y',
|
||||
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
|
||||
confettypath, node)])
|
||||
'800', ' '.join(shlex.quote(arg) for arg in confetty_cmd)])
|
||||
else:
|
||||
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
||||
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
||||
confetty_cmd = [confettypath] + automation_args + ['-m', '5', 'start', '/nodes/{0}/console/session'.format(node)]
|
||||
subprocess.call(
|
||||
['tmux', 'split', '-h', '-t', sessname,
|
||||
'{0} -m 5 start /nodes/{1}/console/session'.format(
|
||||
confettypath, node)])
|
||||
' '.join(shlex.quote(arg) for arg in confetty_cmd)])
|
||||
subprocess.call(['tmux', 'select-layout', '-t', sessname, 'tiled'], stdout=null)
|
||||
pane += 1
|
||||
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
||||
@@ -771,5 +783,6 @@ if options.tile:
|
||||
if not in_tmux:
|
||||
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
|
||||
else:
|
||||
os.execl(confettypath, confettypath, 'start',
|
||||
'/nodes/{0}/console/session'.format(args[0]))
|
||||
execl_args = [confettypath] + automation_args + ['start',
|
||||
'/nodes/{0}/console/session'.format(args[0])]
|
||||
os.execl(confettypath, *execl_args)
|
||||
|
||||
@@ -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'] = ''
|
||||
@@ -67,8 +78,10 @@ def main(args):
|
||||
ap = argparse.ArgumentParser(description='Deploy OS to nodes')
|
||||
ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true')
|
||||
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
|
||||
ap.add_argument('-b', '--bootmethod', help='Specify network boot method (e.g., network, http)', default='network')
|
||||
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 +91,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 +109,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 +151,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)):
|
||||
@@ -172,7 +214,7 @@ def main(args):
|
||||
print('{0}: {1}{2}'.format(node, profile, armed))
|
||||
sys.exit(0)
|
||||
if not args.clear and args.network and not args.prepareonly:
|
||||
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
|
||||
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', args.bootmethod,
|
||||
bootmode='uefi',
|
||||
persistent=False,
|
||||
errnodes=errnodes)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
@@ -59,6 +59,8 @@ argparser = optparse.OptionParser(
|
||||
"%prog <noderange> [list][updatestatus][update [--backup <file>]]|[<components>]")
|
||||
argparser.add_option('-b', '--backup', action='store_true',
|
||||
help='Target a backup bank rather than primary')
|
||||
argparser.add_option('-p', '--parameterfile', type='string',
|
||||
help='When updating, use the specified parameter file')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='When updating, prompt if more than the specified '
|
||||
'number of servers will be affected')
|
||||
@@ -112,13 +114,30 @@ def update_firmware(session, filename):
|
||||
upargs = {'filename': filename}
|
||||
if options.backup:
|
||||
upargs['bank'] = 'backup'
|
||||
if options.parameterfile:
|
||||
with open(options.parameterfile, 'rb') as pf:
|
||||
pfdata = pf.read()
|
||||
upargs['parameterdata'] = pfdata
|
||||
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 +172,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:
|
||||
|
||||
@@ -84,13 +84,13 @@ def main():
|
||||
healthexplanations[node] = []
|
||||
for sensor in health[node]['sensors']:
|
||||
explanation = sensor['name'] + ':'
|
||||
if sensor['value'] is not None:
|
||||
if sensor.get('value', None) is not None:
|
||||
explanation += str(sensor['value'])
|
||||
if sensor['units'] is not None:
|
||||
if sensor.get('units', None) is not None:
|
||||
explanation += sensor['units']
|
||||
if sensor['states']:
|
||||
if sensor.get('states', None):
|
||||
explanation += ','
|
||||
if sensor['states']:
|
||||
if sensor.get('states', None):
|
||||
explanation += ','.join(sensor['states'])
|
||||
healthexplanations[node].append(explanation)
|
||||
if node in healthbynode and node in healthexplanations:
|
||||
|
||||
@@ -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']))
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -123,7 +123,7 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if 'sensors' not in reading[node]:
|
||||
continue
|
||||
for sensedata in reading[node]['sensors']:
|
||||
if sensedata['value'] is None and options.skipnumberless:
|
||||
if sensedata.get('value', None) is None and options.skipnumberless:
|
||||
continue
|
||||
for redundant_state in ('Non-Critical', 'Critical'):
|
||||
try:
|
||||
@@ -134,17 +134,17 @@ def sensorpass(showout=True, appendtime=False):
|
||||
resultdata[node][sensedata['name']] = sensedata
|
||||
sensorname = sensedata['name']
|
||||
sensorheaders[sensorname] = sensorname
|
||||
if sensedata['units'] not in (None, u''):
|
||||
if sensedata.get('units', None) not in (None, u''):
|
||||
sensorheaders[sensorname] += u' ({0})'.format(
|
||||
sensedata['units'])
|
||||
if showout:
|
||||
if sensedata['value'] is None:
|
||||
if sensedata.get('value', None) is None:
|
||||
showval = ''
|
||||
elif isinstance(sensedata['value'], float):
|
||||
showval = u' {0} '.format(floatformat(sensedata['value']))
|
||||
else:
|
||||
showval = u' {0} '.format(sensedata['value'])
|
||||
if sensedata['units'] not in (None, u''):
|
||||
showval = u' {0} '.format(sensedata.get('value', ''))
|
||||
if sensedata.get('units', None) not in (None, u''):
|
||||
showval += sensedata['units']
|
||||
if sensedata.get('health', 'ok') != 'ok':
|
||||
datadescription = [sensedata['health']]
|
||||
|
||||
@@ -32,7 +32,7 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='Usage: %prog [options] <noderange> [default|cd|network|setup|hd|usb|floppy]')
|
||||
usage='Usage: %prog [options] <noderange> [default|cd|network|http|setup|hd|usb|floppy]')
|
||||
argparser.add_option('-b', '--bios', dest='biosmode',
|
||||
action='store_true', default=False,
|
||||
help='Request BIOS style boot (rather than UEFI)')
|
||||
|
||||
@@ -281,6 +281,7 @@ class Command(object):
|
||||
if maxnodes is None:
|
||||
return
|
||||
nsize = self.get_noderange_size(noderange)
|
||||
maxnodes = int(maxnodes)
|
||||
if nsize > maxnodes:
|
||||
if nsize == 1:
|
||||
nodename = list(self.read(
|
||||
@@ -391,8 +392,13 @@ class Command(object):
|
||||
cacert = None
|
||||
certreqs = ssl.CERT_NONE
|
||||
knownhosts = True
|
||||
self.connection = ssl.wrap_socket(self.connection, ca_certs=cacert,
|
||||
cert_reqs=certreqs)
|
||||
tlsctx = ssl.create_default_context()
|
||||
if certreqs == ssl.CERT_NONE:
|
||||
tlsctx.check_hostname = False
|
||||
tlsctx.verify_mode = certreqs
|
||||
if cacert:
|
||||
tlsctx.load_verify_locations(cacert)
|
||||
self.connection = tlsctx.wrap_socket(self.connection, server_hostname=server)
|
||||
if knownhosts:
|
||||
certdata = self.connection.getpeercert(binary_form=True)
|
||||
fingerprint = 'sha512$' + hashlib.sha512(certdata).hexdigest()
|
||||
|
||||
@@ -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.
|
||||
@@ -24,6 +24,8 @@ For a full list of attributes, run `nodeattrib <node> all` against a node.
|
||||
If `-c` is specified, this will set the nodeattribute to a null value.
|
||||
This is different from setting the value to an empty string.
|
||||
|
||||
Arbitrary custom attributes can also be created with the `custom.` prefix.
|
||||
|
||||
Attributes may be specified by wildcard, for example `net.*switch` will report
|
||||
all attributes that begin with `net.` and end with `switch`.
|
||||
|
||||
|
||||
@@ -49,8 +49,7 @@ actually be in effect until a reboot.
|
||||
|
||||
* `-r COMPONENT`, `--restoredefault=COMPONENT`:
|
||||
Request that the specified component of the targeted nodes will have its
|
||||
configuration reset to default. Currently the only component implemented
|
||||
is uefi.
|
||||
configuration reset to default. The component may be "uefi" or "bmc".
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to configure, prompting if over
|
||||
|
||||
@@ -35,7 +35,10 @@ the out of band facilities. Firmware updates can end in one of three states:
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
When updating, prompt if more than the specified number of servers will
|
||||
be affected
|
||||
|
||||
|
||||
* `-p PARAMETERFILE`, `--paramaterfile=PARAMETERFILE`:
|
||||
For updating, a parameter file to provife along with the update payload
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ nodegroupattrib(8) -- List or change confluent nodegroup attributes
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegroupattrip` queries the confluent server to get information about nodes.
|
||||
`nodegroupattrib` queries the confluent server to get information about nodes.
|
||||
In the simplest form, it simply takes the given group and lists the attributes of that group.
|
||||
|
||||
Contrasted with nodeattrib(8), settings managed by nodegroupattrib will be added
|
||||
|
||||
@@ -32,6 +32,13 @@ BMCs map a virtual USB device to that url. Content is loaded on demand, and
|
||||
as such that URL is referenced potentially once for every IO operation that
|
||||
the host platform attempts.
|
||||
|
||||
## NOTES
|
||||
|
||||
When doing an attach of an https:// url, you may hit an error if you have not enrolled your certificate authority.
|
||||
In a general confluent environment, you can usually address it by:
|
||||
`# for cert in /var/lib/confluent/public/site/tls/*.pem; do nodecertutil s1-s4 installbmccacert $cert; done`
|
||||
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-h`, `--help`:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/python3
|
||||
import glob
|
||||
import gzip
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def collect_certificates(tmpdir):
|
||||
certdata = ''
|
||||
for cacert in glob.glob(f'{tmpdir}/*.pem'):
|
||||
with open(cacert, 'r') as f:
|
||||
certdata += f.read()
|
||||
return certdata
|
||||
|
||||
def embed_certificates(incfg, certdata):
|
||||
if not certdata:
|
||||
raise Exception('No certificates found to embed')
|
||||
incfg = incfg.replace('%CONFLUENTCERTCOLL%', certdata)
|
||||
return incfg
|
||||
|
||||
def embed_identity(incfg, identityjson):
|
||||
incfg = incfg.replace('%IDENTJSON%', identityjson)
|
||||
return incfg
|
||||
|
||||
def embed_apiclient(incfg, apiclient):
|
||||
with open(apiclient, 'r') as f:
|
||||
apiclientdata = f.read()
|
||||
compressed = gzip.compress(apiclientdata.encode())
|
||||
encoded = base64.b64encode(compressed).decode()
|
||||
incfg = incfg.replace('%APICLIENTZ64%', encoded)
|
||||
return incfg
|
||||
|
||||
def embed_data(tmpdir, outfile):
|
||||
templatefile = f'{tmpdir}/bfb.cfg.template'
|
||||
with open(templatefile, 'r') as f:
|
||||
incfg = f.read()
|
||||
|
||||
certdata = collect_certificates(tmpdir)
|
||||
incfg = embed_certificates(incfg, certdata)
|
||||
|
||||
with open(f'{tmpdir}/identity.json', 'r') as f:
|
||||
identityjson = f.read()
|
||||
|
||||
incfg = embed_identity(incfg, identityjson)
|
||||
|
||||
incfg = embed_apiclient(incfg, f'{tmpdir}/../apiclient')
|
||||
|
||||
with open(outfile, 'w') as f:
|
||||
f.write(incfg)
|
||||
|
||||
def get_identity_json(node):
|
||||
identity_file = f'/var/lib/confluent/private/site/identity_files/{node}.json'
|
||||
try:
|
||||
with open(identity_file, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: bfb-autoinstall <node> <bfbfile> <rshim>")
|
||||
sys.exit(1)
|
||||
|
||||
node = sys.argv[1]
|
||||
bfbfile = sys.argv[2]
|
||||
rshim = sys.argv[3]
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
currdir = os.getcwd()
|
||||
tempdir = tempfile.mkdtemp(prefix=f'bfb-autoinstall-{node}-')
|
||||
embed_data(f'{currdir}/{node}', f'{tempdir}/bfb.cfg')
|
||||
subprocess.check_call(['bfb-install', '-b', bfbfile, '-c', f'{tempdir}/bfb.cfg', '-r', rshim])
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/python3
|
||||
import glob
|
||||
import gzip
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def collect_certificates(tmpdir):
|
||||
certdata = ''
|
||||
for cacert in glob.glob(f'{tmpdir}/*.pem'):
|
||||
with open(cacert, 'r') as f:
|
||||
certdata += f.read()
|
||||
return certdata
|
||||
|
||||
def embed_certificates(incfg, certdata):
|
||||
if not certdata:
|
||||
raise Exception('No certificates found to embed')
|
||||
incfg = incfg.replace('%CONFLUENTCERTCOLL%', certdata)
|
||||
return incfg
|
||||
|
||||
def embed_identity(incfg, identityjson):
|
||||
incfg = incfg.replace('%IDENTJSON%', identityjson)
|
||||
return incfg
|
||||
|
||||
def embed_apiclient(incfg, apiclient):
|
||||
with open(apiclient, 'r') as f:
|
||||
apiclientdata = f.read()
|
||||
compressed = gzip.compress(apiclientdata.encode())
|
||||
encoded = base64.b64encode(compressed).decode()
|
||||
incfg = incfg.replace('%APICLIENTZ64%', encoded)
|
||||
return incfg
|
||||
|
||||
def embed_data(tmpdir, outfile):
|
||||
templatefile = f'{tmpdir}/bfb.cfg.template'
|
||||
with open(templatefile, 'r') as f:
|
||||
incfg = f.read()
|
||||
|
||||
certdata = collect_certificates(tmpdir)
|
||||
incfg = embed_certificates(incfg, certdata)
|
||||
|
||||
with open(f'{tmpdir}/identity.json', 'r') as f:
|
||||
identityjson = f.read()
|
||||
|
||||
incfg = embed_identity(incfg, identityjson)
|
||||
|
||||
incfg = embed_apiclient(incfg, f'{tmpdir}/../apiclient')
|
||||
|
||||
with open(outfile, 'w') as f:
|
||||
f.write(incfg)
|
||||
|
||||
def get_identity_json(node):
|
||||
identity_file = f'/var/lib/confluent/private/site/identity_files/{node}.json'
|
||||
try:
|
||||
with open(identity_file, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: bfb-autoinstall <node> <bfbfile> <rshim>")
|
||||
sys.exit(1)
|
||||
|
||||
node = sys.argv[1]
|
||||
bfbfile = sys.argv[2]
|
||||
rshim = sys.argv[3]
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
currdir = os.getcwd()
|
||||
tempdir = tempfile.mkdtemp(prefix=f'bfb-autoinstall-{node}-')
|
||||
embed_data(f'{currdir}/{node}', f'{tempdir}/bfb.cfg')
|
||||
subprocess.check_call(['bfb-install', '-b', bfbfile, '-c', f'{tempdir}/bfb.cfg', '-r', rshim])
|
||||
@@ -0,0 +1,76 @@
|
||||
function bfb_modify_os() {
|
||||
echo 'ubuntu:!' | chpasswd -e
|
||||
mkdir -p /mnt/opt/confluent/bin/
|
||||
cat > /mnt/opt/confluent/bin/confluentbootstrap.sh << 'END_OF_EMBED'
|
||||
#!/bin/bash
|
||||
cat > /usr/local/share/ca-certificates/confluent.crt << 'END_OF_CERTS'
|
||||
%CONFLUENTCERTCOLL%
|
||||
END_OF_CERTS
|
||||
update-ca-certificates
|
||||
mkdir -p /opt/confluent/bin /etc/confluent/
|
||||
cp /usr/local/share/ca-certificates/confluent.crt /etc/confluent/ca.pem
|
||||
cat > /opt/confluent/bin/apiclient.gz.b64 << 'END_OF_CLIENT'
|
||||
%APICLIENTZ64%
|
||||
END_OF_CLIENT
|
||||
base64 -d /opt/confluent/bin/apiclient.gz.b64 | gunzip > /opt/confluent/bin/apiclient
|
||||
cat > /etc/confluent/ident.json << 'END_OF_IDENT'
|
||||
%IDENTJSON%
|
||||
END_OF_IDENT
|
||||
python3 /opt/confluent/bin/apiclient -i /etc/confluent/ident.json /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
|
||||
PROFILE=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
ROOTPASS=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg | awk '{print $2}'|grep -v null)
|
||||
if [ -n "$ROOTPASS" ]; then
|
||||
echo root:$ROOTPASS | chpasswd -e
|
||||
echo "ubuntu:$ROOTPASS" | chpasswd -e
|
||||
else
|
||||
echo 'ubuntu:!' | chpasswd -e
|
||||
fi
|
||||
cntmp=$(mktemp -d)
|
||||
cd "$cntmp" || { echo "Failed to cd to temporary directory $cntmp"; exit 1; }
|
||||
touch /etc/confluent/confluent.deploycfg
|
||||
python3 /opt/confluent/bin/apiclient /confluent-public/os/$PROFILE/scripts/confignet > confignet
|
||||
python3 confignet
|
||||
cd -
|
||||
rm -rf "$cntmp"
|
||||
python3 /opt/confluent/bin/apiclient /confluent-public/os/$PROFILE/scripts/functions > /etc/confluent/functions
|
||||
bash /etc/confluent/functions run_remote setupssh
|
||||
for cert in /etc/ssh/ssh*-cert.pub; do
|
||||
if [ -s $cert ]; then
|
||||
echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf
|
||||
fi
|
||||
done
|
||||
mkdir -p /var/log/confluent
|
||||
chmod 700 /var/log/confluent
|
||||
touch /var/log/confluent/confluent-firstboot.log
|
||||
touch /var/log/confluent/confluent-post.log
|
||||
chmod 600 /var/log/confluent/confluent-post.log
|
||||
chmod 600 /var/log/confluent/confluent-firstboot.log
|
||||
exec >> /var/log/confluent/confluent-post.log
|
||||
exec 2>> /var/log/confluent/confluent-post.log
|
||||
bash /etc/confluent/functions run_remote_python syncfileclient
|
||||
bash /etc/confluent/functions run_remote_parts post.d
|
||||
bash /etc/confluent/functions run_remote_config post.d
|
||||
exec >> /var/log/confluent/confluent-firstboot.log
|
||||
exec 2>> /var/log/confluent/confluent-firstboot.log
|
||||
bash /etc/confluent/functions run_remote_parts firstboot.d
|
||||
bash /etc/confluent/functions run_remote_config firstboot.d
|
||||
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged'
|
||||
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: complete'
|
||||
systemctl disable confluentbootstrap
|
||||
rm /etc/systemd/system/confluentbootstrap.service
|
||||
END_OF_EMBED
|
||||
chmod +x /mnt/opt/confluent/bin/confluentbootstrap.sh
|
||||
cat > /mnt/etc/systemd/system/confluentbootstrap.service << EOS
|
||||
[Unit]
|
||||
Description=First Boot Process
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/confluent/bin/confluentbootstrap.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOS
|
||||
chroot /mnt systemctl enable confluentbootstrap
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import glob
|
||||
import shutil
|
||||
import shlex
|
||||
import subprocess
|
||||
import select
|
||||
|
||||
sys.path.append('/opt/lib/confluent/python')
|
||||
|
||||
import confluent.sortutil as sortutil
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
def prep_outdir(node):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
for certfile in glob.glob('/var/lib/confluent/public/site/tls/*.pem'):
|
||||
basename = os.path.basename(certfile)
|
||||
destfile = os.path.join(tmpdir, basename)
|
||||
shutil.copy2(certfile, destfile)
|
||||
subprocess.check_call(shlex.split(f'confetty set /nodes/{node}/deployment/ident_image=create'))
|
||||
shutil.copy2(f'/var/lib/confluent/private/identity_files/{node}.json', os.path.join(tmpdir, 'identity.json'))
|
||||
return tmpdir
|
||||
|
||||
def exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller):
|
||||
remotedir = subprocess.check_output(shlex.split(f'ssh {host} mktemp -d /tmp/bfb.XXXXXX')).decode().strip()
|
||||
bfbbasename = os.path.basename(bfbfile)
|
||||
subprocess.check_call(shlex.split(f'rsync -avz --info=progress2 {bfbfile} {host}:{remotedir}/{bfbbasename}'))
|
||||
subprocess.check_call(shlex.split(f'rsync -avc --info=progress2 /opt/lib/confluent/osdeploy/bluefield/hostscripts/ {host}:{remotedir}/'))
|
||||
for node in nodetorshim:
|
||||
rshim = nodetorshim[node]
|
||||
nodeoutdir = prep_outdir(node)
|
||||
nodeprofile = subprocess.check_output(shlex.split(f'nodeattrib {node} deployment.pendingprofile')).decode().strip().split(':', 2)[2].strip()
|
||||
shutil.copy2(f'/var/lib/confluent/public/os/{nodeprofile}/bfb.cfg.template', os.path.join(nodeoutdir, 'bfb.cfg.template'))
|
||||
subprocess.check_call(shlex.split(f'rsync -avz {nodeoutdir}/ {host}:{remotedir}/{node}/'))
|
||||
shutil.rmtree(nodeoutdir)
|
||||
run_cmdv(node, shlex.split(f'ssh {host} sh /etc/confluent/functions confluentpython {remotedir}/bfb-autoinstall {node} {remotedir}/{bfbbasename} {rshim}'), all, poller, pipedesc)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, poller, pipedesc):
|
||||
try:
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
sys.stderr.write('{0}: Unable to find local executable file "{1}"\n'.format(node, cmdv[0]))
|
||||
return
|
||||
raise
|
||||
pipedesc[nopen.stdout.fileno()] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout', 'file': nopen.stdout}
|
||||
pipedesc[nopen.stderr.fileno()] = {'node': node, 'popen': nopen,
|
||||
'type': 'stderr', 'file': nopen.stderr}
|
||||
all.add(nopen.stdout)
|
||||
poller.register(nopen.stdout, select.EPOLLIN)
|
||||
all.add(nopen.stderr)
|
||||
poller.register(nopen.stderr, select.EPOLLIN)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print(f'Usage: {sys.argv[0]} <host> <bfbfile> <node1:rshim1> [<node2:rshim2> ...]')
|
||||
sys.exit(1)
|
||||
|
||||
host = sys.argv[1]
|
||||
bfbfile = sys.argv[2]
|
||||
nodetorshim = {}
|
||||
for arg in sys.argv[3:]:
|
||||
node, rshim = arg.split(':')
|
||||
nodetorshim[node] = rshim
|
||||
|
||||
installprocs = {}
|
||||
pipedesc = {}
|
||||
all = set()
|
||||
poller = select.epoll()
|
||||
|
||||
exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller)
|
||||
rdy = poller.poll(10)
|
||||
pendingexecs = []
|
||||
exitcode = 0
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
r = r[0]
|
||||
desc = pipedesc[r]
|
||||
r = desc['file']
|
||||
node = desc['node']
|
||||
data = True
|
||||
singlepoller = select.epoll()
|
||||
singlepoller.register(r, select.EPOLLIN)
|
||||
while data and singlepoller.poll(0):
|
||||
data = r.readline()
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
sys.stderr.flush()
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
poller.unregister(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, poller, pipedesc)
|
||||
singlepoller.close()
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
if all:
|
||||
rdy = poller.poll(10)
|
||||
|
||||
|
||||
@@ -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,6 +16,13 @@ import sys
|
||||
import struct
|
||||
import time
|
||||
import re
|
||||
import hashlib
|
||||
try:
|
||||
import json
|
||||
import hmac
|
||||
except ImportError:
|
||||
json = None
|
||||
hmac = None
|
||||
|
||||
class InvalidApiKey(Exception):
|
||||
pass
|
||||
@@ -39,40 +47,115 @@ c_crypt.restype = ctypes.c_char_p
|
||||
|
||||
|
||||
def get_my_addresses():
|
||||
nlhdrsz = struct.calcsize('IHHII')
|
||||
ifaddrsz = struct.calcsize('BBBBI')
|
||||
# RTM_GETADDR = 22
|
||||
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
|
||||
nlhdr = struct.pack('IHHII', nlhdrsz + ifaddrsz, 22, 0x301, 0, 0)
|
||||
# ifaddrmsg struct: u8 family, u8 prefixlen, u8 flags, u8 scope, u32 index
|
||||
ifaddrmsg = struct.pack('BBBBI', 0, 0, 0, 0, 0)
|
||||
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
|
||||
s.bind((0, 0))
|
||||
s.sendall(nlhdr + ifaddrmsg)
|
||||
addrs = []
|
||||
while True:
|
||||
pdata = s.recv(65536)
|
||||
v = memoryview(pdata)
|
||||
if struct.unpack('H', v[4:6])[0] == 3: # netlink done message
|
||||
break
|
||||
while len(v):
|
||||
length, typ = struct.unpack('IH', v[:6])
|
||||
if typ == 20:
|
||||
fam, plen, _, scope, ridx = struct.unpack('BBBBI', v[nlhdrsz:nlhdrsz+ifaddrsz])
|
||||
if scope in (253, 0):
|
||||
rta = v[nlhdrsz+ifaddrsz:length]
|
||||
while len(rta):
|
||||
rtalen, rtatyp = struct.unpack('HH', rta[:4])
|
||||
if rtalen < 4:
|
||||
break
|
||||
if rtatyp == 1:
|
||||
addrs.append((fam, rta[4:rtalen], plen, ridx))
|
||||
rta = rta[msg_align(rtalen):]
|
||||
v = v[msg_align(length):]
|
||||
for ifa in get_ifaddrs():
|
||||
if ifa[0] == 'ip':
|
||||
addrs.append((ifa[1], ifa[2], ifa[3]))
|
||||
return addrs
|
||||
|
||||
def get_mac_addresses():
|
||||
macs = []
|
||||
for ifa in get_ifaddrs():
|
||||
if ifa[0] == 'ETHER':
|
||||
macs.append((ifa[1], ifa[2]))
|
||||
return macs
|
||||
|
||||
def scan_confluents():
|
||||
def get_ifaddrs():
|
||||
class sockaddr(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_family', ctypes.c_uint16),
|
||||
('sa_data', ctypes.c_ubyte * 14),
|
||||
]
|
||||
|
||||
class sockaddr_in(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sin_family', ctypes.c_uint16),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_ubyte * 4),
|
||||
('sin_zero', ctypes.c_ubyte * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sin6_family', ctypes.c_uint16),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_ubyte * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class sockaddr_ll(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sll_family', ctypes.c_uint16),
|
||||
('sll_protocol', ctypes.c_uint16),
|
||||
('sll_ifindex', ctypes.c_int32),
|
||||
('sll_hatype', ctypes.c_uint16),
|
||||
('sll_pkttype', ctypes.c_uint8),
|
||||
('sll_halen', ctypes.c_uint8),
|
||||
('sll_addr', ctypes.c_ubyte * 8),
|
||||
]
|
||||
|
||||
class ifaddrs(ctypes.Structure):
|
||||
pass
|
||||
|
||||
ifaddrs._fields_ = [
|
||||
('ifa_next', ctypes.POINTER(ifaddrs)),
|
||||
('ifa_name', ctypes.c_char_p),
|
||||
('ifa_flags', ctypes.c_uint),
|
||||
('ifa_addr', ctypes.POINTER(sockaddr)),
|
||||
('ifa_netmask', ctypes.POINTER(sockaddr)),
|
||||
('ifa_ifu', ctypes.POINTER(sockaddr)),
|
||||
('ifa_data', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
libc.getifaddrs.argtypes = [ctypes.POINTER(ctypes.POINTER(ifaddrs))]
|
||||
libc.getifaddrs.restype = ctypes.c_int
|
||||
libc.freeifaddrs.argtypes = [ctypes.POINTER(ifaddrs)]
|
||||
libc.freeifaddrs.restype = None
|
||||
ifap = ctypes.POINTER(ifaddrs)()
|
||||
result = libc.getifaddrs(ctypes.pointer(ifap))
|
||||
if result != 0:
|
||||
return []
|
||||
addresses = []
|
||||
ifa = ifap
|
||||
try:
|
||||
while ifa:
|
||||
if ifa.contents.ifa_addr:
|
||||
family = ifa.contents.ifa_addr.contents.sa_family
|
||||
name = ifa.contents.ifa_name.decode('utf-8') if ifa.contents.ifa_name else None
|
||||
if family in (socket.AF_INET, socket.AF_INET6):
|
||||
# skip loopback and non-multicast interfaces
|
||||
if ifa.contents.ifa_flags & 8 or not ifa.contents.ifa_flags & 0x1000:
|
||||
ifa = ifa.contents.ifa_next
|
||||
continue
|
||||
if family == socket.AF_INET:
|
||||
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_in))
|
||||
addr_bytes = bytes(addr_ptr.contents.sin_addr)
|
||||
if_index = socket.if_nametoindex(name) if name else 0
|
||||
addresses.append(('ip', family, addr_bytes, if_index))
|
||||
elif family == socket.AF_INET6:
|
||||
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_in6))
|
||||
addr_bytes = bytes(addr_ptr.contents.sin6_addr)
|
||||
scope_id = addr_ptr.contents.sin6_scope_id
|
||||
addresses.append(('ip', family, addr_bytes, scope_id))
|
||||
elif family == socket.AF_PACKET:
|
||||
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_ll))
|
||||
halen = addr_ptr.contents.sll_halen
|
||||
if addr_ptr.contents.sll_hatype in (1, 32) and halen > 0: # ARPHRD_ETHER or ARPHRD_INFINIBAND
|
||||
if addr_ptr.contents.sll_hatype == 1 and addr_ptr.contents.sll_addr[0] & 2: # skip locally administered MACs
|
||||
ifa = ifa.contents.ifa_next
|
||||
continue
|
||||
mac_bytes = bytes(addr_ptr.contents.sll_addr[:halen])
|
||||
macaddr = ':'.join('{:02x}'.format(b) for b in mac_bytes)
|
||||
addresses.append(('ETHER', name, macaddr))
|
||||
ifa = ifa.contents.ifa_next
|
||||
finally:
|
||||
libc.freeifaddrs(ifap)
|
||||
|
||||
return addresses
|
||||
|
||||
def scan_confluents(confuuid=None):
|
||||
srvs = {}
|
||||
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
@@ -84,21 +167,24 @@ 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 and os.path.exists('/etc/confluent/confluent.deploycfg'):
|
||||
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 and os.path.exists('/confluent_uuid'):
|
||||
with open('/confluent_uuid') as cuuidin:
|
||||
confluentuuid = cuuidin.read().strip()
|
||||
msg += '/confluentuuid=' + confluentuuid
|
||||
try:
|
||||
with open('/sys/devices/virtual/dmi/id/product_uuid') as uuidin:
|
||||
msg += '/uuid=' + uuidin.read().strip()
|
||||
except Exception:
|
||||
pass
|
||||
for addrf in glob.glob('/sys/class/net/*/address'):
|
||||
with open(addrf) as addrin:
|
||||
hwaddr = addrin.read().strip()
|
||||
msg += '/mac=' + hwaddr
|
||||
for iface, hwaddr in get_mac_addresses():
|
||||
msg += '/mac=' + hwaddr
|
||||
msg = msg.encode('utf8')
|
||||
for addr in get_my_addresses():
|
||||
if addr[0] == socket.AF_INET6:
|
||||
@@ -126,6 +212,7 @@ def scan_confluents():
|
||||
srvlist = []
|
||||
if r:
|
||||
r = r[0]
|
||||
nodename = None
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
@@ -133,6 +220,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
|
||||
@@ -144,20 +232,37 @@ def scan_confluents():
|
||||
if currip.startswith('fe80::') and '%' not in currip:
|
||||
currip = '{0}%{1}'.format(currip, peer[-1])
|
||||
srvs[currip] = current
|
||||
srvlist.append(currip)
|
||||
if currip not in srvlist:
|
||||
srvlist.append(currip)
|
||||
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])
|
||||
@@ -195,7 +300,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()
|
||||
@@ -204,16 +309,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:
|
||||
@@ -231,35 +336,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)
|
||||
@@ -294,15 +407,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()
|
||||
@@ -342,7 +454,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):
|
||||
@@ -416,7 +528,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)
|
||||
@@ -545,8 +657,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')
|
||||
@@ -558,7 +686,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:
|
||||
@@ -566,7 +694,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:
|
||||
@@ -575,4 +703,7 @@ if __name__ == '__main__':
|
||||
elif checkonly:
|
||||
sys.stdout.write(mclient.check_connections())
|
||||
else:
|
||||
sys.stdout.write(mclient.grab_url(sys.argv[1], data).decode())
|
||||
try:
|
||||
sys.stdout.buffer.write(mclient.grab_url(sys.argv[1], data))
|
||||
except AttributeError:
|
||||
sys.stdout.write(mclient.grab_url(sys.argv[1], data))
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
# serial port is not reporting DCD, then it doesn't look like a comfortable enough scenario
|
||||
|
||||
import fcntl
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
import subprocess
|
||||
import termios
|
||||
import platform
|
||||
|
||||
|
||||
addrtoname = {
|
||||
@@ -74,9 +76,8 @@ def fixup_ubuntu_grub_serial():
|
||||
grubout.write(grubline + '\n')
|
||||
subprocess.check_call(['update-grub'])
|
||||
|
||||
def get_serial_config():
|
||||
if not os.path.exists('/sys/firmware/efi'):
|
||||
return None
|
||||
|
||||
def get_spcr_config():
|
||||
if not os.path.exists('/sys/firmware/acpi/tables/SPCR'):
|
||||
return None
|
||||
spcr = open("/sys/firmware/acpi/tables/SPCR", "rb")
|
||||
@@ -99,14 +100,39 @@ def get_serial_config():
|
||||
currattr = termios.tcgetattr(ttyf)
|
||||
currattr[4:6] = [0, termiobaud[retval['speed']]]
|
||||
termios.tcsetattr(ttyf, termios.TCSANOW, currattr)
|
||||
retval['connected'] = bool(struct.unpack('<I', fcntl.ioctl(
|
||||
ttyf, termios.TIOCMGET, '\x00\x00\x00\x00'))[0] & termios.TIOCM_CAR)
|
||||
os.close(ttyf)
|
||||
return retval
|
||||
|
||||
def get_serial_config():
|
||||
if platform.machine() != 'x86_64':
|
||||
return None # Trust non-x86 to do the right thing
|
||||
if not os.path.exists('/sys/firmware/efi'):
|
||||
return None # BIOS might fail at grub output, defer to stock OS behavior
|
||||
retval = get_spcr_config()
|
||||
if retval:
|
||||
return retval
|
||||
firstfound = None
|
||||
numpossible = 0
|
||||
numconnected = 0
|
||||
for serdev in glob.glob('/dev/ttyS*'):
|
||||
ttyf = os.open(serdev, os.O_RDWR | os.O_NOCTTY)
|
||||
try:
|
||||
statusreg = fcntl.ioctl(ttyf, termios.TIOCMGET, '\x00\x00\x00\x00')
|
||||
numpossible += 1
|
||||
if not firstfound:
|
||||
firstfound = serdev
|
||||
except Exception:
|
||||
continue
|
||||
finally:
|
||||
os.close(ttyf)
|
||||
if struct.unpack('<I', statusreg)[0] & termios.TIOCM_CAR:
|
||||
numconnected += 1
|
||||
firstfound = serdev
|
||||
if numpossible == 1 or numconnected == 1:
|
||||
return { 'tty': firstfound, 'speed': 115200 }
|
||||
|
||||
def main():
|
||||
autoconscfg = get_serial_config()
|
||||
if not autoconscfg or not autoconscfg['connected']:
|
||||
if not autoconscfg:
|
||||
return
|
||||
if os.path.exists('/etc/redhat-release'): # redhat family
|
||||
deserialize_grub_rh()
|
||||
|
||||
@@ -80,14 +80,18 @@ def await_tentative():
|
||||
time.sleep(1)
|
||||
|
||||
def map_idx_to_name():
|
||||
map = {}
|
||||
map_dict = {}
|
||||
devtype = {}
|
||||
prevdev = None
|
||||
|
||||
for line in subprocess.check_output(['ip', 'l']).decode('utf8').splitlines():
|
||||
if line.startswith(' ') and 'link/' in line:
|
||||
typ = line.split()[0].split('/')[1]
|
||||
devtype[prevdev] = typ if typ != 'ether' else 'ethernet'
|
||||
if line.startswith(' '):
|
||||
if 'link/' in line and prevdev and prevdev not in devtype:
|
||||
for word in line.split():
|
||||
if word.startswith('link/'):
|
||||
typ = word.split('/')[1]
|
||||
devtype[prevdev] = typ if typ != 'ether' else 'ethernet'
|
||||
break # Stop detection after the first type hit
|
||||
continue
|
||||
idx, iface, rst = line.split(':', 2)
|
||||
prevdev = iface.strip()
|
||||
@@ -99,9 +103,8 @@ def map_idx_to_name():
|
||||
pass
|
||||
idx = int(idx)
|
||||
iface = iface.strip()
|
||||
map[idx] = iface
|
||||
return map, devtype
|
||||
|
||||
map_dict[idx] = iface
|
||||
return map_dict, devtype
|
||||
|
||||
def get_interface_name(iname, settings):
|
||||
explicitname = settings.get('interface_names', None)
|
||||
@@ -114,8 +117,10 @@ def get_interface_name(iname, settings):
|
||||
class NetplanManager(object):
|
||||
def __init__(self, deploycfg):
|
||||
self.cfgbydev = {}
|
||||
self.cfgbybond = {}
|
||||
self.read_connections()
|
||||
self.deploycfg = deploycfg
|
||||
self.teamidx = 0
|
||||
|
||||
def read_connections(self):
|
||||
for plan in glob.glob('/etc/netplan/*.y*ml'):
|
||||
@@ -124,29 +129,61 @@ class NetplanManager(object):
|
||||
if not planinfo:
|
||||
continue
|
||||
nicinfo = planinfo.get('network', {}).get('ethernets', {})
|
||||
for devname in nicinfo:
|
||||
if devname == 'lo':
|
||||
continue
|
||||
if 'gateway4' in nicinfo[devname]:
|
||||
# normalize deprecated syntax on read in
|
||||
gw4 = nicinfo[devname]['gateway4']
|
||||
del nicinfo[devname]['gateway4']
|
||||
routeinfo = nicinfo[devname].get('routes', [])
|
||||
for ri in routeinfo:
|
||||
if ri.get('via', None) == gw4 and ri.get('to', None) in ('default', '0.0.0.0/0', '0/0'):
|
||||
break
|
||||
else:
|
||||
routeinfo.append({
|
||||
'to': 'default',
|
||||
'via': gw4
|
||||
})
|
||||
nicinfo[devname]['routes'] = routeinfo
|
||||
self.cfgbydev[devname] = nicinfo[devname]
|
||||
bondinfo = planinfo.get('network', {}).get('bonds', {})
|
||||
for currinfo in (nicinfo, bondinfo):
|
||||
currcfg = self.cfgbydev if currinfo is nicinfo else self.cfgbybond
|
||||
for devname in currinfo:
|
||||
if devname == 'lo':
|
||||
continue
|
||||
if 'gateway4' in currinfo[devname]:
|
||||
# normalize deprecated syntax on read in
|
||||
gw4 = currinfo[devname]['gateway4']
|
||||
del currinfo[devname]['gateway4']
|
||||
routeinfo = currinfo[devname].get('routes', [])
|
||||
for ri in routeinfo:
|
||||
if ri.get('via', None) == gw4 and ri.get('to', None) in ('default', '0.0.0.0/0', '0/0'):
|
||||
break
|
||||
else:
|
||||
routeinfo.append({
|
||||
'to': 'default',
|
||||
'via': gw4
|
||||
})
|
||||
currinfo[devname]['routes'] = routeinfo
|
||||
currcfg[devname] = currinfo[devname]
|
||||
|
||||
def apply_configuration(self, cfg):
|
||||
devnames = cfg['interfaces']
|
||||
if len(devnames) != 1:
|
||||
raise Exception('Multi-nic team/bonds not yet supported')
|
||||
if len(devnames) > 1:
|
||||
teammode = cfg['settings'].get('team_mode', None)
|
||||
if not teammode:
|
||||
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(devnames)))
|
||||
return
|
||||
if teammode == 'lacp':
|
||||
teammode = '802.3ad'
|
||||
elif teammode == 'activebackup':
|
||||
teammode = 'active-backup'
|
||||
for currdev in self.cfgbybond:
|
||||
for iface in self.cfgbybond[currdev].get('interfaces', []):
|
||||
if iface in devnames:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
# this bond is identified as matching
|
||||
self.cfgbybond[currdev]['interfaces'] = list(devnames)
|
||||
self.cfgbybond[currdev]['parameters']['mode'] = teammode
|
||||
devnames = [currdev]
|
||||
break
|
||||
# no current bond, make a new one
|
||||
connname = cfg['settings'].get('connection_name', None)
|
||||
if not connname:
|
||||
connname = 'bond{0}'.format(self.teamidx)
|
||||
while connname in self.cfgbybond:
|
||||
self.teamidx += 1
|
||||
connname = 'bond{0}'.format(self.teamidx)
|
||||
self.cfgbybond[connname] = {'interfaces': list(devnames), 'parameters': {'mode': teammode, 'mii-monitor-interval': 100}}
|
||||
devnames = [connname]
|
||||
stgs = cfg['settings']
|
||||
needcfgapply = False
|
||||
for devname in devnames:
|
||||
@@ -171,7 +208,7 @@ class NetplanManager(object):
|
||||
gws.append(stgs.get('ipv4_gateway', None))
|
||||
gws.append(stgs.get('ipv6_gateway', None))
|
||||
for gwaddr in gws:
|
||||
if gwaddr:
|
||||
if gwaddr and gwaddr != '0.0.0.0':
|
||||
cfgroutes = self.getcfgarrpath([devname, 'routes'])
|
||||
for rinfo in cfgroutes:
|
||||
if rinfo.get('via', None) == gwaddr:
|
||||
@@ -192,18 +229,56 @@ class NetplanManager(object):
|
||||
if dnsdomain not in currdnsdomain:
|
||||
needcfgwrite = True
|
||||
currdnsdomain.append(dnsdomain)
|
||||
prune_from_cloudinit = []
|
||||
if needcfgwrite:
|
||||
needcfgapply = True
|
||||
newcfg = {'network': {'version': 2, 'ethernets': {devname: self.cfgbydev[devname]}}}
|
||||
oumask = os.umask(0o77)
|
||||
with open('/etc/netplan/{0}-confluentcfg.yaml'.format(devname), 'w') as planout:
|
||||
if devname in self.cfgbydev:
|
||||
prune_from_cloudinit.append(devname)
|
||||
newcfg = {'network': {'version': 2, 'ethernets': {devname: self.cfgbydev[devname]}}}
|
||||
cfgfile = '/etc/netplan/10-{0}-confluentcfg.yaml'.format(devname)
|
||||
elif devname in self.cfgbybond:
|
||||
newcfg = {'network': {'version': 2, 'bonds': {devname: self.cfgbybond[devname]}}}
|
||||
for iface in newcfg['network']['bonds'][devname]['interfaces']:
|
||||
prune_from_cloudinit.append(iface)
|
||||
with open('/etc/netplan/10-{0}-confluentcfg.yaml'.format(iface), 'w') as planout:
|
||||
planout.write(yaml.dump({'network': {'version': 2, 'ethernets': {iface: {'dhcp4': False}}}}))
|
||||
cfgfile = '/etc/netplan/30-{0}-confluentcfg.yaml'.format(devname)
|
||||
with open(cfgfile, 'w') as planout:
|
||||
planout.write(yaml.dump(newcfg))
|
||||
os.umask(oumask)
|
||||
if prune_from_cloudinit:
|
||||
prunecfgs = ['/etc/netplan/00-installer-config.yaml',
|
||||
'/etc/netplan/50-cloud-init.yaml.dist-subiquity',
|
||||
'/etc/netplan/50-cloud-init.yaml']
|
||||
prunecfgs.extend(glob.glob('/etc/cloud/cloud.cfg.d/*.cfg'))
|
||||
for defcfg in prunecfgs:
|
||||
if not os.path.exists(defcfg):
|
||||
continue
|
||||
with open(defcfg, 'r') as cloudinit:
|
||||
cloudinfo = yaml.safe_load(cloudinit)
|
||||
if 'network' not in cloudinfo:
|
||||
continue
|
||||
for clouddev in list(cloudinfo.get('network', {}).get('ethernets', {})):
|
||||
if clouddev in prune_from_cloudinit:
|
||||
del cloudinfo['network']['ethernets'][clouddev]
|
||||
if not cloudinfo['network'].get('ethernets', {}):
|
||||
os.remove(defcfg)
|
||||
if '/etc/cloud/cloud.cfg.d/' in defcfg:
|
||||
# need to also change datasource, probably
|
||||
if os.path.exists('/var/lib/cloud/instances/iid-datasource-none/network-config.json'):
|
||||
os.remove('/var/lib/cloud/instances/iid-datasource-none/network-config.json')
|
||||
else:
|
||||
oumask = os.umask(0o77)
|
||||
with open(defcfg, 'w') as cloudinit:
|
||||
cloudinit.write(yaml.dump(cloudinfo))
|
||||
os.umask(oumask)
|
||||
if needcfgapply:
|
||||
subprocess.check_call(['netplan', 'generate'])
|
||||
subprocess.call(['netplan', 'apply'])
|
||||
|
||||
def getcfgarrpath(self, devpath):
|
||||
currptr = self.cfgbydev
|
||||
currptr = self.cfgbybond if devpath[0] in self.cfgbybond else self.cfgbydev
|
||||
for k in devpath[:-1]:
|
||||
if k not in currptr:
|
||||
currptr[k] = {}
|
||||
@@ -356,7 +431,7 @@ class NetworkManager(object):
|
||||
currteam = deats.get('connection.master', None)
|
||||
if currteam == team:
|
||||
return
|
||||
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname'):
|
||||
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname', 'ipv4.dns-search', 'ipv6.dns-search'):
|
||||
if deats.get(stg, None):
|
||||
bondcfg[stg] = deats[stg]
|
||||
if member in self.uuidbyname:
|
||||
@@ -460,6 +535,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:
|
||||
@@ -485,15 +563,16 @@ if __name__ == '__main__':
|
||||
continue
|
||||
myname = s.getsockname()
|
||||
s.close()
|
||||
curridx = None
|
||||
if len(myname) == 4:
|
||||
curridx = myname[-1]
|
||||
else:
|
||||
myname = myname[0]
|
||||
myname = socket.inet_pton(socket.AF_INET, myname)
|
||||
for addr in myaddrs:
|
||||
if myname == addr[1].tobytes():
|
||||
if myname == addr[1]:
|
||||
curridx = addr[-1]
|
||||
if curridx in doneidxs:
|
||||
if curridx is not None and curridx in doneidxs:
|
||||
continue
|
||||
for tries in (1, 2, 3):
|
||||
try:
|
||||
@@ -545,7 +624,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()
|
||||
@@ -567,4 +646,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,6 +7,7 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
continue
|
||||
fi
|
||||
certfile=${pubkey/.pub/-cert.pub}
|
||||
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
|
||||
@@ -54,4 +55,9 @@ confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' >
|
||||
cat /etc/ssh/shosts.equiv > /root/.shosts
|
||||
cd -
|
||||
rm -rf $TMPDIR
|
||||
systemctl try-restart sshd
|
||||
# ssh may be sshd or ssh, depending
|
||||
if systemctl list-unit-files | grep -q '^sshd\.service'; then
|
||||
systemctl try-restart sshd
|
||||
elif systemctl list-unit-files | grep -q '^ssh\.service'; then
|
||||
systemctl try-restart ssh
|
||||
fi
|
||||
|
||||
@@ -26,13 +26,12 @@ mkdir -p opt/confluent/bin
|
||||
mkdir -p stateless-bin
|
||||
cp -a el8bin/* .
|
||||
ln -s el8 el9
|
||||
ln -s el8 el10
|
||||
mv el10/initramfs/usr el10/initramfs/var
|
||||
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 debian13 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
|
||||
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04 coreos el9 el10; do
|
||||
mkdir ${os}out
|
||||
cd ${os}out
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -48,7 +47,7 @@ for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu20.04 ubuntu22.04 u
|
||||
mv ../addons.cpio .
|
||||
cd ..
|
||||
done
|
||||
for os in el7 el8 suse15 el9 el10 ubuntu20.04; do
|
||||
for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04; do
|
||||
mkdir ${os}disklessout
|
||||
cd ${os}disklessout
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -86,7 +85,10 @@ 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 debian debian13 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/common
|
||||
cp common/initramfs/opt/confluent/bin/apiclient %{buildroot}/opt/confluent/lib/osdeploy/common
|
||||
|
||||
for os in rhvh4 el7 el8 el9 el10 debian debian13 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.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
|
||||
|
||||
@@ -33,7 +33,7 @@ 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 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 ubuntu26.04 coreos el9 el10; do
|
||||
mkdir ${os}out
|
||||
cd ${os}out
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -49,7 +49,7 @@ for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu18.04 ubuntu20.04 u
|
||||
mv ../addons.cpio .
|
||||
cd ..
|
||||
done
|
||||
for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04; do
|
||||
for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04 ubuntu26.04; do
|
||||
mkdir ${os}disklessout
|
||||
cd ${os}disklessout
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -89,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 debian13 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 ubuntu26.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
|
||||
|
||||
+14
-6
@@ -110,14 +110,22 @@ else
|
||||
for nic in $(ip link | grep mtu|grep -v LOOPBACK|cut -d: -f 2|sed -e 's/ //'); do
|
||||
ip link set $nic up
|
||||
done
|
||||
for nic in $(ip link | grep mtu|grep -v LOOPBACK|grep LOWER_UP|cut -d: -f 2|sed -e 's/ //'); do
|
||||
if udhcpc -i $nic -p $dhcpid -t 2 -T 2 -n -x 93:0007 -x 97:00$dhuuid -q; then
|
||||
/opt/confluent/bin/copernicus > /etc/confluent/confluent.info
|
||||
if grep ^MANAGER:.*\\. /etc/confluent/confluent.info ; then
|
||||
break
|
||||
nic=
|
||||
while [ -z "$nic" ]; do
|
||||
for nic in $(ip link | grep mtu|grep -v LOOPBACK|grep LOWER_UP|cut -d: -f 2|sed -e 's/ //'); do
|
||||
if udhcpc -i $nic -p $dhcpid -t 2 -T 2 -n -x 93:0007 -x 97:00$dhuuid -q; then
|
||||
/opt/confluent/bin/copernicus > /etc/confluent/confluent.info
|
||||
if grep ^MANAGER:.*\\. /etc/confluent/confluent.info ; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
ip -4 address flush dev $nic
|
||||
nic=""
|
||||
done
|
||||
if [ -z "$nic" ]; then
|
||||
echo "No network interface could be detected, retrying...."
|
||||
sleep 2
|
||||
fi
|
||||
ip -4 flush dev $nic
|
||||
done
|
||||
mgr=$(grep ^MANAGER:.*\\. /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //')
|
||||
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,8 +3,8 @@ confluent_whost=$confluent_mgr
|
||||
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
@@ -40,20 +40,53 @@ fi
|
||||
|
||||
|
||||
#mount -t tmpfs overlay /mnt/overlay
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
if [ ! "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
fi
|
||||
TETHERED=0
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -o discard /dev/zram0 /sysroot
|
||||
else
|
||||
mount -t tmpfs disklessroot /sysroot
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: 0%\r"
|
||||
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
|
||||
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
|
||||
dstsz=$(du -sk /sysroot | awk '{print $1}')
|
||||
pct=$((dstsz * 100 / srcsz))
|
||||
if [ $pct -gt 99 ]; then
|
||||
pct=99
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: $pct%\r"
|
||||
sleep 0.25
|
||||
done &
|
||||
cp -ax /mnt/remote/* /sysroot/
|
||||
umount /mnt/remote
|
||||
if [ -e /dev/mapper/cryptimg ]; then
|
||||
dmsetup remove cryptimg
|
||||
fi
|
||||
losetup -d $loopdev
|
||||
rm /mnt/remoteimg/rootimg.sfs
|
||||
umount /mnt/remoteimg
|
||||
wait
|
||||
echo -e "Decrypting and extracting root filesystem: 100%"
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
TETHERED=1
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
fi
|
||||
fi
|
||||
mkdir -p /sysroot/etc/ssh
|
||||
mkdir -p /sysroot/etc/confluent
|
||||
@@ -109,7 +142,7 @@ echo ' EnableSSHKeysign yes' >> $sshconf
|
||||
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
|
||||
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chmod 600 /sysroot/etc/ssh/*_key
|
||||
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
|
||||
chroot /sysroot/ update-ca-trust
|
||||
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
|
||||
@@ -129,10 +162,25 @@ 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)
|
||||
if [ $TETHERED -eq 1 ]; then
|
||||
(
|
||||
sleep 86400 &
|
||||
ONBOOTPID=$!
|
||||
mkdir -p /run/confluent
|
||||
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
|
||||
wait $ONBOOTPID
|
||||
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
|
||||
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/
|
||||
) &
|
||||
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
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/
|
||||
fi
|
||||
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)
|
||||
|
||||
@@ -21,17 +21,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
|
||||
fi
|
||||
|
||||
if [ -f /tmp/timeservers ]; then
|
||||
|
||||
ntpsrvs=$(cat /tmp/timeservers)
|
||||
|
||||
sed -i "1,/^pool * /c\\
|
||||
|
||||
${ntpsrvs//$'\n'/\\$'\n'}" /etc/chrony.conf
|
||||
|
||||
|
||||
systemctl restart chronyd
|
||||
|
||||
rm -f /tmp/timeservers
|
||||
sed -i '/^[[:space:]]*\(pool\|server\)[[:space:]]/d' /etc/chrony.conf
|
||||
cat /tmp/timeservers >> /etc/chrony.conf
|
||||
systemctl restart chronyd
|
||||
rm -f /tmp/timeservers
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,10 +4,12 @@ if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
TETHERED=0
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
TETHERED=1
|
||||
confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs"
|
||||
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
|
||||
fi
|
||||
@@ -130,4 +132,17 @@ 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)
|
||||
if [ $TETHERED -eq 1 ]; then
|
||||
(
|
||||
sleep 86400 &
|
||||
ONBOOTPID=$!
|
||||
mkdir -p /run/confluent
|
||||
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
|
||||
wait $ONBOOTPID
|
||||
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
|
||||
) &
|
||||
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
exec /opt/confluent/bin/start_root
|
||||
|
||||
@@ -27,17 +27,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
|
||||
fi
|
||||
|
||||
if [ -f /tmp/timeservers ]; then
|
||||
|
||||
ntpsrvs=$(cat /tmp/timeservers)
|
||||
|
||||
sed -i "1,/^pool * /c\\
|
||||
${ntpsrvs//$'\n'/\\$'\n'}" /etc/chrony.conf
|
||||
|
||||
|
||||
systemctl restart chronyd
|
||||
|
||||
rm -f /tmp/timeservers
|
||||
|
||||
sed -i '/^[[:space:]]*\(pool\|server\)[[:space:]]/d' /etc/chrony.conf
|
||||
cat /tmp/timeservers >> /etc/chrony.conf
|
||||
systemctl restart chronyd
|
||||
rm -f /tmp/timeservers
|
||||
fi
|
||||
|
||||
|
||||
@@ -59,7 +52,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
|
||||
@@ -68,5 +61,14 @@ run_remote_parts onboot.d
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
|
||||
run_remote_config onboot.d
|
||||
|
||||
if [ -f /run/confluent/onboot_sleep.pid ]; then
|
||||
loopdev=$(losetup -j /mnt/remoteimg/rootimg.sfs|cut -d: -f 1)
|
||||
if [ -n "$loopdev" ]; then
|
||||
losetup "$loopdev" --direct-io=on
|
||||
fi
|
||||
sleeppid=$(cat /run/confluent/onboot_sleep.pid)
|
||||
kill "$sleeppid"
|
||||
rm -f /run/confluent/onboot_sleep.pid
|
||||
fi
|
||||
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
|
||||
@@ -277,7 +277,10 @@ def synchronize():
|
||||
try:
|
||||
uid = pwd.getpwnam(opts[fname][opt]['name']).pw_uid
|
||||
except KeyError:
|
||||
uid = opts[fname][opt]['id']
|
||||
try:
|
||||
uid = opts[fname][opt]['id']
|
||||
except KeyError:
|
||||
raise Exception(f"Unable to map owner of {fname}")
|
||||
elif opt == 'group':
|
||||
try:
|
||||
gid = grp.getgrnam(opts[fname][opt]['name']).gr_gid
|
||||
|
||||
@@ -3,7 +3,7 @@ sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/; s/oraclelinux/Oracle
|
||||
if grep Fedora $2/profile.yaml > /dev/null; then
|
||||
sed -i 's/@^minimal-environment/#/' $2/packagelist
|
||||
fi
|
||||
if grep ^label: $2/profile.yaml | grep 10 > /dev/null; then
|
||||
if grep ^label: $2/profile.yaml | grep ' 10' > /dev/null; then
|
||||
echo 'echo openssh-keysign >> /tmp/addonpackages' > $2/scripts/pre.d/enablekeysign
|
||||
chmod 644 $2/scripts/pre.d/enablekeysign
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -467,7 +467,7 @@ def install_to_disk(imgpath):
|
||||
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
|
||||
subprocess.check_call(['vgcreate', vgname, lvmpart])
|
||||
vgroupmap = {}
|
||||
if yaml and vgmap:
|
||||
if yaml and vgmap and os.path.exists('/tmp/volumegroupmap.yml'):
|
||||
with open('/tmp/volumegroupmap.yml') as mapin:
|
||||
vgroupmap = yaml.safe_load(mapin)
|
||||
donedisks = {}
|
||||
|
||||
@@ -3,8 +3,8 @@ confluent_whost=$confluent_mgr
|
||||
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
@@ -40,20 +40,53 @@ fi
|
||||
|
||||
|
||||
#mount -t tmpfs overlay /mnt/overlay
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
if [ ! "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
fi
|
||||
TETHERED=0
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -o discard /dev/zram0 /sysroot
|
||||
else
|
||||
mount -t tmpfs disklessroot /sysroot
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: 0%\r"
|
||||
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
|
||||
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
|
||||
dstsz=$(du -sk /sysroot | awk '{print $1}')
|
||||
pct=$((dstsz * 100 / srcsz))
|
||||
if [ $pct -gt 99 ]; then
|
||||
pct=99
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: $pct%\r"
|
||||
sleep 0.25
|
||||
done &
|
||||
cp -ax /mnt/remote/* /sysroot/
|
||||
umount /mnt/remote
|
||||
if [ -e /dev/mapper/cryptimg ]; then
|
||||
dmsetup remove cryptimg
|
||||
fi
|
||||
losetup -d $loopdev
|
||||
rm /mnt/remoteimg/rootimg.sfs
|
||||
umount /mnt/remoteimg
|
||||
wait
|
||||
echo -e "Decrypting and extracting root filesystem: 100%"
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
TETHERED=1
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
fi
|
||||
fi
|
||||
mkdir -p /sysroot/etc/ssh
|
||||
mkdir -p /sysroot/etc/confluent
|
||||
@@ -109,8 +142,10 @@ echo ' EnableSSHKeysign yes' >> $sshconf
|
||||
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
|
||||
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
|
||||
if grep ^ssh_keys: /etc/group > /dev/null; then
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
|
||||
fi
|
||||
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
|
||||
chroot /sysroot/ update-ca-trust
|
||||
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
|
||||
@@ -129,10 +164,37 @@ 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)
|
||||
if grep debugssh /proc/cmdline >& /dev/null; then
|
||||
debugssh=1
|
||||
else
|
||||
debugssh=0
|
||||
fi
|
||||
if [ $TETHERED -eq 1 ]; then
|
||||
# In tethered mode, the double-caching is useful to get through tricky part of
|
||||
# onboot with confignet. After that, it's excessive cache usage.
|
||||
# Give the onboot script a hook to have us come in and enable directio to the
|
||||
# squashfs and drop the cache of the rootimg so far
|
||||
(
|
||||
sleep 86400 &
|
||||
ONBOOTPID=$!
|
||||
mkdir -p /run/confluent
|
||||
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
|
||||
wait $ONBOOTPID
|
||||
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
|
||||
if [ $debugssh -eq 0 ]; then
|
||||
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/
|
||||
fi
|
||||
) &
|
||||
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
elif [ $debugssh -eq 0 ]; then
|
||||
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/
|
||||
fi
|
||||
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
|
||||
|
||||
@@ -26,17 +26,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
|
||||
fi
|
||||
|
||||
if [ -f /tmp/timeservers ]; then
|
||||
|
||||
ntpsrvs=$(cat /tmp/timeservers)
|
||||
|
||||
sed -i "1,/^pool * /c\\
|
||||
|
||||
${ntpsrvs//$'\n'/\\$'\n'}" /etc/chrony.conf
|
||||
|
||||
|
||||
systemctl restart chronyd
|
||||
|
||||
rm -f /tmp/timeservers
|
||||
sed -i '/^[[:space:]]*\(pool\|server\)[[:space:]]/d' /etc/chrony.conf
|
||||
cat /tmp/timeservers >> /etc/chrony.conf
|
||||
systemctl restart chronyd
|
||||
rm -f /tmp/timeservers
|
||||
fi
|
||||
|
||||
export nodename confluent_mgr confluent_profile
|
||||
@@ -53,7 +46,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
|
||||
@@ -62,5 +55,15 @@ run_remote_parts onboot.d
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
|
||||
run_remote_config onboot.d
|
||||
|
||||
if [ -f /run/confluent/onboot_sleep.pid ]; then
|
||||
loopdev=$(losetup -j /mnt/remoteimg/rootimg.sfs|cut -d: -f 1)
|
||||
if [ -n "$loopdev" ]; then
|
||||
losetup "$loopdev" --direct-io=on
|
||||
fi
|
||||
sleeppid=$(cat /run/confluent/onboot_sleep.pid)
|
||||
kill "$sleeppid"
|
||||
rm -f /run/confluent/onboot_sleep.pid
|
||||
fi
|
||||
|
||||
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -56,7 +56,7 @@ cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
|
||||
TRIES=0
|
||||
touch /etc/confluent/confluent.info
|
||||
TRIES=5
|
||||
echo -n "Waitiing for disks..."
|
||||
echo -n "Waiting for disks..."
|
||||
while [ ! -e /dev/disk/by-label ] && [ $TRIES -gt 0 ]; do
|
||||
sleep 1
|
||||
TRIES=$((TRIES - 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
|
||||
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
|
||||
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
|
||||
# subdirectory other than public.
|
||||
#
|
||||
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
|
||||
#
|
||||
# This file lists files to synchronize or merge to the deployed systems from the deployment server
|
||||
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
|
||||
# Note particularly the use of '->' to delineate source from target.
|
||||
# /some/path/hosts -> /etc/hosts
|
||||
|
||||
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
|
||||
# /etc/hosts
|
||||
|
||||
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
|
||||
# owner, group, and permissions in octal notation:
|
||||
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
|
||||
|
||||
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
|
||||
# contain the data in the source already in its entirety. This allows append in a fashion that
|
||||
# is friendly to being run repeatedly
|
||||
|
||||
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
|
||||
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
|
||||
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
|
||||
MERGE:
|
||||
# /etc/passwd
|
||||
# /etc/group
|
||||
@@ -8,7 +8,9 @@ for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed
|
||||
fi
|
||||
done
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
if grep confluent_imagemethod=untethered /proc/cmdline > /dev/null; then
|
||||
TETHERED=1
|
||||
if grep -q confluent_imagemethod=untethered /proc/cmdline || grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
|
||||
TETHERED=0
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
@@ -44,15 +46,46 @@ fi
|
||||
|
||||
|
||||
#mount -t tmpfs overlay /mnt/overlay
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
modprobe xfs
|
||||
mkdir /sysroot
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
if ! grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
modprobe xfs
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
if [ "$TETHERED" = 1 ]; then
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
else
|
||||
mount -o discard /dev/zram0 /sysroot
|
||||
fi
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
elif grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
|
||||
mount -t tmpfs disklessroot /sysroot
|
||||
fi
|
||||
if [ "$TETHERED" = 0 ]; then
|
||||
echo -en "Decrypting and extracting root filesystem: 0%\r"
|
||||
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
|
||||
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
|
||||
dstsz=$(du -sk /sysroot | awk '{print $1}')
|
||||
pct=$((dstsz * 100 / srcsz))
|
||||
if [ $pct -gt 99 ]; then
|
||||
pct=99
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: $pct%\r"
|
||||
sleep 0.25
|
||||
done &
|
||||
cp -a /mnt/remote/* /sysroot/
|
||||
umount /mnt/remote
|
||||
if [ -e /dev/mapper/cryptimg ]; then
|
||||
dmsetup remove cryptimg
|
||||
fi
|
||||
losetup -d $loopdev
|
||||
rm /mnt/remoteimg/rootimg.sfs
|
||||
umount /mnt/remoteimg
|
||||
wait
|
||||
echo -e "Decrypting and extracting root filesystem: 100%"
|
||||
elif [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
else
|
||||
@@ -68,6 +101,7 @@ cp /root/.ssh/* /sysroot/root/.ssh
|
||||
chmod 700 /sysroot/root/.ssh
|
||||
cp /etc/confluent/* /sysroot/etc/confluent/
|
||||
cp /etc/ssh/*key* /sysroot/etc/ssh/
|
||||
cp /tls/* /sysroot/etc/ssl/certs
|
||||
for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
certfile=${pubkey/.pub/-cert.pub}
|
||||
privfile=${pubkey%.pub}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
|
||||
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
|
||||
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
|
||||
# subdirectory other than public.
|
||||
#
|
||||
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
|
||||
#
|
||||
# This file lists files to synchronize or merge to the deployed systems from the deployment server
|
||||
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
|
||||
# Note particularly the use of '->' to delineate source from target.
|
||||
# /some/path/hosts -> /etc/hosts
|
||||
|
||||
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
|
||||
# /etc/hosts
|
||||
|
||||
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
|
||||
# owner, group, and permissions in octal notation:
|
||||
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
|
||||
|
||||
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
|
||||
# contain the data in the source already in its entirety. This allows append in a fashion that
|
||||
# is friendly to being run repeatedly
|
||||
|
||||
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
|
||||
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
|
||||
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
|
||||
MERGE:
|
||||
# /etc/passwd
|
||||
# /etc/group
|
||||
@@ -52,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}')
|
||||
|
||||
@@ -7,7 +7,7 @@ exec >> /target/var/log/confluent/confluent-firstboot.log
|
||||
exec 2>> /target/var/log/confluent/confluent-firstboot.log
|
||||
chmod 600 /target/var/log/confluent/confluent-firstboot.log
|
||||
cp -a /etc/confluent/ssh/* /etc/ssh/
|
||||
systemctl restart sshd
|
||||
systemctl restart ssh
|
||||
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
if [ ! -z "$rootpw" -a "$rootpw" != "null" ]; then
|
||||
echo root:$rootpw | chpasswd -e
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -33,7 +33,7 @@ done
|
||||
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
systemctl restart sshd
|
||||
systemctl restart ssh
|
||||
mkdir -p /etc/confluent
|
||||
export nodename confluent_profile confluent_mgr
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
|
||||
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
|
||||
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
|
||||
# subdirectory other than public.
|
||||
#
|
||||
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
|
||||
#
|
||||
# This file lists files to synchronize or merge to the deployed systems from the deployment server
|
||||
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
|
||||
# Note particularly the use of '->' to delineate source from target.
|
||||
# /some/path/hosts -> /etc/hosts
|
||||
|
||||
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
|
||||
# /etc/hosts
|
||||
|
||||
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
|
||||
# owner, group, and permissions in octal notation:
|
||||
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
|
||||
|
||||
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
|
||||
# contain the data in the source already in its entirety. This allows append in a fashion that
|
||||
# is friendly to being run repeatedly
|
||||
|
||||
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
|
||||
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
|
||||
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
|
||||
MERGE:
|
||||
# /etc/passwd
|
||||
# /etc/group
|
||||
@@ -37,10 +37,20 @@ else
|
||||
chroot . custom-installation/confluent/bin/clortho $NODENAME $MGR > /root/custom-installation/confluent/confluent.apikey
|
||||
APIKEY=$(cat /root/custom-installation/confluent/confluent.apikey)
|
||||
done
|
||||
MGR=[$MGR]
|
||||
nic=$(grep ^MANAGER /custom-installation/confluent/confluent.info|grep fe80::|sed -e s/.*%//|head -n 1)
|
||||
nic=$(ip link |grep ^$nic:|awk '{print $2}')
|
||||
DEVICE=${nic%:}
|
||||
if echo "$MGR" | grep -q ':'; then
|
||||
# IPv6 manager: wrap address in brackets and resolve interface from scoped manager entry.
|
||||
MGR=[$MGR]
|
||||
nic=$(grep ^MANAGER /custom-installation/confluent/confluent.info|grep fe80::|sed -e s/.*%//|head -n 1)
|
||||
nic=$(ip link |grep ^$nic:|awk '{print $2}')
|
||||
DEVICE=${nic%:}
|
||||
else
|
||||
# IPv4 routed deployment: use previously detected NIC, fallback to route lookup.
|
||||
if [ -f /tmp/autodetectnic ]; then
|
||||
DEVICE=$(cat /tmp/autodetectnic)
|
||||
else
|
||||
DEVICE=$(ip route get ${MGR} 2>/dev/null | head -1 | sed -n 's/.*dev \([^ ]*\).*/\1/p')
|
||||
fi
|
||||
fi
|
||||
IP=done
|
||||
fi
|
||||
if [ -z "$MGTIFACE" ]; then
|
||||
|
||||
@@ -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}')
|
||||
@@ -70,6 +97,62 @@ while ! grep NODENAME /custom-installation/confluent/confluent.info; do
|
||||
echo -n $(grep ^apitoken: cnflnt.yml|awk '{print $2}') > $hmackeyfile
|
||||
cd -
|
||||
umount $tmnt
|
||||
elif confluentsrv=$(sed -n 's/.*confluent=\([^ ]*\).*/\1/p' /proc/cmdline); [ ! -z "$confluentsrv" ]; then
|
||||
echo "confluent= kernel arg found: $confluentsrv" > /dev/console 2>&1
|
||||
. /scripts/functions
|
||||
|
||||
rmmod cdc_ether 2> /dev/null
|
||||
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 2>/dev/null
|
||||
echo 0 > /proc/sys/net/ipv6/conf/$dev/addr_gen_mode 2>/dev/null
|
||||
done
|
||||
unset DEVICE DEVICE6 IP IP6 dev
|
||||
|
||||
echo "Starting DHCP configure_networking..." > /dev/console 2>&1
|
||||
configure_networking
|
||||
echo "DHCP done, DEVICE=$DEVICE" > /dev/console 2>&1
|
||||
echo $DEVICE > /tmp/autodetectnic
|
||||
|
||||
RETRIES=0
|
||||
while [ $RETRIES -lt 5 ]; do
|
||||
if openssl s_client -connect $confluentsrv:443 </dev/null > /dev/null 2>&1; then
|
||||
echo "TLS connectivity to $confluentsrv OK" > /dev/console 2>&1
|
||||
break
|
||||
fi
|
||||
RETRIES=$((RETRIES + 1))
|
||||
echo "Cannot reach $confluentsrv:443, retry $RETRIES/5..." > /dev/console 2>&1
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [ $RETRIES -ge 5 ]; then
|
||||
echo "Failed to reach $confluentsrv after 5 retries, falling back to copernicus" > /dev/console 2>&1
|
||||
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
|
||||
continue
|
||||
fi
|
||||
|
||||
myids="uuid=$(cat /sys/devices/virtual/dmi/id/product_uuid)"
|
||||
for mac in $(ip link | grep 'link/ether' | awk '{print $2}'); do
|
||||
myids="$myids/mac=$mac"
|
||||
done
|
||||
echo "Calling whoami with IDs: $myids" > /dev/console 2>&1
|
||||
|
||||
myname=$( (printf "GET /confluent-api/self/whoami HTTP/1.0\r\nHost: $confluentsrv\r\nCONFLUENT_IDS: $myids\r\n\r\n"; sleep 3) \
|
||||
| openssl s_client -connect $confluentsrv:443 -quiet 2>/dev/null \
|
||||
| tail -1 | tr -d '\r\n')
|
||||
|
||||
echo "whoami returned: '$myname'" > /dev/console 2>&1
|
||||
|
||||
if [ ! -z "$myname" ]; then
|
||||
MGR=$confluentsrv
|
||||
echo "NODENAME: $myname" > /custom-installation/confluent/confluent.info
|
||||
echo "MANAGER: $confluentsrv" >> /custom-installation/confluent/confluent.info
|
||||
echo "EXTMGRINFO: $confluentsrv||1" >> /custom-installation/confluent/confluent.info
|
||||
else
|
||||
echo "whoami returned empty, retrying in 10s..." > /dev/console 2>&1
|
||||
sleep 10
|
||||
fi
|
||||
else
|
||||
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
|
||||
fi
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#!/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
|
||||
#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
|
||||
#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
|
||||
#fi
|
||||
mkdir -p $2/boot/efi/boot
|
||||
if [ -d $1/EFI/boot/ ]; then
|
||||
ln -s $1/EFI/boot/* $2/boot/efi/boot
|
||||
|
||||
@@ -23,6 +23,7 @@ touch /etc/cloud/cloud-init.disabled
|
||||
source /etc/confluent/functions
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
export confluent_mgr confluent_profile
|
||||
run_remote_python confignet
|
||||
run_remote_parts firstboot.d
|
||||
run_remote_config firstboot.d
|
||||
curl --capath /etc/confluent/tls -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -89,7 +89,6 @@ chroot /target update-ca-certificates
|
||||
chroot /target bash -c "source /etc/confluent/functions; run_remote_python autoconsole"
|
||||
|
||||
chroot /target bash -c "source /etc/confluent/functions; run_remote_python syncfileclient"
|
||||
chroot /target bash -c "source /etc/confluent/functions; run_remote_python confignet"
|
||||
chroot /target bash -c "source /etc/confluent/functions; run_remote_parts post.d"
|
||||
source /target/etc/confluent/functions
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ done
|
||||
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
systemctl restart sshd
|
||||
systemctl restart ssh
|
||||
mkdir -p /etc/confluent
|
||||
export nodename confluent_profile confluent_mgr
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
|
||||
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
|
||||
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
|
||||
# subdirectory other than public.
|
||||
#
|
||||
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
|
||||
#
|
||||
# This file lists files to synchronize or merge to the deployed systems from the deployment server
|
||||
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
|
||||
# Note particularly the use of '->' to delineate source from target.
|
||||
# /some/path/hosts -> /etc/hosts
|
||||
|
||||
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
|
||||
# /etc/hosts
|
||||
|
||||
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
|
||||
# owner, group, and permissions in octal notation:
|
||||
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
|
||||
|
||||
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
|
||||
# contain the data in the source already in its entirety. This allows append in a fashion that
|
||||
# is friendly to being run repeatedly
|
||||
|
||||
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
|
||||
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
|
||||
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
|
||||
MERGE:
|
||||
# /etc/passwd
|
||||
# /etc/group
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
ubuntu22.04/
|
||||
+1
@@ -0,0 +1 @@
|
||||
ubuntu20.04-diskless/
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#define COM1 0x3f8
|
||||
#define COM2 0x2f8
|
||||
@@ -19,6 +20,137 @@
|
||||
#define SPEED57600 6
|
||||
#define SPEED115200 7
|
||||
|
||||
typedef struct {
|
||||
char devnode[32];
|
||||
speed_t speed;
|
||||
int valid;
|
||||
} serial_port_t;
|
||||
|
||||
serial_port_t process_spcr() {
|
||||
serial_port_t result = {0};
|
||||
char buff[128];
|
||||
int fd;
|
||||
uint64_t address;
|
||||
int currspeed;
|
||||
|
||||
result.valid = 0;
|
||||
|
||||
fd = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (read(fd, buff, 80) < 80) {
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (buff[8] != 2) return result; // revision 2
|
||||
if (buff[36] != 0) return result; // 16550 only
|
||||
if (buff[40] != 1) return result; // IO only
|
||||
|
||||
address = *(uint64_t *)(buff + 44);
|
||||
currspeed = buff[58];
|
||||
|
||||
if (address == COM1) {
|
||||
strncpy(result.devnode, "/dev/ttyS0", sizeof(result.devnode));
|
||||
} else if (address == COM2) {
|
||||
strncpy(result.devnode, "/dev/ttyS1", sizeof(result.devnode));
|
||||
} else if (address == COM3) {
|
||||
strncpy(result.devnode, "/dev/ttyS2", sizeof(result.devnode));
|
||||
} else if (address == COM4) {
|
||||
strncpy(result.devnode, "/dev/ttyS3", sizeof(result.devnode));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (currspeed == SPEED9600) {
|
||||
result.speed = B9600;
|
||||
} else if (currspeed == SPEED19200) {
|
||||
result.speed = B19200;
|
||||
} else if (currspeed == SPEED57600) {
|
||||
result.speed = B57600;
|
||||
} else if (currspeed == SPEED115200) {
|
||||
result.speed = B115200;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.valid = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
serial_port_t identify_by_sys_vendor() {
|
||||
serial_port_t result = {0};
|
||||
char buff[128];
|
||||
FILE *f;
|
||||
|
||||
f = fopen("/sys/devices/virtual/dmi/id/sys_vendor", "r");
|
||||
if (f) {
|
||||
if (fgets(buff, sizeof(buff), f)) {
|
||||
if (strstr(buff, "Supermicro")) {
|
||||
strncpy(result.devnode, "/dev/ttyS1", sizeof(result.devnode));
|
||||
result.speed = B115200;
|
||||
result.valid = 1;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
serial_port_t search_serial_ports() {
|
||||
serial_port_t result = {0};
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
int fd;
|
||||
int status;
|
||||
int numfound= 0;
|
||||
int numpossible = 0;
|
||||
|
||||
dir = opendir("/dev");
|
||||
if (!dir) {
|
||||
return result;
|
||||
}
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strncmp(entry->d_name, "ttyS", 4) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char devpath[64];
|
||||
snprintf(devpath, sizeof(devpath), "/dev/%s", entry->d_name);
|
||||
|
||||
fd = open(devpath, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ioctl(fd, TIOCMGET, &status) == 0) {
|
||||
numpossible++;
|
||||
if (numfound < 1) {
|
||||
strncpy(result.devnode, devpath, sizeof(result.devnode));
|
||||
result.speed = B115200;
|
||||
}
|
||||
if (status & TIOCM_CAR) {
|
||||
strncpy(result.devnode, devpath, sizeof(result.devnode));
|
||||
numfound++;
|
||||
result.speed = B115200;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
if (numfound == 1 || numpossible == 1) {
|
||||
result.valid = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct termios tty;
|
||||
struct termios tty2;
|
||||
@@ -36,46 +168,38 @@ int main(int argc, char* argv[]) {
|
||||
char* offset;
|
||||
uint64_t address;
|
||||
bufflen = 0;
|
||||
tmpi = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
|
||||
if (tmpi < 0) {
|
||||
exit(0);
|
||||
}
|
||||
if (read(tmpi, buff, 80) < 80) {
|
||||
exit(0);
|
||||
}
|
||||
close(tmpi);
|
||||
if (buff[8] != 2) exit(0); //revision 2
|
||||
if (buff[36] != 0) exit(0); //16550 only
|
||||
if (buff[40] != 1) exit(0); //IO only
|
||||
address = *(uint64_t *)(buff + 44);
|
||||
currspeed = buff[58];
|
||||
offset = buff + 10;
|
||||
if (address == COM1) {
|
||||
strncpy(buff, "/dev/ttyS0", 128);
|
||||
} else if (address == COM2) {
|
||||
strncpy(buff, "/dev/ttyS1", 128);
|
||||
} else if (address == COM3) {
|
||||
strncpy(buff, "/dev/ttyS2", 128);
|
||||
} else if (address == COM4) {
|
||||
strncpy(buff, "/dev/ttyS3", 128);
|
||||
} else {
|
||||
#ifndef __x86_64__
|
||||
// Only x86 needs autoconsole, other platforms have reasonable default serial console
|
||||
exit(0);
|
||||
#endif
|
||||
serial_port_t spcr = process_spcr();
|
||||
if (!spcr.valid) {
|
||||
spcr = search_serial_ports();
|
||||
}
|
||||
if (!spcr.valid) {
|
||||
spcr = identify_by_sys_vendor();
|
||||
}
|
||||
if (!spcr.valid) {
|
||||
exit(0);
|
||||
}
|
||||
strncpy(buff, spcr.devnode, sizeof(buff));
|
||||
offset = strchr(buff, 0);
|
||||
currspeed = spcr.speed;
|
||||
ttyf = open(buff, O_RDWR | O_NOCTTY);
|
||||
if (ttyf < 0) {
|
||||
fprintf(stderr, "Unable to open tty\n");
|
||||
exit(1);
|
||||
}
|
||||
if (currspeed == SPEED9600) {
|
||||
if (currspeed == B9600) {
|
||||
cspeed = B9600;
|
||||
strncpy(offset, ",9600", 6);
|
||||
} else if (currspeed == SPEED19200) {
|
||||
} else if (currspeed == B19200) {
|
||||
cspeed = B19200;
|
||||
strncpy(offset, ",19200", 7);
|
||||
} else if (currspeed == SPEED57600) {
|
||||
} else if (currspeed == B57600) {
|
||||
cspeed = B57600;
|
||||
strncpy(offset, ",57600", 7);
|
||||
} else if (currspeed == SPEED115200) {
|
||||
} else if (currspeed == B115200) {
|
||||
cspeed = B115200;
|
||||
strncpy(offset, ",115200", 8);
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -213,97 +213,101 @@ int main(int argc, char* argv[]) {
|
||||
memset(msg, 0, 1024);
|
||||
/* Deny packet access to the last 24 bytes to assure null */
|
||||
recvfrom(n4, msg, 1000, 0, (struct sockaddr *)&dst4, &dst4size);
|
||||
if (nodenameidx = strstr(msg, "NODENAME: ")) {
|
||||
if (strstr(msg, "HTTP/1.1 200 OK")) {
|
||||
if (nodenameidx = strstr(msg, "NODENAME: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
nodenameidx = strstr(nodename, "\r");
|
||||
if (nodenameidx) { nodenameidx[0] = 0; }
|
||||
if (strncmp(lastnodename, nodename, 1024) != 0) {
|
||||
printf("NODENAME: %s\n", nodename);
|
||||
strncpy(lastnodename, nodename, 1024);
|
||||
}
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "CURRTIME: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
nodenameidx = strstr(nodename, "\r");
|
||||
if (nodenameidx) { nodenameidx[0] = 0; }
|
||||
if (strncmp(lastnodename, nodename, 1024) != 0) {
|
||||
printf("NODENAME: %s\n", nodename);
|
||||
strncpy(lastnodename, nodename, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
}
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "CURRTIME: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
settime = strtol(nodename, NULL, 10);
|
||||
}
|
||||
settime = strtol(nodename, NULL, 10);
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "CURRMSECS: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
if (nodenameidx = strstr(msg, "CURRMSECS: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
}
|
||||
setusec = strtol(nodename, NULL, 10) * 1000;
|
||||
}
|
||||
memset(msg, 0, 1024);
|
||||
inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size);
|
||||
/* Take measure from printing out the same ip twice in a row */
|
||||
if (strncmp(lastmsg, msg, 1024) != 0) {
|
||||
sendto(n4, "PING", 4, 0, (const struct sockaddr *)&dst4, dst4size);
|
||||
printf("MANAGER: %s\n", msg);
|
||||
strncpy(lastmsg, msg, 1024);
|
||||
}
|
||||
setusec = strtol(nodename, NULL, 10) * 1000;
|
||||
}
|
||||
memset(msg, 0, 1024);
|
||||
inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size);
|
||||
/* Take measure from printing out the same ip twice in a row */
|
||||
if (strncmp(lastmsg, msg, 1024) != 0) {
|
||||
sendto(n4, "PING", 4, 0, (const struct sockaddr *)&dst4, dst4size);
|
||||
printf("MANAGER: %s\n", msg);
|
||||
strncpy(lastmsg, msg, 1024);
|
||||
}
|
||||
}
|
||||
if (FD_ISSET(ns, &rfds)) {
|
||||
memset(msg, 0, 1024);
|
||||
/* Deny packet access to the last 24 bytes to assure null */
|
||||
recvfrom(ns, msg, 1000, 0, (struct sockaddr *)&dst, &dstsize);
|
||||
if (nodenameidx = strstr(msg, "NODENAME: ")) {
|
||||
if (strstr(msg, "HTTP/1.1 200 OK")) {
|
||||
if (nodenameidx = strstr(msg, "NODENAME: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
nodenameidx = strstr(nodename, "\r");
|
||||
if (nodenameidx) { nodenameidx[0] = 0; }
|
||||
if (strncmp(lastnodename, nodename, 1024) != 0) {
|
||||
printf("NODENAME: %s\n", nodename);
|
||||
strncpy(lastnodename, nodename, 1024);
|
||||
}
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "CURRTIME: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
nodenameidx = strstr(nodename, "\r");
|
||||
if (nodenameidx) { nodenameidx[0] = 0; }
|
||||
if (strncmp(lastnodename, nodename, 1024) != 0) {
|
||||
printf("NODENAME: %s\n", nodename);
|
||||
strncpy(lastnodename, nodename, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
}
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "CURRTIME: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
settime = strtol(nodename, NULL, 10);
|
||||
}
|
||||
settime = strtol(nodename, NULL, 10);
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "DEFAULTNET: 1")) {
|
||||
isdefault = 1;
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "MGTIFACE: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(mgtifname, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(mgtifname, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
if (nodenameidx = strstr(msg, "DEFAULTNET: 1")) {
|
||||
isdefault = 1;
|
||||
}
|
||||
}
|
||||
if (nodenameidx = strstr(msg, "CURRMSECS: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
if (nodenameidx = strstr(msg, "MGTIFACE: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(mgtifname, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(mgtifname, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
}
|
||||
}
|
||||
setusec = strtol(nodename, NULL, 10) * 1000;
|
||||
}
|
||||
memset(msg, 0, 1024);
|
||||
inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize);
|
||||
if (strncmp(last6msg, msg, 1024) != 0 || lastidx != dst.sin6_scope_id) {
|
||||
lastidx = dst.sin6_scope_id;
|
||||
sendto(ns, "PING", 4, 0, (const struct sockaddr *)&dst, dstsize);
|
||||
printf("MANAGER: %s", msg);
|
||||
if (strncmp(msg, "fe80::", 6) == 0) {
|
||||
printf("%%%u", dst.sin6_scope_id);
|
||||
if (nodenameidx = strstr(msg, "CURRMSECS: ")) {
|
||||
nodenameidx += 10;
|
||||
strncpy(nodename, nodenameidx, 1024);
|
||||
if (nodenameidx = strstr(nodename, "\r")) {
|
||||
nodenameidx[0] = 0;
|
||||
}
|
||||
setusec = strtol(nodename, NULL, 10) * 1000;
|
||||
}
|
||||
printf("\n");
|
||||
printf("EXTMGRINFO: %s", msg);
|
||||
if (strncmp(msg, "fe80::", 6) == 0) {
|
||||
printf("%%%u", dst.sin6_scope_id);
|
||||
memset(msg, 0, 1024);
|
||||
inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize);
|
||||
if (strncmp(last6msg, msg, 1024) != 0 || lastidx != dst.sin6_scope_id) {
|
||||
lastidx = dst.sin6_scope_id;
|
||||
sendto(ns, "PING", 4, 0, (const struct sockaddr *)&dst, dstsize);
|
||||
printf("MANAGER: %s", msg);
|
||||
if (strncmp(msg, "fe80::", 6) == 0) {
|
||||
printf("%%%u", dst.sin6_scope_id);
|
||||
}
|
||||
printf("\n");
|
||||
printf("EXTMGRINFO: %s", msg);
|
||||
if (strncmp(msg, "fe80::", 6) == 0) {
|
||||
printf("%%%u", dst.sin6_scope_id);
|
||||
}
|
||||
printf("|%s|%d\n", mgtifname, isdefault);
|
||||
strncpy(last6msg, msg, 1024);
|
||||
}
|
||||
printf("|%s|%d\n", mgtifname, isdefault);
|
||||
strncpy(last6msg, msg, 1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,8 +132,8 @@ static int http_read(const char *path, char *buf, size_t size, off_t offset,
|
||||
|
||||
if (strcmp(path, filename) != 0) return -ENOENT;
|
||||
memset(headbuffer, 0, 512);
|
||||
if (offset >= filesize) return 0;
|
||||
if (offset + size - 1 >= filesize) size = filesize - offset - 1;
|
||||
if (offset >= filesize || size == 0) return 0;
|
||||
if (offset + size > filesize) size = filesize - offset;
|
||||
snprintf(headbuffer, 512, "%ld-%ld", offset, offset + size - 1);
|
||||
if (curl_easy_setopt(curl, CURLOPT_RANGE, headbuffer) != CURLE_OK) {
|
||||
fprintf(stderr, "Error setting range\n");
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -49,6 +49,7 @@ def main(args):
|
||||
wiz.add_argument('-p', help='Copy in TFTP contents required for PXE support', action='store_true')
|
||||
wiz.add_argument('-i', help='Interactively prompt for behaviors', action='store_true')
|
||||
wiz.add_argument('-l', help='Set up local management node to allow login from managed nodes', action='store_true')
|
||||
wiz.add_argument('-r', help='Repack site contents if present', action='store_true')
|
||||
osip = sp.add_parser('importcheck', help='Check import of an OS image from an ISO image')
|
||||
osip.add_argument('imagefile', help='File to use for source of importing')
|
||||
osip = sp.add_parser('import', help='Import an OS image from an ISO image')
|
||||
@@ -367,6 +368,8 @@ def initialize(cmdset):
|
||||
rc = initialize_genesis()
|
||||
if rc != 0:
|
||||
sys.exit(rc)
|
||||
if cmdset.r:
|
||||
didsomething = True
|
||||
if not didsomething and (cmdset.k or cmdset.l or cmdset.g or cmdset.p):
|
||||
if cmdset.g:
|
||||
updateboot('genesis-x86_64')
|
||||
|
||||
@@ -94,6 +94,7 @@ _allowedbyrole = {
|
||||
'/node*/power/state',
|
||||
'/node*/sensors/*',
|
||||
'/node*/attributes/current',
|
||||
'/node*/attributes/all',
|
||||
'/node*/description',
|
||||
'/noderange/*/nodes/',
|
||||
'/nodes/',
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
import os
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.util as util
|
||||
from os.path import exists
|
||||
import datetime
|
||||
import shutil
|
||||
import socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import tempfile
|
||||
try:
|
||||
import cryptography.x509 as x509
|
||||
except ImportError:
|
||||
x509 = None
|
||||
|
||||
def mkdirp(targ):
|
||||
try:
|
||||
@@ -178,6 +189,17 @@ def assure_tls_ca():
|
||||
os.symlink(certname, hashname)
|
||||
finally:
|
||||
os.seteuid(ouid)
|
||||
return certout
|
||||
|
||||
#def is_self_signed(pem):
|
||||
# cert = ssl.PEM_cert_to_DER_cert(pem)
|
||||
# return cert.get('subjectAltName', []) == cert.get('issuer', [])
|
||||
# x509 certificate issuer subject comparison..
|
||||
#>>> b.issuer
|
||||
#<Name(C=US,ST=NC,L=RTP,O=Lenovo,CN=XCC-7D9D-J102MM2T)>
|
||||
#>>> b.subject
|
||||
#<Name(C=US,ST=NC,L=RTP,O=Lenovo,CN=XCC-7D9D-J102MM2T)>
|
||||
|
||||
|
||||
def substitute_cfg(setting, key, val, newval, cfgfile, line):
|
||||
if key.strip() == setting:
|
||||
@@ -222,7 +244,7 @@ def create_full_ca(certout):
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
continue
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = CA:true\n\n[ca_confluent]\n')
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\nkeyUsage = critical,keyCertSign,cRLSign\n[ca_confluent]\n')
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
@@ -231,7 +253,7 @@ def create_full_ca(certout):
|
||||
subprocess.check_call(
|
||||
['openssl', 'ca', '-config', newcfg, '-batch', '-selfsign',
|
||||
'-extensions', 'CACert', '-extfile', newcfg,
|
||||
'-notext', '-startdate',
|
||||
'-notext', '-md', 'sha384', '-startdate',
|
||||
'19700101010101Z', '-enddate', '21000101010101Z', '-keyfile',
|
||||
keyout, '-out', '/etc/confluent/tls/ca/cacert.pem', '-in', csrout]
|
||||
)
|
||||
@@ -257,7 +279,7 @@ def create_simple_ca(keyout, certout):
|
||||
if len(subj) > 68:
|
||||
subj = subj[:68]
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = CA:true\n')
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\n')
|
||||
subprocess.check_call([
|
||||
'openssl', 'req', '-new', '-x509', '-key', keyout, '-days',
|
||||
'27300', '-out', certout, '-subj', subj,
|
||||
@@ -266,70 +288,112 @@ def create_simple_ca(keyout, certout):
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
|
||||
def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
if not keyout:
|
||||
def create_certificate(keyout=None, certout=None, csrfile=None, subj=None, san=None, backdate=True, days=None):
|
||||
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
||||
if backdate:
|
||||
# To deal with wildly off clocks, we backdate certificates.
|
||||
startdate = '20000101010101Z'
|
||||
else:
|
||||
# apply a mild backdate anyway, even if these are supposed to be for more accurate clocks
|
||||
startdate = (now_utc - datetime.timedelta(hours=24)).strftime('%Y%m%d%H%M%SZ')
|
||||
if days is None:
|
||||
enddate = '21000101010101Z'
|
||||
else:
|
||||
enddate = (now_utc + datetime.timedelta(days=days)).strftime('%Y%m%d%H%M%SZ')
|
||||
tlsmateriallocation = {}
|
||||
if not certout:
|
||||
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:
|
||||
if (not keyout and not csrfile) or not certout:
|
||||
raise Exception('Unable to locate TLS certificate path automatically')
|
||||
assure_tls_ca()
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = shortname # socket.getfqdn()
|
||||
if not csrout:
|
||||
cacertname = assure_tls_ca()
|
||||
if not subj:
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = shortname # socket.getfqdn()
|
||||
subj = '/CN={0}'.format(longname)
|
||||
elif '/CN=' not in subj:
|
||||
subj = '/CN={0}'.format(subj)
|
||||
if not csrfile:
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
ipaddrs = list(get_ip_addresses())
|
||||
san = ['IP:{0}'.format(x) for x in ipaddrs]
|
||||
# It is incorrect to put IP addresses as DNS type. However
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
# san.extend(['DNS:{0}'.format(x) for x in ipaddrs])
|
||||
dnsnames = set(ipaddrs)
|
||||
dnsnames.add(shortname)
|
||||
for currip in ipaddrs:
|
||||
dnsnames.add(socket.getnameinfo((currip, 0), 0)[0])
|
||||
for currname in dnsnames:
|
||||
san.append('DNS:{0}'.format(currname))
|
||||
#san.append('DNS:{0}'.format(longname))
|
||||
san = ','.join(san)
|
||||
permitdomains = []
|
||||
if x509:
|
||||
# check if this CA has name constraints, and avoid violating them
|
||||
with open(cacertname, 'rb') as f:
|
||||
cer = x509.load_pem_x509_certificate(f.read())
|
||||
for extension in cer.extensions:
|
||||
if extension.oid == x509.ExtensionOID.NAME_CONSTRAINTS:
|
||||
nc = extension.value
|
||||
for pname in nc.permitted_subtrees:
|
||||
permitdomains.append(pname.value)
|
||||
if not san:
|
||||
ipaddrs = list(get_ip_addresses())
|
||||
if not permitdomains:
|
||||
san = ['IP:{0}'.format(x) for x in ipaddrs]
|
||||
# It is incorrect to put IP addresses as DNS type. However
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
# san.extend(['DNS:{0}'.format(x) for x in ipaddrs])
|
||||
dnsnames = set(ipaddrs)
|
||||
dnsnames.add(shortname)
|
||||
dnsnames.add(longname)
|
||||
else:
|
||||
# nameconstraints preclude IP and shortname
|
||||
san = []
|
||||
dnsnames = set()
|
||||
for suffix in permitdomains:
|
||||
if longname.endswith(suffix):
|
||||
dnsnames.add(longname)
|
||||
break
|
||||
for currip in ipaddrs:
|
||||
currname = socket.getnameinfo((currip, 0), 0)[0]
|
||||
for suffix in permitdomains:
|
||||
if currname.endswith(suffix):
|
||||
dnsnames.add(currname)
|
||||
break
|
||||
if not permitdomains:
|
||||
dnsnames.add(currname)
|
||||
for currname in dnsnames:
|
||||
san.append('DNS:{0}'.format(currname))
|
||||
#san.append('DNS:{0}'.format(longname))
|
||||
san = ','.join(san)
|
||||
sslcfg = get_openssl_conf_location()
|
||||
tmphdl, tmpconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
tmphdl, extconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
needcsr = False
|
||||
if csrout is None:
|
||||
if csrfile is None:
|
||||
needcsr = True
|
||||
tmphdl, csrout = tempfile.mkstemp()
|
||||
tmphdl, csrfile = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
shutil.copy2(sslcfg, tmpconfig)
|
||||
try:
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=critical,CA:false\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=serverAuth,clientAuth\nsubjectAltName={0}'.format(san))
|
||||
if needcsr:
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\nsubjectAltName={0}'.format(san))
|
||||
subprocess.check_call([
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj',
|
||||
'/CN={0}'.format(longname),
|
||||
'-extensions', 'SAN', '-config', tmpconfig
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrfile, '-subj',
|
||||
subj, '-extensions', 'SAN', '-config', tmpconfig
|
||||
])
|
||||
else:
|
||||
# when used manually, allow the csr SAN to stand
|
||||
# may add explicit subj/SAN argument, in which case we would skip copy
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\ncopy_extensions=copy\n')
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\n')
|
||||
#else:
|
||||
# # when used manually, allow the csr SAN to stand
|
||||
# # may add explicit subj/SAN argument, in which case we would skip copy
|
||||
# #with open(tmpconfig, 'a') as cfgfile:
|
||||
# # cfgfile.write('\ncopy_extensions=copy\n')
|
||||
# with open(extconfig, 'a') as cfgfile:
|
||||
# cfgfile.write('\nbasicConstraints=CA:false\n')
|
||||
if os.path.exists('/etc/confluent/tls/cakey.pem'):
|
||||
# simple style CA in effect, make a random serial number and
|
||||
# hope for the best, and accept inability to backdate the cert
|
||||
serialnum = '0x' + ''.join(['{:02x}'.format(x) for x in bytearray(os.urandom(20))])
|
||||
subprocess.check_call([
|
||||
'openssl', 'x509', '-req', '-in', csrout,
|
||||
'openssl', 'x509', '-req', '-in', csrfile,
|
||||
'-CA', '/etc/confluent/tls/cacert.pem',
|
||||
'-CAkey', '/etc/confluent/tls/cakey.pem',
|
||||
'-set_serial', serialnum, '-out', certout, '-days', '27300',
|
||||
@@ -348,12 +412,11 @@ def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
shutil.copy2(cacfgfile, tmpcafile)
|
||||
os.close(tmphdl)
|
||||
cacfgfile = tmpcafile
|
||||
# with realcalock: # if we put it in server, we must lock it
|
||||
subprocess.check_call([
|
||||
'openssl', 'ca', '-config', cacfgfile,
|
||||
'-in', csrout, '-out', certout, '-batch', '-notext',
|
||||
'-startdate', '19700101010101Z', '-enddate', '21000101010101Z',
|
||||
'-extfile', extconfig
|
||||
'openssl', 'ca', '-config', cacfgfile, '-rand_serial',
|
||||
'-in', csrfile, '-out', certout, '-batch', '-notext',
|
||||
'-startdate', startdate, '-enddate', enddate, '-md', 'sha384',
|
||||
'-extfile', extconfig, '-subj', subj
|
||||
])
|
||||
for keycopy in tlsmateriallocation.get('keys', []):
|
||||
if keycopy != keyout:
|
||||
@@ -381,18 +444,43 @@ def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
if needcsr:
|
||||
os.remove(csrout)
|
||||
print(extconfig) # os.remove(extconfig)
|
||||
os.remove(csrfile)
|
||||
os.remove(extconfig)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import ipaddress
|
||||
outdir = os.getcwd()
|
||||
keyout = os.path.join(outdir, 'key.pem')
|
||||
certout = os.path.join(outdir, sys.argv[2] + 'cert.pem')
|
||||
certout = os.path.join(outdir, 'cert.pem')
|
||||
csrout = None
|
||||
subj, san = (None, None)
|
||||
try:
|
||||
bindex = sys.argv.index('-b')
|
||||
bmcnode = sys.argv.pop(bindex + 1) # Remove bmcnode argument
|
||||
sys.argv.pop(bindex) # Remove -b flag
|
||||
import confluent.config.configmanager as cfm
|
||||
c = cfm.ConfigManager(None)
|
||||
subj, san = util.get_bmc_subject_san(c, bmcnode)
|
||||
except ValueError:
|
||||
bindex = None
|
||||
if subj is None:
|
||||
try:
|
||||
sans = set()
|
||||
sindex = sys.argv.index('-s')
|
||||
subj = sys.argv.pop(sindex + 1) # Remove subject argument
|
||||
sys.argv.pop(sindex) # Remove -s flag
|
||||
try:
|
||||
ipaddress.ip_address(subj)
|
||||
sans.add('IP:{0}'.format(subj))
|
||||
except ValueError:
|
||||
sans.add('DNS:{0}'.format(subj))
|
||||
san = ','.join(sans) if sans else None
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
csrout = sys.argv[1]
|
||||
except IndexError:
|
||||
csrout = None
|
||||
create_certificate(keyout, certout, csrout)
|
||||
create_certificate(keyout, certout, csrout, subj, san, backdate=False, days=3650)
|
||||
|
||||
@@ -716,6 +716,7 @@ def become_leader(connection):
|
||||
if reassimilate is not None:
|
||||
reassimilate.kill()
|
||||
reassimilate = eventlet.spawn(reassimilate_missing)
|
||||
cfm._init_indexes()
|
||||
cfm._ready = True
|
||||
if _assimilate_missing(skipaddr):
|
||||
schedule_rebalance()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2019 Lenovo
|
||||
# Copyright 2015-2025 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -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', 'proxmox'),
|
||||
'validvalues': ('ssh', 'ipmi', 'openbmc', 'megasol', 'tsmsol', 'vcenter', 'proxmox'),
|
||||
},
|
||||
# 'virtualization.host': {
|
||||
# 'description': ('Hypervisor where this node does/should reside'),
|
||||
@@ -405,6 +408,12 @@ node = {
|
||||
'include /<prefixlen> CIDR suffix to indicate subnet length, which is '
|
||||
'autodetected by default where possible.',
|
||||
},
|
||||
'hardwaremanagement.manager_tls_name': {
|
||||
'description': 'A name to use in lieu of the value in hardwaremanagement.manager for '
|
||||
'TLS certificate verification purposes. Some strategies involve a non-IP, '
|
||||
'non-resolvable name, or this can be used to access by IP while using name-based '
|
||||
'validation',
|
||||
},
|
||||
'hardwaremanagement.method': {
|
||||
'description': 'The method used to perform operations such as power '
|
||||
'control, get sensor data, get inventory, and so on. '
|
||||
@@ -441,6 +450,9 @@ node = {
|
||||
#IBM Flex)''',
|
||||
# 'appliesto': ['system'],
|
||||
# },
|
||||
'id.index': {
|
||||
'description': 'Confluent generated numeric index for the node.',
|
||||
},
|
||||
'id.model': {
|
||||
'description': 'The model number of a node. In scenarios where there '
|
||||
'is both a name and a model number, it is generally '
|
||||
@@ -466,17 +478,17 @@ node = {
|
||||
'the discovery process to decide where to place the mac address of a detected PXE nic.',
|
||||
},
|
||||
'net.connection_name': {
|
||||
'description': 'Name to use when specifiying a name for connection and/or interface name for a team. This may be the name of a team interface, '
|
||||
'description': 'Name to use when specifiying a name for connection and/or interface name for a team/bond. This may be the name of a team/bond interface, '
|
||||
'the connection name in network manager for the interface, or may be installed as an altname '
|
||||
'as supported by the respective OS deployment profiles. Default is to accept default name for '
|
||||
'a team consistent with the respective OS, or to use the matching original port name as connection name.'
|
||||
'a team/bond consistent with the respective OS, or to use the matching original port name as connection name.'
|
||||
},
|
||||
'net.interface_names': {
|
||||
'description': 'Interface name or comma delimited list of names to match for this interface. It is generally recommended '
|
||||
'to leave this blank unless needing to set up interfaces that are not on a common subnet with a confluent server, '
|
||||
'as confluent servers provide autodetection for matching the correct network definition to an interface. '
|
||||
'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.'
|
||||
'a team/bond or a single interface for VLAN/PKEY connections.'
|
||||
},
|
||||
'net.mtu': {
|
||||
'description': 'MTU to apply to this connection',
|
||||
@@ -562,7 +574,7 @@ node = {
|
||||
'operating system',
|
||||
},
|
||||
'net.team_mode': {
|
||||
'description': 'Indicates that this interface should be a team and what mode or runner to use when teamed. '
|
||||
'description': 'Indicates that this interface should be a team/bond and what mode or runner to use when teamed or bonded. '
|
||||
'If this covers a deployment interface, one of the member interfaces may be brought up as '
|
||||
'a standalone interface until deployment is complete, as supported by the OS deployment profile. '
|
||||
'To support this scenario, the switch should be set up to allow independent operation of member ports (e.g. lacp bypass mode or fallback mode).',
|
||||
@@ -596,6 +608,10 @@ node = {
|
||||
'description': ('SNMPv1 community string, it is highly recommended to'
|
||||
'step up to SNMPv3'),
|
||||
},
|
||||
'snmp.privacyprotocol': {
|
||||
'description': 'The privacy protocol to use for SNMPv3',
|
||||
'valid_values': ('aes', 'des'),
|
||||
},
|
||||
# 'secret.snmplocalizedkey': {
|
||||
# 'description': ("SNMPv3 key localized to this node's SNMP Engine id"
|
||||
# 'This can be used in lieu of snmppassphrase to avoid'
|
||||
|
||||
@@ -164,6 +164,45 @@ def _mkpath(pathname):
|
||||
raise
|
||||
|
||||
|
||||
def _count_freeindexes(freeindexes):
|
||||
count = 0
|
||||
for idx in freeindexes:
|
||||
if isinstance(idx, list):
|
||||
for subidx in range(idx[0], idx[1] + 1):
|
||||
count += 1
|
||||
else:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def _is_free_index(freeindexes, idx):
|
||||
for freeidx in freeindexes:
|
||||
if isinstance(freeidx, list):
|
||||
if freeidx[0] <= idx <= freeidx[1]:
|
||||
return True
|
||||
else:
|
||||
if freeidx == idx:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _remove_free_index(freeindexes, idx):
|
||||
for i, freeidx in enumerate(freeindexes):
|
||||
if isinstance(freeidx, list):
|
||||
if freeidx[0] <= idx <= freeidx[1]:
|
||||
if freeidx[0] == freeidx[1]:
|
||||
del freeindexes[i]
|
||||
elif freeidx[0] == idx:
|
||||
freeindexes[i][0] += 1
|
||||
elif freeidx[1] == idx:
|
||||
freeindexes[i][1] -= 1
|
||||
else:
|
||||
freeindexes.insert(i + 1, [idx + 1, freeidx[1]])
|
||||
freeindexes[i][1] = idx - 1
|
||||
return
|
||||
else:
|
||||
if freeidx == idx:
|
||||
del freeindexes[i]
|
||||
return
|
||||
|
||||
def _derive_keys(password, salt):
|
||||
#implement our specific combination of pbkdf2 transforms to get at
|
||||
#key. We bump the iterations up because we can afford to
|
||||
@@ -334,7 +373,7 @@ def _rpc_rename_nodes(tenant, renamemap):
|
||||
|
||||
|
||||
def _rpc_rename_nodegroups(tenant, renamemap):
|
||||
ConfigManager(tenant)._true_rename_nodegroups(renamemap)
|
||||
ConfigManager(tenant)._true_rename_groups(renamemap)
|
||||
|
||||
|
||||
def _rpc_set_group_attributes(tenant, attribmap, autocreate):
|
||||
@@ -974,6 +1013,14 @@ def del_collective_member(name):
|
||||
if cfgstreams:
|
||||
exec_on_followers_unconditional('_true_del_collective_member', name)
|
||||
_true_del_collective_member(name)
|
||||
if cfgstreams:
|
||||
_hasquorum = has_quorum()
|
||||
pushes = eventlet.GreenPool()
|
||||
payload = msgpack.packb({'quorum': _hasquorum}, use_bin_type=False)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s]['stream'], payload) for s in cfgstreams]):
|
||||
pass
|
||||
|
||||
def _true_del_collective_member(name, sync=True):
|
||||
global cfgleader
|
||||
@@ -1142,7 +1189,10 @@ class _ExpressionFormat(string.Formatter):
|
||||
# such as 'net.pxe.hwaddr'
|
||||
key = '.' + left.attr + key
|
||||
left = left.value
|
||||
key = left.id + key
|
||||
if isinstance(left, ast.Name):
|
||||
key = left.id + key
|
||||
else:
|
||||
raise ValueError("Invalid AST structure: expected ast.Name at end of attribute chain")
|
||||
if (not key.startswith('custom.') and
|
||||
_get_valid_attrname(key) not in allattributes.node):
|
||||
raise ValueError(
|
||||
@@ -1203,6 +1253,32 @@ class _ExpressionFormat(string.Formatter):
|
||||
return strval[index]
|
||||
elif isinstance(node, ast.Constant):
|
||||
return node.value
|
||||
elif isinstance(node, ast.Call):
|
||||
key = ''
|
||||
baseval = ''
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
fun_name = node.func.attr
|
||||
baseval = self._handle_ast_node(node.func.value)
|
||||
else:
|
||||
raise ValueError("Invalid function call syntax in expression")
|
||||
if fun_name == 'replace':
|
||||
if len(node.args) != 2:
|
||||
raise ValueError("Invalid number of arguments to replace")
|
||||
arg1 = self._handle_ast_node(node.args[0])
|
||||
arg2 = self._handle_ast_node(node.args[1])
|
||||
return baseval.replace(arg1, arg2)
|
||||
elif fun_name == 'upper':
|
||||
return baseval.upper()
|
||||
elif fun_name == 'lower':
|
||||
return baseval.lower()
|
||||
elif fun_name == 'block_number':
|
||||
chunk_size = self._handle_ast_node(node.args[0])
|
||||
return (int(baseval) - 1) // chunk_size + 1
|
||||
elif fun_name == 'block_offset':
|
||||
chunk_size = self._handle_ast_node(node.args[0])
|
||||
return (int(baseval) - 1) % chunk_size + 1
|
||||
else:
|
||||
raise ValueError("Unsupported function in expression")
|
||||
else:
|
||||
raise ValueError("Unrecognized expression syntax")
|
||||
|
||||
@@ -1306,7 +1382,7 @@ class ConfigManager(object):
|
||||
return _cfgstore['main']
|
||||
return _cfgstore['tenant'][self.tenant]
|
||||
|
||||
def __init__(self, tenant, decrypt=False, username=None):
|
||||
def __init__(self, tenant, decrypt=False, username=None, create_tenant=False):
|
||||
self.clientfiles = {}
|
||||
global _cfgstore
|
||||
self.inrestore = False
|
||||
@@ -1328,12 +1404,14 @@ class ConfigManager(object):
|
||||
self._cfgstore['nodes'] = {}
|
||||
self._bg_sync_to_file()
|
||||
return
|
||||
elif 'tenant' not in _cfgstore:
|
||||
elif 'tenant' not in _cfgstore and create_tenant:
|
||||
_cfgstore['tenant'] = {tenant: {}}
|
||||
self._bg_sync_to_file()
|
||||
elif tenant not in _cfgstore['tenant']:
|
||||
elif tenant not in _cfgstore['tenant'] and create_tenant:
|
||||
_cfgstore['tenant'][tenant] = {}
|
||||
self._bg_sync_to_file()
|
||||
elif tenant and tenant not in _cfgstore.get('tenant', {}):
|
||||
raise ValueError("Tenant {0} does not exist".format(tenant))
|
||||
self.tenant = tenant
|
||||
if 'nodegroups' not in self._cfgstore:
|
||||
self._cfgstore['nodegroups'] = {'everything': {}}
|
||||
@@ -1344,6 +1422,9 @@ class ConfigManager(object):
|
||||
self.wait_for_sync()
|
||||
|
||||
def add_client_file(self, clientfile):
|
||||
filename = os.path.normpath(clientfile.filename)
|
||||
if filename.startswith('../') or filename.startswith('..\\'):
|
||||
raise ValueError("Invalid filename: {0}".format(clientfile.filename))
|
||||
self.clientfiles[clientfile.filename] = clientfile.fileobject
|
||||
|
||||
def close_client_files(self):
|
||||
@@ -2206,7 +2287,7 @@ class ConfigManager(object):
|
||||
watcher = self._nodecollwatchers[self.tenant][watcher]
|
||||
watcher(added=(), deleting=nodes, renamed=(), configmanager=self)
|
||||
changeset = {}
|
||||
for node in nodes:
|
||||
for node in confluent.util.natural_sort(nodes):
|
||||
# set a reserved attribute for the sake of the change notification
|
||||
# framework to trigger on
|
||||
changeset[node] = {'_nodedeleted': 1}
|
||||
@@ -2214,6 +2295,29 @@ class ConfigManager(object):
|
||||
if node in self._cfgstore['nodes']:
|
||||
self._sync_groups_to_node(node=node, groups=[],
|
||||
changeset=changeset)
|
||||
nidx = self._cfgstore['nodes'][node].get('id.index', {}).get('value', None)
|
||||
if nidx is not None:
|
||||
currmaxidx = get_global('max_node_index')
|
||||
freeindexes = get_global('free_node_indexes')
|
||||
if not freeindexes:
|
||||
freeindexes = []
|
||||
if nidx == currmaxidx - 1:
|
||||
currmaxidx = currmaxidx - 1
|
||||
while _is_free_index(freeindexes, currmaxidx - 1):
|
||||
_remove_free_index(freeindexes, currmaxidx - 1)
|
||||
currmaxidx = currmaxidx - 1
|
||||
set_global('max_node_index', currmaxidx)
|
||||
else:
|
||||
lastindex = freeindexes[-1] if freeindexes else [-2, -2]
|
||||
if not isinstance(lastindex, list):
|
||||
lastindex = [lastindex, lastindex]
|
||||
if nidx == lastindex[1] + 1:
|
||||
lastindex[1] = nidx
|
||||
if freeindexes:
|
||||
freeindexes[-1] = lastindex
|
||||
else:
|
||||
freeindexes.append(nidx)
|
||||
set_global('free_node_indexes', freeindexes)
|
||||
del self._cfgstore['nodes'][node]
|
||||
_mark_dirtykey('nodes', node, self.tenant)
|
||||
self._notif_attribwatchers(changeset)
|
||||
@@ -2491,12 +2595,29 @@ class ConfigManager(object):
|
||||
attrname, node)
|
||||
raise ValueError(errstr)
|
||||
attribmap[node][attrname] = attrval
|
||||
for node in attribmap:
|
||||
for node in confluent.util.natural_sort(attribmap):
|
||||
node = confluent.util.stringify(node)
|
||||
exprmgr = None
|
||||
if node not in self._cfgstore['nodes']:
|
||||
newnodes.append(node)
|
||||
self._cfgstore['nodes'][node] = {}
|
||||
freeindexes = get_global('free_node_indexes')
|
||||
if not freeindexes:
|
||||
freeindexes = []
|
||||
if _count_freeindexes(freeindexes) > 128: # tend to leave freed indexes disused until a lot have accumulated
|
||||
if isinstance(freeindexes[0], list):
|
||||
nidx = freeindexes[0][0]
|
||||
freeindexes[0][0] = nidx + 1
|
||||
if freeindexes[0][0] == freeindexes[0][1]:
|
||||
freeindexes[0] = freeindexes[0][0]
|
||||
else:
|
||||
nidx = freeindexes.pop(0)
|
||||
set_global('free_node_indexes', freeindexes)
|
||||
else:
|
||||
nidx = get_global('max_node_index')
|
||||
if nidx is None:
|
||||
nidx = 1
|
||||
set_global('max_node_index', nidx + 1)
|
||||
self._cfgstore['nodes'][node] = {'id.index': {'value': nidx}}
|
||||
cfgobj = self._cfgstore['nodes'][node]
|
||||
recalcexpressions = False
|
||||
for attrname in attribmap[node]:
|
||||
@@ -3129,6 +3250,29 @@ def get_globals():
|
||||
bkupglobals[globvar] = _cfgstore['globals'][globvar]
|
||||
return bkupglobals
|
||||
|
||||
def _init_indexes():
|
||||
maxidx = get_global('max_node_index')
|
||||
if maxidx is not None or 'main' not in _cfgstore:
|
||||
return
|
||||
maxidx = 1
|
||||
maincfgstore = _cfgstore['main']
|
||||
nodes_without_index = []
|
||||
for node in confluent.util.natural_sort(maincfgstore.get('nodes', {})):
|
||||
nidx = maincfgstore['nodes'][node].get('id.index', {}).get('value', None)
|
||||
if nidx is not None:
|
||||
if nidx >= maxidx:
|
||||
maxidx = nidx + 1
|
||||
else:
|
||||
nodes_without_index.append(node)
|
||||
for node in nodes_without_index:
|
||||
maincfgstore['nodes'][node]['id.index'] = {'value': maxidx}
|
||||
maxidx += 1
|
||||
_mark_dirtykey('nodes', node, None)
|
||||
set_global('max_node_index', maxidx)
|
||||
set_global('free_node_indexes', [])
|
||||
ConfigManager._bg_sync_to_file()
|
||||
|
||||
|
||||
def init(stateless=False):
|
||||
global _cfgstore
|
||||
global _ready
|
||||
@@ -3141,6 +3285,7 @@ def init(stateless=False):
|
||||
_cfgstore = {}
|
||||
members = list(list_collective())
|
||||
if len(members) < 2:
|
||||
_init_indexes()
|
||||
_ready = True
|
||||
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ def chunk_output(output, n):
|
||||
yield output[i:i + n]
|
||||
|
||||
def get_buffer_output(nodename):
|
||||
if _bufferdaemon is None:
|
||||
eventlet.spawn(run_buffer_daemon)
|
||||
out = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
out.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
|
||||
out.connect("\x00confluent-vtbuffer")
|
||||
@@ -85,6 +87,8 @@ def get_buffer_output(nodename):
|
||||
def send_output(nodename, output):
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
if _bufferdaemon is None:
|
||||
eventlet.spawn(run_buffer_daemon)
|
||||
out = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
out.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
|
||||
out.connect("\x00confluent-vtbuffer")
|
||||
@@ -597,14 +601,25 @@ def _start_tenant_sessions(cfm):
|
||||
event=log.Events.stacktrace)
|
||||
cfm.watch_nodecollection(_nodechange)
|
||||
|
||||
running = True
|
||||
|
||||
def run_buffer_daemon():
|
||||
global _bufferdaemon
|
||||
while running:
|
||||
minrestartdeadline = time.time() + 30 # Do not restart more than once every 30 seconds
|
||||
_bufferdaemon = subprocess.Popen(
|
||||
['/opt/confluent/bin/vtbufferd', 'confluent-vtbuffer'], bufsize=0, stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL)
|
||||
_bufferdaemon.wait()
|
||||
# Ensure we do not restart more than once every 30 seconds
|
||||
sleep_time = minrestartdeadline - time.time()
|
||||
if sleep_time > 0:
|
||||
eventlet.sleep(sleep_time)
|
||||
|
||||
def initialize():
|
||||
global _tracelog
|
||||
global _bufferdaemon
|
||||
_tracelog = log.Logger('trace')
|
||||
_bufferdaemon = subprocess.Popen(
|
||||
['/opt/confluent/bin/vtbufferd', 'confluent-vtbuffer'], bufsize=0, stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL)
|
||||
|
||||
def start_console_sessions():
|
||||
configmodule.hook_new_configmanagers(_start_tenant_sessions)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user