Beispiel #1
0
    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
Beispiel #4
0
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'])
Beispiel #5
0
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'])