// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.

#ident "$XORP: xorp/rtrmgr/task.cc,v 1.60 2007/02/16 22:47:25 pavlin Exp $"

// #define DEBUG_LOGGING
// #define DEBUG_PRINT_FUNCTION_NAME

#include "rtrmgr_module.h"

#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/utils.hh"

#include "master_conf_tree.hh"
#include "module_command.hh"
#include "module_manager.hh"
#include "task.hh"
#include "unexpanded_xrl.hh"
#include "xorp_client.hh"
#include "util.hh"


#define MAX_STATUS_RETRIES 30



// ----------------------------------------------------------------------------
// DelayValidation implementation
DelayValidation::DelayValidation(const string& module_name,
				 EventLoop& eventloop,
				 uint32_t ms,
				 bool verbose)
    : Validation(module_name, verbose),
      _eventloop(eventloop),
      _delay_in_ms(ms)
{
}

void
DelayValidation::validate(RunShellCommand::ExecId exec_id, CallBack cb)
{
    _cb = cb;
    _timer = _eventloop.new_oneoff_after_ms(_delay_in_ms,
			callback(this, &DelayValidation::timer_expired));
    UNUSED(exec_id);
}

void
DelayValidation::timer_expired()
{
    _cb->dispatch(true);
}


// ----------------------------------------------------------------------------
// XrlStatusValidation implementation

XrlStatusValidation::XrlStatusValidation(const string& module_name,
					 const XrlAction& xrl_action,
					 TaskManager& taskmgr)
    : Validation(module_name, taskmgr.verbose()),
      _xrl_action(xrl_action),
      _task_manager(taskmgr),
      _retries(0)
{
}

EventLoop&
XrlStatusValidation::eventloop()
{
    return _task_manager.eventloop();
}

void
XrlStatusValidation::validate(RunShellCommand::ExecId exec_id, CallBack cb)
{
    debug_msg("validate\n");

    _exec_id = exec_id;
    _cb = cb;

    if (_task_manager.do_exec()) {
	string xrl_request, errmsg;
	Xrl* xrl = NULL;
	do {
	    // Try to expand using the configuration tree
	    const MasterConfigTreeNode* ctn;
	    ctn = _task_manager.config_tree().find_config_module(_module_name);
	    if (ctn != NULL) {
		xrl = _xrl_action.expand_xrl_variables(*ctn, errmsg);
		if (xrl == NULL) {
		    XLOG_FATAL("Cannot expand XRL validation action %s "
			       "for module %s: %s",
			       _xrl_action.str().c_str(),
			       _module_name.c_str(),
			       errmsg.c_str());
		}
		break;
	    }

	    // Try to expand using the template tree
	    const TemplateTreeNode& ttn = _xrl_action.template_tree_node();
	    xrl = _xrl_action.expand_xrl_variables(ttn, errmsg);
	    if (xrl == NULL) {
		XLOG_FATAL("Cannot expand XRL validation action %s "
			   "for module %s: %s",
			   _xrl_action.str().c_str(),
			   _module_name.c_str(),
			   errmsg.c_str());
	    }
	    break;
	} while (false);
	if (xrl == NULL) {
	    XLOG_FATAL("Cannot expand XRL validation action %s for module %s",
		       _xrl_action.str().c_str(), _module_name.c_str());
	}

	XLOG_TRACE(_verbose, "Validating with XRL: >%s<\n",
		   xrl->str().c_str());

	string response = _xrl_action.xrl_return_spec();
	_task_manager.xorp_client().send_now(*xrl,
			callback(this, &XrlStatusValidation::xrl_done),
			response, true);
	delete xrl;
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, but we want to ensure
	// that the xrl_done response gets the right arguments even
	// though we're not going to call the XRL.
	//
	_retry_timer = eventloop().new_oneoff_after_ms(1000,
			callback(this, &XrlStatusValidation::dummy_response));
    }
}

void
XrlStatusValidation::dummy_response()
{
    XrlError e = XrlError::OKAY();
    XrlArgs a;

    a.add_uint32("status", PROC_READY);
    a.add_string("reason", string(""));
    xrl_done(e, &a);
}

void
XrlStatusValidation::xrl_done(const XrlError& e, XrlArgs* xrl_args)
{
    switch (e.error_code()) {
    case OKAY:
	try {
	    ProcessStatus status;
	    status = static_cast<ProcessStatus>(xrl_args->get_uint32("status"));
	    string reason(xrl_args->get_string("reason"));
	    handle_status_response(status, reason);
	} catch (XrlArgs::XrlAtomNotFound) {
	    // Not a valid response
	    XLOG_ERROR("Bad XRL response to get_status");
	    _cb->dispatch(false);
	}
	break;

    case BAD_ARGS:
    case COMMAND_FAILED:
    case NO_SUCH_METHOD:
	//
	// The template file must have been wrong - the target doesn't
	// support the common interface.
	// Just return true and hope everything's OK
	//
	XLOG_ERROR("Module %s doesn't support status validation",
		   _module_name.c_str());
	_cb->dispatch(true);
	break;

    case RESOLVE_FAILED:
    case SEND_FAILED:
    case REPLY_TIMED_OUT:
    case SEND_FAILED_TRANSIENT:
	//
	// REPLY_TIMED_OUT => We did not yet receive a reply.
	// RESOLVE_FAILED => It's not yet registered with the finder -
	// retry after a short delay.
	// SEND_FAILED and SEND_FAILED_TRANSIENT=> ???  We're dealing with
	// startup conditions here, so give the problem a chance to resolve
	// itself.
	//
	// TODO: Make retries and delay configurable (no magic numbers).
	//
	_retries++;
	if (_retries > MAX_STATUS_RETRIES) {
	    _cb->dispatch(false);
	}
	_retry_timer = eventloop().new_oneoff_after_ms(1000,
			callback(this, &XrlStatusValidation::validate,
				 _exec_id, _cb));
	break;

    case INTERNAL_ERROR:
	XLOG_ERROR("Error while validating module %s", _module_name.c_str());
	XLOG_WARNING("Continuing anyway, cross your fingers...");
	_cb->dispatch(true);
	break;

    case NO_FINDER:
	// We're in trouble now! This shouldn't be able to happen.
	XLOG_UNREACHABLE();
	break;
    }
}


// ----------------------------------------------------------------------------
// ProgramStatusValidation implementation

ProgramStatusValidation::ProgramStatusValidation(
    const string& module_name,
    const ProgramAction& program_action,
    TaskManager& taskmgr)
    : Validation(module_name, taskmgr.verbose()),
      _program_action(program_action),
      _task_manager(taskmgr),
      _run_command(NULL)
{
}

ProgramStatusValidation::~ProgramStatusValidation()
{
    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }
}

EventLoop&
ProgramStatusValidation::eventloop()
{
    return _task_manager.eventloop();
}

