276 lines
11 KiB
Python
276 lines
11 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
|
|
#
|
|
# 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 json
|
|
import re
|
|
import tempfile
|
|
import time
|
|
|
|
from devstack import date
|
|
from devstack import exceptions as excp
|
|
from devstack import log as logging
|
|
from devstack import runner as base
|
|
from devstack import settings
|
|
from devstack import shell as sh
|
|
from devstack import trace as tr
|
|
from devstack import utils
|
|
|
|
LOG = logging.getLogger("devstack.runners.screen")
|
|
|
|
# My running type
|
|
RUN_TYPE = settings.RUN_TYPE_SCREEN
|
|
TYPE = settings.RUN_TYPE_TYPE
|
|
|
|
# Trace constants
|
|
SCREEN_TEMPL = "%s.screen"
|
|
ARGS = "ARGS"
|
|
NAME = "NAME"
|
|
SESSION_ID = 'SESSION_ID'
|
|
|
|
# Screen session name
|
|
SESSION_NAME = 'stack'
|
|
SESSION_DEF_TITLE = SESSION_NAME
|
|
SESSION_NAME_MTCHER = re.compile(r"^\s*([\d]+\.%s)\s*(.*)$" % (SESSION_NAME))
|
|
|
|
# How we setup screens status bar
|
|
STATUS_BAR_CMD = r'hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%< %= %H"'
|
|
|
|
# Screen commands/template commands
|
|
SESSION_INIT = ['screen', '-d', '-m', '-S', SESSION_NAME, '-t', SESSION_DEF_TITLE, '-s', "/bin/bash"]
|
|
BAR_INIT = ['screen', '-r', SESSION_NAME, '-X', STATUS_BAR_CMD]
|
|
CMD_INIT = ['screen', '-S', '%SESSION_NAME%', '-X', 'screen', '-t', "%NAME%"]
|
|
CMD_KILL = ['screen', '-S', '%SESSION_NAME%', '-p', "%NAME%", '-X', 'kill']
|
|
CMD_WIPE = ['screen', '-S', '%SESSION_NAME%', '-wipe']
|
|
CMD_START = ['screen', '-S', '%SESSION_NAME%', '-p', "%NAME%", '-X', 'stuff', "\"%CMD%\r\""]
|
|
LIST_CMD = ['screen', '-ls']
|
|
SCREEN_KILLER = ['screen', '-X', '-S', '%SCREEN_ID%', 'quit']
|
|
|
|
# Where our screen sockets will go
|
|
SCREEN_SOCKET_DIR_NAME = "devstack-screen-sockets"
|
|
SCREEN_SOCKET_PERM = 0700
|
|
|
|
# Used to wait until started before we can run the actual start cmd
|
|
WAIT_ONLINE_TO = settings.WAIT_ALIVE_SECS
|
|
|
|
# Run screen as root?
|
|
ROOT_GO = True
|
|
|
|
# Screen RC file
|
|
SCREEN_RC = settings.RC_FN_TEMPL % ('screen')
|
|
|
|
|
|
class ScreenRunner(base.RunnerBase):
|
|
def __init__(self, cfg, component_name, trace_dir):
|
|
base.RunnerBase.__init__(self, cfg, component_name, trace_dir)
|
|
self.socket_dir = sh.joinpths(tempfile.gettempdir(), SCREEN_SOCKET_DIR_NAME)
|
|
|
|
def stop(self, app_name):
|
|
trace_fn = tr.trace_fn(self.trace_dir, SCREEN_TEMPL % (app_name))
|
|
session_id = self._find_session(app_name, trace_fn)
|
|
self._do_stop(app_name, session_id)
|
|
sh.unlink(trace_fn)
|
|
|
|
def _find_session(self, app_name, trace_fn):
|
|
session_id = None
|
|
for (key, value) in tr.TraceReader(trace_fn).read():
|
|
if key == SESSION_ID and value:
|
|
session_id = value
|
|
if not session_id:
|
|
msg = "Could not find a screen session id for %s in file [%s]" % (app_name, trace_fn)
|
|
raise excp.StopException(msg)
|
|
return session_id
|
|
|
|
def _do_stop(self, app_name, session_id):
|
|
mp = dict()
|
|
mp['SESSION_NAME'] = session_id
|
|
mp['NAME'] = app_name
|
|
LOG.info("Stopping program running in session [%s] in window named [%s]." % (session_id, app_name))
|
|
kill_cmd = self._gen_cmd(CMD_KILL, mp)
|
|
sh.execute(*kill_cmd,
|
|
shell=True,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env(),
|
|
check_exit_code=False)
|
|
# We have really no way of knowing if it worked or not, screen sux...
|
|
wipe_cmd = self._gen_cmd(CMD_WIPE, mp)
|
|
sh.execute(*wipe_cmd,
|
|
shell=True,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env(),
|
|
check_exit_code=False)
|
|
|
|
def _get_env(self):
|
|
env = dict()
|
|
env['SCREENDIR'] = self.socket_dir
|
|
return env
|
|
|
|
def _gen_cmd(self, base_cmd, params=dict()):
|
|
return utils.param_replace_list(base_cmd, params)
|
|
|
|
def _active_sessions(self):
|
|
knowns = list()
|
|
list_cmd = self._gen_cmd(LIST_CMD)
|
|
(sysout, _) = sh.execute(*list_cmd,
|
|
check_exit_code=False,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env())
|
|
if sysout.lower().find("No Sockets found") != -1:
|
|
return knowns
|
|
for line in sysout.splitlines():
|
|
mtch = SESSION_NAME_MTCHER.match(line)
|
|
if mtch:
|
|
knowns.append(mtch.group(1))
|
|
return knowns
|
|
|
|
def _get_session(self):
|
|
sessions = self._active_sessions()
|
|
LOG.debug("Found sessions [%s]" % ", ".join(sessions))
|
|
if not sessions:
|
|
return None
|
|
if len(sessions) > 1:
|
|
msg = [
|
|
"You are running multiple screen sessions [%s], please reduce the set to zero or one." % (", ".join(sessions)),
|
|
]
|
|
for s in sorted(sessions):
|
|
mp = {'SCREEN_ID': s}
|
|
cmd_msg = self._gen_cmd(SCREEN_KILLER, mp)
|
|
env = self._get_env()
|
|
for (k, v) in env.items():
|
|
cmd_msg.insert(0, "%s=%s" % (k, v))
|
|
msg.append("Try running '%s' to quit that session." % (" ".join(cmd_msg)))
|
|
raise excp.StartException(utils.joinlinesep(msg))
|
|
return sessions[0]
|
|
|
|
def _do_screen_init(self):
|
|
LOG.info("Creating a new screen session named [%s]" % (SESSION_NAME))
|
|
session_init_cmd = self._gen_cmd(SESSION_INIT)
|
|
sh.execute(*session_init_cmd,
|
|
shell=True,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env())
|
|
LOG.info("Waiting %s seconds before we attempt to set the title bar for that session." % (WAIT_ONLINE_TO))
|
|
time.sleep(WAIT_ONLINE_TO)
|
|
bar_init_cmd = self._gen_cmd(BAR_INIT)
|
|
sh.execute(*bar_init_cmd,
|
|
shell=True,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env())
|
|
|
|
def _do_start(self, session, prog_name, cmd):
|
|
init_cmd = list()
|
|
mp = dict()
|
|
run_cmd = " ".join(cmd)
|
|
mp['SESSION_NAME'] = session
|
|
mp['NAME'] = prog_name
|
|
mp['CMD'] = run_cmd
|
|
init_cmd = self._gen_cmd(CMD_INIT, mp)
|
|
LOG.info("Creating a new screen window named [%s] in session [%s]" % (prog_name, session))
|
|
sh.execute(*init_cmd,
|
|
shell=True,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env())
|
|
LOG.info("Waiting %s seconds before we attempt to run command [%s] in that window." % (WAIT_ONLINE_TO, run_cmd))
|
|
time.sleep(WAIT_ONLINE_TO)
|
|
start_cmd = self._gen_cmd(CMD_START, mp)
|
|
sh.execute(*start_cmd,
|
|
shell=True,
|
|
run_as_root=ROOT_GO,
|
|
env_overrides=self._get_env())
|
|
# We have really no way of knowing if it worked or not, screen sux...
|
|
|
|
def _do_socketdir_init(self, socketdir, perm):
|
|
LOG.debug("Making screen socket directory [%s] (with permissions %o)" % (socketdir, perm))
|
|
with sh.Rooted(ROOT_GO):
|
|
dirs = sh.mkdirslist(socketdir)
|
|
for d in dirs:
|
|
sh.chmod(d, perm)
|
|
|
|
def _begin_start(self, name, program, args):
|
|
run_trace = tr.TraceWriter(tr.trace_fn(self.trace_dir, SCREEN_TEMPL % (name)))
|
|
run_trace.trace(TYPE, RUN_TYPE)
|
|
run_trace.trace(NAME, name)
|
|
run_trace.trace(ARGS, json.dumps(args))
|
|
full_cmd = [program] + list(args)
|
|
session_name = self._get_session()
|
|
inited_screen = False
|
|
if session_name is None:
|
|
inited_screen = True
|
|
self._do_screen_init()
|
|
session_name = self._get_session()
|
|
if session_name is None:
|
|
msg = "After initializing screen with session named [%s], no screen session with that name was found!" % (SESSION_NAME)
|
|
raise excp.StartException(msg)
|
|
run_trace.trace(SESSION_ID, session_name)
|
|
if inited_screen or not sh.isfile(SCREEN_RC):
|
|
rc_gen = ScreenRcGenerator(self)
|
|
rc_contents = rc_gen.create(session_name, self._get_env())
|
|
out_fn = sh.abspth(SCREEN_RC)
|
|
LOG.info("Writing your created screen rc file to [%s]" % (out_fn))
|
|
sh.write_file(out_fn, rc_contents)
|
|
self._do_start(session_name, name, full_cmd)
|
|
return run_trace.filename()
|
|
|
|
def start(self, app_name, runtime_info):
|
|
(program, _, program_args) = runtime_info
|
|
if not sh.isdir(self.socket_dir):
|
|
self._do_socketdir_init(self.socket_dir, SCREEN_SOCKET_PERM)
|
|
return self._begin_start(app_name, program, program_args)
|
|
|
|
|
|
class ScreenRcGenerator(object):
|
|
def __init__(self, sr):
|
|
self.runner = sr
|
|
|
|
def _generate_help(self, session_name, env_exports):
|
|
lines = list()
|
|
lines.append("# Screen help stuff")
|
|
cmd_pieces = list()
|
|
for (k, v) in env_exports.items():
|
|
cmd_pieces.append("%s=%s" % (k, sh.shellquote(v)))
|
|
cmd_pieces.append("screen -r %s" % (session_name))
|
|
if ROOT_GO:
|
|
cmd_pieces.insert(0, "sudo")
|
|
lines.append("# To connect to this session run the following command: ")
|
|
lines.append("# %s" % (" ".join(cmd_pieces)))
|
|
lines.append("")
|
|
return lines
|
|
|
|
def _generate_lines(self, session_name, env_exports):
|
|
lines = list()
|
|
lines.append("# RC file generated on %s" % (date.rcf8222date()))
|
|
lines.append("")
|
|
if env_exports:
|
|
lines.append("# Environment settings (these will need to be exported)")
|
|
for (k, v) in env_exports.items():
|
|
lines.append("# export %s=%s" % (k, sh.shellquote(v)))
|
|
lines.append("")
|
|
if ROOT_GO:
|
|
lines.append("# Screen sockets & programs were created/ran as the root user")
|
|
lines.append("# So you will need to run as user root (or sudo) to enter the following sessions")
|
|
lines.append("")
|
|
lines.append("# Session settings")
|
|
lines.append("sessionname %s" % (session_name))
|
|
lines.append(STATUS_BAR_CMD)
|
|
lines.append("screen -t %s bash" % (SESSION_DEF_TITLE))
|
|
lines.append("")
|
|
lines.extend(self._generate_help(session_name, env_exports))
|
|
return lines
|
|
|
|
def create(self, session_name, env_exports):
|
|
lines = self._generate_lines(session_name, env_exports)
|
|
contents = utils.joinlinesep(*lines)
|
|
return contents
|