// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// vim:set sts=4 ts=8:

// 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/master_conf_tree_node.cc,v 1.25 2007/02/16 22:47:22 pavlin Exp $"

#include "rtrmgr_module.h"

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

#include "command_tree.hh"
#include "master_conf_tree_node.hh"
#include "module_command.hh"
#include "template_commands.hh"
#include "template_tree_node.hh"
#include "util.hh"


extern int booterror(const char *s) throw (ParseError);

MasterConfigTreeNode::MasterConfigTreeNode(bool verbose)
    : ConfigTreeNode(verbose),
      _actions_pending(0),
      _actions_succeeded(true),
      _cmd_that_failed(NULL)
{

}

MasterConfigTreeNode::MasterConfigTreeNode(const string& nodename,
					   const string& path, 
					   const TemplateTreeNode* ttn,
					   MasterConfigTreeNode* parent,
					   const ConfigNodeId& node_id,
					   uid_t user_id,
					   bool verbose)
    : ConfigTreeNode(nodename, path, ttn, parent, node_id, user_id, 
		     /* clientid */ 0, verbose),
      _actions_pending(0),
      _actions_succeeded(true),
      _cmd_that_failed(NULL)
{
}

MasterConfigTreeNode::MasterConfigTreeNode(const MasterConfigTreeNode& ctn)
    : ConfigTreeNode(ctn),
      _actions_pending(0),
      _actions_succeeded(true),
      _cmd_that_failed(NULL)
{
}

ConfigTreeNode*
MasterConfigTreeNode::create_node(const string& segment, const string& path,
				  const TemplateTreeNode* ttn, 
				  ConfigTreeNode* parent_node, 
				  const ConfigNodeId& node_id,
				  uid_t user_id,
				  uint32_t clientid,
				  bool verbose)
{
    UNUSED(clientid);
    MasterConfigTreeNode *new_node, *parent;
    parent = dynamic_cast<MasterConfigTreeNode *>(parent_node);

    // sanity check - all nodes in this tree should be Master nodes
    if (parent_node != NULL)
	XLOG_ASSERT(parent != NULL);

    new_node = new MasterConfigTreeNode(segment, path, ttn, parent, 
					node_id, user_id, verbose);
    return reinterpret_cast<ConfigTreeNode*>(new_node);
}

ConfigTreeNode*
MasterConfigTreeNode::create_node(const ConfigTreeNode& ctn) {
    MasterConfigTreeNode *new_node;
    const MasterConfigTreeNode *orig;

    // sanity check - all nodes in this tree should be Master nodes
    orig = dynamic_cast<const MasterConfigTreeNode *>(&ctn);
    XLOG_ASSERT(orig != NULL);

    new_node = new MasterConfigTreeNode(*orig);
    return new_node;
}


void
MasterConfigTreeNode::command_status_callback(const Command* cmd, bool success)
{
    debug_msg("command_status_callback node %s cmd %s\n",
	      _segname.c_str(), cmd->str().c_str());
    debug_msg("actions_pending = %d\n", _actions_pending);

    XLOG_ASSERT(_actions_pending > 0);

    if (_actions_succeeded && (success == false)) {
	_actions_succeeded = false;
	_cmd_that_failed = cmd;
    }
    _actions_pending--;
}


