2023-04-21 14:22:10 +00:00
|
|
|
#!/usr/libexec/platform-python
|
2015-10-13 18:12:43 +00:00
|
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
|
|
|
|
# Copyright 2015 Lenovo
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
import optparse
|
|
|
|
import os
|
2018-12-03 20:53:15 +00:00
|
|
|
import subprocess
|
2015-10-13 18:12:43 +00:00
|
|
|
import sys
|
2018-12-03 20:53:15 +00:00
|
|
|
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)
|
2023-04-21 14:22:10 +00:00
|
|
|
|
2018-12-03 20:53:15 +00:00
|
|
|
import confluent.client as client
|
|
|
|
import confluent.sortutil as sortutil
|
2022-04-08 16:29:45 +00:00
|
|
|
import confluent.logreader as logreader
|
2023-01-30 19:13:44 +00:00
|
|
|
import time
|
|
|
|
import socket
|
|
|
|
import re
|
2015-10-13 18:12:43 +00:00
|
|
|
|
|
|
|
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
|
|
|
|
argparser = optparse.OptionParser(
|
2023-02-16 20:54:21 +00:00
|
|
|
usage="Usage: %prog [options] <noderange> [kill][-- [passthroughoptions]]",
|
2015-10-13 18:12:43 +00:00
|
|
|
epilog="Command sequences are available while connected to a console, hit "
|
|
|
|
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
|
|
|
|
"For example, ctrl-'e', then 'c', then '.' will exit the current "
|
|
|
|
"console")
|
2018-12-03 20:53:15 +00:00
|
|
|
argparser.add_option('-t', '--tile', action='store_true', default=False,
|
|
|
|
help='Tile console windows in the terminal')
|
2022-04-08 16:29:45 +00:00
|
|
|
argparser.add_option('-l', '--log', action='store_true', default=False,
|
|
|
|
help='Enter log replay mode instead of showing a live console')
|
2023-05-31 21:39:53 +00:00
|
|
|
|
|
|
|
argparser.add_option('-d', '--dump', action='store_true', default=False,
|
|
|
|
help= 'Dump log in stdout with timestamps')
|
|
|
|
|
2022-09-29 20:09:46 +00:00
|
|
|
argparser.add_option('-w','--windowed', action='store_true', default=False,
|
2022-10-16 01:29:56 +00:00
|
|
|
help='Open terminal windows for each node. The '
|
|
|
|
'environment variable NODECONSOLE_WINDOWED_COMMAND '
|
2022-11-01 17:25:20 +00:00
|
|
|
'should be set, which should be a text string corresponding '
|
2022-10-16 01:29:56 +00:00
|
|
|
'to a command that can be used to open a windowed console,'
|
|
|
|
' omitting the "nodeconsole <noderange>" part of the '
|
|
|
|
'command, for example, to open a set of consoles for a '
|
|
|
|
'range of nodes in separate xterm windows, set '
|
|
|
|
'NODECONSOLE_WINDOWED_COMMAND to "xterm -e". To open a '
|
|
|
|
'set of consoles for a range of nodes in separate '
|
|
|
|
'GNOME Terminal windows with a size of 100 columns and '
|
|
|
|
'31 rows, set NODECONSOLE_WINDOWED_COMMAND '
|
|
|
|
'to "gnome-terminal --geometry 100x31 --" or in a WSL '
|
|
|
|
'environment, to open a set of consoles for a range of '
|
|
|
|
'nodes in separate Windows Terminal windows, with the '
|
|
|
|
'title set for each node, set NODECONSOLE_WINDOWED_COMMAND'
|
2022-11-01 15:29:41 +00:00
|
|
|
' to "wt.exe wsl.exe -d AlmaLinux-8 '
|
2022-11-01 17:25:20 +00:00
|
|
|
'--shell-type login". If the NODECONSOLE_WINDOWED_COMMAND '
|
|
|
|
'environment variable isn\'t set, xterm will be used by'
|
|
|
|
'default.')
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2015-10-13 18:12:43 +00:00
|
|
|
(options, args) = argparser.parse_args()
|
2023-01-27 14:30:46 +00:00
|
|
|
|
|
|
|
pass_through_args = []
|
2023-02-16 20:54:21 +00:00
|
|
|
killcon = False
|
|
|
|
try:
|
|
|
|
noderange = args[0]
|
|
|
|
if len(args) > 1:
|
|
|
|
if args[1] == 'kill':
|
|
|
|
killcon = True
|
|
|
|
pass_through_args = args[1:]
|
|
|
|
args = args[:1]
|
|
|
|
except IndexError:
|
|
|
|
argparser.print_help()
|
|
|
|
sys.exit(1)
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2015-10-13 18:12:43 +00:00
|
|
|
if len(args) != 1:
|
|
|
|
argparser.print_help()
|
|
|
|
sys.exit(1)
|
2023-02-16 20:54:21 +00:00
|
|
|
|
2022-04-08 16:29:45 +00:00
|
|
|
if options.log:
|
|
|
|
logname = args[0]
|
|
|
|
if not os.path.exists(logname) and logname[0] != '/':
|
|
|
|
logname = os.path.join('/var/log/confluent/consoles', logname)
|
|
|
|
if not os.path.exists(logname):
|
|
|
|
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
|
|
|
|
sys.exit(1)
|
|
|
|
logreader.replay_to_console(logname)
|
|
|
|
sys.exit(0)
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2023-05-31 21:39:53 +00:00
|
|
|
if options.dump:
|
|
|
|
logname = args[0]
|
|
|
|
if not os.path.exists(logname) and logname[0] != '/':
|
|
|
|
logname = os.path.join('/var/log/confluent/consoles', logname)
|
|
|
|
if not os.path.exists(logname):
|
|
|
|
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
|
|
|
|
sys.exit(1)
|
|
|
|
logreader.dump_to_console(logname)
|
|
|
|
sys.exit(0)
|
|
|
|
|
2023-02-16 20:54:21 +00:00
|
|
|
def kill(noderange):
|
|
|
|
sess = client.Command()
|
|
|
|
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
|
|
|
|
if not envstring:
|
|
|
|
envstring = 'xterm'
|
|
|
|
|
|
|
|
nodes = []
|
|
|
|
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
|
|
|
|
node = res.get('item', {}).get('href', '/').replace('/', '')
|
|
|
|
if not node:
|
|
|
|
sys.stderr.write(res.get('error', repr(res)) + '\n')
|
|
|
|
sys.exit(1)
|
|
|
|
nodes.append(node)
|
|
|
|
|
|
|
|
for node in nodes:
|
2023-04-21 14:22:10 +00:00
|
|
|
s=socket.socket(socket.AF_UNIX)
|
|
|
|
winid=None
|
|
|
|
try:
|
|
|
|
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
|
|
|
|
wintr=win.communicate()[0]
|
|
|
|
for line in wintr.decode('utf-8').split('\n'):
|
|
|
|
if 'console: {0}'.format(node) in line or 'confetty' in line:
|
|
|
|
win_obj = [ele for ele in line.split(' ') if ele.strip()]
|
|
|
|
winid = win_obj[0]
|
|
|
|
except:
|
|
|
|
print("Error: cannot retrieve window id of node {}".format(node))
|
|
|
|
|
|
|
|
if winid:
|
|
|
|
ps_data=subprocess.Popen(['xkill', '-id', winid ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
2023-02-16 20:54:21 +00:00
|
|
|
|
|
|
|
sys.exit(0)
|
2023-01-27 14:30:46 +00:00
|
|
|
|
|
|
|
def handle_geometry(envlist, sizegeometry, side_pad=0, top_pad=0, first=False):
|
|
|
|
if '-geometry' in envlist:
|
|
|
|
g_index = envlist.index('-geometry')
|
|
|
|
elif '-g' in envlist:
|
|
|
|
g_index = envlist.index('-g')
|
|
|
|
else:
|
|
|
|
g_index = 0
|
|
|
|
if g_index:
|
|
|
|
if first:
|
|
|
|
envlist[g_index+1] = '{0}+{1}+{2}'.format(envlist[g_index+1],side_pad, top_pad)
|
|
|
|
else:
|
|
|
|
envlist[g_index+1] = '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad)
|
|
|
|
else:
|
|
|
|
envlist.insert(1, '-geometry')
|
|
|
|
envlist.insert(2, '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad))
|
|
|
|
g_index = 1
|
|
|
|
return envlist
|
|
|
|
|
2023-02-16 20:54:21 +00:00
|
|
|
# add funcltionality to close/kill all open consoles
|
|
|
|
if killcon:
|
|
|
|
kill(noderange)
|
|
|
|
|
2022-09-29 15:58:06 +00:00
|
|
|
#added functionality for wcons
|
2022-09-29 20:09:46 +00:00
|
|
|
if options.windowed:
|
2023-01-20 21:41:56 +00:00
|
|
|
result=subprocess.Popen(['xwininfo', '-root'], stdout=subprocess.PIPE)
|
|
|
|
rootinfo=result.communicate()[0]
|
|
|
|
result.wait()
|
|
|
|
for line in rootinfo.decode('utf-8').split('\n'):
|
|
|
|
if 'Width' in line:
|
|
|
|
screenwidth = int(line.split(':')[1])
|
|
|
|
if 'Height' in line:
|
|
|
|
screenheight = int(line.split(':')[1])
|
|
|
|
|
2022-10-16 01:29:56 +00:00
|
|
|
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
|
|
|
|
if not envstring:
|
2023-01-24 16:18:21 +00:00
|
|
|
sizegeometry='100x31'
|
2023-01-27 14:30:46 +00:00
|
|
|
corrected_x, corrected_y = (13,84)
|
|
|
|
envlist = handle_geometry(['xterm'] + pass_through_args + ['-e'],sizegeometry, first=True)
|
2023-01-30 19:13:44 +00:00
|
|
|
#envlist=['xterm', '-bg', 'black', '-fg', 'white', '-geometry', '{sizegeometry}+0+0'.format(sizegeometry=sizegeometry), '-e']
|
2022-11-01 17:25:20 +00:00
|
|
|
else:
|
|
|
|
envlist=os.environ.get('NODECONSOLE_WINDOWED_COMMAND').split(' ')
|
2023-01-20 21:41:56 +00:00
|
|
|
if envlist[0] == 'xterm':
|
|
|
|
if '-geometry' in envlist:
|
|
|
|
g_index = envlist.index('-geometry')
|
|
|
|
elif '-g' in envlist:
|
|
|
|
g_index = envlist.index('-g')
|
|
|
|
else:
|
|
|
|
g_index = 0
|
|
|
|
if g_index:
|
|
|
|
envlist[g_index+1] = envlist[g_index+1] + '+0+0'
|
|
|
|
|
|
|
|
else:
|
|
|
|
envlist.insert(1, '-geometry')
|
2023-01-24 16:18:21 +00:00
|
|
|
envlist.insert(2, '100x31+0+0')
|
2023-01-27 14:30:46 +00:00
|
|
|
g_index = 1
|
|
|
|
|
2022-09-29 15:58:06 +00:00
|
|
|
nodes = []
|
|
|
|
sess = client.Command()
|
|
|
|
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
|
|
|
|
node = res.get('item', {}).get('href', '/').replace('/', '')
|
|
|
|
if not node:
|
|
|
|
sys.stderr.write(res.get('error', repr(res)) + '\n')
|
|
|
|
sys.exit(1)
|
|
|
|
nodes.append(node)
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2023-01-20 21:41:56 +00:00
|
|
|
if options.tile and not envlist[0] == 'xterm':
|
|
|
|
sys.stderr.write('[ERROR] UNSUPPORTED OPTIONS. \nWindowed and tiled consoles are only supported when using xterm \n')
|
|
|
|
sys.exit(1)
|
|
|
|
firstnode=nodes[0]
|
|
|
|
nodes.pop(0)
|
|
|
|
with open(os.devnull, 'wb') as devnull:
|
|
|
|
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode), '-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode) ] , stdin=devnull)
|
|
|
|
time.sleep(2)
|
|
|
|
s=socket.socket(socket.AF_UNIX)
|
|
|
|
winid=''
|
|
|
|
try:
|
|
|
|
s.connect('/tmp/controlpath-{firstnode}'.format(firstnode=firstnode))
|
|
|
|
s.recv(64)
|
|
|
|
s.send(b'GETWINID')
|
|
|
|
winid=s.recv(64).decode('utf-8')
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2023-01-20 21:41:56 +00:00
|
|
|
except:
|
|
|
|
time.sleep(2)
|
|
|
|
# try to get id of first panel/xterm window using name
|
|
|
|
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
|
|
|
|
wintr=win.communicate()[0]
|
|
|
|
for line in wintr.decode('utf-8').split('\n'):
|
|
|
|
if 'console: {firstnode}'.format(firstnode=firstnode) in line or 'confetty' in line:
|
|
|
|
win_obj = [ele for ele in line.split(' ') if ele.strip()]
|
|
|
|
winid = win_obj[0]
|
|
|
|
if winid:
|
|
|
|
firstnode_window=subprocess.Popen(['xwininfo', '-id', '{winid}'.format(winid=winid)], stdout=subprocess.PIPE)
|
|
|
|
xinfo=firstnode_window.communicate()[0]
|
|
|
|
xinfl = xinfo.decode('utf-8').split('\n')
|
|
|
|
for line in xinfl:
|
|
|
|
if 'Absolute upper-left X:' in line:
|
|
|
|
side_pad = int(line.split(':')[1])
|
|
|
|
elif 'Absolute upper-left Y:' in line:
|
|
|
|
top_pad = int(line.split(':')[1])
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2023-01-20 21:41:56 +00:00
|
|
|
elif 'Width:' in line:
|
|
|
|
window_width = int(line.split(':')[1])
|
|
|
|
elif 'Height' in line:
|
|
|
|
window_height = int(line.split(':')[1])
|
|
|
|
elif '-geometry' in line:
|
2023-04-28 13:20:31 +00:00
|
|
|
l = re.split(' |x|-|\+', line)
|
2023-01-20 21:41:56 +00:00
|
|
|
l_nosp = [ele for ele in l if ele.strip()]
|
|
|
|
wmxo = int(l_nosp[1])
|
|
|
|
wmyo = int(l_nosp[2])
|
|
|
|
sizegeometry = str(wmxo) + 'x' + str(wmyo)
|
|
|
|
else:
|
|
|
|
pass
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2023-01-20 21:41:56 +00:00
|
|
|
window_width += side_pad*2
|
|
|
|
window_height += side_pad+top_pad
|
|
|
|
screenwidth -= wmxo
|
|
|
|
screenheight -= wmyo
|
|
|
|
currx = window_width
|
|
|
|
curry = 0
|
|
|
|
maxcol = int(screenwidth/window_width)
|
2023-01-27 14:30:46 +00:00
|
|
|
|
2022-09-29 15:58:06 +00:00
|
|
|
for node in sortutil.natural_sort(nodes):
|
2023-01-20 21:41:56 +00:00
|
|
|
if options.tile and envlist[0] == 'xterm':
|
|
|
|
corrected_x = currx
|
|
|
|
corrected_y = curry
|
|
|
|
xgeometry = '{0}+{1}+{2}'.format(sizegeometry, corrected_x, corrected_y)
|
|
|
|
currx += window_width
|
|
|
|
if currx >= screenwidth:
|
|
|
|
currx=0
|
|
|
|
curry += window_height
|
|
|
|
if curry > screenheight:
|
|
|
|
curry =top_pad
|
|
|
|
if not envstring:
|
2023-01-27 14:30:46 +00:00
|
|
|
envlist= handle_geometry(envlist, sizegeometry, corrected_x, corrected_y)
|
2023-01-20 21:41:56 +00:00
|
|
|
else:
|
|
|
|
if g_index:
|
|
|
|
envlist[g_index+1] = xgeometry
|
|
|
|
elif envlist[0] == 'xterm':
|
2023-01-27 14:30:46 +00:00
|
|
|
envlist=handle_geometry(envlist, sizegeometry, side_pad, top_pad)
|
|
|
|
side_pad+=(side_pad+1)
|
|
|
|
top_pad+=(top_pad+30)
|
2023-01-20 21:41:56 +00:00
|
|
|
else:
|
|
|
|
pass
|
2022-11-01 15:29:41 +00:00
|
|
|
with open(os.devnull, 'wb') as devnull:
|
2023-04-21 14:22:10 +00:00
|
|
|
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node), '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
|
2022-10-16 01:03:35 +00:00
|
|
|
sys.exit(0)
|
2022-09-29 15:58:06 +00:00
|
|
|
#end of wcons
|
2018-12-03 20:53:15 +00:00
|
|
|
if options.tile:
|
2019-01-29 21:02:54 +00:00
|
|
|
null = open('/dev/null', 'w')
|
2018-12-03 20:53:15 +00:00
|
|
|
nodes = []
|
|
|
|
sess = client.Command()
|
|
|
|
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
|
|
|
|
node = res.get('item', {}).get('href', '/').replace('/', '')
|
|
|
|
if not node:
|
|
|
|
sys.stderr.write(res.get('error', repr(res)) + '\n')
|
|
|
|
sys.exit(1)
|
|
|
|
nodes.append(node)
|
|
|
|
initial = True
|
2023-04-27 19:39:32 +00:00
|
|
|
in_tmux = False
|
2018-12-03 20:53:15 +00:00
|
|
|
pane = 0
|
2020-03-27 14:57:14 +00:00
|
|
|
sessname = 'nodeconsole_{0}'.format(os.getpid())
|
2023-04-27 19:39:32 +00:00
|
|
|
if os.environ.get("TMUX"):
|
|
|
|
initial = False
|
|
|
|
in_tmux = True
|
|
|
|
subprocess.call(['tmux', 'rename-session', sessname])
|
2018-12-03 20:53:15 +00:00
|
|
|
for node in sortutil.natural_sort(nodes):
|
2020-03-27 14:57:14 +00:00
|
|
|
panename = '{0}:{1}'.format(sessname, pane)
|
2018-12-03 20:53:15 +00:00
|
|
|
if initial:
|
|
|
|
initial = False
|
|
|
|
subprocess.call(
|
|
|
|
['tmux', 'new-session', '-d', '-s',
|
2020-03-27 14:57:14 +00:00
|
|
|
sessname, '-x', '800', '-y',
|
2018-12-03 20:53:15 +00:00
|
|
|
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
|
|
|
|
confettypath, node)])
|
|
|
|
else:
|
2020-03-27 14:57:14 +00:00
|
|
|
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
|
|
|
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
2018-12-03 20:53:15 +00:00
|
|
|
subprocess.call(
|
2020-03-27 14:57:14 +00:00
|
|
|
['tmux', 'split', '-h', '-t', sessname,
|
2018-12-03 20:53:15 +00:00
|
|
|
'{0} -m 5 start /nodes/{1}/console/session'.format(
|
|
|
|
confettypath, node)])
|
2020-03-27 14:57:14 +00:00
|
|
|
subprocess.call(['tmux', 'select-layout', '-t', sessname, 'tiled'], stdout=null)
|
|
|
|
pane += 1
|
|
|
|
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
|
|
|
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
2023-04-27 19:39:32 +00:00
|
|
|
if not in_tmux:
|
|
|
|
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
|
2018-12-03 20:53:15 +00:00
|
|
|
else:
|
|
|
|
os.execl(confettypath, confettypath, 'start',
|
2023-04-28 13:20:31 +00:00
|
|
|
'/nodes/{0}/console/session'.format(args[0]))
|