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

Compare commits

..

4 Commits

Author SHA1 Message Date
Jarrod Johnson 59def49e2a Remove stray incorrect python script from tree 2022-05-04 08:30:02 -04:00
Jarrod Johnson b40eef252a Remove stale TODO file 2022-05-03 08:17:29 -04:00
Jarrod Johnson 5daf0b231e Also register to run before coreos-ignition-setup-user
For coreos, make sure we preempt either name.
2022-02-22 14:39:17 -05:00
Jarrod Johnson 896fc09ad8 Fix deb build of vtbuffered 2022-02-04 16:03:02 -05:00
482 changed files with 2780 additions and 33805 deletions
+25
View File
@@ -175,3 +175,28 @@
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-30
View File
@@ -1,30 +0,0 @@
# Confluent
![Python 3](https://img.shields.io/badge/python-3-blue.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/xcat2/confluent/blob/master/LICENSE)
Confluent is a software package to handle essential bootstrap and operation of scale-out server configurations.
It supports stateful and stateless deployments for various operating systems.
Check [this page](https://hpc.lenovo.com/users/documentation/whatisconfluent.html
) for a more detailed list of features.
Confluent is the modern successor of [xCAT](https://github.com/xcat2/xcat-core).
If you're coming from xCAT, check out [this comparison](https://hpc.lenovo.com/users/documentation/confluentvxcat.html).
# Documentation
Confluent documentation is hosted on: https://xcat2.github.io/confluent-docs/
# Download
Get the latest version from: https://hpc.lenovo.com/users/downloads/
Check release notes on: https://hpc.lenovo.com/users/news/
# Open Source License
Confluent is made available under the Apache 2.0 license: https://opensource.org/license/apache-2-0
# Developers
Want to help? Submit a [Pull Request](https://github.com/xcat2/confluent/pulls).
-65
View File
@@ -1,65 +0,0 @@
-other auth
-pam if user exists but has no passphrase
-keystone?
-ad? (specialized to the AD case)
-expressionkeys never gets smaller - perf impact
-When a user account is changed, have httpapi and sockapi notified of changes
to kill off related sessions. password changes are given a pass, but user
deletion will result in immediate session termination
-When the time comes to dole out configuration/discovery, take page from xCAT
'flexdiscover' command, if possible bring an ipmi device under management
by way of ipv6 to eliminate requirement for ip to be specified.
Requires the polling event support (which is required for security anyway)
-this stack trace (happened with method was set to ""):
Traceback (most recent call last):
File "/usr/lib/python2.6/site-packages/eventlet/wsgi.py", line 402, in handle_one_response
for data in result:
File "/home/jbjohnso/Development/confluent/confluent/httpapi.py", line 301, in resourcehandler
cfgmgr, querydict)
File "/home/jbjohnso/Development/confluent/confluent/pluginapi.py", line 273, in handle_path
passvalue = pluginmap[plugpath].__dict__[operation](
KeyError: ''
-have pyghmi and friends do multiprocessing pools
-investigate console behavior when target off (causing disconnects/reconnects
on input)
-test to make sure that 'Session Disconnected' is working
-confetty to do right thing with respect to status updates when run right on a
console. It currently clutters up the screen with data that would land in
a titlebar
-audit log did not show confetty activity for starting console
-read exclusive and full exclusive console access modes
-remove the socket ignore code from pyghmi and repeat tests to understand
why python 2.7 with excessive select() would transpose typing so badly...
while the problem cannot be reproduced without that problem fixed, the
problem should only cause poor performance, not induce transpose of data...
-test out bg_sync in cfg to make sure if it runs just before a change it doesn't cause loss
-set boot/nextdevice=cd after exiting console did:
/nodes/n3/ -> set boot/nextdevice=cd
Traceback (most recent call last):
File "/usr/bin/confetty", line 561, in <module>
do_command(currcommand, netserver)
File "/usr/bin/confetty", line 300, in do_command
setvalues(argv[1:])
File "/usr/bin/confetty", line 369, in setvalues
print_result(res)
File "/usr/bin/confetty", line 211, in print_result
for key in res.iterkeys():
AttributeError: 'str' object has no attribute 'iterkeys'
-/detected/ collection
-SLP snooping: snoop for srvrequests, likel
-Config backup/restore
-seal to password
-Scenario was somehow solconnection object is 'broken' (broken=True) but the supervising console manager
never notices. The 'write()' doesn't raise an exception, just returns.
-pyghmi should except on attempt to write to broken console I think...
-need to investigate what path does not end up getting immediate notification
for disconnection
-last observed when a switch between confluent and imm was resetting in a loop
while it was trying to keep console open as much as possibl
-cs._handled_consoles[('n4', None)]._console.handle_data({'error':1}) is how I was able to
kick the session state back to working order after whatever event that was missed above
occurred.
-save and restore the terminal title on startup/exit respectively
-77
View File
@@ -1,77 +0,0 @@
Project: https://hpc.lenovo.com/users/
Source: https://github.com/lenovo/confluent
Upstream-Name: confluent-client
All file of the Confluent-client software is distributed under the terms of an Apache-2.0 license as indicated below:
Files: *
Copyright: 2014-2019 Lenovo
License: Apache-2.0
File: client.py:
Copyright: 2014 IBM Corporation
2015-2019 Lenovo
License: Apache-2.0
File: sortutil.py,
tlvdata.py
Copyright: 2014 IBM Corporation
2015-2016 Lenovo
License: Apache-2.0
File: tlv.py
Copyright: 2014 IBM Corporation
License: Apache-2.0
License: Apache-2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+6 -11
View File
@@ -14,16 +14,11 @@ import shutil
shutil.copyfile('doc/man/nodeattrib.ronn.tmpl', 'doc/man/nodeattrib.ronn')
shutil.copyfile('doc/man/nodegroupattrib.ronn.tmpl', 'doc/man/nodegroupattrib.ronn')
with open('doc/man/nodeattrib.ronn', 'a') as outf:
for field in sorted(attr.node):
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
with open('doc/man/nodegroupattrib.ronn', 'a') as outf:
for field in sorted(attr.node):
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
def append_attributes(filename):
with open(filename, 'a') as outf:
for field in sorted(attr.node):
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
# Optionally write valid values if they exist
for key, values in attr.node[field].items():
if key.startswith('valid'):
values_formatted = ', '.join("'{0}'".format(v) for v in values)
outf.write(f'\n Valid values: {values_formatted}\n')
append_attributes('doc/man/nodeattrib.ronn')
append_attributes('doc/man/nodegroupattrib.ronn')
+2 -9
View File
@@ -21,7 +21,6 @@
import optparse
import os
import re
import select
import sys
@@ -85,7 +84,6 @@ fullline = sys.stdin.readline()
printpending = True
clearpending = False
holdoff = 0
padded = None
while fullline:
for line in fullline.split('\n'):
if not line:
@@ -94,18 +92,13 @@ while fullline:
line = 'UNKNOWN: ' + line
if options.log:
node, output = line.split(':', 1)
if padded is None:
if output.startswith(' '):
padded = True
else:
padded = False
if padded:
output = re.sub(r'^ ', '', output)
output = output.lstrip()
currlog = options.log.format(node=node, nodename=node)
with open(currlog, mode='a') as log:
log.write(output + '\n')
continue
node, output = line.split(':', 1)
output = output.lstrip()
grouped.add_line(node, output)
if options.watch:
if not holdoff:
+9 -74
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
@@ -45,7 +45,6 @@ import math
import getpass
import optparse
import os
import re
import select
import shlex
import signal
@@ -636,7 +635,7 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
global didconsole
if fixterm or didconsole:
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl & ~os.O_NONBLOCK)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
if oldtcattr is not None:
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
# Request default color scheme, to undo potential weirdness of terminal
@@ -655,17 +654,13 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
def get_session_node(shellargs):
# straight to node console
if len(shellargs) == 1 and ' ' not in shellargs[0]:
targ = "/nodes/%s/console/session" % shellargs[0]
return targ, shellargs[0]
return shellargs[0]
if len(shellargs) == 2 and shellargs[0] == 'start':
args = [s for s in shellargs[1].split('/') if s]
if len(args) == 4 and args[0] == 'nodes' and args[2] == 'console' and \
args[3] == 'session':
return shellargs[1], args[1]
if len(args) == 5 and args[0] == 'nodes' and args[2] == 'shell' and \
args[3] == 'sessions':
return shellargs[1], args[1]
return None, None
return args[1]
return None
def run_inline_command(path, arg, completion, **kwargs):
@@ -922,10 +917,10 @@ def main():
doexit = False
inconsole = False
pendingcommand = ""
targ, session_node = get_session_node(shellargs)
session_node = get_session_node(shellargs)
if session_node is not None:
consoleonly = True
do_command("start %s" % targ, netserver)
do_command("start /nodes/%s/console/session" % session_node, netserver)
doexit = True
elif shellargs:
do_command(shellargs, netserver)
@@ -953,9 +948,8 @@ def main():
except IOError:
pass
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
if powerstate == None:
powerstate = True
powertime = time.time()
powerstate = True
check_power_state()
else:
currcommand = prompt()
@@ -970,15 +964,8 @@ def main():
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
sgr_re = re.compile(r'(\x1b\[[0-9;]*m)')
sgr_parameters_re = re.compile(r'\x1b\[([0-9;]*)m')
fgcolor = None
bgcolor = None
fgshifted = False
pendseq = ''
def consume_termdata(fh, bufferonly=False):
global clearpowermessage
global fgcolor, bgcolor, fgshifted, pendseq
try:
data = tlvdata.recv(fh)
except Exception:
@@ -987,59 +974,7 @@ def consume_termdata(fh, bufferonly=False):
updatestatus(data)
return ''
if data is not None:
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'
data = client.stringify(data)
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
+25 -42
View File
@@ -1,9 +1,8 @@
#!/usr/bin/python3
#!/usr/bin/python2
import optparse
import signal
import sys
import os
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
@@ -18,16 +17,18 @@ import confluent.client as client
import confluent.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts -a
''')
usage='''\n %prog noderange -o ansible.hosts
\n ''')
argparser.add_option('-o', '--output',
help='Writes an Ansible hosts file')
argparser.add_option('-a', '--append', action='store_true',
help='Appends to existing hosts file')
help='Ansible hosts file')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -44,42 +45,24 @@ def main():
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
nodesbygroup = {}
for node in sortutil.natural_sort(databynode):
data = databynode[node]
groups = data.get('groups', [])
if not groups:
nodesbygroup.setdefault('', set()).add(node.strip().lower())
else:
for g in groups:
nodesbygroup.setdefault(g, set()).add(node.strip().lower())
existing_data = {}
if options.append and os.path.exists(options.output):
current_group = ''
with open(options.output, 'r') as f:
for line in f:
line = line.strip().lower()
if not line:
continue
if line.startswith('[') and line.endswith(']'):
current_group = line[1:-1]
existing_data.setdefault(current_group, set())
else:
existing_data.setdefault(current_group, set()).add(line)
for group, nodes in nodesbygroup.items():
nodes = {n.strip().lower() for n in nodes}
current_nodes = existing_data.get(group, set())
new_nodes = nodes - current_nodes
if new_nodes:
existing_data.setdefault(group, set()).update(nodes)
with open(options.output, 'w') as importfile:
for group in sortutil.natural_sort(existing_data.keys()):
if group:
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(existing_data[group]):
needempty = False
for node in sortutil.natural_sort(databynode):
data = databynode[node]
if not data.get('groups', []):
importfile.write(node + '\n')
needempty = True
for g in data.get('groups', []):
if g not in nodesbygroup:
nodesbygroup[g] = set([node])
else:
nodesbygroup[g].add(node)
if needempty:
importfile.write('\n')
for group in sortutil.natural_sort(nodesbygroup):
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(nodesbygroup[group]):
importfile.write('{0}\n'.format(node))
importfile.write('\n')
+2 -9
View File
@@ -118,7 +118,6 @@ def main():
ap.add_argument('-a', '--attrib', help='Pull ip addresses and hostnames from attribute database', action='store_true')
ap.add_argument('-i', '--ip', help='Expression to generate addresses (e.g. 172.16.1.{n1} or fd2b:246f:8a50::{n1:x})')
ap.add_argument('-n', '--name', help='Expression for name to add ({node}-compute, etc). If unspecified, "{node} {node}.{dns.domain}" will be used', action='append')
ap.add_argument('-f', '--fqdn-first', help='Put the FQDN first in the hosts entries', action='store_true')
args = ap.parse_args()
c = client.Command()
if args.name:
@@ -158,7 +157,7 @@ def main():
elif attrib.endswith('.ipv6_address') and val:
ip6bynode[node][currnet] = val.split('/', 1)[0]
elif attrib.endswith('.hostname'):
namesbynode[node][currnet] = re.split(r'\s+|,', val)
namesbynode[node][currnet] = re.split('\s+|,', val)
for node in ip4bynode:
mydomain = domainsbynode.get(node, None)
for ipdb in (ip4bynode, ip6bynode):
@@ -174,13 +173,7 @@ def main():
break
else:
for name in list(names):
fqdn = '{0}.{1}'.format(name, mydomain)
if args.fqdn_first:
# Insert FQDN at the beginning if --fqdn-first flag is set
names.insert(0, fqdn)
else:
# Otherwise, append FQDN at the end (original behavior)
names.append(fqdn)
names.append('{0}.{1}'.format(name, mydomain))
names = ' '.join(names)
merger.add_entry(ipdb[node][currnet], names)
merger.write_out('/etc/whatnowhosts')
-2
View File
@@ -79,8 +79,6 @@ def collect_attribute_data(databynode, node):
xcatattrs['groups'] = ','.join(data.get('groups', []))
xcatattrs['bmc'] = data.get('hardwaremanagement.manager', {}).get(
'value', None)
if xcatattrs['bmc']:
xcatattrs['bmc'] = xcatattrs['bmc'].split('/', 1)[0]
xcatattrs['mpa'] = data.get('enclosure.manager', {}).get(
'value', None)
xcatattrs['slotid'] = data.get('enclosure.bay', {}).get(
+19 -39
View File
@@ -8,35 +8,30 @@ import os
import subprocess
import sys
def create_image(directory, image, label=None, esize=0, totalsize=None):
if totalsize:
datasz = totalsize * 1048576
else:
ents = 0
datasz = 512 + (esize * 1048576)
for dir in os.walk(sys.argv[1]):
def create_image(directory, image, label=None):
ents = 0
datasz = 512
for dir in os.walk(sys.argv[1]):
ents += 1
for filen in dir[2]:
ents += 1
for filen in dir[2]:
ents += 1
filename = os.path.join(dir[0], filen)
currsz = os.path.getsize(filename)
# assuming up to 65k cluster
currsz = (currsz // 512 + 1) * 512
datasz += currsz
datasz += ents * 32768
datasz = datasz // 65536 + 1
filename = os.path.join(dir[0], filen)
currsz = os.path.getsize(filename)
# assuming up to 65k cluster
currsz = (currsz // 512 +1) * 512
datasz += currsz
datasz += ents * 32768
datasz = datasz // 512 + 1
with open(image, 'wb') as imgfile:
imgfile.seek(datasz * 65536 - 1)
imgfile.seek(datasz * 512 - 1)
imgfile.write(b'\x00')
if label:
# 4 heads, 32 sectors, means 65k per track
subprocess.check_call(['mformat', '-i', image, '-v', label,
'-r', '16', '-d', '1', '-t', str(datasz),
'-s', '32','-h', '4', '::'])
'-s', '1','-h', '1', '::'])
else:
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
str(datasz), '-s', '32','-h', '4', '::'])
str(datasz), '-s', '1','-h', '1', '::'])
# Some clustered filesystems will have the lock from mformat
# linger after close (mformat doesn't unlock)
# do a blocking wait for shared lock and then explicitly
@@ -61,21 +56,6 @@ if __name__ == '__main__':
sys.argv[0]))
sys.exit(1)
label = None
args = sys.argv
esize = 0
try:
earg = args.index('-e')
esize = int(args[earg + 1])
args = args[:earg] + args[earg +2:]
except ValueError:
pass
totsize = None
try:
earg = args.index('-s')
totsize = int(args[earg + 1])
args = args[:earg] + args[earg +2:]
except ValueError:
pass
if len(args) > 3:
label = args[3]
create_image(args[1], args[2], label, esize, totsize)
if len(sys.argv) > 3:
label = sys.argv[3]
create_image(sys.argv[1], sys.argv[2], label)
-175
View File
@@ -1,175 +0,0 @@
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import deque
import optparse
import os
import select
import signal
import subprocess
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run():
global devnull
devnull = open(os.devnull, 'rb')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of commands to run at a time')
argparser.add_option('-k', '--security', action='store_true',
help='Update SSH setup')
argparser.add_option('-F', '--sync', action='store_true',
help='Run the syncfiles associated with the currently completed OS profile on the noderange')
argparser.add_option('-P', '--scripts',
help='Re-run specified scripts, with full path under scripts, e.g. post.d/first,firstboot.d/second')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
#argparser.disable_interspersed_args()
(options, args) = argparser.parse_args()
if len(args) < 1:
argparser.print_help()
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
currprocs = 0
all = set([])
poller = select.epoll()
pipedesc = {}
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
nodemap = {}
cmdparms = []
nodes = []
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
break
node = res['item']['href'][:-1]
nodes.append(node)
cmdstorun = []
if options.security:
cmdstorun.append(['run_remote', 'setupssh'])
if options.sync:
cmdstorun.append(['run_remote_python', 'syncfileclient'])
if options.scripts:
for script in options.scripts.split(','):
cmdstorun.append(['run_remote', script])
if not cmdstorun:
argparser.print_help()
sys.exit(1)
idxbynode = {}
cmdvbase = ['bash', '/etc/confluent/functions']
for sshnode in nodes:
idxbynode[sshnode] = 1
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[0]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(sshnode, cmdv, all, poller, pipedesc)
else:
pendingexecs.append((sshnode, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy = poller.poll(10)
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':
if idxbynode[node] < len(cmdstorun):
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[idxbynode[node]]
idxbynode[node] += 1
run_cmdv(node, cmdv, all, poller, pipedesc)
elif 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)
line = line.lstrip('\x08')
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy = poller.poll(10)
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, poller, pipedesc):
nopen = subprocess.Popen(
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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__':
run()
-24
View File
@@ -22,7 +22,6 @@ import optparse
import os
import signal
import sys
import shlex
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
@@ -54,8 +53,6 @@ argparser.add_option('-p', '--prompt', action='store_true',
argparser.add_option('-m', '--maxnodes', type='int',
help='Prompt if trying to set attributes on more '
'than specified number of nodes')
argparser.add_option('-s', '--set', dest='set', metavar='settings.batch',
default=False, help='set attributes using a batch file')
(options, args) = argparser.parse_args()
@@ -108,32 +105,11 @@ if len(args) > 1:
requestargs=args[1:]
except:
pass
elif options.clear or options.environment or options.prompt:
sys.stderr.write('Attribute names required with specified options\n')
argparser.print_help()
exitcode = 400
elif options.set:
arglist = [noderange]
showtype='current'
argfile = open(options.set, 'r')
argset = argfile.readline()
while argset:
try:
argset = argset[:argset.index('#')]
except ValueError:
pass
argset = argset.strip()
if argset:
arglist += shlex.split(argset)
argset = argfile.readline()
session.stop_if_noderange_over(noderange, options.maxnodes)
exitcode=client.updateattrib(session,arglist,nodetype, noderange, options, None)
if exitcode != 0:
sys.exit(exitcode)
# Lists all attributes
if len(args) > 0:
# setting output to all so it can search since if we do have something to search, we want to show all outputs even if it is blank.
if requestargs is None:
-121
View File
@@ -1,121 +0,0 @@
#!/usr/libexec/platform-python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__author__ = 'tkucherera'
from getpass import getpass
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> <username> <new_password>")
argparser.add_option('-m', '--maxnodes', type='int',
help='Number of nodes to affect before prompting for confirmation')
argparser.add_option('-p', '--prompt', action='store_true',
help='Prompt for password values interactively')
argparser.add_option('-e', '--environment', action='store_true',
help='Set passwod, but from environment variable of '
'same name')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
username = args[1]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
if options.prompt:
oneval = 1
twoval = 2
while oneval != twoval:
oneval = getpass('Enter pass for {0}: '.format(username))
twoval = getpass('Confirm pass for {0}: '.format(username))
if oneval != twoval:
print('Values did not match.')
new_password = twoval
elif len(args) == 3:
if options.environment:
key = args[2]
new_password = os.environ.get(key, os.environ[key.upper()])
else:
new_password = args[2]
else:
argparser.print_help()
sys.exit(1)
errorNodes = set([])
uid_dict = {}
session.stop_if_noderange_over(noderange, options.maxnodes)
for rsp in session.read('/noderange/{0}/configuration/management_controller/users/all'.format(noderange)):
databynode = rsp["databynode"]
for node in databynode:
if 'error' in rsp['databynode'][node]:
print(node, ':', rsp['databynode'][node]['error'])
errorNodes.add(node)
continue
for user in rsp['databynode'][node]['users']:
if user['username'] == username:
if not user['uid'] in uid_dict:
uid_dict[user['uid']] = node
continue
uid_dict[user['uid']] = uid_dict[user['uid']] + ',{}'.format(node)
break
if not uid_dict:
print("Error: Could not reach target node's bmc user")
sys.exit(1)
for uid in uid_dict:
success = session.simple_noderange_command(uid_dict[uid], 'configuration/management_controller/users/{0}'.format(uid), new_password, key='password', errnodes=errorNodes) # = 0 if successful
allNodes = set([])
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
if 'error' in node and success != 0:
sys.exit(success)
allNodes.add(node['item']['href'].replace("/", ""))
goodNodes = allNodes - errorNodes
for node in goodNodes:
print(node + ": Password Change Successful")
sys.exit(success)
-89
View File
@@ -1,89 +0,0 @@
#!/usr/bin/python3
import os
import sys
from cryptography import x509
from cryptography.hazmat.primitives import hashes
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
def removebmccacert(noderange, certid, cmd):
for res in cmd.delete(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities/{certid}'):
print(repr(res))
def listbmccacerts(noderange, cmd):
certids = []
for res in cmd.read(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities'):
certids.append(res.get('item', {}).get('href', ''))
for certid in certids:
for res in cmd.read(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities/{certid}'):
for node in res.get('databynode', {}):
certdata = res['databynode'][node].get('pem', {}).get('value', '')
summary = ''
if not certdata:
continue
san = res['databynode'][node].get('san', {}).get('value', '')
if san:
summary += f" SANs: {san}"
subject = res['databynode'][node].get('subject', {}).get('value', '')
if subject:
summary = subject
try:
cert = x509.load_pem_x509_certificate(certdata.encode())
sha256 = cert.fingerprint(hashes.SHA256()).hex().upper()
except Exception as e:
print(f"Error processing certificate for {node}: {e}", file=sys.stderr)
continue
summary += f" (SHA256={sha256})"
print(f"{node}: {certid}: {summary}")
def installbmccacert(noderange, certfile, cmd):
if certfile:
try:
with open(certfile, 'r') as f:
certdata = f.read()
except Exception as e:
print(f"Error reading certificate file: {e}", file=sys.stderr)
sys.exit(1)
# Simple validation: check if it starts and ends with the correct PEM markers
if not (certdata.startswith("-----BEGIN CERTIFICATE-----") and certdata.strip().endswith("-----END CERTIFICATE-----")):
print("Invalid certificate format. Must be a PEM encoded certificate.", file=sys.stderr)
sys.exit(1)
payload = {'pem': certdata}
for res in cmd.update(f'/noderange/{noderange}/configuration/management_controller/certificate_authorities', payload):
print(repr(res))
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Node certificate utility')
parser.add_argument('noderange', help='Node range to operate on')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# installbmccacert subcommand
install_parser = subparsers.add_parser('installbmccacert', help='Install BMC CA certificate')
install_parser.add_argument('filename', help='Certificate file to install')
remove_parser = subparsers.add_parser('removebmccacert', help='Remove BMC CA certificate')
remove_parser.add_argument('id', help='Certificate id to remove')
list_parser = subparsers.add_parser('listbmccacerts', help='List BMC CA certificates')
args = parser.parse_args()
c = client.Command()
if args.command == 'installbmccacert':
installbmccacert(args.noderange, args.filename, c)
elif args.command == 'removebmccacert':
removebmccacert(args.noderange, args.id, c)
elif args.command == 'listbmccacerts':
listbmccacerts(args.noderange, c)
else:
parser.print_help()
sys.exit(1)
+9 -24
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2025 Lenovo
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -90,18 +90,6 @@ cfgpaths = {
'bmc.ipv4_gateway': (
'configuration/management_controller/net_interfaces/management',
'ipv4_gateway'),
'bmc.static_ipv6_addresses': (
'configuration/management_controller/net_interfaces/management',
'static_v6_addresses'),
'bmc.static_ipv6_gateway': (
'configuration/management_controller/net_interfaces/management',
'static_v6_gateway'),
'bmc.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'),
}
@@ -301,22 +289,19 @@ else:
for path in queryparms:
if options.comparedefault:
continue
rcode |= client.print_attrib_path(path, session, list(queryparms[path]),
rc = client.print_attrib_path(path, session, list(queryparms[path]),
NullOpt(), queryparms[path])
if rc:
sys.exit(rc)
if printsys == 'all' or printextbmc or printbmc or printallbmc:
if printbmc or not printextbmc:
rcode |= client.print_attrib_path(
rcode = client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
session, printbmc, options, attrprefix='bmc.')
if options.extra:
if options.advanced:
rcode |= client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/extra_advanced'.format(noderange),
session, printextbmc, options)
else:
rcode |= client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/extra'.format(noderange),
session, printextbmc, options)
rcode |= client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/extra'.format(noderange),
session, printextbmc, options)
if printsys or options.exclude:
if printsys == 'all':
printsys = []
+3 -700
View File
@@ -1,4 +1,4 @@
#!/usr/libexec/platform-python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
@@ -15,7 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import optparse
import os
import subprocess
@@ -24,712 +23,22 @@ path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
import confluent.logreader as logreader
import time
import select
import signal
import socket
import re
import tty
import termios
import fcntl
import confluent.screensqueeze as sq
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
Image = None
try:
# sixel is optional, attempt to import but stub out if unavailable
import io
import sixel
class DumbWriter(sixel.SixelWriter):
def restore_position(self, output):
return
except ImportError:
class DumbWriter():
def draw(self, imgfile):
sys.stderr.write("PySixel not detected, Sixel format display not supported\n")
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] <noderange> [kill][-- [passthroughoptions]]",
usage="Usage: %prog [options] node",
epilog="Command sequences are available while connected to a console, hit "
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
"For example, ctrl-'e', then 'c', then '.' will exit the current "
"console")
argparser.add_option('-t', '--tile', action='store_true', default=False,
help='Tile console windows in the terminal')
argparser.add_option('-l', '--log', action='store_true', default=False,
help='Enter log replay mode instead of showing a live console')
argparser.add_option('-T', '--Timestamp', action='store_true', default=False,
help= 'Dump log in stdout with timestamps')
argparser.add_option('-s', '--screenshot', action='store_true', default=False,
help='Attempt to grab screenshot and render using kitty image protocol')
argparser.add_option('-i', '--interval', type='float',
help='Interval in seconds to redraw the screenshot. Currently only '
'works for one node')
argparser.add_option('-w','--windowed', action='store_true', default=False,
help='Open terminal windows for each node. The '
'environment variable NODECONSOLE_WINDOWED_COMMAND '
'should be set, which should be a text string corresponding '
'to a command that can be used to open a windowed console,'
' omitting the "nodeconsole <noderange>" part of the '
'command, for example, to open a set of consoles for a '
'range of nodes in separate xterm windows, set '
'NODECONSOLE_WINDOWED_COMMAND to "xterm -e". To open a '
'set of consoles for a range of nodes in separate '
'GNOME Terminal windows with a size of 100 columns and '
'31 rows, set NODECONSOLE_WINDOWED_COMMAND '
'to "gnome-terminal --geometry 100x31 --" or in a WSL '
'environment, to open a set of consoles for a range of '
'nodes in separate Windows Terminal windows, with the '
'title set for each node, set NODECONSOLE_WINDOWED_COMMAND'
' to "wt.exe wsl.exe -d AlmaLinux-8 '
'--shell-type login". If the NODECONSOLE_WINDOWED_COMMAND '
'environment variable isn\'t set, xterm will be used by'
'default.')
(options, args) = argparser.parse_args()
oldtcattr = None
oldfl = None
def get_coords():
sys.stdout.write('\x1b[6n') #
sys.stdout.flush()
gotreply = select.select([sys.stdin,], [], [], 0.250)[0]
if gotreply:
response = ''
while select.select([sys.stdin,], [], [], 0.1)[0] and 'R' not in response:
response += sys.stdin.read()
coords = response.replace('R', '').split('[')[1].split(';')
#sys.stdout.write('\x1b[{}:{}H'.format(*coords))
def direct_console():
global oldtcattr
global oldfl
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
oldfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
tty.setraw(sys.stdin.fileno())
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
def indirect_console():
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, oldfl & ~os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
def determine_tile_size(numnodes):
# for now, smash everything to a common aspect ratio. 16:11
# is pretty much wrong for everything, making 4:3 a bit too wide
# and 16:9 significantly too narrow, but it is serviceable
# An improvement could come with us owning the scaling
# instead of delegating to Kitty, which says if we specify both,
# we get stretching. In theory we should be able to get aspect correct
# from kitty by omitting, but:
# then we don't know how much to move the cursor left after draw_image
# Konsole won't scale at all with only partial scaling specified
cheight, cwidth, pixwidth, pixheight = sq.get_screengeom()
# 16:12 is to roughly account for the 'titles' of the tiles
ratio = (pixwidth / 16) / (pixheight / 12)
bestdeviation = None
bestdims = []
for i in range(1, numnodes + 1):
number = numnodes
while number % i != 0:
number += 1
columns = i
rows = number // i
deviation = abs(ratio - (columns / rows))
if bestdeviation is None:
bestdeviation = deviation
bestdims = [columns, rows]
elif deviation < bestdeviation:
bestdeviation = deviation
bestdims = [columns, rows]
# ok, the above algorithm can still pick things like
# 1 2 3
# 4
# So we will let it pick the number of rows, and
# then see if we can chop columns and still fit
while (bestdims[0] - 1) * bestdims[1] >= numnodes:
bestdims[0] = bestdims[0] - 1
cellswide = cwidth // bestdims[0]
cellshigh = cheight // bestdims[1]
tilewidth = cellswide * pixwidth / cwidth
tileheight = cellshigh * pixheight / cheight
if tilewidth > (tileheight * 16 / 11):
tilewidth = tileheight * 16 / 11
cellswide = int(tilewidth // (pixwidth / cwidth))
if tileheight > (tilewidth * 11 /16):
tileheight = tilewidth * 11 / 16
cellshigh = int(tileheight // (pixheight / cheight))
bestdims = bestdims + [cellswide, cellshigh, cellshigh * bestdims[1]]
# incur any scrolling we might get. This allows us to accurately
# save/restore cursor or even get coordinates without scrolling fouling
# the desired target
sys.stdout.write('\n' * bestdims[4])
sys.stdout.flush()
cursor_up(bestdims[4])
return bestdims
cursor_saved = False
def sticky_cursor():
global cursor_saved
# get cursor restore_position
if sys.stdin.isatty() and not cursor_saved:
try:
direct_console()
sys.stdout.write('\x1b7')
cursor_saved = True
finally:
indirect_console()
elif cursor_saved:
try:
direct_console()
sys.stdout.write('\x1b8')
finally:
indirect_console()
def cursor_up(count=1):
sys.stdout.write(f'\x1b[{count}A')
def cursor_down(count=1):
sys.stdout.write(f'\x1b[{count}B')
def cursor_right(count=1):
sys.stdout.write(f'\x1b[{count}C')
def cursor_left(count=1):
sys.stdout.write(f'\x1b[{count}D')
def cursor_save():
sys.stdout.write('\x1b7')
def cursor_restore():
sys.stdout.write('\x1b8')
def cursor_hide():
sys.stdout.write('\x1b[?25l')
def cursor_show():
sys.stdout.write('\x1b[?25h')
def get_pix_dimensions(width, height):
cheight, cwidth, pixwidth, pixheight = sq.get_screengeom()
imgwidth = int(pixwidth / cwidth * width)
imgheight = int(pixheight / cheight * height)
return imgwidth, imgheight
def draw_text(text, width, height):
if Image:
maxfntsize = 256
imgwidth, imgheight = get_pix_dimensions(width, height)
nerr = Image.new(mode='RGB', size=(imgwidth, imgheight), color='green')
nd = ImageDraw.Draw(nerr)
for txtpiece in text.split('\n'):
fntsize = 8
txtfont = ImageFont.truetype('DejaVuSans.ttf', size=fntsize)
while nd.textlength(txtpiece, font=txtfont) < int(imgwidth * 0.90):
fntsize += 1
txtfont = ImageFont.truetype('DejaVuSans.ttf', size=fntsize)
fntsize -= 1
if fntsize < maxfntsize:
maxfntsize = fntsize
hmargin = int(imgwidth * 0.05)
vmargin = int(imgheight * 0.10)
nd.text((hmargin, vmargin), text, font=txtfont)
nd.rectangle((0, 0, nerr.width - 1, nerr.height -1), outline='white')
outfile = io.BytesIO()
nerr.save(outfile, format='PNG')
data = base64.b64encode(outfile.getbuffer())
draw_image(data, width, height, doscale=False)
else:
sys.stdout.write(text)
cursor_left(len(text))
def draw_image(data, width, height, doscale=True):
imageformat = os.environ.get('CONFLUENT_IMAGE_PROTOCOL', 'kitty')
if doscale and Image and width:
bindata = base64.b64decode(data)
binfile = io.BytesIO()
binfile.write(bindata)
binfile.seek(0)
try:
img = Image.open(binfile)
except Exception as e:
errstr = 'Error rendering image:\n' + str(e)
return draw_text(errstr, width, height)
imgwidth, imgheight = get_pix_dimensions(width, height)
nimg = Image.new(mode='RGBA', size=(imgwidth, imgheight))
imgwidth -= 4
imgheight -= 4
hscalefact = imgwidth / img.width
vscalefact = imgheight / img.height
if hscalefact < vscalefact:
rzwidth = imgwidth
rzheight = int(img.height * hscalefact)
else:
rzwidth = int(img.width * vscalefact)
rzheight = imgheight
img = img.resize((rzwidth, rzheight))
nd = ImageDraw.Draw(nimg)
nd.rectangle((1, 1, rzwidth + 2, rzheight + 2), outline='black')
nd.rectangle((0, 0, rzwidth + 3, rzheight + 3), outline='white')
nimg.paste(img, box=(2, 2))
outfile = io.BytesIO()
nimg.save(outfile, format='PNG')
data = base64.b64encode(outfile.getbuffer())
if imageformat == 'sixel':
sixel_draw(data)
elif imageformat == 'iterm':
iterm_draw(data, width, height)
else:
kitty_draw(data, width, height)
def sixel_draw(data):
bindata = base64.b64decode(data)
binfile = io.BytesIO()
binfile.write(bindata)
binfile.seek(0)
DumbWriter().draw(binfile)
def iterm_draw(data, width, height):
if not height:
height = 'auto'
if not width:
width = 'auto'
bindata = base64.b64decode(data)
datalen = len(bindata)
sys.stdout.write(
'\x1b]1337;File=inline=1;width={};height={};size={}:'.format(width,height,datalen))
sys.stdout.write(data.decode('utf8'))
sys.stdout.write('\a')
sys.stdout.flush()
def kitty_draw(data, width, height):
preamble = '\x1b_Ga=T,f=100'
if height:
preamble += f',r={height},c={width}'
#sys.stdout.write(repr(preamble))
#sys.stdout.write('\xb[{}D'.format(len(repr(preamble))))
#return
first = True
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
if first:
sys.stdout.write('{},m={};'.format(preamble, m))
else:
sys.stdout.write('\x1b_Gm={};'.format(m))
sys.stdout.write(chunk.decode('utf8'))
sys.stdout.write('\x1b\\')
sys.stdout.flush()
pass_through_args = []
killcon = False
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'kill':
killcon = True
pass_through_args = args[1:]
args = args[:1]
except IndexError:
argparser.print_help()
sys.exit(1)
if len(args) != 1:
argparser.print_help()
sys.exit(1)
if options.log:
logname = args[0]
if not os.path.exists(logname) and logname[0] != '/':
logname = os.path.join('/var/log/confluent/consoles', logname)
if not os.path.exists(logname):
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
sys.exit(1)
logreader.replay_to_console(logname)
sys.exit(0)
if options.Timestamp:
logname = args[0]
if not os.path.exists(logname) and logname[0] != '/':
logname = os.path.join('/var/log/confluent/consoles', logname)
if not os.path.exists(logname):
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
sys.exit(1)
logreader.dump_to_console(logname)
sys.exit(0)
def prep_node_tile(node):
currcolcell, currrowcell = nodepositions[node]
if currcolcell:
cursor_right(currcolcell)
if currrowcell:
cursor_down(currrowcell)
sys.stdout.write('▏' + node)
cursor_left(len(node) + 1)
cursor_down()
def reset_cursor(node):
currcolcell, currrowcell = nodepositions[node]
if currcolcell:
cursor_left(currcolcell)
cursor_up(currrowcell + 1)
nodepositions = {}
numrows = 0
cwidth = 0
cheight = 0
imagedatabynode = {}
def redraw():
for node in imagedatabynode:
imgdata = imagedatabynode[node]
if node in nodepositions:
prep_node_tile(node)
cursor_save()
else:
if options.interval is not None:
if node != firstnodename:
sys.stderr.write('Multiple nodes not supported for interval')
sys.exit(1)
sticky_cursor()
sys.stdout.write('{}: '.format(node))
# one row is used by our own name, so cheight - 1 for that allowance
draw_image(imgdata.encode(), cwidth, cheight - 1 if cheight else cheight)
if node in nodepositions:
cursor_restore()
reset_cursor(node)
else:
sys.stdout.write('\n')
sys.stdout.flush()
resized = False
def do_screenshot():
global resized
global numrows
sess = client.Command()
if options.tile:
imageformat = os.environ.get('CONFLUENT_IMAGE_PROTOCOL', 'kitty')
if imageformat not in ('kitty', 'iterm'):
sys.stderr.write('Tiled screenshots only supported with kitty or iterm protocol')
sys.exit(1)
allnodes = []
numnodes = 0
for res in sess.read('/noderange/{}/nodes/'.format(args[0])):
allnodes.append(res['item']['href'].replace('/', ''))
numnodes += 1
resized = False
def do_resize(a=None, b=None):
global resized
if a:
resized = True
# on a window resize, clear the old stuff
# ideally we'd retain the images and redraw them
sys.stdout.write('\x1bc')
global numrows
global cwidth
global cheight
cols, rows, cwidth, cheight, numrows = determine_tile_size(numnodes)
currcol = 1
currcolcell = 0
currrowcell = 0
for node in allnodes:
nodepositions[node] = currcolcell, currrowcell
if currcol < cols:
currcol += 1
currcolcell += cwidth
else:
currcol = 1
currcolcell = 0
currrowcell += cheight
if a:
redraw()
do_resize()
signal.signal(signal.SIGWINCH, do_resize)
elif options.interval is not None:
sys.stdout.write('\x1bc')
firstnodename = None
dorefresh = True
vnconly = set([])
while dorefresh:
for res in sess.read('/noderange/{}/console/ikvm_screenshot'.format(args[0])):
for node in res.get('databynode', {}):
errorstr = ''
if not firstnodename:
firstnodename = node
error = res['databynode'][node].get('error')
if error and 'vnc available' in error:
vnconly.add(node)
continue
elif error:
errorstr = error
imgdata = res['databynode'][node].get('image', {}).get('imgdata', None)
if imgdata:
if len(imgdata) < 32: # We were subjected to error
errorstr = f'Unable to get screenshot'
if errorstr or imgdata:
draw_node(node, imgdata, errorstr, firstnodename, cwidth, cheight)
if asyncvnc:
urlbynode = {}
for node in vnconly:
for res in sess.update(f'/nodes/{node}/console/ikvm', {'method': 'unix'}):
url = res.get('item', {}).get('href')
if url:
urlbynode[node] = url
draw_vnc_grabs(urlbynode, cwidth, cheight)
if resized:
do_resize(True)
resized = False
elif vnconly:
sys.stderr.write("Require asyncvnc installed to do VNC screenshotting\n")
if options.interval is None:
dorefresh = False
else:
dorefresh = True
time.sleep(options.interval)
sys.exit(0)
try:
import asyncio, asyncvnc
except ImportError:
asyncvnc = None
def draw_vnc_grabs(urlbynode, cwidth, cheight):
asyncio.run(grab_vncs(urlbynode, cwidth, cheight))
async def grab_vncs(urlbynode, cwidth, cheight):
tasks = []
for node in urlbynode:
url = urlbynode[node]
tasks.append(asyncio.create_task(do_vnc_screenshot(node, url, cwidth, cheight)))
await asyncio.gather(*tasks)
async def my_opener(host, port):
# really, host is the unix
return await asyncio.open_unix_connection(host)
async def do_vnc_screenshot(node, url, cwidth, cheight):
async with asyncvnc.connect(url, opener=my_opener) as client:
# Retrieve pixels as a 3D numpy array
pixels = await client.screenshot()
# Save as PNG using PIL/pillow
image = Image.fromarray(pixels)
outfile = io.BytesIO()
image.save(outfile, format='PNG')
imgdata = base64.b64encode(outfile.getbuffer()).decode()
if imgdata:
draw_node(node, imgdata, '', '', cwidth, cheight)
def draw_node(node, imgdata, errorstr, firstnodename, cwidth, cheight):
imagedatabynode[node] = imgdata
if node in nodepositions:
prep_node_tile(node)
cursor_save()
else:
if options.interval is not None:
if node != firstnodename:
sys.stderr.write('Multiple nodes not supported for interval')
sys.exit(1)
sticky_cursor()
sys.stdout.write('{}: '.format(node))
# one row is used by our own name, so cheight - 1 for that allowance
if errorstr:
draw_text(errorstr, cwidth, cheight -1 if cheight else cheight)
else:
draw_image(imgdata.encode(), cwidth, cheight - 1 if cheight else cheight)
if node in nodepositions:
cursor_restore()
reset_cursor(node)
else:
sys.stdout.write('\n')
sys.stdout.flush()
if options.screenshot:
try:
cursor_hide()
do_screenshot()
except KeyboardInterrupt:
pass
finally:
cursor_show()
cursor_down(numrows)
sys.stdout.write('\n')
sys.exit(0)
def kill(noderange):
sess = client.Command()
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
if not envstring:
envstring = 'xterm'
nodes = []
for res in sess.read('/noderange/{0}/nodes/'.format(noderange)):
node = res.get('item', {}).get('href', '/').replace('/', '')
if not node:
sys.stderr.write(res.get('error', repr(res)) + '\n')
sys.exit(1)
nodes.append(node)
for node in nodes:
command = "ps auxww | grep {0} | grep console | egrep '\\b{1}\\b' | grep -v grep | awk '{{print $2}}'".format(envstring, node)
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
try:
process_id = stdout.decode('utf-8').split()[0]
except IndexError:
sys.stderr.write(node + ": console window not found \n")
continue
subprocess.Popen(["kill", process_id], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sys.exit(0)
def handle_geometry(envlist, sizegeometry, side_pad=0, top_pad=0, first=False):
if '-geometry' in envlist:
g_index = envlist.index('-geometry')
elif '-g' in envlist:
g_index = envlist.index('-g')
else:
g_index = 0
if g_index:
if first:
envlist[g_index+1] = '{0}+{1}+{2}'.format(envlist[g_index+1],side_pad, top_pad)
else:
envlist[g_index+1] = '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad)
else:
envlist.insert(1, '-geometry')
envlist.insert(2, '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad))
g_index = 1
return envlist
# add funcltionality to close/kill all open consoles
if killcon:
kill(noderange)
#added functionality for wcons
if options.windowed:
result=subprocess.Popen(['xwininfo', '-root'], stdout=subprocess.PIPE)
rootinfo=result.communicate()[0]
result.wait()
for line in rootinfo.decode('utf-8').split('\n'):
if 'Width' in line:
screenwidth = int(line.split(':')[1])
if 'Height' in line:
screenheight = int(line.split(':')[1])
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
if not envstring:
sizegeometry='100x31'
corrected_x, corrected_y = (13,84)
envlist = handle_geometry(['xterm'] + pass_through_args + ['-e'],sizegeometry, first=True)
#envlist=['xterm', '-bg', 'black', '-fg', 'white', '-geometry', '{sizegeometry}+0+0'.format(sizegeometry=sizegeometry), '-e']
else:
envlist=os.environ.get('NODECONSOLE_WINDOWED_COMMAND').split(' ')
if envlist[0] == 'xterm':
if '-geometry' in envlist:
g_index = envlist.index('-geometry')
elif '-g' in envlist:
g_index = envlist.index('-g')
else:
g_index = 0
if g_index:
envlist[g_index+1] = envlist[g_index+1] + '+0+0'
else:
envlist.insert(1, '-geometry')
envlist.insert(2, '100x31+0+0')
g_index = 1
nodes = []
sess = client.Command()
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
node = res.get('item', {}).get('href', '/').replace('/', '')
if not node:
sys.stderr.write(res.get('error', repr(res)) + '\n')
sys.exit(1)
nodes.append(node)
if options.tile and not envlist[0] == 'xterm':
sys.stderr.write('[ERROR] UNSUPPORTED OPTIONS. \nWindowed and tiled consoles are only supported when using xterm \n')
sys.exit(1)
firstnode=nodes[0]
nodes.pop(0)
with open(os.devnull, 'wb') as devnull:
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode), '-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode) ] , stdin=devnull)
time.sleep(2)
s=socket.socket(socket.AF_UNIX)
winid=''
try:
s.connect('/tmp/controlpath-{firstnode}'.format(firstnode=firstnode))
s.recv(64)
s.send(b'GETWINID')
winid=s.recv(64).decode('utf-8')
except:
time.sleep(2)
# try to get id of first panel/xterm window using name
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
wintr=win.communicate()[0]
for line in wintr.decode('utf-8').split('\n'):
if 'console: {firstnode}'.format(firstnode=firstnode) in line or 'confetty' in line:
win_obj = [ele for ele in line.split(' ') if ele.strip()]
winid = win_obj[0]
if winid:
firstnode_window=subprocess.Popen(['xwininfo', '-id', '{winid}'.format(winid=winid)], stdout=subprocess.PIPE)
xinfo=firstnode_window.communicate()[0]
xinfl = xinfo.decode('utf-8').split('\n')
for line in xinfl:
if 'Absolute upper-left X:' in line:
side_pad = int(line.split(':')[1])
elif 'Absolute upper-left Y:' in line:
top_pad = int(line.split(':')[1])
elif 'Width:' in line:
window_width = int(line.split(':')[1])
elif 'Height' in line:
window_height = int(line.split(':')[1])
elif '-geometry' in line:
l = re.split(' |x|-|\\+', line)
l_nosp = [ele for ele in l if ele.strip()]
wmxo = int(l_nosp[1])
wmyo = int(l_nosp[2])
sizegeometry = str(wmxo) + 'x' + str(wmyo)
else:
pass
window_width += side_pad*2
window_height += side_pad+top_pad
screenwidth -= wmxo
screenheight -= wmyo
currx = window_width
curry = 0
for node in sortutil.natural_sort(nodes):
if options.tile and envlist[0] == 'xterm':
corrected_x = currx
corrected_y = curry
xgeometry = '{0}+{1}+{2}'.format(sizegeometry, corrected_x, corrected_y)
currx += window_width
if currx + window_width >= screenwidth:
currx=0
curry += window_height
if curry > screenheight:
curry =top_pad
if not envstring:
envlist= handle_geometry(envlist, sizegeometry, corrected_x, corrected_y)
else:
if g_index:
envlist[g_index+1] = xgeometry
elif envlist[0] == 'xterm':
envlist=handle_geometry(envlist, sizegeometry, side_pad, top_pad)
side_pad+=(side_pad+1)
top_pad+=(top_pad+30)
else:
pass
with open(os.devnull, 'wb') as devnull:
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node), '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
sys.exit(0)
#end of wcons
if options.tile:
null = open('/dev/null', 'w')
nodes = []
@@ -741,13 +50,8 @@ if options.tile:
sys.exit(1)
nodes.append(node)
initial = True
in_tmux = False
pane = 0
sessname = 'nodeconsole_{0}'.format(os.getpid())
if os.environ.get("TMUX"):
initial = False
in_tmux = True
subprocess.call(['tmux', 'rename-session', sessname])
for node in sortutil.natural_sort(nodes):
panename = '{0}:{1}'.format(sessname, pane)
if initial:
@@ -768,8 +72,7 @@ if options.tile:
pane += 1
subprocess.call(['tmux', 'select-pane', '-t', sessname])
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
if not in_tmux:
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
else:
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
+26 -97
View File
@@ -48,19 +48,8 @@ def armonce(nr, cli):
pass
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': ''}
def setpending(nr, profile, cli):
args = {'deployment.pendingprofile': profile}
if not profile.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
args['deployment.profile'] = ''
@@ -80,7 +69,6 @@ def main(args):
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true')
ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
ap.add_argument('-r', '--redeploy', help='Redeploy nodes with the current or pending profile', action='store_true')
ap.add_argument('noderange', help='Set of nodes to deploy')
ap.add_argument('profile', nargs='?', help='Profile name to deploy')
args, extra = ap.parse_known_args(args)
@@ -90,86 +78,36 @@ 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 and not args.redeploy:
if not args.profile and args.network:
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:
sys.stderr.write(
'The -c/--clear option should not be used with a profile, '
'it is a request to not deploy any profile, and will clear '
'whatever the current profile is without being specified\n')
return 1
if extra:
sys.stderr.write('Unrecognized arguments: ' + repr(extra) + '\n')
c = client.Command()
c.stop_if_noderange_over(args.noderange, args.maxnodes)
errnodes = set([])
for rsp in c.read('/noderange/{0}/nodes/'.format(args.noderange)):
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.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', {}):
lockstate = lockinfo['databynode'][node]['lock']['value']
if lockstate == 'locked':
lockednodes.append(node)
if lockednodes:
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
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('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)
sys.stderr.write('No deployment profiles available, try osdeploy fiimport or imgutil capture\n')
sys.exit(1)
armonce(args.noderange, c)
setpending(args.noderange, args.profile, profilebynode, c)
setpending(args.noderange, args.profile, c)
else:
databynode = {}
for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
@@ -178,7 +116,7 @@ def main(args):
if node not in databynode:
databynode[node] = {}
for attr in dbn[node]:
if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile', 'deployment.state', 'deployment.state_detail'):
if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile'):
databynode[node][attr] = dbn[node][attr].get('value', '')
for node in sortutil.natural_sort(databynode):
profile = databynode[node].get('deployment.pendingprofile', '')
@@ -199,20 +137,10 @@ def main(args):
armed = ' (node authentication armed)'
else:
armed = ''
stateinfo = ''
deploymentstate = databynode[node].get('deployment.state', '')
if deploymentstate:
statedetails = databynode[node].get('deployment.state_detail', '')
if statedetails:
stateinfo = '{}: {}'.format(deploymentstate, statedetails)
else:
stateinfo = deploymentstate
if stateinfo:
print('{0}: {1} ({2})'.format(node, profile, stateinfo))
else:
print('{0}: {1}{2}'.format(node, profile, armed))
print('{0}: {1}{2}'.format(node, profile, armed))
sys.exit(0)
if not args.clear and args.network and not args.prepareonly:
errnodes = set([])
if args.network and not args.prepareonly:
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
bootmode='uefi',
persistent=False,
@@ -223,6 +151,7 @@ def main(args):
','.join(errnodes)))
return 1
rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
return rc
return 0
if __name__ == '__main__':
+11 -74
View File
@@ -48,54 +48,20 @@ columnmapping = {
'Advertised IP': 'otheripaddrs',
'Other IP': 'otheripaddrs',
}
#TODO: add chassis uuid
def register_endpoint(options, session, addr):
neednewline = False
current = 0
for rsp in session.update('/discovery/register', {'addresses': addr}):
if 'count' in rsp:
total = rsp['count']
elif total > 1:
current += 1
sys.stdout.write('\x1b[2K\r{0:.1f}% Scanned'.format(100 * current / total))
sys.stdout.flush()
neednewline = True
if 'created' in rsp:
if neednewline:
print('')
neednewline = False
print('Registered: {0}'.format(rsp['created']))
if neednewline:
print('')
def subscribe_discovery(options, session, subscribe, targ):
keyn = 'subscribe' if subscribe else 'unsubscribe'
payload = {keyn: targ}
if subscribe:
for rsp in session.update('/discovery/subscriptions/{0}'.format(targ), payload):
if 'status' in rsp:
print(rsp['status'])
else:
for rsp in session.delete('/discovery/subscriptions/{0}'.format(targ)):
if 'status' in rsp:
print(rsp['status'])
def print_disco(options, session, currmac, outhandler, columns):
procinfo = {}
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
procinfo.update(tmpinfo)
if 'Switch' in columns or 'Port' in columns:
if 'switch' in procinfo:
procinfo['port'] = procinfo['switchport']
else:
for tmpinfo in session.read(
'/networking/macs/by-mac/{0}'.format(currmac)):
if 'ports' in tmpinfo:
# The api sorts so that the most specific available value
# is last
procinfo.update(tmpinfo['ports'][-1])
for tmpinfo in session.read(
'/networking/macs/by-mac/{0}'.format(currmac)):
if 'ports' in tmpinfo:
# The api sorts so that the most specific available value
# is last
procinfo.update(tmpinfo['ports'][-1])
record = []
for col in columns:
rawcol = columnmapping[col]
@@ -123,7 +89,7 @@ def process_header(header):
fields.append('serial')
elif datum == 'uuid':
fields.append('uuid')
elif datum in ('bmc', 'imm', 'xcc', 'ip'):
elif datum in ('bmc', 'imm', 'xcc'):
fields.append('hardwaremanagement.manager')
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
fields.append('net.bmc.ipv4_gateway')
@@ -191,7 +157,6 @@ def import_csv(options, session):
if field in unique_fields:
unique_data[field] = set([])
broken = False
alldata=[]
for record in records:
currfields = list(fields)
nodedatum = {}
@@ -208,15 +173,9 @@ def import_csv(options, session):
nodedatum[currfield] = datum
if not datum_complete(nodedatum):
sys.exit(1)
alldata.append(nodedatum)
allthere = True
for nodedatum in alldata:
if not search_record(nodedatum, options, session) and not broken:
allthere = False
blocking_scan(session)
break
for nodedatum in alldata:
if not allthere and not search_record(nodedatum, options, session):
if not search_record(nodedatum, options, session):
sys.stderr.write(
"Could not match the following data: " +
repr(nodedatum) + '\n')
@@ -237,12 +196,8 @@ def import_csv(options, session):
print('Defined ' + res['created'])
else:
print(repr(res))
child = os.fork()
if child:
continue
for mac in maclist:
mysess = client.Command()
for res in mysess.update('/discovery/by-mac/{0}'.format(mac),
for res in session.update('/discovery/by-mac/{0}'.format(mac),
{'node': nodename}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
@@ -252,12 +207,6 @@ def import_csv(options, session):
print('Discovered ' + res['assigned'])
else:
print(repr(res))
sys.exit(0)
while True:
try:
os.wait()
except ChildProcessError:
break
if exitcode:
sys.exit(exitcode)
@@ -370,7 +319,7 @@ def blocking_scan(session):
def main():
parser = optparse.OptionParser(
usage='Usage: %prog [list|assign|rescan|clear|subscribe|unsubscribe|register] [options]')
usage='Usage: %prog [list|assign|rescan|clear] [options]')
# -a for 'address' maybe?
# order by
# show state (discovered or..
@@ -408,15 +357,9 @@ def main():
parser.add_option('-o', '--order', dest='order',
help='Order output by given field', metavar='ORDER')
(options, args) = parser.parse_args()
if len(args) == 0 or args[0] not in ('list', 'assign', 'reassign', 'rescan', 'clear', 'subscribe', 'unsubscribe', 'register'):
if len(args) == 0 or args[0] not in ('list', 'assign', 'reassign', 'rescan', 'clear'):
parser.print_help()
sys.exit(1)
if args[0] == 'register' and len(args) != 2:
sys.stderr.write('Register requires target address or range (e.g. 192.168.2.30, 192.168.2.0/24, or 192.168.2.1-192.168.2.30)\n')
sys.exit(1)
if 'subscribe' in args[0] and len(args) != 2:
sys.stderr.write('subscribe/unsubscribe subcommands require switch name as argument\n')
sys.exit(1)
session = client.Command()
if args[0] == 'list':
list_discovery(options, session)
@@ -426,12 +369,6 @@ def main():
assign_discovery(options, session)
if args[0] == 'reassign':
assign_discovery(options, session, False)
if args[0] == 'register':
register_endpoint(options, session, args[1])
if args[0] == 'subscribe':
subscribe_discovery(options, session, True, args[1])
if args[0] == 'unsubscribe':
subscribe_discovery(options, session, False, args[1])
if args[0] == 'rescan':
blocking_scan(session)
print("Rescan complete")
+7 -69
View File
@@ -17,7 +17,6 @@
import codecs
from datetime import datetime as dt
from datetime import timedelta
import optparse
import os
import signal
@@ -42,18 +41,7 @@ argparser = optparse.OptionParser(
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to clear if clearing log, '
'prompting if over the threshold')
argparser.add_option('-l', '--lines', type='int',
help='return the last <n> entries '
'for each node in the eventlog. '
)
argparser.add_option('-t', '--timeframe', type='string',
help='return entries within a specified timeframe '
'for each node in the eventlog. This will return '
'entries from the last hours or days. '
'1h would be one hour, 4d would be four days. '
'format <num>h or <num>d'
)
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -78,11 +66,8 @@ exitcode = 0
def format_event(evt):
retparts = []
if 'timestamp' in evt and evt['timestamp'] is not None:
try:
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
except ValueError:
display = ''
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
dscparts = []
if evt.get('log_id', None):
retparts.append(evt['log_id'] + ':')
@@ -104,34 +89,12 @@ def format_event(evt):
msg = ''
return ' '.join(retparts) + msg
if deletemode:
func = session.delete
session.stop_if_noderange_over(noderange, options.maxnodes)
else:
func = session.read
if options.timeframe:
try:
delta = int(options.timeframe[:-1])
except ValueError:
argparser.print_help()
sys.exit(1)
if options.timeframe[-1].lower() == 'd':
tdelta = timedelta(days=delta)
elif options.timeframe[-1].lower() == 'h':
tdelta = timedelta(hours=delta)
else:
argparser.print_help()
sys.exit(1)
timeframe = dt.now() - tdelta
event_dict = {}
nodes = []
for res in session.read('/noderange/{0}/nodes/'.format(args[0])):
node = res.get('item', {}).get('href', '/').replace('/', '')
nodes.append(node)
event_dict[node] = []
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
@@ -144,31 +107,6 @@ for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
sys.stderr.write('{0}: {1}\n'.format(node, thisdata['error']))
exitcode |= 1
if 'events' in thisdata:
evtdata = thisdata['events']
if options.lines:
event_dict[node].extend(evtdata)
else:
for evt in evtdata:
if options.timeframe:
# check if line is in timeframe
if 'timestamp' in evt and evt['timestamp'] is not None:
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
if display > timeframe:
print('{0}: {1}'.format(node, format_event(evt)))
else:
print('{0}: {1}'.format(node, format_event(evt)))
if options.lines:
for node in nodes:
evtdata_list = event_dict[node]
if len(evtdata_list) != 0:
if len(evtdata_list) > options.lines:
evtdata_list = evtdata_list[-abs(options.lines):]
for evt in evtdata_list:
if options.timeframe:
if 'timestamp' in evt and evt['timestamp'] is not None:
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
if display > timeframe:
print('{0}: {1}'.format(node, format_event(evt)))
else:
print('{0}: {1}'.format(node, format_event(evt)))
evtdata = thisdata['events']
for evt in evtdata:
print('{0}: {1}'.format(node, format_event(evt)))
+13 -43
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -56,7 +56,7 @@ components = ['all']
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][updatestatus][update [--backup <file>]]|[<components>]")
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
argparser.add_option('-m', '--maxnodes', type='int',
@@ -65,18 +65,14 @@ argparser.add_option('-m', '--maxnodes', type='int',
(options, args) = argparser.parse_args()
upfile = None
querystatus = False
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'update':
upfile = args[2]
else:
comps = []
if args[1] == 'list':
comps = args[2:]
elif args[1] == 'updatestatus':
querystatus = True
else:
comps = args[1:]
components = []
@@ -92,9 +88,9 @@ client.check_globbing(noderange)
def get_update_progress(session, url):
for res in session.read(url):
status = res.get('phase', 'error')
percent = res.get('progress', None)
detail = res.get('detail', repr(res)),
status = res['phase']
percent = res['progress']
detail = res['detail']
if status == 'error':
text = 'error!'
else:
@@ -114,24 +110,11 @@ def update_firmware(session, filename):
upargs['bank'] = 'backup'
noderrs = {}
if session.unixdomain:
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
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
@@ -166,13 +149,9 @@ 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/{2}/{1}'.format(
noderange, component, category)):
'/noderange/{0}/inventory/firmware/all/{1}'.format(
noderange, component)):
nodes_matched = True
exitcode |= client.printerror(res)
if 'databynode' not in res:
@@ -192,16 +171,7 @@ def show_firmware(session):
try:
session = client.Command()
if querystatus:
for res in session.read(
'/noderange/{0}/inventory/firmware/updatestatus'.format(noderange)):
for node in res.get('databynode', {}):
currstat = res['databynode'][node].get('status', None)
if currstat:
print('{}: {}'.format(node, currstat))
else:
print(repr(res))
elif upfile is None:
if upfile is None:
show_firmware(session)
else:
update_firmware(session, upfile)
+1 -3
View File
@@ -60,12 +60,10 @@ nodetype="nodegroups"
try:
nodegroups = args[0]
client.check_globbing(nodegroups)
nodelist = '/{0}/{1}/'.format(nodetype,nodegroups)
except IndexError:
argparser.print_help()
sys.exit(1)
nodelist = '/nodegroups/'
client.check_globbing(nodegroups)
session = client.Command()
exitcode = 0
+7 -42
View File
@@ -49,32 +49,23 @@ def pretty(text):
def print_mem_info(node, prefix, meminfo):
memdescfmt = '{0}GB PC'
if meminfo['memory_type'] is None:
memdescfmt = '{0}GB '
elif meminfo['memory_type'] == 'DDR3 SDRAM':
if meminfo['memory_type'] == 'DDR3 SDRAM':
memdescfmt += '3-{1} '
elif 'DDR4' in meminfo['memory_type']:
memdescfmt += '4-{1} '
elif 'DDR5' in meminfo['memory_type']:
memdescfmt += '5-{1} '
elif 'DCPMM' in meminfo['memory_type']:
memdescfmt = '{0}GB {1} '
meminfo['module_type'] = 'DCPMM'
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
if meminfo.get('capacity_mb', None):
capacity = meminfo['capacity_mb'] // 1024
memdesc = memdescfmt.format(capacity, meminfo['speed'])
else:
memdesc = 'Unspecified Module'
memdesc = memdescfmt.format(capacity, meminfo['speed'])
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
print('{0}: {1} manufacturer: {2}'.format(
node, prefix, meminfo['manufacturer']))
@@ -108,7 +99,6 @@ usedprefixes = set([])
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
argparser.add_option('-j', '--json', action='store_true', help='Output JSON')
argparser.add_option('-s', '--store', action='store_true', help='Store serial, model, and uuid into id.serial, id.model, and id.uuid')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -136,36 +126,17 @@ if len(args) > 1:
try:
if options.json:
databynode = {}
if options.store and len(args) <= 1:
url = '/noderange/{0}/inventory/hardware/all/system'
pushattribs = {}
session = client.Command()
for res in session.read(url.format(noderange)):
printerror(res)
if 'databynode' not in res:
continue
for node in res['databynode']:
if options.store and node not in pushattribs:
pushattribs[node] = {}
printerror(res['databynode'][node], node)
if 'inventory' not in res['databynode'][node]:
continue
for inv in res['databynode'][node]['inventory']:
prefix = inv['name']
if options.store and prefix == 'System':
currinfo = inv.get('information', {})
curruuid = currinfo.get('UUID', '')
if curruuid:
curruuid = curruuid.lower()
pushattribs[node]['id.uuid'] = curruuid
currserial = currinfo.get('Serial Number', '')
if currserial:
currserial = currserial.strip()
pushattribs[node]['id.serial'] = currserial
currmodelnum = currinfo.get('Model', '')
if currmodelnum:
currmodelnum = currmodelnum.strip()
pushattribs[node]['id.model'] = currmodelnum
idx = 2
while (node, prefix) in usedprefixes:
prefix = '{0} {1}'.format(inv['name'], idx)
@@ -177,7 +148,7 @@ try:
if node not in databynode:
databynode[node] = {}
databynode[node][prefix] = inv
elif not options.store:
else:
print('{0}: {1}: Not Present'.format(node, prefix))
continue
info = inv['information']
@@ -208,18 +179,12 @@ try:
databynode[node] = {}
databynode[node][prefix] = inv
break
elif not options.store:
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
pretty(datum),
info[datum]))
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
pretty(datum),
info[datum]))
if options.json:
print(json.dumps(databynode, sort_keys=True, indent=4,
separators=(',', ': ')))
if pushattribs:
for node in pushattribs:
for rsp in session.update('/nodes/{0}/attributes/current'.format(node), pushattribs[node]):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
-176
View File
@@ -1,176 +0,0 @@
#!/usr/libexec/platform-python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__author__ = 'tkucherera'
import optparse
import os
import signal
import sys
import subprocess
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(
usage="Usage: %prog <start_node> -i <interface> <end_node> -e <eface>",
)
argparser.add_option('-i', '--interface', type='str',
help='interface to check path against for the start node')
argparser.add_option('-e', '--eface', type='str',
help='interface to check path against for the end node')
argparser.add_option('-c', '--cumulus', action="store_true", dest="cumulus",
help='return layer 2 route through cumulus switches only')
(options, args) = argparser.parse_args()
try:
start_node = args[0]
end_node = args[1]
interface = options.interface
eface = options.eface
except IndexError:
argparser.print_help()
sys.exit(1)
session = client.Command()
def get_neighbors(switch):
switch_neigbors = []
url = '/networking/neighbors/by-switch/{0}/by-peername/'.format(switch)
for neighbor in session.read(url):
try:
switch = neighbor['item']['href'].strip('/')
except:
continue
if switch in all_switches:
switch_neigbors.append(switch)
return switch_neigbors
def find_path(start, end, path=[]):
path = path + [start]
if start == end:
return path # If start and end are the same, return the path
for node in get_neighbors(start):
if node not in path:
new_path = find_path(node, end, path)
if new_path:
return new_path # If a path is found, return it
return None # If no path is found, return None
def is_cumulus(switch):
try:
read_attrib = subprocess.check_output(['nodeattrib', switch, 'hardwaremanagement.method'])
except subprocess.CalledProcessError:
return False
for attribs in read_attrib.decode('utf-8').split('\n'):
if len(attribs.split(':')) > 1:
attrib = attribs.split(':')
if attrib[2].strip() == 'affluent':
return True
else:
return False
else:
return False
def host_to_switch(node, interface=None):
# first check the the node config to see what switches are connected
# if host is in rhel can use nmstate package
if node in all_switches:
return [node]
switches = []
netarg = 'net.*.switch'
if interface:
netarg = 'net.{0}.switch'.format(interface)
try:
read_attrib = subprocess.check_output(['nodeattrib', node, netarg])
except subprocess.CalledProcessError:
return False
for attribs in read_attrib.decode('utf-8').split('\n'):
attrib = attribs.split(':')
try:
if ' net.mgt.switch' in attrib or attrib[2] == '':
continue
except IndexError:
continue
switch = attrib[2].strip()
if is_cumulus(switch) and options.cumulus:
switches.append(switch)
else:
switches.append(switch)
return switches
def path_between_nodes(start_switches, end_switches):
for start_switch in start_switches:
for end_switch in end_switches:
if start_switch == end_switch:
return [start_switch]
else:
path = find_path(start_switch, end_switch)
if path:
return path
else:
return 'No path found'
all_switches = []
for res in session.read('/networking/neighbors/by-switch/'):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
switch = (res['item']['href'].replace('/', ''))
all_switches.append(switch)
end_nodeslist = []
nodelist = '/noderange/{0}/nodes/'.format(end_node)
for res in session.read(nodelist):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
elem=(res['item']['href'].replace('/', ''))
end_nodeslist.append(elem)
start_switches = host_to_switch(start_node, interface)
for end_node in end_nodeslist:
if end_node:
end_switches = host_to_switch(end_node, eface)
if not end_switches:
print('Error: net.{0}.switch attribute is not valid')
continue
path = path_between_nodes(start_switches, end_switches)
print(f'{start_node} to {end_node}: {path}')
# TODO dont put switches that are connected through management interfaces.
+6 -20
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
@@ -65,30 +65,16 @@ 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:
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
of = open(filename, 'rb')
try:
session.add_file(filename, 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]:
+3 -10
View File
@@ -1,4 +1,4 @@
#!/usr/libexec/platform-python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -15,15 +15,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__author__ = 'jjohnson2,alin37,wgrzeda'
__author__ = 'jjohnson2,alin37'
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
@@ -41,12 +39,9 @@ def main():
" or: %prog [options] noderange <nodeattribute>...")
argparser.add_option('-b', '--blame', action='store_true',
help='Show information about how attributes inherited')
argparser.add_option('-d', '--delim', metavar="STRING", default = "\n",
help='Delimiter separating the values')
(options, args) = argparser.parse_args()
noderange=""
nodelist=""
list = []
try:
noderange = args[0]
nodelist = '/noderange/{0}/nodes/'.format(noderange)
@@ -66,9 +61,7 @@ def main():
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
elem=(res['item']['href'].replace('/', ''))
list.append(elem)
print(options.delim.join(list))
print(res['item']['href'].replace('/', ''))
sys.exit(exitcode)
+10 -25
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
@@ -101,37 +101,22 @@ 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:
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
of = open(filename, 'rb')
try:
session.add_file(filename, of.fileno(), 'rb')
except Exception:
pass
nodeurls = {}
for res in session.create(resource, upargs):
if 'created' not in res:
-157
View File
@@ -1,157 +0,0 @@
#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import deque
import optparse
import os
import select
import signal
import subprocess
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of commands to run at a time')
argparser.add_option('-o', '--origname', action='store_true',
help='Use original nodename in print out even if substituted')
argparser.add_option('-s', '--substitutename',
help='Use a different name other than the nodename for ping, with {}, it is the entire name evaluated as an expression, otherwise it is used as a suffix')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
(options, args) = argparser.parse_args()
if len(args) < 1:
argparser.print_help()
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
currprocs = 0
all = set([])
poller = select.epoll()
pipedesc = {}
pendingexecs = deque()
exitcode = 0
nodemap = {}
if options.substitutename:
subname = options.substitutename
if '{' not in subname:
subname = '{node}' + subname
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': subname}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
nodemap[node] = ex[node]['value']
for rsp in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
exitcode |= rsp.get('errorcode', 1)
if rsp.get('item', {}).get('href', None):
node = rsp['item']['href'][:-1]
pingnode = nodemap.get(node, node)
cmdv = ['ping', '-c', '1', '-W', '1', pingnode]
if currprocs < concurrentprocs:
currprocs += 1
if options.origname:
run_cmdv(node, cmdv, all, poller, pipedesc)
else:
run_cmdv(pingnode, cmdv, all, poller, pipedesc)
else:
if options.origname:
pendingexecs.append((node, cmdv))
else:
pendingexecs.append((pingnode, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy = poller.poll(10)
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 not data:
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':
if ret:
print('{0}: no_ping'.format(node))
else:
print('{0}: ping'.format(node))
if 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)
if options.nonodeprefix:
sys.stdout.write(line)
else:
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy = poller.poll(10)
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, poller, pipedesc):
nopen = subprocess.Popen(
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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__':
run()
+6 -14
View File
@@ -33,7 +33,7 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage="Usage: %prog [options] <noderange> "
"([status|on|off|shutdown|boot|reset|pdu_status|pdu_off|pdu_on])")
"([status|on|off|shutdown|boot|reset])")
argparser.add_option('-p', '--showprevious', dest='previous',
action='store_true', default=False,
help='Show previous power state')
@@ -51,27 +51,22 @@ except IndexError:
client.check_globbing(noderange)
setstate = None
if len(args) > 1:
setstate = args[1]
if setstate == 'softoff':
setstate = 'shutdown'
elif not args[1] in ('stat', 'state', 'status'):
setstate = args[1]
if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset', 'pdu_status', 'pdu_stat', 'pdu_on', 'pdu_off', 'status', 'stat', 'state'):
if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset'):
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
session.add_precede_key('oldstate')
powurl = 'state'
if setstate and setstate.startswith('pdu_'):
setstate = setstate.replace('pdu_', '')
powurl = 'inlets/all'
if setstate in ('status', 'state', 'stat'):
setstate = None
if options.previous:
# get previous states
prev = {}
for rsp in session.read("/noderange/{0}/power/{1}".format(noderange, powurl)):
for rsp in session.read("/noderange/{0}/power/state".format(noderange)):
# gets previous (current) states
databynode = rsp["databynode"]
@@ -82,7 +77,4 @@ if options.previous:
# add dictionary to session
session.add_precede_dict(prev)
def outhandler(node, res):
for k in res[node]:
client.cprint('{0}: {1}: {2}'.format(node, k.replace('inlet_', ''), res[node][k]))
sys.exit(session.simple_noderange_command(noderange, '/power/{0}'.format(powurl), setstate, promptover=options.maxnodes, key='state', outhandler=outhandler))
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
+1 -18
View File
@@ -46,10 +46,6 @@ def run():
help='Specify a maximum number of '
'nodes to run rsync to, '
'prompting if over the threshold')
argparser.add_option('-l', '--loginname', type='str',
help='Username to use when connecting, defaults to current user.')
argparser.add_option('-s', '--substitutename',
help='Use a different name other than the nodename for rsync')
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of nodes to concurrently rsync')
# among other things, FD_SETSIZE limits. Besides, spawning too many
@@ -65,20 +61,7 @@ def run():
c = client.Command()
cmdstr = ' '.join(args[:-1])
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
targname = options.substitutename
if targname and '{' in targname:
targname = targname + ':'
elif targname:
targname = '{node}' + targname + ':'
else:
targname = '{node}:'
if options.loginname:
cmdstr += ' {}@'.format(options.loginname) + targname + targpath
else:
cmdstr += ' {}'.format(targname) + targpath
cmdstr += ' {node}:' + targpath
currprocs = 0
all = set([])
+14 -31
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -35,12 +35,9 @@ if path.startswith('/opt'):
import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run():
global devnull
devnull = open(os.devnull, 'rb')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] <noderange> <command expression>",
epilog="Expressions are the same as in attributes, e.g. "
@@ -67,7 +64,6 @@ def run():
currprocs = 0
all = set([])
poller = select.epoll()
pipedesc = {}
pendingexecs = deque()
exitcode = 0
@@ -85,23 +81,19 @@ def run():
cmdv = shlex.split(cmd)
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, poller, pipedesc)
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy = poller.poll(10)
rdy, _, _ = select.select(all, [], [], 10)
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):
while data and select.select([r], [], [], 0)[0]:
data = r.readline()
if data:
if desc['type'] == 'stdout':
@@ -121,12 +113,10 @@ def run():
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()
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
@@ -136,27 +126,20 @@ def run():
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy = poller.poll(10)
rdy, _, _ = select.select(all, [], [], 10)
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, poller, pipedesc):
try:
nopen = subprocess.Popen(
cmdv, stdin=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}"'.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}
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
'type': 'stderr'}
all.add(nopen.stdout)
poller.register(nopen.stdout, select.EPOLLIN)
all.add(nopen.stderr)
poller.register(nopen.stderr, select.EPOLLIN)
if __name__ == '__main__':
run()
+2 -10
View File
@@ -37,12 +37,6 @@ class hybridcsv(csv.excel):
lineterminator = '\n'
def floatformat(num):
fm = u'{:.5f}'.format(num).rstrip('0')
if fm[-1:] == u'.':
return fm + u'0'
return fm
csv.register_dialect('hybrid', hybridcsv)
import confluent.client as client
@@ -67,7 +61,7 @@ argparser.add_option('-n', '--numreadings', type='int',
argparser.add_option('-c', '--csv', action='store_true',
help='Output in CSV format')
argparser.add_option('-s', '--skipnumberless', action='store_true',
help='Do not show non-numeric sensors')
help='Output in CSV format')
(options, args) = argparser.parse_args()
repeatmode = False
if options.interval:
@@ -141,7 +135,7 @@ def sensorpass(showout=True, appendtime=False):
if sensedata['value'] is None:
showval = ''
elif isinstance(sensedata['value'], float):
showval = u' {0} '.format(floatformat(sensedata['value']))
showval = u' {0:.5f} '.format(sensedata['value'])
else:
showval = u' {0} '.format(sensedata['value'])
if sensedata['units'] not in (None, u''):
@@ -197,8 +191,6 @@ def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
datum = ','.join([datum, healthstates])
else:
datum = healthstates
if isinstance(datum, float):
datum = floatformat(datum)
rowdata.append(datum)
except KeyError:
rowdata.append('N/A')
+35 -59
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -34,11 +34,10 @@ if path.startswith('/opt'):
import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run():
global devnull
devnull = open(os.devnull, 'rb')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
@@ -53,8 +52,6 @@ def run():
help='Specify a custom port for ssh')
argparser.add_option('-s', '--substitutename',
help='Use a different name other than the nodename for ssh')
argparser.add_option('-x', '--noexpression', action='store_true',
help='Suppress expression expansion of command')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
@@ -91,54 +88,38 @@ def run():
ex = exp.get('databynode', ())
for node in ex:
nodemap[node] = ex[node]['value']
cmdparms = []
if options.noexpression:
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]
cmdparms.append((node, cmdstr))
else:
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmdparms.append((node, ex[node]['value']))
poller = select.epoll()
for node, cmd in cmdparms:
sshnode = nodemap.get(node, node)
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmdv = ['ssh']
if options.port:
cmdv += ['-p', '{0}'.format(options.port)]
if options.loginname:
cmdv += ['-l', options.loginname]
cmdv += [sshnode, cmd]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, poller, pipedesc)
else:
pendingexecs.append((node, cmdv))
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value']
sshnode = nodemap.get(node, node)
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmdv = ['ssh']
if options.port:
cmdv += ['-p', '{0}'.format(options.port)]
if options.loginname:
cmdv += ['-l', options.loginname]
cmdv += [sshnode, cmd]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy = poller.poll(10)
rdy, _, _ = select.select(all, [], [], 10)
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):
while data and select.select([r], [], [], 0)[0]:
data = r.readline()
if data:
if desc['type'] == 'stdout':
@@ -158,37 +139,32 @@ def run():
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()
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
if options.nonodeprefix:
sys.stdout.write(line)
else:
line = line.lstrip('\x08')
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy = poller.poll(10)
rdy, _, _ = select.select(all, [], [], 10)
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, poller, pipedesc):
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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}
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
'type': 'stderr'}
all.add(nopen.stdout)
all.add(nopen.stderr)
poller.register(nopen.stdout, select.EPOLLIN)
poller.register(nopen.stderr, select.EPOLLIN)
if __name__ == '__main__':
+9 -39
View File
@@ -16,11 +16,13 @@
# limitations under the License.
import argparse
import base64
import csv
import fcntl
import io
import os
import numpy as np
import os
import subprocess
import sys
try:
@@ -33,31 +35,7 @@ except ImportError:
pass
def iterm_draw(data):
databuf = data.getbuffer()
datalen = len(databuf)
data = base64.b64encode(databuf).decode('utf8')
sys.stdout.write(
'\x1b]1337;File=inline=1;size={}:'.format(datalen))
sys.stdout.write(data)
sys.stdout.write('\a')
sys.stdout.write('\n')
sys.stdout.flush()
def kitty_draw(data):
data = base64.b64encode(data.getbuffer())
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
sys.stdout.write('\x1b_Ga=T,f=100,m={};'.format(m))
sys.stdout.write(chunk.decode('utf8'))
sys.stdout.write('\x1b\\')
sys.stdout.flush()
sys.stdout.write('\n')
def plot(gui, output, plotdata, bins, fmt):
def plot(gui, output, plotdata, bins):
import matplotlib as mpl
if gui and mpl.get_backend() == 'agg':
sys.stderr.write('Error: No GUI backend available and -g specified!\n')
@@ -73,15 +51,8 @@ def plot(gui, output, plotdata, bins, fmt):
tdata = io.BytesIO()
plt.savefig(tdata)
if not gui and not output:
if fmt == 'environment':
fmt = os.environ.get('CONFLUENT_IMAGE_PROTOCOL', 'kitty')
if fmt == 'sixel':
writer = DumbWriter()
writer.draw(tdata)
elif fmt == 'kitty':
kitty_draw(tdata)
elif fmt == 'iterm':
iterm_draw(tdata)
writer = DumbWriter()
writer.draw(tdata)
return n, bins
def textplot(plotdata, bins):
@@ -110,8 +81,7 @@ histogram = False
aparser = argparse.ArgumentParser(description='Quick access to common statistics')
aparser.add_argument('-c', type=int, default=0, help='Column number to analyze (default is last column)')
aparser.add_argument('-d', default=None, help='Value used to separate columns')
aparser.add_argument('-x', default=False, action='store_true', help='Output histogram in graphical format')
aparser.add_argument('-f', default='environment', help='Format for histogram output (sixel/iterm/kitty)')
aparser.add_argument('-x', default=False, action='store_true', help='Output histogram in sixel format')
aparser.add_argument('-s', default=0, help='Number of header lines to skip before processing')
aparser.add_argument('-g', default=False, action='store_true', help='Open histogram in separate graphical window')
aparser.add_argument('-o', default=None, help='Output histogram to the specified filename in PNG format')
@@ -168,7 +138,7 @@ while data:
data = list(csv.reader([data], delimiter=delimiter))[0]
n = None
if args.g or args.o or args.x:
n, bins = plot(args.g, args.o, plotdata, bins=args.b, fmt=args.f)
n, bins = plot(args.g, args.o, plotdata, bins=args.b)
if args.t:
n, bins = textplot(plotdata, bins=args.b)
print('Samples: {5} Min: {3} Median: {0} Mean: {1} Max: {4} StandardDeviation: {2} Sum: {6}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata), np.sum(plotdata)))
+13 -26
View File
@@ -42,9 +42,9 @@ _attraliases = {
}
try:
getinput = raw_input
input = raw_input
except NameError:
getinput = input
pass
class NestedDict(dict):
@@ -57,7 +57,7 @@ def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
# current python version
if isinstance(instr, bytes) and not isinstance(instr, str):
return instr.decode('utf-8', errors='replace')
return instr.decode('utf-8')
elif not isinstance(instr, bytes) and not isinstance(instr, str):
return instr.encode('utf-8')
return instr
@@ -208,7 +208,7 @@ class Command(object):
def add_precede_dict(self, dict):
self._prevdict = dict
def handle_results(self, ikey, rc, res, errnodes=None, outhandler=None):
def handle_results(self, ikey, rc, res, errnodes=None):
if 'error' in res:
if errnodes is not None:
errnodes.add(self._currnoderange)
@@ -245,12 +245,10 @@ class Command(object):
node, val, self._prevdict[node]))
else:
cprint('{0}: {1}'.format(node, val))
elif outhandler:
outhandler(node, res)
return rc
def simple_noderange_command(self, noderange, resource, input=None,
key=None, errnodes=None, promptover=None, outhandler=None, **kwargs):
key=None, errnodes=None, promptover=None, **kwargs):
try:
self._currnoderange = noderange
rc = 0
@@ -264,13 +262,13 @@ class Command(object):
if input is None:
for res in self.read('/noderange/{0}/{1}'.format(
noderange, resource)):
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
rc = self.handle_results(ikey, rc, res, errnodes)
else:
self.stop_if_noderange_over(noderange, promptover)
kwargs[ikey] = input
for res in self.update('/noderange/{0}/{1}'.format(
noderange, resource), kwargs):
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
rc = self.handle_results(ikey, rc, res, errnodes)
self._currnoderange = None
return rc
except KeyboardInterrupt:
@@ -286,9 +284,9 @@ class Command(object):
nodename = list(self.read(
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
nodename = nodename[:-1]
p = getinput('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
else:
p = getinput('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
if p.lower() != 'y':
sys.stderr.write('Aborting at user request\n')
sys.exit(1)
@@ -403,7 +401,7 @@ class Command(object):
if fingerprint == khf[hostid]:
return
else:
replace = getinput(
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
@@ -464,8 +462,8 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
def _sort_attrib(k):
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
return sortutil.naturalize_string('{}'.format(k[1]['sortid']))
return sortutil.naturalize_string(k[0])
return k[1]['sortid']
return k[0]
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
exitcode = 0
@@ -595,7 +593,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
else:
printmissing.add(attr)
for missing in printmissing:
sys.stderr.write('Error: {0} not a valid attribute\n'.format(missing))
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
return exitcode
@@ -668,9 +666,6 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
for attrib in updateargs[1:]:
keydata[attrib] = None
for res in session.update(targpath, keydata):
for node in res.get('databynode', {}):
for warnmsg in res['databynode'][node].get('_warnings', []):
sys.stderr.write('Warning: ' + warnmsg + '\n')
if 'error' in res:
if 'errorcode' in res:
exitcode = res['errorcode']
@@ -705,14 +700,6 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
noderange, 'attributes/all', dictassign[key], key)
else:
if "=" in updateargs[1]:
update_ready = True
for arg in updateargs[1:]:
if not '=' in arg:
update_ready = False
exitcode = 1
if not update_ready:
sys.stderr.write('Error: {0} Can not set and read at the same time!\n'.format(str(updateargs[1:])))
sys.exit(exitcode)
try:
for val in updateargs[1:]:
val = val.split('=', 1)
-342
View File
@@ -1,342 +0,0 @@
#!/usr/bin/python3
import collections
import os
import struct
import sys
import time
import fcntl
import select
import termios
import tty
def writeout(data):
if isinstance(data, str) and not isinstance(data, bytes):
data = data.encode('utf8')
try:
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.write(data)
else:
sys.stdout.write(data)
except IOError:
time.sleep(0.1)
pass
class LogReplay(object):
def __init__(self, logfile, cblfile):
self.bin = open(cblfile, 'rb')
self.txt = open(logfile, 'rb')
self.cleardata = []
self.clearidx = 0
self.pendingdata = collections.deque([])
self.priordata = collections.deque([])
self.laststamp = None
self.needclear = False
self.lasttxt = ''
def _rewind(self, datasize=None):
curroffset = self.bin.tell() - 16
if self.cleardata and self.clearidx > 1:
self.clearidx -= 1
priordata = self.cleardata[self.clearidx - 1]
return curroffset, priordata
self.cleardata = []
self.clearidx = 0
newoffset = curroffset - 32
if newoffset < 0: #TODO: Follow a log roll
newoffset = 0
if datasize:
while datasize > 0 and newoffset > 0:
self.bin.seek(newoffset)
tmprec = self.bin.read(16)
newoffset -= 32
tmprec = struct.unpack('!BBIHIBBH', tmprec)
if tmprec[1] == 2:
datasize -= tmprec[3]
if newoffset >= 0:
self.bin.seek(newoffset)
return curroffset, None
def debuginfo(self):
return '{0}, {1}'.format(self.bin.tell(), self.clearidx)
def get_output(self, reverse=False):
endoffset = None
output = b''
if reverse: # Forget the uncommited future, if present
output += b'\x1b[2J\x1b[H'
endoffset, priordata = self._rewind(4096)
if priordata is not None:
return priordata, 1
elif self.needclear:
output += b'\x1b[2J\x1b[H'
self.needclear = False
if self.cleardata and self.clearidx < len(self.cleardata):
datachunk = self.cleardata[self.clearidx]
self.clearidx += 1
return datachunk, 1
self.cleardata = []
self.clearidx = 0
while (not reverse) or (self.bin.tell() < endoffset):
record = self.bin.read(16)
if not record:
return b'', 0
record = struct.unpack('!BBIHIBBH', record)
if record[0] > 16:
# Unsupported record, skip
self.bin.seek(record[0] - 16, 1)
continue
type = record[1]
offset = record[2]
size = record[3]
evtdata = record[5]
auxdata = record[6]
if type == 3:
#TODO: provide data for status bar
continue
elif type == 2:
self.laststamp = record[4]
self.txt.seek(offset)
txtout = self.txt.read(size)
if reverse and self.bin.tell() < endoffset:
output += txtout
continue
self.paginate(txtout)
if self.cleardata:
self.clearidx = 0
if not self.cleardata[0]:
self.cleardata = self.cleardata[1:]
if self.cleardata:
if reverse:
output = self.cleardata[-1]
self.clearidx = len(self.cleardata)
else:
output += self.cleardata[0]
self.clearidx = 1
else:
output += txtout
break
if endoffset is not None and endoffset >= 0:
self.bin.seek(endoffset)
self.lasttxt = output
return output, 1
def paginate(self, txtout):
cleardata = [txtout]
nextcleardata = []
for sep in (b'\x1b[2J', b'\x1b[H\x1b[J'):
replacementcleardata = []
for txtout in cleardata:
nextcleardata = txtout.split(sep)
if len(nextcleardata) > 1:
for idx in range(1, len(nextcleardata)):
nextcleardata[idx] = sep + nextcleardata[idx]
replacementcleardata.extend(nextcleardata)
nextcleardata = []
cleardata = replacementcleardata
if len(cleardata) > 1:
self.cleardata = cleardata
return
def begin(self):
self.needclear = True
self.bin.seek(0)
def end(self):
self.bin.seek(0, 2)
def search(self, searchstr, skipfirst=False):
overlap = len(searchstr)
firstoffset = self.bin.tell()
lastoffset = firstoffset
output = self.lasttxt
while searchstr not in output:
output, delay = self.get_output()
if not self.cleardata and lastoffset == self.bin.tell():
self.bin.seek(firstoffset)
return b'', 0
lastoffset = self.bin.tell()
if skipfirst:
output, delay = self.get_output()
while searchstr not in output:
output, delay = self.get_output()
if not self.cleardata and lastoffset == self.bin.tell():
self.bin.seek(firstoffset)
return b'', 0
lastoffset = self.bin.tell()
clear = b'\x1b[2J\x1b[H'
txtchunks = self.lasttxt.split(searchstr)
output = clear + txtchunks[0]
txtchunks = txtchunks[1:]
while txtchunks:
output += b'\x1b[7m' + searchstr + b'\x1b[27m' + txtchunks[0]
txtchunks = txtchunks[1:]
return output, 0
def _replay_to_console(txtfile, binfile):
replay = LogReplay(txtfile, binfile)
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
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)
reverse = False
skipnext = False
quitit = False
searchstr = None
writeout('\x1b[2J\x1b[;H')
prepend = ''
try:
while not quitit:
if not skipnext:
newdata, delay = replay.get_output(reverse)
skipnext = False
reverse = False
if newdata:
if prepend:
newdata = prepend + newdata
prepend = b''
writeout(newdata)
newdata = ''
writeout('\x1b]0;[Time: {0}]\x07'.format(
time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))))
try:
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.flush()
else:
sys.stdout.flush()
except IOError:
pass
while True:
select.select((sys.stdin,), (), (), 86400)
myinput = sys.stdin.read()
if myinput.startswith('\x1b[C') or myinput.startswith('\x1bOC') or myinput == '\r': # right
break
elif myinput.startswith('\x1b[D') or myinput.startswith('\x1bOD') or myinput == 'y': # left
writeout('\x1b[2J\x1b[;H')
reverse = True
break
elif myinput == 'G' or myinput.startswith('\x1b[F'):
replay.end()
reverse = True
break
elif myinput == 'g' or myinput.startswith('\x1b[H'):
replay.begin()
break
elif myinput.lower() == 'q' or myinput == '\x03':
quitit = True
break
elif myinput.lower() == 'd':
writeout('\x1b];{0}\x07'.format(replay.debuginfo()))
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.flush()
else:
sys.stdout.flush()
elif myinput.lower() == '/':
sys.stdout.write('\x1b7\x1b[99999;0H\x1b[2K')
searchstr = ''
nxtchr = '/'
while '\r' not in searchstr:
sys.stdout.write(nxtchr)
sys.stdout.flush()
select.select((sys.stdin,), (), (), 86400)
nxtchr = sys.stdin.read(1)
if nxtchr in ('\x08', '\x7f'):
searchstr = searchstr[:-1]
nxtchr = '\x08 \x08'
else:
searchstr += nxtchr
if not isinstance(searchstr, bytes):
searchstr = searchstr.encode('utf8')
searchstr = searchstr[:-1]
newdata, delay = replay.search(searchstr)
if not newdata:
sys.stdout.write('\x1b[1K\rNo match found')
prepend = b'\x1b[1K\x1b8'
else:
sys.stdout.write('\x1b[1K\x1b8')
sys.stdout.flush()
skipnext = True
break
elif myinput.lower() == 'n' and searchstr:
newdata, delay = replay.search(searchstr, True)
if not newdata:
sys.stdout.write('\x1b7\x1b[99999;0H\x1b[2KNo more matches found')
sys.stdout.flush()
prepend = b'\x1b[1K\x1b8'
skipnext = True
break
else:
pass # print(repr(myinput))
except Exception:
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
writeout('\x1b[m\x1b[?25h\n')
raise
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
writeout('\x1b[m\x1b[?25h\n')
def replay_to_console(txtfile):
if os.path.exists(txtfile + '.cbl'):
binfile = txtfile + '.cbl'
elif '.' not in txtfile:
if '/' not in txtfile:
txtfile = os.getcwd() + '/' + txtfile
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(txtfile + '.cbl'))
sys.exit(1)
else:
fileparts = txtfile.split('.')
prefix = '.'.join(fileparts[:-1])
binfile = prefix + '.cbl.' + fileparts[-1]
if not os.path.exists(binfile):
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(binfile))
sys.exit(1)
_replay_to_console(txtfile, binfile)
def dump_to_console(txtfile):
if os.path.exists(txtfile + '.cbl'):
binfile = txtfile + '.cbl'
elif '.' not in txtfile:
if '/' not in txtfile:
txtfile = os.getcwd() + '/' + txtfile
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(txtfile + '.cbl'))
sys.exit(1)
else:
fileparts = txtfile.split('.')
prefix = '.'.join(fileparts[:-1])
binfile = prefix + '.cbl.' + fileparts[-1]
if not os.path.exists(binfile):
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(binfile))
sys.exit(1)
replay = LogReplay(txtfile, binfile)
quitit = False
writeout('\x1b[2J\x1b[;H')
prepend = ''
try:
while not quitit:
newdata, delay = replay.get_output(False)
if newdata:
if prepend:
newdata = prepend + newdata
prepend = b''
writeout(newdata.replace(b'\r\n', '\r\n[ {0} ] '.format(time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))).encode('utf8')))
newdata = ''
try:
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.flush()
else:
sys.stdout.flush()
except IOError:
pass
else:
break
except Exception:
writeout('\x1b[m\x1b[?25h\n')
raise
writeout('\x1b[m\x1b[?25h\n')
if __name__ == '__main__':
replay_to_console(sys.argv[1])
+16 -48
View File
@@ -18,9 +18,8 @@ import struct
import termios
def get_screengeom():
# returns height in cells, width in cells, width in pixels, height in pixels
return struct.unpack('hhhh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
b'........'))
return struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
b'....'))
class ScreenPrinter(object):
def __init__(self, noderange, client, textlen=4):
@@ -30,9 +29,6 @@ class ScreenPrinter(object):
self.client = client
self.nodeoutput = {}
self.nodelist = []
self.nodepos = {}
self.lastrows = 0
self.fieldchanged = False
maxlen = 0
for ans in self.client.read('/noderange/{0}/nodes/'.format(noderange)):
if 'error' in ans:
@@ -54,19 +50,16 @@ class ScreenPrinter(object):
if len(text) >= self.textlen:
self.textlen = len(text) + 1
self.fieldwidth = self.textlen + self.nodenamelen + 1
self.fieldchanged = True
self.drawscreen(node)
self.drawscreen()
def drawscreen(self, node=None):
def drawscreen(self):
if self.squeeze:
currheight, currwidth, _, _ = get_screengeom()
currheight -= 2
if currheight < 1:
currheight = 1
currheight, currwidth = get_screengeom()
currheight -= 1
numfields = currwidth // self.fieldwidth
fieldformat = '{{0:>{0}}}:{{1:{1}}}'.format(self.nodenamelen,
self.textlen)
#sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
if len(self.nodelist) < (numfields * currheight):
numfields = len(self.nodelist) // currheight + 1
else:
@@ -74,40 +67,16 @@ class ScreenPrinter(object):
fieldformat = '{0}: {1}'
if self.squeeze:
columns = [self.nodelist[x:x+currheight] for x in range(0, len(self.nodelist), currheight)]
if self.lastrows:
sys.stdout.write('\x1b[{0}A'.format(self.lastrows))
if node and self.lastrows == len(columns[0]) and not self.fieldchanged:
targline, targcol = self.nodepos[node]
if targline:
sys.stdout.write('\x1b[{0}B'.format(targline))
if targcol:
sys.stdout.write('\x1b[{0}C'.format(targcol))
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
sys.stdout.write('\r\x1b[{0}B'.format(self.lastrows - targline))
else:
self.lastrows = 0
self.fieldchanged = False
column = 0
for currow in range(0, len(columns[0])):
sys.stdout.write('\x1b[2K')
for col in columns:
try:
node = col[currow]
self.nodepos[node] = (currow, column)
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
column += len(fieldformat.format(node, self.nodeoutput[node]))
except IndexError:
break
sys.stdout.write('\n')
column = 0
self.lastrows += 1
sys.stdout.write('\x1b[J')
for currow in range(0, len(columns[0])):
for col in columns:
try:
node = col[currow]
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
except IndexError:
break
sys.stdout.write('\n')
else:
if node:
nodes = [node]
else:
nodes = self.nodelist
for node in nodes:
for node in self.nodelist:
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
sys.stdout.write('\n')
sys.stdout.flush()
@@ -121,7 +90,6 @@ if __name__ == '__main__':
c = client.Command()
p = ScreenPrinter('d1-d12', c)
p.set_output('d3', 'Upload: 67%')
p.set_output('d7', 'Upload: 67%')
+4 -7
View File
@@ -53,16 +53,13 @@ class TermHandler(object):
connection = None
try:
connection, address = self.socket.accept()
connection.sendall(b"confetty control v1--\n")
connection.sendall("confetty control v1--\n")
cmd = connection.recv(8)
if b'GETWINID' == cmd:
winid = os.environ['WINDOWID']
if not isinstance(winid, bytes):
winid = winid.encode('utf8')
connection.sendall(winid)
if 'GETWINID' == cmd:
connection.sendall(os.environ['WINDOWID'])
connection.close()
except BaseException:
pass
finally:
if connection is not None:
connection.close()
connection.close()
+4 -15
View File
@@ -98,24 +98,17 @@ class GroupedData(object):
self.byoutput = {}
self.header = {}
self.client = confluentconnection
self.detectedpad = None
def generate_byoutput(self):
self.byoutput = {}
thepad = self.detectedpad if self.detectedpad else ''
for n in self.bynode:
output = ''
for ln in self.bynode[n]:
output += ln.replace(thepad, '', 1) + '\n'
output = '\n'.join(self.bynode[n])
if output not in self.byoutput:
self.byoutput[output] = set([n])
else:
self.byoutput[output].add(n)
def add_line(self, node, line):
wspc = re.search(r'^\s*', line).group()
if self.detectedpad is None or len(wspc) < len(self.detectedpad):
self.detectedpad = wspc
if node not in self.bynode:
self.bynode[node] = [line]
else:
@@ -171,9 +164,7 @@ class GroupedData(object):
self.byoutput[outdata])))
currout += '\n====================================\n'
currout += outdata
if currout[-1] != '\n':
currout += '\n'
currout += '\n'
currout += '\n\n'
output.write(currout)
output.flush()
@@ -213,9 +204,7 @@ class GroupedData(object):
else:
currout += '\n'.join(colordiff(modaloutput.split('\n'),
outdata.split('\n')))
if currout[-1] != '\n':
currout += '\n'
currout += '\n'
currout += '\n\n'
if reverse:
revoutput.append(currout)
else:
@@ -230,4 +219,4 @@ if __name__ == '__main__':
if not line:
continue
groupoutput.add_line(*line.split(': ', 1))
groupoutput.print_deviants()
groupoutput.print_deviants()
+4 -12
View File
@@ -19,12 +19,8 @@ import array
import ctypes
import ctypes.util
import confluent.tlv as tlv
try:
import eventlet.green.socket as socket
import eventlet.green.select as select
except ImportError:
import socket
import select
import eventlet.green.socket as socket
import eventlet.green.select as select
from datetime import datetime
import json
import os
@@ -244,12 +240,8 @@ def recv(handle):
cdata = cmsgarr[CMSG_LEN(0).value:]
data = rawbuffer[:i]
if cmsg.cmsg_level == socket.SOL_SOCKET and cmsg.cmsg_type == SCM_RIGHTS:
try:
filehandles.fromstring(bytes(
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
except AttributeError:
filehandles.frombytes(bytes(
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
filehandles.fromstring(bytes(
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
data = json.loads(bytes(data))
return ClientFile(data['filename'], data['mode'], filehandles[0])
else:
+10 -17
View File
@@ -1,23 +1,19 @@
%define name confluent_client
%define version #VERSION#
%define fversion %{lua:
sv, _ = string.gsub("#VERSION#", "[~]", "-")
print(sv)
}
%define release 1
Summary: Client libraries and utilities for confluent
Name: %{name}
Version: %{version}
Release: %{release}
Source0: %{name}-%{fversion}.tar.gz
Source0: %{name}-%{version}.tar.gz
License: Apache2
Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Vendor: Lenovo
Url: http://github.com/lenovo/confluent
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
Url: http://xcat.sf.net/
Obsoletes: confluent_common
%description
@@ -25,20 +21,20 @@ This package enables python development and command line access to
a confluent server.
%prep
%setup -n %{name}-%{fversion}
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
%if "%{dist}" == ".el7"
python2 setup.py build
%else
%if "%{dist}" == ".el8"
python3 setup.py build
%else
python2 setup.py build
%endif
%install
%if "%{dist}" == ".el7"
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%else
%if "%{dist}" == ".el8"
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%else
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%endif
@@ -46,7 +42,4 @@ python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUI
rm -rf $RPM_BUILD_ROOT
%files -f INSTALLED_FILES
%license /opt/confluent/share/licenses/confluent_client/LICENSE
%license /opt/confluent/share/licenses/confluent_client/COPYRIGHT
%defattr(-,root,root)
/opt/confluent
+5 -41
View File
@@ -82,17 +82,6 @@ function _confluent_generic_completion()
return;
fi
}
function _confluent_generic_ng_completion()
{
_confluent_get_args
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_ng_completion
return;
fi
}
_confluent_nodeidentify_completion()
{
COMP_CANDIDATES=("on,off,blink -h")
@@ -153,15 +142,15 @@ _confluent_osimage_completion()
{
_confluent_get_args
if [ $NUMARGS == 2 ]; then
COMPREPLY=($(compgen -W "initialize import importcheck updateboot rebase" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "initialize import updateboot" -- ${COMP_WORDS[COMP_CWORD]}))
return
elif [ ${CMPARGS[1]} == 'initialize' ]; then
COMPREPLY=($(compgen -W "-h -u -s -t -i" -- ${COMP_WORDS[COMP_CWORD]}))
elif [ ${CMPARGS[1]} == 'import' ] || [ ${CMPARGS[1]} == 'importcheck' ]; then
elif [ ${CMPARGS[1]} == 'import' ]; then
compopt -o default
COMPREPLY=()
return
elif [ ${CMPARGS[1]} == 'updateboot' -o ${CMPARGS[1]} == 'rebase' ]; then
elif [ ${CMPARGS[1]} == 'updateboot' ]; then
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
return
fi
@@ -279,20 +268,7 @@ _confluent_nodeattrib_completion()
_confluent_generic_completion
}
_confluent_define_completion()
{
COMP_CANDIDATES=$(nodegroupattrib 'everything' all | awk '{print $2}'|sed -e 's/://')
_confluent_get_args
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
fi
}
_confluent_nodegroupattrib_completion()
{
COMP_CANDIDATES=$(nodegroupattrib 'everything' all | awk '{print $2}'|sed -e 's/://')
_confluent_generic_ng_completion
}
_confluent_nn_completion()
{
_confluent_get_args
@@ -347,30 +323,18 @@ _confluent_ng_completion()
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
}
_confluent_nodediscover_completion()
{
_confluent_get_args
if [ $NUMARGS == 2 ]; then
COMPREPLY=($(compgen -W "list assign rescan clear subscribe unsubscribe register" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
}
complete -F _confluent_nodediscover_completion nodediscover
complete -F _confluent_nodeattrib_completion nodeattrib
complete -F _confluent_nodeattrib_completion nodegroupattrib
complete -F _confluent_nr_completion nodebmcreset
complete -F _confluent_nodesetboot_completion nodeboot
complete -F _confluent_nr_completion nodeconfig
complete -F _confluent_nn_completion nodeconsole
complete -F _confluent_define_completion nodedefine
complete -F _confluent_define_completion nodegroupdefine
complete -F _confluent_nr_completion nodeeventlog
complete -F _confluent_nodefirmware_completion nodefirmware
complete -F _confluent_nodedeploy_completion nodedeploy
complete -F _confluent_osimage_completion osdeploy
complete -F _confluent_imgutil_completion imgutil
complete -F _confluent_nodegroupattrib_completion nodegroupattrib
complete -F _confluent_ng_completion nodegroupattrib
complete -F _confluent_ng_completion nodegroupremove
complete -F _confluent_nr_completion nodehealth
complete -F _confluent_nodeidentify_completion nodeidentify
@@ -1,28 +0,0 @@
confluent2hosts(8) -- Generate /etc/hosts entries for nodes
========================================================
## SYNOPSIS
`confluent2hosts -i <ip expression> -n <name expression> <noderange>`
`confluent2hosts -a <noderange>`
## DESCRIPTION
`confluent2hosts` can be used to help generate /etc/hosts entries for a
noderange. There are two general approaches.
It can be used ad-hoc, using -i and -n to specify the address and name portions respectively. This accepts the standard confluent expression syntax, allowing for things like 172.30.1.{n1} or {node}.{dns.domain} or {bmc}.
It can also read from the confluent db, using `-a`. In this mode, each net.<value>.<attribute> group is pulled together into hosts lines. ipv4_address and ipv6_address fields are associated with the corresponding hostname attributes. You can use `-f` to put the FQDN first.
## EXAMPLES
* Generate /etc/hosts entries ad-hoc using default name:
`# confluent2hosts -i 10.2.3.{n1} d9-d12`
* Generate /etc/hosts entries ad-hoc using alternative name:
`# confluent2hosts -i 10.2.3.{n1} -n "{node}-alt {node}-alt.{dns.domain}" d9-d12`
* Generate /etc/hosts entries using the confluent DB as a reference:
`# confluent2hosts -a d9-d12`
@@ -38,8 +38,5 @@ the json files (password protected, removed from the files, or unprotected).
keys do not change and as such they do not require
incremental backup.
* `-y`, `--yaml
Use YAML instead of JSON as file format
* `-h`, `--help`:
Show help message and exit
-139
View File
@@ -1,139 +0,0 @@
# 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.
-30
View File
@@ -1,30 +0,0 @@
nodeapply(8) -- Execute command on many nodes in a noderange through ssh
=========================================================================
## SYNOPSIS
`nodeapply [options] <noderange>`
## DESCRIPTION
Provides shortcut access to a number of common operations against deployed
nodes. These operations include refreshing ssh certificates and configuration,
rerunning syncflies, and executing specified postscripts.
## OPTIONS
* `-k`, `--security`
Refresh SSH configuration (hosts.equiv and node SSH certificates)
* `-F`, `--sync`
Rerun syncfiles from deployed profile
* `-P SCRIPTS`, `--scripts=SCRIPTS`
Re-run specified scripts, with full path under scripts specified, e.g. post.d/scriptname,firstboot.d/otherscriptname
* `-c COUNT`, `-f COUNT`, `--count=COUNT`
Specify the maximum number of instances to run concurrently
* `-m MAXNODES`, `--maxnodes=MAXNODES`
Specify a maximum number of nodes to run remote ssh command to, prompting
if over the threshold
+7 -33
View File
@@ -7,8 +7,7 @@ nodeattrib(8) -- List or change confluent nodes attributes
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib <noderange> -s <attributes.batch> ...`
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
## DESCRIPTION
@@ -54,18 +53,12 @@ to a blank value will allow masking a group defined attribute with an empty valu
* `-e`, `--environment`:
Set specified attributes based on exported environment variable of matching name.
Environment variable names may be lower case or all upper case.
Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE)
Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
* `-p`, `--prompt`:
Request interactive prompting to provide values rather than the command line
or environment variables.
* `-s`, `--set`:
Set attributes using a batch file rather than the command line. The attributes in the batch file
can be specified as one line of key=value pairs simmilar to command line or each attribute can
be in its own line. Lines that start with # sign will be read as a comment. See EXAMPLES for batch
file syntax.
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Prompt if trying to set attributes on more than
specified number of nodes.
@@ -90,12 +83,12 @@ to a blank value will allow masking a group defined attribute with an empty valu
`# nodeattrib n1,n2 hardwaremanagement --blame`
`n1: hardwaremanagement.manager: 172.30.3.1`
`n1: hardwaremanagement.method: ipmi (inherited from group everything)`
`n1: net.switch: r8e1`
`n1: net.switchport: 14`
`n1: hardwaremanagement.switch: r8e1`
`n1: hardwaremanagement.switchport: 14`
`n2: hardwaremanagement.manager: 172.30.3.2`
`n2: hardwaremanagement.method: ipmi (inherited from group everything)`
`n2: net.switch: r8e1`
`n2: net.switchport: 2`
`n2: hardwaremanagement.switch: r8e1`
`n2: hardwaremanagement.switchport: 2`
* Listing matching nodes of a simple noderange that are set:
`# nodeattrib n1-n2 current`
@@ -123,25 +116,6 @@ to a blank value will allow masking a group defined attribute with an empty valu
`d1: net.pxe.switch: pxeswitch1`
`d1: net.switch:`
* Setting attributes using a batch file with syntax similar to command line:
`# cat nodeattributes.batch`
`# power`
`power.psu1.outlet=3 power.psu1.pdu=pdu2`
`# nodeattrib n41 -s nodeattributes.batch`
`n41: 3`
`n41: pdu2`
* Setting attributes using a batch file with syntax where each attribute is in its own line:
`# cat nodeattributes.batch`
`# management`
`custom.mgt.switch=switch_main`
`custom.mgt.switch.port=swp4`
`# nodeattrib n41 -s nodeattributes.batch`
`n41: switch_main`
`n41: swp4`
## SEE ALSO
nodegroupattrib(8), nodeattribexpressions(5)
@@ -1,28 +0,0 @@
nodebmcpassword(8) -- Change management controller password for a specified user
=========================================================
## SYNOPSIS
`nodebmcpassword <noderange> <username> <new_password>`
## DESCRIPTION
`nodebmcpassword` allows you to change the management controller password for a user on a specified noderange
## OPTIONS
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Number of nodes to affect before prompting for
confirmation
* `-h`, `--help`:
Show help message and exit
## EXAMPLES:
* Reset the management controller for nodes n1 through n4:
`# nodebmcreset n1-n4`
`n1: Password Change Successful`
`n2: Password Change Successful`
`n3: Password Change Successful`
`n4: Password Change Successful`
+3 -61
View File
@@ -2,7 +2,7 @@ nodeconsole(8) -- Open a console to a confluent node
=====================================================
## SYNOPSIS
`nodeconsole [options] <noderange> [kill][-- [passthroughoptions]]`
`nodeconsole [options] <noderange>`
## DESCRIPTION
@@ -16,58 +16,11 @@ will initiate an automatic retry interval that is randomized between 2 and 4 min
The reopen escape sequence below requests an immediate retry, as does connecting
a new session.
When a windowed console is open the `nodeconsole <noderange> kill` command will kill the
console process which will result in the console window closing.
## OPTIONS
* `-i N`, `--interval`:
For screenshot mode, fetch new screenshots and overwrite old screenshots every N seconds.
For example, `nodeconsole r3u[21:24] -tsi 3` will tile screenshots of r3u21 through r3u24 and
refresh them every 3 seconds.
* `-t`, `--tile`:
For text consoles, use tmux to arrange consoles of the given noderange into a tiled layout on
the terminal screen. If using 'screenshot' mode, divide the terminal and display the images
in a grid.
* `-l`, `--log`:
Perform a log reply on the current, local log in /var/log/confluent/consoles.
If in collective mode, this only makes sense to use on the current collective
manager at this time.
* `-T`, `--Timestamp`:
Dump the log with Timpstamps on the current, local log in /var/log/confluent/consoles.
If in collective mode, this only makes sense to use on the current collective
manager at this time.
* `-s`, `--screenshot`:
Attempt to grab screenshot(s) and render using a terminal
image protocol. The image protocol defaults to kitty, and
can be selected by CONFLUENT_IMAGE_PROTOCOL environment variable.
Supported protocols are kitty, iterm, and, sixel (sixel only
if PySixel is installed). This only presents screenshots, there
is no input supported to graphical consoles from a terminal.
* `-w`, `--windowed`:
Open terminal windows for each node. The
environment variable **NODECONSOLE_WINDOWED_COMMAND**
should be set, which should be a text string corresponding
to a command that can be used to open a windowed console,
omitting the `nodeconsole <noderange>` part of the
command, for example, to open a set of consoles for a
range of nodes in separate xterm windows, set
**NODECONSOLE_WINDOWED_COMMAND** to `xterm -e`. To open a
set of consoles for a range of nodes in separate
GNOME Terminal windows with a size of 100 columns and
31 rows, set **NODECONSOLE_WINDOWED_COMMAND**
to `gnome-terminal --geometry 100x31 --` or in a WSL
environment, to open a set of consoles for a range of
nodes in separate Windows Terminal windows, with the
title set for each node, set **NODECONSOLE_WINDOWED_COMMAND**
to `wt.exe wsl.exe -d AlmaLinux-8 --shell-type login. If the
NODECONSOLE_WINDOWED_COMMAND environment variable isn't set,
xterm will be used bydefault.
Use tmux to arrange consoles of the given noderange into a tiled layout on
the terminal screen
## ESCAPE SEQUENCE COMMANDS
@@ -108,14 +61,3 @@ keystroke will be interpreted as a command. The following commands are availabl
Get a list of supported commands
* `<ent>`:
Hit enter to skip entering a command at the escape prompt.
## PASSTHROUGH OPTIONS
While opening a windowed console with xterm or any other console of choice. The
nodeconsole command gives capality to specify passthrough options targeted at
the console. All options after the -- will be parsed the console program. For
example, opening a windowed console using xterm with a black background.
`nodeconconsole -w n1 -- -bg black`
+3 -12
View File
@@ -5,12 +5,9 @@ nodediscover(8) -- List or manage confluent node discovery
`nodediscover rescan`
`nodediscover [options] list`
`nodediscover [options] assign`
`nodediscover [options] rescan`
`nodediscover [options] clear`
`nodediscover [options] subscribe [switch]`
`nodediscover [options] unsubscribe [switch]`
`nodediscover [options] register [Addresses, e.g. 192.168.1.2 or 192.168.1.0/24 or 192.168.1.1-192.168.1.28]`
`nodediscover [options] assign`
`nodediscover [options] rescan`
`nodediscover [options] clear`
## DESCRIPTION
@@ -38,12 +35,6 @@ scan is the best approach to catch something that appears to be missing.
**nodedsicover clear** requests the server forget about a selection of
detected device. It takes the same arguments as **nodediscover list**.
**nodediscover subscribe** and **unsubscribe** instructs confluent to subscribe to or
unsubscribe from the designated switch running affluent with system discovery support.
**nodediscover register** instructs confluent to perform a remote probe of an address, subnet,
or range of IP addresses.
## CSV FORMAT
The CSV format used by nodediscover consists of one header describing the
@@ -15,15 +15,6 @@ noderange.
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to clear if clearing log, prompting if
over the threshold
* `-l LINES`, `--lines=LINES`:
return the last <n> entries for each node in the eventlog.
* `-t TIMEFRAME`, `--timeframe=TIMEFRAME`:
return entries within a specified timeframe for each node's event log.
This will return entries from the last <n> hours or days. 1h would be
entries from with the last one hour.
* `-h`, `--help`:
Show help message and exit
+1 -4
View File
@@ -3,7 +3,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
## SYNOPSIS
`nodefirmware <noderange> [list][updatestatus][update [--backup <file>]]|[<components>]`
`nodefirmware <noderange> [list][update [--backup <file>]]|[<components>]`
## DESCRIPTION
@@ -17,9 +17,6 @@ not be relevant to redfish. Additionally, the Lenovo XCC makes certain
information available over IPMI that is not otherwise available (for example
the FPGA version where applicable).
The updatestatus argument will describe the state of firmware updates on the
nodes.
In the update form, it accepts a single file and attempts to update it using
the out of band facilities. Firmware updates can end in one of three states:
@@ -1,42 +0,0 @@
nodel2traceroute(8) -- returns the layer 2 route through an Ethernet network managed by confluent given 2 end points.
==============================
## SYNOPSIS
`nodel2traceroute [options] <start_node> <end_noderange>`
## DESCRIPTION
**nodel2traceroute** is a command that returns the layer 2 route for the configered interfaces in nodeattrib.
It can also be used with the -i and -e options to check against specific interfaces on the endpoints. If the
--interface or --eface option are not used then the command will check for routes against all the defined
interfaces in nodeattrib (net.*.switch) for the nodes.
## PREREQUISITES
**nodel2traceroute** the net.<interface>.switch attributes have to be set on the end points if endpoint is not a switch
## OPTIONS
* ` -e` EFACE, --eface=INTERFACE
interface to check against for the second end point or end points if using checking against multiple nodes
* ` -i` INTERFACE, --interface=INTERFACE
interface to check against for the first end point
* ` -c` CUMULUS, --cumulus=CUMULUS
return layer 2 route through cumulus switches only
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
* Checking route between two nodes:
`# nodel2traceroute n244 n1851`
`n244 to n1851: ['switch114']`
* Checking route from one node to multiple nodes:
`# nodel2traceroute n244 n1833,n1851`
`n244 to n1833: ['switch114', 'switch7', 'switch32', 'switch253', 'switch85', 'switch72', 'switch21', 'switch2', 'switch96', 'switch103', 'switch115']
n244 to n1851: ['switch114']`
+2 -3
View File
@@ -4,7 +4,7 @@ nodelist(8) -- List confluent nodes and their attributes
## SYNOPSIS
`nodelist <noderange>`
`nodelist <noderange> [-b] [-d] {string} <nodeattribute>...`
`nodelist <noderange> [-b] <nodeattribute>...`
## DESCRIPTION
@@ -24,8 +24,7 @@ all attributes that begin with `net.` and end with `switch`.
* `-b`, `--blame`:
Annotate inherited and expression based attributes to show their base value.
* `-d`, `--delim`:
Choose a delimiter to separat the values. Default - ENTER.
## EXAMPLES
* Listing matching nodes of a simple noderange:
`# nodelist n1-n4`
-47
View File
@@ -1,47 +0,0 @@
nodeping(8) -- Pings a node or a noderange.
==============================
## SYNOPSIS
`nodeping [options] noderange`
## DESCRIPTION
**nodeping** is a command that pings the default NIC on a node.
It can also be used with the `-s` flag to change the ping location to something that is 'non primary'
## OPTIONS
* ` -f` COUNT, `-c` COUNT, --count=COUNT
Number of commands to run at a time
* `-h`, `--help`:
Show help message and exit
* `-s` SUBSTITUTENAME, --substitutename=SUBSTITUTENAME
Use a different name other than the nodename for ping. This may be a
expression, such as {bmc} or, if no { character is present, it is treated as a suffix. -s -eth1 would make n1 become n1-eth1, for example.
## EXAMPLES
* Pinging a node :
`# nodeping <node>`
`node : ping`
* Pinging a group:
`# nodeping <groupname>`
`Node1 : ping
Node2 : ping
Node3 : ping`
* Pinging BMC on a node:
`# nodeping -s {bmc} <noderange>`
` Node-bmc : ping`
* Pinging by specifying a suffix:
`# nodeping d1-d4 -s -eth1`
`d2-eth1: no_ping`
`d1-eth1: no_ping`
`d3-eth1: no_ping`
`d4-eth1: no_ping`
* Fail to ping node:
`# nodeping <node>`
`node : no_ping`
-3
View File
@@ -24,9 +24,6 @@ respond.
* `reset`: Request immediate reset of nodes of the noderange. Nodes that are
off will not react to this request.
* `status`: Behave identically to having no argument passed at all.
* `pdu_status`: Query state of associated PDU outlets, if configured.
* `pdu_on`: Energize all PDU outlets associated with the noderange.
* `pdu_off`: De-energize all PDU outlets associated with the noderange.
## OPTIONS
-3
View File
@@ -16,9 +16,6 @@ noderange. This will present progress as percentage for all nodes.
Specify how many rsync executions to do concurrently. If noderange
exceeds the count, then excess nodes will wait until one of the
active count completes.
* `-s`, `--substitutename`:
'Use a different name other than the nodename for rsync'
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to run rsync to, prompting if over the
+1 -3
View File
@@ -19,9 +19,7 @@ interval of 1 second is used.
## OPTIONS
* `-c`, `--csv`:
Organize output into CSV format, one sensor per column. Note that while normally nodesensors reports
sensors in order as returned by server, CSV output enforces consistency by sorting after receiving
the results, which may have a different ordering than non-CSV usage of nodesensors.
Organize output into CSV format, one sensor per column.
* `-i`, `--interval`=**SECONDS**:
Repeat data gathering waiting, waiting the specified time between samples. Unless `-n` is
-3
View File
@@ -17,9 +17,6 @@ as stderr, unlike psh which combines all stdout and stderr into stdout.
* `-c COUNT`, `-f COUNT`, `--count=COUNT`
Specify the maximum number of instances to run concurrently
* `-l LOGINNAME`, `--loginname=LOGINNAME`
Username to use when connecting, defaults to current user.
* `-m MAXNODES`, `--maxnodes=MAXNODES`
Specify a maximum number of nodes to run remote ssh command to, prompting
if over the threshold
+2 -4
View File
@@ -5,7 +5,6 @@ osdeploy(8) --- Configure general OS deployment facilities of confluent
`osdeploy import <iso>`
`osdeploy updateboot <profile>`
`osdeploy rebase <profile>`
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l] [-a]`
## DESCRIPTION
@@ -14,6 +13,5 @@ osdeploy(8) --- Configure general OS deployment facilities of confluent
`import` subcommand will generate profiles from an iso image. `updateboot` will take any
changes in the specified update that need to be pushed into boot configuration and/or images
and ensure those needed changes are performed. `initialize` provides assistance in setting
up the most commonly required facilities. `rebase` has confluent attempt to update profile
content that came from /opt/confluent in an rpm update, if profile supports it.
Run `osdeploy initialize -h` for more detail on the options offered by `osdeploy initialize`.
up the most commonly required facilities. Run `osdeploy initialize -h` for more detail
on the options offered by `osdeploy initialize`
+3 -9
View File
@@ -1,9 +1,7 @@
from setuptools import setup
import os
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh']),
('/opt/confluent/share/licenses/confluent_client/', ['LICENSE', 'COPYRIGHT'])
]
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh'])]
try:
scriptlist = ['bin/{0}'.format(d) for d in os.listdir('bin/')]
data_files.append(('/opt/confluent/share/man/man1', ['man/man1/' + x for x in os.listdir('man/man1')]))
@@ -15,14 +13,10 @@ except OSError:
setup(
name='confluent_client',
version='#VERSION#',
author='Lenovo',
author='Jarrod Johnson',
author_email='jjohnson2@lenovo.com',
url='http://github.com/lenovo/confluent/',
license='Apache-2.0',
description='Command line client and libraries for confluent management server',
long_description='Command line client and libraries for confluent management server',
url='http://xcat.sf.net/',
packages=['confluent'],
platforms=['Linux'],
scripts=scriptlist,
data_files=data_files,
)
+4 -20
View File
@@ -1,38 +1,22 @@
VERSION=`git describe|cut -d- -f 1`
NUMCOMMITS=`git describe|cut -d- -f 2`
if [ "$NUMCOMMITS" != "$VERSION" ]; then
LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev)
LASTNUM=$((LASTNUM+1))
FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev)
VERSION=${FIRSTPART}.${LASTNUM}
VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3`
VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3`
fi
sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy.spec.tmpl > confluent_osdeploy.spec
cd ..
cp ../LICENSE .
tar Jcvf confluent_osdeploy.tar.xz confluent_osdeploy
mv confluent_osdeploy.tar.xz ~/rpmbuild/SOURCES/
cd -
mkdir -p el9bin/opt/confluent/bin
mkdir -p el9bin/stateless-bin
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t fedorabuild make -C /buildutils
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t fedorabuilder make -C /buildutils
cd utils
cp confluent_imginfo copernicus clortho autocons ../el9bin/opt/confluent/bin
cp start_root urlmount ../el9bin/stateless-bin/
make clean
cd ..
mkdir -p el8bin/opt/confluent/bin
mkdir -p el8bin/stateless-bin
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t el7build make -C /buildutils
cd utils
cp confluent_imginfo copernicus clortho autocons ../el8bin/opt/confluent/bin
cp start_root urlmount ../el8bin/stateless-bin/
mv confluent_imginfo copernicus clortho autocons ../el9bin/opt/confluent/bin
mv start_root urlmount ../el9bin/stateless-bin/
make clean
cd ..
tar Jcvf confluent_el9bin.tar.xz el9bin/
tar Jcvf confluent_el8bin.tar.xz el8bin/
mv confluent_el8bin.tar.xz ~/rpmbuild/SOURCES/
mv confluent_el9bin.tar.xz ~/rpmbuild/SOURCES/
rm -rf el9bin
rm -rf el8bin
rpmbuild -ba confluent_osdeploy.spec
-38
View File
@@ -1,38 +0,0 @@
cd $(dirname $0)
VERSION=`git describe|cut -d- -f 1`
NUMCOMMITS=`git describe|cut -d- -f 2`
if [ "$NUMCOMMITS" != "$VERSION" ]; then
LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev)
LASTNUM=$((LASTNUM+1))
FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev)
VERSION=${FIRSTPART}.${LASTNUM}
VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3`
fi
sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy-aarch64.spec.tmpl > confluent_osdeploy-aarch64.spec
cd ..
cp ../LICENSE .
tar Jcvf confluent_osdeploy.tar.xz confluent_osdeploy
mv confluent_osdeploy.tar.xz ~/rpmbuild/SOURCES/
cd -
mkdir -p el9bin/opt/confluent/bin
mkdir -p el9bin/stateless-bin
mkdir -p el8bin/opt/confluent/bin
mkdir -p el8bin/stateless-bin
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t el9builder make -C /buildutils
cd utils
mv confluent_imginfo copernicus clortho autocons ../el9bin/opt/confluent/bin
mv start_root urlmount ../el9bin/stateless-bin/
cd ..
podman run --privileged --rm -v $(pwd)/utils:/buildutils -i -t el8builder make -C /buildutils
cd utils
mv confluent_imginfo copernicus clortho autocons ../el8bin/opt/confluent/bin
mv start_root urlmount ../el8bin/stateless-bin/
cd ..
tar Jcvf confluent_el9bin.tar.xz el9bin/
tar Jcvf confluent_el8bin.tar.xz el8bin/
mv confluent_el8bin.tar.xz ~/rpmbuild/SOURCES/
mv confluent_el9bin.tar.xz ~/rpmbuild/SOURCES/
rm -rf el9bin
rm -rf el8bin
podman run --privileged --rm -v $HOME:/root el8builder rpmbuild -ba /root/confluent/confluent_osdeploy/confluent_osdeploy-aarch64.spec
@@ -3,7 +3,6 @@ try:
import http.client as client
except ImportError:
import httplib as client
import base64
import ctypes
import ctypes.util
import glob
@@ -15,31 +14,16 @@ import ssl
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
def msg_align(len):
return (len + 3) & ~3
try:
_protoparm = ssl.PROTOCOL_TLS_CLIENT
except AttributeError:
_protoparm = ssl.PROTOCOL_SSLv23
cryptname = ctypes.util.find_library('crypt')
if not cryptname:
if os.path.exists('/lib64/libcrypt.so.2') or os.path.exists('/usr/lib64/libcrypt.so.2'):
cryptname = 'libcrypt.so.2'
else:
if os.path.exists('/lib64/libcrypt.so.1'):
cryptname = 'libcrypt.so.1'
elif os.path.exists('/lib64/libcrypt.so.2'):
cryptname = 'libcrypt.so.2'
c_libcrypt = ctypes.CDLL(cryptname)
c_crypt = c_libcrypt.crypt
c_crypt.argtypes = (ctypes.c_char_p, ctypes.c_char_p)
@@ -75,12 +59,12 @@ def get_my_addresses():
break
if rtatyp == 1:
addrs.append((fam, rta[4:rtalen], plen, ridx))
rta = rta[msg_align(rtalen):]
v = v[msg_align(length):]
rta = rta[rtalen:]
v = v[length:]
return addrs
def scan_confluents(confuuid=None):
def scan_confluents():
srvs = {}
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
@@ -92,18 +76,14 @@ def scan_confluents(confuuid=None):
s4.bind(('0.0.0.0', 1900))
doneidxs = set([])
msg = 'M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:'
if not confuuid:
with open('/etc/confluent/confluent.deploycfg') as dcfg:
for line in dcfg.read().split('\n'):
if line.startswith('confluent_uuid:'):
confluentuuid = line.split(': ')[1]
msg += '/confluentuuid=' + confluentuuid
break
try:
with open('/sys/devices/virtual/dmi/id/product_uuid') as uuidin:
msg += '/uuid=' + uuidin.read().strip()
except Exception:
pass
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
with open('/sys/devices/virtual/dmi/id/product_uuid') as uuidin:
msg += '/uuid=' + uuidin.read().strip()
for addrf in glob.glob('/sys/class/net/*/address'):
with open(addrf) as addrin:
hwaddr = addrin.read().strip()
@@ -135,7 +115,6 @@ def scan_confluents(confuuid=None):
srvlist = []
if r:
r = r[0]
nodename = None
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
@@ -143,7 +122,6 @@ def scan_confluents(confuuid=None):
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
@@ -151,40 +129,21 @@ def scan_confluents(confuuid=None):
current['mgtiface'] = line.replace(b'MGTIFACE: ', b'').strip().decode('utf8')
if len(peer) > 2:
current['myidx'] = peer[-1]
currip = peer[0]
if currip.startswith('fe80::') and '%' not in currip:
currip = '{0}%{1}'.format(currip, peer[-1])
srvs[currip] = current
srvlist.append(currip)
srvs[peer[0]] = current
srvlist.append(peer[0])
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, hmackey=None, confuuid=None):
def get_net_apikey(nodename, mgr):
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])
@@ -194,7 +153,6 @@ def get_net_apikey(nodename, mgr, hmackey=None, confuuid=None):
else:
cliaddr = ('::', 302)
clisock.bind(cliaddr)
clisock.settimeout(1)
clisock.connect(addrinfo[-1])
rsp = clisock.recv(8)
if rsp != b'\xc2\xd1-\xa8\x80\xd8j\xba':
@@ -222,7 +180,7 @@ def get_net_apikey(nodename, mgr, hmackey=None, confuuid=None):
return ''
def get_apikey(nodename, hosts, errout=None, hmackey=None, confuuid=None):
def get_apikey(nodename, hosts):
apikey = ""
if os.path.exists('/etc/confluent/confluent.apikey'):
apikey = open('/etc/confluent/confluent.apikey').read().strip()
@@ -231,25 +189,23 @@ def get_apikey(nodename, hosts, errout=None, hmackey=None, confuuid=None):
while not apikey:
for host in hosts:
try:
apikey = get_net_apikey(nodename, host, hmackey=hmackey, confuuid=confuuid)
apikey = get_net_apikey(nodename, host)
except OSError:
apikey = None
if apikey:
break
else:
srvlist, _ = scan_confluents(confuuid=confuuid)
srvlist, _ = scan_confluents()
for host in srvlist:
try:
apikey = get_net_apikey(nodename, host, hmackey=hmackey, confuuid=confuuid)
apikey = get_net_apikey(nodename, host)
except OSError:
apikey = None
if apikey:
break
if not apikey:
errmsg = "Failed getting API token, check deployment.apiarmed attribute on {}\n".format(nodename)
sys.stderr.write(errmsg)
if errout:
errout.write(errmsg)
sys.stderr.write(
"Failed getting API token, check deployment.apiarmed attribute on {}\n".format(nodename))
time.sleep(10)
with open('/etc/confluent/confluent.apikey', 'w+') as apiout:
apiout.write(apikey)
@@ -258,66 +214,41 @@ def get_apikey(nodename, hosts, errout=None, hmackey=None, confuuid=None):
return apikey
class HTTPSClient(client.HTTPConnection, object):
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
def __init__(self, usejson=False, port=443, host=None):
self.stdheaders = {}
if nodename:
self.stdheaders['CONFLUENT_NODENAME'] = nodename
if errout:
self.errout = open(errout, 'w')
self.errout.flush()
mgtiface = None
if usejson:
self.stdheaders['ACCEPT'] = 'application/json'
if host:
self.hosts = [host]
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
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
else:
self.hosts = []
try:
info = open('/etc/confluent/confluent.info').read().split('\n')
except Exception:
info = []
info = open('/etc/confluent/confluent.info').read().split('\n')
havedefault = '0'
plainhost = ''
for line in info:
host = ''
if line.startswith('NODENAME:'):
nodename = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = nodename
node = line.split(' ')[1]
self.stdheaders['CONFLUENT_NODENAME'] = node
if line.startswith('MANAGER:') and not host:
host = line.split(' ')[1]
self.hosts.append(host)
if not plainhost:
plainhost = host
if line.startswith('EXTMGRINFO:'):
extinfo = line.split(' ')[1]
extinfo = extinfo.split('|')
if havedefault == '0' and extinfo[2] == '1':
host, mgtiface, havedefault = extinfo[:3]
if '%' in host:
ifidx = host.split('%', 1)[1]
with open('/tmp/confluent.ifidx', 'w+') as ifout:
ifout.write(ifidx)
if not mgtiface:
host, mgtiface, havedefault = extinfo[:3]
if host:
if havedefault == '0':
if '%' in host:
ifidx = host.split('%', 1)[1]
with open('/tmp/confluent.ifidx', 'w+') as ifout:
ifout.write(ifidx)
self.hosts.append(host)
if havedefault == '0' and extinfo[2] == '1':
host, mgtiface, havedefault = extinfo[:3]
if '%' in host:
ifidx = host.split('%', 1)[1]
with open('/tmp/confluent.ifidx', 'w+') as ifout:
ifout.write(ifidx)
self.hosts.append(host)
try:
info = open('/etc/confluent/confluent.deploycfg').read().split('\n')
except Exception:
@@ -326,17 +257,12 @@ class HTTPSClient(client.HTTPConnection, object):
for line in info:
if line.startswith('deploy_server: ') or line.startswith('deploy_server_v6: '):
self.hosts.append(line.split(': ', 1)[1])
if plainhost and not self.hosts:
self.hosts.append(plainhost)
if self.phmac:
self.stdheaders['CONFLUENT_CRYPTHMAC'] = self.phmac
elif not checkonly:
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(nodename, self.hosts, errout=self.errout, hmackey=hmackey, confuuid=self.confuuid)
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, self.hosts)
if mgtiface:
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
self.port = port
self.host = None
self.node = nodename
self.node = node
host = self.check_connections()
client.HTTPConnection.__init__(self, host, port)
self.connect()
@@ -348,35 +274,23 @@ class HTTPSClient(client.HTTPConnection, object):
def check_connections(self):
foundsrv = None
hosts = self.hosts
ctx = ssl.SSLContext(_protoparm)
ctx.load_verify_locations('/etc/confluent/ca.pem')
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.check_hostname = True
for timeo in (0.1, 5):
for host in hosts:
if host in self.ignorehosts:
continue
try:
addrinf = socket.getaddrinfo(host, self.port)[0]
psock = socket.socket(addrinf[0])
psock.settimeout(timeo)
psock.connect(addrinf[4])
chost = host.split('%', 1)[0]
ctx.wrap_socket(psock, server_hostname=chost)
foundsrv = host
psock.close()
break
except (OSError, socket.error):
continue
except ssl.SSLError:
continue
except ssl.CertificateError:
except OSError:
continue
else:
continue
break
if not foundsrv:
srvlist, srvs = scan_confluents(self.confuuid)
srvlist, srvs = scan_confluents()
hosts = []
for srv in srvlist:
if srvs[srv].get('isdefault', False):
@@ -398,12 +312,11 @@ class HTTPSClient(client.HTTPConnection, object):
raise Exception('Unable to reach any hosts')
return foundsrv
def connect(self, tries=3):
def connect(self):
addrinf = socket.getaddrinfo(self.host, self.port)[0]
psock = socket.socket(addrinf[0])
psock.settimeout(15)
psock.connect(addrinf[4])
ctx = ssl.SSLContext(_protoparm)
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ctx.load_verify_locations('/etc/confluent/ca.pem')
host = self.host.split('%', 1)[0]
if '[' not in host and ':' in host:
@@ -412,19 +325,7 @@ class HTTPSClient(client.HTTPConnection, object):
self.stdheaders['Host'] = '{0}'.format(host)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.check_hostname = True
try:
self.sock = ctx.wrap_socket(psock, server_hostname=host)
except ssl.SSLError:
errmsg = 'Error validating certificate on deployer (try `osdeploy initialize -t` on the deployment server {0})\n'.format(host)
sys.stderr.write(errmsg)
if self.errout:
self.errout.write(errmsg)
self.errout.flush()
sys.exit(1)
except socket.timeout:
if not tries:
raise
return self.connect(tries=tries-1)
self.sock = ctx.wrap_socket(psock, server_hostname=host)
def grab_url(self, url, data=None, returnrsp=False):
return self.grab_url_with_status(url, data, returnrsp)[1]
@@ -450,101 +351,11 @@ 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, 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)
host = self.check_connections()
client.HTTPConnection.__init__(self, host, self.port)
self.node, [self.host])
raise Exception(rsp.read())
def get_current_vmnic_vswitch():
uplinkmatch = re.compile(r'^\s*Uplinks:\s*(.*)')
switchmatch = re.compile(r'^\s*Name:\s*(.*)')
vswinfo = subprocess.check_output(['localcli', 'network', 'vswitch', 'standard', 'list']).decode()
vmnic = None
vswitch_name = None
for info in vswinfo.split('\n'):
name_match = switchmatch.match(info)
if name_match:
vswitch_name = name_match.group(1).strip()
upinfo = uplinkmatch.match(info)
if upinfo:
vmnic = upinfo.group(1).strip()
if vmnic and 'vusb0' not in vmnic:
return vswitch_name, vmnic
return vswitch_name, vmnic
def get_available_nics():
nicinfo = subprocess.check_output(['localcli', 'network', 'nic', 'list']).decode('utf8').split('\n')
available_nics = {}
# Skip headers and separators
parsing_started = False
for line in nicinfo:
if re.match(r'^-+', line):
parsing_started = True
continue
if not parsing_started or not line.strip():
continue
parts = re.split(r'\s{2,}', line.strip())
if len(parts) >= 5:
nic_name = parts[0]
nic_status = parts[4] # "Link Status" is the 5th field
available_nics[nic_name] = nic_status
return available_nics
def is_esxi():
return os.path.isdir("/etc/vmware")
def fix_vswitch():
if is_esxi():
start_time = time.time()
while True:
current_vswitch, current_vmnic = get_current_vmnic_vswitch()
if current_vswitch is None:
raise RuntimeError("Panic: current vswitch is None")
if current_vmnic is None:
raise RuntimeError("Panic: current vmnic is None")
available_nics = get_available_nics()
if current_vmnic and available_nics.get(current_vmnic) == 'Up':
break
new_vmnic = next((nic for nic, status in available_nics.items() if status == 'Up'), None)
if new_vmnic and new_vmnic != current_vmnic:
subprocess.check_call(['localcli', 'network', 'vswitch', 'standard', 'uplink', 'remove',
'--uplink-name', current_vmnic, '--vswitch-name', current_vswitch])
subprocess.check_call(['localcli', 'network', 'vswitch', 'standard', 'uplink', 'add',
'--uplink-name', new_vmnic, '--vswitch-name', current_vswitch])
elif not new_vmnic:
if time.time() - start_time > 300:
break
time.sleep(5)
time.sleep(5)
if __name__ == '__main__':
data = None
if '-f' in sys.argv:
try:
fix_vswitch()
except Exception as e:
print("fix_vswitch() error: {}".format(e))
sys.argv.remove('-f')
sys.exit(0)
usejson = False
if '-j' in sys.argv:
usejson = True
@@ -569,60 +380,22 @@ if __name__ == '__main__':
data = sys.argv.pop(data)
except ValueError:
data = None
try:
errout = sys.argv.index('-e')
sys.argv.pop(errout)
errout = sys.argv.pop(errout)
except ValueError:
errout = None
try:
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')
sys.argv.pop(idxit)
checkonly = True
except Exception:
pass
if len(sys.argv) > 2 and os.path.exists(sys.argv[-1]):
data = open(sys.argv[-1]).read()
if outbin:
with open(outbin, 'ab+') as outf:
reader = HTTPSClient(usejson=usejson, errout=errout, hmackey=hmackey, nodename=nodename, confuuid=confuuid).grab_url(
reader = HTTPSClient(usejson=usejson).grab_url(
sys.argv[1], data, returnrsp=True)
chunk = reader.read(16384)
while chunk:
outf.write(chunk)
chunk = reader.read(16384)
sys.exit(0)
mclient = HTTPSClient(usejson, errout=errout, phmac=phmac, checkonly=checkonly, hmackey=hmackey, nodename=nodename, confuuid=confuuid)
if len(sys.argv) > 2 and os.path.exists(sys.argv[-1]):
data = open(sys.argv[-1]).read()
if waitfor:
client = HTTPSClient(usejson)
status = 201
while status != waitfor:
status, rsp = mclient.grab_url_with_status(sys.argv[1], data)
status, rsp = client.grab_url_with_status(sys.argv[1], data)
sys.stdout.write(rsp.decode())
elif checkonly:
sys.stdout.write(mclient.check_connections())
else:
sys.stdout.write(mclient.grab_url(sys.argv[1], data).decode())
sys.stdout.write(HTTPSClient(usejson).grab_url(sys.argv[1], data).decode())
@@ -1,119 +0,0 @@
#!/usr/bin/python3
# This script evaluates whether firmware redirection is likely. It uses three cues:
# - Does the system offer up SPCR? This would indicate that the firmware is doing serial output.
# Otherwise, there's no indication that the firmware cares about serial console.
# - Is the system EFI? BIOS implementations may not intercept text draw calls after POST exit,
# thus even when BIOS tells us serial port is in use, it may not be doing anything when
# grub would be running
# - Is the serial port connected? In the event that firmware indicates serial port, but
# serial port is not reporting DCD, then it doesn't look like a comfortable enough scenario
import fcntl
import os
import os.path
import struct
import subprocess
import termios
addrtoname = {
0x3f8: '/dev/ttyS0',
0x2f8: '/dev/ttyS1',
0x3e8: '/dev/ttyS2',
0x2e8: '/dev/ttyS3',
}
speedmap = {
0: None,
3: 9600,
4: 19200,
6: 57600,
7: 115200,
}
termiobaud = {
9600: termios.B9600,
19200: termios.B19200,
57600: termios.B57600,
115200: termios.B115200,
}
def deserialize_grub_rh():
if 'console=ttyS' in open('/proc/cmdline').read():
return None # User manually indicated serial config
# they own the grub behavior too for now
grublines = []
with open('/etc/default/grub') as grubin:
grublines = grubin.read().split('\n')
with open('/etc/default/grub', 'w') as grubout:
for grubline in grublines:
if grubline.startswith('GRUB_TERMINAL'):
grubline = grubline.replace('serial ', '')
grubout.write(grubline + '\n')
subprocess.check_call(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
def fixup_ubuntu_grub_serial():
# Ubuntu aggressively tries to graphics up
# grub. We will counter that for serial
# They also aggressively hide UI and
# block ability to interject. We will
# compromise and lean on nodeboot <node> setup
# as a means to give someone reasonable shot at
# the short timeout
with open('/etc/default/grub') as grubin:
grublines = grubin.read().split('\n')
with open('/etc/default/grub', 'w') as grubout:
for grubline in grublines:
if grubline.startswith('GRUB_TIMEOUT_STYLE=hidden'):
grubline = 'GRUB_TIMEOUT_STYLE=menu'
elif grubline.startswith('GRUB_TIMEOUT=0'):
grubline = 'GRUB_TIMEOUT=2'
elif grubline.startswith('#GRUB_TERMINAL=console'):
grubline = grubline.replace('#', '')
grubout.write(grubline + '\n')
subprocess.check_call(['update-grub'])
def get_serial_config():
if not os.path.exists('/sys/firmware/efi'):
return None
if not os.path.exists('/sys/firmware/acpi/tables/SPCR'):
return None
spcr = open("/sys/firmware/acpi/tables/SPCR", "rb")
spcr = bytearray(spcr.read())
if spcr[8] != 2 or spcr[36] != 0 or spcr[40] != 1:
return None
address = struct.unpack('<Q', spcr[44:52])[0]
tty = None
try:
tty = addrtoname[address]
except KeyError:
return None
retval = { 'tty': tty }
try:
retval['speed'] = speedmap[spcr[58]]
except KeyError:
return None
if retval['speed']:
ttyf = os.open(tty, os.O_RDWR | os.O_NOCTTY)
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 main():
autoconscfg = get_serial_config()
if not autoconscfg or not autoconscfg['connected']:
return
if os.path.exists('/etc/redhat-release'): # redhat family
deserialize_grub_rh()
elif os.path.exists('/etc/os-release'):
with open('/etc/os-release') as osr:
if 'Ubuntu' in osr.read():
fixup_ubuntu_grub_serial()
if __name__ == '__main__':
main()
@@ -8,10 +8,6 @@ import sys
import time
import shlex
import subprocess
try:
import yaml
except ImportError:
pass
try:
from importlib.machinery import SourceFileLoader
def load_source(mod, path):
@@ -29,8 +25,6 @@ def add_lla(iface, mac):
initbyte = int(pieces[0], 16) ^ 2
lla = 'fe80::{0:x}{1}:{2}ff:fe{3}:{4}{5}/64'.format(initbyte, pieces[1], pieces[2], pieces[3], pieces[4], pieces[5])
try:
with open('/proc/sys/net/ipv6/conf/{0}/disable_ipv6'.format(iface), 'w') as setin:
setin.write('0')
subprocess.check_call(['ip', 'addr', 'add', 'dev', iface, lla, 'scope', 'link'])
except Exception:
return None
@@ -86,7 +80,7 @@ def map_idx_to_name():
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'
devtype[prevdev] = typ if type != 'ether' else 'ethernet'
if line.startswith(' '):
continue
idx, iface, rst = line.split(':', 2)
@@ -111,109 +105,6 @@ def get_interface_name(iname, settings):
return iname
return None
class NetplanManager(object):
def __init__(self, deploycfg):
self.cfgbydev = {}
self.read_connections()
self.deploycfg = deploycfg
def read_connections(self):
for plan in glob.glob('/etc/netplan/*.y*ml'):
with open(plan) as planfile:
planinfo = yaml.safe_load(planfile)
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]
def apply_configuration(self, cfg):
devnames = cfg['interfaces']
if len(devnames) != 1:
raise Exception('Multi-nic team/bonds not yet supported')
stgs = cfg['settings']
needcfgapply = False
for devname in devnames:
needcfgwrite = False
# ipv6_method missing at uconn...
if stgs.get('ipv6_method', None) == 'static':
curraddr = stgs['ipv6_address']
currips = self.getcfgarrpath([devname, 'addresses'])
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('ipv4_method', None) == 'static':
curraddr = stgs['ipv4_address']
currips = self.getcfgarrpath([devname, 'addresses'])
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('mtu', None):
devdict = self.getcfgarrpath([devname])
devdict['mtu'] = int(stgs['mtu'])
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
for gwaddr in gws:
if gwaddr:
cfgroutes = self.getcfgarrpath([devname, 'routes'])
for rinfo in cfgroutes:
if rinfo.get('via', None) == gwaddr:
break
else:
needcfgwrite = True
cfgroutes.append({'via': gwaddr, 'to': 'default'})
dnsips = self.deploycfg.get('nameservers', [])
dnsdomain = self.deploycfg.get('dnsdomain', '')
if dnsips:
currdnsips = self.getcfgarrpath([devname, 'nameservers', 'addresses'])
for dnsip in dnsips:
if dnsip and dnsip not in currdnsips:
needcfgwrite = True
currdnsips.append(dnsip)
if dnsdomain:
currdnsdomain = self.getcfgarrpath([devname, 'nameservers', 'search'])
if dnsdomain not in currdnsdomain:
needcfgwrite = True
currdnsdomain.append(dnsdomain)
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:
planout.write(yaml.dump(newcfg))
os.umask(oumask)
if needcfgapply:
subprocess.call(['netplan', 'apply'])
def getcfgarrpath(self, devpath):
currptr = self.cfgbydev
for k in devpath[:-1]:
if k not in currptr:
currptr[k] = {}
currptr = currptr[k]
if devpath[-1] not in currptr:
currptr[devpath[-1]] = []
return currptr[devpath[-1]]
class WickedManager(object):
def __init__(self):
self.teamidx = 0
@@ -229,11 +120,7 @@ class WickedManager(object):
self.cfgbydev[devname] = currcfg
for cfg in open(ifcfg).read().splitlines():
cfg = cfg.split('#', 1)[0]
try:
kv = ' '.join(shlex.split(cfg)).split('=', 1)
except Exception:
# unparseable line, likely having something we can't handle
del self.cfgbydev[devname]
kv = ' '.join(shlex.split(cfg)).split('=', 1)
if len(kv) != 2:
continue
k, v = kv
@@ -267,14 +154,12 @@ class WickedManager(object):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
return
if not stgs.get('connection_name', None):
stgs['connection_name'] = 'bond{0}'.format(self.teamidx)
stgs['connection_name'] = 'team{0}'.format(self.teamidx)
self.teamidx += 1
cname = stgs['connection_name']
with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as teamout:
teamout.write(ipcfg)
if stgs['team_mode'] == 'lacp':
stgs['team_mode'] = '802.3ad'
teamout.write("BONDING_MODULE_OPTS='mode={0} miimon=100'\nBONDING_MASTER=yes\n".format(stgs['team_mode']))
teamout.write('TEAM_RUNNER={0}\n'.format(stgs['team_mode']))
idx = 1
for iface in cfg['interfaces']:
subprocess.call(['wicked', 'ifdown', iface])
@@ -283,7 +168,7 @@ class WickedManager(object):
os.remove('/etc/sysconfig/network/ifroute-{0}'.format(iface))
except OSError:
pass
teamout.write('BONDING_SLAVE{0}={1}\n'.format(idx, iface))
teamout.write('TEAM_PORT_DEVICE_{0}={1}\n'.format(idx, iface))
idx += 1
else:
cname = list(cfg['interfaces'])[0]
@@ -304,14 +189,7 @@ class WickedManager(object):
class NetworkManager(object):
bondtypes = {
'lacp': '802.3ad',
'loadbalance': 'balance-alb',
'roundrobin': 'balance-rr',
'activebackup': 'active-backup',
}
def __init__(self, devtypes, deploycfg):
self.deploycfg = deploycfg
def __init__(self, devtypes):
self.connections = {}
self.uuidbyname = {}
self.uuidbydev = {}
@@ -361,8 +239,7 @@ class NetworkManager(object):
bondcfg[stg] = deats[stg]
if member in self.uuidbyname:
subprocess.check_call(['nmcli', 'c', 'del', self.uuidbyname[member]])
devtype = self.devtypes.get(member, 'bond-slave')
subprocess.check_call(['nmcli', 'c', 'add', 'type', devtype, 'master', team, 'con-name', member, 'connection.interface-name', member])
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'team-slave', 'master', team, 'con-name', member, 'connection.interface-name', member])
if bondcfg:
args = []
for parm in bondcfg:
@@ -370,9 +247,8 @@ class NetworkManager(object):
args.append(bondcfg[parm])
subprocess.check_call(['nmcli', 'c', 'm', team] + args)
def apply_configuration(self, cfg, lastchance=False):
def apply_configuration(self, cfg):
cmdargs = {}
cmdargs['connection.autoconnect'] = 'yes'
stgs = cfg['settings']
cmdargs['ipv6.method'] = stgs.get('ipv6_method', 'link-local')
if stgs.get('ipv6_address', None):
@@ -384,22 +260,6 @@ class NetworkManager(object):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if stgs.get('mtu', None):
cmdargs['802-3-ethernet.mtu'] = stgs['mtu']
dnsips = self.deploycfg.get('nameservers', [])
if not dnsips:
dnsips = []
dns4 = []
dns6 = []
for dnsip in dnsips:
if '.' in dnsip:
dns4.append(dnsip)
elif ':' in dnsip:
dns6.append(dnsip)
if dns4:
cmdargs['ipv4.dns'] = ','.join(dns4)
if dns6:
cmdargs['ipv6.dns'] = ','.join(dns6)
if len(cfg['interfaces']) > 1: # team time.. should be..
if not cfg['settings'].get('team_mode', None):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
@@ -411,58 +271,31 @@ class NetworkManager(object):
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append('{}'.format(cmdargs[arg]))
if stgs['team_mode'] in self.bondtypes:
stgs['team_mode'] = self.bondtypes[stgs['team_mode']]
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'miimon=100,mode={}'.format(stgs['team_mode'])] + cargs)
cargs.append(cmdargs[arg])
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'team', 'con-name', cname, 'connection.interface-name', cname, 'team.runner', stgs['team_mode']] + cargs)
for iface in cfg['interfaces']:
self.add_team_member(cname, iface)
subprocess.check_call(['nmcli', 'c', 'u', cname])
else:
cname = stgs.get('connection_name', None)
iname = list(cfg['interfaces'])[0]
ctype = self.devtypes.get(iname, None)
if not ctype:
if lastchance:
sys.stderr.write("Warning, no device found for interface_name ({0}), skipping setup\n".format(iname))
return 1
if stgs.get('vlan_id', None):
vlan = stgs['vlan_id']
if ctype == 'infiniband':
vlan = '0x{0}'.format(vlan) if not vlan.startswith('0x') else vlan
cmdargs['infiniband.parent'] = iname
cmdargs['infiniband.p-key'] = vlan
iname = '{0}.{1}'.format(iname, vlan[2:])
elif ctype == 'ethernet':
ctype = 'vlan'
cmdargs['vlan.parent'] = iname
cmdargs['vlan.id'] = vlan
iname = '{0}.{1}'.format(iname, vlan)
else:
sys.stderr.write("Warning, unknown interface_name ({0}) device type ({1}) for VLAN/PKEY, skipping setup\n".format(iname, ctype))
return
cname = iname if not cname else cname
if not cname:
cname = iname
u = self.uuidbyname.get(cname, None)
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append('{}'.format(cmdargs[arg]))
cargs.append(cmdargs[arg])
if u:
subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs)
cmdargs['connection.interface-name'] = iname
subprocess.check_call(['nmcli', 'c', 'm', u] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
else:
subprocess.check_call(['nmcli', 'c', 'add', 'type', ctype, 'con-name', cname, 'connection.interface-name', iname] + cargs)
self.read_connections()
u = self.uuidbyname.get(cname, None)
if u:
subprocess.check_call(['nmcli', 'c', 'u', u])
subprocess.check_call(['nmcli', 'c', 'add', 'type', self.devtypes[iname], 'con-name', cname, 'connection.interface-name', iname] + cargs)
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:
@@ -474,18 +307,8 @@ if __name__ == '__main__':
myaddrs = apiclient.get_my_addresses()
srvs, _ = apiclient.scan_confluents()
doneidxs = set([])
dc = None
if not srvs: # the multicast scan failed, fallback to deploycfg cfg file
with open('/etc/confluent/confluent.deploycfg', 'r') as dci:
for cfgline in dci.read().split('\n'):
if cfgline.startswith('deploy_server:'):
srvs = [cfgline.split()[1]]
break
for srv in srvs:
try:
s = socket.create_connection((srv, 443))
except socket.error:
continue
s = socket.create_connection((srv, 443))
myname = s.getsockname()
s.close()
if len(myname) == 4:
@@ -498,99 +321,31 @@ if __name__ == '__main__':
curridx = addr[-1]
if curridx in doneidxs:
continue
for tries in (1, 2, 3):
try:
status, nc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/netcfg')
break
except Exception:
if tries == 3:
raise
time.sleep(1)
continue
status, nc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/netcfg')
nc = json.loads(nc)
if not dc:
for tries in (1, 2, 3):
try:
status, dc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/deploycfg2')
break
except Exception:
if tries == 3:
raise
time.sleep(1)
continue
dc = json.loads(dc)
iname = get_interface_name(idxmap[curridx], nc.get('default', {}))
if iname:
for iname in iname.split(','):
if 'default' in netname_to_interfaces:
netname_to_interfaces['default']['interfaces'].add(iname)
else:
netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']}
if 'default' in netname_to_interfaces:
netname_to_interfaces['default']['interfaces'].add(iname)
else:
netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']}
for netname in nc.get('extranets', {}):
uname = '_' + netname
iname = get_interface_name(idxmap[curridx], nc['extranets'][netname])
if iname:
for iname in iname.split(','):
if uname in netname_to_interfaces:
netname_to_interfaces[uname]['interfaces'].add(iname)
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
if uname in netname_to_interfaces:
netname_to_interfaces[uname]['interfaces'].add(iname)
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
doneidxs.add(curridx)
if 'default' in netname_to_interfaces:
for netn in netname_to_interfaces:
if netn == 'default':
continue
netname_to_interfaces['default']['interfaces'] -= netname_to_interfaces[netn]['interfaces']
if not netname_to_interfaces['default']['interfaces']:
del netname_to_interfaces['default']
# Make sure VLAN/PKEY connections are created last
netname_to_interfaces = dict(sorted(netname_to_interfaces.items(), key=lambda item: 'vlan_id' in item[1]['settings']))
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
elif os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes, dc)
if os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
retrynics = []
for netn in netname_to_interfaces:
redo = nm.apply_configuration(netname_to_interfaces[netn])
if redo == 1:
retrynics.append(netn)
if retrynics:
idxmap, devtypes = map_idx_to_name()
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
if os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes, dc)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
for netn in retrynics:
nm.apply_configuration(netname_to_interfaces[netn], lastchance=True)
nm.apply_configuration(netname_to_interfaces[netn])
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)
@@ -1,58 +0,0 @@
[ -f /lib/confluent/functions ] && . /lib/confluent/functions
[ -f /etc/confluent/functions ] && . /etc/confluent/functions
[ -f /opt/confluent/bin/apiclient ] && confapiclient=/opt/confluent/bin/apiclient
[ -f /etc/confluent/apiclient ] && confapiclient=/etc/confluent/apiclient
for pubkey in /etc/ssh/ssh_host*key.pub; do
if [ "$pubkey" = /etc/ssh/ssh_host_key.pub ]; then
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
for cert in /etc/ssh/ssh*-cert.pub; do
echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf
done
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/90-confluent.conf
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/90-confluent.conf
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/90-confluent.conf
elif [ ! -d /etc/ssh/sshd_config.d/ ] && ! grep HostCertificate /etc/ssh/sshd_config > /dev/null; then
for cert in /etc/ssh/ssh*-cert.pub; do
echo HostCertificate $cert >> /etc/ssh/sshd_config
done
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config
echo IgnoreRhosts no >> /etc/ssh/sshd_config
fi
TMPDIR=$(mktemp -d)
cd $TMPDIR
confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs.tgz
tar xf initramfs.tgz
for ca in ssh/*.ca; do
LINE=$(cat $ca)
if [ -z "$LINE" ]; then continue; fi
if [ -f /etc/ssh/ssh_known_hosts ]; then
cp -af /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts.new
grep -v "$LINE" /etc/ssh/ssh_known_hosts > /etc/ssh/ssh_known_hosts.new
fi
echo '@cert-authority *' $LINE >> /etc/ssh/ssh_known_hosts.new
mv /etc/ssh/ssh_known_hosts.new /etc/ssh/ssh_known_hosts
done
mkdir -p /root/.ssh/
chmod 700 /root/.ssh/
touch /root/.ssh/authorized_keys
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
if [ -z "$LINE" ]; then continue; fi
cp -af /root/.ssh/authorized_keys /root/.ssh/authorized_keys.new
grep -v "$LINE" /root/.ssh/authorized_keys > /root/.ssh/authorized_keys.new
echo "$LINE" >> /root/.ssh/authorized_keys.new
mv /root/.ssh/authorized_keys.new /root/.ssh/authorized_keys
done
confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' > /etc/ssh/shosts.equiv
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
systemctl try-restart sshd
@@ -1,101 +0,0 @@
Name: confluent_osdeploy-aarch64
Version: #VERSION#
Release: 1
Summary: OS Deployment support for confluent
License: Apache2
URL: https://hpc.lenovo.com/
Source0: confluent_osdeploy.tar.xz
Source1: confluent_el9bin.tar.xz
Source2: confluent_el8bin.tar.xz
BuildArch: noarch
Requires: confluent_ipxe mtools tar
BuildRoot: /tmp
%description
This contains support utilities for enabling deployment of aarch64 architecture systems
%define debug_package %{nil}
%prep
%setup -n confluent_osdeploy -a 2 -a 1
%build
mkdir -p opt/confluent/bin
mkdir -p stateless-bin
cp -a el8bin/* .
ln -s el8 el9
ln -s el8 el10
mv el10/initramfs/usr el10/initramfs/var
cp -a debian debian13
mkdir -p debian13/initramfs/usr
mv debian13/initramfs/lib debian13/initramfs/usr/
mv el10/initramfs/usr el10/initramfs/var
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
elif [ $os = el10 ]; then
cp -a ../el9bin/opt .
else
cp -a ../opt .
fi
cp -a ../${os}/initramfs/* .
cp -a ../common/initramfs/* .
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
cd ..
done
for os in el7 el8 suse15 el9 el10 ubuntu20.04; do
mkdir ${os}disklessout
cd ${os}disklessout
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
elif [ $os = el10 ]; then
cp -a ../el9bin/opt .
else
cp -a ../opt .
fi
cp -a ../${os}-diskless/initramfs/* .
cp -a ../common/initramfs/* .
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/stateless-bin/* opt/confluent/bin
else
cp -a ../stateless-bin/* opt/confluent/bin
fi
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
cd ..
done
mkdir esxi7out
cd esxi7out
cp -a ../opt .
cp -a ../esxi7/initramfs/* .
cp -a ../common/initramfs/* .
chmod +x bin/* opt/confluent/bin/*
tar zcvf ../addons.tgz *
mv ../addons.tgz .
cd ..
cp -a esxi7out esxi6out
cp -a esxi7 esxi6
cp -a esxi7out esxi8out
cp -a esxi7 esxi8
%install
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
#cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
for os in rhvh4 el7 el8 el9 el10 debian debian13 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
if [ -d ${os}disklessout ]; then
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs/aarch64/
cp ${os}disklessout/addons.* %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs/aarch64/
fi
done
find %{buildroot}/opt/confluent/lib/osdeploy/ -name .gitignore -exec rm -f {} +
%files
/opt/confluent/lib/osdeploy
#%license /opt/confluent/share/licenses/confluent_osdeploy/LICENSE
+16 -30
View File
@@ -7,7 +7,6 @@ License: Apache2
URL: https://hpc.lenovo.com/
Source0: confluent_osdeploy.tar.xz
Source1: confluent_el9bin.tar.xz
Source2: confluent_el8bin.tar.xz
BuildArch: noarch
Requires: confluent_ipxe mtools tar
BuildRoot: /tmp
@@ -19,29 +18,25 @@ This contains support utilities for enabling deployment of x86_64 architecture s
%define debug_package %{nil}
%prep
%setup -n confluent_osdeploy -a 2 -a 1
%setup -n confluent_osdeploy -a 1
%build
#cd utils
#make all
#cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
#cp start_root urlmount ../stateless-bin/
#cd ..
mkdir -p opt/confluent/bin
mkdir -p stateless-bin
cd utils
make all
cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
cp start_root urlmount ../stateless-bin/
cd ..
ln -s el8 el9
cp -a el8 el10
cp -a debian debian13
mkdir -p debian13/initramfs/usr
mv debian13/initramfs/lib debian13/initramfs/usr/
mv el10/initramfs/usr el10/initramfs/var
for os in rhvh4 el7 genesis el8 suse15 debian debian13 ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do
ln -s el8-diskless el9-diskless
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 coreos el9; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
elif [ $os = el10 ]; then
cp -a ../el9bin/opt .
else
cp -a ../el8bin/opt .
cp -a ../opt .
fi
cp -a ../${os}/initramfs/* .
cp -a ../common/initramfs/* .
@@ -49,22 +44,20 @@ 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 el8 suse15 el9 ubuntu20.04; do
mkdir ${os}disklessout
cd ${os}disklessout
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/opt .
elif [ $os = el10 ]; then
cp -a ../el9bin/opt .
else
cp -a ../el8bin/opt .
cp -a ../opt .
fi
cp -a ../${os}-diskless/initramfs/* .
cp -a ../common/initramfs/* .
if [ -d ../${os}bin ]; then
cp -a ../${os}bin/stateless-bin/* opt/confluent/bin
else
cp -a ../el8bin/stateless-bin/* opt/confluent/bin
cp -a ../stateless-bin/* opt/confluent/bin
fi
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
@@ -72,7 +65,7 @@ for os in el7 el8 suse15 el9 el10 ubuntu20.04 ubuntu22.04 ubuntu24.04; do
done
mkdir esxi7out
cd esxi7out
cp -a ../el8bin/opt .
cp -a ../opt .
cp -a ../esxi7/initramfs/* .
cp -a ../common/initramfs/* .
chmod +x bin/* opt/confluent/bin/*
@@ -81,15 +74,9 @@ mv ../addons.tgz .
cd ..
cp -a esxi7out esxi6out
cp -a esxi7 esxi6
cp -a esxi7out esxi8out
cp -a esxi7out esxi9out
cp -a esxi7 esxi8
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 genesis suse15 ubuntu20.04 esxi6 esxi7 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
@@ -111,4 +98,3 @@ find %{buildroot}/opt/confluent/lib/osdeploy/ -name .gitignore -exec rm -f {} +
%files
/opt/confluent/lib/osdeploy
%license /opt/confluent/share/licenses/confluent_osdeploy/LICENSE
@@ -5,55 +5,6 @@ umask 0077
mkdir -p /etc/confluent
echo -n > /etc/confluent/confluent.info
umask $oum
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=$(mktemp -d)
tcfg=$(mktemp)
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/, /^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
nodename=$(grep ^nodename: cnflnt.yml|awk '{print $2}')
sed -n '/^net_cfgs:/, /^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/, /^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
/usr/libexec/nm-initrd-generator ip=:dhcp
else
v4addr=$(grep ^ipv4_address: $tcfg)
v4addr=${v4addr#ipv4_address: }
v4addr=${v4addr%/*}
v4gw=$(grep ^ipv4_gateway: $tcfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
/usr/libexec/nm-initrd-generator ip=$v4addr::$v4gw:$v4nm:$hostname::none
fi
NetworkManager --configure-and-quit=initrd --no-daemon
hmackeyfile=$(mktemp)
echo -n $(grep ^apitoken: cnflnt.yml|awk '{print $2}') > $hmackeyfile
cd -
umount $tmnt
passfile=$(mktemp)
passcrypt=$(mktemp)
hmacfile=$(mktemp)
ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac
/opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
for deployer in $deploysrvs; do
if curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" -d@$passcrypt -k https://$deployer/confluent-api/self/registerapikey; then
cp $passfile /etc/confluent/confluent.apikey
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$deployer/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$deployer/confluent-api/self/profileprivate/pending/config.ign > /config.ign
[ -s /config.ign ] || rm /config.ign
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg)
confluent_profile=${confluent_profile#profile: }
curl -sf https://$deployer/confluent-public/os/$confluent_profile/rootfs.img | rdcore stream-hash /etc/coreos-live-want-rootfs | bsdtar -xf - -C /
exit 0
fi
done
fi
cd /sys/class/net
echo "Searching for confluent deployment server...." > /dev/console
while ! grep ^NODE /etc/confluent/confluent.info; do
@@ -67,7 +18,6 @@ while ! grep ^NODE /etc/confluent/confluent.info; do
if ! grep ^NODE /etc/confluent/confluent.info; then
echo 'Current net config:' > /dev/console
ip -br a > /dev/console
exit 1
fi
done
echo "Found confluent deployment services on local network" > /dev/console
@@ -115,7 +65,7 @@ curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_api
[ -s /config.ign ] || rm /config.ign
umask $oldumask
autoconfigmethod=$(grep ^ipv4_method: /etc/confluent/confluent.deploycfg)
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg)
confluent_profile=${confluent_profile#profile: }
@@ -1,209 +0,0 @@
reverse_uuid() {
echo $1 | head -c 8 | tail -c 2
echo $1 | head -c 6 | tail -c 2
echo $1 | head -c 4 | tail -c 2
echo $1 | head -c 2 | tail -c 2
echo $1 | head -c 13 | tail -c 2
echo $1 | head -c 11 | tail -c 2
echo $1 | head -c 18 | tail -c 2
echo $1 | head -c 16 | tail -c 2
echo $1 | tail -c 18 | sed -e s/-//
}
setdebopt() {
debconf-set $1 $2
echo d-i $1 $3 $2 >> /preseed.cfg
}
mkdir -p /etc/confluent
for i in /sys/class/net/*; do
ip link set $(basename $i) up
done
TRIES=5
while [ ! -e /dev/disk ] && [ $TRIES -gt 0 ]; do
sleep 2
TRIES=$((TRIES - 1))
done
for i in /sys/class/net/*; do
ip link set $(basename $i) down
udevadm info $i | grep ID_NET_DRIVER=cdc_ether > /dev/null && continue
ip link set $(basename $i) up
done
cp -a /tls/* /etc/ssl/certs/
mkdir -p /etc/confluent
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=$(mktemp -d)
tcfg=$(mktemp)
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/,/^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
nodename=$(grep ^nodename: cnflnt.yml|cut -f 2 -d ' ')
echo NODENAME: $nodename > /etc/confluent/confluent.info
sed -n '/^net_cfgs:/,/^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/,/^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "static" ]; then
setdebopt netcfg/disable_dhcp true boolean
v4addr=$(grep ^ipv4_address: $tcfg|cut -d: -f 2|sed -e 's/ //')
v4gw=$(grep ^ipv4_gateway: $tcfg|cut -d: -f 2| sed -e 's/ //')
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^ipv4_netmask: $tcfg|cut -d: -f 2|sed -e 's/ //')
setdebopt netcfg/get_netmask $v4nm string
setdebopt netcfg/get_ipaddress ${v4addr%/*} string
setdebopt netcfg/confirm_static true boolean
if [ ! -z "$v4gw" ]; then
setdebopt netcfg/get_gateway $v4gw string
fi
NIC=""
while [ -z "$NIC" ]; do
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do
ip addr add dev $NICGUESS $v4addr
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
for dsrv in $deploysrvs; do
if wget https://$dsrv/confluent-public/ --tries=1 --timeout=1 -O /dev/null > /dev/null 2>&1; then
deploysrvs=$dsrv
NIC=$NICGUESS
setdebopt netcfg/choose_interface $NIC select
break
fi
done
if [ -z "$NIC" ]; then
ip -4 a flush dev $NICGUESS
else
break
fi
done
done
#TODO: nameservers
elif [ "$v4cfgmeth" = "dhcp" ]; then
setdebopt netcfg/disable_dhcp false boolean
setdebopt netcfg/confirm_static false boolean
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do
udhcpc $NICGUESS
done
for dsrv in $deploysrvs; do
if wget https://$dsrv/confluent-public/ --tries=1 --timeout=1 -O /dev/null > /dev/null 2>&1; then
deploysrvs=$dsrv
fi
done
fi
mgr=$deploysrvs
ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac
hmackeyfile=/tmp/cnflnthmackeytmp
passfile=/tmp/cnflnttmppassfile
passcrypt=/tmp/cnflntcryptfile
hmacfile=/tmp/cnflnthmacfile
echo -n $(grep ^apitoken: cnflnt.yml|cut -d ' ' -f 2) > $hmackeyfile
/opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" --post-file=$passcrypt https://$mgr/confluent-api/self/registerapikey -O - --quiet
cp $passfile /etc/confluent/confluent.apikey
nic=$NIC
else
dhuuid=$(reverse_uuid $(cat /sys/devices/virtual/dmi/id/product_uuid))
dhcpid=$(mktemp)
mkdir -p /etc/confluent
cp /tls/* /etc/ssl/certs/
cat /tls/*.pem >> /etc/confluent/ca.pem
for nic in $(ip link | grep mtu|grep -v LOOPBACK|cut -d: -f 2|sed -e 's/ //'); do
ip link set $nic up
done
for nic in $(ip link | grep mtu|grep -v LOOPBACK|grep LOWER_UP|cut -d: -f 2|sed -e 's/ //'); do
if udhcpc -i $nic -p $dhcpid -t 2 -T 2 -n -x 93:0007 -x 97:00$dhuuid -q; then
/opt/confluent/bin/copernicus > /etc/confluent/confluent.info
if grep ^MANAGER:.*\\. /etc/confluent/confluent.info ; then
break
fi
fi
ip -4 flush dev $nic
done
mgr=$(grep ^MANAGER:.*\\. /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //')
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|head -n 1|cut -d: -f 2|sed -e 's/ //')
/opt/confluent/bin/clortho $nodename $mgr > /etc/confluent/confluent.apikey
fi
apikey=$(cat /etc/confluent/confluent.apikey)
cd /etc/confluent
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/deploycfg
cd -
predir=$(mktemp -d)
cd $predir
cp /etc/confluent/deploycfg /etc/confluent/confluent.deploycfg
profile=$(grep ^profile: /etc/confluent/deploycfg|cut -d ' ' -f 2)
namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //')
for namesrv in "$namesrvs"; do
setdebopt netcfg/get_nameservers $namesrv string
done
rootpass=$(grep ^rootpassword: /etc/confluent/deploycfg|cut -d ' ' -f 2|sed -e 's/ //')
if [ "$rootpass" = null ] || [ -z "$rootpass" ]; then
setdebopt passwd/root-login false boolean
else
setdebopt passwd/root-login true boolean
setdebopt passwd/root-password-crypted $rootpass string
fi
setdebopt time/zone $(grep ^timezone: /etc/confluent/deploycfg|cut -d ' ' -f 2|sed -e 's/ //') string
ntpsrvs=$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //')
for ntpsrv in "$ntpsrvs"; do
setdebopt clock-setup/ntp true boolean
setdebopt clock-setup/ntp-server $ntpsrv string
done
setdebopt debian-installer/locale $(grep ^locale: /etc/confluent/deploycfg|cut -d ' ' -f 2) select
domainname=$(grep ^dnsdomain: /etc/confluent/deploycfg|cut -d ' ' -f 2)
if [ ! -z "$domainname" ] && [ "$domainname" != "null" ]; then
setdebopt netcfg/get_domain $domainname string
fi
wget https://$mgr/confluent-public/os/$profile/scripts/pre.sh
chmod u+x pre.sh
wget https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh
chmod u+x prechroot.sh
wget https://$mgr/confluent-public/os/$profile/scripts/post.sh
chmod u+x post.sh
wget https://$mgr/confluent-public/os/$profile/preseed.cfg
cat preseed.cfg >> /preseed.cfg
echo $mgr > /etc/confluent/deployer
setdebopt auto-install/enable true boolean
setdebopt partman/early_command $predir/pre.sh string
setdebopt preseed/late_command $predir/prechroot.sh string
mv $predir/post.sh /tmp/
cd -
ip -4 a flush dev $nic
setdebopt netcfg/choose_interface $nic select
setdebopt netcfg/get_hostname $nodename string
setdebopt netcfg/hostname $nodename string
setdebopt mirror/protocol https string
setdebopt mirror/country manual string
setdebopt mirror/https/hostname deb.debian.org string
setdebopt mirror/https/directory /debian/ string
setdebopt mirror/protocol https string
setdebopt mirror/https/proxy "" string
#setdebopt apt-setup/security_host $mgr string
if [ ! -e /dev/disk/by-label/CNFLNT_IDNT ]; then
v4cfgmeth=$(grep ipv4_method: /etc/confluent/deploycfg |cut -d: -f 2|sed -e 's/ //')
if [ "$v4cfgmeth" = "static" ]; then
setdebopt netcfg/disable_dhcp true boolean
v4addr=$(grep ^ipv4_address: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //')
v4gw=$(grep ^ipv4_gateway: /etc/confluent/deploycfg|cut -d: -f 2| sed -e 's/ //')
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^ipv4_netmask: /etc/confluent/deploycfg|cut -d: -f 2|sed -e 's/ //')
setdebopt netcfg/get_netmask $v4nm string
setdebopt netcfg/get_ipaddress $v4addr string
setdebopt netcfg/confirm_static true boolean
if [ ! -z "$v4gw" ]; then
setdebopt netcfg/get_gateway $v4gw string
fi
namesrvs=$(sed -n '/^nameservers:/,/^[^-]/p' /etc/confluent/deploycfg|grep ^- | cut -d ' ' -f 2|sed -e 's/ //')
for namesrv in "$namesrvs"; do
setdebopt netcfg/get_nameservers $namesrv string
done
elif [ "$vpcfgmeth" = "dhcp" ]; then
setdebopt netcfg/disable_dhcp false boolean
setdebopt netcfg/confirm_static false boolean
fi
fi
@@ -1,8 +0,0 @@
#!/bin/sh
sed -i 's/label: debian/label: Debian/' $2/profile.yaml && \
ln -s $1/linux $2/boot/kernel && \
ln -s $1/initrd.gz $2/boot/initramfs/distribution && \
mkdir -p $2/boot/efi/boot && \
rm $2/distribution && \
mcopy -i $1/boot/grub/efi.img ::/efi/boot/* $2/boot/efi/boot
@@ -1,27 +0,0 @@
d-i anna/choose_modules string openssh-server-udeb
d-i partman-auto/method string regular
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-auto/expert_recipe_file string /tmp/partitionfile
d-i partman/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i passwd/make-user boolean false
d-i clock-setup/utc boolean true
d-i apt-setup/multiverse boolean false
d-i apt-setup/universe boolean false
d-i apt-setup/backports boolean false
d-i apt-setup/updates boolean false
d-i grub-installer/only_debian boolean true
tasksel tasksel/first multiselect standard
d-i pkgsel/include string openssh-server curl
d-i pkgsel/update-policy select none
d-i pkgsel/updatedb boolean false
d-i finish-install/reboot_in_progress note
popularity-contest popularity-contest/participate boolean false
d-i partman-auto/method string lvm
d-i partman-auto/choose_recipe select atomic
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-auto-lvm/guided_size string max
@@ -1,3 +0,0 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet osprofile=%%PROFILE%%
#installedargs: example # These arguments would be added to the installed system
@@ -1,570 +0,0 @@
#!/usr/bin/python
import glob
import json
import os
import socket
import sys
import time
import shlex
import subprocess
try:
import yaml
except ImportError:
pass
try:
from importlib.machinery import SourceFileLoader
def load_source(mod, path):
return SourceFileLoader(mod, path).load_module()
except ImportError:
from imp import load_source
try:
apiclient = load_source('apiclient', '/opt/confluent/bin/apiclient')
except IOError:
apiclient = load_source('apiclient', '/etc/confluent/apiclient')
def add_lla(iface, mac):
pieces = mac.split(':')
initbyte = int(pieces[0], 16) ^ 2
lla = 'fe80::{0:x}{1}:{2}ff:fe{3}:{4}{5}/64'.format(initbyte, pieces[1], pieces[2], pieces[3], pieces[4], pieces[5])
try:
with open('/proc/sys/net/ipv6/conf/{0}/disable_ipv6'.format(iface), 'w') as setin:
setin.write('0')
subprocess.check_call(['ip', 'addr', 'add', 'dev', iface, lla, 'scope', 'link'])
except Exception:
return None
return lla
#cli = apiclient.HTTPSClient(json=True)
#c = cli.grab_url_with_status('/confluent-api/self/netcfg')
def add_missing_llas():
#NetworkManager goes out of its way to suppress ipv6 lla, so will just add some
added = {}
linkinfo = subprocess.check_output(['ip', '-br', 'l']).decode('utf8')
ifaces = {}
for line in linkinfo.split('\n'):
line = line.strip().split()
if not line or 'LOOPBACK' in line[-1] or 'NO-CARRIER' in line[-1]:
continue
if 'UP' not in line[-1]:
subprocess.call(['ip', 'link', 'set', line[0], 'up'])
ifaces[line[0]] = line[2]
ips = {}
ipinfo = subprocess.check_output(['ip', '-br', '-6', 'a']).decode('utf8')
for line in ipinfo.split('\n'):
line = line.strip().split(None, 2)
if not line:
continue
ips[line[0]] = line[2]
for iface in ifaces:
for addr in ips.get(iface, '').split():
if addr.startswith('fe80::'):
break
else:
newlla = add_lla(iface, ifaces[iface])
if newlla:
added[iface] = newlla
return added
def rm_tmp_llas(tmpllas):
for iface in tmpllas:
subprocess.check_call(['ip', 'addr', 'del', 'dev', iface, tmpllas[iface]])
def await_tentative():
maxwait = 10
while b'tentative' in subprocess.check_output(['ip', 'a']):
if maxwait == 0:
break
maxwait -= 1
time.sleep(1)
def map_idx_to_name():
map = {}
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(' '):
continue
idx, iface, rst = line.split(':', 2)
prevdev = iface.strip()
rst = rst.split()
try:
midx = rst.index('master')
continue
except ValueError:
pass
idx = int(idx)
iface = iface.strip()
map[idx] = iface
return map, devtype
def get_interface_name(iname, settings):
explicitname = settings.get('interface_names', None)
if explicitname:
return explicitname
if settings.get('current_nic', False):
return iname
return None
class NetplanManager(object):
def __init__(self, deploycfg):
self.cfgbydev = {}
self.read_connections()
self.deploycfg = deploycfg
def read_connections(self):
for plan in glob.glob('/etc/netplan/*.y*ml'):
with open(plan) as planfile:
planinfo = yaml.safe_load(planfile)
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]
def apply_configuration(self, cfg):
devnames = cfg['interfaces']
if len(devnames) != 1:
raise Exception('Multi-nic team/bonds not yet supported')
stgs = cfg['settings']
needcfgapply = False
for devname in devnames:
needcfgwrite = False
# ipv6_method missing at uconn...
if stgs.get('ipv6_method', None) == 'static':
curraddr = stgs['ipv6_address']
currips = self.getcfgarrpath([devname, 'addresses'])
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('ipv4_method', None) == 'static':
curraddr = stgs['ipv4_address']
currips = self.getcfgarrpath([devname, 'addresses'])
if curraddr not in currips:
needcfgwrite = True
currips.append(curraddr)
if stgs.get('mtu', None):
devdict = self.getcfgarrpath([devname])
devdict['mtu'] = int(stgs['mtu'])
gws = []
gws.append(stgs.get('ipv4_gateway', None))
gws.append(stgs.get('ipv6_gateway', None))
for gwaddr in gws:
if gwaddr:
cfgroutes = self.getcfgarrpath([devname, 'routes'])
for rinfo in cfgroutes:
if rinfo.get('via', None) == gwaddr:
break
else:
needcfgwrite = True
cfgroutes.append({'via': gwaddr, 'to': 'default'})
dnsips = self.deploycfg.get('nameservers', [])
dnsdomain = self.deploycfg.get('dnsdomain', '')
if dnsips:
currdnsips = self.getcfgarrpath([devname, 'nameservers', 'addresses'])
for dnsip in dnsips:
if dnsip and dnsip not in currdnsips:
needcfgwrite = True
currdnsips.append(dnsip)
if dnsdomain:
currdnsdomain = self.getcfgarrpath([devname, 'nameservers', 'search'])
if dnsdomain not in currdnsdomain:
needcfgwrite = True
currdnsdomain.append(dnsdomain)
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:
planout.write(yaml.dump(newcfg))
os.umask(oumask)
if needcfgapply:
subprocess.call(['netplan', 'apply'])
def getcfgarrpath(self, devpath):
currptr = self.cfgbydev
for k in devpath[:-1]:
if k not in currptr:
currptr[k] = {}
currptr = currptr[k]
if devpath[-1] not in currptr:
currptr[devpath[-1]] = []
return currptr[devpath[-1]]
class WickedManager(object):
def __init__(self):
self.teamidx = 0
self.read_connections()
def read_connections(self):
self.cfgbydev = {}
for ifcfg in glob.glob('/etc/sysconfig/network/ifcfg-*'):
devname = ifcfg.replace('/etc/sysconfig/network/ifcfg-', '')
if devname == 'lo':
continue
currcfg = {}
self.cfgbydev[devname] = currcfg
for cfg in open(ifcfg).read().splitlines():
cfg = cfg.split('#', 1)[0]
try:
kv = ' '.join(shlex.split(cfg)).split('=', 1)
except Exception:
# unparseable line, likely having something we can't handle
del self.cfgbydev[devname]
if len(kv) != 2:
continue
k, v = kv
k = k.strip()
v = v.strip()
currcfg[k] = v
def apply_configuration(self, cfg):
stgs = cfg['settings']
ipcfg = 'STARTMODE=auto\n'
routecfg = ''
bootproto4 = stgs.get('ipv4_method', 'none')
bootproto6 = stgs.get('ipv6_method', 'none')
if bootproto4 == 'dhcp' and bootproto6 == 'dhcp':
ipcfg += 'BOOTPROTO=dhcp\n'
elif bootproto4 == 'dhcp':
ipcfg += 'BOOTPROTO=dhcp4\n'
elif bootproto6 == 'dhcp':
ipcfg += 'BOOTPROTO=dhcp6\n'
else:
ipcfg += 'BOOTPROTO=static\n'
if stgs.get('ipv4_address', None):
ipcfg += 'IPADDR=' + stgs['ipv4_address'] + '\n'
v4gw = stgs.get('ipv4_gateway', None)
if stgs.get('ipv6_address', None):
ipcfg += 'IPADDR_V6=' + stgs['ipv6_address'] + '\n'
v6gw = stgs.get('ipv6_gateway', None)
cname = None
if len(cfg['interfaces']) > 1: # creating new team
if not stgs.get('team_mode', None):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
return
if not stgs.get('connection_name', None):
stgs['connection_name'] = 'bond{0}'.format(self.teamidx)
self.teamidx += 1
cname = stgs['connection_name']
with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as teamout:
teamout.write(ipcfg)
if stgs['team_mode'] == 'lacp':
stgs['team_mode'] = '802.3ad'
teamout.write("BONDING_MODULE_OPTS='mode={0} miimon=100'\nBONDING_MASTER=yes\n".format(stgs['team_mode']))
idx = 1
for iface in cfg['interfaces']:
subprocess.call(['wicked', 'ifdown', iface])
try:
os.remove('/etc/sysconfig/network/ifcfg-{0}'.format(iface))
os.remove('/etc/sysconfig/network/ifroute-{0}'.format(iface))
except OSError:
pass
teamout.write('BONDING_SLAVE{0}={1}\n'.format(idx, iface))
idx += 1
else:
cname = list(cfg['interfaces'])[0]
priorcfg = self.cfgbydev.get(cname, {})
for cf in priorcfg:
if cf.startswith('TEAM_'):
ipcfg += '{0}={1}\n'.format(cf, priorcfg[cf])
with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as iout:
iout.write(ipcfg)
if v4gw:
routecfg += 'default {0} - {1}\n'.format(v4gw, cname)
if v6gw:
routecfg += 'default {0} - {1}\n'.format(v6gw, cname)
if routecfg:
with open('/etc/sysconfig/network/ifroute-{0}'.format(cname), 'w') as routeout:
routeout.write(routecfg)
subprocess.call(['wicked', 'ifup', cname])
class NetworkManager(object):
bondtypes = {
'lacp': '802.3ad',
'loadbalance': 'balance-alb',
'roundrobin': 'balance-rr',
'activebackup': 'active-backup',
}
def __init__(self, devtypes, deploycfg):
self.deploycfg = deploycfg
self.connections = {}
self.uuidbyname = {}
self.uuidbydev = {}
self.connectiondetail = {}
self.read_connections()
self.teamidx = 0
self.devtypes = devtypes
def read_connections(self):
self.connections = {}
self.uuidbyname = {}
self.uuidbydev = {}
self.connectiondetail = {}
ci = subprocess.check_output(['nmcli', '-t', 'c']).decode('utf8')
for inf in ci.splitlines():
n, u, t, dev = inf.split(':')
if n == 'NAME':
continue
if dev == '--':
dev = None
self.uuidbyname[n] = u
if dev:
self.uuidbydev[dev] = u
self.connections[u] = {'name': n, 'uuid': u, 'type': t, 'dev': dev}
deats = {}
for deat in subprocess.check_output(['nmcli', 'c', 's', u]).decode('utf8').splitlines():
k, v = deat.split(':', 1)
v = v.strip()
if v == '--':
continue
if '(default)' in v:
continue
deats[k] = v
self.connectiondetail[u] = deats
def add_team_member(self, team, member):
bondcfg = {}
if member in self.uuidbydev:
myuuid = self.uuidbydev[member]
deats = self.connectiondetail[myuuid]
currteam = deats.get('connection.master', None)
if currteam == team:
return
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname'):
if deats.get(stg, None):
bondcfg[stg] = deats[stg]
if member in self.uuidbyname:
subprocess.check_call(['nmcli', 'c', 'del', self.uuidbyname[member]])
devtype = self.devtypes.get(member, 'bond-slave')
subprocess.check_call(['nmcli', 'c', 'add', 'type', devtype, 'master', team, 'con-name', member, 'connection.interface-name', member])
if bondcfg:
args = []
for parm in bondcfg:
args.append(parm)
args.append(bondcfg[parm])
subprocess.check_call(['nmcli', 'c', 'm', team] + args)
def apply_configuration(self, cfg, lastchance=False):
cmdargs = {}
cmdargs['connection.autoconnect'] = 'yes'
stgs = cfg['settings']
cmdargs['ipv6.method'] = stgs.get('ipv6_method', 'link-local')
if stgs.get('ipv6_address', None):
cmdargs['ipv6.addresses'] = stgs['ipv6_address']
cmdargs['ipv4.method'] = stgs.get('ipv4_method', 'disabled')
if stgs.get('ipv4_address', None):
cmdargs['ipv4.addresses'] = stgs['ipv4_address']
if stgs.get('ipv4_gateway', None):
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
if stgs.get('ipv6_gateway', None):
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
if stgs.get('mtu', None):
cmdargs['802-3-ethernet.mtu'] = stgs['mtu']
dnsips = self.deploycfg.get('nameservers', [])
if not dnsips:
dnsips = []
dns4 = []
dns6 = []
for dnsip in dnsips:
if '.' in dnsip:
dns4.append(dnsip)
elif ':' in dnsip:
dns6.append(dnsip)
if dns4:
cmdargs['ipv4.dns'] = ','.join(dns4)
if dns6:
cmdargs['ipv6.dns'] = ','.join(dns6)
if len(cfg['interfaces']) > 1: # team time.. should be..
if not cfg['settings'].get('team_mode', None):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
return
if not cfg['settings'].get('connection_name', None):
cfg['settings']['connection_name'] = 'team{0}'.format(self.teamidx)
self.teamidx += 1
cname = cfg['settings']['connection_name']
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append('{}'.format(cmdargs[arg]))
if stgs['team_mode'] in self.bondtypes:
stgs['team_mode'] = self.bondtypes[stgs['team_mode']]
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'miimon=100,mode={}'.format(stgs['team_mode'])] + cargs)
for iface in cfg['interfaces']:
self.add_team_member(cname, iface)
subprocess.check_call(['nmcli', 'c', 'u', cname])
else:
cname = stgs.get('connection_name', None)
iname = list(cfg['interfaces'])[0]
ctype = self.devtypes.get(iname, None)
if not ctype:
if lastchance:
sys.stderr.write("Warning, no device found for interface_name ({0}), skipping setup\n".format(iname))
return 1
if stgs.get('vlan_id', None):
vlan = stgs['vlan_id']
if ctype == 'infiniband':
vlan = '0x{0}'.format(vlan) if not vlan.startswith('0x') else vlan
cmdargs['infiniband.parent'] = iname
cmdargs['infiniband.p-key'] = vlan
iname = '{0}.{1}'.format(iname, vlan[2:])
elif ctype == 'ethernet':
ctype = 'vlan'
cmdargs['vlan.parent'] = iname
cmdargs['vlan.id'] = vlan
iname = '{0}.{1}'.format(iname, vlan)
else:
sys.stderr.write("Warning, unknown interface_name ({0}) device type ({1}) for VLAN/PKEY, skipping setup\n".format(iname, ctype))
return
cname = iname if not cname else cname
u = self.uuidbyname.get(cname, None)
cargs = []
for arg in cmdargs:
cargs.append(arg)
cargs.append('{}'.format(cmdargs[arg]))
if u:
subprocess.check_call(['nmcli', 'c', 'm', u, 'connection.interface-name', iname] + cargs)
subprocess.check_call(['nmcli', 'c', 'u', u])
else:
subprocess.check_call(['nmcli', 'c', 'add', 'type', ctype, 'con-name', cname, 'connection.interface-name', iname] + cargs)
self.read_connections()
u = self.uuidbyname.get(cname, None)
if u:
subprocess.check_call(['nmcli', 'c', 'u', u])
if __name__ == '__main__':
havefirewall = subprocess.call(['systemctl', 'status', 'firewalld'])
havefirewall = havefirewall == 0
if havefirewall:
subprocess.check_call(['systemctl', 'stop', 'firewalld'])
tmpllas = add_missing_llas()
await_tentative()
idxmap, devtypes = map_idx_to_name()
netname_to_interfaces = {}
myaddrs = apiclient.get_my_addresses()
srvs, _ = apiclient.scan_confluents()
doneidxs = set([])
dc = None
if not srvs: # the multicast scan failed, fallback to deploycfg cfg file
with open('/etc/confluent/confluent.deploycfg', 'r') as dci:
for cfgline in dci.read().split('\n'):
if cfgline.startswith('deploy_server:'):
srvs = [cfgline.split()[1]]
break
for srv in srvs:
try:
s = socket.create_connection((srv, 443))
except socket.error:
continue
myname = s.getsockname()
s.close()
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():
curridx = addr[-1]
if curridx in doneidxs:
continue
for tries in (1, 2, 3):
try:
status, nc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/netcfg')
break
except Exception:
if tries == 3:
raise
time.sleep(1)
continue
nc = json.loads(nc)
if not dc:
for tries in (1, 2, 3):
try:
status, dc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/deploycfg2')
break
except Exception:
if tries == 3:
raise
time.sleep(1)
continue
dc = json.loads(dc)
iname = get_interface_name(idxmap[curridx], nc.get('default', {}))
if iname:
for iname in iname.split(','):
if 'default' in netname_to_interfaces:
netname_to_interfaces['default']['interfaces'].add(iname)
else:
netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']}
for netname in nc.get('extranets', {}):
uname = '_' + netname
iname = get_interface_name(idxmap[curridx], nc['extranets'][netname])
if iname:
for iname in iname.split(','):
if uname in netname_to_interfaces:
netname_to_interfaces[uname]['interfaces'].add(iname)
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
doneidxs.add(curridx)
if 'default' in netname_to_interfaces:
for netn in netname_to_interfaces:
if netn == 'default':
continue
netname_to_interfaces['default']['interfaces'] -= netname_to_interfaces[netn]['interfaces']
if not netname_to_interfaces['default']['interfaces']:
del netname_to_interfaces['default']
# Make sure VLAN/PKEY connections are created last
netname_to_interfaces = dict(sorted(netname_to_interfaces.items(), key=lambda item: 'vlan_id' in item[1]['settings']))
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
if os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes, dc)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
retrynics = []
for netn in netname_to_interfaces:
redo = nm.apply_configuration(netname_to_interfaces[netn])
if redo == 1:
retrynics.append(netn)
if retrynics:
idxmap, devtypes = map_idx_to_name()
if os.path.exists('/usr/sbin/netplan'):
nm = NetplanManager(dc)
if os.path.exists('/usr/bin/nmcli'):
nm = NetworkManager(devtypes, dc)
elif os.path.exists('/usr/sbin/wicked'):
nm = WickedManager()
for netn in retrynics:
nm.apply_configuration(netname_to_interfaces[netn], lastchance=True)
if havefirewall:
subprocess.check_call(['systemctl', 'start', 'firewalld'])
await_tentative()
@@ -1,11 +0,0 @@
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -1,22 +0,0 @@
#!/bin/bash
echo "Confluent first boot is running"
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
export HOME
#cp -a /etc/confluent/ssh/* /etc/ssh/
#systemctl restart sshd
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ ! -z "$rootpw" -a "$rootpw" != "null" ]; then
echo root:$rootpw | chpasswd -e
fi
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
source /etc/confluent/functions
run_remote_parts firstboot.d
run_remote_config firstboot.d
systemctl disable firstboot
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -1,216 +0,0 @@
#!/bin/bash
function test_mgr() {
whost=$1
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
if curl -gs https://${whost}/confluent-api/ > /dev/null; then
return 0
fi
return 1
}
function 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 $*
elif [ -x /usr/bin/python3 ]; then
/usr/bin/python3 $*
elif [ -x /usr/bin/python ]; then
/usr/bin/python $*
elif [ -x /usr/bin/python2 ]; then
/usr/bin/python2 $*
fi
}
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [[ "$confluent_mgr" == *"%"* ]]; then
confluent_mgr=""
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if ! test_mgr $confluent_mgr; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [[ "$confluent_mgr" = *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
fi
if ! test_mgr $confluent_mgr; then
BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
for confluent_mgr in $BESTMGRS $OKMGRS; do
if [[ $confluent_mgr == *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
if test_mgr $confluent_mgr; then
break
fi
done
fi
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
export confluent_profile confluent_mgr nodename
}
fetch_remote() {
curlargs=""
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
run_remote_parts() {
unset confluentscripttmpdir
initconfluentscriptstmp
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
unsettmpdir=0
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
initconfluentscriptstmp
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source ./$cmd
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
rm -rf $confluentscripttmpdir
return $retcode
}
run_remote() {
requestedcmd="'$*'"
unsettmpdir=0
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
unsettmpdir=1
fi
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
if [ -x /usr/bin/chcon ]; then
chcon system_u:object_r:bin_t:s0 $cmd
fi
shift
./$cmd $*
retcode=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
return $retcode
}
run_remote_python() {
echo
set_confluent_vars
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
unset confluentscripttmpdir
initconfluentscriptstmp
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
whost=$confluent_mgr
if [[ "$whost" == *:* ]] && [[ "$whost" != *[* ]] ; then
whost="[$whost]"
fi
curl -gf -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
return $retcode
}
run_remote_config() {
echo
set_confluent_vars
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
#If invoked as a command, use the arguments to actually run a function
(return 0 2>/dev/null) || $1 "${@:2}"
@@ -1,67 +0,0 @@
#!/bin/bash
mkdir -p /run/sshd
mkdir -p /root/.ssh
cat /tmp/ssh/*pubkey >> /root/.ssh/authorized_keys
cat /tmp/ssh/*.ca | sed -e s/^/'@cert-authority * '/ >> /etc/ssh/ssh_known_hosts
chmod 700 /etc/confluent
chmod go-rwx /etc/confluent/*
sshconf=/etc/ssh/ssh_config
if [ -d /etc/ssh/ssh_config.d/ ]; then
sshconf=/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | awk '{print $2}')
mkdir -p /opt/confluent/bin
python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
chmod +x /opt/confluent/bin/firstboot.sh
python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
systemctl enable firstboot
python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
source /etc/confluent/functions
python3 /opt/confluent/bin/apiclient /confluent-api/self/nodelist | sed -e s/'^- //' > /tmp/allnodes
cp /tmp/allnodes /root/.shosts
cp /tmp/allnodes /etc/ssh/shosts.equiv
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
ntps=$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|sed 1d|sed '$d' | sed -e 's/^- //' | paste -sd ' ')
sed -i "s/#NTP=/NTP=$ntps/" /etc/systemd/timesyncd.conf
fi
textcons=$(grep ^textconsole: /etc/confluent/confluent.deploycfg |awk '{print $2}')
updategrub=0
if [ "$textcons" = "true" ] && ! grep console= /proc/cmdline > /dev/null; then
cons=""
if [ -f /tmp/autocons.info ]; then
cons=$(cat /tmp/autocons.info)
fi
if [ ! -z "$cons" ]; then
sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 console='${cons#/dev/}'"/' /etc/default/grub
updategrub=1
fi
fi
kargs=$(python3 /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/profile.yaml | grep ^installedargs: | sed -e 's/#.*//')
if [ ! -z "$kargs" ]; then
sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 '"${kargs}"'"/' /etc/default/grub
fi
if [ 1 = $updategrub ]; then
update-grub
fi
if [ -e /sys/firmware/efi ]; then
bootnum=$(efibootmgr | grep ubuntu | sed -e 's/ .*//' -e 's/\*//' -e s/Boot//)
if [ ! -z "$bootnum" ]; then
currboot=$(efibootmgr | grep ^BootOrder: | awk '{print $2}')
nextboot=$(echo $currboot| awk -F, '{print $1}')
[ "$nextboot" = "$bootnum" ] || efibootmgr -o $bootnum,$currboot
efibootmgr -D
fi
fi
run_remote_python syncfileclient
run_remote_parts post.d
run_remote_config post
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged'
@@ -1,88 +0,0 @@
#!/bin/sh
## Use the following option to add additional boot parameters for the
## installed system (if supported by the bootloader installer).
## Note: options passed to the installer will be added automatically.
#d-i debian-installer/add-kernel-opts string [from profile.yaml]
deploycfg=/etc/confluent/confluent.deploycfg
mgr=$(cat /etc/confluent/deployer)
cryptboot=$(grep encryptboot: $deploycfg|sed -e 's/^encryptboot: //')
if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "null" ]; then
echo "****Encrypted boot requested, but not implemented for this OS, halting install" > /dev/console
[ -f '/tmp/autoconsdev' ] && (echo "****Encryptod boot requested, but not implemented for this OS,halting install" >> $(cat /tmp/autoconsdev))
while :; do sleep 86400; done
fi
cat > /usr/lib/live-installer.d/confluent-certs << EOF
#!/bin/sh
cp /tls/* /target/etc/ssl/certs/
cat /tls/*.pem >> /target/etc/ssl/certs/ca-certificates.crt
EOF
chmod a+x /usr/lib/live-installer.d/confluent-certs
mkdir -p /.ssh/
cat /ssh/*pubkey > /.ssh/authorized_keys
mkdir -p /etc/ssh
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info|cut -d ' ' -f 2)
apikey=$(cat /etc/confluent/confluent.apikey)
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=$(echo $pubkey | sed -e s/.pub/-cert.pub/)
keyfile=${pubkey%.pub}
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" --post-file=$pubkey https://$mgr/confluent-api/self/sshcert -O $certfile --quiet
echo HostKey $keyfile >> /etc/ssh/sshd_config
echo HostCertificate $certfile >> /etc/ssh/sshd_config
done
if [ -e /tmp/installdisk ]; then
instdisk=$(cat /tmp/installdisk)
else
for blockdev in $(ls /sys/class/block/); do
shortname=$(basename $blockdev)
if [ "$shortname" != "${shortname%loop*}" ]; then
continue
fi
udevadm info --query=property /dev/$shortname |grep DEVTYPE=disk > /dev/null || continue # ignore partitions
udevadm info --query=property /dev/$shortname |grep DM_NAME > /dev/null && continue # not a real disk
sz=$(cat /sys/block/$shortname/size 2> /dev/null)
[ -z "$sz" ] && continue
[ $sz -lt 1048576 ] && continue # Too small
[ -z "$firstdisk" ] && firstdisk=$shortname
if udevadm info --query=property /dev/$shortname|grep ID_MODEL=| sed -e s/' '/_/g | grep -iE '(thinksystem_m.2|m.2_nvme_2-bay_raid_kit)' > /dev/null; then
instdisk=$shortname
break
fi
if udevadm info --query=property /dev/$shortname|grep MD_CONTAINER=imsm; then
sraid=$sortname
else
drv=$(udevadm info -a /dev/sdb|grep DRIVERS==|grep -Ev '""|"sd"' | sed -e s/.*=// -e s/'"'//g)
if [ "ahci" = "$drv" -a -z "$onbdisk" ]; then
onbdisk=$shortname
elif [ "megaraid" = "$drv" -a -z "$rdisk" ]; then
rdisk=$shortname
fi
fi
done
fi
if [ -z "$instdisk" ]; then
if [ ! -z "$sraid"]; then
instdisk=$sraid
elif [ ! -z "$onbdisk" ]; then
instdisk=$onbdisk
elif [ ! -z "$rdisk" ]; then
instdisk=$rdisk
else
instdisk=$firstdisk
fi
fi
if [ ! -z "$instdisk" ]; then
debconf-set partman-auto/disk /dev/$instdisk
debconf-set grub-installer/bootdev /dev/$instdisk
fi
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config
echo IgnoreRhosts no >> /etc/ssh/sshd_config
echo sshd:x:1:1::/run/sshd:/bin/false >> /etc/passwd
/usr/sbin/sshd
wget --header="CONFLUENT_NODENAME: $nodename" --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/nodelist -O /tmp/allnodes --quiet
#kill -HUP $(ps | grep -v grep | grep /usr/sbin/sshd | sed -e 's/^ *//'|cut -d ' ' -f 1)
#curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /tmp/getinstalldisk
#python3 /tmp/getinstalldisk
#sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml
@@ -1,19 +0,0 @@
#!/bin/sh
mount -o bind /sys /target/sys
mount -o bind /dev /target/dev
mount -o bind /dev/pts /target/dev/pts
mount -o bind /proc /target/proc
mount -o bind /dev/pts /target/dev/pts
mount -o bind /run /target/run
cp -a /etc/confluent /target/etc/confluent
cp -a /opt/confluent /target/opt/confluent
mv /tmp/post.sh /target/tmp/
cp -a /ssh /tls /target/tmp
cat /tls/*.pem >> /target/etc/confluent/ca.pem
cp -a /etc/ssh/ssh_host_* /target/etc/ssh/
grep HostCertificate /etc/ssh/sshd_config >> /target/etc/ssh/sshd_config
echo Port 2222 >> /etc/ssh/sshd_config
kill -HUP $(ps |grep -v grep|grep sshd|grep /usr|sed -e s/' root.*//')
cp /tls/* /target/etc/ssl/certs/
cat /tls/*.pem >> /target/etc/ssl/certs/ca-certificates.crt
chroot /target bash /tmp/post.sh
@@ -1,5 +0,0 @@
#!/bin/bash
# Add this to firstboot.d
export DEBIAN_FRONTEND=noninteractive
apt-get -y install proxmox-ve postfix open-iscsi chrony < /dev/null
@@ -1,26 +0,0 @@
#!/bin/bash
# This script would run in post.d
#
export DEBIAN_FRONTEND=noninteractive
codename=$(grep ^VERSION_CODENAME /etc/os-release | cut -d= -f2)
echo "deb [arch=amd64] http://download.proxmox.com/debian/pve $codename pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
wget https://enterprise.proxmox.com/debian/proxmox-release-$codename.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg
sum=$(sha512sum /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg)
if [ $codename == "bookworm" ]; then
expectedsum=7da6fe34168adc6e479327ba517796d4702fa2f8b4f0a9833f5ea6e6b48f6507a6da403a274fe201595edc86a84463d50383d07f64bdde2e3658108db7d6dc87
elif [ $codename == "trixie" ]; then
expectedsum=8678f2327c49276615288d7ca11e7d296bc8a2b96946fe565a9c81e533f9b15a5dbbad210a0ad5cd46d361ff1d3c4bac55844bc296beefa4f88b86e44e69fa51
fi
if [ "$sum" -ne "$expectedsum" ]; then
echo "Mismatch in fingerprint!"
rm /etc/apt/trusted.gpg.d/proxmox-release-$codename.gpg
exit 1
fi
apt-get update && apt-get -y full-upgrade < /dev/null
apt-get -y install proxmox-default-kernel < /dev/null
apt-get -y remove linux-image-amd64 'linux-image-6.1*' < /dev/null
update-grub
apt-get -y remove os-prober < /dev/null
@@ -1,45 +0,0 @@
[ -f /lib/confluent/functions ] && . /lib/confluent/functions
[ -f /etc/confluent/functions ] && . /etc/confluent/functions
[ -f /opt/confluent/bin/apiclient ] && confapiclient=/opt/confluent/bin/apiclient
[ -f /etc/confluent/apiclient ] && confapiclient=/etc/confluent/apiclient
for pubkey in /etc/ssh/ssh_host*key.pub; do
if [ "$pubkey" = /etc/ssh/ssh_host_key.pub ]; then
continue
fi
certfile=${pubkey/.pub/-cert.pub}
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
for cert in /etc/ssh/ssh*-cert.pub; do
echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf
done
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/90-confluent.conf
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/90-confluent.conf
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/90-confluent.conf
fi
TMPDIR=$(mktemp -d)
cd $TMPDIR
confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs.tgz
tar xf initramfs.tgz
for ca in ssh/*.ca; do
LINE=$(cat $ca)
if [ -z "$LINE" ]; then continue; fi
cp -af /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts.new
grep -v "$LINE" /etc/ssh/ssh_known_hosts > /etc/ssh/ssh_known_hosts.new
echo '@cert-authority *' $LINE >> /etc/ssh/ssh_known_hosts.new
mv /etc/ssh/ssh_known_hosts.new /etc/ssh/ssh_known_hosts
done
for pubkey in ssh/*.*pubkey; do
LINE=$(cat $pubkey)
if [ -z "$LINE" ]; then continue; fi
cp -af /root/.ssh/authorized_keys /root/.ssh/authorized_keys.new
grep -v "$LINE" /root/.ssh/authorized_keys > /root/.ssh/authorized_keys.new
echo "$LINE" >> /root/.ssh/authorized_keys.new
mv /root/.ssh/authorized_keys.new /root/.ssh/authorized_keys
done
confluentpython $confapiclient /confluent-api/self/nodelist | sed -e 's/^- //' > /etc/ssh/shosts.equiv
cat /etc/ssh/shosts.equiv > /root/.shosts
cd -
rm -rf $TMPDIR
systemctl try-restart sshd
@@ -1,327 +0,0 @@
get_remote_apikey() {
while [ -z "$confluent_apikey" ]; do
/opt/confluent/bin/clortho $nodename $confluent_mgr > /etc/confluent/confluent.apikey
if grep ^SEALED: /etc/confluent/confluent.apikey > /dev/null; then
# we don't support remote sealed api keys anymore
echo > /etc/confluent/confluent.apikey
fi
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..."
if [ ! -z "$autoconsdev" ]; then echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..." > $autoconsdev; fi
sleep 10
elif [ -c /dev/tpmrm0 ]; then
tmpdir=$(mktemp -d)
cd $tmpdir
tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
tpm2_createprimary -G ecc -Q --key-context=prim.ctx
(echo -n "CONFLUENT_APIKEY:";cat /etc/confluent/confluent.apikey) | tpm2_create -Q --policy=pcr15.sha256.policy --public=data.pub --private=data.priv -i - -C prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=data.pub --private=data.priv --name=confluent.apikey --key-context=data.ctx
tpm2_evictcontrol -Q -c data.ctx
tpm2_flushcontext session.ctx
cd - > /dev/null
rm -rf $tmpdir
fi
done
}
root=1
rootok=1
netroot=confluent
echo -ne '\033[H\033[2J\033[3J'
mkdir -p /etc/ssh
mkdir -p /var/tmp/
mkdir -p /var/empty/sshd
mkdir -p /usr/share/empty.sshd
mkdir -p /etc/confluent
sed -i '/^root:x/d' /etc/passwd
echo root:x:0:0::/:/bin/bash >> /etc/passwd
echo sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin >> /etc/passwd
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
autoconsdev=${autocons%,*}
autocons=${autocons##*/}
echo "Automatic console configured for $autocons"
fi
echo "Initializing confluent diskless environment"
echo -n "udevd: "
/usr/lib/systemd/systemd-udevd --daemon
echo -n "Loading drivers..."
udevadm trigger
udevadm trigger --type=devices --action=add
udevadm settle
modprobe ib_ipoib
modprobe ib_umad
modprobe hfi1
modprobe mlx5_ib
echo "done"
cat > /etc/ssh/sshd_config << EOF
Port 2222
Subsystem sftp /usr/libexec/openssh/sftp-server
PermitRootLogin yes
AuthorizedKeysFile .ssh/authorized_keys
EOF
mkdir /root/.ssh
mkdir /.ssh
cat /ssh/*pubkey > /root/.ssh/authorized_keys 2>/dev/null
cp /root/.ssh/authorized_keys /.ssh/
cat /tls/*.pem > /etc/confluent/ca.pem
mkdir -p /etc/pki/tls/certs
cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
TRIES=0
oldumask=$(umask)
umask 0077
tpmdir=$(mktemp -d)
cd $tpmdir
lasthdl=""
if [ -c /dev/tpmrm0 ]; then
for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
unsealeddata=$(tpm2_unseal --auth=session:session.ctx -Q -c $hdl 2>/dev/null)
tpm2_flushcontext session.ctx
if [[ $unsealeddata == "CONFLUENT_APIKEY:"* ]]; then
confluent_apikey=${unsealeddata#CONFLUENT_APIKEY:}
echo $confluent_apikey > /etc/confluent/confluent.apikey
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
lasthdl=$hdl
fi
done
fi
cd - > /dev/null
rm -rf $tpmdir
touch /etc/confluent/confluent.info
cd /sys/class/net
echo -n "Scanning for network configuration..."
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 30 ]; do
TRIES=$((TRIES + 1))
for i in *; do
ip link set $i up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
echo -n .
done
TRIES=0
while ! grep ^NODENAME: /etc/confluent/confluent.info >& /dev/null && [ "$TRIES" -lt 300 ]; do
sleep 0.5
echo -n .
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
TRIES=$((TRIES + 1))
done
cd /
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
hostname $nodename
confluent_mgr=$(grep '^EXTMGRINFO:.*1$' /etc/confluent/confluent.info | head -n 1 | awk -F': ' '{print $2}' | awk -F'|' '{print $1}')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
fi
if [[ $confluent_mgr == *%* ]]; then
echo $confluent_mgr | awk -F% '{print $2}' > /tmp/confluent.ifidx
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
ready=0
while [ $ready = "0" ]; do
get_remote_apikey
if [[ $confluent_mgr == *:* ]] && [[ $confluent_mgr != "["* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
tmperr=$(mktemp)
curl -sSf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg 2> $tmperr
if grep 401 $tmperr > /dev/null; then
confluent_apikey=""
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
elif grep 'SSL' $tmperr > /dev/null; then
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
echo 'Failure establishing TLS conneection to '$confluent_mgr' (try `osdeploy initialize -t` on the deployment server)'
if [ ! -z "$autoconsdev" ]; then echo 'Failure establishing TLS conneection to '$confluent_mgr' (try `osdeploy initialize -t` on the deployment server)' > $autoconsdev; fi
sleep 10
else
ready=1
fi
rm $tmperr
done
if [ ! -z "$autocons" ] && grep "textconsole: true" /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
if [ -c /dev/tpmrm0 ]; then
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
fi
umask $oldumask
mkdir -p /run/NetworkManager/system-connections
cat > /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
[connection]
EOC
echo id=${ifname} >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo uuid=$(uuidgen) >> /run/NetworkManager/system-connections/$ifname.nmconnection
linktype=$(ip link show dev ${ifname}|grep link/|awk '{print $1}')
if [ "$linktype" = link/infiniband ]; then
linktype="infiniband"
else
linktype="ethernet"
fi
echo type=$linktype >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
autoconnect-retries=1
EOC
echo interface-name=$ifname >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
multi-connect=1
permissions=
wait-device-timeout=60000
EOC
if [ "$linktype" = infiniband ]; then
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
[infiniband]
transport-mode=datagram
EOC
fi
autoconfigmethod=$(grep ^ipv4_method: /etc/confluent/confluent.deploycfg |awk '{print $2}')
auto6configmethod=$(grep ^ipv6_method: /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhcpcd $ifname
echo "Complete:"
ip addr show dev $ifname
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
elif [ "$autoconfigmethod" = "static" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^prefix: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#prefix: }
echo "Setting up $ifname as static at $v4addr/$v4nm"
ip addr add dev $ifname $v4addr/$v4nm
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
echo '[ipv4]' >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo address1=$v4addr/$v4nm >> /run/NetworkManager/system-connections/$ifname.nmconnection
if [ ! -z "$v4gw" ]; then
echo gateway=$v4gw >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
nameserversec=0
nameservers=""
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"*.* ]]; then
nameservers="$nameservers"${entry#- }";"
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
echo dns=$nameservers >> /run/NetworkManager/system-connections/$ifname.nmconnection
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
echo dns-search=$dnsdomain >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
may-fail=false
method=manual
[ipv6]
addr-gen-mode=eui64
method=auto
EOC
elif [ "$auto6configmethod" = "static" ]; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg| awk '{print $2}')
v6addr=$(grep ^ipv6_address: /etc/confluent/confluent.deploycfg)
v6addr=${v6addr#ipv6_address: }
v6gw=$(grep ^ipv6_gateway: /etc/confluent/confluent.deploycfg)
v6gw=${v6gw#ipv6_gateway: }
if [ "$v6gw" = "null" ]; then
v6gw=""
fi
v6nm=$(grep ^ipv6_prefix: /etc/confluent/confluent.deploycfg)
v6nm=${v6nm#ipv6_prefix: }
echo "Setting up $ifname as static at $v6addr/$v6nm"
ip addr add dev $ifname $v6addr/$v6nm
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
[ipv4]
dhcp-timeout=90
dhcp-vendor-class-identifier=anaconda-Linux
method=disabled
[ipv6]
addr-gen-mode=eui64
method=manual
may-fail=false
EOC
echo address1=$v6addr/$v6nm >> /run/NetworkManager/system-connections/$ifname.nmconnection
if [ ! -z "$v6gw" ]; then
ip route add default via $v6gw
echo gateway=$v6gw >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
nameserversec=0
nameservers=""
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"*:* ]]; then
nameservers="$nameservers"${entry#- }";"
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
echo dns=$nameservers >> /run/NetworkManager/system-connections/$ifname.nmconnection
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
echo dns-search=$dnsdomain >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
echo '[proxy]' >> /run/NetworkManager/system-connections/$ifname.nmconnection
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
confluent_websrv=$confluent_mgr
if [[ $confluent_websrv == *:* ]] && [[ $confluent_websrv != "["* ]]; then
confluent_websrv="[$confluent_websrv]"
fi
echo -n "Initializing ssh..."
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -C '' -N ''
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d @$pubkey https://$confluent_websrv/confluent-api/self/sshcert > $certfile
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /etc/ssh/sshd_config
fi
echo HostKey $privfile >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_urls=""
for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed -e s/%/%25/); do
if [[ $addr == *:* ]]; then
confluent_urls="$confluent_urls $confluent_proto://[$addr]/confluent-public/os/$confluent_profile/rootimg.sfs"
else
confluent_urls="$confluent_urls $confluent_proto://$addr/confluent-public/os/$confluent_profile/rootimg.sfs"
fi
done
mkdir -p /etc/confluent
curl -sf https://$confluent_websrv/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
. /etc/confluent/functions
source_remote imageboot.sh
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/add_local_repositories
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/firstboot.custom
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/firstboot.service
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/firstboot.sh
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/functions
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/getinstalldisk
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/image2disk.py
@@ -1,138 +0,0 @@
. /lib/dracut-lib.sh
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
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs"
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
fi
/opt/confluent/bin/confluent_imginfo /mnt/remoteimg/rootimg.sfs > /tmp/rootimg.info
loopdev=$(losetup -f)
export mountsrc=$loopdev
losetup -r $loopdev /mnt/remoteimg/rootimg.sfs
if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
while ! curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/profileprivate/pending/rootimg.key > /tmp/rootimg.key; do
echo "Unable to retrieve private key from $confluent_mgr (verify that confluent can access /var/lib/confluent/private/os/$confluent_profile/pending/rootimg.key)"
sleep 1
done
cipher=$(head -n 1 /tmp/rootimg.key)
key=$(tail -n 1 /tmp/rootimg.key)
len=$(wc -c /mnt/remoteimg/rootimg.sfs | awk '{print $1}')
len=$(((len-4096)/512))
dmsetup create cryptimg --table "0 $len crypt $cipher $key 0 $loopdev 8"
/opt/confluent/bin/confluent_imginfo /dev/mapper/cryptimg > /tmp/rootimg.info
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
. /tmp/mountparts.sh
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
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
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
mkdir -p /sysroot/root/.ssh
cp /root/.ssh/* /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cp /etc/confluent/* /sysroot/etc/confluent/
cp /etc/ssh/*key* /sysroot/etc/ssh/
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /sysroot/etc/ssh/sshd_config
fi
echo HostKey $privfile >> /sysroot/etc/ssh/sshd_config
done
mkdir -p /sysroot/dev /sysroot/sys /sysroot/proc /sysroot/run
if [ ! -z "$autocons" ]; then
autocons=${autocons%,*}
mkdir -p /run/systemd/generator/getty.target.wants
ln -s /usr/lib/systemd/system/serial-getty@.service /run/systemd/generator/getty.target.wants/serial-getty@${autocons}.service
fi
while [ ! -e /sysroot/sbin/init ]; do
echo "Failed to access root filesystem or it is missing /sbin/init"
echo "System should be accessible through ssh at port 2222 with the appropriate key"
while [ ! -e /sysroot/sbin/init ]; do
sleep 1
done
done
rootpassword=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg)
rootpassword=${rootpassword#rootpassword: }
if [ "$rootpassword" = "null" ]; then
rootpassword=""
fi
if [ ! -z "$rootpassword" ]; then
sed -i "s@root:[^:]*:@root:$rootpassword:@" /sysroot/etc/shadow
fi
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
echo HostbasedAuthentication yes >> /sysroot/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /sysroot/etc/ssh/sshd_config
echo IgnoreRhosts no >> /sysroot/etc/ssh/sshd_config
sshconf=/sysroot/etc/ssh/ssh_config
if [ -d /sysroot/etc/ssh/ssh_config.d/ ]; then
sshconf=/sysroot/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
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
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
mkdir -p /sysroot/opt/confluent/bin
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.sh > /sysroot/opt/confluent/bin/onboot.sh
chmod +x /sysroot/opt/confluent/bin/onboot.sh
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
ln -s /etc/systemd/system/onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/onboot.service
cp /etc/confluent/functions /sysroot/etc/confluent/functions
if grep installtodisk /proc/cmdline > /dev/null; then
. /etc/confluent/functions
run_remote installimage
exec reboot -f
fi
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
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
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
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/installimage
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/onboot.custom
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/onboot.service
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/onboot.sh
@@ -1 +0,0 @@
../../../../el9-diskless/profiles/default/scripts/post.sh

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