void
MasterConfigTreeNode::find_changed_modules(set<string>& changed_modules) const
{
    if (_template_tree_node != NULL) {
	// XXX: ignore deprecated subtrees
	if (_template_tree_node->is_deprecated())
	    return;
    }

    if ((_template_tree_node != NULL)
	&& (!_existence_committed || !_value_committed)) {
	const BaseCommand *base_cmd = NULL;
	const Command *cmd = NULL;
	set<string> modules;
	set<string>::const_iterator iter;

	if (_deleted) {
	    base_cmd = _template_tree_node->const_command("%delete");
	    if (base_cmd == NULL) {
		// No need to go to children
		return;
	    }
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();

	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		changed_modules.insert(*iter);
	    return;
	}

	if (!_existence_committed) {
	    if (! _template_tree_node->module_name().empty())
		changed_modules.insert(_template_tree_node->module_name());
	    base_cmd = _template_tree_node->const_command("%create");
	    if (base_cmd != NULL) {
		cmd = reinterpret_cast<const Command*>(base_cmd);
		modules = cmd->affected_modules();
		for (iter = modules.begin(); iter != modules.end(); ++iter)
		    changed_modules.insert(*iter);
	    }
	    base_cmd = _template_tree_node->const_command("%activate");
	    if (base_cmd != NULL) {
		cmd = reinterpret_cast<const Command*>(base_cmd);
		modules = cmd->affected_modules();
		for (iter = modules.begin(); iter != modules.end(); ++iter)
		    changed_modules.insert(*iter);
	    }
	} else if (!_value_committed) {
	    base_cmd = _template_tree_node->const_command("%set");
	    if (base_cmd != NULL) {
		cmd = reinterpret_cast<const Command*>(base_cmd);
		modules = cmd->affected_modules();
		for (iter = modules.begin(); iter != modules.end(); ++iter)
		    changed_modules.insert(*iter);
	    }
	    base_cmd = _template_tree_node->const_command("%update");
	    if (base_cmd != NULL) {
		cmd = reinterpret_cast<const Command*>(base_cmd);
		modules = cmd->affected_modules();
		for (iter = modules.begin(); iter != modules.end(); ++iter)
		    changed_modules.insert(*iter);
	    }
	}
    }

    list<ConfigTreeNode*>::const_iterator li;
    for (li = _children.begin(); li != _children.end(); ++li) {
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*li);
	child->find_changed_modules(changed_modules);
    }
}

void
MasterConfigTreeNode::find_active_modules(set<string>& active_modules) const
{
    if (_template_tree_node != NULL) {
	// XXX: ignore deprecated subtrees
	if (_template_tree_node->is_deprecated())
	    return;
    }

    if (_deleted) {
	// XXX: ignore deleted subtrees
	return;
    }

    if (_template_tree_node != NULL) {
	const BaseCommand *base_cmd;
	const Command *cmd;
	set<string> modules;
	set<string>::const_iterator iter;

	//
	// XXX: If the module's top-level node is not deleted explicitly, then
	// the module is considered still active.
	//
	if (_template_tree_node->is_module_root_node())
	    active_modules.insert(_template_tree_node->module_name());

	base_cmd = _template_tree_node->const_command("%create");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		active_modules.insert(*iter);
	}
	base_cmd = _template_tree_node->const_command("%activate");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		active_modules.insert(*iter);
	}
	base_cmd = _template_tree_node->const_command("%update");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		active_modules.insert(*iter);
	}
	base_cmd = _template_tree_node->const_command("%set");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		active_modules.insert(*iter);
	}
    }

    //
    // Process the subtree
    //
    list<ConfigTreeNode*>::const_iterator li;
    for (li = _children.begin(); li != _children.end(); ++li) {
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*li);
	child->find_active_modules(active_modules);
    }
}

void
MasterConfigTreeNode::find_all_modules(set<string>& all_modules) const
{
    if (_template_tree_node != NULL) {
	// XXX: ignore deprecated subtrees
	if (_template_tree_node->is_deprecated())
	    return;
    }

    if (_template_tree_node != NULL) {
	const BaseCommand *base_cmd = NULL;
	const Command *cmd = NULL;
	set<string> modules;
	set<string>::const_iterator iter;

        //
        // XXX: If the module's top-level node is in the configuration, then
        // the module is added to the list.
        //
        if (_template_tree_node->is_module_root_node())
            all_modules.insert(_template_tree_node->module_name());

	base_cmd = _template_tree_node->const_command("%create");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		all_modules.insert(*iter);
	}
	base_cmd = _template_tree_node->const_command("%activate");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		all_modules.insert(*iter);
	}
	base_cmd = _template_tree_node->const_command("%update");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		all_modules.insert(*iter);
	}
	base_cmd = _template_tree_node->const_command("%set");
	if (base_cmd != NULL) {
	    cmd = reinterpret_cast<const Command*>(base_cmd);
	    modules = cmd->affected_modules();
	    for (iter = modules.begin(); iter != modules.end(); ++iter)
		all_modules.insert(*iter);
	}
    }
    list<ConfigTreeNode*>::const_iterator li;
    for (li = _children.begin(); li != _children.end(); ++li) {
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*li);
	child->find_all_modules(all_modules);
    }
}

void 
MasterConfigTreeNode::initialize_commit()
{
    _actions_pending = 0;
    _actions_succeeded = true;
    _cmd_that_failed = NULL;

    list<ConfigTreeNode *>::iterator iter;
    for (iter = _children.begin(); iter != _children.end(); ++iter) {
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*iter);
	child->initialize_commit();
    }
}

