mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
479 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbee00d3bb | |||
| 14bb841b2d | |||
| 03a75ec67d | |||
| 269300119a | |||
| a2edf46b8a | |||
| 74e011e964 | |||
| 66372bea0c | |||
| d250a3e850 | |||
| 49b69a2e19 | |||
| 93d4847763 | |||
| b283b50cc6 | |||
| 1f320af8e5 | |||
| 5fea265dc8 | |||
| b74d648e7f | |||
| 380f21853c | |||
| 9900d76b0c | |||
| aa7701ea3c | |||
| 3044c662ad | |||
| 7e02d1d008 | |||
| f321e06e6e | |||
| 5c0af9ebc7 | |||
| c146b50b5b | |||
| cae8b2d0f8 | |||
| 2664adefa4 | |||
| c5f03ee62d | |||
| a932e91b17 | |||
| 1c00aa3c4e | |||
| bd889f4c78 | |||
| 409a0796a1 | |||
| 70ffef6b06 | |||
| 77a043faa6 | |||
| d69eb6ef12 | |||
| 984c3044fe | |||
| 8543129fb9 | |||
| 257edff3a7 | |||
| 763feda63b | |||
| 508e5b4a46 | |||
| 7676c4fd26 | |||
| c3b954a743 | |||
| a94a341582 | |||
| 08dfd55ea0 | |||
| cf6975ded3 | |||
| d17b1d060c | |||
| fcb4b33e8e | |||
| 121e0727cf | |||
| b5b9631b3f | |||
| c09f96b8c9 | |||
| 196ed31bee | |||
| bb05f72583 | |||
| 68b4f83627 | |||
| 672bc4f754 | |||
| 4df3fda8ea | |||
| f32619aa3e | |||
| dd7b9f0244 | |||
| def534e73f | |||
| 2cb641e734 | |||
| ca78b664c9 | |||
| 0e55d390e3 | |||
| 002146b2f7 | |||
| 917a51a406 | |||
| d3fa08d78b | |||
| 38eb88249e | |||
| c333c3eb9c | |||
| f32a9a2f08 | |||
| f584b9bc11 | |||
| affbbcc89b | |||
| 45cc4b7788 | |||
| 945f108548 | |||
| 1f9c440525 | |||
| 8397c5defc | |||
| b7af6b5c27 | |||
| c38aac88df | |||
| 6d1348f5fa | |||
| c05f18d865 | |||
| bf00c75f43 | |||
| 2e04647c95 | |||
| 479ddd582e | |||
| d82690f0d9 | |||
| 34cfd99fd8 | |||
| 9009f63b2d | |||
| ab6bf82da6 | |||
| 0fae615756 | |||
| 37dca72579 | |||
| 1aea406852 | |||
| 374b87e2d7 | |||
| 3469c8ab2b | |||
| e08ca5fb7e | |||
| 23dffe882e | |||
| b6fb91b228 | |||
| ac4d1fa8e1 | |||
| 1d902c4d90 | |||
| f11687998f | |||
| 153956b2cd | |||
| 64d5081be3 | |||
| 8d16b412ae | |||
| c5ec34d5a5 | |||
| 6a4086679c | |||
| 3d2422ded3 | |||
| 7d0f47bbcb | |||
| 9a779f2dd2 | |||
| 4724d9e45b | |||
| 8404ddf3a2 | |||
| 872a13589c | |||
| 433a22e6e7 | |||
| 1f238a3f8b | |||
| 670fc87e1d | |||
| 3be3d4a588 | |||
| 14d13749ad | |||
| 92a10ede11 | |||
| d9051e80d3 | |||
| 2d43fac1b5 | |||
| 4a0d419f85 | |||
| 7fb5f9756d | |||
| b482410072 | |||
| 4d47f58e53 | |||
| c5bb55e8f2 | |||
| 2320718618 | |||
| d64b3e6a4e | |||
| 637e0f0a81 | |||
| e26218eb92 | |||
| 03eb026b61 | |||
| beab6a3c02 | |||
| 5e0ebce300 | |||
| 85c4ec5654 | |||
| 7957a6abd4 | |||
| 39a7d8d131 | |||
| 54c5bf128e | |||
| ca0c592044 | |||
| 829d1316b2 | |||
| c2c1c85651 | |||
| c5833f1417 | |||
| 07ae3593c3 | |||
| 8ab35b11cd | |||
| 35ef6170ba | |||
| d650f11255 | |||
| b4926b39fe | |||
| 53ccee734f | |||
| fe9d0e84d3 | |||
| 4e7e12f90f | |||
| 8ae68cd34a | |||
| 607d69b1e1 | |||
| 75e13239c2 | |||
| c88d8d047a | |||
| 304fbba2bb | |||
| d2f35ccbde | |||
| 273c32d4a7 | |||
| 1616999cb9 | |||
| 195db5c6e8 | |||
| fa0e216c7e | |||
| 1605169b92 | |||
| 7fa0016536 | |||
| b4d26c3c4e | |||
| 7470d9b55c | |||
| aaf1fb8c94 | |||
| 50e62c7b73 | |||
| 363eff831f | |||
| 31820ddfd7 | |||
| 153749aebe | |||
| 7c5dd85e74 | |||
| 7a5bf8a06f | |||
| 9c05057deb | |||
| 8b1902601f | |||
| d94d9d0a78 | |||
| 457b0851c9 | |||
| d2cae9faca | |||
| e27739eafd | |||
| 874302537d | |||
| cd3b67d4cc | |||
| 7aacac2fb7 | |||
| a69fb3fec3 | |||
| 2d9cbe4f33 | |||
| e33ed0afc8 | |||
| ace6544444 | |||
| 1468c56e1f | |||
| b36756ebbc | |||
| a8f378deb7 | |||
| cb4e53c459 | |||
| 3e0b283593 | |||
| 3b4e9fd2e4 | |||
| 6af6195798 | |||
| a8d1f484b5 | |||
| 619c60533d | |||
| c6fd760006 | |||
| 03f88c6e3e | |||
| 697b33ae80 | |||
| 79ef1c4811 | |||
| f442e949aa | |||
| 8050d3f69a | |||
| 66efa1da5c | |||
| df97e808c6 | |||
| 98d14344ce | |||
| a149b0bcd0 | |||
| 0be1e350e9 | |||
| 4818bd57bb | |||
| 7763327a63 | |||
| dac957364f | |||
| 528dd4c3e8 | |||
| e40c362743 | |||
| 7fdb4dccca | |||
| a3c8c305c1 | |||
| aeb1b704b3 | |||
| f2cb6ea535 | |||
| 5a81279b9d | |||
| 9954bf5b51 | |||
| 309ea00f10 | |||
| cafbc1d1c2 | |||
| 797e197bc7 | |||
| 19d4a3a83f | |||
| df328d6812 | |||
| 7da0dfa0bb | |||
| d3a699a8fb | |||
| 82598d02d8 | |||
| 874947076d | |||
| 8032ea36be | |||
| 8ad130e14c | |||
| ffe0754dd9 | |||
| 5aa6fbc019 | |||
| 7026fdfa3a | |||
| 9dbecd4c95 | |||
| d85219d5fa | |||
| bbb8e50c3e | |||
| 26782811d6 | |||
| e38dbc4470 | |||
| a26624a614 | |||
| 4f246e6a41 | |||
| c53e758170 | |||
| 1a7c238b54 | |||
| 6a88f35fc2 | |||
| 7a27fba94b | |||
| 94a4a7e20b | |||
| 2a87c32800 | |||
| d99bc37ed3 | |||
| 2c40d2fd0d | |||
| 611927d2ff | |||
| eb9ae61e75 | |||
| 4ae102018a | |||
| 4f01545328 | |||
| 762fb8259d | |||
| 2ca146c1aa | |||
| 16f40efb69 | |||
| a51a428761 | |||
| a5261cc1da | |||
| c8166b7cce | |||
| a11317c994 | |||
| 279f622026 | |||
| 32fa07d9ce | |||
| c9157b90eb | |||
| 2e63ff3aca | |||
| 5d0423c38b | |||
| e3c17491e5 | |||
| da7b7c8923 | |||
| 5861af799c | |||
| 2b0353c771 | |||
| bc6c3c5e68 | |||
| 37ec16058f | |||
| b3efd2f31d | |||
| 357ebfca3f | |||
| 7e0cd9d709 | |||
| ba321551e4 | |||
| d96026ef5f | |||
| 08aa667b13 | |||
| 0cb93c4bea | |||
| 7b3ecc6e4f | |||
| 5a5b3f9927 | |||
| 4455226ee0 | |||
| 0881d74731 | |||
| 55d7191573 | |||
| bc9d0a9d3b | |||
| 866f138652 | |||
| ee8ad66959 | |||
| be0e47142b | |||
| 4131f1ba4f | |||
| f2966bfeaf | |||
| eee6330aaf | |||
| aa9ece529f | |||
| 865d18d367 | |||
| e583d34555 | |||
| b27d07f304 | |||
| 50e60a5a17 | |||
| 8bf7e045d5 | |||
| f0f4427dc3 | |||
| b1f1f15ba8 | |||
| 09ca449eab | |||
| d08ce9e563 | |||
| ce92ea4084 | |||
| 0695f8e827 | |||
| 7609a46594 | |||
| 247838f70c | |||
| 594d419ca0 | |||
| da0c83513a | |||
| 5ed0b50110 | |||
| e01a7cf4fb | |||
| ad92ab13f3 | |||
| 236e5466cb | |||
| cc16e58c3a | |||
| 0ff9d5dc7e | |||
| 95466392f9 | |||
| b3857f8d33 | |||
| f176ebe4c2 | |||
| e24852c480 | |||
| 008089c4c0 | |||
| f34e184d31 | |||
| 814257fbf8 | |||
| 83d92ecfcc | |||
| 7eb06f2722 | |||
| 0fbe39690e | |||
| ec2ad9861a | |||
| 16096ad745 | |||
| 481a70c304 | |||
| facd501100 | |||
| 4c6f41ffb2 | |||
| 3e93ab1966 | |||
| 0b5c4f6f0f | |||
| e8778cb992 | |||
| 2fae35b2c4 | |||
| 08c7bd4e42 | |||
| 1a17bf8fbb | |||
| f21b0570e8 | |||
| 9cac77cac7 | |||
| 3831d409f5 | |||
| 10fcb5fa6d | |||
| ff00f48e58 | |||
| 225a49f05e | |||
| d3c9c90f35 | |||
| 78592bfe2a | |||
| 1b2f5b6019 | |||
| 3c6511a0e7 | |||
| c525a08c17 | |||
| edaaafa059 | |||
| f16e84de32 | |||
| 4c99bc142b | |||
| 8e4f5fcae6 | |||
| c4d3cb409d | |||
| 9eed1378f6 | |||
| 5f282dd40a | |||
| 6e88a44399 | |||
| 94e731274a | |||
| 50c150f4b4 | |||
| a46810ca29 | |||
| 76cdd958ec | |||
| e72292f989 | |||
| 8cf264602d | |||
| a2f5b11185 | |||
| ed4db91383 | |||
| a9e39eab96 | |||
| 209430ed35 | |||
| d29d2bf683 | |||
| 3d2b579f1a | |||
| c99ab2ac73 | |||
| e4591eaf57 | |||
| 695bb3757b | |||
| 4e2767ce9d | |||
| 8cdc9c9479 | |||
| 6458eac93b | |||
| 8df15b3a54 | |||
| b4f9bb78ae | |||
| c8e1efecdb | |||
| 22dc852277 | |||
| 784ac5ecba | |||
| 66c9777b3c | |||
| dba4c40f0e | |||
| 6997508a0c | |||
| 203253e05f | |||
| edc4804146 | |||
| 7cfdf11bf2 | |||
| a3bd21d605 | |||
| 6d8474a16a | |||
| 5736c41daa | |||
| a5c4b64c60 | |||
| f7a940227d | |||
| ebf50359f0 | |||
| 5160023cc4 | |||
| a738b761b4 | |||
| d27ef81e32 | |||
| f5344fabaa | |||
| fa1c2f5c1e | |||
| 25c3f40559 | |||
| 5812a0eef6 | |||
| 086ce9823b | |||
| 2d6bdffebe | |||
| efdbeeae0d | |||
| a2a1142f18 | |||
| 8c89deaa95 | |||
| 1ec5231ebe | |||
| 674e2887f3 | |||
| 4768bc257a | |||
| 7610f9b963 | |||
| b29e7bc94a | |||
| 04d63a269d | |||
| e1bf22911b | |||
| d6642f1bde | |||
| 36f027ac71 | |||
| c025f4d2fc | |||
| 1238babe60 | |||
| f9a82bde00 | |||
| 48c868e935 | |||
| caf9115439 | |||
| 8b11acbcf2 | |||
| db0f91c160 | |||
| cbb46dec3a | |||
| 0afa4c217c | |||
| 47f04c8462 | |||
| 5b0e23b8d4 | |||
| 14d9284cc5 | |||
| cd251fa5d6 | |||
| 8d47395e53 | |||
| d19b5e4376 | |||
| 7a9276300a | |||
| 87ef68e26a | |||
| 55b97793fd | |||
| fa823510b6 | |||
| 99609aa669 | |||
| 906011a80b | |||
| ff7f5daac6 | |||
| 2d58741f15 | |||
| 57b74d59af | |||
| 191cd8192a | |||
| 475eaca56b | |||
| f33ddf3ab9 | |||
| 3422f3cdc5 | |||
| 4c74581f0c | |||
| 674d32e9e5 | |||
| 666059c8bf | |||
| 0137f99636 | |||
| 0c66021d3e | |||
| 014727d355 | |||
| dc262c366c | |||
| 8f99d87fda | |||
| edaaa2393d | |||
| 1ecef6f251 | |||
| 31c2c5f6f7 | |||
| c8747ac369 | |||
| f7e7d05729 | |||
| 40c74699f0 | |||
| 3903cda789 | |||
| 72049657d7 | |||
| 71cc0adadd | |||
| b4e6e7caa8 | |||
| 10ac1756f1 | |||
| 95659db00a | |||
| bddbc37e8e | |||
| 7a2b295945 | |||
| af8429ebf9 | |||
| 3ac6677d2d | |||
| 8b5744b7eb | |||
| 7fcfc05205 | |||
| 4b42bbda7e | |||
| ed41d93de5 | |||
| d36712d014 | |||
| 21cc9d66db | |||
| 4508cfa364 | |||
| 05e84f2a7c | |||
| 184727408a | |||
| e7fbbe2737 | |||
| 9a0c4ce4ce | |||
| 247a7f5d8a | |||
| 745b82a603 | |||
| 504bee2d2a | |||
| 8285f2a3de | |||
| cfa97f7a9a | |||
| cbf42469c3 | |||
| 61f793040e | |||
| 8dd66211b7 | |||
| 5a24619560 | |||
| d466595828 | |||
| b0b965db98 | |||
| 9e73979b5b | |||
| f4395abade | |||
| a194e2293e | |||
| d27577d2b7 | |||
| 1113c2a849 | |||
| 587197e934 | |||
| ef901f64af | |||
| 2ba05fb7b1 | |||
| a263851614 | |||
| eeb3a3fa65 | |||
| 56f8ca0982 | |||
| 8f94149627 | |||
| ed842fcc1a |
@@ -1,4 +1,7 @@
|
||||
*.pyc
|
||||
.*.
|
||||
confluent_client/man/man*
|
||||
.vscode
|
||||
.*.sw*
|
||||
.sw*
|
||||
.idea/*
|
||||
|
||||
+105
-86
@@ -373,7 +373,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':
|
||||
@@ -663,6 +663,28 @@ 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'
|
||||
@@ -698,13 +720,13 @@ def conserver_command(filehandle, localcommand):
|
||||
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
|
||||
|
||||
if localcommand[1] == 'o': # off
|
||||
print("powering off...")
|
||||
session.simple_noderange_command(consolename, '/power/state', 'off')
|
||||
print("complete]\r")
|
||||
sys.stdout.write("powering off...")
|
||||
sys.stdout.flush()
|
||||
run_inline_command('/power/state', 'off', 'complete]')
|
||||
elif localcommand[1] == 's': # shutdown
|
||||
print("shutting down...")
|
||||
session.simple_noderange_command(consolename, '/power/state', 'shutdown')
|
||||
print("complete]\r")
|
||||
sys.stdout.write("shutting down...")
|
||||
sys.stdout.flush()
|
||||
run_inline_command('/power/state', 'shutdown', 'complete]')
|
||||
elif localcommand[1] == 'b': # boot
|
||||
cmdlen += 1
|
||||
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
|
||||
@@ -715,59 +737,46 @@ def conserver_command(filehandle, localcommand):
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'setup'
|
||||
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
print("Error]")
|
||||
|
||||
elif localcommand[2] == 'n': # boot to network
|
||||
print("booting to network...")
|
||||
|
||||
sys.stdout.write("booting to network...")
|
||||
sys.stdout.flush()
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'network'
|
||||
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
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 = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
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')
|
||||
@@ -929,63 +938,7 @@ def main():
|
||||
rdylist = ()
|
||||
for fh in rdylist:
|
||||
if fh == session.connection:
|
||||
# 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()
|
||||
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")
|
||||
break
|
||||
consume_termdata(fh)
|
||||
else:
|
||||
try:
|
||||
myinput = fh.read()
|
||||
@@ -1011,6 +964,64 @@ 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")
|
||||
|
||||
if __name__ == '__main__':
|
||||
errcode = 0
|
||||
deadline = 0
|
||||
@@ -1020,6 +1031,14 @@ 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]')
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def create_image(directory, image):
|
||||
def create_image(directory, image, label=None):
|
||||
ents = 0
|
||||
datasz = 512
|
||||
for dir in os.walk(sys.argv[1]):
|
||||
@@ -25,8 +25,13 @@ def create_image(directory, image):
|
||||
with open(image, 'wb') as imgfile:
|
||||
imgfile.seek(datasz * 512 - 1)
|
||||
imgfile.write(b'\x00')
|
||||
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
|
||||
str(datasz), '-s', '1','-h', '1', '::'])
|
||||
if label:
|
||||
subprocess.check_call(['mformat', '-i', image, '-v', label,
|
||||
'-r', '16', '-d', '1', '-t', str(datasz),
|
||||
'-s', '1','-h', '1', '::'])
|
||||
else:
|
||||
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
|
||||
str(datasz), '-s', '1','-h', '1', '::'])
|
||||
# Some clustered filesystems will have the lock from mformat
|
||||
# linger after close (mformat doesn't unlock)
|
||||
# do a blocking wait for shared lock and then explicitly
|
||||
@@ -50,4 +55,7 @@ if __name__ == '__main__':
|
||||
sys.stderr.write("Usage: {0} <directory> <imagefile>".format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
create_image(sys.argv[1], sys.argv[2])
|
||||
label = None
|
||||
if len(sys.argv) > 3:
|
||||
label = sys.argv[3]
|
||||
create_image(sys.argv[1], sys.argv[2], label)
|
||||
@@ -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
|
||||
|
||||
@@ -134,9 +134,10 @@ def _assign_value():
|
||||
assignment[key] = value
|
||||
|
||||
|
||||
def parse_config_line(arguments):
|
||||
def parse_config_line(arguments, single=False):
|
||||
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
|
||||
for param in arguments:
|
||||
for pidx in range(0, len(arguments)):
|
||||
param = arguments[pidx]
|
||||
if param == 'show':
|
||||
continue # forgive muscle memory of pasu users
|
||||
if param == 'set':
|
||||
@@ -146,14 +147,19 @@ def parse_config_line(arguments):
|
||||
if needval:
|
||||
key = needval
|
||||
needval = None
|
||||
value = param
|
||||
if single:
|
||||
value = ' '.join(arguments[pidx:])
|
||||
_assign_value()
|
||||
break
|
||||
else:
|
||||
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')
|
||||
bailout('Cannot do set and query in same command: Query detected but "{0}" appears to be set'.format(param))
|
||||
if '=' in param:
|
||||
key, _, value = param.partition('=')
|
||||
_assign_value()
|
||||
@@ -165,7 +171,7 @@ def parse_config_line(arguments):
|
||||
if setmode is None:
|
||||
setmode = False
|
||||
if setmode != False:
|
||||
bailout('Cannot do set and query in same command')
|
||||
bailout('Cannot do set and query in same command: Set mode detected but "{0}" appears to be a query'.format(param))
|
||||
if '.' not in param:
|
||||
if param == 'bmc':
|
||||
printallbmc = True
|
||||
@@ -215,7 +221,7 @@ if options.batch:
|
||||
pass
|
||||
argset = argset.strip()
|
||||
if argset:
|
||||
parse_config_line(shlex.split(argset))
|
||||
parse_config_line(shlex.split(argset), single=True)
|
||||
argset = argfile.readline()
|
||||
else:
|
||||
parse_config_line(args[1:])
|
||||
@@ -272,7 +278,6 @@ 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']
|
||||
|
||||
@@ -79,7 +79,7 @@ def main(args):
|
||||
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('profile is a required argument to request a network deployment\n')
|
||||
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')
|
||||
|
||||
@@ -34,6 +34,7 @@ defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
|
||||
columnmapping = {
|
||||
'Node': 'nodename',
|
||||
'Model': 'modelnumber',
|
||||
'Model Name': 'modelname',
|
||||
'Serial': 'serialnumber',
|
||||
'UUID': 'uuid',
|
||||
'Type': 'types',
|
||||
@@ -148,6 +149,7 @@ 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))
|
||||
@@ -188,6 +190,7 @@ 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'])
|
||||
@@ -198,11 +201,14 @@ 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):
|
||||
@@ -263,7 +269,11 @@ def list_matching_macs(options, session, node=None, checknode=True):
|
||||
return [options.mac.replace(':', '-')]
|
||||
else:
|
||||
path += 'by-mac/'
|
||||
return [x['item']['href'] for x in session.read(path)]
|
||||
ret = []
|
||||
for x in session.read(path):
|
||||
if 'item' in x and 'href' in x['item']:
|
||||
ret.append(x['item']['href'])
|
||||
return ret
|
||||
|
||||
def assign_discovery(options, session, needid=True):
|
||||
abort = False
|
||||
@@ -287,12 +297,18 @@ 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'}))
|
||||
|
||||
@@ -109,6 +109,12 @@ 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', ()):
|
||||
@@ -149,7 +155,6 @@ def show_firmware(session):
|
||||
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']:
|
||||
|
||||
@@ -69,6 +69,12 @@ 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,6 +111,12 @@ 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:
|
||||
@@ -187,5 +193,7 @@ def main():
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -32,6 +32,13 @@ 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 = {
|
||||
@@ -209,7 +216,7 @@ def main():
|
||||
headernames.append(headername)
|
||||
if options.csv:
|
||||
linebyline = False
|
||||
csvwriter = csv.writer(sys.stdout)
|
||||
csvwriter = csv.writer(sys.stdout, dialect='hybrid')
|
||||
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|floppy]')
|
||||
usage='Usage: %prog [options] noderange [default|cd|network|setup|hd|usb|floppy]')
|
||||
argparser.add_option('-b', '--bios', dest='biosmode',
|
||||
action='store_true', default=False,
|
||||
help='Request BIOS style boot (rather than UEFI)')
|
||||
|
||||
@@ -48,6 +48,8 @@ def run():
|
||||
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('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
@@ -71,6 +73,19 @@ 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']
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
@@ -79,12 +94,13 @@ def run():
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value']
|
||||
sshnode = nodemap.get(node, node)
|
||||
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
|
||||
cmd = cmd.encode('utf-8')
|
||||
if options.port:
|
||||
cmdv = ['ssh', '-p', '{0}'.format(options.port), node, cmd]
|
||||
cmdv = ['ssh', '-p', '{0}'.format(options.port), sshnode, cmd]
|
||||
else:
|
||||
cmdv = ['ssh', node, cmd]
|
||||
cmdv = ['ssh', sshnode, cmd]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
|
||||
@@ -186,7 +186,7 @@ funmap = {
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage='Usage: %prog <noderange> [show|create|delete|diskset]',
|
||||
usage='Usage: %prog <noderange> [show|create|delete|diskset] [hotspare|jbod|unconfigured] [options]',
|
||||
epilog='',
|
||||
)
|
||||
argparser.add_option('-r', '--raidlevel', type='int',
|
||||
|
||||
@@ -116,7 +116,7 @@ class OptParser(optparse.OptionParser):
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage="Usage: %prog <noderange> servicedata "
|
||||
"<filename>",
|
||||
"<directory or 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,17 +22,23 @@ import io
|
||||
import numpy as np
|
||||
|
||||
import os
|
||||
import sixel
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
class DumbWriter(sixel.SixelWriter):
|
||||
def restore_position(self, output):
|
||||
return
|
||||
try:
|
||||
import sixel
|
||||
|
||||
class DumbWriter(sixel.SixelWriter):
|
||||
def restore_position(self, output):
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -46,6 +46,13 @@ try:
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
class NestedDict(dict):
|
||||
def __missing__(self, key):
|
||||
value = self[key] = type(self)()
|
||||
return value
|
||||
|
||||
|
||||
def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
# current python version
|
||||
@@ -111,6 +118,12 @@ 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']))
|
||||
@@ -148,6 +161,7 @@ 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']
|
||||
@@ -157,11 +171,13 @@ 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()
|
||||
tlvdata.recv(self.connection)
|
||||
self.protversion = int(tlvdata.recv(self.connection).split(
|
||||
b'--')[1].strip()[1:])
|
||||
authdata = tlvdata.recv(self.connection)
|
||||
if authdata['authpassed'] == 1:
|
||||
self.authenticated = True
|
||||
@@ -172,6 +188,13 @@ 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})
|
||||
@@ -411,7 +434,7 @@ def send_request(operation, path, server, parameters=None):
|
||||
result = tlvdata.recv(server)
|
||||
|
||||
|
||||
def attrrequested(attr, attrlist, seenattributes):
|
||||
def attrrequested(attr, attrlist, seenattributes, node=None):
|
||||
for candidate in attrlist:
|
||||
truename = candidate
|
||||
if candidate.startswith('hm'):
|
||||
@@ -419,10 +442,16 @@ def attrrequested(attr, attrlist, seenattributes):
|
||||
if candidate in _attraliases:
|
||||
candidate = _attraliases[candidate]
|
||||
if fnmatch.fnmatch(attr.lower(), candidate.lower()):
|
||||
seenattributes.add(truename)
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = True
|
||||
return True
|
||||
elif attr.lower().startswith(candidate.lower() + '.'):
|
||||
seenattributes.add(truename)
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = 1
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -438,13 +467,15 @@ def _sort_attrib(k):
|
||||
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
seenattributes = set([])
|
||||
seenattributes = NestedDict()
|
||||
allnodes = 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))
|
||||
@@ -452,7 +483,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
if attr == 'errorcode':
|
||||
exitcode |= val
|
||||
continue
|
||||
seenattributes.add(attr)
|
||||
seenattributes[node][attr] = True
|
||||
if rename:
|
||||
printattr = rename.get(attr, attr)
|
||||
else:
|
||||
@@ -460,7 +491,7 @@ 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):
|
||||
if show_attr(attr, requestargs, seenattributes, options, node):
|
||||
if 'value' in currattr:
|
||||
if currattr['value'] is not None:
|
||||
val = currattr['value']
|
||||
@@ -541,23 +572,39 @@ 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:
|
||||
if attr not in seenattributes:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
exitcode = 1
|
||||
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))
|
||||
return exitcode
|
||||
|
||||
|
||||
def show_attr(attr, requestargs, seenattributes, options):
|
||||
def show_attr(attr, requestargs, seenattributes, options, node):
|
||||
try:
|
||||
reverse = options.exclude
|
||||
except AttributeError:
|
||||
reverse = False
|
||||
if requestargs is None or requestargs == []:
|
||||
return True
|
||||
processattr = attrrequested(attr, requestargs, seenattributes)
|
||||
processattr = attrrequested(attr, requestargs, seenattributes, node)
|
||||
if reverse:
|
||||
processattr = not processattr
|
||||
return processattr
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
|
||||
|
||||
class Types(object):
|
||||
text, json = range(2)
|
||||
text, json, filehandle = range(3)
|
||||
|
||||
@@ -15,9 +15,15 @@
|
||||
# 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
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.select as select
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
|
||||
try:
|
||||
@@ -30,6 +36,72 @@ 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:
|
||||
@@ -65,7 +137,7 @@ def _unicode_list(currlist):
|
||||
_unicode_list(currlist[i])
|
||||
|
||||
|
||||
def send(handle, data):
|
||||
def send(handle, data, filehandle=None):
|
||||
if isinstance(data, unicode):
|
||||
try:
|
||||
data = data.encode('utf-8')
|
||||
@@ -93,9 +165,27 @@ def send(handle, data):
|
||||
if tl > 16777215:
|
||||
raise Exception("JSON data exceeds protocol limits")
|
||||
# xor in the type (0b1 << 24)
|
||||
tl |= 16777216
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
handle.sendall(sdata)
|
||||
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)
|
||||
|
||||
|
||||
def recvall(handle, size):
|
||||
rd = handle.recv(size)
|
||||
@@ -125,12 +215,42 @@ def recv(handle):
|
||||
datatype = (tl & 2130706432) >> 24 # grab 7 bits from near beginning
|
||||
if dlen == 0:
|
||||
return None
|
||||
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.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:
|
||||
filehandles.fromstring(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
|
||||
if datatype == tlv.Types.text:
|
||||
return data
|
||||
elif datatype == tlv.Types.json:
|
||||
|
||||
@@ -91,7 +91,7 @@ _confluent_nodeidentify_completion()
|
||||
|
||||
_confluent_nodesetboot_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("default,cd,network,setup,hd,floppy -h -b -p")
|
||||
COMP_CANDIDATES=("default,cd,network,setup,hd,floppy,usb -h -b -p")
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ collate(1) -- Organize text input by node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
`<other command> | collate [-a] [-b] [-d] [-w] [-g] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -21,30 +21,41 @@ 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.
|
||||
* `-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
|
||||
|
||||
## EXAMPLES
|
||||
* Organizing power state of multiple nodes:
|
||||
|
||||
@@ -6,7 +6,8 @@ collective(1) -- Check and manage a confluent collective
|
||||
`collective invite <server>`
|
||||
`collective join <server> [-i TOKEN]`
|
||||
`collective show`
|
||||
`collective gencert`
|
||||
`collective gencert`
|
||||
`collective delete`
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
@@ -36,6 +37,9 @@ 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,6 +15,21 @@ 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
|
||||
|
||||
@@ -3,8 +3,7 @@ confluentdbutil(8) -- Backup or restore confluent database
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
|
||||
`confluentdbutil [options] <dump|restore> <path>`
|
||||
`confluentdbutil [options] [dump|restore] <path>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -19,8 +18,25 @@ the json files (password protected, removed from the files, or unprotected).
|
||||
* `-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.
|
||||
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
|
||||
|
||||
@@ -40,12 +40,25 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
|
||||
* `-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.
|
||||
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
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
|
||||
@@ -9,6 +9,15 @@ 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,8 +3,7 @@ nodeboot(8) -- Reboot a confluent node to a specific device
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeboot <noderange>`
|
||||
`nodeboot [options] <noderange>` [default|cd|network|setup|hd]
|
||||
`nodeboot [options] <noderange> [default|cd|network|setup|hd]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -25,6 +24,13 @@ 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,8 +3,7 @@ nodeconfig(8) -- Show or change node configuration
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeconfig <noderange> [options] [<configuration>..]`
|
||||
`nodeconfig <noderange> [options] [<configuration=value>..]`
|
||||
`nodeconfig [options] <noderange> [setting|setting=value]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -14,6 +13,12 @@ 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`:
|
||||
@@ -21,6 +26,13 @@ given as a node expression, as documented in the man page for nodeattribexpressi
|
||||
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`, `--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.
|
||||
@@ -31,18 +43,21 @@ given as a node expression, as documented in the man page for nodeattribexpressi
|
||||
Rather than listing only the specified configuration parameters, list all
|
||||
attributes except for the specified ones
|
||||
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
* `-a`, `--advanced`:
|
||||
Include advanced settings, which are normally not intended to be used
|
||||
without direction from the relevant server vendor.
|
||||
|
||||
* `-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.
|
||||
|
||||
* `-b`, '--batch':
|
||||
Provide arguments as lines of a file, rather than the command line.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to configure, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Showing the current IP configuration of noderange BMC/IMM/XCC:
|
||||
|
||||
@@ -3,9 +3,7 @@ nodedeploy(8) -- Request preparation and/or initiating a node deployment
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodedeploy <noderange>`
|
||||
`nodedeploy <noderange> -c`
|
||||
`nodedeploy <noderange> [-n] [-p] <name of profile>`
|
||||
`nodedeploy [-h] [-c] [-n] [-p] [-m MAXNODES] <noderange> [profile]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -19,7 +17,7 @@ deployment status.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* '-c', '--clear`:
|
||||
* `-c`, `--clear`:
|
||||
Remove any pending deployment action
|
||||
|
||||
* `-n`, `--network`:
|
||||
@@ -28,6 +26,12 @@ deployment status.
|
||||
* `-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:
|
||||
|
||||
@@ -3,14 +3,22 @@ nodeeventlog(8) -- Pull eventlog from confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeeventlog <noderange>`
|
||||
`nodeeventlog <noderange> [clear]`
|
||||
`nodeeventlog [options] <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,9 +3,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodefirmware <noderange>`
|
||||
`nodefirmware <noderange> list|<components>|core`
|
||||
`nodefirmware <noderange> update [--backup] <filename>`
|
||||
`nodefirmware <noderange> [list][update [--backup <file>]]|[<components>]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -26,6 +24,18 @@ 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,8 +25,20 @@ 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
|
||||
|
||||
|
||||
@@ -9,9 +9,16 @@ nodelicense(8) -- Manage license keys on BMC
|
||||
|
||||
`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 th eBMC. The `save` subcommand will take the passed directory (which may be in the form
|
||||
from the BMC. 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
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Listing currently mounted media:
|
||||
|
||||
@@ -30,6 +30,13 @@ off will not react to this request.
|
||||
|
||||
* `-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,6 +9,14 @@ 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
|
||||
|
||||
@@ -22,4 +30,3 @@ noderemove(8) -- Remove nodes from the confluent management service
|
||||
`r2u3: deleted`
|
||||
`r2u2: deleted`
|
||||
`r2u1: deleted`
|
||||
|
||||
|
||||
@@ -11,9 +11,16 @@ 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,10 +12,14 @@ noderange. This will present progress as percentage for all nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`:
|
||||
Specify maximum number of nodes for noderange max.
|
||||
|
||||
* `-c`:
|
||||
* `-c`, `-f`, `--count`:
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -13,6 +13,21 @@ 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`, `-c`, `--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:
|
||||
|
||||
@@ -2,9 +2,8 @@ nodesetboot(8) -- Check or set next boot device for noderange
|
||||
====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodesetboot <noderange>`
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd|floppy]`
|
||||
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd|usb|floppy]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -21,6 +20,8 @@ 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`:
|
||||
@@ -33,6 +34,13 @@ control.
|
||||
|
||||
* `-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
|
||||
@@ -46,6 +54,10 @@ control.
|
||||
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 <noderange> [options] <command to execute on each node>`
|
||||
`nodeshell [options] <noderange> <command to execute on each node>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,9 +13,26 @@ 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
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`
|
||||
Specify a maximum number of nodes to run remote ssh command to, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-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.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -3,12 +3,15 @@ nodestorage(8) -- Examine/Modify storage configuration of a node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodestorage <noderange> [show|create|delete] [options]`
|
||||
`nodestorage <noderange> [show|create|delete|diskset] [hotspare|jbod|unconfigured] [options]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodestorage` provides access to the remote storage configuration of
|
||||
the noderange.
|
||||
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`.
|
||||
|
||||
## OPTIONS
|
||||
* `-r` **RAIDLEVEL**, `--raidlevel`=**RAIDLEVEL**:
|
||||
@@ -29,6 +32,15 @@ the noderange.
|
||||
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,6 +19,15 @@ 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
|
||||
|
||||
@@ -5,7 +5,7 @@ osdeploy(8) --- Configure general OS deployment facilities of confluent
|
||||
|
||||
`osdeploy import <iso>`
|
||||
`osdeploy updateboot <profile>`
|
||||
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l]`
|
||||
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l] [-a]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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.
|
||||
|
||||
@@ -10,13 +10,32 @@ import ssl
|
||||
import sys
|
||||
|
||||
def get_apikey(nodename, mgr):
|
||||
sealnew = True
|
||||
if os.path.exists('/etc/confluent/confluent.apikey'):
|
||||
return open('/etc/confluent/confluent.apikey').read().strip()
|
||||
apikey = subprocess.check_output(['/opt/confluent/bin/clortho', nodename, mgr])
|
||||
if not isinstance(apikey, str):
|
||||
apikey = apikey.decode('utf8')
|
||||
if apikey.startswith('SEALED:'):
|
||||
sealnew = False
|
||||
with open('/etc/confluent/confluent.sealedapikey', 'w+') as apiout:
|
||||
apiout.write(apikey[7:])
|
||||
with open('/etc/confluent/confluent.sealedapikey') as inp:
|
||||
sp = subprocess.Popen(['/usr/bin/clevis-decrypt-tpm2'],
|
||||
stdin=inp, stdout=subprocess.PIPE)
|
||||
apikey = sp.communicate()[0]
|
||||
if not isinstance(apikey, str):
|
||||
apikey = apikey.decode('utf8')
|
||||
with open('/etc/confluent/confluent.apikey', 'w+') as apiout:
|
||||
apiout.write(apikey)
|
||||
if sealnew and os.path.exists('/usr/bin/clevis-encrypt-tpm2'):
|
||||
try:
|
||||
with open('/etc/confluent/confluent.apikey') as apin:
|
||||
sealed = subprocess.check_output(
|
||||
['/usr/bin/clevis-encrypt-tpm2', '{}'], stdin=apin)
|
||||
print(HTTPSClient().grab_url('/confluent-api/self/saveapikey', sealed).decode())
|
||||
except Exception:
|
||||
sys.stderr.write('Unable to persist API key through TPM2 sealing\n')
|
||||
apikey = apikey.strip()
|
||||
os.chmod('/etc/confluent/confluent.apikey', 0o600)
|
||||
return apikey
|
||||
@@ -47,6 +66,15 @@ class HTTPSClient(client.HTTPConnection, object):
|
||||
ifout.write(ifidx)
|
||||
if json:
|
||||
self.stdheaders['ACCEPT'] = 'application/json'
|
||||
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: '):
|
||||
host = line.split(': ', 1)[1]
|
||||
break
|
||||
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, host)
|
||||
if mgtiface:
|
||||
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
|
||||
@@ -72,6 +100,9 @@ class HTTPSClient(client.HTTPConnection, object):
|
||||
self.sock = ctx.wrap_socket(psock, server_hostname=host)
|
||||
|
||||
def grab_url(self, url, data=None, returnrsp=False):
|
||||
return self.grab_url_with_status(url, data, returnrsp)[1]
|
||||
|
||||
def grab_url_with_status(self, url, data=None, returnrsp=False):
|
||||
if data:
|
||||
method = 'POST'
|
||||
else:
|
||||
@@ -80,9 +111,9 @@ class HTTPSClient(client.HTTPConnection, object):
|
||||
rsp = self.getresponse()
|
||||
if rsp.status >= 200 and rsp.status < 300:
|
||||
if returnrsp:
|
||||
return rsp
|
||||
return rsp.status, rsp
|
||||
else:
|
||||
return rsp.read()
|
||||
return rsp.status, rsp.read()
|
||||
raise Exception(rsp.read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -93,8 +124,26 @@ if __name__ == '__main__':
|
||||
if len(sys.argv) == 1:
|
||||
HTTPSClient()
|
||||
sys.exit(0)
|
||||
if sys.argv[-2] == '-o':
|
||||
with open(sys.argv[3], 'wb') as outf:
|
||||
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
|
||||
if outbin:
|
||||
with open(outbin, 'ab+') as outf:
|
||||
reader = HTTPSClient(json=json).grab_url(
|
||||
sys.argv[1], data, returnrsp=True)
|
||||
chunk = reader.read(16384)
|
||||
@@ -102,6 +151,13 @@ if __name__ == '__main__':
|
||||
outf.write(chunk)
|
||||
chunk = reader.read(16384)
|
||||
sys.exit(0)
|
||||
if os.path.exists(sys.argv[-1]):
|
||||
if len(sys.argv) > 2 and os.path.exists(sys.argv[-1]):
|
||||
data = open(sys.argv[-1]).read()
|
||||
print(HTTPSClient(json=json).grab_url(sys.argv[1], data).decode())
|
||||
if waitfor:
|
||||
client = HTTPSClient(json=json)
|
||||
status = 201
|
||||
while status != waitfor:
|
||||
status, rsp = client.grab_url_with_status(sys.argv[1], data)
|
||||
sys.stdout.write(rsp.decode())
|
||||
else:
|
||||
sys.stdout.write(HTTPSClient(json=json).grab_url(sys.argv[1], data).decode())
|
||||
|
||||
@@ -53,6 +53,7 @@ for os in rhvh4 el7 el8 genesis suse15 ubuntu20.04 esxi6 esxi7; do
|
||||
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
|
||||
find %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles -name .gitignore -exec rm -f {} +
|
||||
done
|
||||
|
||||
%files
|
||||
|
||||
@@ -12,7 +12,7 @@ sed -i 's/install::/install:*:/' /sysroot/etc/shadow
|
||||
sed -i 's/root::/root:*:/' /sysroot/etc/shadow
|
||||
mkdir -p /sysroot/root/.ssh
|
||||
#chmod 700 /sysroot/root/.ssh
|
||||
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > /sysroot/root/.ssh/authorized_keys
|
||||
#chmod 600 /sysroot/root/.ssh/authorized_keys
|
||||
mkdir -p /sysroot/etc/ssh/
|
||||
for i in /ssh/*.ca; do
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/' $2/profile.yaml
|
||||
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/; s/oraclelinux/Oracle Linux/; s/alma/AlmaLinux/' $2/profile.yaml
|
||||
ln -s $1/images/pxeboot/vmlinuz $2/boot/kernel && \
|
||||
ln -s $1/images/pxeboot/initrd.img $2/boot/initramfs/distribution
|
||||
mkdir -p $2/boot/efi/boot && \
|
||||
|
||||
@@ -33,12 +33,15 @@
|
||||
reboot
|
||||
|
||||
%packages
|
||||
#-kernel-uek # This can opt out of the UEK for the relevant distribution
|
||||
chrony
|
||||
rsync
|
||||
python
|
||||
tar
|
||||
pciutils
|
||||
%include /tmp/addonpackages
|
||||
%end
|
||||
|
||||
|
||||
# A kickstart.custom file is provided to enable easily adding
|
||||
# kickstart content without modifying the stock file.
|
||||
# While the stock file may be safely modified, using the .custom
|
||||
@@ -47,32 +50,33 @@ python
|
||||
%include /tmp/kickstart.custom
|
||||
|
||||
%pre
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/preinst.sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/preinst.sh
|
||||
. /tmp/preinst.sh
|
||||
%end
|
||||
|
||||
%post --nochroot
|
||||
mkdir -p /mnt/sysimage/etc/confluent
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh > /tmp/postinst.sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/prechroot.sh > /tmp/postinst.sh
|
||||
. /tmp/postinst.sh
|
||||
|
||||
# Hook firstboot.sh
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/sysimage/etc/confluent/firstboot.sh
|
||||
chmod +x /mnt/sysimage/etc/confluent/firstboot.sh
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
|
||||
mkdir -p /mnt/sysimage/opt/confluent/bin
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /mnt/sysimage/opt/confluent/bin/firstboot.sh
|
||||
chmod +x /mnt/sysimage/opt/confluent/bin/firstboot.sh
|
||||
%end
|
||||
|
||||
%post
|
||||
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
|
||||
systemctl enable firstboot
|
||||
chgrp ssh_keys /etc/ssh/ssh*key
|
||||
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /etc/confluent/firstboot.sh
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/postinst.sh
|
||||
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /opt/confluent/bin/firstboot.sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/postinst.sh
|
||||
. /tmp/postinst.sh
|
||||
%end
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
|
||||
kernelargs: quiet
|
||||
kernelargs: quiet # These arguments are passed to the installer
|
||||
#installedargs: example # These arguments would be added to the installed system
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import argparse
|
||||
import ctypes
|
||||
import fcntl
|
||||
import json
|
||||
from select import select
|
||||
import socket
|
||||
import struct
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
if os.path.exists('/opt/confluent/bin/apiclient'):
|
||||
apiclient = '/opt/confluent/bin/apiclient'
|
||||
elif os.path.exists('/etc/confluent/apiclient'):
|
||||
apiclient = '/etc/confluent/apiclient'
|
||||
|
||||
class IpmiMsg(ctypes.Structure):
|
||||
_fields_ = [('netfn', ctypes.c_ubyte),
|
||||
('cmd', ctypes.c_ubyte),
|
||||
('data_len', ctypes.c_short),
|
||||
('data', ctypes.POINTER(ctypes.c_ubyte))]
|
||||
|
||||
|
||||
class IpmiSystemInterfaceAddr(ctypes.Structure):
|
||||
_fields_ = [('addr_type', ctypes.c_int),
|
||||
('channel', ctypes.c_short),
|
||||
('lun', ctypes.c_ubyte)]
|
||||
|
||||
|
||||
class IpmiRecv(ctypes.Structure):
|
||||
_fields_ = [('recv_type', ctypes.c_int),
|
||||
('addr', ctypes.POINTER(IpmiSystemInterfaceAddr)),
|
||||
('addr_len', ctypes.c_uint),
|
||||
('msgid', ctypes.c_long),
|
||||
('msg', IpmiMsg)]
|
||||
|
||||
|
||||
class IpmiReq(ctypes.Structure):
|
||||
_fields_ = [('addr', ctypes.POINTER(IpmiSystemInterfaceAddr)),
|
||||
('addr_len', ctypes.c_uint),
|
||||
('msgid', ctypes.c_long),
|
||||
('msg', IpmiMsg)]
|
||||
|
||||
|
||||
_IONONE = 0
|
||||
_IOWRITE = 1
|
||||
_IOREAD = 2
|
||||
IPMICTL_SET_MY_ADDRESS_CMD = (
|
||||
_IOREAD << 30 | ctypes.sizeof(ctypes.c_uint) << 16
|
||||
| ord('i') << 8 | 17) # from ipmi.h
|
||||
IPMICTL_SEND_COMMAND = (
|
||||
_IOREAD << 30 | ctypes.sizeof(IpmiReq) << 16
|
||||
| ord('i') << 8 | 13) # from ipmi.h
|
||||
# next is really IPMICTL_RECEIVE_MSG_TRUNC, but will only use that
|
||||
IPMICTL_RECV = (
|
||||
(_IOWRITE | _IOREAD) << 30 | ctypes.sizeof(IpmiRecv) << 16
|
||||
| ord('i') << 8 | 11) # from ipmi.h
|
||||
BMC_SLAVE_ADDR = ctypes.c_uint(0x20)
|
||||
CURRCHAN = 0xf
|
||||
ADDRTYPE = 0xc
|
||||
|
||||
|
||||
class Session(object):
|
||||
def __init__(self, devnode='/dev/ipmi0'):
|
||||
"""Create a local session inband
|
||||
|
||||
:param: devnode: The path to the ipmi device
|
||||
"""
|
||||
self.ipmidev = open(devnode, 'r+')
|
||||
fcntl.ioctl(self.ipmidev, IPMICTL_SET_MY_ADDRESS_CMD, BMC_SLAVE_ADDR)
|
||||
# the interface is initted, create some reusable memory for our session
|
||||
self.databuffer = ctypes.create_string_buffer(4096)
|
||||
self.req = IpmiReq()
|
||||
self.rsp = IpmiRecv()
|
||||
self.addr = IpmiSystemInterfaceAddr()
|
||||
self.req.msg.data = ctypes.cast(
|
||||
ctypes.addressof(self.databuffer),
|
||||
ctypes.POINTER(ctypes.c_ubyte))
|
||||
self.rsp.msg.data = self.req.msg.data
|
||||
self.userid = None
|
||||
self.password = None
|
||||
|
||||
def await_reply(self):
|
||||
rd, _, _ = select((self.ipmidev,), (), (), 1)
|
||||
while not rd:
|
||||
rd, _, _ = select((self.ipmidev,), (), (), 1)
|
||||
|
||||
def pause(self, seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
@property
|
||||
def parsed_rsp(self):
|
||||
response = {'netfn': self.rsp.msg.netfn, 'command': self.rsp.msg.cmd,
|
||||
'code': bytearray(self.databuffer.raw)[0],
|
||||
'data': bytearray(
|
||||
self.databuffer.raw[1:self.rsp.msg.data_len])}
|
||||
return response
|
||||
|
||||
def await_config(s, bmccfg, channel):
|
||||
vlan = bmccfg.get('bmcvlan', None)
|
||||
ipv4 = bmccfg.get('bmcipv4', None)
|
||||
prefix = bmccfg.get('prefixv4', None)
|
||||
gw = bmccfg.get('bmcgw', None)
|
||||
|
||||
|
||||
|
||||
def raw_command(self,
|
||||
netfn,
|
||||
command,
|
||||
data=(),
|
||||
bridge_request=None,
|
||||
retry=True,
|
||||
delay_xmit=None,
|
||||
timeout=None,
|
||||
waitall=False, rslun=0):
|
||||
self.addr.channel = CURRCHAN
|
||||
self.addr.addr_type = ADDRTYPE
|
||||
self.addr.lun = rslun
|
||||
self.req.addr_len = ctypes.sizeof(IpmiSystemInterfaceAddr)
|
||||
self.req.addr = ctypes.pointer(self.addr)
|
||||
self.req.msg.netfn = netfn
|
||||
self.req.msg.cmd = command
|
||||
if data:
|
||||
data = memoryview(bytearray(data))
|
||||
try:
|
||||
self.databuffer[:len(data)] = data[:len(data)]
|
||||
except ValueError:
|
||||
self.databuffer[:len(data)] = data[:len(data)].tobytes()
|
||||
self.req.msg.data_len = len(data)
|
||||
fcntl.ioctl(self.ipmidev, IPMICTL_SEND_COMMAND, self.req)
|
||||
self.await_reply()
|
||||
self.rsp.msg.data_len = 4096
|
||||
self.rsp.addr = ctypes.pointer(self.addr)
|
||||
self.rsp.addr_len = ctypes.sizeof(IpmiSystemInterfaceAddr)
|
||||
fcntl.ioctl(self.ipmidev, IPMICTL_RECV, self.rsp)
|
||||
return self.parsed_rsp
|
||||
|
||||
|
||||
def _is_tsm(model):
|
||||
return model in ('7y00', '7z01', '7y98', '7y99')
|
||||
|
||||
def set_port(s, port, vendor, model):
|
||||
oport = port
|
||||
if vendor not in ('IBM', 'Lenovo'):
|
||||
raise Exception('{0} not implemented'.format(vendor))
|
||||
if _is_tsm(model):
|
||||
return set_port_tsm(s, port, model)
|
||||
else:
|
||||
set_port_xcc(s, port, model)
|
||||
return 1
|
||||
|
||||
|
||||
def set_port_tsm(s, port, model):
|
||||
oport = port
|
||||
sys.stdout.write('Setting TSM port to "{}"...'.format(oport))
|
||||
sys.stdout.flush()
|
||||
if port == 'ocp':
|
||||
s.raw_command(0x32, 0x71, b'\x00\x01\x00')
|
||||
elif port == 'dedicated':
|
||||
s.raw_command(0x32, 0x71, b'\x00\x00\x00')
|
||||
else:
|
||||
raise Exception("Unsupported port for TSM")
|
||||
timer = 15
|
||||
while timer:
|
||||
timer = timer - 1
|
||||
time.sleep(1.0)
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
if port == 'ocp':
|
||||
iface = 0
|
||||
s.raw_command(0x32, 0x71, b'\x00\x00\x03')
|
||||
elif port == 'dedicated':
|
||||
iface = 1
|
||||
s.raw_command(0x32, 0x71, b'\x00\x01\x03')
|
||||
rsp = s.raw_command(0x32, 0x72, bytearray([4, iface, 0]))
|
||||
print('Complete')
|
||||
return int(rsp['data'][0])
|
||||
|
||||
|
||||
def set_port_xcc(s, port, model):
|
||||
oport = port
|
||||
if port.lower() == 'dedicated':
|
||||
port = b'\x01'
|
||||
elif port.lower() in ('ml2', 'ocp'):
|
||||
port = b'\x02\x00'
|
||||
elif port.lower() == 'lom':
|
||||
if model == '7x58':
|
||||
port = b'\x00\x02'
|
||||
else:
|
||||
port = b'\x00\x00'
|
||||
else:
|
||||
port = port.split(' ')
|
||||
port = bytes(bytearray([int(x) for x in port]))
|
||||
currport = bytes(s.raw_command(0xc, 2, b'\x01\xc0\x00\x00')['data'][1:])
|
||||
if port == currport:
|
||||
sys.stdout.write('XCC port already set to "{}"\n'.format(oport))
|
||||
return
|
||||
sys.stdout.write('Setting XCC port to "{}"...'.format(oport))
|
||||
sys.stdout.flush()
|
||||
s.raw_command(0xc, 1, b'\x01\xc0' + port)
|
||||
tries = 60
|
||||
while currport != port and tries:
|
||||
tries -= 1
|
||||
time.sleep(0.5)
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
currport = bytes(s.raw_command(0xc, 2, b'\x01\xc0\x00\x00')['data'][1:])
|
||||
if not tries:
|
||||
raise Exception('Timeout attempting to set port')
|
||||
sys.stdout.write('Complete\n')
|
||||
|
||||
|
||||
def check_vlan(s, vlan, channel):
|
||||
if vlan == 'off':
|
||||
vlan = b'\x00\x00'
|
||||
else:
|
||||
vlan = int(vlan)
|
||||
if vlan:
|
||||
vlan = vlan | 32768
|
||||
vlan = struct.pack('<H', vlan)
|
||||
currvlan = bytes(s.raw_command(0xc, 2, bytearray([channel, 0x14 ,0, 0]))['data'][1:])
|
||||
if bytearray(currvlan)[1] & 0b10000000 == 0:
|
||||
currvlan = b'\x00\x00'
|
||||
return currvlan == vlan
|
||||
|
||||
|
||||
def set_vlan(s, vlan, channel):
|
||||
ovlan = vlan
|
||||
if vlan == 'off':
|
||||
vlan = b'\x00\x00'
|
||||
else:
|
||||
vlan = int(vlan)
|
||||
if vlan:
|
||||
vlan = vlan | 32768
|
||||
vlan = struct.pack('<H', vlan)
|
||||
if check_vlan(s, ovlan, channel):
|
||||
sys.stdout.write('VLAN already configured to "{0}"\n'.format(ovlan))
|
||||
return False
|
||||
rsp = s.raw_command(0xc, 1, bytearray([channel, 0x14]) + vlan)
|
||||
if rsp.get('code', 1) == 0:
|
||||
print('VLAN configured to "{}"'.format(ovlan))
|
||||
else:
|
||||
print('Error setting vlan: ' + repr(rsp))
|
||||
return True
|
||||
|
||||
|
||||
def get_lan_channel(s):
|
||||
for chan in range(1, 16):
|
||||
rsp = s.raw_command(6, 0x42, bytearray([chan]))['data']
|
||||
if not rsp:
|
||||
continue
|
||||
medtype = int(rsp[1]) & 0b1111111
|
||||
if medtype not in (4, 6):
|
||||
continue
|
||||
rsp = s.raw_command(0xc, 2, bytearray([2, chan, 5, 0, 0]))
|
||||
if rsp.get('code', 1) == 0:
|
||||
return chan
|
||||
return 1
|
||||
|
||||
|
||||
def check_ipv4(s, ipaddr, channel):
|
||||
ipaddr = bytearray(socket.inet_aton(ipaddr))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 3, 0, 0]))['data'][-4:]
|
||||
return rsp == ipaddr
|
||||
|
||||
def set_ipv4(s, ipaddr, channel):
|
||||
oipaddr = ipaddr
|
||||
ipaddr = bytearray(socket.inet_aton(ipaddr))
|
||||
if check_ipv4(s, oipaddr, channel):
|
||||
print('IP Address already set to {}'.format(oipaddr))
|
||||
return False
|
||||
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
|
||||
if rsp != 1:
|
||||
sys.stdout.write("Changing configuration to static...")
|
||||
sys.stdout.flush()
|
||||
resp = s.raw_command(0xc, 1, bytearray([channel, 4, 1]))
|
||||
tries = 0
|
||||
while rsp != 1 and tries < 30:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
tries += 1
|
||||
time.sleep(0.5)
|
||||
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
|
||||
sys.stdout.write('Complete\n')
|
||||
sys.stdout.flush()
|
||||
print('Setting IP to {}'.format(oipaddr))
|
||||
s.raw_command(0xc, 1, bytearray([channel, 3]) + ipaddr)
|
||||
return True
|
||||
|
||||
|
||||
def check_subnet(s, prefix, channel):
|
||||
prefix = int(prefix)
|
||||
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 6, 0, 0]))['data'][-4:]
|
||||
return rsp == mask
|
||||
|
||||
def set_subnet(s, prefix, channel):
|
||||
oprefix = prefix
|
||||
prefix = int(prefix)
|
||||
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
|
||||
if check_subnet(s, prefix, channel):
|
||||
print('Subnet Mask already set to /{}'.format(oprefix))
|
||||
return False
|
||||
print('Setting subnet mask to /{}'.format(oprefix))
|
||||
s.raw_command(0xc, 1, bytearray([channel, 6]) + mask)
|
||||
return True
|
||||
|
||||
|
||||
def check_gateway(s, gw, channel):
|
||||
gw = bytearray(socket.inet_aton(gw))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
|
||||
return rsp == gw
|
||||
|
||||
def set_gateway(s, gw, channel):
|
||||
ogw = gw
|
||||
gw = bytearray(socket.inet_aton(gw))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
|
||||
if check_gateway(s, ogw, channel):
|
||||
print('Gateway already set to {}'.format(ogw))
|
||||
return False
|
||||
print('Setting gateway to {}'.format(ogw))
|
||||
s.raw_command(0xc, 1, bytearray([channel, 12]) + gw)
|
||||
return True
|
||||
|
||||
def dotwait():
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.5)
|
||||
|
||||
def main():
|
||||
a = argparse.ArgumentParser(description='Locally configure a BMC device')
|
||||
a.add_argument('-v', help='vlan id or off to disable vlan tagging')
|
||||
a.add_argument('-p', help='Which port to use (dedicated, lom, ocp, ml2)')
|
||||
a.add_argument('-i', help='JSON configuration file to read for '
|
||||
'configuration')
|
||||
a.add_argument('-c', help='Use Confluent API to direct BMC configuration',
|
||||
action='store_true')
|
||||
args = a.parse_args()
|
||||
if args.i:
|
||||
bmccfg = json.load(open(args.i))
|
||||
elif args.c:
|
||||
bmccfgsrc = subprocess.check_output(
|
||||
[sys.executable, apiclient, '/confluent-api/self/bmcconfig', '-j'])
|
||||
bmccfg = json.loads(bmccfgsrc)
|
||||
else:
|
||||
bmccfg = {}
|
||||
if args.p is not None:
|
||||
bmccfg['bmcport'] = args.p
|
||||
if args.v is not None:
|
||||
bmccfg['bmcvlan'] = args.v
|
||||
vendor = open('/sys/devices/virtual/dmi/id/sys_vendor').read()
|
||||
vendor = vendor.strip()
|
||||
try:
|
||||
model = open('/sys/devices/virtual/dmi/id/product_sku').read().strip()
|
||||
if model == 'none':
|
||||
raise Exception('No SKU')
|
||||
except Exception:
|
||||
model = open('/sys/devices/virtual/dmi/id/product_name').read()
|
||||
if vendor in ('Lenovo', 'IBM'):
|
||||
if '[' in model and ']' in model:
|
||||
model = model.split('[')[1].split(']')[0]
|
||||
model = model[:4].lower()
|
||||
s = Session('/dev/ipmi0')
|
||||
if not bmccfg:
|
||||
print("No BMC configuration specified, exiting.")
|
||||
return
|
||||
if bmccfg.get('bmcport', None):
|
||||
channel = set_port(s, bmccfg['bmcport'], vendor, model)
|
||||
else:
|
||||
channel = get_lan_channel(s)
|
||||
awaitvlan = False
|
||||
awaitip = False
|
||||
awaitprefix = False
|
||||
awaitgw = False
|
||||
if bmccfg.get('bmcvlan', None):
|
||||
awaitvlan = set_vlan(s, bmccfg['bmcvlan'], channel)
|
||||
if bmccfg.get('bmcipv4', None):
|
||||
awaitip = set_ipv4(s, bmccfg['bmcipv4'], channel)
|
||||
if bmccfg.get('prefixv4', None):
|
||||
awaitprefix = set_subnet(s, bmccfg['prefixv4'], channel)
|
||||
if bmccfg.get('bmcgw', None):
|
||||
awaitgw = set_gateway(s, bmccfg['bmcgw'], channel)
|
||||
sys.stdout.write('Waiting for changes to take effect...')
|
||||
sys.stdout.flush()
|
||||
while awaitvlan and not check_vlan(s, bmccfg['bmcvlan'], channel):
|
||||
dotwait()
|
||||
while awaitip and not check_ipv4(s, bmccfg['bmcipv4'], channel):
|
||||
dotwait()
|
||||
while awaitprefix and not check_subnet(s, bmccfg['prefixv4'], channel):
|
||||
dotwait()
|
||||
while awaitprefix and not check_gateway(s, bmccfg['bmcgw'], channel):
|
||||
dotwait()
|
||||
sys.stdout.write('done\n')
|
||||
sys.stdout.flush()
|
||||
#await_config(s, bmccfg, channel)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -4,7 +4,7 @@ Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/etc/confluent/firstboot.sh
|
||||
ExecStart=/opt/confluent/bin/firstboot.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -6,17 +6,31 @@
|
||||
# the script notifies confluent that install is fully complete.
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
|
||||
export nodename mgr profile
|
||||
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
|
||||
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
|
||||
logshowpid=$!
|
||||
|
||||
if [ ! -f /etc/confluent/firstboot.ran ]; then
|
||||
touch /etc/confluent/firstboot.ran
|
||||
|
||||
run_remote firstboot.custom
|
||||
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
|
||||
|
||||
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
# 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
|
||||
kill $logshowpid
|
||||
|
||||
@@ -1,12 +1,73 @@
|
||||
run_remote() {
|
||||
requestedcmd="'$*'"
|
||||
function set_confluent_vars() {
|
||||
if [ -z "$confluent_mgr" ]; then
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
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)
|
||||
curl -f -sS $curlargs https://$confluent_mgr/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)
|
||||
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
source_remote $1/$script
|
||||
done
|
||||
unset confluentscripttmpdir
|
||||
}
|
||||
|
||||
run_remote_parts() {
|
||||
confluentscripttmpdir=$(mktemp -d)
|
||||
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
run_remote $1/$script
|
||||
done
|
||||
unset confluentscripttmpdir
|
||||
}
|
||||
|
||||
source_remote() {
|
||||
set_confluent_vars
|
||||
echo
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
|
||||
tmpdir=$(mktemp -d)
|
||||
echo Executing in $tmpdir
|
||||
cd $tmpdir
|
||||
curl -f -sS https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
if [ -z "$confluentscripttmpdir" ]; then
|
||||
confluentscripttmpdir=$(mktemp -d)
|
||||
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
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote() {
|
||||
requestedcmd="'$*'"
|
||||
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)
|
||||
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
|
||||
@@ -16,23 +77,57 @@ run_remote() {
|
||||
shift
|
||||
./$cmd $*
|
||||
retcode=$?
|
||||
echo "$requestedcmd exited with code $retcode"
|
||||
if [ $retcode -ne 0 ]; then
|
||||
echo "$requestedcmd exited with code $retcode"
|
||||
fi
|
||||
cd - > /dev/null
|
||||
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://$mgr/confluent-public/os/$profile/scripts/
|
||||
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
tmpdir=$(mktemp -d)
|
||||
echo Executing in $tmpdir
|
||||
cd $tmpdir
|
||||
curl -f -sS https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
mkdir -p $(dirname $1)
|
||||
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
|
||||
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
|
||||
/usr/libexec/platform-python $*
|
||||
if [ -x /usr/libexec/platform-python ]; then
|
||||
/usr/libexec/platform-python $*
|
||||
else
|
||||
/usr/bin/python $*
|
||||
fi
|
||||
retcode=$?
|
||||
echo "'$*' exited with code $retcode"
|
||||
cd - > /dev/null
|
||||
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
|
||||
if [ -x /usr/libexec/platform-python ]; then
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
else
|
||||
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
fi
|
||||
echo
|
||||
echo 'Completed remote configuration'
|
||||
echo '---------------------------------------------------------------------------'
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class DiskInfo(object):
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2'):
|
||||
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
|
||||
return 0
|
||||
if 'imsm' in self.mdcontainer:
|
||||
return 1
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# To use this script, rename or copy the mofed image to either ofed.tgz or ofed.iso
|
||||
# and modify the script below if wanting to use the iso instead of tgz
|
||||
|
||||
# It checks for mellanox devices and opts not to install, so this script could be added
|
||||
# to a general profile without causing mofed to install on non-mellanox systems
|
||||
. /etc/confluent/functions
|
||||
if lspci -d 15b3:: -n |grep 15b3 > /dev/null; then
|
||||
# Uncomment the following three lines and comment out the next
|
||||
# two lines to use the .iso instead of the tgz packaging
|
||||
#fetch_remote infiniband/mofed.iso
|
||||
#mkdir MLNX_OFED
|
||||
#mount -o loop infiniband/mofed.iso MLNX_OFED
|
||||
fetch_remote infiniband/mofed.tgz
|
||||
tar xf infiniband/mofed.tgz
|
||||
# The rest is common between tar and iso
|
||||
cd MLNX_OFED*
|
||||
./mlnxofedinstall --force
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Add needed base packages to the install
|
||||
cat << EOF >> /tmp/addonpackages
|
||||
perl
|
||||
pkgconf-pkg-config
|
||||
tcsh
|
||||
lsof
|
||||
tk
|
||||
gcc-gfortran
|
||||
tcl
|
||||
EOF
|
||||
@@ -2,3 +2,6 @@
|
||||
# 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.
|
||||
|
||||
# An example for installing OFED for infiniband follows (see the file for more detail):
|
||||
#run_remote infiniband/mofed.post
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
#!/bin/sh
|
||||
# need to copy over ssh key info
|
||||
mkdir -p /var/log/confluent
|
||||
exec >> /var/log/confluent/confluent-post.log
|
||||
exec 2>> /var/log/confluent/confluent-post.log
|
||||
tail -f /var/log/confluent/confluent-post.log > /dev/tty &
|
||||
logshowpid=$!
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
|
||||
chmod 700 /etc/confluent
|
||||
chmod og-rwx /etc/confluent/*
|
||||
|
||||
export mgr profile nodename
|
||||
export confluent_mgr confluent_profile nodename
|
||||
. /etc/confluent/functions
|
||||
|
||||
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
|
||||
if [ -f /tmp/cryptboot ]; then
|
||||
run_remote tpm_luks.sh
|
||||
fi
|
||||
|
||||
# By default, the install repository is ignored, change
|
||||
# this by manually adding local repositories
|
||||
|
||||
@@ -30,6 +32,18 @@ run_remote_python add_local_repositories
|
||||
# run_remote_python will use the appropriate python interpreter path to run the specified script
|
||||
# A post.custom is provided to more conveniently hold customizations, see the post.custom file.
|
||||
|
||||
# This will induce server side processing of the syncfile contents if
|
||||
# present
|
||||
run_remote_python syncfileclient
|
||||
|
||||
# run_remote example.sh
|
||||
# run_remote_python example.py
|
||||
run_remote post.custom
|
||||
|
||||
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
|
||||
run_remote_parts post.d
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
|
||||
run_remote_config post.d
|
||||
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
|
||||
@@ -2,3 +2,12 @@
|
||||
# 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.
|
||||
#
|
||||
#Here is an example to locally configure the platform BMC according
|
||||
#to confluent configuration so that the BMC would be on the correct
|
||||
#network:
|
||||
#run_remote_python configbmc -c
|
||||
|
||||
#Some addons improve efficiency by adding dependencies during install
|
||||
#here is an example for adding OFED install prereqs to the install
|
||||
#run_remote infiniband/mofed.pre
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
# If you want to use a more custom partition plan, the easiest
|
||||
# method is to edit the kicktstart file and comment out or
|
||||
# delete %include /tmp/partitioning
|
||||
|
||||
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
|
||||
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
|
||||
TMUX= tmux a <> $consoledev >&0 2>&1 &
|
||||
fi
|
||||
exec >> /tmp/confluent-pre.log
|
||||
exec 2>> /tmp/confluent-pre.log
|
||||
tail -f /tmp/confluent-pre.log > /dev/tty &
|
||||
logshowpid=$!
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
locale=$(grep ^locale: /etc/confluent/confluent.deploycfg)
|
||||
locale=${locale#locale: }
|
||||
@@ -28,39 +35,48 @@ if [ "$rootpw" = null ]; then
|
||||
else
|
||||
echo "rootpw --iscrypted $rootpw" > /tmp/rootpw
|
||||
fi
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/profile.yaml > /tmp/instprofile.yaml
|
||||
blargs=$(grep ^installedargs: /tmp/instprofile.yaml | sed -e 's/#.*//' -e 's/^installedargs: //')
|
||||
if [ ! -z "$blargs" ]; then
|
||||
blargs=' --append="'$blargs'"'
|
||||
fi
|
||||
grubpw=$(grep ^grubpassword /etc/confluent/confluent.deploycfg | awk '{print $2}')
|
||||
if [ "$grubpw" = "null" ]; then
|
||||
touch /tmp/grubpw
|
||||
else
|
||||
echo "bootloader --iscrypted --password=$grubpw" > /tmp/grubpw
|
||||
blargs=" --iscrypted --password=$grubpw $blargs"
|
||||
fi
|
||||
if [ ! -z "$blargs" ]; then
|
||||
echo "bootloader $blargs" > /tmp/grubpw
|
||||
fi
|
||||
ssh-keygen -A
|
||||
for pubkey in /etc/ssh/ssh_host_*_key.pub; do
|
||||
for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
certfile=${pubkey/.pub/-cert.pub}
|
||||
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
|
||||
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
|
||||
echo HostCertificate $certfile >> /etc/ssh/sshd_config.anaconda
|
||||
done
|
||||
/usr/sbin/sshd -f /etc/ssh/sshd_config.anaconda
|
||||
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
|
||||
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
|
||||
TMUX= tmux a <> $consoledev >&0 2>&1 &
|
||||
fi
|
||||
cryptboot=$(grep ^encryptboot: /etc/confluent/confluent.deploycfg | awk '{print $2}')
|
||||
LUKSPARTY=''
|
||||
touch /tmp/addonpackages
|
||||
if [ "$cryptboot" == "tpm2" ]; then
|
||||
LUKSPARTY="--encrypted --passphrase=$(cat /etc/confluent/confluent.apikey)"
|
||||
echo $cryptboot >> /tmp/cryptboot
|
||||
fi
|
||||
|
||||
|
||||
export mgr profile nodename
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /tmp/functions
|
||||
export confluent_mgr confluent_profile nodename
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
|
||||
. /tmp/functions
|
||||
run_remote_python getinstalldisk
|
||||
if [ -e /tmp/installdisk ]; then
|
||||
run_remote pre.custom
|
||||
run_remote_parts pre.d
|
||||
if [ ! -e /tmp/installdisk ]; then
|
||||
run_remote_python getinstalldisk
|
||||
fi
|
||||
if [ -e /tmp/installdisk -a ! -e /tmp/partitioning ]; then
|
||||
echo clearpart --all --initlabel >> /tmp/partitioning
|
||||
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
|
||||
echo autopart --nohome $LUKSPARTY >> /tmp/partitioning
|
||||
fi
|
||||
python /etc/confluent/apiclient /confluent-public/os/$profile/kickstart.custom -o /tmp/kickstart.custom
|
||||
run_remote pre.custom
|
||||
python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
|
||||
kill $logshowpid
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
# for customization, which will run in a more normal mechanism
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
export mgr profile nodename
|
||||
export confluent_mgr confluent_profile nodename
|
||||
cp -a /etc/confluent /mnt/sysimage/etc
|
||||
chmod -R og-rwx /mnt/sysimage/etc/confluent
|
||||
cp /tmp/functions /mnt/sysimage/etc/confluent/
|
||||
. /tmp/functions
|
||||
cp /tmp/cryptboot /mnt/sysimage/tmp/
|
||||
if [ -f /tmp/cryptboot ]; then
|
||||
cp /tmp/cryptboot /mnt/sysimage/tmp/
|
||||
fi
|
||||
mkdir -p /mnt/sysimage/var/log/confluent
|
||||
mv /tmp/confluent-pre.log /mnt/sysimage/var/log/confluent
|
||||
echo Port 2222 >> /etc/ssh/sshd_config.anaconda
|
||||
echo Match LocalPort 22 >> /etc/ssh/sshd_config.anaconda
|
||||
echo " ChrootDirectory /mnt/sysimage" >> /etc/ssh/sshd_config.anaconda
|
||||
kill -HUP $(cat /run/sshd.pid)
|
||||
|
||||
# Preserve the ssh setup work done for the installer
|
||||
# by copying into the target system and setting up
|
||||
|
||||
@@ -18,6 +18,6 @@ chmod 700 /mnt/sysimage/root/.ssh/
|
||||
cp /root/.ssh/authorized_keys /mnt/sysimage/root/.ssh/
|
||||
chmod 600 /mnt/sysimage/root/.ssh/authorized_keys
|
||||
cp /etc/ssh/ssh_known_hosts /mnt/sysimage/etc/ssh/
|
||||
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
|
||||
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
|
||||
cp /tmp/allnodes /mnt/sysimage/etc/ssh/shosts.equiv
|
||||
cp /tmp/allnodes /mnt/sysimage/root/.shosts
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
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 partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
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
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.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 not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
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')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -0,0 +1,13 @@
|
||||
# This file lists files to synchronize or merge to the deployed systems from the deployment server
|
||||
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
|
||||
# /some/path/hosts -> /etc/hosts
|
||||
|
||||
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
|
||||
# /etc/hosts
|
||||
|
||||
# Entries under MERGE will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
|
||||
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
|
||||
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
|
||||
MERGE:
|
||||
# /etc/passwd
|
||||
# /etc/group
|
||||
@@ -7,6 +7,13 @@ if [ -f /tmp/dd_disk ]; then
|
||||
fi
|
||||
done
|
||||
fi
|
||||
vlaninfo=$(getarg vlan)
|
||||
if [ ! -z "$vlaninfo" ]; then
|
||||
vldev=${vlaninfo#*:}
|
||||
vlid=${vlaninfo#*.}
|
||||
vlid=${vlid%:*}
|
||||
ip link add link $vldev name $vldev.$vlid type vlan id $vlid
|
||||
fi
|
||||
TRIES=0
|
||||
oum=$(umask)
|
||||
umask 0077
|
||||
@@ -35,6 +42,7 @@ cat /tls/*.pem > /etc/confluent/ca.pem
|
||||
ifidx=$(cat /tmp/confluent.ifidx)
|
||||
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
|
||||
ifname=${ifname%:}
|
||||
ifname=${ifname%@*}
|
||||
echo $ifname > /tmp/net.ifaces
|
||||
|
||||
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
|
||||
@@ -98,4 +106,16 @@ while read -r entry; do
|
||||
continue
|
||||
fi
|
||||
done < /etc/confluent/confluent.deploycfg
|
||||
if [ -e /lib/nm-lib.sh ]; then
|
||||
. /lib/nm-lib.sh
|
||||
nm_generate_connections
|
||||
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
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ sed -i 's/install::/install:*:/' /sysroot/etc/shadow
|
||||
sed -i 's/root::/root:*:/' /sysroot/etc/shadow
|
||||
mkdir -p /sysroot/root/.ssh
|
||||
chmod 700 /sysroot/root/.ssh
|
||||
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > /sysroot/root/.ssh/authorized_keys
|
||||
chmod 600 /sysroot/root/.ssh/authorized_keys
|
||||
mkdir -p /sysroot/etc/ssh/
|
||||
for i in /ssh/*.ca; do
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/' $2/profile.yaml
|
||||
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/; s/oraclelinux/Oracle Linux/; s/alma/AlmaLinux/' $2/profile.yaml
|
||||
ln -s $1/images/pxeboot/vmlinuz $2/boot/kernel && \
|
||||
ln -s $1/images/pxeboot/initrd.img $2/boot/initramfs/distribution
|
||||
mkdir -p $2/boot/efi/boot && \
|
||||
|
||||
@@ -34,9 +34,13 @@ reboot
|
||||
|
||||
%packages
|
||||
@^minimal-environment
|
||||
#-kernel-uek # This can opt out of the UEK for the relevant distribution
|
||||
chrony
|
||||
rsync
|
||||
python3
|
||||
tar
|
||||
pciutils
|
||||
%include /tmp/addonpackages
|
||||
%include /tmp/cryptpkglist
|
||||
%end
|
||||
|
||||
@@ -48,32 +52,33 @@ python3
|
||||
%include /tmp/kickstart.custom
|
||||
|
||||
%pre
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/preinst.sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/preinst.sh
|
||||
. /tmp/preinst.sh
|
||||
%end
|
||||
|
||||
%post --nochroot
|
||||
mkdir -p /mnt/sysimage/etc/confluent
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh > /tmp/postinst.sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/prechroot.sh > /tmp/postinst.sh
|
||||
. /tmp/postinst.sh
|
||||
|
||||
# Hook firstboot.sh
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/sysimage/etc/confluent/firstboot.sh
|
||||
chmod +x /mnt/sysimage/etc/confluent/firstboot.sh
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
|
||||
mkdir -p /mnt/sysimage/opt/confluent/bin
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /mnt/sysimage/opt/confluent/bin/firstboot.sh
|
||||
chmod +x /mnt/sysimage/opt/confluent/bin/firstboot.sh
|
||||
%end
|
||||
|
||||
%post
|
||||
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
|
||||
systemctl enable firstboot
|
||||
chgrp ssh_keys /etc/ssh/ssh*key
|
||||
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /etc/confluent/firstboot.sh
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/postinst.sh
|
||||
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /opt/confluent/bin/firstboot.sh
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/postinst.sh
|
||||
. /tmp/postinst.sh
|
||||
%end
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
|
||||
kernelargs: quiet
|
||||
kernelargs: quiet # These arguments are passed to the installer
|
||||
#installedargs: example # These arguments would be added to the installed system
|
||||
|
||||
@@ -111,6 +111,14 @@ class Session(object):
|
||||
self.databuffer.raw[1:self.rsp.msg.data_len])}
|
||||
return response
|
||||
|
||||
def await_config(s, bmccfg, channel):
|
||||
vlan = bmccfg.get('bmcvlan', None)
|
||||
ipv4 = bmccfg.get('bmcipv4', None)
|
||||
prefix = bmccfg.get('prefixv4', None)
|
||||
gw = bmccfg.get('bmcgw', None)
|
||||
|
||||
|
||||
|
||||
def raw_command(self,
|
||||
netfn,
|
||||
command,
|
||||
@@ -217,8 +225,7 @@ def set_port_xcc(s, port, model):
|
||||
sys.stdout.write('Complete\n')
|
||||
|
||||
|
||||
def set_vlan(s, vlan, channel):
|
||||
ovlan = vlan
|
||||
def check_vlan(s, vlan, channel):
|
||||
if vlan == 'off':
|
||||
vlan = b'\x00\x00'
|
||||
else:
|
||||
@@ -229,7 +236,19 @@ def set_vlan(s, vlan, channel):
|
||||
currvlan = bytes(s.raw_command(0xc, 2, bytearray([channel, 0x14 ,0, 0]))['data'][1:])
|
||||
if bytearray(currvlan)[1] & 0b10000000 == 0:
|
||||
currvlan = b'\x00\x00'
|
||||
if currvlan == vlan:
|
||||
return currvlan == vlan
|
||||
|
||||
|
||||
def set_vlan(s, vlan, channel):
|
||||
ovlan = vlan
|
||||
if vlan == 'off':
|
||||
vlan = b'\x00\x00'
|
||||
else:
|
||||
vlan = int(vlan)
|
||||
if vlan:
|
||||
vlan = vlan | 32768
|
||||
vlan = struct.pack('<H', vlan)
|
||||
if check_vlan(s, ovlan, channel):
|
||||
sys.stdout.write('VLAN already configured to "{0}"\n'.format(ovlan))
|
||||
return False
|
||||
rsp = s.raw_command(0xc, 1, bytearray([channel, 0x14]) + vlan)
|
||||
@@ -237,7 +256,7 @@ def set_vlan(s, vlan, channel):
|
||||
print('VLAN configured to "{}"'.format(ovlan))
|
||||
else:
|
||||
print('Error setting vlan: ' + repr(rsp))
|
||||
return
|
||||
return True
|
||||
|
||||
|
||||
def get_lan_channel(s):
|
||||
@@ -253,13 +272,18 @@ def get_lan_channel(s):
|
||||
return chan
|
||||
return 1
|
||||
|
||||
|
||||
def check_ipv4(s, ipaddr, channel):
|
||||
ipaddr = bytearray(socket.inet_aton(ipaddr))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 3, 0, 0]))['data'][-4:]
|
||||
return rsp == ipaddr
|
||||
|
||||
def set_ipv4(s, ipaddr, channel):
|
||||
oipaddr = ipaddr
|
||||
ipaddr = bytearray(socket.inet_aton(ipaddr))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 3, 0, 0]))['data'][-4:]
|
||||
if rsp == ipaddr:
|
||||
if check_ipv4(s, oipaddr, channel):
|
||||
print('IP Address already set to {}'.format(oipaddr))
|
||||
return
|
||||
return False
|
||||
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
|
||||
if rsp != 1:
|
||||
sys.stdout.write("Changing configuration to static...")
|
||||
@@ -276,29 +300,47 @@ def set_ipv4(s, ipaddr, channel):
|
||||
sys.stdout.flush()
|
||||
print('Setting IP to {}'.format(oipaddr))
|
||||
s.raw_command(0xc, 1, bytearray([channel, 3]) + ipaddr)
|
||||
return True
|
||||
|
||||
|
||||
def check_subnet(s, prefix, channel):
|
||||
prefix = int(prefix)
|
||||
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 6, 0, 0]))['data'][-4:]
|
||||
return rsp == mask
|
||||
|
||||
def set_subnet(s, prefix, channel):
|
||||
oprefix = prefix
|
||||
prefix = int(prefix)
|
||||
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 6, 0, 0]))['data'][-4:]
|
||||
if rsp == mask:
|
||||
if check_subnet(s, prefix, channel):
|
||||
print('Subnet Mask already set to /{}'.format(oprefix))
|
||||
return
|
||||
return False
|
||||
print('Setting subnet mask to /{}'.format(oprefix))
|
||||
s.raw_command(0xc, 1, bytearray([channel, 6]) + mask)
|
||||
return True
|
||||
|
||||
|
||||
def check_gateway(s, gw, channel):
|
||||
gw = bytearray(socket.inet_aton(gw))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
|
||||
return rsp == gw
|
||||
|
||||
def set_gateway(s, gw, channel):
|
||||
ogw = gw
|
||||
gw = bytearray(socket.inet_aton(gw))
|
||||
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
|
||||
if rsp == gw:
|
||||
if check_gateway(s, ogw, channel):
|
||||
print('Gateway already set to {}'.format(ogw))
|
||||
return
|
||||
return False
|
||||
print('Setting gateway to {}'.format(ogw))
|
||||
s.raw_command(0xc, 1, bytearray([channel, 12]) + gw)
|
||||
return True
|
||||
|
||||
def dotwait():
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.5)
|
||||
|
||||
def main():
|
||||
a = argparse.ArgumentParser(description='Locally configure a BMC device')
|
||||
@@ -341,14 +383,30 @@ def main():
|
||||
channel = set_port(s, bmccfg['bmcport'], vendor, model)
|
||||
else:
|
||||
channel = get_lan_channel(s)
|
||||
awaitvlan = False
|
||||
awaitip = False
|
||||
awaitprefix = False
|
||||
awaitgw = False
|
||||
if bmccfg.get('bmcvlan', None):
|
||||
set_vlan(s, bmccfg['bmcvlan'], channel)
|
||||
awaitvlan = set_vlan(s, bmccfg['bmcvlan'], channel)
|
||||
if bmccfg.get('bmcipv4', None):
|
||||
set_ipv4(s, bmccfg['bmcipv4'], channel)
|
||||
awaitip = set_ipv4(s, bmccfg['bmcipv4'], channel)
|
||||
if bmccfg.get('prefixv4', None):
|
||||
set_subnet(s, bmccfg['prefixv4'], channel)
|
||||
awaitprefix = set_subnet(s, bmccfg['prefixv4'], channel)
|
||||
if bmccfg.get('bmcgw', None):
|
||||
set_gateway(s, bmccfg['bmcgw'], channel)
|
||||
awaitgw = set_gateway(s, bmccfg['bmcgw'], channel)
|
||||
sys.stdout.write('Waiting for changes to take effect...')
|
||||
sys.stdout.flush()
|
||||
while awaitvlan and not check_vlan(s, bmccfg['bmcvlan'], channel):
|
||||
dotwait()
|
||||
while awaitip and not check_ipv4(s, bmccfg['bmcipv4'], channel):
|
||||
dotwait()
|
||||
while awaitprefix and not check_subnet(s, bmccfg['prefixv4'], channel):
|
||||
dotwait()
|
||||
while awaitprefix and not check_gateway(s, bmccfg['bmcgw'], channel):
|
||||
dotwait()
|
||||
sys.stdout.write('done\n')
|
||||
sys.stdout.flush()
|
||||
#await_config(s, bmccfg, channel)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/etc/confluent/firstboot.sh
|
||||
ExecStart=/opt/confluent/bin/firstboot.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -6,17 +6,31 @@
|
||||
# the script notifies confluent that install is fully complete.
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
|
||||
export nodename mgr profile
|
||||
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
|
||||
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
|
||||
logshowpid=$!
|
||||
|
||||
if [ ! -f /etc/confluent/firstboot.ran ]; then
|
||||
touch /etc/confluent/firstboot.ran
|
||||
|
||||
run_remote firstboot.custom
|
||||
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
|
||||
|
||||
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
# 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
|
||||
kill $logshowpid
|
||||
|
||||
@@ -1,12 +1,73 @@
|
||||
run_remote() {
|
||||
requestedcmd="'$*'"
|
||||
function set_confluent_vars() {
|
||||
if [ -z "$confluent_mgr" ]; then
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
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)
|
||||
curl -f -sS $curlargs https://$confluent_mgr/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)
|
||||
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
source_remote $1/$script
|
||||
done
|
||||
unset confluentscripttmpdir
|
||||
}
|
||||
|
||||
run_remote_parts() {
|
||||
confluentscripttmpdir=$(mktemp -d)
|
||||
scriptlist=$(/usr/libexec/platform-python /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
run_remote $1/$script
|
||||
done
|
||||
unset confluentscripttmpdir
|
||||
}
|
||||
|
||||
source_remote() {
|
||||
set_confluent_vars
|
||||
echo
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
|
||||
tmpdir=$(mktemp -d)
|
||||
echo Executing in $tmpdir
|
||||
cd $tmpdir
|
||||
curl -f -sS https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
if [ -z "$confluentscripttmpdir" ]; then
|
||||
confluentscripttmpdir=$(mktemp -d)
|
||||
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
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote() {
|
||||
requestedcmd="'$*'"
|
||||
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)
|
||||
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
|
||||
@@ -16,23 +77,57 @@ run_remote() {
|
||||
shift
|
||||
./$cmd $*
|
||||
retcode=$?
|
||||
echo "$requestedcmd exited with code $retcode"
|
||||
if [ $retcode -ne 0 ]; then
|
||||
echo "$requestedcmd exited with code $retcode"
|
||||
fi
|
||||
cd - > /dev/null
|
||||
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://$mgr/confluent-public/os/$profile/scripts/
|
||||
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
|
||||
tmpdir=$(mktemp -d)
|
||||
echo Executing in $tmpdir
|
||||
cd $tmpdir
|
||||
curl -f -sS https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
mkdir -p $(dirname $1)
|
||||
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
|
||||
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
|
||||
/usr/libexec/platform-python $*
|
||||
if [ -x /usr/libexec/platform-python ]; then
|
||||
/usr/libexec/platform-python $*
|
||||
else
|
||||
/usr/bin/python $*
|
||||
fi
|
||||
retcode=$?
|
||||
echo "'$*' exited with code $retcode"
|
||||
cd - > /dev/null
|
||||
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
|
||||
if [ -x /usr/libexec/platform-python ]; then
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
else
|
||||
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
fi
|
||||
echo
|
||||
echo 'Completed remote configuration'
|
||||
echo '---------------------------------------------------------------------------'
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class DiskInfo(object):
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2'):
|
||||
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
|
||||
return 0
|
||||
if 'imsm' in self.mdcontainer:
|
||||
return 1
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# To use this script, rename or copy the mofed image to either ofed.tgz or ofed.iso
|
||||
# and modify the script below if wanting to use the iso instead of tgz
|
||||
|
||||
# It checks for mellanox devices and opts not to install, so this script could be added
|
||||
# to a general profile without causing mofed to install on non-mellanox systems
|
||||
. /etc/confluent/functions
|
||||
if lspci -d 15b3:: -n |grep 15b3 > /dev/null; then
|
||||
# Uncomment the following three lines and comment out the next
|
||||
# two lines to use the .iso instead of the tgz packaging
|
||||
#fetch_remote infiniband/mofed.iso
|
||||
#mkdir MLNX_OFED
|
||||
#mount -o loop infiniband/mofed.iso MLNX_OFED
|
||||
fetch_remote infiniband/mofed.tgz
|
||||
tar xf infiniband/mofed.tgz
|
||||
# The rest is common between tar and iso
|
||||
cd MLNX_OFED*
|
||||
./mlnxofedinstall --force
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Add needed base packages to the install
|
||||
cat << EOF >> /tmp/addonpackages
|
||||
perl
|
||||
pkgconf-pkg-config
|
||||
tcsh
|
||||
lsof
|
||||
tk
|
||||
gcc-gfortran
|
||||
tcl
|
||||
EOF
|
||||
@@ -2,3 +2,6 @@
|
||||
# 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.
|
||||
|
||||
# An example for installing OFED for infiniband follows (see the file for more detail):
|
||||
#run_remote infiniband/mofed.post
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#!/bin/sh
|
||||
# need to copy over ssh key info
|
||||
mkdir -p /var/log/confluent
|
||||
exec >> /var/log/confluent/confluent-post.log
|
||||
exec 2>> /var/log/confluent/confluent-post.log
|
||||
tail -f /var/log/confluent/confluent-post.log > /dev/tty &
|
||||
logshowpid=$!
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
|
||||
chmod 700 /etc/confluent
|
||||
chmod og-rwx /etc/confluent/*
|
||||
export mgr profile nodename
|
||||
export confluent_mgr confluent_profile nodename
|
||||
. /etc/confluent/functions
|
||||
|
||||
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
|
||||
if [ -f /tmp/cryptboot ]; then
|
||||
run_remote tpm_luks.sh
|
||||
@@ -30,6 +32,18 @@ run_remote_python add_local_repositories
|
||||
# run_remote_python will use the appropriate python interpreter path to run the specified script
|
||||
# A post.custom is provided to more conveniently hold customizations, see the post.custom file.
|
||||
|
||||
# This will induce server side processing of the syncfile contents if
|
||||
# present
|
||||
run_remote_python syncfileclient
|
||||
|
||||
# run_remote example.sh
|
||||
# run_remote_python example.py
|
||||
run_remote post.custom
|
||||
|
||||
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
|
||||
run_remote_parts post.d
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
|
||||
run_remote_config post.d
|
||||
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
|
||||
@@ -7,3 +7,7 @@
|
||||
#to confluent configuration so that the BMC would be on the correct
|
||||
#network:
|
||||
#run_remote_python configbmc -c
|
||||
|
||||
#Some addons improve efficiency by adding dependencies during install
|
||||
#here is an example for adding OFED install prereqs to the install
|
||||
#run_remote infiniband/mofed.pre
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
# If you want to use a more custom partition plan, the easiest
|
||||
# method is to edit the kicktstart file and comment out or
|
||||
# delete %include /tmp/partitioning
|
||||
|
||||
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
|
||||
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
|
||||
TMUX= tmux a <> $consoledev >&0 2>&1 &
|
||||
fi
|
||||
exec >> /tmp/confluent-pre.log
|
||||
exec 2>> /tmp/confluent-pre.log
|
||||
tail -f /tmp/confluent-pre.log > /dev/tty &
|
||||
logshowpid=$!
|
||||
/usr/libexec/platform-python /etc/confluent/apiclient >& /dev/null
|
||||
nicname=$(ip link|grep ^$(cat /tmp/confluent.ifidx): | awk '{print $2}' | awk -F: '{print $1}')
|
||||
nmcli c u $nicname
|
||||
@@ -34,40 +41,57 @@ if [ "$rootpw" = null ]; then
|
||||
else
|
||||
echo "rootpw --iscrypted $rootpw" > /tmp/rootpw
|
||||
fi
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/profile.yaml > /tmp/instprofile.yaml
|
||||
blargs=$(grep ^installedargs: /tmp/instprofile.yaml | sed -e 's/#.*//' -e 's/^installedargs: //')
|
||||
if [ ! -z "$blargs" ]; then
|
||||
blargs=' --append="'$blargs'"'
|
||||
fi
|
||||
grubpw=$(grep ^grubpassword /etc/confluent/confluent.deploycfg | awk '{print $2}')
|
||||
if [ "$grubpw" = "null" ]; then
|
||||
touch /tmp/grubpw
|
||||
else
|
||||
echo "bootloader --iscrypted --password=$grubpw" > /tmp/grubpw
|
||||
blargs=" --iscrypted --password=$grubpw $blargs"
|
||||
fi
|
||||
if [ ! -z "$blargs" ]; then
|
||||
echo "bootloader $blargs" > /tmp/grubpw
|
||||
fi
|
||||
ssh-keygen -A
|
||||
for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
certfile=${pubkey/.pub/-cert.pub}
|
||||
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
|
||||
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
|
||||
echo HostCertificate $certfile >> /etc/ssh/sshd_config.anaconda
|
||||
done
|
||||
/usr/sbin/sshd -f /etc/ssh/sshd_config.anaconda
|
||||
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
|
||||
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
|
||||
tmux a <> $consoledev >&0 2>&1 &
|
||||
fi
|
||||
cryptboot=$(grep ^encryptboot: /etc/confluent/confluent.deploycfg | awk '{print $2}')
|
||||
LUKSPARTY=''
|
||||
touch /tmp/cryptpkglist
|
||||
touch /tmp/addonpackages
|
||||
if [ "$cryptboot" == "tpm2" ]; then
|
||||
LUKSPARTY="--encrypted --passphrase=$(cat /etc/confluent/confluent.apikey)"
|
||||
echo $cryptboot >> /tmp/cryptboot
|
||||
echo clevis-dracut >> /tmp/cryptpkglist
|
||||
echo clevis-dracut >> /tmp/cryptpkglist
|
||||
fi
|
||||
|
||||
|
||||
export mgr profile nodename
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /tmp/functions
|
||||
export confluent_mgr confluent_profile nodename
|
||||
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
|
||||
. /tmp/functions
|
||||
run_remote_python getinstalldisk
|
||||
if [ -e /tmp/installdisk ]; then
|
||||
run_remote pre.custom
|
||||
run_remote_parts pre.d
|
||||
if [ ! -e /tmp/installdisk ]; then
|
||||
run_remote_python getinstalldisk
|
||||
fi
|
||||
if [ -e /tmp/installdisk -a ! -e /tmp/partitioning ]; then
|
||||
echo clearpart --all --initlabel >> /tmp/partitioning
|
||||
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
|
||||
echo autopart --nohome $LUKSPARTY >> /tmp/partitioning
|
||||
fi
|
||||
curl -f https://$mgr/confluent-public/os/$profile/kickstart.custom > /tmp/kickstart.custom
|
||||
run_remote pre.custom
|
||||
if [ -e /usr/libexec/platform-python ]; then
|
||||
python=/usr/libexec/platform-python
|
||||
elif [ -e /usr/bin/python3 ]; then
|
||||
python=/usr/byn/python3
|
||||
else
|
||||
python=/usr/bin/python
|
||||
fi
|
||||
$python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
|
||||
kill $logshowpid
|
||||
|
||||
@@ -8,12 +8,20 @@
|
||||
# for customization, which will run in a more normal mechanism
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
export mgr profile nodename
|
||||
export confluent_mgr confluent_profile nodename
|
||||
cp -a /etc/confluent /mnt/sysimage/etc
|
||||
chmod -R og-rwx /mnt/sysimage/etc/confluent
|
||||
cp /tmp/functions /mnt/sysimage/etc/confluent/
|
||||
. /tmp/functions
|
||||
cp /tmp/cryptboot /mnt/sysimage/tmp/
|
||||
if [ -f /tmp/cryptboot ]; then
|
||||
cp /tmp/cryptboot /mnt/sysimage/tmp/
|
||||
fi
|
||||
mkdir -p /mnt/sysimage/var/log/confluent
|
||||
mv /tmp/confluent-pre.log /mnt/sysimage/var/log/confluent
|
||||
echo Port 2222 >> /etc/ssh/sshd_config.anaconda
|
||||
echo Match LocalPort 22 >> /etc/ssh/sshd_config.anaconda
|
||||
echo " ChrootDirectory /mnt/sysimage" >> /etc/ssh/sshd_config.anaconda
|
||||
kill -HUP $(cat /run/sshd.pid)
|
||||
|
||||
# Preserve the ssh setup work done for the installer
|
||||
# by copying into the target system and setting up
|
||||
|
||||
@@ -18,6 +18,6 @@ chmod 700 /mnt/sysimage/root/.ssh/
|
||||
cp /root/.ssh/authorized_keys /mnt/sysimage/root/.ssh/
|
||||
chmod 600 /mnt/sysimage/root/.ssh/authorized_keys
|
||||
cp /etc/ssh/ssh_known_hosts /mnt/sysimage/etc/ssh/
|
||||
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
|
||||
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
|
||||
cp /tmp/allnodes /mnt/sysimage/etc/ssh/shosts.equiv
|
||||
cp /tmp/allnodes /mnt/sysimage/root/.shosts
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
except FileNotFoundError:
|
||||
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
|
||||
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
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
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.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 not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
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')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -0,0 +1,13 @@
|
||||
# This file lists files to synchronize or merge to the deployed systems from the deployment server
|
||||
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
|
||||
# /some/path/hosts -> /etc/hosts
|
||||
|
||||
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
|
||||
# /etc/hosts
|
||||
|
||||
# Entries under MERGE will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
|
||||
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
|
||||
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
|
||||
MERGE:
|
||||
# /etc/passwd
|
||||
# /etc/group
|
||||
@@ -7,18 +7,46 @@ echo $uuid > /sys/devices/virtual/dmi/id/product_uuid
|
||||
mkdir -p /etc/confluent
|
||||
localcli network firewall unload
|
||||
touch /etc/confluent/confluent.info
|
||||
begin=$(date +%s)
|
||||
while ! grep NODENAME /etc/confluent/confluent.info; do
|
||||
echo "Searching for deployment service on local network..."
|
||||
/opt/confluent/bin/copernicus > /etc/confluent/confluent.info
|
||||
esxcfg-nics -l
|
||||
if [ $(date +%s) -gt $((begin + 90)) ]; then
|
||||
if [ ! -f /var/run/vmware/show-esx-shell-login ]; then
|
||||
chvt 1
|
||||
sleep 1
|
||||
chvt 2
|
||||
/etc/init.d/ESXShell start
|
||||
fi
|
||||
echo "Unable to locate a deployment system on the local network, verify network connectivity"
|
||||
echo "A debug session has been made available on Alt-F1"
|
||||
sleep 30
|
||||
fi
|
||||
done
|
||||
node=$(grep NODENAME: /etc/confluent/confluent.info|head -n 1|awk '{print $2}')
|
||||
mgr=$(grep MANAGER: /etc/confluent/confluent.info|head -n 1|awk '{print $2}')
|
||||
cp /opt/confluent/bin/clortho /clortho
|
||||
/clortho $node $mgr > /etc/confluent/confluent.apikey
|
||||
APIKEY=$(cat /etc/confluent/confluent.apikey)
|
||||
while [ -z "$APIKEY" ]; do
|
||||
if [ ! -f /var/run/vmware/show-esx-shell-login ]; then
|
||||
chvt 1
|
||||
sleep 1
|
||||
chvt 2
|
||||
/etc/init.d/ESXShell start
|
||||
fi
|
||||
echo "Deployment server failed to grant node deployment token, retrying token retrieval"
|
||||
echo "A debug session has been made available on Alt-F1"
|
||||
/clortho $node $mgr > /etc/confluent/confluent.apikey
|
||||
APIKEY=$(cat /etc/confluent/confluent.apikey)
|
||||
done
|
||||
cat /tls/*.pem > /etc/confluent/ca.pem
|
||||
/opt/confluent/bin/apiclient /confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/^profile: //')
|
||||
/opt/confluent/bin/apiclient /confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg.new
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg.new | sed -e 's/^profile: //')
|
||||
/opt/confluent/bin/apiclient /confluent-public/os/$profile/kickstart > /etc/confluent/ks.cfg
|
||||
/opt/confluent/bin/apiclient /confluent-public/os/$profile/scripts/modinstall > /tmp/modinstall
|
||||
mv /etc/confluent/confluent.deploycfg.new /etc/confluent/confluent.deploycfg
|
||||
export node mgr profile
|
||||
. /tmp/modinstall
|
||||
exec /bin/install
|
||||
|
||||
@@ -4,3 +4,8 @@ install --firstdisk --overwritevmfs
|
||||
%include /tmp/ksnet
|
||||
%include /tmp/rootpw
|
||||
reboot
|
||||
%post --interpreter=busybox
|
||||
localcli network firewall unload
|
||||
STATUP=$(mktemp)
|
||||
echo '{"status": "complete"}' > $STATUP
|
||||
/opt/confluent/bin/apiclient /confluent-api/self/updatestatus $STATUP
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
label: VMware ESXi %%VERSION%% Hypervisor
|
||||
label: Confluent installation of VMware ESXi %%VERSION%% Hypervisor
|
||||
ostype: esxi
|
||||
kernelargs: runweasel
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/python
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
uplinkmatch = re.compile('^\s*Uplinks:\s*(.*)')
|
||||
nodename = None
|
||||
for inf in open('/etc/confluent/confluent.info', 'r').read().split('\n'):
|
||||
if inf.startswith('NODENAME: '):
|
||||
@@ -24,8 +27,15 @@ for line in deploycfg:
|
||||
else:
|
||||
nslist=False
|
||||
cfg['nameservers'] = ','.join(nameservers)
|
||||
|
||||
vswinfo = subprocess.check_output(['localcli', 'network', 'vswitch', 'standard', 'list']).decode('utf8')
|
||||
vmnic = None
|
||||
for info in vswinfo.split('\n'):
|
||||
upinfo = uplinkmatch.match(info)
|
||||
if upinfo:
|
||||
vmnic = upinfo.group(1)
|
||||
netline = 'network --hostname={0} --bootproto={1}'.format(nodename, cfg['ipv4_method'])
|
||||
if vmnic:
|
||||
netline += ' --device={0}'.format(vmnic)
|
||||
if cfg['ipv4_method'] == 'static':
|
||||
netline += ' --ip={0} --netmask={1}'.format(cfg['ipv4_address'], cfg['ipv4_netmask'])
|
||||
if cfg.get('ipv4_gateway', 'null') not in (None, '', 'null'):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user