mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7593e9960c |
@@ -1,7 +1,4 @@
|
||||
*.pyc
|
||||
.*.
|
||||
confluent_client/man/man*
|
||||
.vscode
|
||||
.*.sw*
|
||||
.sw*
|
||||
.idea/*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Name: confluent-client
|
||||
Project: https://hpc.lenovo.com/users/
|
||||
Source: https://github.com/lenovo/confluent
|
||||
|
||||
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
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2016 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: tlv.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
License: Apache-2.0
|
||||
@@ -88,18 +88,15 @@ while fullline:
|
||||
for line in fullline.split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
if ':' not in line:
|
||||
if ': ' not in line:
|
||||
line = 'UNKNOWN: ' + line
|
||||
if options.log:
|
||||
node, output = line.split(':', 1)
|
||||
output = output.lstrip()
|
||||
node, output = line.split(': ', 1)
|
||||
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)
|
||||
grouped.add_line(*line.split(': ', 1))
|
||||
if options.watch:
|
||||
if not holdoff:
|
||||
holdoff = os.times()[4] + 0.250
|
||||
|
||||
+91
-118
@@ -292,8 +292,6 @@ def rcompleter(text, state):
|
||||
|
||||
|
||||
def parse_command(command):
|
||||
if isinstance(command, list):
|
||||
return command
|
||||
try:
|
||||
args = shlex.split(command, posix=True)
|
||||
except ValueError as ve:
|
||||
@@ -373,7 +371,7 @@ def do_command(command, server):
|
||||
if argv[0] == 'exit':
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;\x07')
|
||||
raise BailOut()
|
||||
raise Bailout()
|
||||
elif argv[0] in ('help', '?'):
|
||||
return print_help()
|
||||
elif argv[0] == 'cd':
|
||||
@@ -635,7 +633,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
|
||||
@@ -663,28 +661,6 @@ def get_session_node(shellargs):
|
||||
return None
|
||||
|
||||
|
||||
def run_inline_command(path, arg, completion, **kwargs):
|
||||
tlvdata.send(session.connection, {'operation': 'pause',
|
||||
'path': currconsole})
|
||||
buffdata = ''
|
||||
while select.select((session.connection,), (), (), 0)[0]:
|
||||
buffdata += consume_termdata(session.connection, bufferonly=True)
|
||||
rc = session.simple_noderange_command(consolename, path, arg, **kwargs)
|
||||
tlvdata.send(session.connection, {'operation': 'resume',
|
||||
'path': currconsole})
|
||||
sys.stdout.write(completion)
|
||||
sys.stdout.flush()
|
||||
if buffdata:
|
||||
try:
|
||||
sys.stdout.write(buffdata)
|
||||
except UnicodeEncodeError:
|
||||
sys.stdout.buffer.write(buffdata.encode('utf8'))
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
for d in buffdata:
|
||||
sys.stdout.write(d)
|
||||
return rc
|
||||
|
||||
def conserver_command(filehandle, localcommand):
|
||||
# x - conserver has that as 'show baud', I am inclined to replace that with
|
||||
# 'request exclusive'
|
||||
@@ -720,13 +696,13 @@ def conserver_command(filehandle, localcommand):
|
||||
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
|
||||
|
||||
if localcommand[1] == 'o': # off
|
||||
sys.stdout.write("powering off...")
|
||||
sys.stdout.flush()
|
||||
run_inline_command('/power/state', 'off', 'complete]')
|
||||
print("powering off...")
|
||||
session.simple_noderange_command(consolename, '/power/state', 'off')
|
||||
print("complete]\r")
|
||||
elif localcommand[1] == 's': # shutdown
|
||||
sys.stdout.write("shutting down...")
|
||||
sys.stdout.flush()
|
||||
run_inline_command('/power/state', 'shutdown', 'complete]')
|
||||
print("shutting down...")
|
||||
session.simple_noderange_command(consolename, '/power/state', 'shutdown')
|
||||
print("complete]\r")
|
||||
elif localcommand[1] == 'b': # boot
|
||||
cmdlen += 1
|
||||
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
|
||||
@@ -737,46 +713,59 @@ def conserver_command(filehandle, localcommand):
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'setup'
|
||||
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
if rc:
|
||||
print("Error]")
|
||||
|
||||
elif localcommand[2] == 'n': # boot to network
|
||||
sys.stdout.write("booting to network...")
|
||||
sys.stdout.flush()
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'network'
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
|
||||
elif localcommand[2] == 'n': # boot to network
|
||||
print("booting to network...")
|
||||
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'network'
|
||||
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
|
||||
elif localcommand[2] == '\x0d': # boot to default
|
||||
print("booting to default...")
|
||||
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'default'
|
||||
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
|
||||
else:
|
||||
print("Unknown boot state.]\r")
|
||||
|
||||
else:
|
||||
print("Unknown power state.]\r")
|
||||
|
||||
check_power_state()
|
||||
elif localcommand[0] == 'r':
|
||||
sys.stdout.write('\x1b7\x1b[999;999H\x1b[6n')
|
||||
@@ -923,7 +912,8 @@ def main():
|
||||
do_command("start /nodes/%s/console/session" % session_node, netserver)
|
||||
doexit = True
|
||||
elif shellargs:
|
||||
do_command(shellargs, netserver)
|
||||
command = " ".join(shellargs)
|
||||
do_command(command, netserver)
|
||||
quitconfetty(fullexit=True, fixterm=False)
|
||||
|
||||
powerstate = None
|
||||
@@ -938,7 +928,57 @@ def main():
|
||||
rdylist = ()
|
||||
for fh in rdylist:
|
||||
if fh == session.connection:
|
||||
consume_termdata(fh)
|
||||
# this only should get called in the
|
||||
# case of a console session
|
||||
# each command should slurp up all relevant
|
||||
# recv potential
|
||||
#fh.read()
|
||||
try:
|
||||
data = tlvdata.recv(fh)
|
||||
except Exception:
|
||||
data = None
|
||||
if type(data) == dict:
|
||||
updatestatus(data)
|
||||
continue
|
||||
if data is not None:
|
||||
data = client.stringify(data)
|
||||
if clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
try:
|
||||
sys.stdout.write(data)
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
for d in data:
|
||||
sys.stdout.write(d)
|
||||
now = time.time()
|
||||
if ('showtime' not in laststate or
|
||||
(now // 60) != laststate['showtime'] // 60):
|
||||
# don't bother churning if minute does not change
|
||||
laststate['showtime'] = now
|
||||
updatestatus()
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
deadline = 5
|
||||
connected = False
|
||||
while not connected and deadline > 0:
|
||||
try:
|
||||
server_connect()
|
||||
connected = True
|
||||
except Exception:
|
||||
pass
|
||||
if not connected:
|
||||
time.sleep(1)
|
||||
deadline -=1
|
||||
if connected:
|
||||
do_command(
|
||||
"start /nodes/%s/console/session skipreplay=True" % consolename,
|
||||
netserver)
|
||||
else:
|
||||
doexit = True
|
||||
inconsole = False
|
||||
sys.stdout.write("\r\n[remote disconnected]\r\n")
|
||||
break
|
||||
else:
|
||||
try:
|
||||
myinput = fh.read()
|
||||
@@ -964,65 +1004,6 @@ def main():
|
||||
sys.stdout.write('Lost connection to server')
|
||||
quitconfetty(fullexit=True)
|
||||
|
||||
def consume_termdata(fh, bufferonly=False):
|
||||
global clearpowermessage
|
||||
try:
|
||||
data = tlvdata.recv(fh)
|
||||
except Exception:
|
||||
data = None
|
||||
if type(data) == dict:
|
||||
updatestatus(data)
|
||||
return ''
|
||||
if data is not None:
|
||||
data = client.stringify(data)
|
||||
if clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
if bufferonly:
|
||||
return data
|
||||
try:
|
||||
sys.stdout.write(data)
|
||||
except UnicodeEncodeError:
|
||||
sys.stdout.buffer.write(data.encode('utf8'))
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
for d in data:
|
||||
sys.stdout.write(d)
|
||||
now = time.time()
|
||||
if ('showtime' not in laststate or
|
||||
(now // 60) != laststate['showtime'] // 60):
|
||||
# don't bother churning if minute does not change
|
||||
laststate['showtime'] = now
|
||||
updatestatus()
|
||||
try:
|
||||
sys.stdout.flush()
|
||||
except Exception:
|
||||
# EWOULDBLOCK causes this to raise, ignore
|
||||
# this scenario comfortable that it
|
||||
# will come out soon enough
|
||||
pass
|
||||
else:
|
||||
deadline = 5
|
||||
connected = False
|
||||
while not connected and deadline > 0:
|
||||
try:
|
||||
server_connect()
|
||||
connected = True
|
||||
except Exception:
|
||||
pass
|
||||
if not connected:
|
||||
time.sleep(1)
|
||||
deadline -=1
|
||||
if connected:
|
||||
do_command(
|
||||
"start /nodes/%s/console/session skipreplay=True" % consolename,
|
||||
netserver)
|
||||
else:
|
||||
doexit = True
|
||||
inconsole = False
|
||||
sys.stdout.write("\r\n[remote disconnected]\r\n")
|
||||
return ''
|
||||
|
||||
if __name__ == '__main__':
|
||||
errcode = 0
|
||||
deadline = 0
|
||||
@@ -1032,14 +1013,6 @@ if __name__ == '__main__':
|
||||
main()
|
||||
except BailOut as e:
|
||||
errcode = e.errorcode
|
||||
except Exception as e:
|
||||
import traceback
|
||||
excinfo = traceback.print_exc()
|
||||
try:
|
||||
quitconfetty()
|
||||
except Exception:
|
||||
pass
|
||||
print(excinfo)
|
||||
finally:
|
||||
if deadline and os.times()[4] < deadline:
|
||||
sys.stderr.write('[Exited early, hit enter to continue]')
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
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
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
if '#' in line:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
if not line.strip():
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(None, 1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger(object):
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.byname6 = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
byname = self.byname
|
||||
if ':' in currip:
|
||||
byname = self.byname6
|
||||
for name in names:
|
||||
byname[name] = x
|
||||
|
||||
def add_entry(self, ip, names):
|
||||
targ = self.byname
|
||||
if ':' in ip:
|
||||
targ = self.byname6
|
||||
line = '{:<39} {}'.format(ip, names)
|
||||
x = len(self.sourcelines)
|
||||
self.sourcelines.append(line)
|
||||
for name in names.split():
|
||||
if not name:
|
||||
continue
|
||||
targ[name] = x
|
||||
self.byip[ip] = x
|
||||
|
||||
|
||||
def read_target(self, targetfile):
|
||||
if not os.path.exists(targetfile):
|
||||
return
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
while lines and not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
byname = self.byname
|
||||
if ':' in currip:
|
||||
byname = self.byname6
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this entry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in byname:
|
||||
x = byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while self.targlines and not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while self.sourcelines and not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
if not self.sourcelines:
|
||||
break
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Create/amend /etc/hosts file for given noderange")
|
||||
ap.add_argument('noderange', help='Noderange to generate/update /etc/hosts for')
|
||||
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')
|
||||
args = ap.parse_args()
|
||||
c = client.Command()
|
||||
if args.name:
|
||||
names = ' '.join(args.name)
|
||||
else:
|
||||
names = '{node} {node}.{dns.domain}'
|
||||
merger = HostMerger()
|
||||
if not args.ip and not args.attrib:
|
||||
sys.stderr.write('-a or -i is currently required\n')
|
||||
sys.exit(1)
|
||||
if args.attrib:
|
||||
ip4bynode = {}
|
||||
ip6bynode = {}
|
||||
namesbynode = {}
|
||||
domainsbynode = {}
|
||||
for ent in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
|
||||
ent = ent.get('databynode', {})
|
||||
for node in ent:
|
||||
for attrib in ent[node]:
|
||||
val = ent[node][attrib]
|
||||
if not isinstance(val, dict):
|
||||
continue
|
||||
val = val.get('value', None)
|
||||
if attrib == 'dns.domain':
|
||||
domainsbynode[node] = val
|
||||
if attrib.startswith('net.'):
|
||||
nameparts = attrib.split('.')
|
||||
if len(nameparts) == 2:
|
||||
currnet = None
|
||||
else:
|
||||
currnet = '.'.join(nameparts[1:-1])
|
||||
for currdict in (ip4bynode, ip6bynode, namesbynode):
|
||||
if node not in currdict:
|
||||
currdict[node] = {}
|
||||
if attrib.endswith('.ipv4_address') and val:
|
||||
ip4bynode[node][currnet] = val.split('/', 1)[0]
|
||||
elif attrib.endswith('.ipv6_address') and val:
|
||||
ip6bynode[node][currnet] = val.split('/', 1)[0]
|
||||
elif attrib.endswith('.hostname'):
|
||||
namesbynode[node][currnet] = re.split('\s+|,', val)
|
||||
for node in ip4bynode:
|
||||
mydomain = domainsbynode.get(node, None)
|
||||
for ipdb in (ip4bynode, ip6bynode):
|
||||
for currnet in ipdb[node]:
|
||||
if args.name:
|
||||
sys.stderr.write('Custom name and attribute addresses not currently supported together\n')
|
||||
sys.exit(1)
|
||||
else:
|
||||
names = list(namesbynode.get(node, {}).get(currnet, [node]))
|
||||
if mydomain:
|
||||
for name in names:
|
||||
if mydomain in names:
|
||||
break
|
||||
else:
|
||||
for name in list(names):
|
||||
names.append('{0}.{1}'.format(name, mydomain))
|
||||
names = ' '.join(names)
|
||||
merger.add_entry(ipdb[node][currnet], names)
|
||||
merger.write_out('/etc/whatnowhosts')
|
||||
else:
|
||||
namesbynode = {}
|
||||
ipbynode = {}
|
||||
expurl = '/noderange/{0}/attributes/expression'.format(args.noderange)
|
||||
expression = names
|
||||
exitcode = 0
|
||||
exitcode |= expand_expression(c, namesbynode, expurl, names)
|
||||
exitcode |= expand_expression(c, ipbynode, expurl, args.ip)
|
||||
if exitcode:
|
||||
sys.exit(exitcode)
|
||||
for node in ipbynode:
|
||||
merger.add_entry(ipbynode[node], namesbynode[node])
|
||||
if os.path.exists('/etc/hosts'):
|
||||
merger.read_target('/etc/hosts')
|
||||
os.rename('/etc/hosts', '/etc/hosts.confluentbkup')
|
||||
merger.write_out('/etc/hosts')
|
||||
|
||||
|
||||
|
||||
def expand_expression(c, namesbynode, expurl, expression):
|
||||
exitcode = 0
|
||||
for exp in c.create(expurl, {'expression': expression}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
namesbynode[node] = ex[node]['value']
|
||||
return exitcode
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -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(
|
||||
|
||||
@@ -1,61 +1,19 @@
|
||||
#!/usr/bin/python
|
||||
#!/bin/sh
|
||||
# This will take a given directory and make a 'big floppy image'
|
||||
# out of it, suitable for nodemedia upload.
|
||||
|
||||
import fcntl
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
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
|
||||
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 // 4096 + 1
|
||||
with open(image, 'wb') as imgfile:
|
||||
imgfile.seek(datasz * 4096 - 1)
|
||||
imgfile.write(b'\x00')
|
||||
if label:
|
||||
subprocess.check_call(['mformat', '-i', image, '-v', label,
|
||||
'-r', '16', '-d', '1', '-t', str(datasz),
|
||||
'-s', '4','-h', '2', '::'])
|
||||
else:
|
||||
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
|
||||
str(datasz), '-s', '4','-h', '2', '::'])
|
||||
# Some clustered filesystems will have the lock from mformat
|
||||
# linger after close (mformat doesn't unlock)
|
||||
# do a blocking wait for shared lock and then explicitly
|
||||
# unlock between calls to mtools
|
||||
with open(image, 'rb') as imgfile:
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_SH)
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_UN)
|
||||
cpycmd = ['mcopy', '-i', image, '-s']
|
||||
cpycmd.extend(glob.glob('{0}/*'.format(directory)))
|
||||
cpycmd.append('::')
|
||||
subprocess.check_call(cpycmd)
|
||||
# though not necessary for us, make sure dir2img doesn't have a lingering
|
||||
# flock from mcopy for any subsequent commands
|
||||
with open(image, 'rb') as imgfile:
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_SH)
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_UN)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
sys.stderr.write("Usage: {0} <directory> <imagefile>".format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
label = None
|
||||
if len(sys.argv) > 3:
|
||||
label = sys.argv[3]
|
||||
create_image(sys.argv[1], sys.argv[2], label)
|
||||
if [ -z "$1" -o -z "$2" ]; then
|
||||
echo "Usage: $0 <directory> <imagefile>"
|
||||
exit 1
|
||||
fi
|
||||
# Get the needed payload side
|
||||
SIZE=$(du -sB 512 $1|awk '{print $1}')
|
||||
ENTRIES=$(find $1 |wc -l)
|
||||
# Also, each file and directory has overhead, pad size by 32kb per file,
|
||||
# which should be overkill but other values proved to be inadequate
|
||||
# A deeper understanding of VFAT would probably allow for more precise value
|
||||
SIZE=$((SIZE + ENTRIES * 64))
|
||||
dd if=/dev/zero of=$2 bs=512 count=$SIZE
|
||||
# Make a big single sided floppy with many many tracks, saves on complex math
|
||||
mformat -i $2 -d 1 -t $SIZE -s 1 -h 1 ::
|
||||
mcopy -i $2 -s $1/* ::
|
||||
|
||||
@@ -105,10 +105,7 @@ 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
|
||||
|
||||
if exitcode != 0:
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
@@ -47,14 +47,14 @@ exitcode = 0
|
||||
errorNodes = set([])
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
|
||||
if success != 0:
|
||||
sys.exit(success)
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
|
||||
allNodes = set([])
|
||||
|
||||
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if 'error' in node and success != 0:
|
||||
sys.exit(success)
|
||||
allNodes.add(node['item']['href'].replace("/", ""))
|
||||
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
@@ -43,7 +43,7 @@ def bailout(msg, code=1):
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog [options] <noderange> [setting|setting=value]")
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog [options] noderange [option|option=value]")
|
||||
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
|
||||
action='store_true', default=False,
|
||||
help='Compare given settings to default or list settings '
|
||||
@@ -134,10 +134,9 @@ def _assign_value():
|
||||
assignment[key] = value
|
||||
|
||||
|
||||
def parse_config_line(arguments, single=False):
|
||||
def parse_config_line(arguments):
|
||||
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
|
||||
for pidx in range(0, len(arguments)):
|
||||
param = arguments[pidx]
|
||||
for param in arguments:
|
||||
if param == 'show':
|
||||
continue # forgive muscle memory of pasu users
|
||||
if param == 'set':
|
||||
@@ -147,19 +146,14 @@ def parse_config_line(arguments, single=False):
|
||||
if needval:
|
||||
key = needval
|
||||
needval = None
|
||||
if single:
|
||||
value = ' '.join(arguments[pidx:])
|
||||
_assign_value()
|
||||
break
|
||||
else:
|
||||
value = param
|
||||
value = param
|
||||
_assign_value()
|
||||
continue
|
||||
if '=' in param or param[-1] == ':' or forceset:
|
||||
if setmode is None:
|
||||
setmode = True
|
||||
if setmode != True:
|
||||
bailout('Cannot do set and query in same command: Query detected but "{0}" appears to be set'.format(param))
|
||||
bailout('Cannot do set and query in same command')
|
||||
if '=' in param:
|
||||
key, _, value = param.partition('=')
|
||||
_assign_value()
|
||||
@@ -171,7 +165,7 @@ def parse_config_line(arguments, single=False):
|
||||
if setmode is None:
|
||||
setmode = False
|
||||
if setmode != False:
|
||||
bailout('Cannot do set and query in same command: Set mode detected but "{0}" appears to be a query'.format(param))
|
||||
bailout('Cannot do set and query in same command')
|
||||
if '.' not in param:
|
||||
if param == 'bmc':
|
||||
printallbmc = True
|
||||
@@ -221,7 +215,7 @@ if options.batch:
|
||||
pass
|
||||
argset = argset.strip()
|
||||
if argset:
|
||||
parse_config_line(shlex.split(argset), single=True)
|
||||
parse_config_line(shlex.split(argset))
|
||||
argset = argfile.readline()
|
||||
else:
|
||||
parse_config_line(args[1:])
|
||||
@@ -278,6 +272,7 @@ if setmode:
|
||||
rcode |= client.printerror(fr)
|
||||
for node in fr.get('databynode', []):
|
||||
r = fr['databynode'][node]
|
||||
rcode |= client.printerror(r, node)
|
||||
if 'value' not in r:
|
||||
continue
|
||||
keyval = r['value']
|
||||
@@ -289,11 +284,13 @@ 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:
|
||||
|
||||
@@ -25,7 +25,6 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
import confluent.logreader as logreader
|
||||
|
||||
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
|
||||
argparser = optparse.OptionParser(
|
||||
@@ -36,62 +35,10 @@ argparser = optparse.OptionParser(
|
||||
"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('-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()
|
||||
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)
|
||||
#added functionality for wcons
|
||||
|
||||
if options.windowed:
|
||||
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
|
||||
if not envstring:
|
||||
envlist=["xterm", "-e"]
|
||||
else:
|
||||
envlist=os.environ.get('NODECONSOLE_WINDOWED_COMMAND').split(' ')
|
||||
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)
|
||||
for node in sortutil.natural_sort(nodes):
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
subprocess.Popen(envlist + [confettypath, '-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 = []
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
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 cleararm(nr, cli):
|
||||
nodes = set([])
|
||||
for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
|
||||
for node in rsp.get('databynode', {}):
|
||||
nodeinfo = rsp['databynode'][node]
|
||||
for attr in nodeinfo:
|
||||
if attr == 'deployment.apiarmed':
|
||||
curr = nodeinfo[attr].get('value', '')
|
||||
if curr == 'continuous':
|
||||
nodes.add(node)
|
||||
noderange = nr
|
||||
if nodes:
|
||||
noderange += ',-({0})'.format(','.join(nodes))
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
|
||||
{'deployment.apiarmed': ''}):
|
||||
pass
|
||||
|
||||
|
||||
def armonce(nr, cli):
|
||||
nodes = set([])
|
||||
for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
|
||||
for node in rsp.get('databynode', {}):
|
||||
nodeinfo = rsp['databynode'][node]
|
||||
for attr in nodeinfo:
|
||||
if attr == 'deployment.apiarmed':
|
||||
curr = nodeinfo[attr].get('value', '')
|
||||
if curr == 'continuous':
|
||||
nodes.add(node)
|
||||
noderange = nr
|
||||
if nodes:
|
||||
noderange += ',-({0})'.format(','.join(nodes))
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
|
||||
{'deployment.apiarmed': 'once'}):
|
||||
pass
|
||||
|
||||
|
||||
def setpending(nr, profile, cli):
|
||||
args = {'deployment.pendingprofile': profile}
|
||||
if not profile.startswith('genesis-'):
|
||||
args['deployment.stagedprofile'] = ''
|
||||
args['deployment.profile'] = ''
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
|
||||
args):
|
||||
pass
|
||||
|
||||
|
||||
def clearpending(nr, cli):
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
|
||||
{'deployment.pendingprofile': ''}):
|
||||
pass
|
||||
|
||||
def main(args):
|
||||
ap = argparse.ArgumentParser(description='Deploy OS to nodes')
|
||||
ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true')
|
||||
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
|
||||
ap.add_argument('-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('noderange', help='Set of nodes to deploy')
|
||||
ap.add_argument('profile', nargs='?', help='Profile name to deploy')
|
||||
args, extra = ap.parse_known_args(args)
|
||||
if args.profile is None and len(extra) == 1:
|
||||
args.profile = extra[0]
|
||||
extra = extra[1:]
|
||||
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:
|
||||
sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\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)
|
||||
if not args.clear and args.network and not args.prepareonly:
|
||||
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
|
||||
bootmode='uefi',
|
||||
persistent=False,
|
||||
errnodes=errnodes)
|
||||
if errnodes:
|
||||
sys.stderr.write(
|
||||
'Unable to set boot device for following nodes: {0}\n'.format(
|
||||
','.join(errnodes)))
|
||||
return 1
|
||||
rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
|
||||
if args.clear:
|
||||
cleararm(args.noderange, c)
|
||||
clearpending(args.noderange, c)
|
||||
elif args.profile:
|
||||
profnames = []
|
||||
for prof in c.read('/deployment/profiles/'):
|
||||
profname = prof.get('item', {}).get('href', None)
|
||||
if profname:
|
||||
profname = profname.replace('/', '')
|
||||
profnames.append(profname)
|
||||
if profname == args.profile:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
|
||||
if profnames:
|
||||
sys.stderr.write('The following profiles are available:\n')
|
||||
for profname in profnames:
|
||||
sys.stderr.write(' ' + profname + '\n')
|
||||
else:
|
||||
sys.stderr.write('No deployment profiles available, try osdeploy fiimport or imgutil capture\n')
|
||||
sys.exit(1)
|
||||
armonce(args.noderange, c)
|
||||
setpending(args.noderange, args.profile, c)
|
||||
else:
|
||||
databynode = {}
|
||||
for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
|
||||
dbn = r.get('databynode', {})
|
||||
for node in dbn:
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
for attr in dbn[node]:
|
||||
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', '')
|
||||
if profile:
|
||||
profile = 'pending: {}'.format(profile)
|
||||
else:
|
||||
profile = databynode[node].get('deployment.stagedprofile', '')
|
||||
if profile:
|
||||
profile = 'staged: {}'.format(profile)
|
||||
else:
|
||||
profile = databynode[node].get('deployment.profile', '')
|
||||
if profile:
|
||||
profile = 'completed: {}'.format(profile)
|
||||
else:
|
||||
profile= 'No profile pending or applied'
|
||||
armed = databynode[node].get('deployment.apiarmed', '')
|
||||
if armed in ('once', 'continuous'):
|
||||
armed = ' (node authentication armed)'
|
||||
else:
|
||||
armed = ''
|
||||
print('{0}: {1}{2}'.format(node, profile, armed))
|
||||
sys.exit(0)
|
||||
if args.network and not args.prepareonly:
|
||||
return rc
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -34,7 +34,6 @@ defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
|
||||
columnmapping = {
|
||||
'Node': 'nodename',
|
||||
'Model': 'modelnumber',
|
||||
'Model Name': 'modelname',
|
||||
'Serial': 'serialnumber',
|
||||
'UUID': 'uuid',
|
||||
'Type': 'types',
|
||||
@@ -48,54 +47,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]
|
||||
@@ -183,7 +148,6 @@ unique_fields = frozenset(['serial', 'mac', 'uuid'])
|
||||
def import_csv(options, session):
|
||||
nodedata = []
|
||||
unique_data = {}
|
||||
exitcode = 0
|
||||
with open(options.importfile, 'r') as datasrc:
|
||||
records = csv.reader(datasrc)
|
||||
fields = process_header(next(records))
|
||||
@@ -224,7 +188,6 @@ def import_csv(options, session):
|
||||
for res in session.create('/nodes/', datum):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
continue
|
||||
elif 'created' in res:
|
||||
print('Defined ' + res['created'])
|
||||
@@ -235,14 +198,11 @@ def import_csv(options, session):
|
||||
{'node': nodename}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
continue
|
||||
elif 'assigned' in res:
|
||||
print('Discovered ' + res['assigned'])
|
||||
else:
|
||||
print(repr(res))
|
||||
if exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def list_discovery(options, session):
|
||||
@@ -303,11 +263,7 @@ def list_matching_macs(options, session, node=None, checknode=True):
|
||||
return [options.mac.replace(':', '-')]
|
||||
else:
|
||||
path += 'by-mac/'
|
||||
ret = []
|
||||
for x in session.read(path):
|
||||
if 'item' in x and 'href' in x['item']:
|
||||
ret.append(x['item']['href'])
|
||||
return ret
|
||||
return [x['item']['href'] for x in session.read(path)]
|
||||
|
||||
def assign_discovery(options, session, needid=True):
|
||||
abort = False
|
||||
@@ -331,18 +287,12 @@ def assign_discovery(options, session, needid=True):
|
||||
if not matches:
|
||||
sys.stderr.write("No matching discovery candidates found\n")
|
||||
sys.exit(1)
|
||||
exitcode = 0
|
||||
for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
|
||||
{'node': options.node}):
|
||||
if 'assigned' in res:
|
||||
print('Assigned: {0}'.format(res['assigned']))
|
||||
elif 'error' in res:
|
||||
sys.stderr.write('Error: {0}\n'.format(res['error']))
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
else:
|
||||
print(repr(res))
|
||||
if exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
def blocking_scan(session):
|
||||
list(session.update('/discovery/rescan', {'rescan': 'start'}))
|
||||
@@ -353,7 +303,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..
|
||||
@@ -391,15 +341,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)
|
||||
@@ -409,12 +353,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")
|
||||
|
||||
@@ -37,7 +37,7 @@ if sys.version_info[0] < 3:
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] <noderange> [clear]")
|
||||
usage="Usage: %prog [options] noderange [clear]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to clear if clearing log, '
|
||||
|
||||
@@ -88,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:
|
||||
@@ -109,12 +109,6 @@ def update_firmware(session, filename):
|
||||
if options.backup:
|
||||
upargs['bank'] = 'backup'
|
||||
noderrs = {}
|
||||
if session.unixdomain:
|
||||
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', ()):
|
||||
@@ -147,25 +141,22 @@ def update_firmware(session, filename):
|
||||
def show_firmware(session):
|
||||
global exitcode
|
||||
firmware_shown = False
|
||||
nodes_matched = False
|
||||
for component in components:
|
||||
for res in session.read(
|
||||
'/noderange/{0}/inventory/firmware/all/{1}'.format(
|
||||
noderange, component)):
|
||||
nodes_matched = True
|
||||
exitcode |= client.printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
exitcode |= client.printerror(res['databynode'][node], node)
|
||||
if 'firmware' not in res['databynode'][node]:
|
||||
continue
|
||||
for inv in res['databynode'][node]['firmware']:
|
||||
for prefix in inv:
|
||||
firmware_shown = True
|
||||
printfirm(node, prefix, inv[prefix])
|
||||
if not nodes_matched:
|
||||
sys.stderr.write('No matching nodes for noderange "{0}"\n'.format(noderange))
|
||||
elif not firmware_shown and not exitcode:
|
||||
if not firmware_shown and not exitcode:
|
||||
argparser.print_help()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -45,15 +45,14 @@ except IndexError:
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
for group in noderange.split(','):
|
||||
attribs = {'name': group}
|
||||
for arg in args[1:]:
|
||||
key, val = arg.split('=', 1)
|
||||
attribs[key] = val
|
||||
for r in session.create('/nodegroups/', attribs):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
attribs = {'name': noderange}
|
||||
for arg in args[1:]:
|
||||
key, val = arg.split('=', 1)
|
||||
attribs[key] = val
|
||||
for r in session.create('/nodegroups/', attribs):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -43,11 +43,10 @@ noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
for group in noderange.split(','):
|
||||
for r in session.delete('/nodegroups/{0}'.format(group)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
for r in session.delete('/nodegroups/{0}'.format(noderange)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -50,7 +50,6 @@ codemappings = {
|
||||
'warning': 1,
|
||||
'critical': 2,
|
||||
'failed': 2,
|
||||
'unknown': 2,
|
||||
}
|
||||
|
||||
def main():
|
||||
|
||||
@@ -53,9 +53,6 @@ def print_mem_info(node, prefix, meminfo):
|
||||
memdescfmt += '3-{1} '
|
||||
elif 'DDR4' in meminfo['memory_type']:
|
||||
memdescfmt += '4-{1} '
|
||||
elif 'DCPMM' in meminfo['memory_type']:
|
||||
memdescfmt = '{0}GB {1} '
|
||||
meminfo['module_type'] = 'DCPMM'
|
||||
else:
|
||||
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
|
||||
return
|
||||
|
||||
@@ -36,7 +36,7 @@ exitcode = 0
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: "
|
||||
"%prog <noderange> [list][install <license_filename>|save <directory>|delete <license_feature_name>]")
|
||||
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete licenses from, '
|
||||
@@ -69,12 +69,6 @@ def install_license(session, filename):
|
||||
'management_controller/licenses/'.format(noderange)
|
||||
filename = os.path.abspath(filename)
|
||||
instargs = {'filename': filename}
|
||||
if session.unixdomain:
|
||||
of = open(filename, 'rb')
|
||||
try:
|
||||
session.add_file(filename, of.fileno(), 'rb')
|
||||
except Exception:
|
||||
pass
|
||||
for res in session.create(resource, instargs):
|
||||
for node in res.get('databynode', []):
|
||||
if 'error' in res['databynode'][node]:
|
||||
|
||||
@@ -111,12 +111,6 @@ def upload_media(noderange, media):
|
||||
resource = '/noderange/{0}/media/uploads/'.format(noderange)
|
||||
upargs = {'filename': filename}
|
||||
noderrs = {}
|
||||
if session.unixdomain:
|
||||
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:
|
||||
@@ -193,7 +187,5 @@ def main():
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# 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('-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([])
|
||||
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
|
||||
run_cmdv(pingnode, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((pingnode, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0)[0]:
|
||||
data = r.readline()
|
||||
if not data:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(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, 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:
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
'type': 'stderr'}
|
||||
all.add(nopen.stdout)
|
||||
all.add(nopen.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -32,8 +32,8 @@ if path.startswith('/opt'):
|
||||
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])")
|
||||
usage="Usage: %prog [options] noderange "
|
||||
"([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))
|
||||
@@ -33,7 +33,7 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog <noderange>
|
||||
usage='''\n %prog noderange
|
||||
\n ''')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
|
||||
@@ -40,14 +40,12 @@ import confluent.sortutil as sortutil
|
||||
|
||||
def run():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <file/directorylist> <noderange>:<destination>",
|
||||
usage="Usage: %prog location noderange:location",
|
||||
)
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
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('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of nodes to concurrently rsync')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
@@ -63,10 +61,7 @@ def run():
|
||||
c = client.Command()
|
||||
cmdstr = ' '.join(args[:-1])
|
||||
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
|
||||
if options.loginname:
|
||||
cmdstr += ' {}@'.format(options.loginname) + '{node}:' + targpath
|
||||
else:
|
||||
cmdstr += ' {node}:' + targpath
|
||||
cmdstr += ' {node}:' + targpath
|
||||
|
||||
currprocs = 0
|
||||
all = set([])
|
||||
|
||||
@@ -35,14 +35,11 @@ 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>",
|
||||
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,
|
||||
@@ -134,14 +131,8 @@ def run():
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, 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
|
||||
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,
|
||||
|
||||
@@ -32,13 +32,6 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
|
||||
class hybridcsv(csv.excel):
|
||||
lineterminator = '\n'
|
||||
|
||||
|
||||
csv.register_dialect('hybrid', hybridcsv)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
sensorcollections = {
|
||||
@@ -216,7 +209,7 @@ def main():
|
||||
headernames.append(headername)
|
||||
if options.csv:
|
||||
linebyline = False
|
||||
csvwriter = csv.writer(sys.stdout, dialect='hybrid')
|
||||
csvwriter = csv.writer(sys.stdout)
|
||||
if options.interval:
|
||||
csvwriter.writerow(['time', 'node'] + headernames)
|
||||
else:
|
||||
|
||||
@@ -32,7 +32,7 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='Usage: %prog [options] <noderange> [default|cd|network|setup|hd|usb|floppy]')
|
||||
usage='Usage: %prog [options] noderange [default|cd|network|setup|hd]')
|
||||
argparser.add_option('-b', '--bios', dest='biosmode',
|
||||
action='store_true', default=False,
|
||||
help='Request BIOS style boot (rather than UEFI)')
|
||||
|
||||
@@ -34,31 +34,22 @@ 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. "
|
||||
"'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('-l', '--loginname', type='str',
|
||||
help='Username to use when connecting, defaults to current user.')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-p', '--port', type='int', default=0,
|
||||
help='Specify a custom port for ssh')
|
||||
argparser.add_option('-s', '--substitutename',
|
||||
help='Use a different name other than the nodename for ssh')
|
||||
argparser.add_option('-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, '
|
||||
'prompting if over the threshold')
|
||||
'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()
|
||||
@@ -68,7 +59,7 @@ def run():
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
currprocs = 0
|
||||
@@ -78,52 +69,22 @@ def run():
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
nodemap = {}
|
||||
if options.substitutename:
|
||||
subname = options.substitutename
|
||||
if '{' not in subname:
|
||||
subname = '{node}' + subname
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': subname}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
nodemap[node] = ex[node]['value']
|
||||
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']))
|
||||
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, 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']
|
||||
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = ['ssh', node, 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, _, _ = select.select(all, [], [], 10)
|
||||
@@ -163,7 +124,6 @@ def run():
|
||||
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:
|
||||
@@ -173,7 +133,7 @@ def run():
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
|
||||
@@ -20,10 +20,6 @@ import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
@@ -190,7 +186,7 @@ funmap = {
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage='Usage: %prog <noderange> [show|create|delete|diskset] [hotspare|jbod|unconfigured] [options]',
|
||||
usage='Usage: %prog <noderange> [show|create|delete|diskset]',
|
||||
epilog='',
|
||||
)
|
||||
argparser.add_option('-r', '--raidlevel', type='int',
|
||||
@@ -199,8 +195,6 @@ def main():
|
||||
help='Comma separated list of disks to use, or the '
|
||||
'word "rest" to indicate use of all available '
|
||||
'disks')
|
||||
argparser.add_option('-i', '--interactive', default=False, action='store_true',
|
||||
help='Confirm any potentially destructive changes interactively')
|
||||
argparser.add_option('-s', '--size', type='str',
|
||||
help='Comma separated list of sizes to use when '
|
||||
'creating volumes. The sizes may be absolute '
|
||||
@@ -236,13 +230,6 @@ def main():
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if options.interactive and operation != 'show':
|
||||
showstorage(noderange, None, None)
|
||||
sys.stdout.write('The above configuration will be changed by this action, do you wish to continue (y/N): ')
|
||||
confirm = input()
|
||||
if confirm and confirm.lower()[0] != 'y':
|
||||
sys.stdout.write('Aborting\n')
|
||||
sys.exit(1)
|
||||
handler(noderange, options, args[2:])
|
||||
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class OptParser(optparse.OptionParser):
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage="Usage: %prog <noderange> servicedata "
|
||||
"<directory or filename>",
|
||||
"<filename>",
|
||||
epilog='\nservicedata will save service data to the given '
|
||||
'directory. It is saved to the location on the relevant '
|
||||
'management server (the confluent server if running remote, '
|
||||
|
||||
@@ -22,23 +22,17 @@ import io
|
||||
import numpy as np
|
||||
|
||||
import os
|
||||
import sixel
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
import sixel
|
||||
|
||||
class DumbWriter(sixel.SixelWriter):
|
||||
def restore_position(self, output):
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
class DumbWriter(sixel.SixelWriter):
|
||||
def restore_position(self, output):
|
||||
return
|
||||
|
||||
|
||||
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')
|
||||
if not gui:
|
||||
mpl.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
@@ -36,22 +36,13 @@ SO_PASSCRED = 16
|
||||
_attraliases = {
|
||||
'bmc': 'hardwaremanagement.manager',
|
||||
'bmcuser': 'secret.hardwaremanagementuser',
|
||||
'switchuser': 'secret.hardwaremanagementuser',
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
'switchpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
try:
|
||||
getinput = raw_input
|
||||
input = raw_input
|
||||
except NameError:
|
||||
getinput = input
|
||||
|
||||
|
||||
class NestedDict(dict):
|
||||
def __missing__(self, key):
|
||||
value = self[key] = type(self)()
|
||||
return value
|
||||
|
||||
pass
|
||||
|
||||
def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
@@ -118,12 +109,6 @@ def printerror(res, node=None):
|
||||
exitcode = 0
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
for node in res.get('databynode', {}):
|
||||
exitcode = res['databynode'][node].get('errorcode', exitcode)
|
||||
if 'error' in res['databynode'][node]:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['databynode'][node]['error']))
|
||||
if exitcode == 0:
|
||||
exitcode = 1
|
||||
if 'error' in res:
|
||||
if node:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
|
||||
@@ -161,7 +146,6 @@ class Command(object):
|
||||
self._prevkeyname = None
|
||||
self.connection = None
|
||||
self._currnoderange = None
|
||||
self.unixdomain = False
|
||||
if server is None:
|
||||
if 'CONFLUENT_HOST' in os.environ:
|
||||
self.serverloc = os.environ['CONFLUENT_HOST']
|
||||
@@ -171,13 +155,11 @@ class Command(object):
|
||||
self.serverloc = server
|
||||
if os.path.isabs(self.serverloc) and os.path.exists(self.serverloc):
|
||||
self._connect_unix()
|
||||
self.unixdomain = True
|
||||
elif self.serverloc == '/var/run/confluent/api.sock':
|
||||
raise Exception('Confluent service is not available')
|
||||
else:
|
||||
self._connect_tls()
|
||||
self.protversion = int(tlvdata.recv(self.connection).split(
|
||||
b'--')[1].strip()[1:])
|
||||
tlvdata.recv(self.connection)
|
||||
authdata = tlvdata.recv(self.connection)
|
||||
if authdata['authpassed'] == 1:
|
||||
self.authenticated = True
|
||||
@@ -188,13 +170,6 @@ class Command(object):
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
self.authenticate(username, passphrase)
|
||||
|
||||
def add_file(self, name, handle, mode):
|
||||
if self.protversion < 3:
|
||||
raise Exception('Not supported with connected confluent server')
|
||||
if not self.unixdomain:
|
||||
raise Exception('Can only add a file to a unix domain connection')
|
||||
tlvdata.send(self.connection, {'filename': name, 'mode': mode}, handle)
|
||||
|
||||
def authenticate(self, username, password):
|
||||
tlvdata.send(self.connection,
|
||||
{'username': username, 'password': password})
|
||||
@@ -208,7 +183,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 +220,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 +237,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 +259,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 +376,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")
|
||||
@@ -436,7 +409,7 @@ def send_request(operation, path, server, parameters=None):
|
||||
result = tlvdata.recv(server)
|
||||
|
||||
|
||||
def attrrequested(attr, attrlist, seenattributes, node=None):
|
||||
def attrrequested(attr, attrlist, seenattributes):
|
||||
for candidate in attrlist:
|
||||
truename = candidate
|
||||
if candidate.startswith('hm'):
|
||||
@@ -444,16 +417,10 @@ def attrrequested(attr, attrlist, seenattributes, node=None):
|
||||
if candidate in _attraliases:
|
||||
candidate = _attraliases[candidate]
|
||||
if fnmatch.fnmatch(attr.lower(), candidate.lower()):
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = True
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
elif attr.lower().startswith(candidate.lower() + '.'):
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = 1
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -469,15 +436,13 @@ def _sort_attrib(k):
|
||||
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
seenattributes = NestedDict()
|
||||
allnodes = set([])
|
||||
seenattributes = set([])
|
||||
for res in session.read(path):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
continue
|
||||
for node in sorted(res['databynode']):
|
||||
allnodes.add(node)
|
||||
for attr, val in sorted(res['databynode'][node].items(), key=_sort_attrib):
|
||||
if attr == 'error':
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
|
||||
@@ -485,7 +450,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
if attr == 'errorcode':
|
||||
exitcode |= val
|
||||
continue
|
||||
seenattributes[node][attr] = True
|
||||
seenattributes.add(attr)
|
||||
if rename:
|
||||
printattr = rename.get(attr, attr)
|
||||
else:
|
||||
@@ -493,14 +458,14 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
if attrprefix:
|
||||
printattr = attrprefix + printattr
|
||||
currattr = res['databynode'][node][attr]
|
||||
if show_attr(attr, requestargs, seenattributes, options, node):
|
||||
if show_attr(attr, requestargs, seenattributes, options):
|
||||
if 'value' in currattr:
|
||||
if currattr['value'] is not None:
|
||||
val = currattr['value']
|
||||
if isinstance(val, list):
|
||||
val = ','.join(val)
|
||||
attrout = '{0}: {1}: {2}'.format(
|
||||
node, printattr, val).strip()
|
||||
node, printattr, val)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
elif 'isset' in currattr:
|
||||
@@ -509,7 +474,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
printattr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
elif isinstance(currattr, dict) and 'broken' in currattr:
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(node, printattr,
|
||||
currattr['broken'])
|
||||
@@ -526,7 +491,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
blame = options.blame
|
||||
except AttributeError:
|
||||
blame = False
|
||||
if blame or (isinstance(currattr, dict) and 'broken' in currattr):
|
||||
if blame or 'broken' in currattr:
|
||||
blamedata = []
|
||||
if 'inheritedfrom' in currattr:
|
||||
blamedata.append('inherited from group {0}'.format(
|
||||
@@ -574,39 +539,23 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
except TypeError:
|
||||
pass
|
||||
cprint(attrout)
|
||||
somematched = set([])
|
||||
printmissing = set([])
|
||||
badnodes = NestedDict()
|
||||
if not exitcode:
|
||||
if requestargs:
|
||||
for attr in requestargs:
|
||||
for node in allnodes:
|
||||
if attr in seenattributes[node]:
|
||||
somematched.add(attr)
|
||||
else:
|
||||
badnodes[node][attr] = True
|
||||
exitcode = 1
|
||||
for node in sortutil.natural_sort(badnodes):
|
||||
for attr in badnodes[node]:
|
||||
if attr in somematched:
|
||||
sys.stderr.write(
|
||||
'Error: {0} matches no valid value for {1}\n'.format(
|
||||
attr, node))
|
||||
else:
|
||||
printmissing.add(attr)
|
||||
for missing in printmissing:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
if attr not in seenattributes:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
exitcode = 1
|
||||
return exitcode
|
||||
|
||||
|
||||
def show_attr(attr, requestargs, seenattributes, options, node):
|
||||
def show_attr(attr, requestargs, seenattributes, options):
|
||||
try:
|
||||
reverse = options.exclude
|
||||
except AttributeError:
|
||||
reverse = False
|
||||
if requestargs is None or requestargs == []:
|
||||
return True
|
||||
processattr = attrrequested(attr, requestargs, seenattributes, node)
|
||||
processattr = attrrequested(attr, requestargs, seenattributes)
|
||||
if reverse:
|
||||
processattr = not processattr
|
||||
return processattr
|
||||
@@ -635,7 +584,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
|
||||
attrout = '{0}: {1}: ********'.format(noderange, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(noderange, attr)
|
||||
elif isinstance(currattr, dict) and 'broken' in currattr:
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(noderange, attr,
|
||||
currattr['broken'])
|
||||
|
||||
@@ -17,9 +17,9 @@ import sys
|
||||
import struct
|
||||
import termios
|
||||
|
||||
def get_screengeom():
|
||||
def get_screenwidth():
|
||||
return struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
|
||||
b'....'))
|
||||
b'....'))[1]
|
||||
class ScreenPrinter(object):
|
||||
|
||||
def __init__(self, noderange, client, textlen=4):
|
||||
@@ -29,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:
|
||||
@@ -47,68 +44,33 @@ class ScreenPrinter(object):
|
||||
self.fieldwidth = maxlen + textlen + 1 # 1 for column
|
||||
|
||||
def set_output(self, node, text):
|
||||
if self.nodeoutput.get(node, None) == text:
|
||||
if self.nodeoutput[node] == text:
|
||||
return
|
||||
self.nodeoutput[node] = text
|
||||
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
|
||||
currwidth = get_screenwidth()
|
||||
numfields = currwidth // self.fieldwidth
|
||||
fieldformat = '{{0:>{0}}}:{{1:{1}}}'.format(self.nodenamelen,
|
||||
self.textlen)
|
||||
#sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
if len(self.nodelist) < (numfields * currheight):
|
||||
numfields = len(self.nodelist) // currheight + 1
|
||||
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
else:
|
||||
numfields = 1
|
||||
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')
|
||||
else:
|
||||
if node:
|
||||
nodes = [node]
|
||||
else:
|
||||
nodes = self.nodelist
|
||||
for node in nodes:
|
||||
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
|
||||
currfields = 0
|
||||
for node in self.nodelist:
|
||||
if currfields >= numfields:
|
||||
sys.stdout.write('\n')
|
||||
currfields = 1
|
||||
else:
|
||||
currfields += 1
|
||||
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
@@ -118,8 +80,8 @@ class ScreenPrinter(object):
|
||||
if __name__ == '__main__':
|
||||
import confluent.client as client
|
||||
c = client.Command()
|
||||
p = ScreenPrinter('d1-d12', c)
|
||||
p.set_output('d3', 'Upload: 67%')
|
||||
p = ScreenPrinter('n1-n13', c)
|
||||
p.set_output('n3', 'Upload: 67%')
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -17,4 +17,4 @@
|
||||
|
||||
|
||||
class Types(object):
|
||||
text, json, filehandle = range(3)
|
||||
text, json = range(2)
|
||||
|
||||
@@ -15,19 +15,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
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
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
|
||||
try:
|
||||
@@ -40,72 +30,6 @@ try:
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
class iovec(ctypes.Structure): # from uio.h
|
||||
_fields_ = [('iov_base', ctypes.c_void_p),
|
||||
('iov_len', ctypes.c_size_t)]
|
||||
|
||||
|
||||
iovec_ptr = ctypes.POINTER(iovec)
|
||||
|
||||
|
||||
class cmsghdr(ctypes.Structure): # also from bits/socket.h
|
||||
_fields_ = [('cmsg_len', ctypes.c_size_t),
|
||||
('cmsg_level', ctypes.c_int),
|
||||
('cmsg_type', ctypes.c_int)]
|
||||
|
||||
@classmethod
|
||||
def init_data(cls, cmsg_len, cmsg_level, cmsg_type, cmsg_data):
|
||||
Data = ctypes.c_ubyte * ctypes.sizeof(cmsg_data)
|
||||
class _flexhdr(ctypes.Structure):
|
||||
_fields_ = cls._fields_ + [('cmsg_data', Data)]
|
||||
|
||||
datab = Data(*bytearray(cmsg_data))
|
||||
return _flexhdr(cmsg_len=cmsg_len, cmsg_level=cmsg_level,
|
||||
cmsg_type=cmsg_type, cmsg_data=datab)
|
||||
|
||||
|
||||
def CMSG_LEN(length):
|
||||
sizeof_cmshdr = ctypes.sizeof(cmsghdr)
|
||||
return ctypes.c_size_t(CMSG_ALIGN(sizeof_cmshdr).value + length)
|
||||
|
||||
|
||||
SCM_RIGHTS = 1
|
||||
|
||||
|
||||
class msghdr(ctypes.Structure): # from bits/socket.h
|
||||
_fields_ = [('msg_name', ctypes.c_void_p),
|
||||
('msg_namelen', ctypes.c_uint),
|
||||
('msg_iov', ctypes.POINTER(iovec)),
|
||||
('msg_iovlen', ctypes.c_size_t),
|
||||
('msg_control', ctypes.c_void_p),
|
||||
('msg_controllen', ctypes.c_size_t),
|
||||
('msg_flags', ctypes.c_int)]
|
||||
|
||||
|
||||
def CMSG_ALIGN(length): # bits/socket.h
|
||||
ret = (length + ctypes.sizeof(ctypes.c_size_t) - 1
|
||||
& ~(ctypes.sizeof(ctypes.c_size_t) - 1))
|
||||
return ctypes.c_size_t(ret)
|
||||
|
||||
|
||||
def CMSG_SPACE(length): # bits/socket.h
|
||||
ret = CMSG_ALIGN(length).value + CMSG_ALIGN(ctypes.sizeof(cmsghdr)).value
|
||||
return ctypes.c_size_t(ret)
|
||||
|
||||
|
||||
class ClientFile(object):
|
||||
def __init__(self, name, mode, fd):
|
||||
self.fileobject = os.fdopen(fd, mode)
|
||||
self.filename = name
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
recvmsg = libc.recvmsg
|
||||
recvmsg.argtypes = [ctypes.c_int, ctypes.POINTER(msghdr), ctypes.c_int]
|
||||
recvmsg.restype = ctypes.c_int
|
||||
sendmsg = libc.sendmsg
|
||||
sendmsg.argtypes = [ctypes.c_int, ctypes.POINTER(msghdr), ctypes.c_int]
|
||||
sendmsg.restype = ctypes.c_size_t
|
||||
|
||||
def decodestr(value):
|
||||
ret = None
|
||||
try:
|
||||
@@ -141,7 +65,7 @@ def _unicode_list(currlist):
|
||||
_unicode_list(currlist[i])
|
||||
|
||||
|
||||
def send(handle, data, filehandle=None):
|
||||
def send(handle, data):
|
||||
if isinstance(data, unicode):
|
||||
try:
|
||||
data = data.encode('utf-8')
|
||||
@@ -169,27 +93,9 @@ def send(handle, data, filehandle=None):
|
||||
if tl > 16777215:
|
||||
raise Exception("JSON data exceeds protocol limits")
|
||||
# xor in the type (0b1 << 24)
|
||||
if filehandle is None:
|
||||
tl |= 16777216
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
handle.sendall(sdata)
|
||||
else:
|
||||
tl |= (2 << 24)
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
cdtype = ctypes.c_ubyte * len(sdata)
|
||||
cdata = cdtype.from_buffer(bytearray(sdata))
|
||||
ciov = iovec(iov_base=ctypes.addressof(cdata),
|
||||
iov_len=ctypes.c_size_t(ctypes.sizeof(cdata)))
|
||||
fd = ctypes.c_int(filehandle)
|
||||
cmh = cmsghdr.init_data(
|
||||
cmsg_len=CMSG_LEN(
|
||||
ctypes.sizeof(fd)), cmsg_level=socket.SOL_SOCKET,
|
||||
cmsg_type=SCM_RIGHTS, cmsg_data=fd)
|
||||
mh = msghdr(msg_name=None, msg_len=0, msg_iov=iovec_ptr(ciov),
|
||||
msg_iovlen=1, msg_control=ctypes.addressof(cmh),
|
||||
msg_controllen=ctypes.c_size_t(ctypes.sizeof(cmh)))
|
||||
sendmsg(handle.fileno(), mh, 0)
|
||||
|
||||
tl |= 16777216
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
handle.sendall(sdata)
|
||||
|
||||
def recvall(handle, size):
|
||||
rd = handle.recv(size)
|
||||
@@ -219,46 +125,12 @@ def recv(handle):
|
||||
datatype = (tl & 2130706432) >> 24 # grab 7 bits from near beginning
|
||||
if dlen == 0:
|
||||
return None
|
||||
if datatype == tlv.Types.filehandle:
|
||||
filehandles = array.array('i')
|
||||
rawbuffer = bytearray(2048)
|
||||
pkttype = ctypes.c_ubyte * 2048
|
||||
data = pkttype.from_buffer(rawbuffer)
|
||||
cmsgsize = CMSG_SPACE(ctypes.sizeof(ctypes.c_int)).value
|
||||
cmsgarr = bytearray(cmsgsize)
|
||||
cmtype = ctypes.c_ubyte * cmsgsize
|
||||
cmsg = cmtype.from_buffer(cmsgarr)
|
||||
cmsg.cmsg_level = socket.SOL_SOCKET
|
||||
cmsg.cmsg_type = SCM_RIGHTS
|
||||
cmsg.cmsg_len = CMSG_LEN(ctypes.sizeof(ctypes.c_int))
|
||||
iov = iovec()
|
||||
iov.iov_base = ctypes.addressof(data)
|
||||
iov.iov_len = 2048
|
||||
msg = msghdr()
|
||||
msg.msg_iov = ctypes.pointer(iov)
|
||||
msg.msg_iovlen = 1
|
||||
msg.msg_control = ctypes.addressof(cmsg)
|
||||
msg.msg_controllen = ctypes.sizeof(cmsg)
|
||||
select.select([handle], [], [])
|
||||
i = recvmsg(handle.fileno(), ctypes.pointer(msg), 0)
|
||||
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]))
|
||||
data = json.loads(bytes(data))
|
||||
return ClientFile(data['filename'], data['mode'], filehandles[0])
|
||||
else:
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
data += ndata
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
data += ndata
|
||||
if datatype == tlv.Types.text:
|
||||
return data
|
||||
elif datatype == tlv.Types.json:
|
||||
|
||||
@@ -7,13 +7,13 @@ Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
License: Apache2
|
||||
License: UNKNOWN
|
||||
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
|
||||
@@ -24,17 +24,17 @@ a confluent server.
|
||||
%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
|
||||
|
||||
|
||||
@@ -42,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
|
||||
|
||||
@@ -19,7 +19,6 @@ alias nodebmcreset='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
|
||||
alias nodeboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeboot'
|
||||
alias nodeconfig='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconfig'
|
||||
alias nodeconsole='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconsole'
|
||||
alias nodedeploy='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedeploy'
|
||||
alias nodedefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedefine'
|
||||
alias nodeeventlog='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeeventlog'
|
||||
alias nodefirmware='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodefirmware'
|
||||
@@ -37,7 +36,7 @@ alias nodereseat='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export C
|
||||
alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderun'
|
||||
alias nodesensors='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesensors'
|
||||
alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesetboot'
|
||||
alias nodestorage='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodestorage -i'
|
||||
alias nodestorage='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodestorage'
|
||||
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
|
||||
alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelicense'
|
||||
# Do not continue for non-bash shells, the rest of this sets up bash completion functions
|
||||
@@ -82,17 +81,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")
|
||||
@@ -102,7 +90,7 @@ _confluent_nodeidentify_completion()
|
||||
|
||||
_confluent_nodesetboot_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("default,cd,network,setup,hd,floppy,usb -h -b -p")
|
||||
COMP_CANDIDATES=("default,cd,network,setup,hd -h -b -p")
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
@@ -149,71 +137,6 @@ _confluent_nodefirmware_completion()
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_osimage_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 2 ]; then
|
||||
COMPREPLY=($(compgen -W "initialize import updateboot rebase" -- ${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' ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'updateboot' -o ${CMPARGS[1]} == 'rebase' ]; then
|
||||
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_imgutil_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 2 ]; then
|
||||
COMPREPLY=($(compgen -W "build exec unpack pack capture" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'build' ]; then
|
||||
COMPREPLY=($(compgen -W "-s $(ls /var/lib/confluent/distributions)" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'unpack' ]; then
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "-s $(ls /var/lib/confluent/public/os)" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
compopt -o dirnames
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'pack' ]; then
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
compopt -o dirnames
|
||||
fi
|
||||
COMPREPLY=()
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'capture' ]; then
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
_confluent_nn_completion
|
||||
return
|
||||
fi
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'exec' ]; then
|
||||
compopt -o dirnames
|
||||
COMPREPLY=($(compgen -W "-v" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
}
|
||||
_confluent_nodedeploy_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodeshell_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
@@ -279,23 +202,13 @@ _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
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
@@ -313,8 +226,6 @@ _confluent_nr_completion()
|
||||
CMPARGS=($COMP_LINE)
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
@@ -347,30 +258,15 @@ _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
|
||||
|
||||
@@ -3,7 +3,7 @@ collate(1) -- Organize text input by node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | collate [-a] [-b] [-d] [-w] [-g] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -21,41 +21,30 @@ node and group names sorted alphanumerically.
|
||||
when output differs along a telling group boundary. For example, if
|
||||
output suggests a large number of nodes are unreachable, abbreviate
|
||||
showing 'rack1' as being unreachable may make more obvious a possible cause.
|
||||
|
||||
* `-b BASE`, `--base=BASE`:
|
||||
Use given node as reference for comparison when using
|
||||
-d, instead of using the most common result
|
||||
|
||||
* `-d`, `--diff`:
|
||||
Express all but the most common result group in terms of diff from
|
||||
the most common result group
|
||||
|
||||
|
||||
* `-l`, `--log`:
|
||||
Save output per node to individual log files, replacing {node} in the name
|
||||
with the nodename of each
|
||||
|
||||
* `-w`, `--watch`:
|
||||
Update results dynamically as data becomes available, rather than
|
||||
waiting for the command to fully complete.
|
||||
|
||||
* `-g`, `--groupcount`:
|
||||
Show count of output groups rather than the actual
|
||||
output
|
||||
|
||||
|
||||
* `-s`, `--skipcommon`:
|
||||
Suppress printing of the most common result text group. This is used to
|
||||
focus on stray output against a well known and expected result.
|
||||
|
||||
* `-c`, `--count`:
|
||||
Print a count of the number of nodes in an output group under the
|
||||
noderange.
|
||||
noderange.
|
||||
|
||||
* `-r`, `--reverse`:
|
||||
Rather than starting with most common to least common, start with
|
||||
the least common and print the most common last.
|
||||
|
||||
* `-l LOG`, `--log=LOG`:
|
||||
Save output per node to individual log files, replacing {node} in the name
|
||||
with the nodename of each
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
* `-r`, `--reverse`:
|
||||
Rather than starting with most common to least common, start with
|
||||
the least common and print the most common last.
|
||||
|
||||
## EXAMPLES
|
||||
* Organizing power state of multiple nodes:
|
||||
|
||||
@@ -7,7 +7,6 @@ collective(1) -- Check and manage a confluent collective
|
||||
`collective join <server> [-i TOKEN]`
|
||||
`collective show`
|
||||
`collective gencert`
|
||||
`collective delete`
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
@@ -37,9 +36,6 @@ on circumstances.
|
||||
|
||||
* `-i`:
|
||||
Provide the token as an argument rather than interactively.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Inviting a server called mgt2:
|
||||
|
||||
@@ -15,21 +15,6 @@ be mostly an aid for developing client software, with
|
||||
day to day administration generally being easier with
|
||||
the various function specific commands.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-s SERVER:PORT`, `--server=SERVER:PORT`:
|
||||
Confluent instance to connect to
|
||||
|
||||
* `-c PATH`, `--control=PATH`:
|
||||
Path to offer terminal control
|
||||
|
||||
* `-m MINTIME`, `--mintime=MINTIME`:
|
||||
Minimum time to run or else pause for input (used to
|
||||
keep a terminal from closing quickly on error)
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## COMMANDS
|
||||
|
||||
The CLI may be navigated by shell commands and some other
|
||||
|
||||
@@ -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.
|
||||
|
||||
## 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`
|
||||
|
||||
@@ -3,7 +3,8 @@ confluentdbutil(8) -- Backup or restore confluent database
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`confluentdbutil [options] [dump|restore] <path>`
|
||||
|
||||
`confluentdbutil [options] <dump|restore> <path>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -15,28 +16,11 @@ the json files (password protected, removed from the files, or unprotected).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-p PASSWORD`, `--password=PASSWORD`:
|
||||
* `-p`, `--password`:
|
||||
If specified, information such as usernames and passwords will be encrypted
|
||||
using the given password.
|
||||
|
||||
* `-i`, `--interactivepassword`:
|
||||
Prompt for password.
|
||||
|
||||
* `-r`, `--redact`:
|
||||
Indicates to replace usernames and passwords with a dummy string rather
|
||||
than included.
|
||||
|
||||
* `-u`, `--unprotected`:
|
||||
The keys.json file will include the encryption keys without any protection.
|
||||
|
||||
* `-s`, `--skipkeys`:
|
||||
This specifies to dump the encrypted data without
|
||||
dumping the keys needed to decrypt it. This is
|
||||
suitable for an automated incremental backup, where an
|
||||
earlier password protected dump has a protected
|
||||
keys.json file, and only the protected data is needed.
|
||||
keys do not change and as such they do not require
|
||||
incremental backup.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
The keys.json file will include the encryption keys without any protection.
|
||||
@@ -34,37 +34,18 @@ For the `groups` attribute, it is possible to add a group by doing
|
||||
`groups,=<newgroup>` and to remove by doing `groups^=<oldgroup>`
|
||||
|
||||
Note that `nodeattrib <group>` will likely not provide the expected behavior.
|
||||
See nodegroupattrib(8) command on how to manage attributes on a group level. Running
|
||||
nodeattrib on a group will simply set node-specific attributes on each individual
|
||||
member of the group.
|
||||
|
||||
Additionally, if wanting to change from a node level attribute value to inherit from a
|
||||
member group, it is required to use '-c' to clear the attribute. Conversely, assigning
|
||||
to a blank value will allow masking a group defined attribute with an empty value.
|
||||
See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Annotate inherited and expression based attributes to show their base value.
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes.
|
||||
|
||||
Clear specified nodeattributes
|
||||
* `-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
|
||||
|
||||
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
|
||||
* `-p`, `--prompt`:
|
||||
Request interactive prompting to provide values rather than the command line
|
||||
or environment variables.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Prompt if trying to set attributes on more than
|
||||
specified number of nodes.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
Request interactive prompting to provide values rather than the command line or environment variables.
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
@@ -83,12 +64,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`
|
||||
|
||||
@@ -9,15 +9,6 @@ nodebmcreset(8) -- Reset management controller
|
||||
|
||||
`nodebmcreset` allows you to reset the management controller of the 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:
|
||||
|
||||
@@ -3,7 +3,8 @@ nodeboot(8) -- Reboot a confluent node to a specific device
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeboot [options] <noderange> [default|cd|network|setup|hd]`
|
||||
`nodeboot <noderange>`
|
||||
`nodeboot [options] <noderange>` [default|cd|network|setup|hd]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -24,13 +25,6 @@ device without inducing a reboot, see the `nodesetboot` command.
|
||||
* `-p`, `--persist`:
|
||||
For a system that supports it, mark the boot override to persist rather than
|
||||
be a one time change. Many systems do not support this functionality.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to boot, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
* `default`:
|
||||
Request a normal default boot with no particular device override
|
||||
|
||||
@@ -3,7 +3,8 @@ nodeconfig(8) -- Show or change node configuration
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeconfig [options] <noderange> [setting|setting=value]`
|
||||
`nodeconfig <noderange> [options] [<configuration>..]`
|
||||
`nodeconfig <noderange> [options] [<configuration=value>..]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,12 +14,6 @@ running configuration on the node firmware. Calling without '=' will show the
|
||||
current value, and '=' will change the value. Network information can be
|
||||
given as a node expression, as documented in the man page for nodeattribexpressions(5).
|
||||
|
||||
Note that when using nodeconfig to submit changes, it will exit when the change
|
||||
is accepted, but the endpoint may not have fully processed it. Doing a show
|
||||
immediately after doing a set may reflect older information. Also, if changing
|
||||
BIOS/UEFI settings, the change may appear in output, but generally won't
|
||||
actually be in effect until a reboot.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`, `--comparedefault`:
|
||||
@@ -26,13 +21,6 @@ actually be in effect until a reboot.
|
||||
no configuration values are specified, it will show only those that differ.
|
||||
If combined with `-x`, will show all differing values except those indicated
|
||||
by `-x`
|
||||
|
||||
* `-b settings.batch`, `--batch=settings.batch`:
|
||||
Provide arguments as lines of a file, rather than the command line.
|
||||
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
|
||||
* `-e`, `--extra`:
|
||||
Read settings that are generally not needed, but may be slow to retrieve.
|
||||
@@ -43,21 +31,18 @@ actually be in effect until a reboot.
|
||||
Rather than listing only the specified configuration parameters, list all
|
||||
attributes except for the specified ones
|
||||
|
||||
* `-a`, `--advanced`:
|
||||
Include advanced settings, which are normally not intended to be used
|
||||
without direction from the relevant server vendor.
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
|
||||
* `-r COMPONENT`, `--restoredefault=COMPONENT`:
|
||||
* `-r`, `--restoredefault`:
|
||||
Request that the specified component of the targeted nodes will have its
|
||||
configuration reset to default. Currently the only component implemented
|
||||
is uefi.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to configure, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
* `-b`, '--batch':
|
||||
Provide arguments as lines of a file, rather than the command line.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
* Showing the current IP configuration of noderange BMC/IMM/XCC:
|
||||
|
||||
@@ -22,31 +22,6 @@ a new session.
|
||||
Use tmux to arrange consoles of the given noderange into a tiled layout on
|
||||
the terminal screen
|
||||
|
||||
* `-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.
|
||||
|
||||
* `-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.
|
||||
|
||||
## ESCAPE SEQUENCE COMMANDS
|
||||
|
||||
While connected to a console, a number of commands may be performed through escape
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
nodedeploy(8) -- Request preparation and/or initiating a node deployment
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodedeploy [-h] [-c] [-n] [-p] [-m MAXNODES] <noderange> [profile]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodedeploy** configures attributes and interacts with BMC devices as appropriate
|
||||
to change the operating system on the noderange to that indicated by `profile`. Tab completion
|
||||
is supported under bash to help list and select a profile name. `-n` indicates that it
|
||||
will be a network based deployment (either HTTP or PXE) and `-p` suppresses any activity
|
||||
that requires the BMC (e.g. setting the next boot device and rebooting the system). Currently
|
||||
**nodedeploy** only supports `-n` style deployment flows. Without options it shows the current
|
||||
deployment status.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Remove any pending deployment action
|
||||
|
||||
* `-n`, `--network`:
|
||||
Prepare for either an HTTP or PXE based deployment, setting boot device to network and rebooting unless `-p` is specified.
|
||||
|
||||
* `-p`, `--prepareonly`:
|
||||
Prepare the network services for deployment, but do not interact with BMCs. This is intended for scenarios where
|
||||
the boot device control and server restart will be handled outside of confluent.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specifiy a maximum nodes to be deployed.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Begin the instalalation of a profile of CentOS 8.2:
|
||||
`# nodedeploy d4 -n centos-8.2-x86_64-default`
|
||||
`d4: network`
|
||||
`d4: reset`
|
||||
|
||||
* Check current deployment state of nodes:
|
||||
`# nodedeploy d4`
|
||||
`d4: pending: centos-8.2-x86_64-default (node authentication armed)`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,9 +23,6 @@ data may be filtered by various parameters, as denoted in the options below.
|
||||
**nodediscover assign** performs manual discovery, assigning an entry to a node
|
||||
identity or, using `-i`, using a csv file to assign nodes all at once. For
|
||||
example, a spreadsheet of serial numbers to desired node names could be used.
|
||||
Note that if you see that the host is unreachable, it may be due to the IP
|
||||
address on the endpoint having changed since last detected. In such a case, it
|
||||
may help to **clear** and try **assign** again.
|
||||
|
||||
**nodediscover rescan** requests the server to do an active sweep for new
|
||||
devices. Generally every effort is made to passively detect devices as they
|
||||
@@ -38,12 +32,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
|
||||
|
||||
@@ -3,22 +3,14 @@ nodeeventlog(8) -- Pull eventlog from confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeeventlog [options] <noderange> [clear]`
|
||||
`nodeeventlog <noderange>`
|
||||
`nodeeventlog <noderange> [clear]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodeeventlog` pulls and optionally clears the event log from the requested
|
||||
noderange.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to clear if clearing log, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Pull the event log from n2 and n3:
|
||||
`# nodeeventlog n2,n3`
|
||||
|
||||
@@ -3,7 +3,9 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodefirmware <noderange> [list][update [--backup <file>]]|[<components>]`
|
||||
`nodefirmware <noderange>`
|
||||
`nodefirmware <noderange> list|<components>|core`
|
||||
`nodefirmware <noderange> update [--backup] <filename>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -24,18 +26,6 @@ the out of band facilities. Firmware updates can end in one of three states:
|
||||
* `pending`: The firmware update process has completed, but the firmware will not be active until the relevant component next resets. Generally speaking, for UEFI update the system will need a reboot, and for BMC updates, the `nodebmcreset` command will begin the process to activate the firmware.
|
||||
* `complete`: The firmware update process has completed and activation has proceeded. Note that while the activation process has commenced, the component may still be in the process of rebooting when nodefirmware exits.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--backup`:
|
||||
Target a backup bank rather than primary
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
When updating, prompt if more than the specified number of servers will
|
||||
be affected
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Pull firmware from a node:
|
||||
|
||||
@@ -25,20 +25,8 @@ the attributes are set on the node versus a group to which a node belongs.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Show information about how attributes inherited
|
||||
|
||||
* `-e`, `--environment`:
|
||||
Set attributes, but from environment variable of same name
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes.
|
||||
|
||||
* `-p`, `--prompt`:
|
||||
Prompt for attribute values interactively
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -3,22 +3,15 @@ nodelicense(8) -- Manage license keys on BMC
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodelicense <noderange> [list][install <license_filename>|save <directory>|delete <license_feature_name>]`
|
||||
`nodelicense <noderange> [list|install <filename>|delete <license>|save <directory>]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodelicense` manages license keys on supported BMCs. Without an argument, the command
|
||||
lists currently installed license. Using `delete` will remove the specified license name
|
||||
from the BMC. The `save` subcommand will take the passed directory (which may be in the form
|
||||
from th eBMC. The `save` subcommand will take the passed directory (which may be in the form
|
||||
of /path/to/{node}/ to have the node name substituted for each node) and back up installed licenses
|
||||
to that directory. The `install` command will take the specified filename and install. The filename
|
||||
argument may be of the form xcc_fod_0034_7X21{id.serial}.key to have the serial number substituted
|
||||
to allow unique licenses to be specified in a single command.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to delete licenses from, prompting if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
@@ -34,8 +34,8 @@ the host platform attempts.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
|
||||
## EXAMPLES
|
||||
* Listing currently mounted media:
|
||||
|
||||
@@ -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,7 +3,8 @@ nodepower(8) -- Check or change power state of confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodepower [options] <noderange> ([status|on|off|shutdown|boot|reset])`
|
||||
`nodepower <noderange>`
|
||||
`nodepower <noderange> [-p] [on|off|boot|shutdown|reset|status]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -24,21 +25,11 @@ 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
|
||||
|
||||
* `-p`, `--showprevious`:
|
||||
* `-p`, '--showprevious':
|
||||
Show previous power state for all directives that may change power state.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to change power state, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Get power state of nodes n1 through n4:
|
||||
|
||||
@@ -9,14 +9,6 @@ noderemove(8) -- Remove nodes from the confluent management service
|
||||
|
||||
`noderemove` simply removes the given noderange from the confluent database.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to delete, prompting if over the
|
||||
threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -30,3 +22,4 @@ noderemove(8) -- Remove nodes from the confluent management service
|
||||
`r2u3: deleted`
|
||||
`r2u2: deleted`
|
||||
`r2u1: deleted`
|
||||
|
||||
|
||||
@@ -11,16 +11,9 @@ nodereseat(8) -- Request a reseat of a node
|
||||
node's slot. This should be equivalent to removing the system entirely from
|
||||
the chassis and putting it back in, but without actually having to do so.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to reseat, prompting if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Reseating the node `s1`:
|
||||
`# nodereseat s1`
|
||||
`s1: Reseat successful`
|
||||
|
||||
|
||||
@@ -12,14 +12,10 @@ noderange. This will present progress as percentage for all nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-f COUNT`, `-c COUNT`, `--count=COUNT`:
|
||||
* `-m`:
|
||||
Specify maximum number of nodes for noderange max.
|
||||
|
||||
* `-c`:
|
||||
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.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to run rsync to, prompting if over the
|
||||
threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
active count completes.
|
||||
|
||||
@@ -3,7 +3,7 @@ noderun(8) -- Run arbitrary commands per node in a noderange
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`noderun [options] <noderange> <command expression>`
|
||||
`noderun <noderange> <command string>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,21 +13,6 @@ nodeattribexpressions(5) are expanded prior to execution of the command. For
|
||||
noderun, the commands are locally executed. To execute commands on the nodes
|
||||
themselves, see nodeshell(8).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-f COUNT`, `-c COUNT`, `--count=COUNT`:
|
||||
Number of commands to run at a time
|
||||
|
||||
* `-n`, `--nonodeprefix`:
|
||||
Do not prefix output with node names
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to run the command with, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Run ping against nodes n1 through n4:
|
||||
|
||||
@@ -29,10 +29,6 @@ interval of 1 second is used.
|
||||
Perform the specified number of readings, waiting `-i` indicated interval or 1 second if not
|
||||
otherwise indicated.
|
||||
|
||||
* `-s`, `--skipnumberless`:
|
||||
Return only sensors that provide numeric data (e.g. temperature, fanspeed),
|
||||
ignoring sensors that do not provide numeric data.
|
||||
|
||||
## EXAMPLES
|
||||
* Retrieving all temperature related sensors from one system
|
||||
`# nodesensors n1 temperature`
|
||||
|
||||
@@ -2,8 +2,9 @@ nodesetboot(8) -- Check or set next boot device for noderange
|
||||
====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd|usb|floppy]`
|
||||
|
||||
`nodesetboot <noderange>`
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -20,8 +21,6 @@ to use `nodesetboot <noderange> setup` and then initiate a reboot from within
|
||||
the operating system with ssh or similar rather than using the remote hardware
|
||||
control.
|
||||
|
||||
Running the command with no target queries the current setting.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--bios`:
|
||||
@@ -34,13 +33,6 @@ Running the command with no target queries the current setting.
|
||||
|
||||
* `-u`, `--uefi`:
|
||||
This flag does nothing, it is for command compatibility with xCAT's rsetboot
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to modify next boot device, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
* `default`:
|
||||
Request a normal default boot with no particular device override
|
||||
@@ -50,14 +42,6 @@ Running the command with no target queries the current setting.
|
||||
remote media mounted as CD/DVD, and detachable hard disks drives such as usb
|
||||
key devices.
|
||||
|
||||
* `floppy`:
|
||||
Request boot from floppy. Generally speaking firmware uses this to mean a USB
|
||||
flash drive or similar (whether virtual or physical).
|
||||
|
||||
* `usb`:
|
||||
Request boot from usb. Generally speaking firmware uses this to mean a USB
|
||||
flash drive or similar (whether virtual or physical).
|
||||
|
||||
* `network`:
|
||||
Request boot to network
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ nodeshell(8) -- Execute command on many nodes in a noderange through ssh
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeshell [options] <noderange> <command to execute on each node>`
|
||||
`nodeshell <noderange> [options] <command to execute on each node>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,29 +13,9 @@ nodeattribexpressions(5). `nodeshell` provides stdout as stdout and stderr
|
||||
as stderr, unlike psh which combines all stdout and stderr into stdout.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-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
|
||||
|
||||
* `-n`, `--nonodeprefix`
|
||||
Do not prefix output with node names
|
||||
|
||||
* `-p PORT`, `--port=PORT`
|
||||
Specify a custom port for ssh
|
||||
|
||||
* `-s SUBSTITUTION`, `--substitutename=SUBSTITITUTION`
|
||||
Specify a substitution name instead of the nodename. If no {} are in the substitution,
|
||||
it is considered to be an append. For example, '-s -ib' would produce 'node1-ib' from 'node1'.
|
||||
Full expression syntax is supported, in which case the substitution is considered to be the entire
|
||||
new name. {node}-ib would be equivalent to -ib. For example, nodeshell -s {bmc} node1
|
||||
would ssh to the BMC instead of the node.
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -3,15 +3,12 @@ nodestorage(8) -- Examine/Modify storage configuration of a node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodestorage <noderange> [show|create|delete|diskset] [hotspare|jbod|unconfigured] [options]`
|
||||
`nodestorage <noderange> [show|create|delete] [options]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodestorage` provides access to the remote storage configuration of
|
||||
the noderange. The `show` subcommand will show current storage configuration,
|
||||
`create` can be used to create new arrays or volumes, `delete` can be used to
|
||||
remove volumes and arrays, and `diskset` can modify the usage of disks
|
||||
indicated by `-d` to either be `unconfigured`, `jbod`, or `hotspare`.
|
||||
the noderange.
|
||||
|
||||
## OPTIONS
|
||||
* `-r` **RAIDLEVEL**, `--raidlevel`=**RAIDLEVEL**:
|
||||
@@ -32,15 +29,6 @@ indicated by `-d` to either be `unconfigured`, `jbod`, or `hotspare`.
|
||||
volumes, or selecting a volume for delete. Default
|
||||
behavior is to use implementation provided default
|
||||
|
||||
* `-z` **STRIPSIZES**, `--stripsizes`=**STRIPSIZES**:
|
||||
Comma separated list of stripsizes to use when creating volumes.
|
||||
This value is in kilobytes. The default behavior is to allow the
|
||||
storage controller to decide
|
||||
|
||||
* `-m` **MAXNODES**, `--maxnodes`=**MAXNODES**:
|
||||
Specify a maximum number of nodes to configure storage on, prompting
|
||||
if over the threshold
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Deleting the volume `somedata`:
|
||||
|
||||
@@ -19,15 +19,6 @@ running in collective mode.
|
||||
Note that due to vendor filename requirements, any filename may have vendor
|
||||
specific suffixes added to any file produced.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to download diagnostic data from, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Download support data from a single node to a specific filename
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
osdeploy(8) --- Configure general OS deployment facilities of confluent
|
||||
==========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`osdeploy import <iso>`
|
||||
`osdeploy updateboot <profile>`
|
||||
`osdeploy rebase <profile>`
|
||||
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l] [-a]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**osdeploy** manages the facilities and os deployment content of a confluent server. The
|
||||
`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`.
|
||||
@@ -1,49 +0,0 @@
|
||||
stats(8) -- Common basic statistics on typical numeric data in output
|
||||
==============================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | stats [-c N] [-d D] [-x|-g|-t|-o image.png] [-s N] [-v] [-b N]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
The **stats** command helps analyze common numerical data such as performance numbers
|
||||
or temperatures or any other numerical value.
|
||||
|
||||
By default it looks for the last numerical output on the first line to identify the numerical column
|
||||
and analyze that number. This can be overriden by **-c COLUMN** to indicate a column. By default,
|
||||
whitespace and commas are treated to delimit columns, and **-d DELIMITER** can be used to override.
|
||||
|
||||
By default it outputs basic statistics, but a histogram is available either text or through X11 output
|
||||
or sixel or output to an image file depending on whether **-x**, **-g**, **-t*, or **-o image.png** is
|
||||
used.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c N`:
|
||||
Select column number. Defaults to last column that appears numeric
|
||||
|
||||
* `-d D`:
|
||||
Specify a custom column delimiter
|
||||
|
||||
* `-x`:
|
||||
Output in Sixel format (supported by mlterm and PuTTY, among others)
|
||||
|
||||
* `-g`:
|
||||
Try to open histogram as an X window
|
||||
|
||||
* `-t`:
|
||||
Output histogram as bars rendered by =
|
||||
|
||||
* `-o image.png`:
|
||||
Write graphical histogram to image.png.
|
||||
|
||||
* `-s N`:
|
||||
Ignore specified number of lines as header content before processing numbers
|
||||
|
||||
* `-v`:
|
||||
Treat value before : on each line as a label, and show which labels belong to which histogram buckets.
|
||||
|
||||
* `-b N`:
|
||||
Specify a custom number of buckets for histogram. The default is 10.
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
VERSION=`git describe|cut -d- -f 1`
|
||||
NUMCOMMITS=`git describe|cut -d- -f 2`
|
||||
if [ "$NUMCOMMITS" != "$VERSION" ]; then
|
||||
VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3`
|
||||
fi
|
||||
sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy.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 fedorabuilder make -C /buildutils
|
||||
cd utils
|
||||
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/
|
||||
mv confluent_el9bin.tar.xz ~/rpmbuild/SOURCES/
|
||||
rm -rf el9bin
|
||||
rpmbuild -ba confluent_osdeploy.spec
|
||||
@@ -1,460 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
try:
|
||||
import http.client as client
|
||||
except ImportError:
|
||||
import httplib as client
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import glob
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import subprocess
|
||||
import ssl
|
||||
import sys
|
||||
import struct
|
||||
import time
|
||||
|
||||
class InvalidApiKey(Exception):
|
||||
pass
|
||||
|
||||
def msg_align(len):
|
||||
return (len + 3) & ~3
|
||||
|
||||
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:
|
||||
cryptname = 'libcrypt.so.1'
|
||||
c_libcrypt = ctypes.CDLL(cryptname)
|
||||
c_crypt = c_libcrypt.crypt
|
||||
c_crypt.argtypes = (ctypes.c_char_p, ctypes.c_char_p)
|
||||
c_crypt.restype = ctypes.c_char_p
|
||||
|
||||
|
||||
def get_my_addresses():
|
||||
nlhdrsz = struct.calcsize('IHHII')
|
||||
ifaddrsz = struct.calcsize('BBBBI')
|
||||
# RTM_GETADDR = 22
|
||||
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
|
||||
nlhdr = struct.pack('IHHII', nlhdrsz + ifaddrsz, 22, 0x301, 0, 0)
|
||||
# ifaddrmsg struct: u8 family, u8 prefixlen, u8 flags, u8 scope, u32 index
|
||||
ifaddrmsg = struct.pack('BBBBI', 0, 0, 0, 0, 0)
|
||||
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
|
||||
s.bind((0, 0))
|
||||
s.sendall(nlhdr + ifaddrmsg)
|
||||
addrs = []
|
||||
while True:
|
||||
pdata = s.recv(65536)
|
||||
v = memoryview(pdata)
|
||||
if struct.unpack('H', v[4:6])[0] == 3: # netlink done message
|
||||
break
|
||||
while len(v):
|
||||
length, typ = struct.unpack('IH', v[:6])
|
||||
if typ == 20:
|
||||
fam, plen, _, scope, ridx = struct.unpack('BBBBI', v[nlhdrsz:nlhdrsz+ifaddrsz])
|
||||
if scope in (253, 0):
|
||||
rta = v[nlhdrsz+ifaddrsz:length]
|
||||
while len(rta):
|
||||
rtalen, rtatyp = struct.unpack('HH', rta[:4])
|
||||
if rtalen < 4:
|
||||
break
|
||||
if rtatyp == 1:
|
||||
addrs.append((fam, rta[4:rtalen], plen, ridx))
|
||||
rta = rta[msg_align(rtalen):]
|
||||
v = v[msg_align(length):]
|
||||
return addrs
|
||||
|
||||
|
||||
def scan_confluents():
|
||||
srvs = {}
|
||||
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
s6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
s6.bind(('::', 1900))
|
||||
s4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s4.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
s4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
s4.bind(('0.0.0.0', 1900))
|
||||
doneidxs = set([])
|
||||
msg = 'M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:'
|
||||
with open('/etc/confluent/confluent.deploycfg') as dcfg:
|
||||
for line in dcfg.read().split('\n'):
|
||||
if line.startswith('confluent_uuid:'):
|
||||
confluentuuid = line.split(': ')[1]
|
||||
msg += '/confluentuuid=' + confluentuuid
|
||||
break
|
||||
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()
|
||||
msg += '/mac=' + hwaddr
|
||||
msg = msg.encode('utf8')
|
||||
for addr in get_my_addresses():
|
||||
if addr[0] == socket.AF_INET6:
|
||||
if addr[-1] in doneidxs:
|
||||
continue
|
||||
try:
|
||||
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, addr[-1])
|
||||
except TypeError:
|
||||
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, addr[-1].tobytes())
|
||||
try:
|
||||
s6.sendto(msg, ('ff02::c', 1900))
|
||||
except OSError:
|
||||
pass
|
||||
doneidxs.add(addr[-1])
|
||||
elif addr[0] == socket.AF_INET:
|
||||
try:
|
||||
s4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, addr[1])
|
||||
except TypeError:
|
||||
s4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, addr[1].tobytes())
|
||||
try:
|
||||
s4.sendto(msg, ('239.255.255.250', 1900))
|
||||
except OSError:
|
||||
pass
|
||||
r = select.select((s4, s6), (), (), 4)
|
||||
srvlist = []
|
||||
if r:
|
||||
r = r[0]
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
rsp = rsp.split(b'\r\n')
|
||||
current = None
|
||||
for line in rsp:
|
||||
if line.startswith(b'NODENAME: '):
|
||||
current = {}
|
||||
elif line.startswith(b'DEFAULTNET: 1'):
|
||||
current['isdefault'] = True
|
||||
elif line.startswith(b'MGTIFACE: '):
|
||||
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)
|
||||
r = select.select((s4, s6), (), (), 2)
|
||||
if r:
|
||||
r = r[0]
|
||||
return srvlist, srvs
|
||||
|
||||
|
||||
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)
|
||||
for addrinfo in socket.getaddrinfo(mgr, 13001, 0, socket.SOCK_STREAM):
|
||||
try:
|
||||
clisock = socket.socket(addrinfo[0], addrinfo[1])
|
||||
clisock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
if addrinfo[0] == socket.AF_INET:
|
||||
cliaddr = ('0.0.0.0', 302)
|
||||
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':
|
||||
raise Exception('Unrecognized credential banner')
|
||||
hellostr = bytearray([1, len(nodename)]) + bytearray(nodename.encode('utf8')) + bytearray(b'\x00\x00')
|
||||
clisock.send(hellostr)
|
||||
rsp = bytearray(clisock.recv(2))
|
||||
if not rsp:
|
||||
continue
|
||||
if rsp[0] == 128:
|
||||
continue
|
||||
if rsp[0] == 2:
|
||||
echotoken = clisock.recv(rsp[1])
|
||||
clisock.recv(2) # drain \x00\x00
|
||||
clisock.send(bytes(bytearray([3, rsp[1]])))
|
||||
clisock.send(echotoken)
|
||||
clisock.send(bytes(bytearray([4, len(crypted)])))
|
||||
clisock.send(crypted)
|
||||
clisock.send(b'\x00\x00')
|
||||
rsp = bytearray(clisock.recv(2))
|
||||
if rsp[0] == 5:
|
||||
return newpass.decode('utf8')
|
||||
finally:
|
||||
clisock.close()
|
||||
return ''
|
||||
|
||||
|
||||
def get_apikey(nodename, hosts, errout=None):
|
||||
apikey = ""
|
||||
if os.path.exists('/etc/confluent/confluent.apikey'):
|
||||
apikey = open('/etc/confluent/confluent.apikey').read().strip()
|
||||
if apikey:
|
||||
return apikey
|
||||
while not apikey:
|
||||
for host in hosts:
|
||||
try:
|
||||
apikey = get_net_apikey(nodename, host)
|
||||
except OSError:
|
||||
apikey = None
|
||||
if apikey:
|
||||
break
|
||||
else:
|
||||
srvlist, _ = scan_confluents()
|
||||
for host in srvlist:
|
||||
try:
|
||||
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)
|
||||
time.sleep(10)
|
||||
with open('/etc/confluent/confluent.apikey', 'w+') as apiout:
|
||||
apiout.write(apikey)
|
||||
apikey = apikey.strip()
|
||||
os.chmod('/etc/confluent/confluent.apikey', 0o600)
|
||||
return apikey
|
||||
|
||||
class HTTPSClient(client.HTTPConnection, object):
|
||||
def __init__(self, usejson=False, port=443, host=None, errout=None, phmac=None):
|
||||
self.phmac = phmac
|
||||
self.errout = None
|
||||
if errout:
|
||||
self.errout = open(errout, 'w')
|
||||
self.errout.flush()
|
||||
self.stdheaders = {}
|
||||
mgtiface = None
|
||||
if usejson:
|
||||
self.stdheaders['ACCEPT'] = 'application/json'
|
||||
if host:
|
||||
self.hosts = [host]
|
||||
with open('/etc/confluent/confluent.info') as cinfo:
|
||||
info = cinfo.read().split('\n')
|
||||
for line in info:
|
||||
if line.startswith('NODENAME:'):
|
||||
node = line.split(' ')[1]
|
||||
self.stdheaders['CONFLUENT_NODENAME'] = node
|
||||
else:
|
||||
self.hosts = []
|
||||
info = open('/etc/confluent/confluent.info').read().split('\n')
|
||||
havedefault = '0'
|
||||
plainhost = ''
|
||||
for line in info:
|
||||
host = ''
|
||||
if line.startswith('NODENAME:'):
|
||||
node = line.split(' ')[1]
|
||||
self.stdheaders['CONFLUENT_NODENAME'] = node
|
||||
if line.startswith('MANAGER:') and not host:
|
||||
host = line.split(' ')[1]
|
||||
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)
|
||||
try:
|
||||
info = open('/etc/confluent/confluent.deploycfg').read().split('\n')
|
||||
except Exception:
|
||||
info = None
|
||||
if info:
|
||||
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:
|
||||
with open(phmac, 'r') as hmacin:
|
||||
self.stdheaders['CONFLUENT_CRYPTHMAC'] = hmacin.read()
|
||||
else:
|
||||
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, self.hosts, errout=self.errout)
|
||||
if mgtiface:
|
||||
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
|
||||
self.port = port
|
||||
self.host = None
|
||||
self.node = node
|
||||
host = self.check_connections()
|
||||
client.HTTPConnection.__init__(self, host, port)
|
||||
self.connect()
|
||||
|
||||
|
||||
def set_header(self, key, val):
|
||||
self.stdheaders[key] = val
|
||||
|
||||
def check_connections(self):
|
||||
foundsrv = None
|
||||
hosts = self.hosts
|
||||
for timeo in (0.1, 5):
|
||||
for host in hosts:
|
||||
try:
|
||||
addrinf = socket.getaddrinfo(host, self.port)[0]
|
||||
psock = socket.socket(addrinf[0])
|
||||
psock.settimeout(timeo)
|
||||
psock.connect(addrinf[4])
|
||||
foundsrv = host
|
||||
psock.close()
|
||||
break
|
||||
except OSError:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
break
|
||||
if not foundsrv:
|
||||
srvlist, srvs = scan_confluents()
|
||||
hosts = []
|
||||
for srv in srvlist:
|
||||
if srvs[srv].get('isdefault', False):
|
||||
hosts = [srv] + hosts
|
||||
else:
|
||||
hosts = hosts + [srv]
|
||||
for host in hosts:
|
||||
try:
|
||||
addrinf = socket.getaddrinfo(host, self.port)[0]
|
||||
psock = socket.socket(addrinf[0])
|
||||
psock.settimeout(timeo)
|
||||
psock.connect(addrinf[4])
|
||||
foundsrv = host
|
||||
psock.close()
|
||||
break
|
||||
except OSError:
|
||||
continue
|
||||
else:
|
||||
raise Exception('Unable to reach any hosts')
|
||||
return foundsrv
|
||||
|
||||
def connect(self):
|
||||
addrinf = socket.getaddrinfo(self.host, self.port)[0]
|
||||
psock = socket.socket(addrinf[0])
|
||||
psock.connect(addrinf[4])
|
||||
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:
|
||||
self.stdheaders['Host'] = '[{0}]'.format(host)
|
||||
else:
|
||||
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)
|
||||
|
||||
def grab_url(self, url, data=None, returnrsp=False):
|
||||
return self.grab_url_with_status(url, data, returnrsp)[1]
|
||||
|
||||
def grab_url_with_status(self, url, data=None, returnrsp=False):
|
||||
if data:
|
||||
method = 'POST'
|
||||
else:
|
||||
method = 'GET'
|
||||
authed = False
|
||||
while not authed:
|
||||
authed = True
|
||||
self.request(method, url, data, headers=self.stdheaders)
|
||||
rsp = self.getresponse()
|
||||
if rsp.status >= 200 and rsp.status < 300:
|
||||
if returnrsp:
|
||||
return rsp.status, rsp
|
||||
else:
|
||||
return rsp.status, rsp.read()
|
||||
if rsp.status == 401:
|
||||
authed = False
|
||||
rsp.read()
|
||||
with open('/etc/confluent/confluent.apikey', 'w+') as akfile:
|
||||
akfile.write('')
|
||||
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(
|
||||
self.node, [self.host], errout=self.errout)
|
||||
raise Exception(rsp.read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
data = None
|
||||
usejson = False
|
||||
if '-j' in sys.argv:
|
||||
usejson = True
|
||||
if len(sys.argv) == 1:
|
||||
HTTPSClient()
|
||||
sys.exit(0)
|
||||
try:
|
||||
outbin = sys.argv.index('-o')
|
||||
sys.argv.pop(outbin)
|
||||
outbin = sys.argv.pop(outbin)
|
||||
except ValueError:
|
||||
outbin = None
|
||||
try:
|
||||
waitfor = sys.argv.index('-w')
|
||||
sys.argv.pop(waitfor)
|
||||
waitfor = int(sys.argv.pop(waitfor))
|
||||
except ValueError:
|
||||
waitfor = None
|
||||
try:
|
||||
data = sys.argv.index('-d')
|
||||
sys.argv.pop(data)
|
||||
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)
|
||||
except ValueError:
|
||||
phmac = 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).grab_url(
|
||||
sys.argv[1], data, returnrsp=True)
|
||||
chunk = reader.read(16384)
|
||||
while chunk:
|
||||
outf.write(chunk)
|
||||
chunk = reader.read(16384)
|
||||
sys.exit(0)
|
||||
client = HTTPSClient(usejson, errout=errout, phmac=phmac)
|
||||
if waitfor:
|
||||
status = 201
|
||||
while status != waitfor:
|
||||
status, rsp = client.grab_url_with_status(sys.argv[1], data)
|
||||
sys.stdout.write(rsp.decode())
|
||||
elif checkonly:
|
||||
sys.stdout.write(client.check_connections())
|
||||
else:
|
||||
sys.stdout.write(client.grab_url(sys.argv[1], data).decode())
|
||||
@@ -1,357 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import shlex
|
||||
import subprocess
|
||||
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 type != '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 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]
|
||||
kv = ' '.join(shlex.split(cfg)).split('=', 1)
|
||||
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):
|
||||
def __init__(self, devtypes):
|
||||
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]])
|
||||
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:
|
||||
args.append(parm)
|
||||
args.append(bondcfg[parm])
|
||||
subprocess.check_call(['nmcli', 'c', 'm', team] + args)
|
||||
|
||||
def apply_configuration(self, cfg):
|
||||
cmdargs = {}
|
||||
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 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(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]
|
||||
if not cname:
|
||||
cname = iname
|
||||
u = self.uuidbyname.get(cname, None)
|
||||
cargs = []
|
||||
for arg in cmdargs:
|
||||
cargs.append(arg)
|
||||
cargs.append(cmdargs[arg])
|
||||
if u:
|
||||
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', self.devtypes[iname], 'con-name', cname, 'connection.interface-name', iname] + cargs)
|
||||
|
||||
|
||||
|
||||
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([])
|
||||
for srv in srvs:
|
||||
s = socket.create_connection((srv, 443))
|
||||
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
|
||||
status, nc = apiclient.HTTPSClient(usejson=True, host=srv).grab_url_with_status('/confluent-api/self/netcfg')
|
||||
nc = json.loads(nc)
|
||||
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)
|
||||
rm_tmp_llas(tmpllas)
|
||||
if os.path.exists('/usr/bin/nmcli'):
|
||||
nm = NetworkManager(devtypes)
|
||||
elif os.path.exists('/usr/sbin/wicked'):
|
||||
nm = WickedManager()
|
||||
for netn in netname_to_interfaces:
|
||||
nm.apply_configuration(netname_to_interfaces[netn])
|
||||
if havefirewall:
|
||||
subprocess.check_call(['systemctl', 'start', 'firewalld'])
|
||||
await_tentative()
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
Name: confluent_osdeploy-x86_64
|
||||
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
|
||||
BuildArch: noarch
|
||||
Requires: confluent_ipxe mtools tar
|
||||
BuildRoot: /tmp
|
||||
|
||||
%description
|
||||
This contains support utilities for enabling deployment of x86_64 architecture systems
|
||||
|
||||
|
||||
%define debug_package %{nil}
|
||||
|
||||
%prep
|
||||
%setup -n confluent_osdeploy -a 1
|
||||
|
||||
%build
|
||||
mkdir -p opt/confluent/bin
|
||||
mkdir -p stateless-bin
|
||||
cd utils
|
||||
make all
|
||||
cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
|
||||
cp start_root urlmount ../stateless-bin/
|
||||
cd ..
|
||||
ln -s el8 el9
|
||||
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 coreos el9; do
|
||||
mkdir ${os}out
|
||||
cd ${os}out
|
||||
if [ -d ../${os}bin ]; then
|
||||
cp -a ../${os}bin/opt .
|
||||
else
|
||||
cp -a ../opt .
|
||||
fi
|
||||
cp -a ../${os}/initramfs/* .
|
||||
cp -a ../common/initramfs/* .
|
||||
find . | cpio -H newc -o > ../addons.cpio
|
||||
mv ../addons.cpio .
|
||||
cd ..
|
||||
done
|
||||
for os in el7 el8 suse15 el9 ubuntu20.04; do
|
||||
mkdir ${os}disklessout
|
||||
cd ${os}disklessout
|
||||
if [ -d ../${os}bin ]; then
|
||||
cp -a ../${os}bin/opt .
|
||||
else
|
||||
cp -a ../opt .
|
||||
fi
|
||||
cp -a ../${os}-diskless/initramfs/* .
|
||||
cp -a ../common/initramfs/* .
|
||||
if [ -d ../${os}bin ]; then
|
||||
cp -a ../${os}bin/stateless-bin/* opt/confluent/bin
|
||||
else
|
||||
cp -a ../stateless-bin/* opt/confluent/bin
|
||||
fi
|
||||
find . | cpio -H newc -o > ../addons.cpio
|
||||
mv ../addons.cpio .
|
||||
cd ..
|
||||
done
|
||||
mkdir esxi7out
|
||||
cd esxi7out
|
||||
cp -a ../opt .
|
||||
cp -a ../esxi7/initramfs/* .
|
||||
cp -a ../common/initramfs/* .
|
||||
chmod +x bin/* opt/confluent/bin/*
|
||||
tar zcvf ../addons.tgz *
|
||||
mv ../addons.tgz .
|
||||
cd ..
|
||||
cp -a esxi7out esxi6out
|
||||
cp -a esxi7 esxi6
|
||||
cp -a esxi7out esxi8out
|
||||
cp -a esxi7 esxi8
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu22.04 esxi6 esxi7 esxi8 coreos; do
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
|
||||
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
|
||||
cp -a $os/profiles/* %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
|
||||
for targ in %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles/*; do
|
||||
cp -a common/profile/* $targ
|
||||
done
|
||||
if [ -d ${os}disklessout ]; then
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/profiles
|
||||
cp ${os}disklessout/addons.* %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs
|
||||
cp -a ${os}-diskless/profiles/* %{buildroot}/opt/confluent/lib/osdeploy/$os-diskless/profiles
|
||||
for targ in %{buildroot}/opt/confluent/lib/osdeploy/$os-diskless/profiles/*; do
|
||||
cp -a common/profile/* $targ
|
||||
done
|
||||
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
|
||||
-1
@@ -1 +0,0 @@
|
||||
/usr/lib/systemd/system/confluent-rootfs.service
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/bin/bash
|
||||
TRIES=0
|
||||
oum=$(umask)
|
||||
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
|
||||
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 60 ]; do
|
||||
TRIES=$((TRIES + 1))
|
||||
for currif in *; do
|
||||
ip link set $currif up
|
||||
done
|
||||
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
|
||||
done
|
||||
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
|
||||
cd /
|
||||
echo -n "" > /tmp/confluent.initq
|
||||
# restart cmdline
|
||||
echo -n "" > /etc/cmdline.d/01-confluent.conf
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
cat /tls/*.pem > /etc/confluent/ca.pem
|
||||
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
|
||||
echo "Connecting to confluent server: $confluent_mgr" > /dev/console
|
||||
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
|
||||
needseal=1
|
||||
oldumask=$(umask)
|
||||
umask 0077
|
||||
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
|
||||
needseal=0
|
||||
sed -e s/^SEALED:// /etc/confluent/confluent.apikey | clevis-decrypt-tpm2 > /etc/confluent/confluent.apikey.decrypt
|
||||
mv /etc/confluent/confluent.apikey.decrypt /etc/confluent/confluent.apikey
|
||||
fi
|
||||
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
if [ -z "$confluent_apikey" ]; then
|
||||
echo "Unable to acquire node api key, no TPM2 sealed nor fresh token available, retrying..." > /dev/console
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
if [[ $confluent_mgr == *:* ]]; then
|
||||
confluent_mgr="[$confluent_mgr]"
|
||||
fi
|
||||
if [ $needseal == 1 ]; then
|
||||
sealed=$(echo $confluent_apikey | clevis-encrypt-tpm2 {})
|
||||
if [ ! -z "$sealed" ]; then
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d $sealed https://$confluent_mgr/confluent-api/self/saveapikey
|
||||
fi
|
||||
fi
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/profileprivate/pending/config.ign > /config.ign
|
||||
[ -s /config.ign ] || rm /config.ign
|
||||
|
||||
umask $oldumask
|
||||
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: }
|
||||
|
||||
if [ "$autoconfigmethod" = "dhcp" ]; then
|
||||
echo ip=$ifname:dhcp >> /etc/cmdline.d/01-confluent.conf
|
||||
else
|
||||
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
|
||||
v4addr=${v4addr#ipv4_address: }
|
||||
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
|
||||
v4gw=${v4gw#ipv4_gateway: }
|
||||
if [ "$v4gw" = "null" ]; then
|
||||
v4gw=""
|
||||
fi
|
||||
v4nm=$(grep ipv4_netmask: /etc/confluent/confluent.deploycfg)
|
||||
v4nm=${v4nm#ipv4_netmask: }
|
||||
echo ip=$v4addr::$v4gw:$v4nm:$hostname:$ifname:none >> /etc/cmdline.d/01-confluent.conf
|
||||
fi
|
||||
nameserversec=0
|
||||
while read -r entry; do
|
||||
if [ $nameserversec = 1 ]; then
|
||||
if [[ $entry == "-"* ]] && [[ $entry != "- ''" ]]; then
|
||||
echo nameserver=${entry#- } >> /etc/cmdline.d/01-confluent.conf
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
nameserversec=0
|
||||
if [ "${entry%:*}" = "nameservers" ]; then
|
||||
nameserversec=1
|
||||
continue
|
||||
fi
|
||||
done < /etc/confluent/confluent.deploycfg
|
||||
|
||||
if [ -e /lib/nm-lib.sh ]; then
|
||||
. /lib/nm-lib.sh
|
||||
nm_generate_connections
|
||||
sed -i 's/method=disabled/method=link-local/' /run/NetworkManager/system-connections/*.nmconnection
|
||||
if [ -f /run/NetworkManager/system-connections/$ifname.nmconnection ]; then
|
||||
rm /run/NetworkManager/system-connections/default_connection.nmconnection
|
||||
fi
|
||||
if [[ "$ifname" == ib* ]]; then
|
||||
sed -i s/type=ethernet/type=infiniband/ /run/NetworkManager/system-connections/$ifname.nmconnection
|
||||
if ! grep '\[infiniband\]' /run/NetworkManager/system-connections/$ifname.nmconnection > /dev/null; then
|
||||
echo >> /run/NetworkManager/system-connections/$ifname.nmconnection
|
||||
echo '[infiniband]' >> /run/NetworkManager/system-connections/$ifname.nmconnection
|
||||
echo transport-mode=datagram >> /run/NetworkManager/system-connections/$ifname.nmconnection
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#cat /proc/cmdline /etc/cmdline.d/01-confluent.conf | tr '\n' ' ' > /run/fakecmdline
|
||||
#chcon system_u:object_r:proc_t:s0 /run/fakecmdline
|
||||
#mount -o bind /run/fakecmdline /proc/cmdline
|
||||
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/rootfs.img | rdcore stream-hash /etc/coreos-live-want-rootfs | bsdtar -xf - -C /
|
||||
@@ -1,10 +0,0 @@
|
||||
cat /tls/*.0 >> /etc/pki/tls/certs/ca-bundle.crt
|
||||
if ! grep console= /proc/cmdline >& /dev/null; then
|
||||
autocons=$(/opt/confluent/bin/autocons)
|
||||
if [ -n "$autocons" ]; then
|
||||
echo console=$autocons |sed -e 's!/dev/!!' >> /tmp/01-autocons.conf
|
||||
autocons=${autocons%,*}
|
||||
echo $autocons > /tmp/01-autocons.devnode
|
||||
echo "Detected firmware specified console at $(cat /tmp/01-autocons.conf)" > $autocons
|
||||
fi
|
||||
fi
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
|
||||
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info| head -n 1| awk '{print $2}' | sed -e s/%/%25/)
|
||||
if [[ $confluent_mgr = *:* ]]; then
|
||||
confluent_mgr=[$confluent_mgr]
|
||||
fi
|
||||
|
||||
mkdir -p /sysroot/var/home/core/.ssh/
|
||||
chmod 700 /sysroot/var/home/core/.ssh
|
||||
cat /ssh/*.rootpubkey >> /sysroot/var/home/core/.ssh/authorized_keys
|
||||
chmod 600 /sysroot/var/home/core/.ssh/authorized_keys
|
||||
cp -a /etc/confluent /sysroot/etc/confluent
|
||||
mkdir -p /sysroot/opt/confluent/bin/
|
||||
cat > /sysroot/opt/confluent/bin/setssh.sh << 'EOF'
|
||||
#!/bin/sh
|
||||
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
|
||||
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info| head -n 1| awk '{print $2}' | sed -e s/%/%25/)
|
||||
if [[ $confluent_mgr = *:* ]]; then
|
||||
confluent_mgr=[$confluent_mgr]
|
||||
fi
|
||||
|
||||
for i in /etc/ssh/ssh_host*key.pub; do
|
||||
certname=${i/.pub/-cert.pub}
|
||||
curl --cacert /etc/confluent/ca.pem -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname
|
||||
if ! grep HostKey ${i%.pub} /etc/ssh/sshd_config > /dev/null; then
|
||||
echo HostKey ${i%.pub} >> /etc/ssh/sshd_config
|
||||
fi
|
||||
if ! grep HostCertificate $certname /etc/ssh/sshd_config > /dev/null; then
|
||||
echo HostCertificate $certname >> /etc/ssh/sshd_config
|
||||
fi
|
||||
done
|
||||
EOF
|
||||
|
||||
chmod 755 /sysroot/opt/confluent/bin/setssh.sh
|
||||
chcon system_u:object_r:bin_t:s0 /sysroot/opt/confluent/bin/setssh.sh
|
||||
|
||||
|
||||
cat > /sysroot/etc/systemd/system/confluent-ssh.service << EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/opt/confluent/bin/setssh.sh
|
||||
|
||||
[Unit]
|
||||
PartOf=sshd.service
|
||||
Before=sshd.service
|
||||
After=sshd-keygen.target
|
||||
Wants=sshd-keygen.target
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=sshd.service
|
||||
EOF
|
||||
|
||||
mkdir -p /sysroot/etc/systemd/system/sshd.service.wants
|
||||
ln -s /etc/systemd/system/confluent-ssh.service /sysroot/etc/systemd/system/sshd.service.wants/
|
||||
ln -s /etc/systemd/system/confluent-onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/
|
||||
|
||||
|
||||
cat > /sysroot/opt/confluent/bin/onboot.sh << 'EOF'
|
||||
#!/bin/sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
|
||||
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info| head -n 1| awk '{print $2}' | sed -e s/%/%25/)
|
||||
if [[ $confluent_mgr = *:* ]]; then
|
||||
confluent_mgr=[$confluent_mgr]
|
||||
fi
|
||||
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/${confluent_profile}/scripts/onboot.sh > /tmp/onboot.sh
|
||||
[ -s /tmp/onboot.sh ] && . /tmp/onboot.sh
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 /sysroot/opt/confluent/bin/onboot.sh
|
||||
chcon system_u:object_r:bin_t:s0 /sysroot/opt/confluent/bin/onboot.sh
|
||||
|
||||
|
||||
cat > /sysroot/etc/systemd/system/confluent-onboot.service << EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/opt/confluent/bin/onboot.sh
|
||||
|
||||
[Unit]
|
||||
Requires=sshd.service
|
||||
After=sshd.service
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
EOF
|
||||
chcon -h system_u:object_r:systemd_unit_file_t:s0 /sysroot/etc/systemd/system/confluent-ssh.service /sysroot/etc/systemd/system/sshd.service.wants/confluent-ssh.service /sysroot/etc/systemd/system/confluent-onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/confluent-onboot.service
|
||||
|
||||
cp -a /opt/confluent/bin/* /sysroot/opt/confluent/bin/
|
||||
chcon system_u:object_r:bin_t:s0 /sysroot/opt/confluent/bin/*
|
||||
@@ -1,22 +0,0 @@
|
||||
[Unit]
|
||||
Description=Confluent initialization
|
||||
DefaultDependencies=false
|
||||
|
||||
After=basic.target
|
||||
# Network is enabled here
|
||||
After=dracut-initqueue.service
|
||||
Before=ignition-setup-user.service
|
||||
Before=coreos-ignition-setup-user.service
|
||||
Before=coreos-livepxe-rootfs.service
|
||||
|
||||
|
||||
|
||||
# If we fail, the boot will fail. Be explicit about it.
|
||||
OnFailure=emergency.target
|
||||
OnFailureJobMode=isolate
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/confluent/bin/initconfluent.sh
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
profname=$(basename $2)
|
||||
if file -L $1/images/ignition.img | grep gzip > /dev/null; then
|
||||
mkdir -p /var/lib/confluent/private/os/${profname}/pending/
|
||||
cd /var/lib/confluent/private/os/${profname}/pending/
|
||||
zcat $1/images/ignition.img | cpio -dumiv
|
||||
fi
|
||||
ln -s $1/images/pxeboot/vmlinuz $2/boot/kernel && \
|
||||
ln -s $1/images/pxeboot/initrd.img $2/boot/initramfs/distribution && \
|
||||
mkdir -p $2/boot/efi/boot/ && \
|
||||
ln -s $1/images/pxeboot/rootfs.img $2/ && \
|
||||
(mcopy -i $1/images/efiboot.img ::efi/redhat/grubx64.efi $2/boot/efi/boot/ || \
|
||||
mcopy -i $1/images/efiboot.img ::efi/boot/grubx64.efi $2/boot/efi/boot/) && \
|
||||
mcopy -i $1/images/efiboot.img ::efi/boot/bootx64.efi $2/boot/efi/boot/
|
||||
@@ -1,5 +0,0 @@
|
||||
label: RedHat CoreOS %%VERSION%% %%ARCH%% (Default Profile)
|
||||
kernelargs: quiet random.trust_cpu=on ignition.firstboot ignition.platform.id=metal
|
||||
#the above boots and will have ignition agent phone home and await instructions
|
||||
#kernelargs: quiet random.trust_cpu=on ignition.firstboot ignition.platform.id=metal coreos.inst=yes coreos.inst.install_dev=sda
|
||||
#the above runs the installer to write to disk
|
||||
@@ -1,97 +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
|
||||
}
|
||||
|
||||
dhuuid=$(reverse_uuid $(cat /sys/devices/virtual/dmi/id/product_uuid))
|
||||
dhcpid=$(mktemp)
|
||||
mkdir -p /etc/confluent
|
||||
cp /tls/* /etc/ssl/certs/
|
||||
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
|
||||
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
|
||||
profile=$(grep ^profile: /etc/confluent/deploycfg|cut -d ' ' -f 2)
|
||||
wget https://$mgr/confluent-public/os/$profile/scripts/pre.sh
|
||||
chmod u+x pre.sh
|
||||
wget https://$mgr/confluent-public/os/$profile/preseed.cfg
|
||||
mv preseed.cfg /
|
||||
setdebopt auto-install/enable true boolean
|
||||
setdebopt partman/early_command $predir/pre.sh string
|
||||
cd -
|
||||
ip -4 a flush dev $nic
|
||||
setdebopt netcfg/choose_interface $nic select
|
||||
setdebopt netcfg/get_hostname $nodename string
|
||||
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
|
||||
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/ntep-server $ntpsrv string
|
||||
done
|
||||
#setdebopt console-setup/layoutcode $(grep ^keymap: /etc/confluent/deploycfg|cut -d ' ' -f 2) string
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
anna-install openssh-server-udeb
|
||||
mkdir -p ~/.ssh/
|
||||
cat /ssh/*pubkey > ~/.ssh/authorized_keys
|
||||
ssh-keygen -A
|
||||
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/ //')
|
||||
apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
certfile=${pubkey%.pub}-cert.pub
|
||||
keyfile=${pubkey%.pub}
|
||||
wget --post-file=$pubkey --header='CONFLUENT_NODENAME: '$nodename --header="CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/sshcert -O $certfile
|
||||
echo HostKey $keyfile >> /etc/ssh/sshd_config
|
||||
echo HostCertificate $certfile >> /etc/ssh/sshd_config
|
||||
done
|
||||
|
||||
echo sshd:x:939:939::/: >> /etc/passwd
|
||||
/usr/sbin/sshd
|
||||
|
||||
-245
@@ -1,245 +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..."
|
||||
sleep 10
|
||||
elif [ -c /dev/tpmrm0 -a -x /usr/bin/tpm2_startauthsession ]; 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
|
||||
clear
|
||||
mkdir -p /etc/ssh
|
||||
mkdir -p /var/tmp/
|
||||
mkdir -p /var/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)
|
||||
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 -a -x /usr/bin/tpm2_getcap ]; 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
|
||||
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 == *:* ]]; then
|
||||
confluent_mgr="[${confluent_mgr/\%/%25}]"
|
||||
fi
|
||||
tmperr=$(mktemp)
|
||||
curl -gsSf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /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%]}
|
||||
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 -a -x /usr/bin/tpm2_pcrextend ]; then
|
||||
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
|
||||
fi
|
||||
umask $oldumask
|
||||
autoconfigmethod=$(grep ^ipv4_method: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
if [ "$autoconfigmethod" = "dhcp" ]; then
|
||||
echo -n "Attempting to use dhcp to bring up $ifname..."
|
||||
dhclient $ifname
|
||||
echo "Complete:"
|
||||
ip addr show dev $ifname
|
||||
else
|
||||
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
|
||||
v4addr=${v4addr#ipv4_address: }
|
||||
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
|
||||
v4gw=${v4gw#ipv4_gateway: }
|
||||
if [ "$v4gw" = "null" ]; then
|
||||
v4gw=""
|
||||
fi
|
||||
v4nm=$(grep ^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
|
||||
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
|
||||
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
|
||||
type=ethernet
|
||||
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
|
||||
|
||||
[ethernet]
|
||||
mac-address-blacklist=
|
||||
|
||||
[ipv4]
|
||||
EOC
|
||||
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
|
||||
|
||||
[proxy]
|
||||
EOC
|
||||
fi
|
||||
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
|
||||
echo -n "Initializing ssh..."
|
||||
ssh-keygen -A
|
||||
for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
certfile=${pubkey/.pub/-cert.pub}
|
||||
privfile=${pubkey%.pub}
|
||||
curl -gsf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d @$pubkey https://$confluent_mgr/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
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
|
||||
mkdir -p /etc/confluent
|
||||
curl -gsf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
|
||||
. /etc/confluent/functions
|
||||
source_remote imageboot.sh
|
||||
@@ -1,4 +0,0 @@
|
||||
. /etc/confluent/functions
|
||||
# This is a convenient place to keep customizations separate from modifying the stock scripts
|
||||
# While modification of the stock scripts is fine, it may be easier to rebase to a newer
|
||||
# stock profile if the '.custom' files are used.
|
||||
@@ -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,40 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script is executed on the first boot after install has
|
||||
# completed. It is best to edit the middle of the file as
|
||||
# noted below so custom commands are executed before
|
||||
# the script notifies confluent that install is fully complete.
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
export nodename confluent_mgr confluent_profile
|
||||
. /etc/confluent/functions
|
||||
(
|
||||
exec >> /var/log/confluent/confluent-firstboot.log
|
||||
exec 2>> /var/log/confluent/confluent-firstboot.log
|
||||
chmod 600 /var/log/confluent/confluent-firstboot.log
|
||||
while ! ping -c 1 $confluent_mgr >& /dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -f /etc/confluent/firstboot.ran ]; then
|
||||
touch /etc/confluent/firstboot.ran
|
||||
|
||||
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
|
||||
|
||||
run_remote firstboot.custom
|
||||
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
|
||||
run_remote_parts firstboot.d
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
|
||||
run_remote_config firstboot.d
|
||||
fi
|
||||
|
||||
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
systemctl disable firstboot
|
||||
rm /etc/systemd/system/firstboot.service
|
||||
rm /etc/confluent/firstboot.ran
|
||||
) &
|
||||
tail --pid $! -F /var/log/confluent/confluent-firstboot.log > /dev/console
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/bin/bash
|
||||
function test_mgr() {
|
||||
whost=$1
|
||||
if [[ "$whost" == *:* ]]; then
|
||||
whost="[$whost]"
|
||||
fi
|
||||
if curl -s https://${whost}/confluent-api/ > /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function confluentpython() {
|
||||
if [ -x /usr/libexec/platform-python ]; then
|
||||
/usr/libexec/platform-python $*
|
||||
elif [ -x /usr/bin/python3 ]; then
|
||||
/usr/bin/python3 $*
|
||||
elif [ -x /usr/bin/python ]; then
|
||||
/usr/bin/python $*
|
||||
elif [ -x /usr/bin/python2 ]; then
|
||||
/usr/bin/python2 $*
|
||||
fi
|
||||
}
|
||||
|
||||
function set_confluent_vars() {
|
||||
if [ -z "$nodename" ]; then
|
||||
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
|
||||
fi
|
||||
if [[ "$confluent_mgr" == *"%"* ]]; then
|
||||
confluent_mgr=""
|
||||
fi
|
||||
if [ -z "$confluent_mgr" ]; then
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
if ! test_mgr $confluent_mgr; then
|
||||
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
if [[ "$confluent_mgr" = *":"* ]]; then
|
||||
confluent_mgr="[$confluent_mgr]"
|
||||
fi
|
||||
fi
|
||||
if ! test_mgr $confluent_mgr; then
|
||||
BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
|
||||
OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
|
||||
for confluent_mgr in $BESTMGRS $OKMGRS; do
|
||||
if [[ $confluent_mgr == *":"* ]]; then
|
||||
confluent_mgr="[$confluent_mgr]"
|
||||
fi
|
||||
if test_mgr $confluent_mgr; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
if [ -z "$confluent_profile" ]; then
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_remote() {
|
||||
curlargs=""
|
||||
if [ -f /etc/confluent/ca.pem ]; then
|
||||
curlargs=" --cacert /etc/confluent/ca.pem"
|
||||
fi
|
||||
set_confluent_vars
|
||||
mkdir -p $(dirname $1)
|
||||
whost=$confluent_mgr
|
||||
if [[ "$whost" == *:* ]]; then
|
||||
whost="[$whost]"
|
||||
fi
|
||||
curl -f -sS $curlargs https://$whost/confluent-public/os/$confluent_profile/scripts/$1 > $1
|
||||
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
|
||||
}
|
||||
|
||||
source_remote_parts() {
|
||||
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
|
||||
apiclient=/opt/confluent/bin/apiclient
|
||||
if [ -f /etc/confluent/apiclient ]; then
|
||||
apiclient=/etc/confluent/apiclient
|
||||
fi
|
||||
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
source_remote $1/$script
|
||||
done
|
||||
rm -rf $confluentscripttmpdir
|
||||
unset confluentscripttmpdir
|
||||
}
|
||||
|
||||
run_remote_parts() {
|
||||
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
|
||||
apiclient=/opt/confluent/bin/apiclient
|
||||
if [ -f /etc/confluent/apiclient ]; then
|
||||
apiclient=/etc/confluent/apiclient
|
||||
fi
|
||||
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
run_remote $1/$script
|
||||
done
|
||||
rm -rf $confluentscripttmpdir
|
||||
unset confluentscripttmpdir
|
||||
}
|
||||
|
||||
source_remote() {
|
||||
set_confluent_vars
|
||||
unsettmpdir=0
|
||||
echo
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
if [ -z "$confluentscripttmpdir" ]; then
|
||||
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
|
||||
unsettmpdir=1
|
||||
fi
|
||||
echo Sourcing from $confluentscripttmpdir
|
||||
cd $confluentscripttmpdir
|
||||
fetch_remote $1
|
||||
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
|
||||
chmod +x $1
|
||||
cmd=$1
|
||||
shift
|
||||
source ./$cmd
|
||||
cd - > /dev/null
|
||||
if [ "$unsettmpdir" = 1 ]; then
|
||||
rm -rf $confluentscripttmpdir
|
||||
unset confluentscripttmpdir
|
||||
unsettmpdir=0
|
||||
fi
|
||||
rm -rf $confluentscripttmpdir
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote() {
|
||||
requestedcmd="'$*'"
|
||||
unsettmpdir=0
|
||||
set_confluent_vars
|
||||
echo
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
if [ -z "$confluentscripttmpdir" ]; then
|
||||
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
|
||||
unsettmpdir=1
|
||||
fi
|
||||
echo Executing in $confluentscripttmpdir
|
||||
cd $confluentscripttmpdir
|
||||
fetch_remote $1
|
||||
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
|
||||
chmod +x $1
|
||||
cmd=$1
|
||||
if [ -x /usr/bin/chcon ]; then
|
||||
chcon system_u:object_r:bin_t:s0 $cmd
|
||||
fi
|
||||
shift
|
||||
./$cmd $*
|
||||
retcode=$?
|
||||
if [ $retcode -ne 0 ]; then
|
||||
echo "$requestedcmd exited with code $retcode"
|
||||
fi
|
||||
cd - > /dev/null
|
||||
if [ "$unsettmpdir" = 1 ]; then
|
||||
rm -rf $confluentscripttmpdir
|
||||
unset confluentscripttmpdir
|
||||
unsettmpdir=0
|
||||
fi
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote_python() {
|
||||
echo
|
||||
set_confluent_vars
|
||||
if [ -f /etc/confluent/ca.pem ]; then
|
||||
curlargs=" --cacert /etc/confluent/ca.pem"
|
||||
fi
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
|
||||
echo Executing in $confluentscripttmpdir
|
||||
cd $confluentscripttmpdir
|
||||
mkdir -p $(dirname $1)
|
||||
whost=$confluent_mgr
|
||||
if [[ "$whost" == *:* ]]; then
|
||||
whost="[$whost]"
|
||||
fi
|
||||
curl -f -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,93 +0,0 @@
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
self.model = ''
|
||||
self.size = 0
|
||||
self.driver = None
|
||||
self.mdcontainer = ''
|
||||
devnode = '/dev/{0}'.format(devname)
|
||||
qprop = subprocess.check_output(
|
||||
['udevadm', 'info', '--query=property', devnode])
|
||||
if not isinstance(qprop, str):
|
||||
qprop = qprop.decode('utf8')
|
||||
for prop in qprop.split('\n'):
|
||||
if '=' not in prop:
|
||||
continue
|
||||
k, v = prop.split('=', 1)
|
||||
if k == 'DEVTYPE' and v != 'disk':
|
||||
raise Exception('Not a disk')
|
||||
elif k == 'DM_NAME':
|
||||
raise Exception('Device Mapper')
|
||||
elif k == 'ID_MODEL':
|
||||
self.model = v
|
||||
elif k == 'DEVPATH':
|
||||
self.path = v
|
||||
elif k == 'ID_WWN':
|
||||
self.wwn = v
|
||||
elif k == 'MD_CONTAINER':
|
||||
self.mdcontainer = v
|
||||
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
|
||||
if not isinstance(attrs, str):
|
||||
attrs = attrs.decode('utf8')
|
||||
for attr in attrs.split('\n'):
|
||||
if '==' not in attr:
|
||||
continue
|
||||
k, v = attr.split('==', 1)
|
||||
k = k.strip()
|
||||
if k == 'ATTRS{size}':
|
||||
self.size = v.replace('"', '')
|
||||
elif (k == 'DRIVERS' and not self.driver
|
||||
and v not in ('"sd"', '""')):
|
||||
self.driver = v.replace('"', '')
|
||||
if not self.driver and 'imsm' not in self.mdcontainer:
|
||||
raise Exception("No driver detected")
|
||||
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
|
||||
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
|
||||
self.size = int(sizesrc.read()) * 512
|
||||
if int(self.size) < 536870912:
|
||||
raise Exception("Device too small for install")
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
if self.model.lower() in ('m.2 nvme 2-bay raid kit', 'thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
|
||||
return 0
|
||||
if 'imsm' in self.mdcontainer:
|
||||
return 1
|
||||
if self.driver == 'ahci':
|
||||
return 2
|
||||
if self.driver.startswith('megaraid'):
|
||||
return 3
|
||||
if self.driver.startswith('mpt'):
|
||||
return 4
|
||||
return 99
|
||||
|
||||
def __repr__(self):
|
||||
return repr({
|
||||
'name': self.name,
|
||||
'path': self.path,
|
||||
'wwn': self.wwn,
|
||||
'driver': self.driver,
|
||||
'size': self.size,
|
||||
'model': self.model,
|
||||
})
|
||||
|
||||
|
||||
def main():
|
||||
disks = []
|
||||
for disk in sorted(os.listdir('/sys/class/block')):
|
||||
try:
|
||||
disk = DiskInfo(disk)
|
||||
disks.append(disk)
|
||||
except Exception as e:
|
||||
print("Skipping {0}: {1}".format(disk, str(e)))
|
||||
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
|
||||
if nd:
|
||||
open('/tmp/installdisk', 'w').write(nd[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,376 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import shutil
|
||||
import socket
|
||||
import stat
|
||||
import struct
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
def get_next_part_meta(img, imgsize):
|
||||
if img.tell() == imgsize:
|
||||
return None
|
||||
pathlen = struct.unpack('!H', img.read(2))[0]
|
||||
mountpoint = img.read(pathlen).decode('utf8')
|
||||
jsonlen = struct.unpack('!I', img.read(4))[0]
|
||||
metadata = json.loads(img.read(jsonlen).decode('utf8'))
|
||||
img.seek(16, 1) # skip the two 64-bit values we don't use, they are in json
|
||||
nextlen = struct.unpack('!H', img.read(2))[0]
|
||||
img.seek(nextlen, 1) # skip filesystem type
|
||||
nextlen = struct.unpack('!H', img.read(2))[0]
|
||||
img.seek(nextlen, 1) # skip orig devname (redundant with json)
|
||||
nextlen = struct.unpack('!H', img.read(2))[0]
|
||||
img.seek(nextlen, 1) # skip padding
|
||||
nextlen = struct.unpack('!Q', img.read(8))[0]
|
||||
img.seek(nextlen, 1) # go to next section
|
||||
return metadata
|
||||
|
||||
def get_multipart_image_meta(img):
|
||||
img.seek(0, 2)
|
||||
imgsize = img.tell()
|
||||
img.seek(16)
|
||||
seekamt = img.read(1)
|
||||
img.seek(struct.unpack('B', seekamt)[0], 1)
|
||||
partinfo = get_next_part_meta(img, imgsize)
|
||||
while partinfo:
|
||||
yield partinfo
|
||||
partinfo = get_next_part_meta(img, imgsize)
|
||||
|
||||
def get_image_metadata(imgpath):
|
||||
with open(imgpath, 'rb') as img:
|
||||
header = img.read(16)
|
||||
if header == b'\x63\x7b\x9d\x26\xb7\xfd\x48\x30\x89\xf9\x11\xcf\x18\xfd\xff\xa1':
|
||||
for md in get_multipart_image_meta(img):
|
||||
yield md
|
||||
else:
|
||||
raise Exception('Installation from single part image not supported')
|
||||
|
||||
class PartedRunner():
|
||||
def __init__(self, disk):
|
||||
self.disk = disk
|
||||
|
||||
def run(self, command):
|
||||
command = command.split()
|
||||
command = ['parted', '-a', 'optimal', '-s', self.disk] + command
|
||||
return subprocess.check_output(command).decode('utf8')
|
||||
|
||||
def fixup(rootdir, vols):
|
||||
devbymount = {}
|
||||
for vol in vols:
|
||||
devbymount[vol['mount']] = vol['targetdisk']
|
||||
fstabfile = os.path.join(rootdir, 'etc/fstab')
|
||||
with open(fstabfile) as tfile:
|
||||
fstab = tfile.read().split('\n')
|
||||
while not fstab[0]:
|
||||
fstab = fstab[1:]
|
||||
if os.path.exists(os.path.join(rootdir, '.autorelabel')):
|
||||
os.unlink(os.path.join(rootdir, '.autorelabel'))
|
||||
with open(fstabfile, 'w') as tfile:
|
||||
for tab in fstab:
|
||||
entry = tab.split()
|
||||
if tab.startswith('#ORIGFSTAB#'):
|
||||
if entry[1] in devbymount:
|
||||
targetdev = devbymount[entry[1]]
|
||||
if targetdev.startswith('/dev/localstorage/'):
|
||||
entry[0] = targetdev
|
||||
else:
|
||||
uuid = subprocess.check_output(['blkid', '-s', 'UUID', '-o', 'value', targetdev]).decode('utf8')
|
||||
uuid = uuid.strip()
|
||||
entry[0] = 'UUID={}'.format(uuid)
|
||||
elif entry[2] == 'swap':
|
||||
entry[0] = '/dev/mapper/localstorage-swap'
|
||||
entry[0] = entry[0].ljust(42)
|
||||
entry[1] = entry[1].ljust(16)
|
||||
entry[3] = entry[3].ljust(28)
|
||||
tab = '\t'.join(entry)
|
||||
tfile.write(tab + '\n')
|
||||
with open(os.path.join(rootdir, 'etc/hostname'), 'w') as nameout:
|
||||
nameout.write(socket.gethostname() + '\n')
|
||||
selinuxconfig = os.path.join(rootdir, 'etc/selinux/config')
|
||||
policy = None
|
||||
if os.path.exists(selinuxconfig):
|
||||
with open(selinuxconfig) as cfgin:
|
||||
sec = cfgin.read().split('\n')
|
||||
for l in sec:
|
||||
l = l.split('#', 1)[0]
|
||||
if l.startswith('SELINUXTYPE='):
|
||||
_, policy = l.split('=')
|
||||
for sshkey in glob.glob(os.path.join(rootdir, 'etc/ssh/*_key*')):
|
||||
os.unlink(sshkey)
|
||||
for sshkey in glob.glob('/etc/ssh/*_key*'):
|
||||
newkey = os.path.join(rootdir, sshkey[1:])
|
||||
shutil.copy2(sshkey, newkey)
|
||||
finfo = os.stat(sshkey)
|
||||
os.chown(newkey, finfo[stat.ST_UID], finfo[stat.ST_GID])
|
||||
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/sysconfig/network-scripts/*')):
|
||||
os.unlink(ifcfg)
|
||||
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/NetworkManager/system-connections/*')):
|
||||
os.unlink(ifcfg)
|
||||
for ifcfg in glob.glob('/run/NetworkManager/system-connections/*'):
|
||||
newcfg = ifcfg.split('/')[-1]
|
||||
newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg))
|
||||
shutil.copy2(ifcfg, newcfg)
|
||||
shutil.rmtree(os.path.join(rootdir, 'etc/confluent/'))
|
||||
shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent'))
|
||||
if policy:
|
||||
sys.stdout.write('Applying SELinux labeling...')
|
||||
sys.stdout.flush()
|
||||
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'etc')])
|
||||
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'opt')])
|
||||
sys.stdout.write('Done\n')
|
||||
sys.stdout.flush()
|
||||
for metafs in ('proc', 'sys', 'dev'):
|
||||
subprocess.check_call(['mount', '-o', 'bind', '/{}'.format(metafs), os.path.join(rootdir, metafs)])
|
||||
with open(os.path.join(rootdir, 'etc/sysconfig/grub')) as defgrubin:
|
||||
defgrub = defgrubin.read().split('\n')
|
||||
with open(os.path.join(rootdir, 'etc/sysconfig/grub'), 'w') as defgrubout:
|
||||
for gline in defgrub:
|
||||
gline = gline.split()
|
||||
newline = []
|
||||
for ent in gline:
|
||||
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
|
||||
continue
|
||||
newline.append(ent)
|
||||
defgrubout.write(' '.join(newline) + '\n')
|
||||
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/')
|
||||
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
|
||||
newroot = None
|
||||
with open('/etc/shadow') as shadowin:
|
||||
shents = shadowin.read().split('\n')
|
||||
for shent in shents:
|
||||
shent = shent.split(':')
|
||||
if not shent:
|
||||
continue
|
||||
if shent[0] == 'root' and shent[1] not in ('*', '!!', ''):
|
||||
newroot = shent[1]
|
||||
if newroot:
|
||||
shlines = None
|
||||
with open(os.path.join(rootdir, 'etc/shadow')) as oshadow:
|
||||
shlines = oshadow.read().split('\n')
|
||||
with open(os.path.join(rootdir, 'etc/shadow'), 'w') as oshadow:
|
||||
for line in shlines:
|
||||
if line.startswith('root:'):
|
||||
line = line.split(':')
|
||||
line[1] = newroot
|
||||
line = ':'.join(line)
|
||||
oshadow.write(line + '\n')
|
||||
partnum = None
|
||||
targblock = None
|
||||
for vol in vols:
|
||||
if vol['mount'] == '/boot/efi':
|
||||
targdev = vol['targetdisk']
|
||||
partnum = re.search('(\d+)$', targdev).group(1)
|
||||
targblock = re.search('(.*)\d+$', targdev).group(1)
|
||||
if targblock:
|
||||
shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip()
|
||||
shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\')
|
||||
subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum])
|
||||
#other network interfaces
|
||||
|
||||
|
||||
def had_swap():
|
||||
with open('/etc/fstab') as tabfile:
|
||||
tabs = tabfile.read().split('\n')
|
||||
for tab in tabs:
|
||||
tab = tab.split()
|
||||
if len(tab) < 3:
|
||||
continue
|
||||
if tab[2] == 'swap':
|
||||
return True
|
||||
return False
|
||||
|
||||
def install_to_disk(imgpath):
|
||||
lvmvols = {}
|
||||
deftotsize = 0
|
||||
mintotsize = 0
|
||||
deflvmsize = 0
|
||||
minlvmsize = 0
|
||||
biggestsize = 0
|
||||
biggestfs = None
|
||||
plainvols = {}
|
||||
allvols = []
|
||||
swapsize = 0
|
||||
if had_swap():
|
||||
with open('/proc/meminfo') as meminfo:
|
||||
swapsize = meminfo.read().split('\n')[0]
|
||||
swapsize = int(swapsize.split()[1])
|
||||
if swapsize < 2097152:
|
||||
swapsize = swapsize * 2
|
||||
elif swapsize > 8388608 and swapsize < 67108864:
|
||||
swapsize = swapsize * 0.5
|
||||
elif swapsize >= 67108864:
|
||||
swapsize = 33554432
|
||||
swapsize = int(swapsize * 1024)
|
||||
deftotsize = swapsize
|
||||
mintotsize = swapsize
|
||||
for fs in get_image_metadata(imgpath):
|
||||
allvols.append(fs)
|
||||
deftotsize += fs['initsize']
|
||||
mintotsize += fs['minsize']
|
||||
if fs['initsize'] > biggestsize:
|
||||
biggestfs = fs
|
||||
biggestsize = fs['initsize']
|
||||
if fs['device'].startswith('/dev/mapper'):
|
||||
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
|
||||
deflvmsize += fs['initsize']
|
||||
minlvmsize += fs['minsize']
|
||||
else:
|
||||
plainvols[int(re.search('(\d+)$', fs['device'])[0])] = fs
|
||||
with open('/tmp/installdisk') as diskin:
|
||||
instdisk = diskin.read()
|
||||
instdisk = '/dev/' + instdisk
|
||||
parted = PartedRunner(instdisk)
|
||||
dinfo = parted.run('unit s print')
|
||||
dinfo = dinfo.split('\n')
|
||||
sectors = 0
|
||||
sectorsize = 0
|
||||
for inf in dinfo:
|
||||
if inf.startswith('Disk {0}:'.format(instdisk)):
|
||||
_, sectors = inf.split(': ')
|
||||
sectors = int(sectors.replace('s', ''))
|
||||
if inf.startswith('Sector size (logical/physical):'):
|
||||
_, sectorsize = inf.split(':')
|
||||
sectorsize = sectorsize.split('/')[0]
|
||||
sectorsize = sectorsize.replace('B', '')
|
||||
sectorsize = int(sectorsize)
|
||||
# for now, only support resizing/growing the largest partition
|
||||
minexcsize = deftotsize - biggestfs['initsize']
|
||||
mintotsize = deftotsize - biggestfs['initsize'] + biggestfs['minsize']
|
||||
minsectors = mintotsize // sectorsize
|
||||
if sectors < (minsectors + 65536):
|
||||
raise Exception('Disk too small to fit image')
|
||||
biggestsectors = sectors - (minexcsize // sectorsize)
|
||||
biggestsize = sectorsize * biggestsectors
|
||||
parted.run('mklabel gpt')
|
||||
curroffset = 2048
|
||||
for volidx in sorted(plainvols):
|
||||
vol = plainvols[volidx]
|
||||
if vol is not biggestfs:
|
||||
size = vol['initsize'] // sectorsize
|
||||
else:
|
||||
size = biggestsize // sectorsize
|
||||
size += 2047 - (size % 2048)
|
||||
end = curroffset + size
|
||||
if end > sectors:
|
||||
end = sectors
|
||||
parted.run('mkpart primary {}s {}s'.format(curroffset, end))
|
||||
vol['targetdisk'] = instdisk + '{0}'.format(volidx)
|
||||
curroffset += size + 1
|
||||
if not lvmvols:
|
||||
if swapsize:
|
||||
swapsize = swapsize // sectorsize
|
||||
swapsize += 2047 - (size % 2048)
|
||||
end = curroffset + swapsize
|
||||
if end > sectors:
|
||||
end = sectors
|
||||
parted.run('mkpart swap {}s {}s'.format(curroffset, end))
|
||||
subprocess.check_call(['mkswap', instdisk + '{}'.format(volidx + 1)])
|
||||
else:
|
||||
parted.run('mkpart lvm {}s 100%'.format(curroffset))
|
||||
lvmpart = instdisk + '{}'.format(volidx + 1)
|
||||
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
|
||||
subprocess.check_call(['vgcreate', 'localstorage', lvmpart])
|
||||
vginfo = subprocess.check_output(['vgdisplay', 'localstorage', '--units', 'b']).decode('utf8')
|
||||
vginfo = vginfo.split('\n')
|
||||
pesize = 0
|
||||
pes = 0
|
||||
for infline in vginfo:
|
||||
infline = infline.split()
|
||||
if len(infline) >= 3 and infline[:2] == ['PE', 'Size']:
|
||||
pesize = int(infline[2])
|
||||
if len(infline) >= 5 and infline[:2] == ['Free', 'PE']:
|
||||
pes = int(infline[4])
|
||||
takeaway = swapsize // pesize
|
||||
for volidx in lvmvols:
|
||||
vol = lvmvols[volidx]
|
||||
if vol is biggestfs:
|
||||
continue
|
||||
takeaway += vol['initsize'] // pesize
|
||||
takeaway += 1
|
||||
biggestextents = pes - takeaway
|
||||
for volidx in lvmvols:
|
||||
vol = lvmvols[volidx]
|
||||
if vol is biggestfs:
|
||||
extents = biggestextents
|
||||
else:
|
||||
extents = vol['initsize'] // pesize
|
||||
extents += 1
|
||||
if vol['mount'] == '/':
|
||||
lvname = 'root'
|
||||
else:
|
||||
lvname = vol['mount'].replace('/', '_')
|
||||
subprocess.check_call(['lvcreate', '-l', '{}'.format(extents), '-y', '-n', lvname, 'localstorage'])
|
||||
vol['targetdisk'] = '/dev/localstorage/{}'.format(lvname)
|
||||
if swapsize:
|
||||
subprocess.check_call(['lvcreate', '-y', '-l', '{}'.format(swapsize // pesize), '-n', 'swap', 'localstorage'])
|
||||
subprocess.check_call(['mkswap', '/dev/localstorage/swap'])
|
||||
os.makedirs('/run/imginst/targ')
|
||||
for vol in allvols:
|
||||
with open(vol['targetdisk'], 'wb') as partition:
|
||||
partition.write(b'\x00' * 1 * 1024 * 1024)
|
||||
subprocess.check_call(['mkfs.{}'.format(vol['filesystem']), vol['targetdisk']])
|
||||
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ'])
|
||||
source = vol['mount'].replace('/', '_')
|
||||
source = '/run/imginst/sources/' + source
|
||||
blankfsstat = os.statvfs('/run/imginst/targ')
|
||||
blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize
|
||||
sys.stdout.write('\nWriting {0}: '.format(vol['mount']))
|
||||
with subprocess.Popen(['cp', '-ax', source + '/.', '/run/imginst/targ']) as copier:
|
||||
stillrunning = copier.poll()
|
||||
lastprogress = 0.0
|
||||
while stillrunning is None:
|
||||
currfsstat = os.statvfs('/run/imginst/targ')
|
||||
currused = (currfsstat.f_blocks - currfsstat.f_bfree) * currfsstat.f_bsize
|
||||
currused -= blankused
|
||||
with open('/proc/meminfo') as meminf:
|
||||
for line in meminf.read().split('\n'):
|
||||
if line.startswith('Dirty:'):
|
||||
_, dirty, _ = line.split()
|
||||
dirty = int(dirty) * 1024
|
||||
progress = (currused - dirty) / vol['minsize']
|
||||
if progress < lastprogress:
|
||||
progress = lastprogress
|
||||
if progress > 0.99:
|
||||
progress = 0.99
|
||||
lastprogress = progress
|
||||
progress = progress * 100
|
||||
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.5)
|
||||
stillrunning = copier.poll()
|
||||
if stillrunning != 0:
|
||||
raise Exception("Error copying volume")
|
||||
with subprocess.Popen(['sync']) as syncrun:
|
||||
stillrunning = syncrun.poll()
|
||||
while stillrunning is None:
|
||||
with open('/proc/meminfo') as meminf:
|
||||
for line in meminf.read().split('\n'):
|
||||
if line.startswith('Dirty:'):
|
||||
_, dirty, _ = line.split()
|
||||
dirty = int(dirty) * 1024
|
||||
progress = (vol['minsize'] - dirty) / vol['minsize']
|
||||
if progress < lastprogress:
|
||||
progress = lastprogress
|
||||
if progress > 0.99:
|
||||
progress = 0.99
|
||||
lastprogress = progress
|
||||
progress = progress * 100
|
||||
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.5)
|
||||
stillrunning = syncrun.poll()
|
||||
sys.stdout.write('\x1b[1K\rDone writing {0}'.format(vol['mount']))
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
subprocess.check_call(['umount', '/run/imginst/targ'])
|
||||
for vol in allvols:
|
||||
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
|
||||
fixup('/run/imginst/targ', allvols)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
@@ -1,128 +0,0 @@
|
||||
. /lib/dracut-lib.sh
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs"
|
||||
modprobe fuse
|
||||
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
|
||||
fi
|
||||
/opt/confluent/bin/confluent_imginfo /mnt/remoteimg/rootimg.sfs > /tmp/rootimg.info
|
||||
modprobe loop
|
||||
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_mgr/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_mgr/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
|
||||
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
|
||||
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
|
||||
chroot /sysroot/ update-ca-trust
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
|
||||
mkdir -p /sysroot/opt/confluent/bin
|
||||
curl -sf https://$confluent_mgr/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/
|
||||
exec /opt/confluent/bin/start_root
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
. /etc/confluent/functions
|
||||
# the image will be used to deploy itself
|
||||
# provide both access to image (for parsing metadata)
|
||||
# and existing mounts of image (to take advantage of caching)
|
||||
mount -o bind /sys /sysroot/sys
|
||||
mount -o bind /dev /sysroot/dev
|
||||
mount -o bind /proc /sysroot/proc
|
||||
mount -o bind /run /sysroot/run
|
||||
|
||||
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /sysroot/run/imginst/sources/_
|
||||
mount -o bind /mnt/remote /sysroot/run/imginst/sources/_
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $2}'); do
|
||||
srcname=${srcmount#/dev/mapper/mproot}
|
||||
srcdir=$(echo $srcmount | sed -e 's!/dev/mapper/mproot!/mnt/remote!' -e 's!_!/!g')
|
||||
mkdir -p /sysroot/run/imginst/sources/$srcname
|
||||
mount -o bind $srcdir /sysroot/run/imginst/sources/$srcname
|
||||
done
|
||||
fi
|
||||
cd /sysroot/run
|
||||
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
|
||||
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
|
||||
if [ ! -f /sysroot/tmp/installdisk ]; then
|
||||
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
|
||||
while [ ! -f /sysroot/tmp/installdisk ]; do
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
lvm vgchange -a n
|
||||
udevadm control -e
|
||||
chroot /sysroot /usr/lib/systemd/systemd-udevd --daemon
|
||||
chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python image2disk.py"
|
||||
echo "Port 22" >> /etc/ssh/sshd_config
|
||||
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
|
||||
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
|
||||
kill -HUP $(cat /run/sshd.pid)
|
||||
|
||||
chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh"
|
||||
chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[Unit]
|
||||
Description=Confluent onboot hook
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/confluent/bin/onboot.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script is executed on each boot as it is
|
||||
# completed. It is best to edit the middle of the file as
|
||||
# noted below so custom commands are executed before
|
||||
# the script notifies confluent that install is fully complete.
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
timedatectl set-timezone $(grep ^timezone: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
export nodename confluent_mgr confluent_profile
|
||||
. /etc/confluent/functions
|
||||
mkdir -p /var/log/confluent
|
||||
chmod 700 /var/log/confluent
|
||||
exec >> /var/log/confluent/confluent-onboot.log
|
||||
exec 2>> /var/log/confluent/confluent-onboot.log
|
||||
chmod 600 /var/log/confluent/confluent-onboot.log
|
||||
tail -f /var/log/confluent/confluent-onboot.log > /dev/console &
|
||||
logshowpid=$!
|
||||
|
||||
run_remote_python syncfileclient
|
||||
run_remote_python confignet
|
||||
|
||||
run_remote onboot.custom
|
||||
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
|
||||
run_remote_parts onboot.d
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
|
||||
run_remote_config onboot.d
|
||||
|
||||
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user