2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-08-11 05:50:16 +00:00

Implement screenshot via nodeconsole -s

This will grab screenshots from Lenovo systems and
output them to the console, using the kitty image protocol.
This commit is contained in:
Jarrod Johnson
2025-04-11 17:13:19 -04:00
parent 507e6fa9ac
commit 6d1da85991
5 changed files with 72 additions and 0 deletions

View File

@@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import optparse
import os
import subprocess
@@ -46,6 +47,8 @@ argparser.add_option('-l', '--log', action='store_true', default=False,
argparser.add_option('-T', '--Timestamp', action='store_true', default=False,
help= 'Dump log in stdout with timestamps')
argparser.add_option('-s', '--screenshot', action='store_true', default=False,
help='Attempt to grab screenshot and render using kitty image protocol')
argparser.add_option('-w','--windowed', action='store_true', default=False,
help='Open terminal windows for each node. The '
'environment variable NODECONSOLE_WINDOWED_COMMAND '
@@ -69,6 +72,16 @@ argparser.add_option('-w','--windowed', action='store_true', default=False,
(options, args) = argparser.parse_args()
def kitty_draw(data):
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
sys.stdout.write('\x1b_Ga=T,f=100,m={};'.format(m))
sys.stdout.write(chunk.decode('utf8'))
sys.stdout.write('\x1b\\')
sys.stdout.flush()
sys.stdout.write('\n')
pass_through_args = []
killcon = False
try:
@@ -106,6 +119,16 @@ if options.Timestamp:
logreader.dump_to_console(logname)
sys.exit(0)
if options.screenshot:
sess = client.Command()
for res in sess.read('/noderange/{}/console/ikvm_screenshot'.format(args[0])):
for node in res.get('databynode', {}):
imgdata = res['databynode'][node]['image']['imgdata']
sys.stdout.write('{}: '.format(node))
kitty_draw(imgdata.encode())
sys.stdout.write('\n')
sys.exit(0)
def kill(noderange):
sess = client.Command()
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')

View File

@@ -442,6 +442,10 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'ikvm_screenshot': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
'description': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],

View File

@@ -18,6 +18,7 @@
# This module implements client/server messages emitted from plugins.
# Things are defined here to 'encourage' developers to coordinate information
# format. This is also how different data formats are supported
import base64
import confluent.exceptions as exc
import confluent.config.configmanager as cfm
import confluent.config.conf as cfgfile
@@ -1882,6 +1883,12 @@ class GraphicalConsole(ConfluentMessage):
else:
self.kvpairs = {name: {'Launcher': kv}}
class ScreenShot(ConfluentMessage):
readonly = True
def __init__(self, imgdata, node, imgformat=None):
self.kvpairs = {node: {'image': {'imgformat': imgformat, 'imgdata': base64.b64encode(imgdata)}}}
class CryptedAttributes(Attributes):
defaulttype = 'password'

View File

@@ -28,6 +28,7 @@ import eventlet.greenpool as greenpool
import eventlet.queue as queue
import eventlet.support.greendns
from fnmatch import fnmatch
import io
import os
import pwd
import pyghmi.constants as pygconstants
@@ -51,6 +52,14 @@ except NameError:
pci_cache = {}
class RetainedIO(io.BytesIO):
# Need to retain buffer after close
def __init__(self):
self.resultbuffer = None
def close(self):
self.resultbuffer = self.getbuffer()
super().close()
def get_dns_txt(qstring):
return eventlet.support.greendns.resolver.query(
qstring, 'TXT')[0].strings[0].replace('i=', '')
@@ -607,6 +616,8 @@ class IpmiHandler(object):
self.handle_description()
elif self.element == ['console', 'ikvm_methods']:
self.handle_ikvm_methods()
elif self.element == ['console', 'ikvm_screenshot']:
self.handle_ikvm_screenshot()
elif self.element == ['console', 'ikvm']:
self.handle_ikvm()
else:
@@ -1641,6 +1652,14 @@ class IpmiHandler(object):
dsc = {'ikvm_methods': dsc}
self.output.put(msg.KeyValueData(dsc, self.node))
def handle_ikvm_screenshot(self):
# good background for the webui, and kitty
imgdata = RetainedIO()
imgformat = self.ipmicmd.get_screenshot(imgdata)
imgdata = imgdata.getvalue()
if imgdata:
self.output.put(msg.ScreenShot(imgdata, self.node, imgformat=imgformat))
def handle_ikvm(self):
methods = self.ipmicmd.get_ikvm_methods()
if 'openbmc' in methods:

View File

@@ -27,6 +27,7 @@ import eventlet.greenpool as greenpool
import eventlet.queue as queue
import eventlet.support.greendns
from fnmatch import fnmatch
import io
import os
import pwd
import pyghmi.constants as pygconstants
@@ -42,6 +43,14 @@ if not hasattr(ssl, 'SSLEOFError'):
pci_cache = {}
class RetainedIO(io.BytesIO):
# Need to retain buffer after close
def __init__(self):
self.resultbuffer = None
def close(self):
self.resultbuffer = self.getbuffer()
super().close()
def get_dns_txt(qstring):
return eventlet.support.greendns.resolver.query(
qstring, 'TXT')[0].strings[0].replace('i=', '')
@@ -464,6 +473,8 @@ class IpmiHandler(object):
self.handle_description()
elif self.element == ['console', 'ikvm_methods']:
self.handle_ikvm_methods()
elif self.element == ['console', 'ikvm_screenshot']:
self.handle_ikvm_screenshot()
elif self.element == ['console', 'ikvm']:
self.handle_ikvm()
else:
@@ -1498,6 +1509,14 @@ class IpmiHandler(object):
dsc = {'ikvm_methods': dsc}
self.output.put(msg.KeyValueData(dsc, self.node))
def handle_ikvm_screenshot(self):
# good background for the webui, and kitty
imgdata = RetainedIO()
imgformat = self.ipmicmd.get_screenshot(imgdata)
imgdata = imgdata.getvalue()
if imgdata:
self.output.put(msg.ScreenShot(imgdata, self.node, imgformat=imgformat))
def handle_ikvm(self):
methods = self.ipmicmd.get_ikvm_methods()
if 'openbmc' in methods: