mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 16:50:57 +00:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 162e4d1d1e | |||
| 8e407cb96e | |||
| d06be55568 | |||
| 8db6382629 | |||
| 663b320868 | |||
| 517242df47 | |||
| c6adf8175a | |||
| 38a95131f9 | |||
| 5d416cb1b1 | |||
| 451e3ba224 | |||
| 6564f8de72 | |||
| bb414524ad | |||
| bab0a77cb6 | |||
| 010c8a0a23 | |||
| c0bcc3791d | |||
| 172c57c6f1 | |||
| 91dce25a8b | |||
| 62be16442c | |||
| d0e73c887b | |||
| b7a5101a34 | |||
| 930ff3e20d | |||
| 39fb229ef1 | |||
| 60f8f964f5 | |||
| 8f01f22bb5 | |||
| 86e612b4bf | |||
| 6f2be355ef | |||
| b606882327 | |||
| 10f0fabb8c | |||
| 0d3e2cee6c | |||
| 3b55f500ce | |||
| 3ba3394a54 | |||
| a6a2f2f2de | |||
| 8e5ee6c9d8 | |||
| ceaf641c1a | |||
| 01722c18c4 | |||
| 8fb889ba73 | |||
| eaffb342b2 | |||
| 1da27083cc | |||
| 67b3c48dc9 | |||
| 8ca9a44476 | |||
| 02f301b5d0 | |||
| 33271451d7 | |||
| f68f9f4693 | |||
| a4e152c17d | |||
| 272c456435 | |||
| 894290f577 | |||
| 34804b2d5f | |||
| 77bb03e96c | |||
| 12bb5d583a | |||
| 03bdbfc8ed | |||
| 661b2ae815 | |||
| ddb8c4cce4 | |||
| 17fff4997b | |||
| 7b3129a1a2 | |||
| d183a3f99c | |||
| b3b3627bf9 | |||
| c1afc144cb | |||
| ac1f7c57b6 | |||
| 19e9c6910d | |||
| e38cd5d3e5 | |||
| f1d3e47439 | |||
| f7a2e51f9c | |||
| f3d0bd9079 | |||
| 296a0e88b4 | |||
| c60bf68cbc | |||
| 466ed74961 | |||
| 838c0920eb | |||
| 6ad0e773de | |||
| 5a7d98c6b8 | |||
| 13fc5d9f37 | |||
| 559e88b144 | |||
| 5f801e6683 | |||
| 7a6b03097b | |||
| 6502573d90 | |||
| 3dd09b95e4 | |||
| a595d31e94 | |||
| b157e55f00 | |||
| 60fe306890 | |||
| bd2288ccb7 | |||
| 789376029d | |||
| 1d4505ff3c | |||
| 876b59c1f0 | |||
| 58d9bc1816 | |||
| 17af9c74b8 | |||
| 0d720baf25 | |||
| 49e614eb32 | |||
| cdefb400f9 | |||
| 3ffeef5cf3 | |||
| 5ae3f4c62a | |||
| 2f8dfac9bc | |||
| 75db6da621 | |||
| fa5b1c671e | |||
| 72e26caf36 | |||
| 21f691cbd8 | |||
| d07e6f86c0 | |||
| 3a0172cccc | |||
| 59a31d38a2 | |||
| a17695ad06 | |||
| 7377c44e0f | |||
| fc82021f2b | |||
| 9ad9912ef1 | |||
| 7618fa8b63 | |||
| 170e585e57 | |||
| 72cace5a50 | |||
| d0373977b3 | |||
| f03525aac5 | |||
| fcb3d917db | |||
| 16ad4e776f | |||
| 87454c1ab1 | |||
| fa3e1202c4 | |||
| 41675e528f | |||
| a3d386dc39 | |||
| e5051408e5 | |||
| fa7cd2940e | |||
| e5736ecb51 | |||
| b90718982e | |||
| 07a7357c87 | |||
| dec7454866 | |||
| bcc631f88d | |||
| 9203ac32e9 | |||
| 141760ea19 | |||
| c7628a3638 | |||
| 5fdd6973f1 | |||
| 07f91d792a | |||
| 56b644ead9 | |||
| ea88ccb0ad | |||
| 382feea68d | |||
| 0dfe66f1b2 | |||
| cfccb046bc | |||
| 70f91d59b2 | |||
| 4d63908164 | |||
| 39c00323b3 | |||
| b0e23121a8 | |||
| 85629dea64 | |||
| 93269a05eb | |||
| 7aef012a42 | |||
| 7b89054b35 | |||
| 3e7cdd1caa | |||
| 3aa91b61e5 | |||
| 6763c86387 | |||
| 63b737dc52 | |||
| 55e60d52fd | |||
| 3730ba049f | |||
| 4fb263e643 | |||
| 0b28d64c83 | |||
| 68ce3d039d |
@@ -1,6 +1,8 @@
|
||||
Name: confluent-client
|
||||
Project: https://hpc.lenovo.com/users/
|
||||
Source: https://github.com/lenovo/confluent
|
||||
Upstream-Name: confluent-client
|
||||
|
||||
All file of the Confluent-client software is distributed under the terms of an Apache-2.0 license as indicated below:
|
||||
|
||||
Files: *
|
||||
Copyright: 2014-2019 Lenovo
|
||||
@@ -11,7 +13,8 @@ Copyright: 2014 IBM Corporation
|
||||
2015-2019 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: sortutil.py
|
||||
File: sortutil.py,
|
||||
tlvdata.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2016 Lenovo
|
||||
License: Apache-2.0
|
||||
@@ -19,3 +22,56 @@ License: Apache-2.0
|
||||
File: tlv.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
License: Apache-2.0
|
||||
|
||||
License: Apache-2.0
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import sys
|
||||
|
||||
@@ -84,6 +85,7 @@ fullline = sys.stdin.readline()
|
||||
printpending = True
|
||||
clearpending = False
|
||||
holdoff = 0
|
||||
padded = None
|
||||
while fullline:
|
||||
for line in fullline.split('\n'):
|
||||
if not line:
|
||||
@@ -92,13 +94,18 @@ while fullline:
|
||||
line = 'UNKNOWN: ' + line
|
||||
if options.log:
|
||||
node, output = line.split(':', 1)
|
||||
output = output.lstrip()
|
||||
if padded is None:
|
||||
if output.startswith(' '):
|
||||
padded = True
|
||||
else:
|
||||
padded = False
|
||||
if padded:
|
||||
output = re.sub(r'^ ', '', output)
|
||||
currlog = options.log.format(node=node, nodename=node)
|
||||
with open(currlog, mode='a') as log:
|
||||
log.write(output + '\n')
|
||||
continue
|
||||
node, output = line.split(':', 1)
|
||||
output = output.lstrip()
|
||||
grouped.add_line(node, output)
|
||||
if options.watch:
|
||||
if not holdoff:
|
||||
|
||||
Executable
+173
@@ -0,0 +1,173 @@
|
||||
#!/usr/libexec/platform-python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'tkucherera'
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <start_node> -i <interface> <end_node> -e <eface>",
|
||||
)
|
||||
argparser.add_option('-i', '--interface', type='str',
|
||||
help='interface to check path against for the start node')
|
||||
argparser.add_option('-e', '--eface', type='str',
|
||||
help='interface to check path against for the end node')
|
||||
argparser.add_option('-c', '--cumulus', action="store_true", dest="cumulus",
|
||||
help='return layer 2 route through cumulus switches only')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
try:
|
||||
start_node = args[0]
|
||||
end_node = args[1]
|
||||
interface = options.interface
|
||||
eface = options.eface
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
session = client.Command()
|
||||
|
||||
def get_neighbors(switch):
|
||||
switch_neigbors = []
|
||||
url = '/networking/neighbors/by-switch/{0}/by-peername/'.format(switch)
|
||||
for neighbor in session.read(url):
|
||||
switch = neighbor['item']['href'].strip('/')
|
||||
if switch in all_switches:
|
||||
switch_neigbors.append(switch)
|
||||
return switch_neigbors
|
||||
|
||||
def find_path(start, end, path=[]):
|
||||
path = path + [start]
|
||||
if start == end:
|
||||
return path # If start and end are the same, return the path
|
||||
|
||||
for node in get_neighbors(start):
|
||||
if node not in path:
|
||||
new_path = find_path(node, end, path)
|
||||
if new_path:
|
||||
return new_path # If a path is found, return it
|
||||
|
||||
return None # If no path is found, return None
|
||||
|
||||
def is_cumulus(switch):
|
||||
try:
|
||||
read_attrib = subprocess.check_output(['nodeattrib', switch, 'hardwaremanagement.method'])
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
for attribs in read_attrib.decode('utf-8').split('\n'):
|
||||
if len(attribs.split(':')) > 1:
|
||||
attrib = attribs.split(':')
|
||||
if attrib[2].strip() == 'affluent':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def host_to_switch(node, interface=None):
|
||||
# first check the the node config to see what switches are connected
|
||||
# if host is in rhel can use nmstate package
|
||||
if node in all_switches:
|
||||
return [node]
|
||||
switches = []
|
||||
netarg = 'net.*.switch'
|
||||
if interface:
|
||||
netarg = 'net.{0}.switch'.format(interface)
|
||||
try:
|
||||
read_attrib = subprocess.check_output(['nodeattrib', node, netarg])
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
for attribs in read_attrib.decode('utf-8').split('\n'):
|
||||
attrib = attribs.split(':')
|
||||
try:
|
||||
if ' net.mgt.switch' in attrib or attrib[2] == '':
|
||||
continue
|
||||
except IndexError:
|
||||
continue
|
||||
switch = attrib[2].strip()
|
||||
if is_cumulus(switch) and options.cumulus:
|
||||
switches.append(switch)
|
||||
else:
|
||||
switches.append(switch)
|
||||
return switches
|
||||
|
||||
def path_between_nodes(start_switches, end_switches):
|
||||
for start_switch in start_switches:
|
||||
for end_switch in end_switches:
|
||||
if start_switch == end_switch:
|
||||
return [start_switch]
|
||||
else:
|
||||
path = find_path(start_switch, end_switch)
|
||||
if path:
|
||||
return path
|
||||
else:
|
||||
return 'No path found'
|
||||
|
||||
|
||||
all_switches = []
|
||||
for res in session.read('/networking/neighbors/by-switch/'):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
switch = (res['item']['href'].replace('/', ''))
|
||||
all_switches.append(switch)
|
||||
|
||||
end_nodeslist = []
|
||||
nodelist = '/noderange/{0}/nodes/'.format(end_node)
|
||||
for res in session.read(nodelist):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
elem=(res['item']['href'].replace('/', ''))
|
||||
end_nodeslist.append(elem)
|
||||
|
||||
start_switches = host_to_switch(start_node, interface)
|
||||
for end_node in end_nodeslist:
|
||||
if end_node:
|
||||
end_switches = host_to_switch(end_node, eface)
|
||||
if not end_switches:
|
||||
print('Error: net.{0}.switch attribute is not valid')
|
||||
continue
|
||||
path = path_between_nodes(start_switches, end_switches)
|
||||
print(f'{start_node} to {end_node}: {path}')
|
||||
|
||||
# TODO dont put switches that are connected through management interfaces.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -102,9 +102,9 @@ def run():
|
||||
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[0]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
run_cmdv(sshnode, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
pendingexecs.append((sshnode, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
|
||||
@@ -126,13 +126,14 @@ elif options.set:
|
||||
argset = argset.strip()
|
||||
if argset:
|
||||
arglist += shlex.split(argset)
|
||||
argset = argfile.readline()
|
||||
argset = argfile.readline()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode=client.updateattrib(session,arglist,nodetype, noderange, options, None)
|
||||
if exitcode != 0:
|
||||
sys.exit(exitcode)
|
||||
|
||||
# Lists all attributes
|
||||
|
||||
if len(args) > 0:
|
||||
# setting output to all so it can search since if we do have something to search, we want to show all outputs even if it is blank.
|
||||
if requestargs is None:
|
||||
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/libexec/platform-python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'tkucherera'
|
||||
|
||||
from getpass import getpass
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> <username> <new_password>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Number of nodes to affect before prompting for confirmation')
|
||||
argparser.add_option('-p', '--prompt', action='store_true',
|
||||
help='Prompt for password values interactively')
|
||||
argparser.add_option('-e', '--environment', action='store_true',
|
||||
help='Set passwod, but from environment variable of '
|
||||
'same name')
|
||||
|
||||
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
|
||||
try:
|
||||
noderange = args[0]
|
||||
username = args[1]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
if options.prompt:
|
||||
oneval = 1
|
||||
twoval = 2
|
||||
while oneval != twoval:
|
||||
oneval = getpass('Enter pass for {0}: '.format(username))
|
||||
twoval = getpass('Confirm pass for {0}: '.format(username))
|
||||
if oneval != twoval:
|
||||
print('Values did not match.')
|
||||
new_password = twoval
|
||||
|
||||
elif len(args) == 3:
|
||||
if options.environment:
|
||||
key = args[2]
|
||||
new_password = os.environ.get(key, os.environ[key.upper()])
|
||||
else:
|
||||
new_password = args[2]
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
errorNodes = set([])
|
||||
uid_dict = {}
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
|
||||
for rsp in session.read('/noderange/{0}/configuration/management_controller/users/all'.format(noderange)):
|
||||
databynode = rsp["databynode"]
|
||||
for node in databynode:
|
||||
if 'error' in rsp['databynode'][node]:
|
||||
print(node, ':', rsp['databynode'][node]['error'])
|
||||
continue
|
||||
for user in rsp['databynode'][node]['users']:
|
||||
if user['username'] == username:
|
||||
if not user['uid'] in uid_dict:
|
||||
uid_dict[user['uid']] = node
|
||||
continue
|
||||
uid_dict[user['uid']] = uid_dict[user['uid']] + ',{}'.format(node)
|
||||
break
|
||||
|
||||
for uid in uid_dict:
|
||||
success = session.simple_noderange_command(uid_dict[uid], 'configuration/management_controller/users/{0}'.format(uid), new_password, key='password', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
allNodes = set([])
|
||||
|
||||
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if 'error' in node and success != 0:
|
||||
sys.exit(success)
|
||||
allNodes.add(node['item']['href'].replace("/", ""))
|
||||
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print(node + ": Password Change Successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
@@ -68,7 +68,7 @@ def main():
|
||||
else:
|
||||
elem=(res['item']['href'].replace('/', ''))
|
||||
list.append(elem)
|
||||
print(options.delim.join(list))
|
||||
print(options.delim.join(list))
|
||||
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
@@ -595,7 +595,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
else:
|
||||
printmissing.add(attr)
|
||||
for missing in printmissing:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(missing))
|
||||
return exitcode
|
||||
|
||||
|
||||
@@ -668,6 +668,9 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
|
||||
for attrib in updateargs[1:]:
|
||||
keydata[attrib] = None
|
||||
for res in session.update(targpath, keydata):
|
||||
for node in res.get('databynode', {}):
|
||||
for warnmsg in res['databynode'][node].get('_warnings', []):
|
||||
sys.stderr.write('Warning: ' + warnmsg + '\n')
|
||||
if 'error' in res:
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
@@ -702,6 +705,14 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
|
||||
noderange, 'attributes/all', dictassign[key], key)
|
||||
else:
|
||||
if "=" in updateargs[1]:
|
||||
update_ready = True
|
||||
for arg in updateargs[1:]:
|
||||
if not '=' in arg:
|
||||
update_ready = False
|
||||
exitcode = 1
|
||||
if not update_ready:
|
||||
sys.stderr.write('Error: {0} Can not set and read at the same time!\n'.format(str(updateargs[1:])))
|
||||
sys.exit(exitcode)
|
||||
try:
|
||||
for val in updateargs[1:]:
|
||||
val = val.split('=', 1)
|
||||
|
||||
@@ -98,17 +98,24 @@ class GroupedData(object):
|
||||
self.byoutput = {}
|
||||
self.header = {}
|
||||
self.client = confluentconnection
|
||||
self.detectedpad = None
|
||||
|
||||
def generate_byoutput(self):
|
||||
self.byoutput = {}
|
||||
thepad = self.detectedpad if self.detectedpad else ''
|
||||
for n in self.bynode:
|
||||
output = '\n'.join(self.bynode[n])
|
||||
output = ''
|
||||
for ln in self.bynode[n]:
|
||||
output += ln.replace(thepad, '', 1) + '\n'
|
||||
if output not in self.byoutput:
|
||||
self.byoutput[output] = set([n])
|
||||
else:
|
||||
self.byoutput[output].add(n)
|
||||
|
||||
def add_line(self, node, line):
|
||||
wspc = re.search(r'^\s*', line).group()
|
||||
if self.detectedpad is None or len(wspc) < len(self.detectedpad):
|
||||
self.detectedpad = wspc
|
||||
if node not in self.bynode:
|
||||
self.bynode[node] = [line]
|
||||
else:
|
||||
@@ -219,4 +226,4 @@ if __name__ == '__main__':
|
||||
if not line:
|
||||
continue
|
||||
groupoutput.add_line(*line.split(': ', 1))
|
||||
groupoutput.print_deviants()
|
||||
groupoutput.print_deviants()
|
||||
|
||||
@@ -153,11 +153,11 @@ _confluent_osimage_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 2 ]; then
|
||||
COMPREPLY=($(compgen -W "initialize import updateboot rebase" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
COMPREPLY=($(compgen -W "initialize import importcheck updateboot rebase" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'initialize' ]; then
|
||||
COMPREPLY=($(compgen -W "-h -u -s -t -i" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
elif [ ${CMPARGS[1]} == 'import' ]; then
|
||||
elif [ ${CMPARGS[1]} == 'import' ] || [ ${CMPARGS[1]} == 'importcheck' ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
l2traceroute(8) -- returns the layer 2 route through an Ethernet network managed by confluent given 2 end points.
|
||||
==============================
|
||||
## SYNOPSIS
|
||||
`l2traceroute [options] <start_node> <end_noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
**l2traceroute** is a command that returns the layer 2 route for the configered interfaces in nodeattrib.
|
||||
It can also be used with the -i and -e options to check against specific interfaces on the endpoints.
|
||||
|
||||
|
||||
## PREREQUISITES
|
||||
**l2traceroute** the net.<interface>.switch attributes have to be set on the end points if endpoint is not a switch
|
||||
|
||||
|
||||
## OPTIONS
|
||||
* ` -e` EFACE, --eface=INTERFACE
|
||||
interface to check against for the second end point
|
||||
* ` -i` INTERFACE, --interface=INTERFACE
|
||||
interface to check against for the first end point
|
||||
* ` -c` CUMULUS, --cumulus=CUMULUS
|
||||
return layer 2 route through cumulus switches only
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
* Checking route between two nodes:
|
||||
`# l2traceroute_client n244 n1851`
|
||||
`n244 to n1851: ['switch114']`
|
||||
|
||||
* Checking route from one node to multiple nodes:
|
||||
`# l2traceroute_client n244 n1833,n1851`
|
||||
`n244 to n1833: ['switch114', 'switch7', 'switch32', 'switch253', 'switch85', 'switch72', 'switch21', 'switch2', 'switch96', 'switch103', 'switch115']
|
||||
n244 to n1851: ['switch114']`
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
nodeapply(8) -- Execute command on many nodes in a noderange through ssh
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeapply [options] <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Provides shortcut access to a number of common operations against deployed
|
||||
nodes. These operations include refreshing ssh certificates and configuration,
|
||||
rerunning syncflies, and executing specified postscripts.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-k`, `--security`
|
||||
Refresh SSH configuration (hosts.equiv and node SSH certificates)
|
||||
|
||||
* `-F`, `--sync`
|
||||
Rerun syncfiles from deployed profile
|
||||
|
||||
* `-P SCRIPTS`, `--scripts=SCRIPTS`
|
||||
Re-run specified scripts, with full path under scripts specified, e.g. post.d/scriptname,firstboot.d/otherscriptname
|
||||
|
||||
* `-c COUNT`, `-f COUNT`, `--count=COUNT`
|
||||
Specify the maximum number of instances to run concurrently
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`
|
||||
Specify a maximum number of nodes to run remote ssh command to, prompting
|
||||
if over the threshold
|
||||
@@ -0,0 +1,28 @@
|
||||
nodebmcpassword(8) -- Change management controller password for a specified user
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodebmcpassword <noderange> <username> <new_password>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodebmcpassword` allows you to change the management controller password for a user on a specified noderange
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Number of nodes to affect before prompting for
|
||||
confirmation
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
* Reset the management controller for nodes n1 through n4:
|
||||
`# nodebmcreset n1-n4`
|
||||
`n1: Password Change Successful`
|
||||
`n2: Password Change Successful`
|
||||
`n3: Password Change Successful`
|
||||
`n4: Password Change Successful`
|
||||
@@ -295,7 +295,8 @@ class WickedManager(object):
|
||||
|
||||
|
||||
class NetworkManager(object):
|
||||
def __init__(self, devtypes):
|
||||
def __init__(self, devtypes, deploycfg):
|
||||
self.deploycfg = deploycfg
|
||||
self.connections = {}
|
||||
self.uuidbyname = {}
|
||||
self.uuidbydev = {}
|
||||
@@ -367,6 +368,20 @@ class NetworkManager(object):
|
||||
cmdargs['ipv4.gateway'] = stgs['ipv4_gateway']
|
||||
if stgs.get('ipv6_gateway', None):
|
||||
cmdargs['ipv6.gateway'] = stgs['ipv6_gateway']
|
||||
dnsips = self.deploycfg.get('nameservers', [])
|
||||
if not dnsips:
|
||||
dnsips = []
|
||||
dns4 = []
|
||||
dns6 = []
|
||||
for dnsip in dnsips:
|
||||
if '.' in dnsip:
|
||||
dns4.append(dnsip)
|
||||
elif ':' in dnsip:
|
||||
dns6.append(dnsip)
|
||||
if dns4:
|
||||
cmdargs['ipv4.dns'] = ','.join(dns4)
|
||||
if dns6:
|
||||
cmdargs['ipv6.dns'] = ','.join(dns6)
|
||||
if len(cfg['interfaces']) > 1: # team time.. should be..
|
||||
if not cfg['settings'].get('team_mode', None):
|
||||
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
|
||||
@@ -379,6 +394,8 @@ class NetworkManager(object):
|
||||
for arg in cmdargs:
|
||||
cargs.append(arg)
|
||||
cargs.append(cmdargs[arg])
|
||||
if stgs['team_mode'] == 'lacp':
|
||||
stgs['team_mode'] = '802.3ad'
|
||||
subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond', 'con-name', cname, 'connection.interface-name', cname, 'bond.options', 'mode={}'.format(stgs['team_mode'])] + cargs)
|
||||
for iface in cfg['interfaces']:
|
||||
self.add_team_member(cname, iface)
|
||||
@@ -485,7 +502,7 @@ if __name__ == '__main__':
|
||||
if os.path.exists('/usr/sbin/netplan'):
|
||||
nm = NetplanManager(dc)
|
||||
if os.path.exists('/usr/bin/nmcli'):
|
||||
nm = NetworkManager(devtypes)
|
||||
nm = NetworkManager(devtypes, dc)
|
||||
elif os.path.exists('/usr/sbin/wicked'):
|
||||
nm = WickedManager()
|
||||
for netn in netname_to_interfaces:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
is_suse=false
|
||||
is_rhel=false
|
||||
|
||||
if test -f /boot/efi/EFI/redhat/grub.cfg; then
|
||||
grubcfg="/boot/efi/EFI/redhat/grub.cfg"
|
||||
grub2-mkconfig -o $grubcfg
|
||||
is_rhel=true
|
||||
elif test -f /boot/efi/EFI/sle_hpc/grub.cfg; then
|
||||
grubcfg="/boot/efi/EFI/sle_hpc/grub.cfg"
|
||||
grub2-mkconfig -o $grubcfg
|
||||
is_suse=true
|
||||
else
|
||||
echo "Expected File missing: Check if os sle_hpc or redhat"
|
||||
exit
|
||||
fi
|
||||
|
||||
# working on SUSE
|
||||
if $is_suse; then
|
||||
start=false
|
||||
num_line=0
|
||||
lines_to_edit=()
|
||||
while read line; do
|
||||
((num_line++))
|
||||
if [[ $line == *"grub_platform"* ]]; then
|
||||
start=true
|
||||
fi
|
||||
if $start; then
|
||||
if [[ $line != "#"* ]];then
|
||||
lines_to_edit+=($num_line)
|
||||
fi
|
||||
fi
|
||||
if [[ ${#line} -eq 2 && $line == *"fi" ]]; then
|
||||
if $start; then
|
||||
start=false
|
||||
fi
|
||||
fi
|
||||
done < grub_cnf.cfg
|
||||
|
||||
for line_num in "${lines_to_edit[@]}"; do
|
||||
line_num+="s"
|
||||
sed -i "${line_num},^,#," $grubcfg
|
||||
done
|
||||
sed -i 's,^terminal,#terminal,' $grubcfg
|
||||
fi
|
||||
|
||||
# Working on Redhat
|
||||
if $is_rhel; then
|
||||
sed -i 's,^serial,#serial, ; s,^terminal,#terminal,' $grubcfg
|
||||
fi
|
||||
@@ -26,7 +26,7 @@ mkdir -p opt/confluent/bin
|
||||
mkdir -p stateless-bin
|
||||
cp -a el8bin/* .
|
||||
ln -s el8 el9
|
||||
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 coreos el9; do
|
||||
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9; do
|
||||
mkdir ${os}out
|
||||
cd ${os}out
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -76,7 +76,7 @@ cp -a esxi7 esxi8
|
||||
%install
|
||||
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
#cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu22.04 esxi6 esxi7 esxi8 coreos; do
|
||||
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
|
||||
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
|
||||
if [ -d ${os}disklessout ]; then
|
||||
|
||||
@@ -28,7 +28,7 @@ This contains support utilities for enabling deployment of x86_64 architecture s
|
||||
#cp start_root urlmount ../stateless-bin/
|
||||
#cd ..
|
||||
ln -s el8 el9
|
||||
for os in rhvh4 el7 genesis el8 suse15 ubuntu18.04 ubuntu20.04 ubuntu22.04 coreos el9; do
|
||||
for os in rhvh4 el7 genesis el8 suse15 ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9; do
|
||||
mkdir ${os}out
|
||||
cd ${os}out
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -42,7 +42,7 @@ for os in rhvh4 el7 genesis el8 suse15 ubuntu18.04 ubuntu20.04 ubuntu22.04 coreo
|
||||
mv ../addons.cpio .
|
||||
cd ..
|
||||
done
|
||||
for os in el7 el8 suse15 el9 ubuntu20.04 ubuntu22.04; do
|
||||
for os in el7 el8 suse15 el9 ubuntu20.04 ubuntu22.04 ubuntu24.04; do
|
||||
mkdir ${os}disklessout
|
||||
cd ${os}disklessout
|
||||
if [ -d ../${os}bin ]; then
|
||||
@@ -78,7 +78,7 @@ cp -a esxi7 esxi8
|
||||
%install
|
||||
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu18.04 ubuntu22.04 esxi6 esxi7 esxi8 coreos; do
|
||||
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu18.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
|
||||
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
import time
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
@@ -223,6 +224,7 @@ def synchronize():
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(2)
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
import pwd
|
||||
import time
|
||||
import grp
|
||||
try:
|
||||
from importlib.machinery import SourceFileLoader
|
||||
@@ -223,6 +224,7 @@ def synchronize():
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(2)
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
|
||||
+2
-2
@@ -155,7 +155,7 @@ fi
|
||||
ready=0
|
||||
while [ $ready = "0" ]; do
|
||||
get_remote_apikey
|
||||
if [[ $confluent_mgr == *:* ]]; then
|
||||
if [[ $confluent_mgr == *:* ]] && [[ $confluent_mgr != "["* ]]; then
|
||||
confluent_mgr="[$confluent_mgr]"
|
||||
fi
|
||||
tmperr=$(mktemp)
|
||||
@@ -324,7 +324,7 @@ fi
|
||||
echo '[proxy]' >> /run/NetworkManager/system-connections/$ifname.nmconnection
|
||||
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
|
||||
confluent_websrv=$confluent_mgr
|
||||
if [[ $confluent_websrv == *:* ]]; then
|
||||
if [[ $confluent_websrv == *:* ]] && [[ $confluent_websrv != "["* ]]; then
|
||||
confluent_websrv="[$confluent_websrv]"
|
||||
fi
|
||||
echo -n "Initializing ssh..."
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -10,6 +10,7 @@ import stat
|
||||
import struct
|
||||
import sys
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
bootuuid = None
|
||||
|
||||
@@ -426,4 +427,9 @@ def install_to_disk(imgpath):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
try:
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
time.sleep(86400)
|
||||
raise
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
. /lib/dracut-lib.sh
|
||||
confluent_whost=$confluent_mgr
|
||||
if [[ "$confluent_whost" == *:* ]]; then
|
||||
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
|
||||
@@ -16,6 +16,7 @@ if [ -z "$confluent_mgr" ]; then
|
||||
fi
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
timedatectl set-timezone $(grep ^timezone: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
hostnamectl set-hostname $nodename
|
||||
export nodename confluent_mgr confluent_profile
|
||||
. /etc/confluent/functions
|
||||
mkdir -p /var/log/confluent
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -7,8 +7,8 @@ if ! grep console= /proc/cmdline >& /dev/null; then
|
||||
if [ -n "$autocons" ]; then
|
||||
echo console=$autocons |sed -e 's!/dev/!!' >> /tmp/01-autocons.conf
|
||||
autocons=${autocons%,*}
|
||||
echo $autocons > /tmp/01-autocons.devnode
|
||||
echo "Detected firmware specified console at $(cat /tmp/01-autocons.conf)" > $autocons
|
||||
echo $autocons > /tmp/01-autocons.devnode
|
||||
echo "Detected firmware specified console at $(cat /tmp/01-autocons.conf)" > $autocons
|
||||
echo "Initializing auto detected console when installer starts" > $autocons
|
||||
fi
|
||||
fi
|
||||
@@ -16,4 +16,5 @@ if grep console=ttyS /proc/cmdline >& /dev/null; then
|
||||
echo "Serial console has been requested in the kernel arguments, the local video may not show progress" > /dev/tty1
|
||||
fi
|
||||
. /lib/anaconda-lib.sh
|
||||
echo rd.fcoe=0 > /etc/cmdline.d/nofcoe.conf
|
||||
wait_for_kickstart
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
[ -e /tmp/confluent.initq ] && return 0
|
||||
. /lib/dracut-lib.sh
|
||||
setsid sh -c 'exec bash <> /dev/tty2 >&0 2>&1' &
|
||||
if [ -f /tmp/dd_disk ]; then
|
||||
for dd in $(cat /tmp/dd_disk); do
|
||||
if [ -e $dd ]; then
|
||||
driver-updates --disk $dd $dd
|
||||
rm $dd
|
||||
fi
|
||||
done
|
||||
rm /tmp/dd_disk
|
||||
fi
|
||||
udevadm trigger
|
||||
udevadm trigger --type=devices --action=add
|
||||
udevadm settle
|
||||
@@ -20,13 +29,6 @@ function confluentpython() {
|
||||
/usr/bin/python2 $*
|
||||
fi
|
||||
}
|
||||
if [ -f /tmp/dd_disk ]; then
|
||||
for dd in $(cat /tmp/dd_disk); do
|
||||
if [ -e $dd ]; then
|
||||
driver-updates --disk $dd $dd
|
||||
fi
|
||||
done
|
||||
fi
|
||||
vlaninfo=$(getarg vlan)
|
||||
if [ ! -z "$vlaninfo" ]; then
|
||||
vldev=${vlaninfo#*:}
|
||||
@@ -61,43 +63,52 @@ if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
|
||||
udevadm info $i | grep ID_NET_DRIVER=cdc_ether > /dev/null && continue
|
||||
ip link set $(basename $i) up
|
||||
done
|
||||
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK| awk '{print $2}' | sed -e 's/:$//'); do
|
||||
if [ "$autoconfigmethod" = "dhcp" ]; then
|
||||
/usr/libexec/nm-initrd-generator ip=$NICGUESS:dhcp
|
||||
else
|
||||
v4addr=$(grep ^ipv4_address: $tcfg)
|
||||
v4addr=${v4addr#ipv4_address: }
|
||||
v4plen=${v4addr#*/}
|
||||
v4addr=${v4addr%/*}
|
||||
v4gw=$(grep ^ipv4_gateway: $tcfg)
|
||||
v4gw=${v4gw#ipv4_gateway: }
|
||||
ip addr add dev $NICGUESS $v4addr/$v4plen
|
||||
if [ "$v4gw" = "null" ]; then
|
||||
v4gw=""
|
||||
fi
|
||||
if [ ! -z "$v4gw" ]; then
|
||||
ip route add default via $v4gw
|
||||
fi
|
||||
v4nm=$(grep ipv4_netmask: $tcfg)
|
||||
v4nm=${v4nm#ipv4_netmask: }
|
||||
DETECTED=0
|
||||
for dsrv in $deploysrvs; do
|
||||
if curl --capath /tls/ -s --connect-timeout 3 https://$dsrv/confluent-public/ > /dev/null; then
|
||||
rm /run/NetworkManager/system-connections/*
|
||||
/usr/libexec/nm-initrd-generator ip=$v4addr::$v4gw:$v4nm:$hostname:$NICGUESS:none
|
||||
DETECTED=1
|
||||
ifname=$NICGUESS
|
||||
TRIES=30
|
||||
DETECTED=0
|
||||
while [ "$DETECTED" = 0 ] && [ $TRIES -gt 0 ]; do
|
||||
TRIES=$((TRIES - 1))
|
||||
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK| awk '{print $2}' | sed -e 's/:$//'); do
|
||||
if [ "$autoconfigmethod" = "dhcp" ]; then
|
||||
/usr/libexec/nm-initrd-generator ip=$NICGUESS:dhcp
|
||||
else
|
||||
v4addr=$(grep ^ipv4_address: $tcfg)
|
||||
v4addr=${v4addr#ipv4_address: }
|
||||
v4plen=${v4addr#*/}
|
||||
v4addr=${v4addr%/*}
|
||||
v4gw=$(grep ^ipv4_gateway: $tcfg)
|
||||
v4gw=${v4gw#ipv4_gateway: }
|
||||
ip addr add dev $NICGUESS $v4addr/$v4plen
|
||||
if [ "$v4gw" = "null" ]; then
|
||||
v4gw=""
|
||||
fi
|
||||
if [ ! -z "$v4gw" ]; then
|
||||
ip route add default via $v4gw
|
||||
fi
|
||||
v4nm=$(grep ipv4_netmask: $tcfg)
|
||||
v4nm=${v4nm#ipv4_netmask: }
|
||||
DETECTED=0
|
||||
for dsrv in $deploysrvs; do
|
||||
if curl --capath /tls/ -s --connect-timeout 3 https://$dsrv/confluent-public/ > /dev/null; then
|
||||
rm /run/NetworkManager/system-connections/*
|
||||
/usr/libexec/nm-initrd-generator ip=$v4addr::$v4gw:$v4nm:$hostname:$NICGUESS:none
|
||||
DETECTED=1
|
||||
ifname=$NICGUESS
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ ! -z "$v4gw" ]; then
|
||||
ip route del default via $v4gw
|
||||
fi
|
||||
ip addr flush dev $NICGUESS
|
||||
if [ $DETECTED = 1 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ ! -z "$v4gw" ]; then
|
||||
ip route del default via $v4gw
|
||||
fi
|
||||
ip addr flush dev $NICGUESS
|
||||
if [ $DETECTED = 1 ]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK| awk '{print $2}' | sed -e 's/:$//'); do
|
||||
ip addr flush dev $NICGUESS
|
||||
ip link set $NICGUESS down
|
||||
done
|
||||
NetworkManager --configure-and-quit=initrd --no-daemon
|
||||
hmackeyfile=/tmp/cnflnthmackeytmp
|
||||
@@ -175,7 +186,7 @@ if [ ! -z "$autocons" ]; then
|
||||
errout="-e $autocons"
|
||||
fi
|
||||
while ! confluentpython /opt/confluent/bin/apiclient $errout /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg; do
|
||||
sleep 10
|
||||
sleep 10
|
||||
done
|
||||
ifidx=$(cat /tmp/confluent.ifidx 2> /dev/null)
|
||||
if [ -z "$ifname" ]; then
|
||||
@@ -216,23 +227,38 @@ proto=${proto#protocol: }
|
||||
textconsole=$(grep ^textconsole: /etc/confluent/confluent.deploycfg)
|
||||
textconsole=${textconsole#textconsole: }
|
||||
if [ "$textconsole" = "true" ] && ! grep console= /proc/cmdline > /dev/null; then
|
||||
autocons=$(cat /tmp/01-autocons.devnode)
|
||||
if [ ! -z "$autocons" ]; then
|
||||
echo Auto-configuring installed system to use text console
|
||||
echo Auto-configuring installed system to use text console > $autocons
|
||||
autocons=$(cat /tmp/01-autocons.devnode)
|
||||
if [ ! -z "$autocons" ]; then
|
||||
echo Auto-configuring installed system to use text console
|
||||
echo Auto-configuring installed system to use text console > $autocons
|
||||
/opt/confluent/bin/autocons -c > /dev/null
|
||||
cp /tmp/01-autocons.conf /etc/cmdline.d/
|
||||
else
|
||||
echo "Unable to automatically detect requested text console"
|
||||
fi
|
||||
cp /tmp/01-autocons.conf /etc/cmdline.d/
|
||||
else
|
||||
echo "Unable to automatically detect requested text console"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo inst.repo=$proto://$mgr/confluent-public/os/$profilename/distribution >> /etc/cmdline.d/01-confluent.conf
|
||||
. /etc/os-release
|
||||
if [ "$ID" = "dracut" ]; then
|
||||
ID=$(echo $PRETTY_NAME|awk '{print $1}')
|
||||
VERSION_ID=$(echo $VERSION|awk '{print $1}')
|
||||
if [ "$ID" = "Oracle" ]; then
|
||||
ID=OL
|
||||
elif [ "$ID" = "Red" ]; then
|
||||
ID=RHEL
|
||||
fi
|
||||
fi
|
||||
ISOSRC=$(blkid -t TYPE=iso9660|grep -Ei ' LABEL="'$ID-$VERSION_ID|sed -e s/:.*//)
|
||||
if [ -z "$ISOSRC" ]; then
|
||||
echo inst.repo=$proto://$mgr/confluent-public/os/$profilename/distribution >> /etc/cmdline.d/01-confluent.conf
|
||||
root=anaconda-net:$proto://$mgr/confluent-public/os/$profilename/distribution
|
||||
export root
|
||||
else
|
||||
echo inst.repo=cdrom:$ISOSRC >> /etc/cmdline.d/01-confluent.conf
|
||||
fi
|
||||
echo inst.ks=$proto://$mgr/confluent-public/os/$profilename/kickstart >> /etc/cmdline.d/01-confluent.conf
|
||||
kickstart=$proto://$mgr/confluent-public/os/$profilename/kickstart
|
||||
root=anaconda-net:$proto://$mgr/confluent-public/os/$profilename/distribution
|
||||
export kickstart
|
||||
export root
|
||||
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg)
|
||||
autoconfigmethod=${autoconfigmethod#ipv4_method: }
|
||||
if [ "$autoconfigmethod" = "dhcp" ]; then
|
||||
@@ -312,4 +338,8 @@ if [ -e /lib/nm-lib.sh ]; then
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK| awk '{print $2}' | sed -e 's/:$//'); do
|
||||
ip addr flush dev $NICGUESS
|
||||
ip link set $NICGUESS down
|
||||
done
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/bin/sh
|
||||
grep HostCert /etc/ssh/sshd_config.anaconda >> /mnt/sysimage/etc/ssh/sshd_config
|
||||
echo HostbasedAuthentication yes >> /mnt/sysimage/etc/ssh/sshd_config
|
||||
echo HostbasedUsesNameFromPacketOnly yes >> /mnt/sysimage/etc/ssh/sshd_config
|
||||
echo IgnoreRhosts no >> /mnt/sysimage/etc/ssh/sshd_config
|
||||
targssh=/mnt/sysimage/etc/ssh/sshd_config
|
||||
if [ -d /mnt/sysimage/etc/ssh/sshd_config.d/ ]; then
|
||||
targssh=/mnt/sysimage/etc/ssh/sshd_config.d/90-confluent.conf
|
||||
fi
|
||||
grep HostCert /etc/ssh/sshd_config.anaconda >> $targssh
|
||||
echo HostbasedAuthentication yes >> $targssh
|
||||
echo HostbasedUsesNameFromPacketOnly yes >> $targssh
|
||||
echo IgnoreRhosts no >> $targssh
|
||||
sshconf=/mnt/sysimage/etc/ssh/ssh_config
|
||||
if [ -d /mnt/sysimage/etc/ssh/ssh_config.d/ ]; then
|
||||
sshconf=/mnt/sysimage/etc/ssh/ssh_config.d/01-confluent.conf
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
+2
-2
@@ -120,7 +120,7 @@ fi
|
||||
ready=0
|
||||
while [ $ready = "0" ]; do
|
||||
get_remote_apikey
|
||||
if [[ $confluent_mgr == *:* ]]; then
|
||||
if [[ $confluent_mgr == *:* ]] && [[ $confluent_mgr != "["* ]]; then
|
||||
confluent_mgr="[$confluent_mgr]"
|
||||
fi
|
||||
tmperr=$(mktemp)
|
||||
@@ -281,7 +281,7 @@ fi
|
||||
echo '[proxy]' >> /run/NetworkManager/system-connections/$ifname.nmconnection
|
||||
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
|
||||
confluent_websrv=$confluent_mgr
|
||||
if [[ $confluent_websrv == *:* ]]; then
|
||||
if [[ $confluent_websrv == *:* ]] && [[ $confluent_websrv != "["* ]]; then
|
||||
confluent_websrv="[$confluent_websrv]"
|
||||
fi
|
||||
echo -n "Initializing ssh..."
|
||||
|
||||
@@ -9,9 +9,16 @@ HOME=$(getent passwd $(whoami)|cut -d: -f 6)
|
||||
export HOME
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
if [ -z "$confluent_mgr" ] || [ "$confluent_mgr" == "null" ] || ! ping -c 1 $confluent_mgr >& /dev/null; then
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
fi
|
||||
confluent_websrv=$confluent_mgr
|
||||
if [[ "$confluent_mgr" == *:* ]]; then
|
||||
confluent_websrv="[$confluent_mgr]"
|
||||
fi
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
export nodename confluent_mgr confluent_profile
|
||||
export nodename confluent_mgr confluent_profile confluent_websrv
|
||||
. /etc/confluent/functions
|
||||
(
|
||||
exec >> /var/log/confluent/confluent-firstboot.log
|
||||
@@ -34,7 +41,7 @@ if [ ! -f /etc/confluent/firstboot.ran ]; then
|
||||
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
|
||||
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_websrv/confluent-api/self/updatestatus
|
||||
systemctl disable firstboot
|
||||
rm /etc/systemd/system/firstboot.service
|
||||
rm /etc/confluent/firstboot.ran
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -10,8 +10,16 @@ import stat
|
||||
import struct
|
||||
import sys
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
bootuuid = None
|
||||
vgname = 'localstorage'
|
||||
oldvgname = None
|
||||
|
||||
def convert_lv(oldlvname):
|
||||
if oldvgname is None:
|
||||
return None
|
||||
return oldlvname.replace(oldvgname, vgname)
|
||||
|
||||
def get_partname(devname, idx):
|
||||
if devname[-1] in '0123456789':
|
||||
@@ -53,6 +61,8 @@ def get_image_metadata(imgpath):
|
||||
header = img.read(16)
|
||||
if header == b'\x63\x7b\x9d\x26\xb7\xfd\x48\x30\x89\xf9\x11\xcf\x18\xfd\xff\xa1':
|
||||
for md in get_multipart_image_meta(img):
|
||||
if md.get('device', '').startswith('/dev/zram'):
|
||||
continue
|
||||
yield md
|
||||
else:
|
||||
raise Exception('Installation from single part image not supported')
|
||||
@@ -86,14 +96,14 @@ def fixup(rootdir, vols):
|
||||
if tab.startswith('#ORIGFSTAB#'):
|
||||
if entry[1] in devbymount:
|
||||
targetdev = devbymount[entry[1]]
|
||||
if targetdev.startswith('/dev/localstorage/'):
|
||||
if targetdev.startswith('/dev/{}/'.format(vgname)):
|
||||
entry[0] = targetdev
|
||||
else:
|
||||
uuid = subprocess.check_output(['blkid', '-s', 'UUID', '-o', 'value', targetdev]).decode('utf8')
|
||||
uuid = uuid.strip()
|
||||
entry[0] = 'UUID={}'.format(uuid)
|
||||
elif entry[2] == 'swap':
|
||||
entry[0] = '/dev/mapper/localstorage-swap'
|
||||
entry[0] = '/dev/mapper/{}-swap'.format(vgname.replace('-', '--'))
|
||||
entry[0] = entry[0].ljust(42)
|
||||
entry[1] = entry[1].ljust(16)
|
||||
entry[3] = entry[3].ljust(28)
|
||||
@@ -141,6 +151,46 @@ def fixup(rootdir, vols):
|
||||
grubsyscfg = os.path.join(rootdir, 'etc/sysconfig/grub')
|
||||
if not os.path.exists(grubsyscfg):
|
||||
grubsyscfg = os.path.join(rootdir, 'etc/default/grub')
|
||||
kcmdline = os.path.join(rootdir, 'etc/kernel/cmdline')
|
||||
if os.path.exists(kcmdline):
|
||||
with open(kcmdline) as kcmdlinein:
|
||||
kcmdlinecontent = kcmdlinein.read()
|
||||
newkcmdlineent = []
|
||||
for ent in kcmdlinecontent.split():
|
||||
if ent.startswith('resume='):
|
||||
newkcmdlineent.append('resume={}'.format(newswapdev))
|
||||
elif ent.startswith('root='):
|
||||
newkcmdlineent.append('root={}'.format(newrootdev))
|
||||
elif ent.startswith('rd.lvm.lv='):
|
||||
ent = convert_lv(ent)
|
||||
if ent:
|
||||
newkcmdlineent.append(ent)
|
||||
else:
|
||||
newkcmdlineent.append(ent)
|
||||
with open(kcmdline, 'w') as kcmdlineout:
|
||||
kcmdlineout.write(' '.join(newkcmdlineent) + '\n')
|
||||
for loadent in glob.glob(os.path.join(rootdir, 'boot/loader/entries/*.conf')):
|
||||
with open(loadent) as loadentin:
|
||||
currentry = loadentin.read().split('\n')
|
||||
with open(loadent, 'w') as loadentout:
|
||||
for cfgline in currentry:
|
||||
cfgparts = cfgline.split()
|
||||
if not cfgparts or cfgparts[0] != 'options':
|
||||
loadentout.write(cfgline + '\n')
|
||||
continue
|
||||
newcfgparts = [cfgparts[0]]
|
||||
for cfgpart in cfgparts[1:]:
|
||||
if cfgpart.startswith('root='):
|
||||
newcfgparts.append('root={}'.format(newrootdev))
|
||||
elif cfgpart.startswith('resume='):
|
||||
newcfgparts.append('resume={}'.format(newswapdev))
|
||||
elif cfgpart.startswith('rd.lvm.lv='):
|
||||
cfgpart = convert_lv(cfgpart)
|
||||
if cfgpart:
|
||||
newcfgparts.append(cfgpart)
|
||||
else:
|
||||
newcfgparts.append(cfgpart)
|
||||
loadentout.write(' '.join(newcfgparts) + '\n')
|
||||
with open(grubsyscfg) as defgrubin:
|
||||
defgrub = defgrubin.read().split('\n')
|
||||
with open(grubsyscfg, 'w') as defgrubout:
|
||||
@@ -148,9 +198,18 @@ def fixup(rootdir, vols):
|
||||
gline = gline.split()
|
||||
newline = []
|
||||
for ent in gline:
|
||||
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
|
||||
continue
|
||||
newline.append(ent)
|
||||
if ent.startswith('resume='):
|
||||
newline.append('resume={}'.format(newswapdev))
|
||||
elif ent.startswith('root='):
|
||||
newline.append('root={}'.format(newrootdev))
|
||||
elif ent.startswith('rd.lvm.lv='):
|
||||
ent = convert_lv(ent)
|
||||
if ent:
|
||||
newline.append(ent)
|
||||
elif '""' in ent:
|
||||
newline.append('""')
|
||||
else:
|
||||
newline.append(ent)
|
||||
defgrubout.write(' '.join(newline) + '\n')
|
||||
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/').replace('//', '/')
|
||||
grubcfg = grubcfg.split('\n')
|
||||
@@ -227,8 +286,14 @@ def had_swap():
|
||||
return True
|
||||
return False
|
||||
|
||||
newrootdev = None
|
||||
newswapdev = None
|
||||
def install_to_disk(imgpath):
|
||||
global bootuuid
|
||||
global newrootdev
|
||||
global newswapdev
|
||||
global vgname
|
||||
global oldvgname
|
||||
lvmvols = {}
|
||||
deftotsize = 0
|
||||
mintotsize = 0
|
||||
@@ -260,6 +325,13 @@ def install_to_disk(imgpath):
|
||||
biggestfs = fs
|
||||
biggestsize = fs['initsize']
|
||||
if fs['device'].startswith('/dev/mapper'):
|
||||
oldvgname = fs['device'].rsplit('/', 1)[-1]
|
||||
# if node has - then /dev/mapper will double up the hypen
|
||||
if '_' in oldvgname and '-' in oldvgname.split('_')[-1]:
|
||||
oldvgname = oldvgname.rsplit('-', 1)[0].replace('--', '-')
|
||||
osname = oldvgname.split('_')[0]
|
||||
nodename = socket.gethostname().split('.')[0]
|
||||
vgname = '{}_{}'.format(osname, nodename)
|
||||
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
|
||||
deflvmsize += fs['initsize']
|
||||
minlvmsize += fs['minsize']
|
||||
@@ -304,6 +376,8 @@ def install_to_disk(imgpath):
|
||||
end = sectors
|
||||
parted.run('mkpart primary {}s {}s'.format(curroffset, end))
|
||||
vol['targetdisk'] = get_partname(instdisk, volidx)
|
||||
if vol['mount'] == '/':
|
||||
newrootdev = vol['targetdisk']
|
||||
curroffset += size + 1
|
||||
if not lvmvols:
|
||||
if swapsize:
|
||||
@@ -313,13 +387,14 @@ def install_to_disk(imgpath):
|
||||
if end > sectors:
|
||||
end = sectors
|
||||
parted.run('mkpart swap {}s {}s'.format(curroffset, end))
|
||||
subprocess.check_call(['mkswap', get_partname(instdisk, volidx + 1)])
|
||||
newswapdev = get_partname(instdisk, volidx + 1)
|
||||
subprocess.check_call(['mkswap', newswapdev])
|
||||
else:
|
||||
parted.run('mkpart lvm {}s 100%'.format(curroffset))
|
||||
lvmpart = get_partname(instdisk, volidx + 1)
|
||||
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
|
||||
subprocess.check_call(['vgcreate', 'localstorage', lvmpart])
|
||||
vginfo = subprocess.check_output(['vgdisplay', 'localstorage', '--units', 'b']).decode('utf8')
|
||||
subprocess.check_call(['vgcreate', vgname, lvmpart])
|
||||
vginfo = subprocess.check_output(['vgdisplay', vgname, '--units', 'b']).decode('utf8')
|
||||
vginfo = vginfo.split('\n')
|
||||
pesize = 0
|
||||
pes = 0
|
||||
@@ -346,13 +421,17 @@ def install_to_disk(imgpath):
|
||||
extents += 1
|
||||
if vol['mount'] == '/':
|
||||
lvname = 'root'
|
||||
|
||||
else:
|
||||
lvname = vol['mount'].replace('/', '_')
|
||||
subprocess.check_call(['lvcreate', '-l', '{}'.format(extents), '-y', '-n', lvname, 'localstorage'])
|
||||
vol['targetdisk'] = '/dev/localstorage/{}'.format(lvname)
|
||||
subprocess.check_call(['lvcreate', '-l', '{}'.format(extents), '-y', '-n', lvname, vgname])
|
||||
vol['targetdisk'] = '/dev/{}/{}'.format(vgname, lvname)
|
||||
if vol['mount'] == '/':
|
||||
newrootdev = vol['targetdisk']
|
||||
if swapsize:
|
||||
subprocess.check_call(['lvcreate', '-y', '-l', '{}'.format(swapsize // pesize), '-n', 'swap', 'localstorage'])
|
||||
subprocess.check_call(['mkswap', '/dev/localstorage/swap'])
|
||||
subprocess.check_call(['lvcreate', '-y', '-l', '{}'.format(swapsize // pesize), '-n', 'swap', vgname])
|
||||
subprocess.check_call(['mkswap', '/dev/{}/swap'.format(vgname)])
|
||||
newswapdev = '/dev/{}/swap'.format(vgname)
|
||||
os.makedirs('/run/imginst/targ')
|
||||
for vol in allvols:
|
||||
with open(vol['targetdisk'], 'wb') as partition:
|
||||
@@ -426,4 +505,9 @@ def install_to_disk(imgpath):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
try:
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
time.sleep(86400)
|
||||
raise
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
. /lib/dracut-lib.sh
|
||||
confluent_whost=$confluent_mgr
|
||||
if [[ "$confluent_whost" == *:* ]]; then
|
||||
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
|
||||
@@ -30,6 +30,7 @@ if [ ! -f /sysroot/tmp/installdisk ]; then
|
||||
done
|
||||
fi
|
||||
lvm vgchange -a n
|
||||
/sysroot/usr/sbin/wipefs -a /dev/$(cat /sysroot/tmp/installdisk)
|
||||
udevadm control -e
|
||||
if [ -f /sysroot/etc/lvm/devices/system.devices ]; then
|
||||
rm /sysroot/etc/lvm/devices/system.devices
|
||||
|
||||
@@ -16,6 +16,7 @@ if [ -z "$confluent_mgr" ]; then
|
||||
fi
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
timedatectl set-timezone $(grep ^timezone: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
hostnamectl set-hostname $nodename
|
||||
export nodename confluent_mgr confluent_profile
|
||||
. /etc/confluent/functions
|
||||
mkdir -p /var/log/confluent
|
||||
|
||||
@@ -5,9 +5,16 @@
|
||||
|
||||
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
|
||||
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
export nodename confluent_mgr confluent_profile
|
||||
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
if [ -z "$confluent_mgr" ] || [ "$confluent_mgr" == "null" ] || ! ping -c 1 $confluent_mgr >& /dev/null; then
|
||||
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
fi
|
||||
confluent_websrv=$confluent_mgr
|
||||
if [[ "$confluent_mgr" == *:* ]]; then
|
||||
confluent_websrv="[$confluent_mgr]"
|
||||
fi
|
||||
export nodename confluent_mgr confluent_profile confluent_websrv
|
||||
. /etc/confluent/functions
|
||||
mkdir -p /var/log/confluent
|
||||
chmod 700 /var/log/confluent
|
||||
@@ -16,9 +23,9 @@ exec 2>> /var/log/confluent/confluent-post.log
|
||||
chmod 600 /var/log/confluent/confluent-post.log
|
||||
tail -f /var/log/confluent/confluent-post.log > /dev/console &
|
||||
logshowpid=$!
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
|
||||
curl -f https://$confluent_websrv/confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
|
||||
mkdir -p /opt/confluent/bin
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
|
||||
curl -f https://$confluent_websrv/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
|
||||
chmod +x /opt/confluent/bin/firstboot.sh
|
||||
systemctl enable firstboot
|
||||
selinuxpolicy=$(grep ^SELINUXTYPE /etc/selinux/config |awk -F= '{print $2}')
|
||||
@@ -33,7 +40,7 @@ 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: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_websrv/confluent-api/self/updatestatus
|
||||
|
||||
kill $logshowpid
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -116,4 +116,5 @@ profile=$(grep ^profile: /etc/confluent/confluent.deploycfg.new | sed -e 's/^pro
|
||||
mv /etc/confluent/confluent.deploycfg.new /etc/confluent/confluent.deploycfg
|
||||
export node mgr profile
|
||||
. /tmp/modinstall
|
||||
localcli network firewall load
|
||||
exec /bin/install
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ fi
|
||||
ready=0
|
||||
while [ $ready = "0" ]; do
|
||||
get_remote_apikey
|
||||
if [[ $confluent_mgr == *:* ]]; then
|
||||
if [[ $confluent_mgr == *:* ]] && [[ $confluent_mgr != "["* ]]; then
|
||||
confluent_mgr="[$confluent_mgr]"
|
||||
fi
|
||||
tmperr=$(mktemp)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -10,6 +10,12 @@ dynamic behavior and replace with static configuration.
|
||||
<hwclock>UTC</hwclock>
|
||||
<timezone>%%TIMEZONE%%</timezone>
|
||||
</timezone>
|
||||
<firstboot>
|
||||
<firstboot_enabled config:type="boolean">false</firstboot_enabled>
|
||||
</firstboot>
|
||||
<kdump>
|
||||
<add_crash_kernel config:type="boolean">false</add_crash_kernel>
|
||||
</kdump>
|
||||
<general>
|
||||
<self_update config:type="boolean">false</self_update>
|
||||
<mode>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/bin/sh
|
||||
# WARNING
|
||||
# be careful when editing files here as this script is called
|
||||
# in parallel to other copy operations, so changes to files can be lost
|
||||
discnum=$(basename $1)
|
||||
if [ "$discnum" != 1 ]; then exit 0; fi
|
||||
if [ -e $2/boot/kernel ]; then exit 0; fi
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/bash
|
||||
# remove online repos
|
||||
grep -lE "baseurl=https?://download.opensuse.org" /etc/zypp/repos.d/*repo | xargs rm --
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -10,6 +10,12 @@ dynamic behavior and replace with static configuration.
|
||||
<hwclock>UTC</hwclock>
|
||||
<timezone>%%TIMEZONE%%</timezone>
|
||||
</timezone>
|
||||
<firstboot>
|
||||
<firstboot_enabled config:type="boolean">false</firstboot_enabled>
|
||||
</firstboot>
|
||||
<kdump>
|
||||
<add_crash_kernel config:type="boolean">false</add_crash_kernel>
|
||||
</kdump>
|
||||
<general>
|
||||
<self_update config:type="boolean">false</self_update>
|
||||
<mode>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/bin/sh
|
||||
# WARNING
|
||||
# be careful when editing files here as this script is called
|
||||
# in parallel to other copy operations, so changes to files can be lost
|
||||
discnum=$(basename $1)
|
||||
if [ "$discnum" != 1 ]; then exit 0; fi
|
||||
if [ -e $2/boot/kernel ]; then exit 0; fi
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/bash
|
||||
# remove online repos
|
||||
grep -lE "baseurl=https?://download.opensuse.org" /etc/zypp/repos.d/*repo | xargs rm --
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -10,6 +10,7 @@ import stat
|
||||
import struct
|
||||
import sys
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
bootuuid = None
|
||||
|
||||
@@ -424,5 +425,10 @@ def install_to_disk(imgpath):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
try:
|
||||
install_to_disk(os.environ['mountsrc'])
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
time.sleep(86400)
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -4,4 +4,5 @@ confluent_mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
|
||||
confluent_profile=$(grep ^profile: $deploycfg|awk '{print $2}')
|
||||
export deploycfg confluent_mgr confluent_profile
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/post.sh
|
||||
. /tmp/post.sh
|
||||
bash /tmp/post.sh
|
||||
true
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
echo "Confluent first boot is running"
|
||||
HOME=$(getent passwd $(whoami)|cut -d: -f 6)
|
||||
export HOME
|
||||
seems a potentially relevant thing to put i... by Jarrod Johnson
|
||||
(
|
||||
exec >> /target/var/log/confluent/confluent-firstboot.log
|
||||
exec 2>> /target/var/log/confluent/confluent-firstboot.log
|
||||
chmod 600 /target/var/log/confluent/confluent-firstboot.log
|
||||
cp -a /etc/confluent/ssh/* /etc/ssh/
|
||||
systemctl restart sshd
|
||||
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
@@ -18,7 +21,10 @@ done
|
||||
hostnamectl set-hostname $(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
|
||||
touch /etc/cloud/cloud-init.disabled
|
||||
source /etc/confluent/functions
|
||||
|
||||
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
|
||||
export confluent_mgr confluent_profile
|
||||
run_remote_parts firstboot.d
|
||||
run_remote_config firstboot.d
|
||||
curl --capath /etc/confluent/tls -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
) &
|
||||
tail --pid $! -n 0 -F /target/var/log/confluent/confluent-post.log > /dev/console
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -8,7 +8,6 @@ chmod go-rwx /etc/confluent/*
|
||||
for i in /custom-installation/ssh/*.ca; do
|
||||
echo '@cert-authority *' $(cat $i) >> /target/etc/ssh/ssh_known_hosts
|
||||
done
|
||||
|
||||
cp -a /etc/ssh/ssh_host* /target/etc/confluent/ssh/
|
||||
cp -a /etc/ssh/sshd_config.d/confluent.conf /target/etc/confluent/ssh/sshd_config.d/
|
||||
sshconf=/target/etc/ssh/ssh_config
|
||||
@@ -19,10 +18,15 @@ echo 'Host *' >> $sshconf
|
||||
echo ' HostbasedAuthentication yes' >> $sshconf
|
||||
echo ' EnableSSHKeysign yes' >> $sshconf
|
||||
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
|
||||
|
||||
cp /etc/confluent/functions /target/etc/confluent/functions
|
||||
source /etc/confluent/functions
|
||||
mkdir -p /target/var/log/confluent
|
||||
cp /var/log/confluent/* /target/var/log/confluent/
|
||||
(
|
||||
exec >> /target/var/log/confluent/confluent-post.log
|
||||
exec 2>> /target/var/log/confluent/confluent-post.log
|
||||
chmod 600 /target/var/log/confluent/confluent-post.log
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /target/etc/confluent/firstboot.sh
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /target/etc/confluent/functions
|
||||
source /target/etc/confluent/functions
|
||||
chmod +x /target/etc/confluent/firstboot.sh
|
||||
cp /tmp/allnodes /target/root/.shosts
|
||||
cp /tmp/allnodes /target/etc/ssh/shosts.equiv
|
||||
@@ -56,6 +60,7 @@ cp /custom-installation/confluent/bin/apiclient /target/opt/confluent/bin
|
||||
mount -o bind /dev /target/dev
|
||||
mount -o bind /proc /target/proc
|
||||
mount -o bind /sys /target/sys
|
||||
mount -o bind /sys/firmware/efi/efivars /target/sys/firmware/efi/efivars
|
||||
if [ 1 = $updategrub ]; then
|
||||
chroot /target update-grub
|
||||
fi
|
||||
@@ -83,6 +88,8 @@ chroot /target bash -c "source /etc/confluent/functions; run_remote_parts post.d
|
||||
source /target/etc/confluent/functions
|
||||
|
||||
run_remote_config post
|
||||
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged'
|
||||
|
||||
umount /target/sys /target/dev /target/proc
|
||||
|
||||
) &
|
||||
tail --pid $! -n 0 -F /target/var/log/confluent/confluent-post.log > /dev/console
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
#!/bin/bash
|
||||
deploycfg=/custom-installation/confluent/confluent.deploycfg
|
||||
mkdir -p /var/log/confluent
|
||||
mkdir -p /opt/confluent/bin
|
||||
mkdir -p /etc/confluent
|
||||
cp /custom-installation/confluent/confluent.info /custom-installation/confluent/confluent.apikey /etc/confluent/
|
||||
cat /custom-installation/tls/*.pem >> /etc/confluent/ca.pem
|
||||
cp /custom-installation/confluent/bin/apiclient /opt/confluent/bin
|
||||
cp $deploycfg /etc/confluent/
|
||||
(
|
||||
exec >> /var/log/confluent/confluent-pre.log
|
||||
exec 2>> /var/log/confluent/confluent-pre.log
|
||||
chmod 600 /var/log/confluent/confluent-pre.log
|
||||
|
||||
cryptboot=$(grep encryptboot: $deploycfg|sed -e 's/^encryptboot: //')
|
||||
if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "null" ]; then
|
||||
@@ -23,7 +34,17 @@ echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/confluent.conf
|
||||
systemctl restart sshd
|
||||
mkdir -p /etc/confluent
|
||||
export nodename confluent_profile confluent_mgr
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
|
||||
. /etc/confluent/functions
|
||||
run_remote_parts pre.d
|
||||
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /custom-installation/getinstalldisk
|
||||
python3 /custom-installation/getinstalldisk
|
||||
if [ ! -e /tmp/installdisk ]; then
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/getinstalldisk > /custom-installation/getinstalldisk
|
||||
python3 /custom-installation/getinstalldisk
|
||||
fi
|
||||
sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml
|
||||
) &
|
||||
tail --pid $! -n 0 -F /var/log/confluent/confluent-pre.log > /dev/console
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
@@ -4,4 +4,5 @@ confluent_mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
|
||||
confluent_profile=$(grep ^profile: $deploycfg|awk '{print $2}')
|
||||
export deploycfg confluent_mgr confluent_profile
|
||||
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /tmp/post.sh
|
||||
. /tmp/post.sh
|
||||
bash /tmp/post.sh
|
||||
true
|
||||
|
||||
@@ -3,5 +3,12 @@ sed -i 's/label: ubuntu/label: Ubuntu/' $2/profile.yaml && \
|
||||
ln -s $1/casper/vmlinuz $2/boot/kernel && \
|
||||
ln -s $1/casper/initrd $2/boot/initramfs/distribution && \
|
||||
mkdir -p $2/boot/efi/boot && \
|
||||
ln -s $1/EFI/boot/* $2/boot/efi/boot
|
||||
if [ -d $1/EFI/boot/ ]; then
|
||||
ln -s $1/EFI/boot/* $2/boot/efi/boot
|
||||
elif [ -d $1/efi/boot/ ]; then
|
||||
ln -s $1/efi/boot/* $2/boot/efi/boot
|
||||
else
|
||||
echo "Unrecogrized boot contents in media" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import os
|
||||
|
||||
class DiskInfo(object):
|
||||
def __init__(self, devname):
|
||||
if devname.startswith('nvme') and 'c' in devname:
|
||||
raise Exception("Skipping multipath devname")
|
||||
self.name = devname
|
||||
self.wwn = None
|
||||
self.path = None
|
||||
|
||||
@@ -60,6 +60,7 @@ cp /custom-installation/confluent/bin/apiclient /target/opt/confluent/bin
|
||||
mount -o bind /dev /target/dev
|
||||
mount -o bind /proc /target/proc
|
||||
mount -o bind /sys /target/sys
|
||||
mount -o bind /sys/firmware/efi/efivars /target/sys/firmware/efi/efivars
|
||||
if [ 1 = $updategrub ]; then
|
||||
chroot /target update-grub
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import time
|
||||
import subprocess
|
||||
import importlib
|
||||
import tempfile
|
||||
@@ -7,6 +9,7 @@ import os
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
@@ -227,9 +230,16 @@ def synchronize():
|
||||
myips.append(addr)
|
||||
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir, 'myips': myips})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status >= 300:
|
||||
sys.stderr.write("Error starting syncfiles - {}:\n".format(status))
|
||||
sys.stderr.write(rsp.decode('utf8'))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
return status
|
||||
if status == 202:
|
||||
lastrsp = ''
|
||||
while status != 204:
|
||||
time.sleep(1+(2*random.random()))
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
@@ -277,10 +287,21 @@ def synchronize():
|
||||
os.chmod(fname, int(opts[fname][opt], 8))
|
||||
if uid != -1 or gid != -1:
|
||||
os.chown(fname, uid, gid)
|
||||
return status
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
shutil.rmtree(appendoncedir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
status = 202
|
||||
while status not in (204, 200):
|
||||
try:
|
||||
status = synchronize()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
status = 300
|
||||
if status not in (204, 200):
|
||||
time.sleep((random.random()*3)+2)
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
ubuntu22.04
|
||||
+1
@@ -0,0 +1 @@
|
||||
ubuntu20.04-diskless
|
||||
@@ -31,6 +31,8 @@ int add_uuid(char* destination, int maxsize) {
|
||||
strncpy(destination, "/uuid=", maxsize);
|
||||
uuidsize = read(uuidf, destination + 6, maxsize - 6);
|
||||
close(uuidf);
|
||||
if (uuidsize < 0) { return 0; }
|
||||
if (uuidsize > 524288) { return 0; }
|
||||
if (destination[uuidsize + 5] == '\n') {
|
||||
destination[uuidsize + 5 ] = 0;
|
||||
}
|
||||
@@ -45,6 +47,8 @@ int add_confluent_uuid(char* destination, int maxsize) {
|
||||
strncpy(destination, "/confluentuuid=", maxsize);
|
||||
uuidsize = read(uuidf, destination + 15, maxsize - 15);
|
||||
close(uuidf);
|
||||
if (uuidsize < 0) { return 0; }
|
||||
if (uuidsize > 524288) { return 0; }
|
||||
if (destination[uuidsize + 14] == '\n') {
|
||||
destination[uuidsize + 14] = 0;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,102 @@
|
||||
Name: confluent-client
|
||||
Project: https://hpc.lenovo.com/users/
|
||||
Source: https://github.com/lenovo/confluent
|
||||
Upstream-Name: confluent-client
|
||||
|
||||
All file of the Confluent-client software is distributed under the terms of an Apache-2.0 license as indicated below:
|
||||
|
||||
Files: *
|
||||
Copyright: 2014-2019 Lenovo
|
||||
Copyright: 2014-2023 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: client.py:
|
||||
File: client.py
|
||||
sockapi.py
|
||||
redfish.py
|
||||
attributes.py
|
||||
httpapi.py
|
||||
configmanager.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2019 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: sortutil.py
|
||||
log.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2016 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: util.py
|
||||
noderange.py
|
||||
main.py
|
||||
exceptions.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2017 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: ipmi.py
|
||||
plugin.py
|
||||
core.py
|
||||
consoleserver.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2018 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: tlv.py
|
||||
shellmodule.py
|
||||
conf.py
|
||||
auth.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
License: Apache-2.0
|
||||
|
||||
|
||||
License: Apache-2.0
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -22,6 +22,8 @@ import shutil
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet
|
||||
import greenlet
|
||||
import pwd
|
||||
import signal
|
||||
|
||||
def fprint(txt):
|
||||
sys.stdout.write(txt)
|
||||
@@ -109,6 +111,8 @@ def nics_missing_ipv6():
|
||||
iname, state = comps[:2]
|
||||
if iname == b'lo':
|
||||
continue
|
||||
if iname == b'virbr0':
|
||||
continue
|
||||
addrs = comps[2:]
|
||||
hasv6 = False
|
||||
hasv4 = False
|
||||
@@ -157,6 +161,7 @@ def lookup_node(node):
|
||||
if __name__ == '__main__':
|
||||
ap = argparse.ArgumentParser(description='Run configuration checks for a system running confluent service')
|
||||
ap.add_argument('-n', '--node', help='A node name to run node specific checks against')
|
||||
ap.add_argument('-a', '--automation', help='Do checks against a deployed node for automation and syncfiles function', action='store_true')
|
||||
args, extra = ap.parse_known_args(sys.argv)
|
||||
if len(extra) > 1:
|
||||
ap.print_help()
|
||||
@@ -217,6 +222,7 @@ if __name__ == '__main__':
|
||||
print('OK')
|
||||
except subprocess.CalledProcessError:
|
||||
emprint('Failed to load confluent automation key, syncfiles and profile ansible plays will not work (Example resolution: osdeploy initialize -a)')
|
||||
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
|
||||
fprint('Checking for blocked insecure boot: ')
|
||||
if insecure_boot_attempts():
|
||||
emprint('Some nodes are attempting network boot using PXE or HTTP boot, but the node is not configured to allow this (Example resolution: nodegroupattrib everything deployment.useinsecureprotocols=firmware)')
|
||||
@@ -274,13 +280,17 @@ if __name__ == '__main__':
|
||||
cfg = configmanager.ConfigManager(None)
|
||||
bootablev4nics = []
|
||||
bootablev6nics = []
|
||||
targsships = []
|
||||
for nic in glob.glob("/sys/class/net/*/ifindex"):
|
||||
idx = int(open(nic, "r").read())
|
||||
nicname = nic.split('/')[-2]
|
||||
ncfg = netutil.get_nic_config(cfg, args.node, ifidx=idx)
|
||||
if ncfg['ipv4_address']:
|
||||
targsships.append(ncfg['ipv4_address'])
|
||||
if ncfg['ipv4_address'] or ncfg['ipv4_method'] == 'dhcp':
|
||||
bootablev4nics.append(nicname)
|
||||
if ncfg['ipv6_address']:
|
||||
targsships.append(ncfg['ipv6_address'])
|
||||
bootablev6nics.append(nicname)
|
||||
if bootablev4nics:
|
||||
print("{} appears to have network configuration suitable for IPv4 deployment via: {}".format(args.node, ",".join(bootablev4nics)))
|
||||
@@ -311,6 +321,34 @@ if __name__ == '__main__':
|
||||
emprint('Name resolution failed for node, it is normally a good idea for the node name to resolve to an IP')
|
||||
if result:
|
||||
print("OK")
|
||||
if args.automation:
|
||||
print(f'Checking confluent automation access to {args.node}...')
|
||||
child = os.fork()
|
||||
if child > 0:
|
||||
pid, extcode = os.waitpid(child, 0)
|
||||
else:
|
||||
sshutil.ready_keys = {}
|
||||
sshutil.agent_pid = None
|
||||
cuser = pwd.getpwnam('confluent')
|
||||
os.setgid(cuser.pw_gid)
|
||||
os.setuid(cuser.pw_uid)
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
for targ in targsships:
|
||||
srun = subprocess.run(
|
||||
['ssh', '-Tn', '-o', 'BatchMode=yes', '-l', 'root',
|
||||
'-o', 'StrictHostKeyChecking=yes', targ, 'true'],
|
||||
stdin=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
if srun.returncode == 0:
|
||||
print(f'Confluent automation access to {targ} seems OK')
|
||||
else:
|
||||
if b'Host key verification failed' in srun.stderr:
|
||||
emprint(f'Confluent ssh unable to verify host key for {targ}, check /etc/ssh/ssh_known_hosts. (Example resolution: osdeploy initialize -k)')
|
||||
elif b'ermission denied' in srun.stderr:
|
||||
emprint(f'Confluent user unable to ssh in to {targ}, check /root/.ssh/authorized_keys on the target system versus /etc/confluent/ssh/automation.pub (Example resolution: osdeploy initialize -a)')
|
||||
else:
|
||||
emprint('Unknown error attempting confluent automation ssh:')
|
||||
sys.stderr.buffer.write(srun.stderr)
|
||||
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
|
||||
else:
|
||||
print("Skipping node checks, no node specified (Example: confluent_selfcheck -n n1)")
|
||||
# possible checks:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
|
||||
__author__ = 'jjohnson2,bfinley'
|
||||
|
||||
@@ -49,8 +49,11 @@ def main(args):
|
||||
wiz.add_argument('-p', help='Copy in TFTP contents required for PXE support', action='store_true')
|
||||
wiz.add_argument('-i', help='Interactively prompt for behaviors', action='store_true')
|
||||
wiz.add_argument('-l', help='Set up local management node to allow login from managed nodes', action='store_true')
|
||||
osip = sp.add_parser('importcheck', help='Check import of an OS image from an ISO image')
|
||||
osip.add_argument('imagefile', help='File to use for source of importing')
|
||||
osip = sp.add_parser('import', help='Import an OS image from an ISO image')
|
||||
osip.add_argument('imagefile', help='File to use for source of importing')
|
||||
osip.add_argument('-n', help='Specific a custom distribution name')
|
||||
upb = sp.add_parser(
|
||||
'updateboot',
|
||||
help='Push profile.yaml of the named profile data into boot assets as appropriate')
|
||||
@@ -63,7 +66,9 @@ def main(args):
|
||||
if cmdset.command == 'list':
|
||||
return oslist()
|
||||
if cmdset.command == 'import':
|
||||
return osimport(cmdset.imagefile)
|
||||
return osimport(cmdset.imagefile, custname=cmdset.n)
|
||||
if cmdset.command == 'importcheck':
|
||||
return osimport(cmdset.imagefile, checkonly=True)
|
||||
if cmdset.command == 'initialize':
|
||||
return initialize(cmdset)
|
||||
if cmdset.command == 'updateboot':
|
||||
@@ -320,14 +325,14 @@ def initialize(cmdset):
|
||||
try:
|
||||
sshutil.initialize_ca()
|
||||
except sshutil.AlreadyExists:
|
||||
emprint('Skipping generation of SSH CA, already present and would likely be more problematic to regenerate than to reuse (if absolutely sure you want to discard old CA, then delete /etc/confluent/ssh/ca*)')
|
||||
emprint('Skipping generation of SSH CA, already present and would likely be more problematic to regenerate than to reuse (if absolutely sure you want to discard old CA, then delete /etc/confluent/ssh/ca* and restart confluent)')
|
||||
if cmdset.a:
|
||||
didsomething = True
|
||||
init_confluent_myname()
|
||||
try:
|
||||
sshutil.initialize_root_key(True, True)
|
||||
except sshutil.AlreadyExists:
|
||||
emprint('Skipping generation of new automation key, already present and regeneration usually causes more problems. (If absolutely certain, delete /etc/confluent/ssh/automation*)')
|
||||
emprint('Skipping generation of new automation key, already present and regeneration usually causes more problems. (If absolutely certain, delete /etc/confluent/ssh/automation* and restart confluent)')
|
||||
if cmdset.p:
|
||||
install_tftp_content()
|
||||
if cmdset.l:
|
||||
@@ -496,7 +501,7 @@ def oslist():
|
||||
print("")
|
||||
|
||||
|
||||
def osimport(imagefile):
|
||||
def osimport(imagefile, checkonly=False, custname=None):
|
||||
c = client.Command()
|
||||
imagefile = os.path.abspath(imagefile)
|
||||
if c.unixdomain:
|
||||
@@ -507,11 +512,33 @@ def osimport(imagefile):
|
||||
pass
|
||||
importing = False
|
||||
shortname = None
|
||||
for rsp in c.create('/deployment/importing/', {'filename': imagefile}):
|
||||
apipath = '/deployment/importing/'
|
||||
if checkonly:
|
||||
apipath = '/deployment/fingerprint/'
|
||||
apiargs = {'filename': imagefile}
|
||||
if custname:
|
||||
apiargs['custname'] = custname
|
||||
for rsp in c.create(apipath, apiargs):
|
||||
if 'target' in rsp:
|
||||
importing = True
|
||||
shortname = rsp['name']
|
||||
print('Importing from {0} to {1}'.format(imagefile, rsp['target']))
|
||||
elif 'targetpath' in rsp:
|
||||
tpath = rsp.get('targetpath', None)
|
||||
tname = rsp.get('name', None)
|
||||
oscat = rsp.get('oscategory', None)
|
||||
if tpath:
|
||||
print('Detected target directory: ' + tpath)
|
||||
if tname:
|
||||
print('Detected distribution name: ' + tname)
|
||||
if oscat:
|
||||
print('Detected OS category: ' + oscat)
|
||||
for err in rsp.get('errors', []):
|
||||
sys.stderr.write('Error: ' + err + '\n')
|
||||
|
||||
elif 'error' in rsp:
|
||||
sys.stderr.write(rsp['error'] + '\n')
|
||||
sys.exit(rsp.get('errorcode', 1))
|
||||
else:
|
||||
print(repr(rsp))
|
||||
try:
|
||||
|
||||
@@ -36,7 +36,7 @@ if [ "$OPKGNAME" = "confluent-server" ]; then
|
||||
if grep wheezy /etc/os-release; then
|
||||
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl, python-msgpack/' debian/control
|
||||
else
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-eventlet, python3-pyparsing, python3-pyghmi, python3-paramiko, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd/' debian/control
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-eventlet, python3-pyparsing, python3-pyghmi, python3-paramiko, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd, python3-netifaces, python3-yaml, python3-dateutil, python3-pyasyncore/' debian/control
|
||||
fi
|
||||
if grep wheezy /etc/os-release; then
|
||||
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
|
||||
|
||||
@@ -206,7 +206,7 @@ def create_simple_ca(keyout, certout):
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
|
||||
def create_certificate(keyout=None, certout=None):
|
||||
def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
if not keyout:
|
||||
keyout, certout = get_certificate_paths()
|
||||
if not keyout:
|
||||
@@ -214,9 +214,10 @@ def create_certificate(keyout=None, certout=None):
|
||||
assure_tls_ca()
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = shortname # socket.getfqdn()
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
if not csrout:
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
san = ['IP:{0}'.format(x) for x in get_ip_addresses()]
|
||||
# It is incorrect to put IP addresses as DNS type. However
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
@@ -229,21 +230,34 @@ def create_certificate(keyout=None, certout=None):
|
||||
os.close(tmphdl)
|
||||
tmphdl, extconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
tmphdl, csrout = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
needcsr = False
|
||||
if csrout is None:
|
||||
needcsr = True
|
||||
tmphdl, csrout = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
shutil.copy2(sslcfg, tmpconfig)
|
||||
serialnum = '0x' + ''.join(['{:02x}'.format(x) for x in bytearray(os.urandom(20))])
|
||||
try:
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\nsubjectAltName={0}'.format(san))
|
||||
subprocess.check_call([
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj',
|
||||
'/CN={0}'.format(longname),
|
||||
'-extensions', 'SAN', '-config', tmpconfig
|
||||
])
|
||||
if needcsr:
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\nsubjectAltName={0}'.format(san))
|
||||
subprocess.check_call([
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj',
|
||||
'/CN={0}'.format(longname),
|
||||
'-extensions', 'SAN', '-config', tmpconfig
|
||||
])
|
||||
else:
|
||||
# when used manually, allow the csr SAN to stand
|
||||
# may add explicit subj/SAN argument, in which case we would skip copy
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\ncopy_extensions=copy\n')
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\n')
|
||||
if os.path.exists('/etc/confluent/tls/cakey.pem'):
|
||||
# simple style CA in effect, make a random serial number and
|
||||
# hope for the best, and accept inability to backdate the cert
|
||||
serialnum = '0x' + ''.join(['{:02x}'.format(x) for x in bytearray(os.urandom(20))])
|
||||
subprocess.check_call([
|
||||
'openssl', 'x509', '-req', '-in', csrout,
|
||||
'-CA', '/etc/confluent/tls/cacert.pem',
|
||||
@@ -252,20 +266,40 @@ def create_certificate(keyout=None, certout=None):
|
||||
'-extfile', extconfig
|
||||
])
|
||||
else:
|
||||
# we moved to a 'proper' CA, mainly for access to backdating
|
||||
# start of certs for finicky system clocks
|
||||
# this also provides a harder guarantee of serial uniqueness, but
|
||||
# not of practical consequence (160 bit random value is as good as
|
||||
# guaranteed unique)
|
||||
# downside is certificate generation is serialized
|
||||
cacfgfile = '/etc/confluent/tls/ca/openssl.cfg'
|
||||
if needcsr:
|
||||
tmphdl, tmpcafile = tempfile.mkstemp()
|
||||
shutil.copy2(cacfgfile, tmpcafile)
|
||||
os.close(tmphdl)
|
||||
cacfgfile = tmpcafile
|
||||
# with realcalock: # if we put it in server, we must lock it
|
||||
subprocess.check_call([
|
||||
'openssl', 'ca', '-config', '/etc/confluent/tls/ca/openssl.cfg',
|
||||
'openssl', 'ca', '-config', cacfgfile,
|
||||
'-in', csrout, '-out', certout, '-batch', '-notext',
|
||||
'-startdate', '19700101010101Z', '-enddate', '21000101010101Z',
|
||||
'-extfile', extconfig
|
||||
])
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
os.remove(csrout)
|
||||
os.remove(extconfig)
|
||||
if needcsr:
|
||||
os.remove(csrout)
|
||||
print(extconfig) # os.remove(extconfig)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
outdir = os.getcwd()
|
||||
keyout = os.path.join(outdir, 'key.pem')
|
||||
certout = os.path.join(outdir, 'cert.pem')
|
||||
create_certificate(keyout, certout)
|
||||
certout = os.path.join(outdir, sys.argv[2] + 'cert.pem')
|
||||
csrout = None
|
||||
try:
|
||||
csrout = sys.argv[1]
|
||||
except IndexError:
|
||||
csrout = None
|
||||
create_certificate(keyout, certout, csrout)
|
||||
|
||||
@@ -252,10 +252,12 @@ def _rpc_master_rename_nodegroups(tenant, renamemap):
|
||||
|
||||
|
||||
def _rpc_master_clear_node_attributes(tenant, nodes, attributes):
|
||||
ConfigManager(tenant).clear_node_attributes(nodes, attributes)
|
||||
warnings = []
|
||||
ConfigManager(tenant).clear_node_attributes(nodes, attributes, warnings)
|
||||
return warnings
|
||||
|
||||
|
||||
def _rpc_clear_node_attributes(tenant, nodes, attributes):
|
||||
def _rpc_clear_node_attributes(tenant, nodes, attributes): # master has to do the warnings
|
||||
ConfigManager(tenant)._true_clear_node_attributes(nodes, attributes)
|
||||
|
||||
|
||||
@@ -348,9 +350,9 @@ def exec_on_leader(function, *args):
|
||||
rpclen = len(rpcpayload)
|
||||
cfgleader.sendall(struct.pack('!Q', rpclen))
|
||||
cfgleader.sendall(rpcpayload)
|
||||
_pendingchangesets[xid].wait()
|
||||
retv = _pendingchangesets[xid].wait()
|
||||
del _pendingchangesets[xid]
|
||||
return
|
||||
return retv
|
||||
|
||||
|
||||
def exec_on_followers(fnname, *args):
|
||||
@@ -714,8 +716,9 @@ def relay_slaved_requests(name, listener):
|
||||
exc = None
|
||||
if not (rpc['function'].startswith('_rpc_') or rpc['function'].endswith('_collective_member')):
|
||||
raise Exception('Unsupported function {0} called'.format(rpc['function']))
|
||||
retv = None
|
||||
try:
|
||||
globals()[rpc['function']](*rpc['args'])
|
||||
retv = globals()[rpc['function']](*rpc['args'])
|
||||
except ValueError as ve:
|
||||
exc = ['ValueError', str(ve)]
|
||||
except Exception as e:
|
||||
@@ -723,7 +726,7 @@ def relay_slaved_requests(name, listener):
|
||||
exc = ['Exception', str(e)]
|
||||
if 'xid' in rpc:
|
||||
res = _push_rpc(listener, msgpack.packb({'xid': rpc['xid'],
|
||||
'exc': exc}, use_bin_type=False))
|
||||
'exc': exc, 'ret': retv}, use_bin_type=False))
|
||||
if not res:
|
||||
break
|
||||
try:
|
||||
@@ -929,7 +932,7 @@ def follow_channel(channel):
|
||||
exc = Exception(excstr)
|
||||
_pendingchangesets[rpc['xid']].send_exception(exc)
|
||||
else:
|
||||
_pendingchangesets[rpc['xid']].send()
|
||||
_pendingchangesets[rpc['xid']].send(rpc.get('ret', None))
|
||||
if 'quorum' in rpc:
|
||||
_hasquorum = rpc['quorum']
|
||||
res = _push_rpc(channel, b'') # use null as ACK
|
||||
@@ -1089,6 +1092,11 @@ class _ExpressionFormat(string.Formatter):
|
||||
self._nodename = nodename
|
||||
self._numbers = None
|
||||
|
||||
def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
|
||||
auto_arg_index=False):
|
||||
return super()._vformat(format_string, args, kwargs, used_args,
|
||||
recursion_depth, auto_arg_index)
|
||||
|
||||
def get_field(self, field_name, args, kwargs):
|
||||
return field_name, field_name
|
||||
|
||||
@@ -2197,16 +2205,19 @@ class ConfigManager(object):
|
||||
self._notif_attribwatchers(changeset)
|
||||
self._bg_sync_to_file()
|
||||
|
||||
def clear_node_attributes(self, nodes, attributes):
|
||||
def clear_node_attributes(self, nodes, attributes, warnings=None):
|
||||
if cfgleader:
|
||||
return exec_on_leader('_rpc_master_clear_node_attributes',
|
||||
mywarnings = exec_on_leader('_rpc_master_clear_node_attributes',
|
||||
self.tenant, nodes, attributes)
|
||||
if mywarnings and warnings is not None:
|
||||
warnings.extend(mywarnings)
|
||||
return
|
||||
if cfgstreams:
|
||||
exec_on_followers('_rpc_clear_node_attributes', self.tenant,
|
||||
nodes, attributes)
|
||||
self._true_clear_node_attributes(nodes, attributes)
|
||||
self._true_clear_node_attributes(nodes, attributes, warnings)
|
||||
|
||||
def _true_clear_node_attributes(self, nodes, attributes):
|
||||
def _true_clear_node_attributes(self, nodes, attributes, warnings=None):
|
||||
# accumulate all changes into a changeset and push in one go
|
||||
changeset = {}
|
||||
realattributes = []
|
||||
@@ -2229,8 +2240,17 @@ class ConfigManager(object):
|
||||
# delete it and check for inheritence to backfil data
|
||||
del nodek[attrib]
|
||||
self._do_inheritance(nodek, attrib, node, changeset)
|
||||
if warnings is not None:
|
||||
if attrib in nodek:
|
||||
warnings.append('The attribute "{}" was defined specifically for the node and clearing now has a value inherited from the group "{}"'.format(attrib, nodek[attrib]['inheritedfrom']))
|
||||
_addchange(changeset, node, attrib)
|
||||
_mark_dirtykey('nodes', node, self.tenant)
|
||||
elif attrib in nodek:
|
||||
if warnings is not None:
|
||||
warnings.append('The attribute "{0}" is inherited from group "{1}", leaving the inherited value alone (use "{0}=" with no value to explicitly blank the value if desired)'.format(attrib, nodek[attrib]['inheritedfrom']))
|
||||
else:
|
||||
if warnings is not None:
|
||||
warnings.append('Attribute "{}" is either already cleared, or does not match a defined attribute (if referencing an attribute group, try a wildcard)'.format(attrib))
|
||||
if ('_expressionkeys' in nodek and
|
||||
attrib in nodek['_expressionkeys']):
|
||||
recalcexpressions = True
|
||||
|
||||
@@ -49,7 +49,6 @@ _handled_consoles = {}
|
||||
|
||||
_tracelog = None
|
||||
_bufferdaemon = None
|
||||
_bufferlock = None
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
@@ -62,39 +61,38 @@ def chunk_output(output, n):
|
||||
yield output[i:i + n]
|
||||
|
||||
def get_buffer_output(nodename):
|
||||
out = _bufferdaemon.stdin
|
||||
instream = _bufferdaemon.stdout
|
||||
out = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
out.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
|
||||
out.connect("\x00confluent-vtbuffer")
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
outdata = bytearray()
|
||||
with _bufferlock:
|
||||
out.write(struct.pack('I', len(nodename)))
|
||||
out.write(nodename)
|
||||
out.flush()
|
||||
select.select((instream,), (), (), 30)
|
||||
while not outdata or outdata[-1]:
|
||||
try:
|
||||
chunk = os.read(instream.fileno(), 128)
|
||||
except IOError:
|
||||
chunk = None
|
||||
if chunk:
|
||||
outdata.extend(chunk)
|
||||
else:
|
||||
select.select((instream,), (), (), 0)
|
||||
return bytes(outdata[:-1])
|
||||
out.send(struct.pack('I', len(nodename)))
|
||||
out.send(nodename)
|
||||
select.select((out,), (), (), 30)
|
||||
while not outdata or outdata[-1]:
|
||||
try:
|
||||
chunk = os.read(out.fileno(), 128)
|
||||
except IOError:
|
||||
chunk = None
|
||||
if chunk:
|
||||
outdata.extend(chunk)
|
||||
else:
|
||||
select.select((out,), (), (), 0)
|
||||
return bytes(outdata[:-1])
|
||||
|
||||
|
||||
def send_output(nodename, output):
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
with _bufferlock:
|
||||
_bufferdaemon.stdin.write(struct.pack('I', len(nodename) | (1 << 29)))
|
||||
_bufferdaemon.stdin.write(nodename)
|
||||
_bufferdaemon.stdin.flush()
|
||||
for chunk in chunk_output(output, 8192):
|
||||
_bufferdaemon.stdin.write(struct.pack('I', len(chunk) | (2 << 29)))
|
||||
_bufferdaemon.stdin.write(chunk)
|
||||
_bufferdaemon.stdin.flush()
|
||||
out = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
out.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
|
||||
out.connect("\x00confluent-vtbuffer")
|
||||
out.send(struct.pack('I', len(nodename) | (1 << 29)))
|
||||
out.send(nodename)
|
||||
for chunk in chunk_output(output, 8192):
|
||||
out.send(struct.pack('I', len(chunk) | (2 << 29)))
|
||||
out.send(chunk)
|
||||
|
||||
def _utf8_normalize(data, decoder):
|
||||
# first we give the stateful decoder a crack at the byte stream,
|
||||
@@ -175,6 +173,9 @@ class ConsoleHandler(object):
|
||||
self.connectstate = 'connecting'
|
||||
eventlet.spawn(self._connect)
|
||||
|
||||
def resize(self, width, height):
|
||||
return None
|
||||
|
||||
def _get_retry_time(self):
|
||||
clustsize = len(self.cfgmgr._cfgstore['nodes'])
|
||||
self._retrytime = self._retrytime * 2 + 1
|
||||
@@ -600,15 +601,10 @@ def _start_tenant_sessions(cfm):
|
||||
def initialize():
|
||||
global _tracelog
|
||||
global _bufferdaemon
|
||||
global _bufferlock
|
||||
_bufferlock = semaphore.Semaphore()
|
||||
_tracelog = log.Logger('trace')
|
||||
_bufferdaemon = subprocess.Popen(
|
||||
['/opt/confluent/bin/vtbufferd'], bufsize=0, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
fl = fcntl.fcntl(_bufferdaemon.stdout.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(_bufferdaemon.stdout.fileno(),
|
||||
fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
['/opt/confluent/bin/vtbufferd', 'confluent-vtbuffer'], bufsize=0, stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL)
|
||||
|
||||
def start_console_sessions():
|
||||
configmodule.hook_new_configmanagers(_start_tenant_sessions)
|
||||
@@ -622,6 +618,8 @@ def connect_node(node, configmanager, username=None, direct=True, width=80,
|
||||
myname = collective.get_myname()
|
||||
if myc and myc != collective.get_myname() and direct:
|
||||
minfo = configmodule.get_collective_member(myc)
|
||||
if not minfo:
|
||||
raise Exception('Unable to get collective member for {}'.format(node))
|
||||
return ProxyConsole(node, minfo, myname, configmanager, username,
|
||||
width, height)
|
||||
consk = (node, configmanager.tenant)
|
||||
|
||||
@@ -44,6 +44,7 @@ import confluent.discovery.core as disco
|
||||
import confluent.interface.console as console
|
||||
import confluent.exceptions as exc
|
||||
import confluent.messages as msg
|
||||
import confluent.mountmanager as mountmanager
|
||||
import confluent.networking.macmap as macmap
|
||||
import confluent.noderange as noderange
|
||||
import confluent.osimage as osimage
|
||||
@@ -69,6 +70,7 @@ import os
|
||||
import eventlet.green.socket as socket
|
||||
import struct
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
pluginmap = {}
|
||||
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol', b'geist', u'geist', b'deltapdu', u'deltapdu', b'eatonpdu', u'eatonpdu', b'affluent', u'affluent', b'cnos', u'cnos')
|
||||
@@ -159,7 +161,7 @@ def _merge_dict(original, custom):
|
||||
|
||||
|
||||
rootcollections = ['deployment/', 'discovery/', 'events/', 'networking/',
|
||||
'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' ,
|
||||
'noderange/', 'nodes/', 'nodegroups/', 'storage/', 'usergroups/' ,
|
||||
'users/', 'uuid', 'version']
|
||||
|
||||
|
||||
@@ -169,6 +171,14 @@ class PluginRoute(object):
|
||||
|
||||
|
||||
|
||||
def handle_storage(configmanager, inputdata, pathcomponents, operation):
|
||||
if len(pathcomponents) == 1:
|
||||
yield msg.ChildCollection('remote/')
|
||||
return
|
||||
if pathcomponents[1] == 'remote':
|
||||
for rsp in mountmanager.handle_request(configmanager, inputdata, pathcomponents[2:], operation):
|
||||
yield rsp
|
||||
|
||||
def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
operation):
|
||||
if len(pathcomponents) == 1:
|
||||
@@ -191,8 +201,19 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
for prof in osimage.list_profiles():
|
||||
yield msg.ChildCollection(prof + '/')
|
||||
return
|
||||
if len(pathcomponents) == 3:
|
||||
profname = pathcomponents[-1]
|
||||
if len(pathcomponents) >= 3:
|
||||
profname = pathcomponents[2]
|
||||
if len(pathcomponents) == 4:
|
||||
if operation == 'retrieve':
|
||||
if len(pathcomponents) == 4 and pathcomponents[-1] == 'info':
|
||||
with open('/var/lib/confluent/public/os/{}/profile.yaml'.format(profname)) as profyaml:
|
||||
profinfo = yaml.safe_load(profyaml)
|
||||
profinfo['name'] = profname
|
||||
yield msg.KeyValueData(profinfo)
|
||||
return
|
||||
elif len(pathcomponents) == 3:
|
||||
if operation == 'retrieve':
|
||||
yield msg.ChildCollection('info')
|
||||
if operation == 'update':
|
||||
if 'updateboot' in inputdata:
|
||||
osimage.update_boot(profname)
|
||||
@@ -208,6 +229,17 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
for cust in customized:
|
||||
yield msg.KeyValueData({'customized': cust})
|
||||
return
|
||||
if pathcomponents[1] == 'fingerprint':
|
||||
if operation == 'create':
|
||||
importer = osimage.MediaImporter(inputdata['filename'], configmanager, checkonly=True)
|
||||
medinfo = {
|
||||
'targetpath': importer.targpath,
|
||||
'name': importer.osname,
|
||||
'oscategory': importer.oscategory,
|
||||
'errors': importer.errors,
|
||||
}
|
||||
yield msg.KeyValueData(medinfo)
|
||||
return
|
||||
if pathcomponents[1] == 'importing':
|
||||
if len(pathcomponents) == 2 or not pathcomponents[-1]:
|
||||
if operation == 'retrieve':
|
||||
@@ -215,8 +247,12 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
yield imp
|
||||
return
|
||||
elif operation == 'create':
|
||||
importer = osimage.MediaImporter(inputdata['filename'],
|
||||
configmanager)
|
||||
if inputdata.get('custname', None):
|
||||
importer = osimage.MediaImporter(inputdata['filename'],
|
||||
configmanager, inputdata['custname'])
|
||||
else:
|
||||
importer = osimage.MediaImporter(inputdata['filename'],
|
||||
configmanager)
|
||||
yield msg.KeyValueData({'target': importer.targpath,
|
||||
'name': importer.importkey})
|
||||
return
|
||||
@@ -1245,6 +1281,9 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
elif pathcomponents[0] == 'deployment':
|
||||
return handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
operation)
|
||||
elif pathcomponents[0] == 'storage':
|
||||
return handle_storage(configmanager, inputdata, pathcomponents,
|
||||
operation)
|
||||
elif pathcomponents[0] == 'nodegroups':
|
||||
return handle_nodegroup_request(configmanager, inputdata,
|
||||
pathcomponents,
|
||||
|
||||
@@ -127,14 +127,15 @@ class CredServer(object):
|
||||
if hmacval != hmac.new(hmackey, etok, hashlib.sha256).digest():
|
||||
client.close()
|
||||
return
|
||||
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.sealedapikey': '', 'deployment.apiarmed': ''}}
|
||||
if hmackey and apiarmed != 'continuous':
|
||||
self.cfm.clear_node_attributes([nodename], ['secret.selfapiarmtoken'])
|
||||
if apiarmed == 'continuous':
|
||||
del cfgupdate[nodename]['deployment.apiarmed']
|
||||
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}}}
|
||||
self.cfm.set_node_attributes(cfgupdate)
|
||||
client.recv(2) # drain end of message
|
||||
client.send(b'\x05\x00') # report success
|
||||
if hmackey and apiarmed != 'continuous':
|
||||
self.cfm.clear_node_attributes([nodename], ['secret.selfapiarmtoken'])
|
||||
if apiarmed != 'continuous':
|
||||
tokclear = {nodename: {'deployment.sealedapikey': '', 'deployment.apiarmed': ''}}
|
||||
self.cfm.set_node_attributes(tokclear)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
@@ -247,6 +247,10 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if rsp.status == 200:
|
||||
pwdchanged = True
|
||||
password = newpassword
|
||||
wc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
wc.grab_json_response_with_status('/api/providers/logout')
|
||||
else:
|
||||
if rspdata.get('locktime', 0) > 0:
|
||||
raise LockedUserException(
|
||||
@@ -280,6 +284,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
rsp.read()
|
||||
if rsp.status != 200:
|
||||
return (None, None)
|
||||
wc.grab_json_response_with_status('/api/providers/logout')
|
||||
self._currcreds = (username, newpassword)
|
||||
wc.set_basic_credentials(username, newpassword)
|
||||
pwdchanged = True
|
||||
@@ -434,6 +439,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
'/api/function',
|
||||
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,,,'.format(uid, username)})
|
||||
if status == 200 and rsp.get('return', 0) == 13:
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
wc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
|
||||
status = 503
|
||||
while status != 200:
|
||||
@@ -442,10 +448,13 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
{'UserName': username}, method='PATCH')
|
||||
if status != 200:
|
||||
rsp = json.loads(rsp)
|
||||
if rsp.get('error', {}).get('code', 'Unknown') in ('Base.1.8.GeneralError', 'Base.1.12.GeneralError'):
|
||||
eventlet.sleep(10)
|
||||
if rsp.get('error', {}).get('code', 'Unknown') in ('Base.1.8.GeneralError', 'Base.1.12.GeneralError', 'Base.1.14.GeneralError'):
|
||||
eventlet.sleep(4)
|
||||
else:
|
||||
break
|
||||
self.tmppasswd = None
|
||||
self._currcreds = (username, passwd)
|
||||
return
|
||||
self.tmppasswd = None
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
self._currcreds = (username, passwd)
|
||||
@@ -632,3 +641,4 @@ def remote_nodecfg(nodename, cfm):
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
|
||||
|
||||
@@ -587,7 +587,10 @@ def get_deployment_profile(node, cfg, cfd=None):
|
||||
return None
|
||||
candmgrs = cfd.get(node, {}).get('collective.managercandidates', {}).get('value', None)
|
||||
if candmgrs:
|
||||
candmgrs = noderange.NodeRange(candmgrs, cfg).nodes
|
||||
try:
|
||||
candmgrs = noderange.NodeRange(candmgrs, cfg).nodes
|
||||
except Exception: # fallback to unverified noderange
|
||||
candmgrs = noderange.NodeRange(candmgrs).nodes
|
||||
if collective.get_myname() not in candmgrs:
|
||||
return None
|
||||
return profile
|
||||
@@ -771,6 +774,14 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile):
|
||||
node, profile, len(bootfile) - 127)})
|
||||
return
|
||||
repview[108:108 + len(bootfile)] = bootfile
|
||||
elif info['architecture'] == 'uefi-aarch64' and packet.get(77, None) == b'iPXE':
|
||||
if not profile:
|
||||
profile = get_deployment_profile(node, cfg)
|
||||
if not profile:
|
||||
log.log({'info': 'No pending profile for {0}, skipping proxyDHCP eply'.format(node)})
|
||||
return
|
||||
bootfile = 'http://{0}/confluent-public/os/{1}/boot.ipxe'.format(myipn, profile).encode('utf8')
|
||||
repview[108:108 + len(bootfile)] = bootfile
|
||||
myip = myipn
|
||||
myipn = socket.inet_aton(myipn)
|
||||
orepview[12:16] = myipn
|
||||
@@ -812,6 +823,13 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile):
|
||||
repview[replen - 1:replen + 1] = b'\x03\x04'
|
||||
repview[replen + 1:replen + 5] = gateway
|
||||
replen += 6
|
||||
if 82 in packet:
|
||||
reloptionslen = len(packet[82])
|
||||
reloptionshdr = struct.pack('BB', 82, reloptionslen)
|
||||
repview[replen - 1:replen + 1] = reloptionshdr
|
||||
repview[replen + 1:replen + reloptionslen + 1] = packet[82]
|
||||
replen += 2 + reloptionslen
|
||||
|
||||
repview[replen - 1:replen] = b'\xff' # end of options, should always be last byte
|
||||
repview = memoryview(reply)
|
||||
pktlen = struct.pack('!H', replen + 28) # ip+udp = 28
|
||||
|
||||
@@ -246,11 +246,11 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
try:
|
||||
net4.sendto(data, ('239.255.255.253', 427))
|
||||
except socket.error as se:
|
||||
# On occasion, multicasting may be disabled
|
||||
# tolerate this scenario and move on
|
||||
if se.errno != 101:
|
||||
raise
|
||||
net4.sendto(data, (bcast, 427))
|
||||
pass
|
||||
try:
|
||||
net4.sendto(data, (bcast, 427))
|
||||
except socket.error as se:
|
||||
pass
|
||||
|
||||
|
||||
def _grab_rsps(socks, rsps, interval, xidmap, deferrals):
|
||||
@@ -411,7 +411,7 @@ def query_srvtypes(target):
|
||||
parsed = _parse_slp_header(rs)
|
||||
if parsed:
|
||||
payload = parsed['payload']
|
||||
if payload[:2] != '\x00\x00':
|
||||
if payload[:2] != b'\x00\x00':
|
||||
return
|
||||
stypelen = struct.unpack('!H', bytes(payload[2:4]))[0]
|
||||
stypes = payload[4:4+stypelen].decode('utf-8')
|
||||
|
||||
@@ -53,7 +53,7 @@ def execupdate(handler, filename, updateobj, type, owner, node, datfile):
|
||||
return
|
||||
if type == 'ffdc' and os.path.isdir(filename):
|
||||
filename += '/' + node
|
||||
if 'type' == 'ffdc':
|
||||
if type == 'ffdc':
|
||||
errstr = False
|
||||
if os.path.exists(filename):
|
||||
errstr = '{0} already exists on {1}, cannot overwrite'.format(
|
||||
|
||||
@@ -618,7 +618,6 @@ def resourcehandler(env, start_response):
|
||||
yield '500 - ' + str(e)
|
||||
return
|
||||
|
||||
|
||||
def resourcehandler_backend(env, start_response):
|
||||
"""Function to handle new wsgi requests
|
||||
"""
|
||||
@@ -728,7 +727,13 @@ def resourcehandler_backend(env, start_response):
|
||||
elif (env['PATH_INFO'].endswith('/forward/web') and
|
||||
env['PATH_INFO'].startswith('/nodes/')):
|
||||
prefix, _, _ = env['PATH_INFO'].partition('/forward/web')
|
||||
_, _, nodename = prefix.rpartition('/')
|
||||
#_, _, nodename = prefix.rpartition('/')
|
||||
default = False
|
||||
if 'default' in env['PATH_INFO']:
|
||||
default = True
|
||||
_,_,nodename,_ = prefix.split('/')
|
||||
else:
|
||||
_, _, nodename = prefix.rpartition('/')
|
||||
hm = cfgmgr.get_node_attributes(nodename, 'hardwaremanagement.manager')
|
||||
targip = hm.get(nodename, {}).get(
|
||||
'hardwaremanagement.manager', {}).get('value', None)
|
||||
@@ -737,6 +742,29 @@ def resourcehandler_backend(env, start_response):
|
||||
yield 'No hardwaremanagement.manager defined for node'
|
||||
return
|
||||
targip = targip.split('/', 1)[0]
|
||||
if default:
|
||||
try:
|
||||
ip_info = socket.getaddrinfo(targip, 0, 0, socket.SOCK_STREAM)
|
||||
except socket.gaierror:
|
||||
start_response('404 Not Found', headers)
|
||||
yield 'hardwaremanagement.manager definition could not be resolved'
|
||||
return
|
||||
# this is just to future proof just in case the indexes of the address family change in future
|
||||
for i in range(len(ip_info)):
|
||||
if ip_info[i][0] == socket.AF_INET:
|
||||
url = 'https://{0}/'.format(ip_info[i][-1][0])
|
||||
start_response('302', [('Location', url)])
|
||||
yield 'Our princess is in another castle!'
|
||||
return
|
||||
elif ip_info[i][0] == socket.AF_INET6:
|
||||
url = 'https://[{0}]/'.format(ip_info[i][-1][0])
|
||||
if url.startswith('https://[fe80'):
|
||||
start_response('405 Method Not Allowed', headers)
|
||||
yield 'link local ipv6 address cannot be used in browser'
|
||||
return
|
||||
start_response('302', [('Location', url)])
|
||||
yield 'Our princess is in another castle!'
|
||||
return
|
||||
funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'],
|
||||
authorized['sessionid'])
|
||||
host = env['HTTP_X_FORWARDED_HOST']
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
import eventlet
|
||||
import confluent.messages as msg
|
||||
import confluent.exceptions as exc
|
||||
import struct
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import os
|
||||
mountsbyuser = {}
|
||||
_browserfsd = None
|
||||
|
||||
def assure_browserfs():
|
||||
global _browserfsd
|
||||
if _browserfsd is None:
|
||||
os.makedirs('/var/run/confluent/browserfs/mount', exist_ok=True)
|
||||
_browserfsd = subprocess.Popen(
|
||||
['/opt/confluent/bin/browserfs',
|
||||
'-c', '/var/run/confluent/browserfs/control',
|
||||
'-s', '127.0.0.1:4006',
|
||||
# browserfs supports unix domain websocket, however apache reverse proxy is dicey that way in some versions
|
||||
'-w', '/var/run/confluent/browserfs/mount'])
|
||||
while not os.path.exists('/var/run/confluent/browserfs/control'):
|
||||
eventlet.sleep(0.5)
|
||||
|
||||
|
||||
def handle_request(configmanager, inputdata, pathcomponents, operation):
|
||||
curruser = configmanager.current_user
|
||||
if len(pathcomponents) == 0:
|
||||
mounts = mountsbyuser.get(curruser, [])
|
||||
if operation == 'retrieve':
|
||||
for mount in mounts:
|
||||
yield msg.ChildCollection(mount['index'])
|
||||
elif operation == 'create':
|
||||
if 'name' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Required parameter "name" is missing')
|
||||
usedidx = set([])
|
||||
for mount in mounts:
|
||||
usedidx.add(mount['index'])
|
||||
curridx = 1
|
||||
while curridx in usedidx:
|
||||
curridx += 1
|
||||
currmount = requestmount(curruser, inputdata['name'])
|
||||
currmount['index'] = curridx
|
||||
if curruser not in mountsbyuser:
|
||||
mountsbyuser[curruser] = []
|
||||
mountsbyuser[curruser].append(currmount)
|
||||
yield msg.KeyValueData({
|
||||
'path': currmount['path'],
|
||||
'fullpath': '/var/run/confluent/browserfs/mount/{}'.format(currmount['path']),
|
||||
'authtoken': currmount['authtoken']
|
||||
})
|
||||
|
||||
def requestmount(subdir, filename):
|
||||
assure_browserfs()
|
||||
a = socket.socket(socket.AF_UNIX)
|
||||
a.connect('/var/run/confluent/browserfs/control')
|
||||
subname = subdir.encode()
|
||||
a.send(struct.pack('!II', 1, len(subname)))
|
||||
a.send(subname)
|
||||
fname = filename.encode()
|
||||
a.send(struct.pack('!I', len(fname)))
|
||||
a.send(fname)
|
||||
rsp = a.recv(4)
|
||||
retcode = struct.unpack('!I', rsp)[0]
|
||||
if retcode != 0:
|
||||
raise Exception("Bad return code")
|
||||
rsp = a.recv(4)
|
||||
nlen = struct.unpack('!I', rsp)[0]
|
||||
idstr = a.recv(nlen).decode('utf8')
|
||||
rsp = a.recv(4)
|
||||
nlen = struct.unpack('!I', rsp)[0]
|
||||
authtok = a.recv(nlen).decode('utf8')
|
||||
thismount = {
|
||||
'id': idstr,
|
||||
'path': '{}/{}/{}'.format(idstr, subdir, filename),
|
||||
'authtoken': authtok
|
||||
}
|
||||
return thismount
|
||||
|
||||
@@ -381,9 +381,10 @@ def list_info(parms, requestedparameter):
|
||||
break
|
||||
else:
|
||||
candidate = info[requestedparameter]
|
||||
candidate = candidate.strip()
|
||||
if candidate != '':
|
||||
results.add(_api_sanitize_string(candidate))
|
||||
if candidate:
|
||||
candidate = candidate.strip()
|
||||
if candidate != '':
|
||||
results.add(_api_sanitize_string(candidate))
|
||||
return [msg.ChildCollection(x + suffix) for x in util.natural_sort(results)]
|
||||
|
||||
def _handle_neighbor_query(pathcomponents, configmanager):
|
||||
|
||||
@@ -96,6 +96,7 @@ class Bracketer(object):
|
||||
txtnums = getnumbers_nodename(nodename)
|
||||
nums = [int(x) for x in txtnums]
|
||||
for n in range(self.count):
|
||||
# First pass to see if we have exactly one different number
|
||||
padto = len(txtnums[n])
|
||||
needpad = (padto != len('{}'.format(nums[n])))
|
||||
if self.sequences[n] is None:
|
||||
@@ -105,7 +106,24 @@ class Bracketer(object):
|
||||
elif self.sequences[n][2] == nums[n] and self.numlens[n][1] == padto:
|
||||
continue # new nodename has no new number, keep going
|
||||
else: # if self.sequences[n][2] != nums[n] or :
|
||||
if self.diffn is not None and (n != self.diffn or
|
||||
if self.diffn is not None and (n != self.diffn or
|
||||
(padto < self.numlens[n][1]) or
|
||||
(needpad and padto != self.numlens[n][1])):
|
||||
self.flush_current()
|
||||
self.sequences[n] = [[], nums[n], nums[n]]
|
||||
self.numlens[n] = [padto, padto]
|
||||
self.diffn = n
|
||||
for n in range(self.count):
|
||||
padto = len(txtnums[n])
|
||||
needpad = (padto != len('{}'.format(nums[n])))
|
||||
if self.sequences[n] is None:
|
||||
# We initialize to text pieces, 'currstart', and 'prev' number
|
||||
self.sequences[n] = [[], nums[n], nums[n]]
|
||||
self.numlens[n] = [len(txtnums[n]), len(txtnums[n])]
|
||||
elif self.sequences[n][2] == nums[n] and self.numlens[n][1] == padto:
|
||||
continue # new nodename has no new number, keep going
|
||||
else: # if self.sequences[n][2] != nums[n] or :
|
||||
if self.diffn is not None and (n != self.diffn or
|
||||
(padto < self.numlens[n][1]) or
|
||||
(needpad and padto != self.numlens[n][1])):
|
||||
self.flush_current()
|
||||
@@ -384,12 +402,16 @@ class NodeRange(object):
|
||||
def _expandstring(self, element, filternodes=None):
|
||||
prefix = ''
|
||||
if element[0][0] in ('/', '~'):
|
||||
if self.purenumeric:
|
||||
raise Exception('Regular expression not supported within "[]"')
|
||||
element = ''.join(element)
|
||||
nameexpression = element[1:]
|
||||
if self.cfm is None:
|
||||
raise Exception('Verification configmanager required')
|
||||
return set(self.cfm.filter_nodenames(nameexpression, filternodes))
|
||||
elif '=' in element[0] or '!~' in element[0]:
|
||||
if self.purenumeric:
|
||||
raise Exception('Equality/Inequality operators (=, !=, =~, !~) are invalid within "[]"')
|
||||
element = ''.join(element)
|
||||
if self.cfm is None:
|
||||
raise Exception('Verification configmanager required')
|
||||
@@ -449,3 +471,29 @@ class NodeRange(object):
|
||||
if self.cfm is None:
|
||||
return set([element])
|
||||
raise Exception(element + ' not a recognized node, group, or alias')
|
||||
|
||||
if __name__ == '__main__':
|
||||
cases = [
|
||||
(['r3u4', 'r5u6'], 'r3u4,r5u6'), # should not erroneously gather
|
||||
(['r3u4s1', 'r5u6s3'], 'r3u4s1,r5u6s3'), # should not erroneously gather
|
||||
(['r3u4s1', 'r3u4s2', 'r5u4s3'], 'r3u4s[1:2],r5u4s3'), # should not erroneously gather
|
||||
(['r3u4', 'r3u5', 'r3u6', 'r3u9', 'r4u1'], 'r3u[4:6,9],r4u1'),
|
||||
(['n01', 'n2', 'n03'], 'n01,n2,n03'),
|
||||
(['n7', 'n8', 'n09', 'n10', 'n11', 'n12', 'n13', 'n14', 'n15', 'n16',
|
||||
'n17', 'n18', 'n19', 'n20'], 'n[7:8],n[09:20]')
|
||||
]
|
||||
for case in cases:
|
||||
gc = case[0]
|
||||
bracketer = Bracketer(gc[0])
|
||||
for chnk in gc[1:]:
|
||||
bracketer.extend(chnk)
|
||||
br = bracketer.range
|
||||
resnodes = NodeRange(br).nodes
|
||||
if set(resnodes) != set(gc):
|
||||
print('FAILED: ' + repr(sorted(gc)))
|
||||
print('RESULT: ' + repr(sorted(resnodes)))
|
||||
print('EXPECTED: ' + repr(case[1]))
|
||||
print('ACTUAL: ' + br)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -411,9 +411,7 @@ def check_ubuntu(isoinfo):
|
||||
]
|
||||
return {'name': 'ubuntu-{0}-{1}'.format(ver, arch),
|
||||
'method': EXTRACT|COPY,
|
||||
'extractlist': ['casper/vmlinuz', 'casper/initrd',
|
||||
'efi/boot/bootx64.efi', 'efi/boot/grubx64.efi'
|
||||
],
|
||||
'extractlist': exlist,
|
||||
'copyto': 'install.iso',
|
||||
'category': 'ubuntu{0}'.format(major)}
|
||||
|
||||
@@ -601,7 +599,7 @@ def fingerprint(archive):
|
||||
return imginfo, None, None
|
||||
|
||||
|
||||
def import_image(filename, callback, backend=False, mfd=None):
|
||||
def import_image(filename, callback, backend=False, mfd=None, custtargpath=None, custdistpath=None, custname=''):
|
||||
if mfd:
|
||||
archive = os.fdopen(int(mfd), 'rb')
|
||||
else:
|
||||
@@ -610,11 +608,16 @@ def import_image(filename, callback, backend=False, mfd=None):
|
||||
if not identity:
|
||||
return -1
|
||||
identity, imginfo, funname = identity
|
||||
targpath = identity['name']
|
||||
distpath = '/var/lib/confluent/distributions/' + targpath
|
||||
if identity.get('subname', None):
|
||||
targpath += '/' + identity['subname']
|
||||
targpath = '/var/lib/confluent/distributions/' + targpath
|
||||
distpath = custdistpath
|
||||
if not distpath:
|
||||
targpath = identity['name']
|
||||
distpath = '/var/lib/confluent/distributions/' + targpath
|
||||
if not custtargpath:
|
||||
if identity.get('subname', None):
|
||||
targpath += '/' + identity['subname']
|
||||
targpath = '/var/lib/confluent/distributions/' + targpath
|
||||
else:
|
||||
targpath = custtargpath
|
||||
try:
|
||||
os.makedirs(targpath, 0o755)
|
||||
except Exception as e:
|
||||
@@ -747,9 +750,9 @@ def rebase_profile(dirname):
|
||||
# customization detected, skip
|
||||
# else
|
||||
# update required, manifest update
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_hashes(dirname):
|
||||
hashmap = {}
|
||||
for dname, _, fnames in os.walk(dirname):
|
||||
@@ -765,18 +768,21 @@ def get_hashes(dirname):
|
||||
|
||||
|
||||
def generate_stock_profiles(defprofile, distpath, targpath, osname,
|
||||
profilelist):
|
||||
profilelist, customname):
|
||||
osd, osversion, arch = osname.split('-')
|
||||
bootupdates = []
|
||||
for prof in os.listdir('{0}/profiles'.format(defprofile)):
|
||||
srcname = '{0}/profiles/{1}'.format(defprofile, prof)
|
||||
profname = '{0}-{1}'.format(osname, prof)
|
||||
if customname:
|
||||
profname = '{0}-{1}'.format(customname, prof)
|
||||
else:
|
||||
profname = '{0}-{1}'.format(osname, prof)
|
||||
dirname = '/var/lib/confluent/public/os/{0}'.format(profname)
|
||||
if os.path.exists(dirname):
|
||||
continue
|
||||
oumask = os.umask(0o22)
|
||||
shutil.copytree(srcname, dirname)
|
||||
hmap = get_hashes(dirname)
|
||||
hmap = get_hashes(dirname)
|
||||
profdata = None
|
||||
try:
|
||||
os.makedirs('{0}/boot/initramfs'.format(dirname), 0o755)
|
||||
@@ -824,11 +830,12 @@ def generate_stock_profiles(defprofile, distpath, targpath, osname,
|
||||
|
||||
class MediaImporter(object):
|
||||
|
||||
def __init__(self, media, cfm=None):
|
||||
def __init__(self, media, cfm=None, customname=None, checkonly=False):
|
||||
self.worker = None
|
||||
if not os.path.exists('/var/lib/confluent/public'):
|
||||
raise Exception('`osdeploy initialize` must be executed before importing any media')
|
||||
self.profiles = []
|
||||
self.errors = []
|
||||
medfile = None
|
||||
self.medfile = None
|
||||
if cfm and media in cfm.clientfiles:
|
||||
@@ -848,25 +855,35 @@ class MediaImporter(object):
|
||||
self.phase = 'copying'
|
||||
if not identity:
|
||||
raise Exception('Unrecognized OS Media')
|
||||
if 'subname' in identity:
|
||||
self.customname = customname if customname else ''
|
||||
if customname:
|
||||
importkey = customname
|
||||
elif 'subname' in identity:
|
||||
importkey = '{0}-{1}'.format(identity['name'], identity['subname'])
|
||||
else:
|
||||
importkey = identity['name']
|
||||
if importkey in importing:
|
||||
if importkey in importing and not checkonly:
|
||||
raise Exception('Media import already in progress for this media')
|
||||
self.importkey = importkey
|
||||
importing[importkey] = self
|
||||
self.importkey = importkey
|
||||
self.osname = identity['name']
|
||||
self.oscategory = identity.get('category', None)
|
||||
targpath = identity['name']
|
||||
if customname:
|
||||
targpath = customname
|
||||
else:
|
||||
targpath = identity['name']
|
||||
self.distpath = '/var/lib/confluent/distributions/' + targpath
|
||||
if identity.get('subname', None):
|
||||
if identity.get('subname', None): # subname is to indicate disk number in a media set
|
||||
targpath += '/' + identity['subname']
|
||||
self.targpath = '/var/lib/confluent/distributions/' + targpath
|
||||
if os.path.exists(self.targpath):
|
||||
del importing[importkey]
|
||||
raise Exception('{0} already exists'.format(self.targpath))
|
||||
errstr = '{0} already exists'.format(self.targpath)
|
||||
if checkonly:
|
||||
self.errors = [errstr]
|
||||
else:
|
||||
raise Exception(errstr)
|
||||
if checkonly:
|
||||
return
|
||||
importing[importkey] = self
|
||||
self.filename = os.path.abspath(media)
|
||||
self.error = ''
|
||||
self.importer = eventlet.spawn(self.importmedia)
|
||||
@@ -884,7 +901,7 @@ class MediaImporter(object):
|
||||
os.environ['CONFLUENT_MEDIAFD'] = '{0}'.format(self.medfile.fileno())
|
||||
with open(os.devnull, 'w') as devnull:
|
||||
self.worker = subprocess.Popen(
|
||||
[sys.executable, __file__, self.filename, '-b'],
|
||||
[sys.executable, __file__, self.filename, '-b', self.targpath, self.distpath, self.customname],
|
||||
stdin=devnull, stdout=subprocess.PIPE, close_fds=False)
|
||||
wkr = self.worker
|
||||
currline = b''
|
||||
@@ -924,7 +941,7 @@ class MediaImporter(object):
|
||||
self.oscategory)
|
||||
try:
|
||||
generate_stock_profiles(defprofile, self.distpath, self.targpath,
|
||||
self.osname, self.profiles)
|
||||
self.osname, self.profiles, self.customname)
|
||||
except Exception as e:
|
||||
self.phase = 'error'
|
||||
self.error = str(e)
|
||||
@@ -951,7 +968,7 @@ if __name__ == '__main__':
|
||||
os.umask(0o022)
|
||||
if len(sys.argv) > 2:
|
||||
mfd = os.environ.get('CONFLUENT_MEDIAFD', None)
|
||||
sys.exit(import_image(sys.argv[1], callback=printit, backend=True, mfd=mfd))
|
||||
sys.exit(import_image(sys.argv[1], callback=printit, backend=True, mfd=mfd, custtargpath=sys.argv[3], custdistpath=sys.argv[4], custname=sys.argv[5]))
|
||||
else:
|
||||
sys.exit(import_image(sys.argv[1], callback=printit))
|
||||
|
||||
|
||||
@@ -21,16 +21,16 @@ import confluent.util as util
|
||||
from fnmatch import fnmatch
|
||||
|
||||
|
||||
def retrieve(nodes, element, configmanager, inputdata):
|
||||
def retrieve(nodes, element, configmanager, inputdata, clearwarnbynode=None):
|
||||
configmanager.check_quorum()
|
||||
if nodes is not None:
|
||||
return retrieve_nodes(nodes, element, configmanager, inputdata)
|
||||
return retrieve_nodes(nodes, element, configmanager, inputdata, clearwarnbynode)
|
||||
elif element[0] == 'nodegroups':
|
||||
return retrieve_nodegroup(
|
||||
element[1], element[3], configmanager, inputdata)
|
||||
element[1], element[3], configmanager, inputdata, clearwarnbynode)
|
||||
|
||||
|
||||
def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
|
||||
def retrieve_nodegroup(nodegroup, element, configmanager, inputdata, clearwarnbynode=None):
|
||||
try:
|
||||
grpcfg = configmanager.get_nodegroup_attributes(nodegroup)
|
||||
except KeyError:
|
||||
@@ -106,10 +106,12 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
|
||||
raise Exception("BUGGY ATTRIBUTE FOR NODEGROUP")
|
||||
|
||||
|
||||
def retrieve_nodes(nodes, element, configmanager, inputdata):
|
||||
def retrieve_nodes(nodes, element, configmanager, inputdata, clearwarnbynode):
|
||||
attributes = configmanager.get_node_attributes(nodes)
|
||||
if element[-1] == 'all':
|
||||
for node in util.natural_sort(nodes):
|
||||
if clearwarnbynode and node in clearwarnbynode:
|
||||
yield msg.Attributes(node, {'_warnings': clearwarnbynode[node]})
|
||||
theattrs = set(allattributes.node).union(set(attributes[node]))
|
||||
for attribute in sorted(theattrs):
|
||||
if attribute in attributes[node]: # have a setting for it
|
||||
@@ -266,6 +268,7 @@ def update_nodes(nodes, element, configmanager, inputdata):
|
||||
namemap[node] = rename['rename']
|
||||
configmanager.rename_nodes(namemap)
|
||||
return yield_rename_resources(namemap, isnode=True)
|
||||
clearwarnbynode = {}
|
||||
for node in nodes:
|
||||
updatenode = inputdata.get_attributes(node, allattributes.node)
|
||||
clearattribs = []
|
||||
@@ -299,10 +302,11 @@ def update_nodes(nodes, element, configmanager, inputdata):
|
||||
markup = (e.text[:e.offset-1] + '-->' + e.text[e.offset-1] + '<--' + e.text[e.offset:]).strip()
|
||||
raise exc.InvalidArgumentException('Syntax error in attribute name: "{0}"'.format(markup))
|
||||
if len(clearattribs) > 0:
|
||||
configmanager.clear_node_attributes([node], clearattribs)
|
||||
clearwarnbynode[node] = []
|
||||
configmanager.clear_node_attributes([node], clearattribs, warnings=clearwarnbynode[node])
|
||||
updatedict[node] = updatenode
|
||||
try:
|
||||
configmanager.set_node_attributes(updatedict)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
return retrieve(nodes, element, configmanager, inputdata)
|
||||
return retrieve(nodes, element, configmanager, inputdata, clearwarnbynode)
|
||||
|
||||
@@ -33,7 +33,8 @@ def simplify_name(name):
|
||||
def sanitize_json(data):
|
||||
if not isinstance(data, str):
|
||||
data = data.decode('utf8')
|
||||
return re.sub(r'([^ {:,]*):', r'"\1":', data).replace("'", '"')
|
||||
return re.sub(r'([^ {:,]*):', r'"\1":', data).replace("'", '"').replace(',,', ',null,')
|
||||
|
||||
|
||||
def answer_challenge(username, password, data):
|
||||
realm = data[0]
|
||||
|
||||
@@ -15,10 +15,31 @@ import confluent.core as core
|
||||
import confluent.messages as msg
|
||||
import pyghmi.exceptions as pygexc
|
||||
import confluent.exceptions as exc
|
||||
import eventlet.queue as queue
|
||||
import eventlet.greenpool as greenpool
|
||||
|
||||
|
||||
def reseat_bays(encmgr, bays, configmanager, rspq):
|
||||
try:
|
||||
for encbay in bays:
|
||||
node = bays[encbay]
|
||||
try:
|
||||
for rsp in core.handle_path(
|
||||
'/nodes/{0}/_enclosure/reseat_bay'.format(encmgr),
|
||||
'update', configmanager,
|
||||
inputdata={'reseat': int(encbay)}):
|
||||
rspq.put(rsp)
|
||||
except pygexc.UnsupportedFunctionality as uf:
|
||||
rspq.put(msg.ConfluentNodeError(node, str(uf)))
|
||||
except exc.TargetEndpointUnreachable as uf:
|
||||
rspq.put(msg.ConfluentNodeError(node, str(uf)))
|
||||
finally:
|
||||
rspq.put(None)
|
||||
|
||||
def update(nodes, element, configmanager, inputdata):
|
||||
emebs = configmanager.get_node_attributes(
|
||||
nodes, (u'enclosure.manager', u'enclosure.bay'))
|
||||
baysbyencmgr = {}
|
||||
for node in nodes:
|
||||
try:
|
||||
em = emebs[node]['enclosure.manager']['value']
|
||||
@@ -30,13 +51,20 @@ def update(nodes, element, configmanager, inputdata):
|
||||
em = node
|
||||
if not eb:
|
||||
eb = -1
|
||||
try:
|
||||
for rsp in core.handle_path(
|
||||
'/nodes/{0}/_enclosure/reseat_bay'.format(em),
|
||||
'update', configmanager,
|
||||
inputdata={'reseat': int(eb)}):
|
||||
yield rsp
|
||||
except pygexc.UnsupportedFunctionality as uf:
|
||||
yield msg.ConfluentNodeError(node, str(uf))
|
||||
except exc.TargetEndpointUnreachable as uf:
|
||||
yield msg.ConfluentNodeError(node, str(uf))
|
||||
if em not in baysbyencmgr:
|
||||
baysbyencmgr[em] = {}
|
||||
baysbyencmgr[em][eb] = node
|
||||
rspq = queue.Queue()
|
||||
gp = greenpool.GreenPool(64)
|
||||
for encmgr in baysbyencmgr:
|
||||
gp.spawn_n(reseat_bays, encmgr, baysbyencmgr[encmgr], configmanager, rspq)
|
||||
while gp.running():
|
||||
nrsp = rspq.get()
|
||||
if nrsp is not None:
|
||||
yield nrsp
|
||||
while not rspq.empty():
|
||||
nrsp = rspq.get()
|
||||
if nrsp is not None:
|
||||
yield nrsp
|
||||
|
||||
|
||||
|
||||
@@ -15,10 +15,16 @@ import confluent.core as core
|
||||
import confluent.messages as msg
|
||||
import pyghmi.exceptions as pygexc
|
||||
import confluent.exceptions as exc
|
||||
import eventlet.greenpool as greenpool
|
||||
import eventlet.queue as queue
|
||||
|
||||
class TaskDone:
|
||||
pass
|
||||
|
||||
def retrieve(nodes, element, configmanager, inputdata):
|
||||
emebs = configmanager.get_node_attributes(
|
||||
nodes, (u'power.*pdu', u'power.*outlet'))
|
||||
relpdus = {}
|
||||
if element == ['power', 'inlets']:
|
||||
outletnames = set([])
|
||||
for node in nodes:
|
||||
@@ -39,13 +45,36 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
for pgroup in outlets[node]:
|
||||
pdu = outlets[node][pgroup]['pdu']
|
||||
outlet = outlets[node][pgroup]['outlet']
|
||||
try:
|
||||
for rsp in core.handle_path(
|
||||
'/nodes/{0}/power/outlets/{1}'.format(pdu, outlet),
|
||||
'retrieve', configmanager):
|
||||
yield msg.KeyValueData({pgroup: rsp.kvpairs['state']['value']}, node)
|
||||
except exc.TargetEndpointBadCredentials:
|
||||
yield msg.ConfluentTargetInvalidCredentials(pdu)
|
||||
if pdu not in relpdus:
|
||||
relpdus[pdu] = {}
|
||||
relpdus[pdu][outlet] = (node, pgroup)
|
||||
rspq = queue.Queue()
|
||||
gp = greenpool.GreenPool(64)
|
||||
for pdu in relpdus:
|
||||
gp.spawn(readpdu, pdu, relpdus[pdu], configmanager, rspq)
|
||||
while gp.running():
|
||||
nrsp = rspq.get()
|
||||
if not isinstance(nrsp, TaskDone):
|
||||
yield nrsp
|
||||
while not rspq.empty():
|
||||
nrsp = rspq.get()
|
||||
if not isinstance(nrsp, TaskDone):
|
||||
yield nrsp
|
||||
|
||||
def readpdu(pdu, outletmap, configmanager, rspq):
|
||||
try:
|
||||
for outlet in outletmap:
|
||||
node, pgroup = outletmap[outlet]
|
||||
try:
|
||||
for rsp in core.handle_path(
|
||||
'/nodes/{0}/power/outlets/{1}'.format(pdu, outlet),
|
||||
'retrieve', configmanager):
|
||||
rspq.put(msg.KeyValueData({pgroup: rsp.kvpairs['state']['value']}, node))
|
||||
except exc.TargetEndpointBadCredentials:
|
||||
rspq.put(msg.ConfluentTargetInvalidCredentials(pdu))
|
||||
finally: # ensure thhat at least one thing triggers the get
|
||||
rspq.put(TaskDone())
|
||||
|
||||
|
||||
def get_outlets(nodes, emebs, inletname):
|
||||
outlets = {}
|
||||
@@ -72,11 +101,34 @@ def update(nodes, element, configmanager, inputdata):
|
||||
emebs = configmanager.get_node_attributes(
|
||||
nodes, (u'power.*pdu', u'power.*outlet'))
|
||||
inletname = element[-1]
|
||||
relpdus = {}
|
||||
rspq = queue.Queue()
|
||||
gp = greenpool.GreenPool(64)
|
||||
outlets = get_outlets(nodes, emebs, inletname)
|
||||
for node in outlets:
|
||||
for pgroup in outlets[node]:
|
||||
pdu = outlets[node][pgroup]['pdu']
|
||||
outlet = outlets[node][pgroup]['outlet']
|
||||
if pdu not in relpdus:
|
||||
relpdus[pdu] = {}
|
||||
relpdus[pdu][outlet] = (node, pgroup)
|
||||
for pdu in relpdus:
|
||||
gp.spawn(updatepdu, pdu, relpdus[pdu], configmanager, inputdata, rspq)
|
||||
while gp.running():
|
||||
nrsp = rspq.get()
|
||||
if not isinstance(nrsp, TaskDone):
|
||||
yield nrsp
|
||||
while not rspq.empty():
|
||||
nrsp = rspq.get()
|
||||
if not isinstance(nrsp, TaskDone):
|
||||
yield nrsp
|
||||
|
||||
def updatepdu(pdu, outletmap, configmanager, inputdata, rspq):
|
||||
try:
|
||||
for outlet in outletmap:
|
||||
node, pgroup = outletmap[outlet]
|
||||
for rsp in core.handle_path('/nodes/{0}/power/outlets/{1}'.format(pdu, outlet),
|
||||
'update', configmanager, inputdata={'state': inputdata.powerstate(node)}):
|
||||
yield msg.KeyValueData({pgroup: rsp.kvpairs['state']['value']}, node)
|
||||
rspq.put(msg.KeyValueData({pgroup: rsp.kvpairs['state']['value']}, node))
|
||||
finally:
|
||||
rspq.put(TaskDone())
|
||||
|
||||
@@ -93,8 +93,14 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
'/noderange/{0}/description'.format(needheight),
|
||||
'retrieve', configmanager,
|
||||
inputdata=None):
|
||||
if not hasattr(rsp, 'kvpairs'):
|
||||
results['errors'].append((rsp.node, rsp.error))
|
||||
continue
|
||||
kvp = rsp.kvpairs
|
||||
for node in kvp:
|
||||
allnodedata[node]['height'] = kvp[node]['height']
|
||||
for node in allnodedata:
|
||||
if 'height' not in allnodedata[node]:
|
||||
allnodedata[node]['height'] = 1
|
||||
yield msg.Generic(results)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ anspypath = None
|
||||
running_status = {}
|
||||
class PlayRunner(object):
|
||||
def __init__(self, playfiles, nodes):
|
||||
self.stderr = ''
|
||||
self.playfiles = playfiles
|
||||
self.nodes = nodes
|
||||
self.worker = None
|
||||
@@ -63,6 +64,9 @@ class PlayRunner(object):
|
||||
else:
|
||||
textout += result['state'] + '\n'
|
||||
textout += '\n'
|
||||
if self.stderr:
|
||||
textout += "ERRORS **********************************\n"
|
||||
textout += self.stderr
|
||||
return textout
|
||||
|
||||
def dump_json(self):
|
||||
@@ -93,7 +97,8 @@ class PlayRunner(object):
|
||||
[mypath, __file__, targnodes, playfilename],
|
||||
stdin=devnull, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, self.stderr = worker.communicate()
|
||||
stdout, stder = worker.communicate()
|
||||
self.stderr += stder.decode('utf8')
|
||||
current = memoryview(stdout)
|
||||
while len(current):
|
||||
sz = struct.unpack('=q', current[:8])[0]
|
||||
|
||||
@@ -30,7 +30,7 @@ import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
currtz = None
|
||||
currtz = 'UTC'
|
||||
keymap = 'us'
|
||||
currlocale = 'en_US.UTF-8'
|
||||
currtzvintage = None
|
||||
@@ -378,6 +378,8 @@ def handle_request(env, start_response):
|
||||
tdc = util.run(['timedatectl'])[0].split(b'\n')
|
||||
except subprocess.CalledProcessError:
|
||||
tdc = []
|
||||
currtzvintage = time.time()
|
||||
ncfg['timezone'] = currtz
|
||||
for ent in tdc:
|
||||
ent = ent.strip()
|
||||
if ent.startswith(b'Time zone:'):
|
||||
@@ -517,8 +519,8 @@ def handle_request(env, start_response):
|
||||
pals = get_extra_names(nodename, cfg, myip)
|
||||
result = syncfiles.start_syncfiles(
|
||||
nodename, cfg, json.loads(reqbody), pals)
|
||||
start_response(result, ())
|
||||
yield ''
|
||||
start_response(result[0], ())
|
||||
yield result[1]
|
||||
return
|
||||
if 'GET' == operation:
|
||||
status, output = syncfiles.get_syncresult(nodename)
|
||||
|
||||
@@ -24,6 +24,7 @@ import confluent.noderange as noderange
|
||||
import eventlet
|
||||
import pwd
|
||||
import grp
|
||||
import sys
|
||||
|
||||
def mkdirp(path):
|
||||
try:
|
||||
@@ -193,8 +194,8 @@ def sync_list_to_node(sl, node, suffixes, peerip=None):
|
||||
targip = node
|
||||
if peerip:
|
||||
targip = peerip
|
||||
output = util.run(
|
||||
['rsync', '-rvLD', targdir + '/', 'root@[{}]:/'.format(targip)])[0]
|
||||
output, stderr = util.run(
|
||||
['rsync', '-rvLD', targdir + '/', 'root@[{}]:/'.format(targip)])
|
||||
except Exception as e:
|
||||
if 'CalledProcessError' not in repr(e):
|
||||
# https://github.com/eventlet/eventlet/issues/413
|
||||
@@ -212,8 +213,11 @@ def sync_list_to_node(sl, node, suffixes, peerip=None):
|
||||
unreadablefiles.append(filename.replace(targdir, ''))
|
||||
if unreadablefiles:
|
||||
raise Exception("Syncing failed due to unreadable files: " + ','.join(unreadablefiles))
|
||||
elif b'Permission denied, please try again.' in e.stderr:
|
||||
elif hasattr(e, 'stderr') and e.stderr and b'Permission denied, please try again.' in e.stderr:
|
||||
raise Exception('Syncing failed due to authentication error, is the confluent automation key not set up (osdeploy initialize -a) or is there some process replacing authorized_keys on the host?')
|
||||
elif hasattr(e, 'stderr') and e.stderr:
|
||||
sys.stderr.write(e.stderr.decode('utf8'))
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
@@ -231,7 +235,7 @@ def stage_ent(currmap, ent, targdir, appendexist=False):
|
||||
everyfent = []
|
||||
allfents = ent.split()
|
||||
for tmpent in allfents:
|
||||
fents = glob.glob(tmpent)
|
||||
fents = glob.glob(tmpent) # TODO: recursive globbing?
|
||||
if not fents:
|
||||
raise Exception('No matching files for "{}"'.format(tmpent))
|
||||
everyfent.extend(fents)
|
||||
@@ -281,9 +285,10 @@ def mkpathorlink(source, destination, appendexist=False):
|
||||
|
||||
|
||||
syncrunners = {}
|
||||
|
||||
cleaner = None
|
||||
|
||||
def start_syncfiles(nodename, cfg, suffixes, principals=[]):
|
||||
global cleaner
|
||||
peerip = None
|
||||
if 'myips' in suffixes:
|
||||
targips = suffixes['myips']
|
||||
@@ -307,13 +312,41 @@ def start_syncfiles(nodename, cfg, suffixes, principals=[]):
|
||||
raise Exception('Cannot perform syncfiles without profile assigned')
|
||||
synclist = '/var/lib/confluent/public/os/{}/syncfiles'.format(profile)
|
||||
if not os.path.exists(synclist):
|
||||
return '200 OK' # not running
|
||||
return '200 OK', 'No synclist' # not running
|
||||
sl = SyncList(synclist, nodename, cfg)
|
||||
if not (sl.appendmap or sl.mergemap or sl.replacemap or sl.appendoncemap):
|
||||
return '200 OK' # the synclist has no actual entries
|
||||
return '200 OK', 'Empty synclist' # the synclist has no actual entries
|
||||
if nodename in syncrunners:
|
||||
if syncrunners[nodename].dead:
|
||||
syncrunners[nodename].wait()
|
||||
else:
|
||||
return '503 Synchronization already in progress', 'Synchronization already in progress for {}'.format(nodename)
|
||||
syncrunners[nodename] = eventlet.spawn(
|
||||
sync_list_to_node, sl, nodename, suffixes, peerip)
|
||||
return '202 Queued' # backgrounded
|
||||
if not cleaner:
|
||||
cleaner = eventlet.spawn(cleanit)
|
||||
return '202 Queued', 'Background synchronization initiated' # backgrounded
|
||||
|
||||
|
||||
def cleanit():
|
||||
toreap = {}
|
||||
while True:
|
||||
for nn in list(syncrunners):
|
||||
if syncrunners[nn].dead:
|
||||
if nn in toreap:
|
||||
try:
|
||||
syncrunners[nn].wait()
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pass
|
||||
del syncrunners[nn]
|
||||
del toreap[nn]
|
||||
else:
|
||||
toreap[nn] = 1
|
||||
elif nn in toreap:
|
||||
del toreap[nn]
|
||||
eventlet.sleep(30)
|
||||
|
||||
|
||||
def get_syncresult(nodename):
|
||||
if nodename not in syncrunners:
|
||||
|
||||
@@ -168,7 +168,7 @@ def cert_matches(fingerprint, certificate):
|
||||
return algo(certificate).digest() == fingerprint
|
||||
algo, _, fp = fingerprint.partition('$')
|
||||
newfp = None
|
||||
if algo in ('sha512', 'sha256'):
|
||||
if algo in ('sha512', 'sha256', 'sha384'):
|
||||
newfp = get_fingerprint(certificate, algo)
|
||||
return newfp and fingerprint == newfp
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user