void
ProgramStatusValidation::validate(RunShellCommand::ExecId exec_id, CallBack cb)
{
    debug_msg("validate\n");

    _cb = cb;
    if (_task_manager.do_exec()) {
	string program_request, errmsg;
	do {
	    // Try to expand using the configuration tree
	    const MasterConfigTreeNode* ctn;
	    ctn = _task_manager.config_tree().find_config_module(_module_name);
	    if (ctn != NULL) {
		if (_program_action.expand_program_variables(*ctn,
							     program_request,
							     errmsg)
		    != XORP_OK) {
		    XLOG_FATAL("Cannot expand program validation action %s "
			       "for module %s: %s",
			       _program_action.str().c_str(),
			       _module_name.c_str(),
			       errmsg.c_str());
		}
		break;
	    }

	    // Try to expand using the template tree
	    const TemplateTreeNode& ttn = _program_action.template_tree_node();
	    if (_program_action.expand_program_variables(ttn, program_request,
							 errmsg)
		!= XORP_OK) {
		XLOG_FATAL("Cannot expand program validation action %s "
			   "for module %s: %s",
			   _program_action.str().c_str(),
			   _module_name.c_str(),
			   errmsg.c_str());
	    }
	    break;
	} while (false);
	if (program_request.empty()) {
	    XLOG_FATAL("Cannot expand program validation action %s for "
		       "module %s",
		       _program_action.str().c_str(), _module_name.c_str());
	}

	// Expand the executable program name
	string executable_filename, program_arguments;
	find_executable_filename_and_arguments(program_request,
					       executable_filename,
					       program_arguments);
	program_request = executable_filename;
	if (! program_arguments.empty())
	    program_request = program_request + " " + program_arguments;
	if (executable_filename.empty()) {
	    XLOG_ERROR("Could not find program %s", program_request.c_str());
	    return;
	}

	// Run the program
	XLOG_TRACE(_verbose, "Validating with program: >%s<\n",
		   program_request.c_str());
	XLOG_ASSERT(_run_command == NULL);
	_run_command = new RunShellCommand(
	    eventloop(),
	    executable_filename,
	    program_arguments,
	    callback(this, &ProgramStatusValidation::stdout_cb),
	    callback(this, &ProgramStatusValidation::stderr_cb),
	    callback(this, &ProgramStatusValidation::done_cb));
	_run_command->set_exec_id(exec_id);
	if (_run_command->execute() != XORP_OK) {
	    delete _run_command;
	    _run_command = NULL;
	    XLOG_ERROR("Could not execute program %s",
		       program_request.c_str());
	    return;
	}
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, hence we schedule
	// a dummy callback as if the program was called.
	//
	_delay_timer = eventloop().new_oneoff_after(
	    TimeVal(0, 0),
	    callback(this, &ProgramStatusValidation::execute_done, true));
    }
}

void
ProgramStatusValidation::stdout_cb(RunShellCommand* run_command,
				   const string& output)
{
    XLOG_ASSERT(run_command == _run_command);
    _command_stdout += output;
}

void
ProgramStatusValidation::stderr_cb(RunShellCommand* run_command,
				   const string& output)
{
    XLOG_ASSERT(run_command == _run_command);
    _command_stderr += output;
}

void
ProgramStatusValidation::done_cb(RunShellCommand* run_command,
				 bool success,
				 const string& error_msg)
{
    XLOG_ASSERT(run_command == _run_command);

    if (! success)
	_command_stderr += error_msg;

    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }

    execute_done(success);
}

void
ProgramStatusValidation::execute_done(bool success)
{
    handle_status_response(success, _command_stdout, _command_stderr);
}


// ----------------------------------------------------------------------------
// XrlStatusStartupValidation implementation

XrlStatusStartupValidation::XrlStatusStartupValidation(
    const string& module_name,
    const XrlAction& xrl_action,
    TaskManager& taskmgr)
    : XrlStatusValidation(module_name, xrl_action, taskmgr)
{
}

void
XrlStatusStartupValidation::handle_status_response(ProcessStatus status,
						   const string& reason)
{
    switch (status) {
    case PROC_NULL:
	// This is not a valid responses.
	XLOG_ERROR("Bad status response; reason: %s", reason.c_str());
	_cb->dispatch(false);
	return;
    case PROC_FAILED:
    case PROC_SHUTDOWN:
    case PROC_DONE:
	_cb->dispatch(false);
	return;
    case PROC_STARTUP:
    case PROC_NOT_READY:
    case PROC_READY:
	// The process is ready to be activated
	_cb->dispatch(true);
	return;
    }
    XLOG_UNREACHABLE();
}


// ----------------------------------------------------------------------------
// ProgramStatusStartupValidation implementation

ProgramStatusStartupValidation::ProgramStatusStartupValidation(
    const string& module_name,
    const ProgramAction& program_action,
    TaskManager& taskmgr)
    : ProgramStatusValidation(module_name, program_action, taskmgr)
{
}

void
ProgramStatusStartupValidation::handle_status_response(
    bool success,
    const string& stdout_output,
    const string& stderr_output)
{
    if (! _cb.is_empty()) {
	_cb->dispatch(success);
    }

    UNUSED(stdout_output);
    UNUSED(stderr_output);
}


// ----------------------------------------------------------------------------
// XrlStatusReadyValidation implementation

XrlStatusReadyValidation::XrlStatusReadyValidation(
    const string& module_name,
    const XrlAction& xrl_action,
    TaskManager& taskmgr)
    : XrlStatusValidation(module_name, xrl_action, taskmgr)
{
}

void
XrlStatusReadyValidation::handle_status_response(ProcessStatus status,
						 const string& reason)
{
    switch (status) {
    case PROC_NULL:
	// This is not a valid response.
	XLOG_ERROR("Bad status response; reason: %s", reason.c_str());
	_cb->dispatch(false);
	return;
    case PROC_FAILED:
    case PROC_SHUTDOWN:
    case PROC_DONE:
	_cb->dispatch(false);
	return;
    case PROC_STARTUP:
    case PROC_NOT_READY:
	// Got a valid response saying we should wait.
	_retry_timer = eventloop().new_oneoff_after_ms(1000,
			callback((XrlStatusValidation*)this,
				 &XrlStatusValidation::validate,
				 _exec_id, _cb));
	return;
    case PROC_READY:
	// The process is ready
	_cb->dispatch(true);
	return;
    }
    XLOG_UNREACHABLE();
}


// ----------------------------------------------------------------------------
// ProgramStatusReadyValidation implementation

ProgramStatusReadyValidation::ProgramStatusReadyValidation(
    const string& module_name,
    const ProgramAction& program_action,
    TaskManager& taskmgr)
    : ProgramStatusValidation(module_name, program_action, taskmgr)
{
}

void
ProgramStatusReadyValidation::handle_status_response(
    bool success,
    const string& stdout_output,
    const string& stderr_output)
{
    if (! _cb.is_empty()) {
	_cb->dispatch(success);
    }

    UNUSED(stdout_output);
    UNUSED(stderr_output);
}


// ----------------------------------------------------------------------------
// XrlStatusConfigMeValidation implementation

XrlStatusConfigMeValidation::XrlStatusConfigMeValidation(
    const string& module_name,
    const XrlAction& xrl_action,
    TaskManager& taskmgr)
    : XrlStatusValidation(module_name, xrl_action, taskmgr)
{
}

void
XrlStatusConfigMeValidation::handle_status_response(ProcessStatus status,
						    const string& reason)
{
    switch (status) {
    case PROC_NULL:
	// This is not a valid responses.
	XLOG_ERROR("Bad status response; reason: %s", reason.c_str());
	_cb->dispatch(false);
	return;
    case PROC_FAILED:
    case PROC_SHUTDOWN:
    case PROC_DONE:
	_cb->dispatch(false);
	return;
    case PROC_STARTUP:
	// Got a valid response saying we should wait.
	_retry_timer = eventloop().new_oneoff_after_ms(1000,
			callback((XrlStatusValidation*)this,
				 &XrlStatusValidation::validate,
				 _exec_id, _cb));
	return;
    case PROC_NOT_READY:
    case PROC_READY:
	// The process is ready to be configured
	_cb->dispatch(true);
	return;
    }
    XLOG_UNREACHABLE();
}


// ----------------------------------------------------------------------------
// ProgramStatusConfigMeValidation implementation

ProgramStatusConfigMeValidation::ProgramStatusConfigMeValidation(
    const string& module_name,
    const ProgramAction& program_action,
    TaskManager& taskmgr)
    : ProgramStatusValidation(module_name, program_action, taskmgr)
{
}

