Skip to content

Instantly share code, notes, and snippets.

@yosshy
Last active April 4, 2016 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yosshy/68f9a0b9b534dec27aad599744915b50 to your computer and use it in GitHub Desktop.
Save yosshy/68f9a0b9b534dec27aad599744915b50 to your computer and use it in GitHub Desktop.
Console service daemon with logging output for ironic-conductor
# ./ironic-console-server.py -f /tmp/log -p 5555 -c "ipmitool -H 192.168.2.236 -U root -P opctest -I lanplus sol activate"
#!/usr/bin/python
#
# ironic-console-server.py: remote console server with logging output
#
# (C) 2016 Akira Yoshiyama <akirayoshiyama@gmail.com>
#
# 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.
#
# usage: ironic-console-server [-h] [-a] [-d] [-p PORT] [-f FILENAME] [-c ARGV]
#
# optional arguments:
# -h, --help show this help message and exit
# -a open logfile as append mode
# -d disable socket service on startup
# -p PORT port number (default: 7777)
# -f FILENAME log file name (default: typescript)
# -c ARGV command line (default: /bin/sh, use quotes for arguments)
#
# on SIGHUP: re-open the log file (for rotation)
# on SIGUSR1: enable socket service (for node-set-console-mode = ON)
# on SIGUSR2: disable socket service (for node-set-console-mode = OFF)
import argparse
import copy
import eventlet
from eventlet.green import socket
from eventlet import hubs
import os
import pty
import select
import shlex
import signal
import sys
DEFAULT_PORT = 7777
DEFAULT_FILENAME = 'typescript'
DEFAULT_COMMAND = '/bin/sh'
CLIENTS = set()
CHILD_PID = None
CHILD_FD = None
LOGFILE_FD = None
LOGFILE = None
IO_SIZE = 1024
SOCKET_ENABLED = True
hubs.use_hub("selects")
eventlet.monkey_patch()
def reopen_logfile(*args):
"""Close and re-open the log file for rotation"""
global LOGFILE_FD
# Close the current file descriptor if needed
if LOGFILE_FD is not None:
LOGFILE_FD.close()
# Re-open the log file
LOGFILE_FD = open(LOGFILE, "wb", 1)
def enable_socket(*args):
"""Enable socket service again"""
global SOCKET_ENABLED
SOCKET_ENABLED = True
def disable_socket(*args):
"""Close exist sockets and disable socket service"""
global SOCKET_ENABLED
SOCKET_ENABLED = False
for fd in CLIENTS:
fd.close()
def run_command_wrapper(argv):
global CHILD_PID
global CHILD_FD
signal.signal(signal.SIGHUP, reopen_logfile)
signal.signal(signal.SIGUSR1, enable_socket)
signal.signal(signal.SIGUSR2, disable_socket)
while True:
CHILD_PID, CHILD_FD = pty.fork()
if CHILD_PID == 0:
# Child process
os.execlp(argv[0], *argv)
# Parent process
while True:
try:
_r, _w, _x = select.select([CHILD_FD], [], [])
except select.error as e:
errno, msg = e
if errno != 4:
raise
if not CHILD_FD in _r:
continue
try:
data = os.read(CHILD_FD, IO_SIZE)
except OSError:
# Re-execute the command
break
LOGFILE_FD.write(data)
for fd in CLIENTS:
try:
fd.sendall(data)
except socket.error as e:
if e[0] != 32:
raise
os.close(CHILD_FD)
def read_socket(sock):
while True:
try:
data = sock.recv(IO_SIZE)
except socket.error as e:
errno, msg = e
if errno == socket.EBADF:
break
if not data:
break
try:
os.write(CHILD_FD, data)
except socket.error as e:
if e[0] != 32:
raise
except OSError:
# The command looks exited.
pass
CLIENTS.remove(sock)
def main(port=DEFAULT_PORT, filename=DEFAULT_FILENAME, append=False,
disabled=False, argv=None):
global CLIENTS
global LOGFILE
global LOGFILE_FD
global SOCKET_ENABLED
prog = sys.argv[0]
argv = shlex.split(argv)
if disabled:
SOCKET_ENABLED = False
if append:
LOGFILE_FD = open(filename, "ab", 1)
else:
LOGFILE_FD = open(filename, "wb", 1)
LOGFILE = filename
eventlet.spawn_n(run_command_wrapper, argv)
try:
print("%s starting up on port %s" % (prog, port))
server = eventlet.listen(('0.0.0.0', port))
while True:
sock, address = server.accept()
if SOCKET_ENABLED:
print("new client:", address)
CLIENTS.add(sock)
eventlet.spawn_n(read_socket, sock)
else:
print("new client not accepted:", address)
sock.close()
except (KeyboardInterrupt, SystemExit):
print("%s exiting." % prog)
LOGFILE_FD.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='append', action='store_true',
help='open logfile as append mode')
parser.add_argument('-d', dest='disabled', action='store_true',
help='disable socket service on startup')
parser.add_argument('-p', dest='port', type=int, default=DEFAULT_PORT,
help='port number (default: %d)' % DEFAULT_PORT)
parser.add_argument('-f', dest='filename', default=DEFAULT_FILENAME,
help='log file name (default: %s)' % DEFAULT_FILENAME)
parser.add_argument('-c', dest='argv', default=DEFAULT_COMMAND,
help='command line (default: %s, use quotes for arguments)' %
DEFAULT_COMMAND)
options = parser.parse_args()
main(**options.__dict__)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment