servlib/cmd/LinkCommand.cc
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlib/cmd/LinkCommand.cc	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,544 @@
+/*
+ *	Copyright 2004-2006 Intel Corporation
+ * 
+ *	Licensed under the Apache License, Version 2.0 (the "License");
+ *	you may not use this file except in compliance with the License.
+ *	You may obtain a copy of the License at
+ * 
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *	Unless required by applicable law or agreed to in writing, software
+ *	distributed under the License is distributed on an "AS IS" BASIS,
+ *	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *	See the License for the specific language governing permissions and
+ *	limitations under the License.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <dtn-config.h>
+#endif
+
+#include <oasys/thread/Lock.h>
+#include <oasys/util/StringBuffer.h>
+
+#include "LinkCommand.h"
+#include "bundling/BundleEvent.h"
+#include "bundling/BundleDaemon.h"
+#include "contacts/Link.h"
+#include "contacts/ContactManager.h"
+#include "conv_layers/ConvergenceLayer.h"
+#include "naming/Scheme.h"
+
+namespace dtn {
+
+LinkCommand::LinkCommand()
+	: TclCommand("link")
+{
+	add_to_help("add <name> <next hop> <type> <conv layer> <opt=val> <opt2=val2>...",
+			"add links"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n" 
+	"			A string to define link name\n"
+	"		<next hop>\n"
+	"			A string to define next hop name "
+	"if CLA type is file is a filesystem, if is eth is an Ethernet "
+	"HW Address or if is tcp or udp is an IP:PORT)\n"
+	"		<conv layer>\n"
+	"			The CLA type\n"
+	"				    udp\n"
+	"				    tcp\n"
+	"				    eth\n"
+	"				    bt\n"
+	"				    serial\n"
+	"				    null\n"
+	"				    file\n"
+	"				    extcl\n"
+	"		<type>\n"
+	"			link type options\n" 
+	"				    ALLWAYSON\n" 
+	"   				    ONDEMAND\n" 
+	"				    SCHEDULED\n" 
+	"				    OPPORTUNISTIC\n" 
+	"		<opt>:\n"
+	"			mtu <number (MTU)>\n"
+	"			min_retry_interval <number (seconds)>\n"
+	"			max_retry_interval <number (seconds)>\n"
+	"			idle_close_time <number (seconds)>\n"
+	"			potential_downtime <number (seconds)>\n"
+	"			prevhop_hdr <true or false>\n"
+	"			cost <number (abstract cost)>\n"
+	"			qlimit_bundles_high <number (bundles)>\n"
+	"			qlimit_bytes_high <number (bytes)>\n"
+	"			qlimit_bundles_low <number (bundles)>\n"
+	"			qlimit_bytes_low <number (bytes)>\n"
+	"			retry_interval <number (seconds)>\n");
+	add_to_help("open <name>", "open the link"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("close <name>", "close the link"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("delete <name>", "delete the link"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("set_available <name> <true | false>", 
+				"hacky way to make the link available"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("state <name>", "return the state of a link"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("stats <name>", "dump link statistics"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("names", "print a list of existing link names"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("dump <name>", "print the list of existing links or "
+				"detailed info about a single link"
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n");
+	add_to_help("reconfigure <name> <opt=val> <opt2=val2>...",
+			"configure link options after initialization "
+			"\n"
+	"	valid options:\n"
+	"		<name>\n"
+	"			A link name string\n"
+	"		<opt>:\n"
+	"			mtu <number (MTU)>\n"
+	"			min_retry_interval <number (seconds)>\n"
+	"			max_retry_interval <number (seconds)>\n"
+	"			idle_close_time <number (seconds)>\n"
+	"			potential_downtime <number (seconds)>\n"
+	"			prevhop_hdr <true or false>\n"
+	"			cost <number (abstract cost)>\n"
+	"			qlimit_bundles_high <number (bundles)>\n"
+	"			qlimit_bytes_high <number (bytes)>\n"
+	"			qlimit_bundles_low <number (bundles)>\n"
+	"			qlimit_bytes_low <number (bytes)>\n"
+	"			retry_interval <number (seconds)>\n");
+	add_to_help("set_cl_defaults <conv layer> <opt=val> <opt2=val2>...",
+				"configure convergence layer specific default options"
+				"\n"
+	"	valid options:\n"
+	"		<conv layer>\n"
+	"			The CLA type\n"
+	"				   udp\n"
+	"				   tcp\n"
+	"				   eth\n"
+	"				   bt\n"
+	"				   serial\n"
+	"				   null\n"
+	"				   file\n"
+	"				   extcl\n"
+	"		<opt>:\n"
+	"			mtu <number (MTU)>\n"
+	"			min_retry_interval <number (seconds)>\n"
+	"			max_retry_interval <number (seconds)>\n"
+	"			idle_close_time <number (seconds)>\n"
+	"			potential_downtime <number (seconds)>\n"
+	"			prevhop_hdr <true or false>\n"
+	"			cost <number (abstract cost)>\n"
+	"			qlimit_bundles_high <number (bundles)>\n"
+	"			qlimit_bytes_high <number (bytes)>\n"
+	"			qlimit_bundles_low <number (bundles)>\n"
+	"			qlimit_bytes_low <number (bytes)>\n"
+	"			retry_interval <number (seconds)>\n\n");
+}
+/*	"	**Note options for add, reconfigure and set_cl_defaults:\n\n"
+	"	mtu is the MTU of the link, used to control\n "
+	"	proactive fragmentation.\n\n"
+	"	min_retry_interval is the minimum amount to \n"
+	"	wait between attempts to re-open the link \n"
+	"	in seconds). Default is set by the various\n "
+	"	link types but can be overridden by \n"
+	"	configuration parameters.\n\n"
+	"	max_retry_interval is the Maximum amount to \n"
+	"	wait between attempts to re-open the link (in\n "
+	"	seconds). Default is set by the various Link \n"
+	"	types but can be overridden by configuration \n"
+	"	parameters.\n\n"
+	"	idle_close_time is seconds of idle time \n"
+	"	before the link is closed. Must be zero for \n"
+	"	always on links (i.e. they are never closed). \n"
+	"	Default is 30 seconds for on demand links, \n"
+	"	zero for opportunistic links.\n\n"
+	"	potential_downtime is a Conservative estimate \n"
+	"	of the maximum amount of time that the link may \n"
+	"	be down during 'normal' operation. Used by \n"
+	"	routing algorithms to determine how long to leave \n"
+	"	bundles queued on the down link before rerouting \n"
+	"	them. Default is 30 seconds.\n\n"
+	"	prevhop_hdr indicates whether or not to send \n"
+	"	the previous hop header on this link. Default is \n"
+	"	false.\n\n"
+	"	cost is the abstract cost of the link, used \n"
+	"	by routing algorithms. Default is 100.\n\n"
+	"	qlimit_bundles_high is the high limits on the \n"
+	"	number of bundles that should be queued on the \n"
+	"	link. The high limits are used to indicate whether \n"
+	"	or not more bundles can be queued onto the link to \n"
+	"	effect backpressure from the convergence layers.\n\n"
+	"	qlimit_bytes_high and qlimit_bytes_low is the \n"
+	"	high or low limits on the number of bytes that \n"
+	"	should be queued on the link. The high limits \n"
+	"	are used to indicate whether or not more bundles \n"
+	"	can be queued onto the link to effect backpressure \n"
+	"	from the convergence layers. The low limits can \n"
+	"	be used by the router to determine when to re-scan \n"
+	"	the pending bundle lists\n\n"
+	"	retry_interval is the seconds to wait between \n"
+	"	attempts to re-open an unavailable link. Initially \n"
+	"	set to min_retry_interval_, then doubles up to \n"
+	"	max_retry_interval");
+*/
+
+int
+LinkCommand::exec(int argc, const char** argv, Tcl_Interp* interp)
+{
+	(void)interp;
+	
+	if (argc < 2) {
+		resultf("need a link subcommand");
+		return TCL_ERROR;
+	}
+
+	const char* cmd = argv[1];
+
+	if (strcmp(cmd, "add") == 0) {
+		// link add <name> <nexthop> <type> <clayer> <args>
+		if (argc < 6) {
+			wrong_num_args(argc, argv, 2, 6, INT_MAX);
+			return TCL_ERROR;
+		}
+		
+		const char* name = argv[2];
+		const char* nexthop = argv[3];
+		const char* type_str = argv[4];
+		const char* cl_str = argv[5];
+
+		// validate the link type, make sure there's no link of the
+		// same name, and validate the convergence layer
+		Link::link_type_t type = Link::str_to_link_type(type_str);
+		if (type == Link::LINK_INVALID) {
+			resultf("invalid link type %s", type_str);
+			return TCL_ERROR;
+		}
+
+		ConvergenceLayer* cl = ConvergenceLayer::find_clayer(cl_str);
+		if (!cl) {
+			resultf("invalid convergence layer %s", cl_str);
+			return TCL_ERROR;
+		}
+
+		// Create the link, parsing the cl-specific next hop string
+		// and other arguments
+		LinkRef link;
+		const char* invalid_arg = "(unknown)";
+		link = Link::create_link(name, type, cl, nexthop, argc - 6, &argv[6],
+								 &invalid_arg);
+		if (link == NULL) {
+			resultf("invalid link option: %s", invalid_arg);
+			return TCL_ERROR;
+		}
+
+		// Add the link to contact manager's table if it is not already
+		// present. The contact manager will post a LinkCreatedEvent to
+		// the daemon if the link is added successfully.
+		if (!BundleDaemon::instance()->contactmgr()->add_new_link(link)) {
+			// A link of that name already exists
+			link->delete_link();
+			resultf("link name %s already exists, use different name", name);
+			return TCL_ERROR;
+		}
+		return TCL_OK;
+
+	} else if (strcmp(cmd, "reconfigure") == 0) {
+		// link set_option <name> <opt=val> <opt2=val2>...
+		if (argc < 4) {
+			wrong_num_args(argc, argv, 2, 4, INT_MAX);
+			return TCL_ERROR;
+		}
+
+		const char* name = argv[2];
+		
+		LinkRef link = BundleDaemon::instance()->contactmgr()->find_link(name);
+		if (link == NULL) {
+			resultf("link %s doesn't exist", name);
+			return TCL_ERROR;
+		} 
+
+		argc -= 3;
+		argv += 3;
+
+		const char* invalid;
+		int count = link->parse_args(argc, argv, &invalid);
+		if (count == -1) {
+			resultf("invalid link option: %s", invalid);
+			return TCL_ERROR;
+		}
+		argc -= count;
+		
+		if (!link->reconfigure_link(argc, argv)) {
+			return TCL_ERROR;
+		}
+		
+		return TCL_OK;
+		
+	} else if (strcmp(cmd, "open") == 0) {
+		// link open <name>
+		if (argc != 3) {
+			wrong_num_args(argc, argv, 2, 3, 3);
+			return TCL_ERROR;
+		}
+
+		const char* name = argv[2];
+		
+		LinkRef link = BundleDaemon::instance()->contactmgr()->find_link(name);
+		if (link == NULL) {
+			resultf("link %s doesn't exist", name);
+			return TCL_ERROR;
+		}
+
+		if (link->isopen()) {
+			resultf("link %s already open", name);
+			return TCL_OK;
+		}
+
+		// XXX/TODO should change all these to post_and_wait
+		BundleDaemon::post(new LinkStateChangeRequest(link, Link::OPEN,
+													  ContactEvent::USER));
+		
+	} else if (strcmp(cmd, "close") == 0) {
+		// link close <name>
+		if (argc != 3) {
+			wrong_num_args(argc, argv, 2, 3, 3);
+			return TCL_ERROR;
+		}
+
+		const char* name = argv[2];
+		
+		LinkRef link = BundleDaemon::instance()->contactmgr()->find_link(name);
+		if (link == NULL) {
+			resultf("link %s doesn't exist", name);
+			return TCL_ERROR;
+		}
+
+		if (! link->isopen() && ! link->isopening()) {
+			resultf("link %s already closed", name);
+			return TCL_OK;
+		}
+
+		BundleDaemon::instance()->post(
+			new LinkStateChangeRequest(link, Link::CLOSED,
+									   ContactEvent::USER));
+
+		return TCL_OK;
+
+	} else if (strcmp(cmd, "delete") == 0) {
+		// link delete <name>
+		if (argc != 3) {
+			wrong_num_args(argc, argv, 2, 3, 3);
+			return TCL_ERROR;
+		}
+
+		const char * name = argv[2];
+
+		LinkRef link = BundleDaemon::instance()->contactmgr()->find_link(name);
+		if (link == NULL) {
+			resultf("link %s doesn't exist", name);
+			return TCL_ERROR;
+		}
+
+		BundleDaemon::instance()->post(new LinkDeleteRequest(link));
+		return TCL_OK;
+
+	} else if (strcmp(cmd, "set_available") == 0) {
+		// link set_available <name> <true|false>
+		if (argc != 4) {
+			wrong_num_args(argc, argv, 2, 4, 4);
+			return TCL_ERROR;
+		}
+
+		const char* name = argv[2];
+
+		LinkRef link = BundleDaemon::instance()->contactmgr()->find_link(name);
+		if (link == NULL) {
+			resultf("link %s doesn't exist", name);
+			return TCL_ERROR;
+		}
+
+		int len = strlen(argv[3]);
+		bool set_available;
+
+		if (strncmp(argv[3], "1", len) == 0) {
+			set_available = true;
+		} else if (strncmp(argv[3], "0", len) == 0) {
+			set_available = false;
+		} else if (strncasecmp(argv[3], "true", len) == 0) {
+			set_available = true;
+		} else if (strncasecmp(argv[3], "false", len) == 0) {
+			set_available = false;
+		} else if (strncasecmp(argv[3], "on", len) == 0) {
+			set_available = true;
+		} else if (strncasecmp(argv[3], "off", len) == 0) {
+			set_available = false;
+		} else {
+			resultf("error converting argument %s to boolean value", argv[3]);
+			return TCL_ERROR;
+		}
+
+		if (set_available) {
+			if (link->state() != Link::UNAVAILABLE) {
+				resultf("link %s already in state %s",
+						name, Link::state_to_str(link->state()));
+				return TCL_OK;
+			}
+
+			BundleDaemon::post(
+				new LinkStateChangeRequest(link, Link::AVAILABLE,
+										   ContactEvent::USER));
+			
+			return TCL_OK;
+
+		} else { // ! set_available
+			if (link->state() != Link::AVAILABLE) {
+				resultf("link %s can't be set unavailable in state %s",
+						name, Link::state_to_str(link->state()));
+				return TCL_OK;
+			}
+			
+			BundleDaemon::post(
+				new LinkStateChangeRequest(link, Link::UNAVAILABLE,
+										   ContactEvent::USER));
+	
+			return TCL_OK;
+		}
+	}
+	else if ((strcmp(cmd, "state") == 0) ||
+			 (strcmp(cmd, "stats") == 0))
+	{
+		// link state <name>
+		// link stats <name>
+		if (argc != 3) {
+			wrong_num_args(argc, argv, 2, 3, 3);
+			return TCL_ERROR;
+		}
+
+		const char* name = argv[2];
+
+		LinkRef link = BundleDaemon::instance()->contactmgr()->find_link(name);
+		if (link == NULL) {
+			resultf("link %s doesn't exist", name);
+			return TCL_ERROR;
+		}
+
+		if (strcmp(cmd, "state") == 0) {
+			resultf("%s", Link::state_to_str(link->state()));
+		} else {
+			oasys::StringBuffer buf;
+			link->dump_stats(&buf);
+			set_result(buf.c_str());
+		}
+		return TCL_OK;
+	}
+	else if (strcmp(cmd, "names") == 0) 
+	{
+		// link names
+		if (argc != 2) {
+			wrong_num_args(argc, argv, 2, 2, 2);
+			return TCL_ERROR;
+		}
+
+		ContactManager* cm = BundleDaemon::instance()->contactmgr();
+		oasys::ScopeLock l(cm->lock(), "LinkCommand::exec");
+			
+		const LinkSet* links = cm->links();
+		for (LinkSet::const_iterator i = links->begin();
+			 i != links->end(); ++i)
+		{
+			append_resultf("%s\n", (*i)->name());
+		}
+		return TCL_OK;
+	}
+	else if (strcmp(cmd, "dump") == 0) 
+	{
+		// link dump <name?>
+		if (argc == 2) {
+			ContactManager* cm = BundleDaemon::instance()->contactmgr();
+			oasys::ScopeLock l(cm->lock(), "LinkCommand::exec");
+			
+			const LinkSet* links = cm->links();
+			for (LinkSet::const_iterator i = links->begin();
+				 i != links->end(); ++i)
+			{
+				append_resultf("*%p\n", (*i).object());
+			}
+		} else if (argc == 3) {
+			const char* name = argv[2];
+			
+			LinkRef link =
+				BundleDaemon::instance()->contactmgr()->find_link(name);
+			if (link == NULL) {
+				resultf("link %s doesn't exist", name);
+				return TCL_ERROR;
+			}
+
+			oasys::StringBuffer buf;
+			link->dump(&buf);
+			set_result(buf.c_str());
+			return TCL_OK;
+		} else {
+			wrong_num_args(argc, argv, 2, 2, 3);
+			return TCL_ERROR;
+		}
+	}
+	else if (strcmp(cmd, "set_cl_defaults") == 0)
+	{
+		// link set_cl_defaults <cl> <opt=val> <opt2=val2> ...
+		if (argc < 4) {
+			wrong_num_args(argc, argv, 2, 4, INT_MAX);
+			return TCL_ERROR;
+		}
+
+		ConvergenceLayer* cl = ConvergenceLayer::find_clayer(argv[2]);
+		if (cl == NULL) {
+			resultf("unknown convergence layer %s", argv[2]);
+			return TCL_ERROR;
+		}
+
+		const char* invalid;
+		if (!cl->set_link_defaults(argc - 3, &argv[3], &invalid)) {
+			resultf("invalid link option: %s", invalid);
+			return TCL_ERROR;
+		}
+		
+		return TCL_OK;
+	}
+	else
+	{
+		resultf("unimplemented link subcommand %s", cmd);
+		return TCL_ERROR;
+	}
+	
+	return TCL_OK;
+}
+
+} // namespace dtn