void
ProgramStatusConfigMeValidation::handle_status_response(
    bool success,
    const string& stdout_output,
    const string& stderr_output)
{
    if (! _cb.is_empty()) {
	_cb->dispatch(success);
    }

    UNUSED(stdout_output);
    UNUSED(stderr_output);
}

// ----------------------------------------------------------------------------
// XrlStatusShutdownValidation implementation

XrlStatusShutdownValidation::XrlStatusShutdownValidation(
    const string& module_name,
    const XrlAction& xrl_action,
    TaskManager& taskmgr)
    : XrlStatusValidation(module_name, xrl_action, taskmgr)
{
}

void
XrlStatusShutdownValidation::handle_status_response(ProcessStatus status,
						    const string& reason)
{
    switch (status) {
    case PROC_NULL:
	// This is not a valid responses.
	XLOG_ERROR("Bad status response; reason: %s", reason.c_str());
	_cb->dispatch(false);
	return;
    case PROC_STARTUP:
    case PROC_NOT_READY:
    case PROC_READY:
    case PROC_FAILED:
	// The process should be in PROC_SHUTDOWN state, or it should not
	// be able to respond to Xrls because it's in NULL state.
	_cb->dispatch(false);
	return;
    case PROC_DONE:
	// The process has completed operation
	_cb->dispatch(true);
	return;
    case PROC_SHUTDOWN:
	// Got a valid response saying we should wait.
	_retry_timer = eventloop().new_oneoff_after_ms(1000,
			callback((XrlStatusValidation*)this,
				 &XrlStatusValidation::validate,
				 _exec_id, _cb));
	return;
    }
    XLOG_UNREACHABLE();
}

void
XrlStatusShutdownValidation::xrl_done(const XrlError& e, XrlArgs* xrl_args)
{
    switch (e.error_code()) {
    case OKAY:
	try {
	    ProcessStatus status;
	    status = static_cast<ProcessStatus>(xrl_args->get_uint32("status"));
	    string reason(xrl_args->get_string("reason"));
	    handle_status_response(status, reason);
	} catch (XrlArgs::XrlAtomNotFound) {
	    // Not a valid response
	    XLOG_ERROR("Bad XRL response to get_status");
	    _cb->dispatch(false);
	}
	break;

    case BAD_ARGS:
    case NO_SUCH_METHOD:
    case COMMAND_FAILED:
	//
	// The template file must have been wrong - the target doesn't
	// support the common interface.
	// Return false, and we can shut it down using kill.
	//
	XLOG_ERROR("Module %s doesn't support shutdown validation",
		   _module_name.c_str());
	_cb->dispatch(false);
	break;

    case RESOLVE_FAILED:
    case REPLY_TIMED_OUT:
    case SEND_FAILED:
    case SEND_FAILED_TRANSIENT:
	//
	// The process appears to have shutdown correctly or the process
	// is probably gone.
	//
	_cb->dispatch(true);
	break;

    case INTERNAL_ERROR:
	// Return false, and we can shut it down using kill.
	XLOG_ERROR("Error while shutdown validation of module %s",
		   _module_name.c_str());
	_cb->dispatch(false);
	break;

    case NO_FINDER:
	// We're in trouble now! This shouldn't be able to happen.
	XLOG_UNREACHABLE();
	break;
    }
}

// ----------------------------------------------------------------------------
// ProgramStatusShutdownValidation implementation

ProgramStatusShutdownValidation::ProgramStatusShutdownValidation(
    const string& module_name,
    const ProgramAction& program_action,
    TaskManager& taskmgr)
    : ProgramStatusValidation(module_name, program_action, taskmgr)
{
}

void
ProgramStatusShutdownValidation::handle_status_response(
    bool success,
    const string& stdout_output,
    const string& stderr_output)
{
    if (! _cb.is_empty()) {
	_cb->dispatch(success);
    }

    UNUSED(stdout_output);
    UNUSED(stderr_output);
}


// ----------------------------------------------------------------------------
// Startup implementation

Startup::Startup(const string& module_name, bool verbose)
    : _module_name(module_name),
      _verbose(verbose)
{
}


// ----------------------------------------------------------------------------
// XrlStartup implementation

XrlStartup::XrlStartup(const string& module_name,
		       const XrlAction& xrl_action,
		       TaskManager& taskmgr)
    : Startup(module_name, taskmgr.verbose()),
      _xrl_action(xrl_action),
      _task_manager(taskmgr)
{
}

EventLoop&
XrlStartup::eventloop() const
{
    return _task_manager.eventloop();
}

void
XrlStartup::startup(const RunShellCommand::ExecId& exec_id, CallBack cb)
{
    _cb = cb;
    if (_task_manager.do_exec()) {
	string xrl_request, errmsg;
	Xrl* xrl = NULL;
	do {
	    // Try to expand using the configuration tree
	    const MasterConfigTreeNode* ctn;
	    ctn = _task_manager.config_tree().find_config_module(_module_name);
	    if (ctn != NULL) {
		xrl = _xrl_action.expand_xrl_variables(*ctn, errmsg);
		if (xrl == NULL) {
		    XLOG_FATAL("Cannot expand XRL startup action %s "
			       "for module %s: %s",
			       _xrl_action.str().c_str(),
			       _module_name.c_str(),
			       errmsg.c_str());
		}
		break;
	    }

	    // Try to expand using the template tree
	    const TemplateTreeNode& ttn = _xrl_action.template_tree_node();
	    xrl = _xrl_action.expand_xrl_variables(ttn, errmsg);
	    if (xrl == NULL) {
		XLOG_FATAL("Cannot expand XRL startup action %s "
			   "for module %s: %s",
			   _xrl_action.str().c_str(),
			   _module_name.c_str(),
			   errmsg.c_str());
	    }
	    break;
	} while (false);
	if (xrl == NULL) {
	    XLOG_ERROR("Cannot expand XRL startup action %s for module %s",
		       _xrl_action.str().c_str(), _module_name.c_str());
	    return;
	}

	XLOG_TRACE(_verbose, "Startup with XRL: >%s<\n", xrl->str().c_str());

	string response = _xrl_action.xrl_return_spec();
	_task_manager.xorp_client().send_now(*xrl,
				callback(this, &XrlStartup::startup_done),
					     response, true);
	delete xrl;
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, but we want to ensure
	// that the xrl_done response gets the right arguments even
	// though we're not going to call the XRL.
	//
	XLOG_TRACE(_verbose, "XRL: dummy call to %s\n",
		   _xrl_action.request().c_str());
	_dummy_timer = eventloop().new_oneoff_after_ms(1000,
			callback(this, &XrlStartup::dummy_response));
    }

    UNUSED(exec_id);
}

void
XrlStartup::dummy_response()
{
    XrlError e = XrlError::OKAY();
    XrlArgs a;

    startup_done(e, &a);
}

void
XrlStartup::startup_done(const XrlError& err, XrlArgs* xrl_args)
{
    UNUSED(xrl_args);

    if (err == XrlError::OKAY()) {
	// Success
	_cb->dispatch(true);
    } else {
	// Failure
	_cb->dispatch(false);
    }
}


// ----------------------------------------------------------------------------
// ProgramStartup implementation

ProgramStartup::ProgramStartup(const string& module_name,
			       const ProgramAction& program_action,
			       TaskManager& taskmgr)
    : Startup(module_name, taskmgr.verbose()),
      _program_action(program_action),
      _task_manager(taskmgr),
      _run_command(NULL)
{
}

ProgramStartup::~ProgramStartup()
{
    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }
}

EventLoop&
ProgramStartup::eventloop() const
{
    return _task_manager.eventloop();
}