bool
MasterConfigTreeNode::children_changed()
{
    if (_existence_committed == false || _value_committed == false)
	return true;
    list<ConfigTreeNode *>::iterator iter;
    for (iter = _children.begin(); iter != _children.end(); ++iter) {
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*iter);
	if (child->children_changed()) {
	    return true;
	}
    }
    return false;
}

bool
MasterConfigTreeNode::commit_changes(TaskManager& task_manager,
				     bool do_commit,
				     int depth, int last_depth,
				     string& error_msg,
				     bool& needs_activate,
				     bool& needs_update)
{
    bool success = true;
    const BaseCommand *base_cmd = NULL;
    const Command *cmd = NULL;
    bool changes_made = false;

    debug_msg("*****COMMIT CHANGES node >%s< >%s<\n",
	      _path.c_str(), _value.c_str());
    if (do_commit)
	debug_msg("do_commit\n");
    if (_existence_committed == false)
	debug_msg("_existence_committed == false\n");
    if (_value_committed == false)
	debug_msg("_value_committed == false\n");

    if (_template_tree_node != NULL) {
	// XXX: ignore deprecated subtrees
	if (_template_tree_node->is_deprecated())
	    return success;
    }

    // Don't bother to recurse if no child node has any changes to commit.
    // Calling this every time is rather inefficient, but for the
    // scales we're talking about, it's not a big deal.
    if (children_changed() == false) {
	debug_msg("No children changed\n");
	return success;
    } else {
	debug_msg("Children have changed\n");
    }


    // The root node has a NULL template
    if (_template_tree_node != NULL) {
	// Do we have to start any modules to implement this functionality
	base_cmd = _template_tree_node->const_command("%modinfo");
	const ModuleCommand* modcmd 
	    = dynamic_cast<const ModuleCommand*>(base_cmd);
	if (modcmd != NULL) {
	    if (modcmd->start_transaction(*this, task_manager) != XORP_OK) {
		error_msg = c_format("Start Transaction failed for "
				     "module %s\n",
				     modcmd->module_name().c_str());
		return false;
	    }
	}
	if ((_existence_committed == false || _value_committed == false)) {
	    debug_msg("we have changes to handle\n");
	    // First handle deletions
	    if (_deleted) {
		if (do_commit == false) {
		    // No point in checking further
		    return true;
		} else {
		    //
		    // We expect a deleted node here to have previously
		    // had its existence committed.
		    // (_existence_committed == true) but its
		    // non-existence not previously committed
		    // (_value_committed == false)
		    //
		    XLOG_ASSERT(_existence_committed);
		    XLOG_ASSERT(!_value_committed);  
		    base_cmd = _template_tree_node->const_command("%delete");
		    if (base_cmd != NULL) {
			cmd = reinterpret_cast<const Command*>(base_cmd);
			int actions = cmd->execute(*this, task_manager);
			if (actions < 0) {
			    // Bad stuff happenned
			    // XXX now what?
			    error_msg = "Something went wrong.\n";
			    error_msg += c_format("The problem was with \"%s\"\n",
						  path().c_str());
			    error_msg += "WARNING: Partially commited changes exist\n";
			    XLOG_WARNING("%s\n", error_msg.c_str());
			    return false;
			}
			_actions_pending += actions;
			// No need to go on to delete children
			return true;
		    }
		}
	    } else {
		// Check any %allow commands that might prevent us
		// going any further
		base_cmd = _template_tree_node->const_command("%allow");
		if (base_cmd == NULL) {
		    // Try allow-range
		    base_cmd 
			= _template_tree_node->const_command("%allow-range");
		}
		if (base_cmd != NULL) {
		    const AllowCommand* allow_cmd;
		    debug_msg("found ALLOW command: %s\n",
			      cmd->str().c_str());
		    allow_cmd = dynamic_cast<const AllowCommand*>(base_cmd);
		    XLOG_ASSERT(allow_cmd != NULL);
		    if (allow_cmd->verify_variables(*this, error_msg)
			!= true) {
			//
			// Commit_changes should always be run first
			// with do_commit not set, so there can be no
			// Allow command errors.
			//
			// XLOG_ASSERT(do_commit == false);
			error_msg = c_format("Bad value for \"%s\": %s; ",
					     path().c_str(),
					     error_msg.c_str());
			error_msg += "No changes have been committed. ";
			error_msg += "Correct this error and try again.";
			XLOG_WARNING("%s\n", error_msg.c_str());
			return false;
		    }
		}
		// Check that the operator is OK
		base_cmd = 
		    _template_tree_node->const_command("%allow-operator");
		if (base_cmd == NULL) {
		    /* no explicit command, so only ":" is allowed */
		    if (_operator != OP_NONE && _operator != OP_ASSIGN) {
			error_msg = c_format("Bad operator for \"%s\": "
					     "operator %s was specified, "
					     "only ':' is allowed\n",
					     path().c_str(), 
					     operator_to_str(_operator).c_str());
			error_msg += "No changes have been committed. ";
			error_msg += "Correct this error and try again.";
			XLOG_WARNING("%s\n", error_msg.c_str());
			return false;
		    }
		} else {
		    const AllowCommand* allow_cmd;
		    debug_msg("found ALLOW command: %s\n",
			      cmd->str().c_str());
		    allow_cmd = dynamic_cast<const AllowCommand*>(base_cmd);
		    XLOG_ASSERT(allow_cmd != NULL);
		    if (allow_cmd->verify_variables(*this, error_msg)
			!= true) {
			error_msg = c_format("Bad operator for \"%s\": %s; ",
					     path().c_str(),
					     error_msg.c_str());
			error_msg += "No changes have been committed. ";
			error_msg += "Correct this error and try again.";
			XLOG_WARNING("%s\n", error_msg.c_str());
			return false;
		    }
		}

		//
		// Next, we run any "create" or "set" commands.
		// Note that if there is no "create" command, then we run
		// the "set" command instead.
		//
		base_cmd = NULL;
		if (_existence_committed == false)
		    base_cmd = _template_tree_node->const_command("%create");
		if (base_cmd == NULL)
		    base_cmd = _template_tree_node->const_command("%set");
		if (base_cmd == NULL) {
		    debug_msg("no appropriate command found\n");
		} else {
		    cmd = reinterpret_cast<const Command*>(base_cmd);
		    debug_msg("found commands: %s\n",
			      cmd->str().c_str());
		    int actions = cmd->execute(*this, task_manager);
		    if (actions < 0) {
			error_msg = c_format("Parameter error for \"%s\"\n",
					     path().c_str());
			error_msg += "No changes have been committed.\n";
			error_msg += "Correct this error and try again.\n";
			XLOG_WARNING("%s\n", error_msg.c_str());
			return false;
		    }
		    _actions_pending += actions;
		}
	    }
	}
    }

    list<ConfigTreeNode *> sorted_children = _children;
    sort_by_template(sorted_children);

    list<ConfigTreeNode *>::iterator iter, prev_iter;
    iter = sorted_children.begin();
    if (changes_made)
	last_depth = depth;
    while (iter != sorted_children.end()) {
	prev_iter = iter;
	++iter;
	debug_msg("  child: %s\n", (*prev_iter)->path().c_str());
	string child_error_msg;
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*prev_iter);
	success = child->commit_changes(task_manager, do_commit,
					depth + 1, last_depth, 
					child_error_msg,
					needs_activate,
					needs_update);
	error_msg += child_error_msg;
	if (success == false) {
	    return false;
	}
    }

    //
    // Take care of %activate and %update commands on the way back out.
    //
    do {
	if (_deleted)
	    break;
	if (_template_tree_node == NULL)
	    break;

	// The %activate command
	if (needs_activate || (_existence_committed == false)) {
	    base_cmd = _template_tree_node->const_command("%activate");
	    if (base_cmd == NULL) {
		if (_existence_committed == false)
		    needs_activate = true;
	    } else {
		cmd = reinterpret_cast<const Command*>(base_cmd);
		debug_msg("found commands: %s\n", cmd->str().c_str());
		needs_activate = false;
		int actions = cmd->execute(*this, task_manager);
		if (actions < 0) {
		    error_msg = c_format("Parameter error for \"%s\"\n",
					 path().c_str());
		    error_msg += "No changes have been committed.\n";
		    error_msg += "Correct this error and try again.\n";
		    XLOG_WARNING("%s\n", error_msg.c_str());
		    return false;
		}
		_actions_pending += actions;
	    }
	    break;
	}

	// The %update command
	if (needs_update || (_value_committed == false)) {
	    base_cmd = _template_tree_node->const_command("%update");
	    if (base_cmd == NULL) {
		if (_value_committed == false)
		    needs_update = true;
	    } else {
		cmd = reinterpret_cast<const Command*>(base_cmd);
		debug_msg("found commands: %s\n", cmd->str().c_str());
		needs_update = false;
		int actions = cmd->execute(*this, task_manager);
		if (actions < 0) {
		    error_msg = c_format("Parameter error for \"%s\"\n",
					 path().c_str());
		    error_msg += "No changes have been committed.\n";
		    error_msg += "Correct this error and try again.\n";
		    XLOG_WARNING("%s\n", error_msg.c_str());
		    return false;
		}
		_actions_pending += actions;
	    }
	    break;
	}

	break;
    } while (false);

    if (_template_tree_node != NULL) {
	base_cmd = _template_tree_node->const_command("%modinfo");
	if (base_cmd != NULL) {
	    const ModuleCommand* modcmd
		= dynamic_cast<const ModuleCommand*>(base_cmd);
	    if (modcmd != NULL) {
		if (modcmd->end_transaction(*this, task_manager) != XORP_OK) {
		    error_msg = c_format("End Transaction failed for module %s\n",
					 modcmd->module_name().c_str());
		    return false;
		}
	    }
	}
    }

    debug_msg("Result: %s\n", error_msg.c_str());
    debug_msg("COMMIT, leaving node >%s<\n", _path.c_str());
    debug_msg("final node %s actions_pending = %d\n",
	      _segname.c_str(), _actions_pending);

    return success;
}

