# 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