void
ProgramStartup::startup(const RunShellCommand::ExecId& exec_id, CallBack cb)
{
    _cb = cb;
    if (_task_manager.do_exec()) {
	string program_request, errmsg;
	do {
	    // Try to expand using the configuration tree
	    const MasterConfigTreeNode* ctn;
	    ctn = _task_manager.config_tree().find_config_module(_module_name);
	    if (ctn != NULL) {
		if (_program_action.expand_program_variables(*ctn,
							     program_request,
							     errmsg)
		    != XORP_OK) {
		    XLOG_FATAL("Cannot expand program startup action %s "
			       "for module %s: %s",
			       _program_action.str().c_str(),
			       _module_name.c_str(),
			       errmsg.c_str());
		}
		break;
	    }

	    // Try to expand using the template tree
	    const TemplateTreeNode& ttn = _program_action.template_tree_node();
	    if (_program_action.expand_program_variables(ttn, program_request,
							 errmsg)
		!= XORP_OK) {
		XLOG_FATAL("Cannot expand program startup action %s "
			   "for module %s: %s",
			   _program_action.str().c_str(),
			   _module_name.c_str(),
			   errmsg.c_str());
	    }
	    break;
	} while (false);
	if (program_request.empty()) {
	    XLOG_ERROR("Cannot expand program startup action %s for "
		       "module %s",
		       _program_action.str().c_str(), _module_name.c_str());
	    return;
	}

	// Expand the executable program name
	string executable_filename, program_arguments;
	find_executable_filename_and_arguments(program_request,
					       executable_filename,
					       program_arguments);
	program_request = executable_filename;
	if (! program_arguments.empty())
	    program_request = program_request + " " + program_arguments;
	if (executable_filename.empty()) {
	    XLOG_ERROR("Could not find program %s", program_request.c_str());
	    return;
	}

	// Run the program
	XLOG_TRACE(_verbose, "Startup with program: >%s<\n",
		   program_request.c_str());
	XLOG_ASSERT(_run_command == NULL);
	_run_command = new RunShellCommand(
	    eventloop(),
	    executable_filename,
	    program_arguments,
	    callback(this, &ProgramStartup::stdout_cb),
	    callback(this, &ProgramStartup::stderr_cb),
	    callback(this, &ProgramStartup::done_cb));
	_run_command->set_exec_id(exec_id);
	if (_run_command->execute() != XORP_OK) {
	    delete _run_command;
	    _run_command = NULL;
	    XLOG_ERROR("Could not execute program %s",
		       program_request.c_str());
	    return;
	}
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, hence we schedule
	// a dummy callback as if the program was called.
	//
	XLOG_TRACE(_verbose, "Program: dummy call to %s\n",
		   _program_action.request().c_str());
	_delay_timer = eventloop().new_oneoff_after(
	    TimeVal(0, 0),
	    callback(this, &ProgramStartup::execute_done, true));
    }
}

void
ProgramStartup::stdout_cb(RunShellCommand* run_command,
			  const string& output)
{
    XLOG_ASSERT(run_command == _run_command);
    _command_stdout += output;
}

void
ProgramStartup::stderr_cb(RunShellCommand* run_command,
			  const string& output)
{
    XLOG_ASSERT(run_command == _run_command);
    _command_stderr += output;
}

void
ProgramStartup::done_cb(RunShellCommand* run_command,
			bool success,
			const string& error_msg)
{
    XLOG_ASSERT(run_command == _run_command);

    if (! success)
	_command_stderr += error_msg;

    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }

    execute_done(success);
}

void
ProgramStartup::execute_done(bool success)
{
    if (! _cb.is_empty()) {
	_cb->dispatch(success);
    }
}

// ----------------------------------------------------------------------------
// Shutdown implementation

Shutdown::Shutdown(const string& module_name, bool verbose)
    : _module_name(module_name),
      _verbose(verbose)
{
}


// ----------------------------------------------------------------------------
// XrlShutdown implementation

XrlShutdown::XrlShutdown(const string& module_name,
			 const XrlAction& xrl_action,
			 TaskManager& taskmgr)
    : Shutdown(module_name, taskmgr.verbose()),
      _xrl_action(xrl_action),
      _task_manager(taskmgr)
{
}

EventLoop&
XrlShutdown::eventloop() const
{
    return _task_manager.eventloop();
}

void
XrlShutdown::shutdown(const RunShellCommand::ExecId& exec_id, CallBack cb)
{
    if (! _task_manager.is_verification())
	XLOG_INFO("Shutting down module: %s\n", _module_name.c_str());

    _cb = cb;
    if (_task_manager.do_exec()) {
	string xrl_request, errmsg;
	Xrl* xrl = NULL;
	do {
	    // Try to expand using the configuration tree
	    const MasterConfigTreeNode* ctn;
	    ctn = _task_manager.config_tree().find_config_module(_module_name);
	    if (ctn != NULL) {
		xrl = _xrl_action.expand_xrl_variables(*ctn, errmsg);
		if (xrl == NULL) {
		    XLOG_FATAL("Cannot expand XRL shutdown action %s "
			       "for module %s: %s",
			       _xrl_action.str().c_str(),
			       _module_name.c_str(),
			       errmsg.c_str());
		}
		break;
	    }

	    // Try to expand using the template tree
	    const TemplateTreeNode& ttn = _xrl_action.template_tree_node();
	    xrl = _xrl_action.expand_xrl_variables(ttn, errmsg);
	    if (xrl == NULL) {
		XLOG_FATAL("Cannot expand XRL shutdown action %s "
			   "for module %s: %s",
			   _xrl_action.str().c_str(),
			   _module_name.c_str(),
			   errmsg.c_str());
	    }
	    break;
	} while (false);
	if (xrl == NULL) {
	    XLOG_ERROR("Cannot expand XRL shutdown action %s for module %s",
		       _xrl_action.str().c_str(), _module_name.c_str());
	    return;
	}

	XLOG_TRACE(_verbose, "Shutdown with XRL: >%s<\n", xrl->str().c_str());

	string response = _xrl_action.xrl_return_spec();
	_task_manager.xorp_client().send_now(*xrl,
				callback(this, &XrlShutdown::shutdown_done),
					     response, true);
	delete xrl;
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, but we want to ensure
	// that the xrl_done response gets the right arguments even
	// though we're not going to call the XRL.
	//
	XLOG_TRACE(_verbose, "XRL: dummy call to %s\n",
		   _xrl_action.request().c_str());
	_dummy_timer = eventloop().new_oneoff_after_ms(1000,
			callback(this, &XrlShutdown::dummy_response));
    }

    UNUSED(exec_id);
}

void
XrlShutdown::dummy_response()
{
    XrlError e = XrlError::OKAY();
    XrlArgs a;

    shutdown_done(e, &a);
}

void
XrlShutdown::shutdown_done(const XrlError& err, XrlArgs* xrl_args)
{
    switch (err.error_code()) {
    case OKAY:
    case RESOLVE_FAILED:
    case SEND_FAILED:
    case REPLY_TIMED_OUT:
    case SEND_FAILED_TRANSIENT:
	// Success - either it said it would shutdown, or it's already gone.
	_cb->dispatch(true);
	break;

    case BAD_ARGS:
    case COMMAND_FAILED:
    case NO_SUCH_METHOD:
    case INTERNAL_ERROR:	// TODO: should be XLOG_UNREACHABLE()?
	// We may have to kill it.
	_cb->dispatch(false);
	break;

    case NO_FINDER:
	XLOG_UNREACHABLE();
	break;
    }

    UNUSED(xrl_args);
}


// ----------------------------------------------------------------------------
// ProgramShutdown implementation

ProgramShutdown::ProgramShutdown(const string& module_name,
				 const ProgramAction& program_action,
				 TaskManager& taskmgr)
    : Shutdown(module_name, taskmgr.verbose()),
      _program_action(program_action),
      _task_manager(taskmgr),
      _run_command(NULL)
{
}

