def test_start_console_fail(self, mock_exec): mock_exec.side_effect = iter( [exception.ConsoleSubprocessFailed(error='error')]) with task_manager.acquire(self.context, self.node.uuid) as task: self.assertRaises(exception.ConsoleSubprocessFailed, self.driver.console.start_console, task)
def start_ics_console_log(node_uuid, port, console_cmd, enable_console): """Start console logging of a node with ironic console server. :param node_uuid: the uuid for the node. :param port: the terminal port for the node. :param console_cmd: the shell command that gets the console. :param enable_console: enable serial console service (default: False) :raises: ConsoleError if the directory for the PID file cannot be created. :raises: ConsoleSubprocessFailed when invoking the subprocess failed. """ # make sure that the old console for this node is stopped # and the files are cleared try: _stop_console(node_uuid) except exception.NoConsolePid: pass except processutils.ProcessExecutionError as exc: LOG.warning( _LW("Failed to kill the old console process " "before starting a new console server" "for node %(node)s. Reason: %(err)s"), { 'node': node_uuid, 'err': exc }) _ensure_console_pid_dir_exists() _ensure_console_log_dir_exists() pid_file = _get_console_pid_file(node_uuid) log_file = _get_console_log_file(node_uuid) # put together the command and arguments for invoking the console args = [] args.append(CONF.console.terminal) if not enable_console: args.append("-d") args.append("-p") args.append(str(port)) args.append("-f") args.append(log_file) args.append("-c") args.append(console_cmd) # run the command as a subprocess try: LOG.debug('Running subprocess: %s', ' '.join(args)) obj = subprocess.Popen(args) except (OSError, ValueError) as e: error = _("%(exec_error)s\n" "Command: %(command)s") % { 'exec_error': str(e), 'command': ' '.join(args) } LOG.warning(error) raise exception.ConsoleSubprocessFailed(error=error) with open(pid_file, "w") as f: f.write("%s" % obj.pid)
def get_ics_console_log(node_uuid): """Get the content of a console log of a node (ironic console server). :param node_uuid: the UUID of the node :raises: ConsoleError if unable to stop the console process """ log_file = _get_console_log_file(node_uuid) args = ["tail", "-n", "100", log_file] try: LOG.debug('Running subprocess: %s', ' '.join(args)) stdout, stderr = utils.execute(*args) except (OSError, ValueError) as e: error = _("%(exec_error)s\n" "Command: %(command)s") % { 'exec_error': str(e), 'command': ' '.join(args) } LOG.warning(error) raise exception.ConsoleSubprocessFailed(error=error) return stdout
def start_socat_console(node_uuid, port, console_cmd): """Open the serial console for a node. :param node_uuid: the uuid of the node :param port: the terminal port for the node :param console_cmd: the shell command that will be executed by socat to establish console to the node :raises ConsoleError: if the directory for the PID file or the PID file cannot be created :raises ConsoleSubprocessFailed: when invoking the subprocess failed """ # Make sure that the old console for this node is stopped. # If no console is running, we may get exception NoConsolePid. try: _stop_console(node_uuid) except exception.NoConsolePid: pass _ensure_console_pid_dir_exists() pid_file = _get_console_pid_file(node_uuid) # put together the command and arguments for invoking the console args = ['socat'] # set timeout check for user's connection. If the timeout value # is not 0, after timeout seconds of inactivity on the client side, # the connection will be closed. if CONF.console.terminal_timeout > 0: args.append('-T%d' % CONF.console.terminal_timeout) args.append('-L%s' % pid_file) console_host = CONF.console.socat_address if netutils.is_valid_ipv6(console_host): arg = ('TCP6-LISTEN:%(port)s,bind=[%(host)s],reuseaddr,fork,' 'max-children=1') else: arg = ('TCP4-LISTEN:%(port)s,bind=%(host)s,reuseaddr,fork,' 'max-children=1') args.append(arg % {'host': console_host, 'port': port}) args.append('EXEC:"%s",pty,stderr' % console_cmd) # run the command as a subprocess try: LOG.debug('Running subprocess: %s', ' '.join(args)) # Use pipe here to catch the error in case socat # fails to start. Note that socat uses stdout as transferring # data, so we only capture stderr for checking if it fails. obj = subprocess.Popen(args, stderr=subprocess.PIPE) except (OSError, ValueError) as e: error = _("%(exec_error)s\n" "Command: %(command)s") % { 'exec_error': str(e), 'command': ' '.join(args) } LOG.exception('Unable to start socat console') raise exception.ConsoleSubprocessFailed(error=error) # NOTE: we need to check if socat fails to start here. # If it starts successfully, it will run in non-daemon mode and # will not return until the console session is stopped. def _wait(node_uuid, popen_obj): wait_state['returncode'] = popen_obj.poll() # socat runs in non-daemon mode, so it should not return now if wait_state['returncode'] is None: # If the pid file is created and the process is running, # we stop checking it periodically. if (os.path.exists(pid_file) and psutil.pid_exists(_get_console_pid(node_uuid))): raise loopingcall.LoopingCallDone() else: # socat returned, it failed to start. # We get the error (out should be None in this case). (_out, err) = popen_obj.communicate() wait_state['errstr'] = _("Command: %(command)s.\n" "Exit code: %(return_code)s.\n" "Stderr: %(error)r") % { 'command': ' '.join(args), 'return_code': wait_state['returncode'], 'error': err } LOG.error(wait_state['errstr']) raise loopingcall.LoopingCallDone() if time.time() > expiration: wait_state['errstr'] = (_("Timeout while waiting for console " "subprocess to start for node %s.") % node_uuid) LOG.error(wait_state['errstr']) raise loopingcall.LoopingCallDone() wait_state = {'returncode': None, 'errstr': ''} expiration = time.time() + CONF.console.subprocess_timeout timer = loopingcall.FixedIntervalLoopingCall(_wait, node_uuid, obj) timer.start(interval=CONF.console.subprocess_checking_interval).wait() if wait_state['errstr']: raise exception.ConsoleSubprocessFailed(error=wait_state['errstr'])
def start_shellinabox_console(node_uuid, port, console_cmd): """Open the serial console for a node. :param node_uuid: the uuid for the node. :param port: the terminal port for the node. :param console_cmd: the shell command that gets the console. :raises: ConsoleError if the directory for the PID file cannot be created or an old process cannot be stopped. :raises: ConsoleSubprocessFailed when invoking the subprocess failed. """ # make sure that the old console for this node is stopped # and the files are cleared try: _stop_console(node_uuid) except exception.NoConsolePid: pass _ensure_console_pid_dir_exists() pid_file = _get_console_pid_file(node_uuid) # put together the command and arguments for invoking the console args = [] args.append(CONF.console.terminal) if CONF.console.terminal_cert_dir: args.append("-c") args.append(CONF.console.terminal_cert_dir) else: args.append("-t") args.append("-p") args.append(str(port)) args.append("--background=%s" % pid_file) args.append("-s") args.append(console_cmd) # run the command as a subprocess try: LOG.debug('Running subprocess: %s', ' '.join(args)) # use pipe here to catch the error in case shellinaboxd # failed to start. obj = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except (OSError, ValueError) as e: error = _("%(exec_error)s\n" "Command: %(command)s") % { 'exec_error': str(e), 'command': ' '.join(args) } LOG.warning(error) raise exception.ConsoleSubprocessFailed(error=error) def _wait(node_uuid, popen_obj): locals['returncode'] = popen_obj.poll() # check if the console pid is created and the process is running. # if it is, then the shellinaboxd is invoked successfully as a daemon. # otherwise check the error. if (locals['returncode'] == 0 and os.path.exists(pid_file) and psutil.pid_exists(_get_console_pid(node_uuid))): raise loopingcall.LoopingCallDone() if (time.time() > expiration or locals['returncode'] is not None): (stdout, stderr) = popen_obj.communicate() locals['errstr'] = _("Timeout or error while waiting for console " "subprocess to start for node: %(node)s.\n" "Command: %(command)s.\n" "Exit code: %(return_code)s.\n" "Stdout: %(stdout)r\n" "Stderr: %(stderr)r") % { 'node': node_uuid, 'command': ' '.join(args), 'return_code': locals['returncode'], 'stdout': stdout, 'stderr': stderr } LOG.warning(locals['errstr']) raise loopingcall.LoopingCallDone() locals = {'returncode': None, 'errstr': ''} expiration = time.time() + CONF.console.subprocess_timeout timer = loopingcall.FixedIntervalLoopingCall(_wait, node_uuid, obj) timer.start(interval=CONF.console.subprocess_checking_interval).wait() if locals['errstr']: raise exception.ConsoleSubprocessFailed(error=locals['errstr'])