bool 
MasterConfigTreeNode::check_commit_status(string& error_msg) const
{
    debug_msg("ConfigTreeNode::check_commit_status %s\n",
	      _segname.c_str());

    if ((_existence_committed == false) || (_value_committed == false)) {
	XLOG_ASSERT(_actions_pending == 0);
	if (_actions_succeeded == false) {
	    error_msg = "WARNING: Commit Failed\n";
	    error_msg += c_format("  Error in %s command for %s\n",
				  _cmd_that_failed->str().c_str(),
				  _path.c_str());
	    error_msg += c_format("  State may be partially committed - "
				  "suggest reverting to previous state\n");
	    return false;
	}
	if (_deleted) {
	    const BaseCommand* cmd 
		= _template_tree_node->const_command("%delete");
	    if (cmd != NULL) {
		//
		// No need to check the children if we succeeded in
		// deleting this node.
		//
		return true;
	    }
	}
    }

    list<ConfigTreeNode *>::const_iterator iter;
    bool result = true;
    for (iter = _children.begin(); iter != _children.end(); ++iter) {
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*iter);
	debug_msg("  child: %s\n", child->path().c_str());
	result = child->check_commit_status(error_msg);
	if (result == false)
	    return false;
    }

    return result;
}

void 
MasterConfigTreeNode::finalize_commit()
{
    debug_msg("MasterConfigTreeNode::finalize_commit %s\n",
	      _segname.c_str());

    if (_deleted) {
	debug_msg("node deleted\n");

	//
	// Delete the entire subtree. We expect a deleted node here
	// to have previously had its existence committed
	// (_existence_committed == true) but its non-existence not
	// previously committed (_value_committed == false)
	//
	XLOG_ASSERT(_existence_committed);
	XLOG_ASSERT(!_value_committed);  
	delete_subtree_silently();
	// No point in going further
	return;
    }
    if ((_existence_committed == false) || (_value_committed == false)) {
	debug_msg("node finalized\n");

	XLOG_ASSERT(_actions_pending == 0);
	XLOG_ASSERT(_actions_succeeded);
	_existence_committed = true;
	_value_committed = true;
	_deleted = false;
	_committed_value = _value;
	_committed_operator = _operator;
	_committed_user_id = _user_id;
	_committed_modification_time = _modification_time;
    }

    //
    // Note: finalize_commit may delete the child, so we need to be
    // careful the iterator stays valid.
    //
    list<ConfigTreeNode *>::iterator iter, prev_iter;
    iter = _children.begin(); 
    while (iter != _children.end()) {
	prev_iter = iter;
	++iter;
	MasterConfigTreeNode *child = (MasterConfigTreeNode*)(*prev_iter);
	child->finalize_commit();
    }
}




syntax highlighted by Code2HTML, v. 0.9.1