ProgramShutdown::~ProgramShutdown()
{
    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }
}

EventLoop&
ProgramShutdown::eventloop() const
{
    return _task_manager.eventloop();
}

void
ProgramShutdown::shutdown(const RunShellCommand::ExecId& exec_id, CallBack cb)
{
    if (! _task_manager.is_verification())
	XLOG_INFO("Shutting down module: %s\n", _module_name.c_str());

    _cb = cb;
    if (_task_manager.do_exec()) {
	string program_request, errmsg;
	do {
	    // Try to expand using the configuration tree
	    const MasterConfigTreeNode* ctn;
	    ctn = _task_manager.config_tree().find_config_module(_module_name);
	    if (ctn != NULL) {
		if (_program_action.expand_program_variables(*ctn,
							     program_request,
							     errmsg)
		    != XORP_OK) {
		    XLOG_FATAL("Cannot expand program shutdown action %s "
			       "for module %s: %s",
			       _program_action.str().c_str(),
			       _module_name.c_str(),
			       errmsg.c_str());
		}
		break;
	    }

	    // Try to expand using the template tree
	    const TemplateTreeNode& ttn = _program_action.template_tree_node();
	    if (_program_action.expand_program_variables(ttn, program_request,
							 errmsg)
		!= XORP_OK) {
		XLOG_FATAL("Cannot expand program shutdown action %s "
			   "for module %s: %s",
			   _program_action.str().c_str(),
			   _module_name.c_str(),
			   errmsg.c_str());
	    }
	    break;
	} while (false);
	if (program_request.empty()) {
	    XLOG_ERROR("Cannot expand program shutdown action %s for "
		       "module %s",
		       _program_action.str().c_str(), _module_name.c_str());
	    return;
	}

	// Expand the executable program name
	string executable_filename, program_arguments;
	find_executable_filename_and_arguments(program_request,
					       executable_filename,
					       program_arguments);
	program_request = executable_filename;
	if (! program_arguments.empty())
	    program_request = program_request + " " + program_arguments;
	if (executable_filename.empty()) {
	    XLOG_ERROR("Could not find program %s", program_request.c_str());
	    return;
	}

	// Run the program
	XLOG_TRACE(_verbose, "Shutdown with program: >%s<\n",
		   program_request.c_str());
	XLOG_ASSERT(_run_command == NULL);
	_run_command = new RunShellCommand(
	    eventloop(),
	    executable_filename,
	    program_arguments,
	    callback(this, &ProgramShutdown::stdout_cb),
	    callback(this, &ProgramShutdown::stderr_cb),
	    callback(this, &ProgramShutdown::done_cb));
	_run_command->set_exec_id(exec_id);
	if (_run_command->execute() != XORP_OK) {
	    delete _run_command;
	    _run_command = NULL;
	    XLOG_ERROR("Could not execute program %s",
		       program_request.c_str());
	    return;
	}
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, hence we schedule
	// a dummy callback as if the program was called.
	//
	XLOG_TRACE(_verbose, "Program: dummy call to %s\n",
		   _program_action.request().c_str());
	_delay_timer = eventloop().new_oneoff_after(
	    TimeVal(0, 0),
	    callback(this, &ProgramShutdown::execute_done, true));
    }
}

void
ProgramShutdown::stdout_cb(RunShellCommand* run_command,
			   const string& output)
{
    XLOG_ASSERT(run_command == _run_command);
    _command_stdout += output;
}

void
ProgramShutdown::stderr_cb(RunShellCommand* run_command,
			   const string& output)
{
    XLOG_ASSERT(run_command == _run_command);
    _command_stderr += output;
}

void
ProgramShutdown::done_cb(RunShellCommand* run_command,
			 bool success,
			 const string& error_msg)
{
    XLOG_ASSERT(run_command == _run_command);

    if (! success)
	_command_stderr += error_msg;

    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }

    execute_done(success);
}

void
ProgramShutdown::execute_done(bool success)
{
    if (! _cb.is_empty()) {
	_cb->dispatch(success);
    }
}


// ----------------------------------------------------------------------------
// TaskXrlItem implementation

const uint32_t	TaskXrlItem::DEFAULT_RESEND_COUNT = 10;
const int	TaskXrlItem::DEFAULT_RESEND_DELAY_MS = 1000;

TaskXrlItem::TaskXrlItem(const UnexpandedXrl& uxrl,
			 const XrlRouter::XrlCallback& cb,
			 Task& task,
			 uint32_t xrl_resend_count,
			 int xrl_resend_delay_ms)
    : TaskBaseItem(task),
      _unexpanded_xrl(uxrl),
      _xrl_callback(cb),
      _xrl_resend_count_limit(xrl_resend_count),
      _xrl_resend_count(_xrl_resend_count_limit),
      _xrl_resend_delay_ms(xrl_resend_delay_ms),
      _verbose(task.verbose())
{
}

TaskXrlItem::TaskXrlItem(const TaskXrlItem& them)
    : TaskBaseItem(them),
      _unexpanded_xrl(them._unexpanded_xrl),
      _xrl_callback(them._xrl_callback),
      _xrl_resend_count_limit(them._xrl_resend_count_limit),
      _xrl_resend_count(_xrl_resend_count_limit),
      _xrl_resend_delay_ms(them._xrl_resend_delay_ms),
      _verbose(them._verbose)
{
}

bool
TaskXrlItem::execute(string& errmsg)
{
    if (task().do_exec())
	XLOG_TRACE(_verbose, "Expanding %s\n", _unexpanded_xrl.str().c_str());

    Xrl* xrl = _unexpanded_xrl.expand(errmsg);
    if (xrl == NULL) {
	errmsg = c_format("Failed to expand XRL %s: %s",
			  _unexpanded_xrl.str().c_str(), errmsg.c_str());
	return false;
    }
    if (task().do_exec())
	XLOG_TRACE(_verbose, "Executing XRL: >%s<\n", xrl->str().c_str());

    string xrl_return_spec = _unexpanded_xrl.return_spec();

    _xrl_resend_count = _xrl_resend_count_limit;
    task().xorp_client().send_now(*xrl,
				  callback(this, &TaskXrlItem::execute_done),
				  xrl_return_spec,
				  task().do_exec());
    return true;
}

void
TaskXrlItem::unschedule()
{
    XrlArgs xrl_args;	    // Empty XRL args should be OK here.

    debug_msg("TaskXrlItem::unschedule()\n");

    //
    // We need to dispatch the callbacks, or the accounting of which
    // actions were taken will be incorrect.
    //
    _xrl_callback->dispatch(XrlError::OKAY(), &xrl_args);
}

void
TaskXrlItem::resend()
{
    string errmsg;

    Xrl* xrl = _unexpanded_xrl.expand(errmsg);
    if (xrl == NULL) {
	// This can't happen in a resend, because we already succeeded
	// in the orginal send.
	XLOG_UNREACHABLE();
    }

    string xrl_return_spec = _unexpanded_xrl.return_spec();

    task().xorp_client().send_now(*xrl,
				  callback(this, &TaskXrlItem::execute_done),
				  xrl_return_spec,
				  task().do_exec());
}

void
TaskXrlItem::execute_done(const XrlError& err, XrlArgs* xrl_args)
{
    bool fatal = false;

    debug_msg("TaskXrlItem::execute_done\n");

    switch (err.error_code()) {
    case OKAY:
	break;

    case BAD_ARGS:
    case COMMAND_FAILED:
	// Non-fatal error for the target - typically this is
	// because the user entered bad information - we can't
	// change the target configuration to what the user
	// specified, but this doesn't mean the target is fatally
	// wounded.
	fatal = false;
	break;

    case NO_FINDER:
    case SEND_FAILED:
	// The error was a fatal one for the target - we now
	// consider the target to be fatally wounded.
	XLOG_ERROR("%s", err.str().c_str());
	fatal = true;
	break;

    case REPLY_TIMED_OUT:
    case RESOLVE_FAILED:
    case SEND_FAILED_TRANSIENT:
	// REPLY_TIMED_OUT shouldn't happen on a reliable
	// transport, but at this point we don't know for sure
	// which Xrls are reliable and while are unreliable, so
	// best handle this as if it were packet loss.
	//
	// RESOLVE_FAILED shouldn't happen if the startup
	// validation has done it's job correctly, but just in case
	// we'll be lenient and give it ten more seconds to be
	// functioning before we declare it dead.
	if (--_xrl_resend_count > 0) {
	    // Re-send the Xrl after a short delay.
	    _xrl_resend_timer = task().eventloop().new_oneoff_after_ms(
		_xrl_resend_delay_ms,
		callback(this, &TaskXrlItem::resend));
	    return;
	} else {
	    // Give up.
	    // The error was a fatal one for the target - we now
	    // consider the target to be fatally wounded.
	    XLOG_ERROR("%s", err.str().c_str());
	    fatal = true;
	}
	break;

    case INTERNAL_ERROR:
    case NO_SUCH_METHOD:
	// Something bad happened but it's not clear what.  Don't
	// consider these to be fatal errors, but they may well
	// prove to be so.  XXX revisit this issue when we've more
	// experience with XRL errors.
	XLOG_ERROR("%s", err.str().c_str());
	fatal = false;
	break;
    }

    if (! _xrl_callback.is_empty())
	_xrl_callback->dispatch(err, xrl_args);

    bool success = true;
    string errmsg;
    if (err != XrlError::OKAY()) {
	success = false;
	errmsg = err.str();
    }
    task().item_done(success, fatal, errmsg);
}


// ----------------------------------------------------------------------------
// TaskProgramItem implementation

TaskProgramItem::TaskProgramItem(const UnexpandedProgram&	program,
				 TaskProgramItem::ProgramCallback program_cb,
				 Task&				task)
    : TaskBaseItem(task),
      _unexpanded_program(program),
      _run_command(NULL),
      _program_cb(program_cb),
      _verbose(task.verbose())
{
}

TaskProgramItem::TaskProgramItem(const TaskProgramItem& them)
    : TaskBaseItem(them),
      _unexpanded_program(them._unexpanded_program),
      _run_command(NULL),
      _program_cb(them._program_cb),
      _verbose(them._verbose)
{
}

TaskProgramItem::~TaskProgramItem()
{
    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }
}

bool
TaskProgramItem::execute(string& errmsg)
{
    const RunShellCommand::ExecId& exec_id = task().exec_id();

    if (task().do_exec())
	XLOG_TRACE(_verbose, "Expanding %s\n",
		   _unexpanded_program.str().c_str());

    string program_request = _unexpanded_program.expand(errmsg);
    if (program_request.empty()) {
	errmsg = c_format("Failed to expand program %s: %s",
			  _unexpanded_program.str().c_str(), errmsg.c_str());
	return false;
    }

    if (_run_command != NULL)
	return (true);		// XXX: already running

    if (task().do_exec()) {
	// Expand the executable program name
	string executable_filename, program_arguments;
	find_executable_filename_and_arguments(program_request,
					       executable_filename,
					       program_arguments);
	if (executable_filename.empty()) {
	    errmsg = c_format("Could not find program %s",
			      program_request.c_str());
	    return (false);
	}
	program_request = executable_filename;
	if (! program_arguments.empty())
	    program_request = program_request + " " + program_arguments;
	XLOG_TRACE(_verbose, "Executing program: >%s<\n",
		   program_request.c_str());
	_run_command = new RunShellCommand(
	    task().eventloop(),
	    executable_filename,
	    program_arguments,
	    callback(this, &TaskProgramItem::stdout_cb),
	    callback(this, &TaskProgramItem::stderr_cb),
	    callback(this, &TaskProgramItem::done_cb));
	_run_command->set_exec_id(exec_id);
	if (_run_command->execute() != XORP_OK) {
	    delete _run_command;
	    _run_command = NULL;
	    errmsg = c_format("Could not execute program %s",
			      program_request.c_str());
	    return (false);
	}
    } else {
	//
	// When we're running with do_exec == false, we want to
	// exercise most of the same machinery, hence we schedule
	// a dummy callback as if the program was called.
	//
	_delay_timer = task().eventloop().new_oneoff_after(
	    TimeVal(0, 0),
	    callback(this, &TaskProgramItem::execute_done, true));
    }

    return (true);
}

void
TaskProgramItem::unschedule()
{
    debug_msg("TaskProgramItem::unschedule()\n");

    if (_run_command != NULL)
	_run_command->terminate();

    //
    // We need to dispatch the callbacks, or the accounting of which
    // actions were taken will be incorrect.
    //
    string error_msg = c_format("The execution of program %s is terminated",
				_unexpanded_program.str().c_str());
    if (! _program_cb.is_empty()) {
	_program_cb->dispatch(false, _command_stdout, error_msg,
			      task().do_exec());
    }

    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }
}

void
TaskProgramItem::stdout_cb(RunShellCommand* run_command, const string& output)
{
    debug_msg("TaskProgramItem::stdout_cb\n");

    XLOG_ASSERT(run_command == _run_command);
    _command_stdout += output;
}

void
TaskProgramItem::stderr_cb(RunShellCommand* run_command, const string& output)
{
    debug_msg("TaskProgramItem::stderr_cb\n");

    XLOG_ASSERT(run_command == _run_command);
    _command_stderr += output;
}

void
TaskProgramItem::done_cb(RunShellCommand* run_command,
			 bool success,
			 const string& error_msg)
{
    debug_msg("TaskProgramItem::done_cb\n");

    XLOG_ASSERT(run_command == _run_command);

    if (! success)
	_command_stderr += error_msg;

    if (_run_command != NULL) {
	delete _run_command;
	_run_command = NULL;
    }

    execute_done(success);
}

void
TaskProgramItem::execute_done(bool success)
{
    bool fatal = false;

    debug_msg("TaskProgramItem::execute_done\n");

    if (! _program_cb.is_empty()) {
	_program_cb->dispatch(success, _command_stdout, _command_stderr,
			      task().do_exec());
    }

    task().item_done(success, fatal, _command_stderr);
}


// ----------------------------------------------------------------------------
// Task implementation

Task::Task(const string& name, TaskManager& taskmgr)
    : _name(name),
      _taskmgr(taskmgr),
      _start_module(false),
      _stop_module(false),
      _startup_validation(NULL),
      _config_validation(NULL),
      _ready_validation(NULL),
      _shutdown_validation(NULL),
      _startup_method(NULL),
      _shutdown_method(NULL),
      _config_done(false),
      _exec_id(taskmgr.exec_id()),
      _verbose(taskmgr.verbose())
{
}

Task::~Task()
{
    if (_startup_validation != NULL)
	delete _startup_validation;
    if (_config_validation != NULL)
	delete _config_validation;
    if (_ready_validation != NULL)
	delete _ready_validation;
    if (_shutdown_validation != NULL)
	delete _shutdown_validation;
    if (_startup_method != NULL)
	delete _startup_method;
    if (_shutdown_method != NULL)
	delete _shutdown_method;

    delete_pointers_list(_task_items);
}

void
Task::start_module(const string& module_name,
		   Validation* startup_validation,
		   Validation* config_validation,
		   Startup* startup)
{
    XLOG_ASSERT(! module_name.empty());
    XLOG_ASSERT(_start_module == false);
    XLOG_ASSERT(_stop_module == false);
    XLOG_ASSERT(_startup_validation == NULL);
    XLOG_ASSERT(_config_validation == NULL);
    XLOG_ASSERT(_startup_method == NULL);

    _start_module = true;
    _module_name = module_name;
    _startup_validation = startup_validation;
    _config_validation = config_validation;
    _startup_method = startup;
}

void
Task::shutdown_module(const string& module_name, Validation* validation,
		      Shutdown* shutdown)
{
    XLOG_ASSERT(! module_name.empty());
    XLOG_ASSERT(_start_module == false);
    XLOG_ASSERT(_stop_module == false);
    XLOG_ASSERT(_shutdown_validation == NULL);
    XLOG_ASSERT(_shutdown_method == NULL);

    _stop_module = true;
    _module_name = module_name;
    _shutdown_validation = validation;
    _shutdown_method = shutdown;
}

void
Task::add_xrl(const UnexpandedXrl& xrl, XrlRouter::XrlCallback& cb)
{
    _task_items.push_back(new TaskXrlItem(xrl, cb, *this));
}

void
Task::add_program(const UnexpandedProgram&		program,
		  TaskProgramItem::ProgramCallback	program_cb)
{
    _task_items.push_back(new TaskProgramItem(program, program_cb, *this));
}

void
Task::set_ready_validation(Validation* validation)
{
    _ready_validation = validation;
}

void
Task::run(CallBack cb)
{
    debug_msg("Task::run (%s)\n", _module_name.c_str());

    _task_complete_cb = cb;
    step1_start();
}

void
Task::step1_start()
{
    debug_msg("step1 (%s)\n", _module_name.c_str());

    if (_start_module) {
	_taskmgr.module_manager().start_module(_module_name, do_exec(),
					       is_verification(),
					       callback(this, &Task::step1_done));
    } else {
	step2_wait();
    }
}

void
Task::step1_done(bool success)
{
    debug_msg("step1_done (%s)\n", _module_name.c_str());

    if (success)
	step2_wait();
    else
	task_fail("Can't start process " + _module_name, false);
}

void
Task::step2_wait()
{
    debug_msg("step2 (%s)\n", _module_name.c_str());

    if (_start_module && (_startup_validation != NULL)) {
	_startup_validation->validate(_exec_id,
				      callback(this, &Task::step2_done));
    } else {
	step2_2_wait();
    }
}

void
Task::step2_done(bool success)
{
    debug_msg("step2_done (%s)\n", _module_name.c_str());

    if (success)
	step2_2_wait();
    else
	task_fail("Can't validate start of process " + _module_name, true);
}

void
Task::step2_2_wait()
{
    debug_msg("step2_2 (%s)\n", _module_name.c_str());

    if (_start_module && (_startup_method != NULL)) {
	_startup_method->startup(_exec_id,
				 callback(this, &Task::step2_2_done));
    } else {
	step2_3_wait();
    }
}

void
Task::step2_2_done(bool success)
{
    debug_msg("step2_2_done (%s)\n", _module_name.c_str());

    if (success)
	step2_3_wait();
    else
	task_fail("Can't startup process " + _module_name, true);
}

void
Task::step2_3_wait()
{
    debug_msg("step2_3 (%s)\n", _module_name.c_str());

    if (_start_module && (_config_validation != NULL)) {
	_config_validation->validate(_exec_id,
				     callback(this, &Task::step2_3_done));
    } else {
	step3_config();
    }
}

void
Task::step2_3_done(bool success)
{
    debug_msg("step2_3_done (%s)\n", _module_name.c_str());

    if (success)
	step3_config();
    else
	task_fail("Can't validate config ready of process " + _module_name,
		  true);
}

void
Task::step3_config()
{
    debug_msg("step3 (%s)\n", _module_name.c_str());

    if (_task_items.empty()) {
	step4_wait();
    } else {
	if (_stop_module) {
	    //
	    // We don't call any task items on a module if we are going to
	    // shut it down immediately afterwards, but we do need to
	    // unschedule the task items.
	    //
	    while (! _task_items.empty()) {
		TaskBaseItem* task_base_item = _task_items.front();
		task_base_item->unschedule();
		delete task_base_item;
		_task_items.pop_front();
	    }
	    // Skip step4 and go directly to stopping the process
	    step5_stop();
	} else {
	    string errmsg;
	    debug_msg("step3: execute\n");

	    if (_task_items.front()->execute(errmsg) == false) {
		XLOG_WARNING("Failed to execute task item: %s",
			     errmsg.c_str());
		task_fail(errmsg, false);
		return;
	    }
	}
    }
}

void
Task::item_done(bool success, bool fatal, string errmsg)
{
    debug_msg("item_done (%s)\n", _module_name.c_str());

    if (success) {
	TaskBaseItem* task_base_item = _task_items.front();
	_task_items.pop_front();
	delete task_base_item;
	_config_done = true;
	step3_config();
    } else {
	task_fail(errmsg, fatal);
    }
}

void
Task::step4_wait()
{
    debug_msg("step4 (%s)\n", _module_name.c_str());

    if (_ready_validation && _config_done) {
	_ready_validation->validate(_exec_id,
				    callback(this, &Task::step4_done));
    } else {
	step5_stop();
    }
}

void
Task::step4_done(bool success)
{
    debug_msg("step4_done (%s)\n", _module_name.c_str());

    if (success) {
	step5_stop();
    } else {
	task_fail("Reconfig of process " + _module_name +
		  " caused process to fail.", true);
    }
}

void
Task::step5_stop()
{
    debug_msg("step5 (%s)\n", _module_name.c_str());

    if (_stop_module) {
	if (_shutdown_method != NULL) {
	    _shutdown_method->shutdown(_exec_id,
				       callback(this, &Task::step5_done));
	} else {
	    step6_wait();
	}
    } else {
	step8_report();
    }
}

void
Task::step5_done(bool success)
{
    debug_msg("step5_done (%s)\n", _module_name.c_str());

    if (success) {
	step6_wait();
    } else {
	XLOG_WARNING(("Can't subtly stop process " + _module_name).c_str());
	_taskmgr.module_manager().kill_module(_module_name,
					callback(this, &Task::step6_wait));
    }
}

void
Task::step6_wait()
{
    debug_msg("step6 (%s)\n", _module_name.c_str());

    if (_stop_module && (_shutdown_validation != NULL)) {
	_shutdown_validation->validate(_exec_id,
				       callback(this, &Task::step6_done));
    } else {
	step8_report();
    }
}

void
Task::step6_done(bool success)
{
    debug_msg("step6_done (%s)\n", _module_name.c_str());

    if (success) {
	step7_wait();
    } else {
	string msg = "Can't validate stop of process " + _module_name;
	XLOG_WARNING("%s", msg.c_str());
	// An error here isn't fatal - module manager will simply kill
	// the process less subtly.
	step7_kill();
    }
}

void
Task::step7_wait()
{
    _wait_timer = _taskmgr.eventloop().new_oneoff_after_ms(1000,
					callback(this, &Task::step7_kill));
}

void
Task::step7_kill()
{
    _taskmgr.module_manager().kill_module(_module_name,
					  callback(this, &Task::step8_report));
}

void
Task::step8_report()
{
    debug_msg("step8 (%s)\n", _module_name.c_str());

    debug_msg("Task done\n");

    _task_complete_cb->dispatch(true, "");
}

void
Task::task_fail(string errmsg, bool fatal)
{
    debug_msg("%s\n", errmsg.c_str());

    if (fatal && !_module_name.empty()) {
	XLOG_ERROR("Shutting down fatally wounded process (%s)",
		   _module_name.c_str());
	_taskmgr.kill_process(_module_name);
    }
    _task_complete_cb->dispatch(false, errmsg);
}

bool
Task::do_exec() const
{
    return _taskmgr.do_exec();
}

bool
Task::is_verification() const
{
    return _taskmgr.is_verification();
}

XorpClient&
Task::xorp_client() const
{
    return _taskmgr.xorp_client();
}

EventLoop&
Task::eventloop() const
{
    return _taskmgr.eventloop();
}


// ----------------------------------------------------------------------------
// TaskManager implementation

TaskManager::TaskManager(MasterConfigTree& config_tree, ModuleManager& mmgr,
			 XorpClient& xclient, bool global_do_exec,
			 bool verbose)
    : _config_tree(config_tree),
      _module_manager(mmgr),
      _xorp_client(xclient),
      _global_do_exec(global_do_exec),
      _is_verification(false),
      _verbose(verbose)
{
}

TaskManager::~TaskManager()
{
    reset();
}

void
TaskManager::set_do_exec(bool do_exec, bool is_verification)
{
    _current_do_exec = do_exec && _global_do_exec;
    _is_verification = is_verification;
}

void
TaskManager::reset()
{
    while (! _tasks.empty()) {
	delete _tasks.begin()->second;
	_tasks.erase(_tasks.begin());
    }
    _shutdown_order.clear();
    _tasklist.clear();
    _exec_id.reset();
}

int
TaskManager::add_module(const ModuleCommand& module_command, string& error_msg)
{
    string module_name = module_command.module_name();
    string module_exec_path = module_command.module_exec_path();

    if (_tasks.find(module_name) == _tasks.end()) {
	Task* newtask = new Task(module_name, *this);
	_tasks[module_name] = newtask;
	_tasklist.push_back(newtask);
    }

    if (_module_manager.module_exists(module_name)) {
	if (_module_manager.module_has_started(module_name)) {
	    return XORP_OK;
	}
    } else {
	if (_module_manager.new_module(module_name, module_exec_path,
				       error_msg)
	    != true) {
	    error_msg = c_format("Cannot create module %s: %s",
				 module_name.c_str(), error_msg.c_str());
	    fail_tasklist_initialization(error_msg);
	    return XORP_ERROR;
	}
    }

    Validation* startup_validation = module_command.startup_validation(*this);
    Validation* config_validation = module_command.config_validation(*this);
    Startup* startup_method = module_command.startup_method(*this);
    find_task(module_name).start_module(module_name, startup_validation,
					config_validation, startup_method);

    _module_commands[module_name] = &module_command;
    return XORP_OK;
}

void
TaskManager::add_xrl(const string& module_name, const UnexpandedXrl& xrl,
		     XrlRouter::XrlCallback& cb)
{
    Task& t(find_task(module_name));
    t.add_xrl(xrl, cb);

    if (t.ready_validation() != NULL)
	return;

    XLOG_ASSERT(_module_commands.find(module_name) != _module_commands.end());
    t.set_ready_validation(_module_commands[module_name]->ready_validation(*this));
}

void
TaskManager::add_program(const string&			module_name,
			 const UnexpandedProgram&	program,
			 TaskProgramItem::ProgramCallback program_cb)
{
    Task& t(find_task(module_name));
    t.add_program(program, program_cb);

    if (t.ready_validation() != NULL)
	return;

    XLOG_ASSERT(_module_commands.find(module_name) != _module_commands.end());
    t.set_ready_validation(_module_commands[module_name]->ready_validation(*this));
}

void
TaskManager::shutdown_module(const string& module_name)
{
    debug_msg("shutdown_module: %s\n", module_name.c_str());

    Task& t(find_task(module_name));

    map<string, const ModuleCommand*>::iterator iter;
    iter = _module_commands.find(module_name);
    XLOG_ASSERT(iter != _module_commands.end());
    const ModuleCommand* mc = iter->second;
    t.shutdown_module(module_name, mc->shutdown_validation(*this),
		      mc->shutdown_method(*this));
    _shutdown_order.push_front(&t);
}

void
TaskManager::run(CallBack cb)
{
    list<Task*>::iterator iter;

    debug_msg("TaskManager::run, tasks (old order): ");

    if (_verbose) {
	string debug_output;
	for (iter = _tasklist.begin(); iter != _tasklist.end(); ++iter) {
	    debug_output += c_format("%s ", (*iter)->name().c_str());
	}
	debug_output += "\n";
	debug_msg("%s", debug_output.c_str());
    }

    reorder_tasks();

    //
    // Set the execution ID of the tasks
    //
    for (iter = _tasklist.begin(); iter != _tasklist.end(); ++iter) {
	Task* task = *iter;
	task->set_exec_id(exec_id());
    }

    debug_msg("TaskManager::run, tasks: ");
    if (_verbose) {
	string debug_output;
	for (iter = _tasklist.begin(); iter != _tasklist.end(); ++iter) {
	    debug_output += c_format("%s ", (*iter)->name().c_str());
	}
	debug_output += "\n";
	debug_msg("%s", debug_output.c_str());
    }

    _completion_cb = cb;
    run_task();
}

void
TaskManager::reorder_tasks()
{
    // We re-order the task list so that process shutdowns (which are
    // irreversable) occur last.
    list<Task*> configs;
    while (! _tasklist.empty()) {
	Task* t = _tasklist.front();
	if (t->will_shutdown_module()) {
	    // We already have a list of the correct order to shutdown
	    // modules in _shutdown_order
	} else {
	    configs.push_back(t);
	}
	_tasklist.pop_front();
    }

    // Re-build the tasklist
    while (! configs.empty()) {
	_tasklist.push_back(configs.front());
	configs.pop_front();
    }

    list<Task*>::const_iterator iter;
    for (iter = _shutdown_order.begin();
	 iter != _shutdown_order.end();
	 ++iter) {
	_tasklist.push_back(*iter);
    }
}

void
TaskManager::run_task()
{
    debug_msg("TaskManager::run_task()\n");

    if (_tasklist.empty()) {
	if (! is_verification())
	    XLOG_INFO("No more tasks to run\n");
	_completion_cb->dispatch(true, "");
	return;
    }
    _tasklist.front()->run(callback(this, &TaskManager::task_done));
}

void
TaskManager::task_done(bool success, string errmsg)
{
    debug_msg("TaskManager::task_done\n");

    if (! success) {
	debug_msg("task failed\n");
	_completion_cb->dispatch(false, errmsg);
	reset();
	return;
    }
    _tasklist.pop_front();
    run_task();
}

void
TaskManager::fail_tasklist_initialization(const string& errmsg)
{
    XLOG_ERROR("%s", errmsg.c_str());
    reset();
    return;
}

Task&
TaskManager::find_task(const string& module_name)
{
    map<string, Task*>::iterator iter;
    iter = _tasks.find(module_name);
    if (iter == _tasks.end()) {
	// The task didn't exist, so we create one.  This is only valid
	// if we've already started the module.
	XLOG_ASSERT(_module_commands.find(module_name)
		    != _module_commands.end());
	const ModuleCommand* module_command = _module_commands[module_name];
	string module_name = module_command->module_name();
	string module_exec_path = module_command->module_exec_path();
	if (_tasks.find(module_name) == _tasks.end()) {
	    Task* newtask = new Task(module_name, *this);
	    _tasks[module_name] = newtask;
	    _tasklist.push_back(newtask);
	}
	iter = _tasks.find(module_name);
    }
    XLOG_ASSERT(iter != _tasks.end());
    XLOG_ASSERT(iter->second != NULL);
    return *(iter->second);
}

void
TaskManager::kill_process(const string& module_name)
{
    // XXX We really should try to restart the failed process, but for
    // now we'll just kill it.
    _module_manager.kill_module(module_name,
				callback(this, &TaskManager::null_callback));
}

EventLoop&
TaskManager::eventloop() const
{
    return _module_manager.eventloop();
}

void
TaskManager::null_callback()
{

}


syntax highlighted by Code2HTML, v. 0.9.1