Commit f772eb96 authored by Jelte Jansen's avatar Jelte Jansen

merged branch jelte_datadef back

Configuration syntax has changed; see README
Also the parkinglot.db format has changed and 'old' configuration will be
thrown away.
Merge back done to facilitate refactoring of names to conform to style guidelines (doing that in a separate branch could create too many conflicts)
next up: tests and documentation


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/parkinglot@405 e5f2f494-b856-4b98-b285-d166d9295462
parents 543dbfd9 ec10fee3
BUILDING
Simple build instructions:
autoreconf
./configure
make
RUNNING
At the moment there is no install yet, you can run the bind10 parkinglot
server from the source tree:
./src/bin/bind10/bind10
The server will listen on port 5300 for DNS requests.
CONFIGURATION
Commands can be given through the tool bigtool;
cd src/bin/bigtool
sh run_bigtool
The server must be running for bigtool to work.
The following configuration commands are available
help: show the different command modules
<module> help: show the commands for module
<module> <command> help: show info for the command
config show [identifier]: Show the currently set values. If no identifier is
given, the current location is used. If a config
option is a list or a map, the value is not
shown directly, but must be requested separately.
config go [identifier]: Go to the given location within the configuration.
config set [identifier] <value>: Set a configuration value.
config unset [identifier]: Remove a value (reverts to default if the option
is mandatory).
config add [identifier] <value>: add a value to a list
config remove [identifier] <value>: remove a value from a list
config revert: Revert all changes that have not been committed
config commit: Commit all changes
EXAMPLE SESSION
~> sh run_bigtool
> config show
ParkingLot/ module
> config show ParkingLot/
port: 5300 integer (default)
zones/ list
a_records/ list (default)
aaaa_records/ list (default)
ns_records/ list (default)
> config go ParkingLot/
/ParkingLot> config show
port: 5300 integer (default)
zones/ list
a_records/ list (default)
aaaa_records/ list (default)
ns_records/ list (default)
/ParkingLot> config show zones
/ParkingLot> config add zone tjeb.nl
Error: /ParkingLot/zone not found
/ParkingLot> config add zones tjeb.nl
/ParkingLot> config show zones
zone_name: tjeb.nl string
/ParkingLot> config show
port: 5300 integer (default)
zones/ list (modified)
a_records/ list (default)
aaaa_records/ list (default)
ns_records/ list (default)
/ParkingLot> config go /
> config show ParkingLot/port
port: 5300 integer (default)
> config go ParkingLot/a_records/
/ParkingLot/a_records> config show
address: 127.0.0.1 string
/ParkingLot/a_records> config add "127.0.0.2"
/ParkingLot/a_records> config show
address: 127.0.0.2 string
/ParkingLot/a_records>
......@@ -73,6 +73,7 @@ AC_OUTPUT([src/bin/bind-cfgd/bind-cfgd
src/bin/bind10/bind10
src/bin/msgq/msgq
src/bin/msgq/msgq_test
src/bin/parkinglot/config.h
], [
chmod +x src/bin/bind-cfgd/bind-cfgd
chmod +x src/bin/bigtool/run_bigtool
......
......@@ -4,20 +4,6 @@ import ISC
def _prepare_fake_data(bigtool):
add_cmd = CommandInfo(name = "add", desc = "add one zone")
remove_cmd = CommandInfo(name = 'remove', desc = 'remove one zone')
list_cmd = CommandInfo(name = 'list', desc = 'list all zones',
need_inst_param = False)
zone_module = ModuleInfo(name = "zone",
inst_name = "zone_name",
inst_type = STRING_TYPE,
inst_desc = "the name of one zone",
desc = "manage all the zones")
zone_module.add_command(add_cmd)
zone_module.add_command(remove_cmd)
zone_module.add_command(list_cmd)
shutdown_param = ParamInfo(name = "module_name", desc = "the name of module")
shutdown_cmd = CommandInfo(name = 'shutdown', desc = "stop bind10",
need_inst_param = False)
......@@ -25,18 +11,70 @@ def _prepare_fake_data(bigtool):
boss_module = ModuleInfo(name = "boss", desc = "boss of bind10")
boss_module.add_command(shutdown_cmd)
bigtool.add_module_info(zone_module)
bigtool.add_module_info(boss_module)
def prepare_commands(bigtool, command_spec):
for module_name in command_spec.keys():
bigtool.prepare_module_commands(module_name, command_spec[module_name])
def prepare_config_commands(bigtool):
module = ModuleInfo(name = "config", desc = "Configuration commands")
cmd = CommandInfo(name = "show", desc = "Show configuration", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "add", desc = "Add entry to configuration list", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "set", desc = "Set a configuration value", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "unset", desc = "Unset a configuration value", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=False)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False)
module.add_command(cmd)
cmd = CommandInfo(name = "commit", desc = "Commit all local changes", need_inst_param = False)
module.add_command(cmd)
cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part", need_inst_param = False)
param = ParamInfo(name = "identifier", type="string", optional=False)
cmd.add_param(param)
module.add_command(cmd)
bigtool.add_module_info(module)
if __name__ == '__main__':
try:
cc = ISC.CC.Session()
#cc.group_subscribe("BigTool", "*", "meonly")
#cc.group_subscribe("ConfigManager", "*", "meonly")
#cc.group_subscribe("Boss", "*", "meonly")
cc.group_subscribe("BigTool", "*")
cc.group_subscribe("Boss", "*")
tool = BigTool(cc)
cc.group_sendmsg({ "command": ["get_commands"] }, "ConfigManager")
command_spec, env = cc.group_recvmsg(False)
prepare_commands(tool, command_spec["result"][1])
prepare_config_commands(tool)
_prepare_fake_data(tool)
tool.cmdloop()
except ISC.CC.SessionError:
......
......@@ -197,6 +197,8 @@ class MsgQ:
def process_command(self, fd, sock, routing, data):
"""Process a single command. This will split out into one of the
other functions, above."""
print("[XX] got command: ")
print(routing)
cmd = routing["type"]
if cmd == 'send':
self.process_command_send(sock, routing, data)
......
......@@ -14,38 +14,103 @@
// $Id$
//
// todo: generalize this and make it into a specific API for all modules
// to use (i.e. connect to cc, send config and commands, get config,
// react on config change announcements)
//
#include <stdexcept>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <boost/foreach.hpp>
#include <cc/cpp/data.h>
#include <cc/cpp/data_def.h>
#include <cc/cpp/session.h>
#include "common.h"
#include "ccsession.h"
#include "config.h"
using namespace std;
using ISC::Data::Element;
using ISC::Data::ElementPtr;
using ISC::Data::DataDefinition;
using ISC::Data::ParseError;
using ISC::Data::DataDefinitionError;
void
CommandSession::read_data_definition(const std::string& filename) {
std::ifstream file;
// this file should be declared in a @something@ directive
file.open(PARKINGLOT_SPECFILE_LOCATION);
if (!file) {
cout << "error opening " << PARKINGLOT_SPECFILE_LOCATION << endl;
exit(1);
}
CommandSession::CommandSession() :
try {
data_definition_ = DataDefinition(file, true);
} catch (ParseError pe) {
cout << "Error parsing definition file: " << pe.what() << endl;
exit(1);
} catch (DataDefinitionError dde) {
cout << "Error reading definition file: " << dde.what() << endl;
exit(1);
}
file.close();
}
CommandSession::CommandSession(std::string module_name,
std::string spec_file_name,
ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config),
ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command)
) :
module_name_(module_name),
session_(ISC::CC::Session())
{
try {
session_.establish();
session_.subscribe("ParkingLot", "*");
session_.subscribe("Boss", "ParkingLot");
//session_.subscribe("ConfigManager", "*", "meonly");
//session_.subscribe("statistics", "*", "meonly");
} catch (...) {
throw std::runtime_error("SessionManager: failed to open sessions");
config_handler_ = config_handler;
command_handler_ = command_handler;
// todo: workaround, let boss wait until msgq is started
// and remove sleep here
sleep(1);
ElementPtr answer, env;
session_.establish();
session_.subscribe(module_name, "*");
session_.subscribe("Boss", "*");
session_.subscribe("statistics", "*");
read_data_definition(spec_file_name);
sleep(1);
// send the data specification
session_.group_sendmsg(data_definition_.getDefinition(), "ConfigManager");
session_.group_recvmsg(env, answer, false);
// get any stored configuration from the manager
if (config_handler_) {
ElementPtr cmd = Element::create_from_string("{ \"command\": [ \"get_config\", \"" + module_name + "\" ] }");
session_.group_sendmsg(cmd, "ConfigManager");
session_.group_recvmsg(env, answer, false);
cout << "[XX] got config: " << endl << answer->str() << endl;
if (answer->contains("result") &&
answer->get("result")->get(0)->int_value() == 0 &&
answer->get("result")->size() > 1) {
config_handler(answer->get("result")->get(1));
} else {
cout << "[XX] no result in answer" << endl;
}
}
}
......@@ -55,55 +120,37 @@ CommandSession::getSocket()
return (session_.getSocket());
}
std::pair<std::string, std::string>
CommandSession::getCommand(int counter) {
ElementPtr cmd, routing, data, ep;
string s;
session_.group_recvmsg(routing, data, false);
string channel = routing->get("group")->string_value();
if (channel == "statistics") {
cmd = data->get("command");
if (cmd != NULL && cmd->string_value() == "getstat") {
struct timeval now;
ElementPtr resp = Element::create(std::map<std::string,
ElementPtr>());
gettimeofday(&now, NULL);
resp->set("sent", Element::create(now.tv_sec +
(double)now.tv_usec / 1000000));
resp->set("counter", Element::create(counter));
session_.group_sendmsg(resp, "statistics");
int
CommandSession::check_command()
{
cout << "[XX] check for command" << endl;
ElementPtr cmd, routing, data;
if (session_.group_recvmsg(routing, data, true)) {
/* ignore result messages (in case we're out of sync, to prevent
* pingpongs */
if (!data->get_type() == Element::map || data->contains("result")) {
return 0;
}
} else {
cmd = data->get("zone_added");
if (cmd != NULL)
return std::pair<string, string>("addzone", cmd->string_value());
cmd = data->get("zone_deleted");
if (cmd != NULL) {
return std::pair<string, string>("delzone", cmd->string_value());
cout << "[XX] got something!" << endl << data->str() << endl;
ElementPtr answer;
if (data->contains("config_update")) {
if (config_handler_) {
// handle config update
answer = config_handler_(data->get("config_update"));
} else {
answer = Element::create_from_string("{ \"result\": [0] }");
}
}
cmd = data->get("command");
if (cmd != NULL) {
if (cmd->get_type() == Element::string && cmd->string_value() == "shutdown") {
return std::pair<string, string>("shutdown", "");
if (data->contains("command")) {
if (command_handler_) {
answer = command_handler_(data->get("command"));
} else {
answer = Element::create_from_string("{ \"result\": [0] }");
}
}
session_.reply(routing, answer);
}
return std::pair<string, string>("unknown", "");
return 0;
}
std::vector<std::string>
CommandSession::getZones() {
ElementPtr cmd, result, env;
std::vector<std::string> zone_names;
cmd = Element::create_from_string("{ \"command\": [ \"zone\", \"list\" ] }");
sleep(1);
session_.group_sendmsg(cmd, "ConfigManager");
session_.group_recvmsg(env, result, false);
BOOST_FOREACH(ElementPtr zone_name, result->get("result")->list_value()) {
zone_names.push_back(zone_name->string_value());
}
return zone_names;
}
......@@ -20,15 +20,66 @@
#include <string>
#include <cc/cpp/session.h>
#include <cc/cpp/data_def.h>
#include <cc/cpp/data.h>
class CommandSession {
public:
CommandSession();
/**
* Initialize a config/command session
* @param module_name: The name of this module. This is not a
* reference because we expect static strings
* to be passed here.
* @param spec_file_name: The name of the file containing the data
* definition.
*/
CommandSession(std::string module_name, std::string spec_file_name,
ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config) = NULL,
ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command) = NULL
);
int getSocket();
std::pair<std::string, std::string> getCommand(int counter);
std::vector<std::string> getZones();
/**
* Check if there is a command or config change on the command
* session. If so, the appropriate handler is called if set.
* If not set, a default answer is returned.
* This is a non-blocking read; if there is nothing this function
* will return 0.
*/
int check_command();
/**
* The config handler function should expect an ElementPtr containing
* the full configuration where non-default values have been set.
* Later we might want to think about more granular control
* (i.e. this does not scale to for instance lists containing
* 100000 zones, where the whole list is passed every time a single
* thing changes)
*/
void set_config_handler(ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config)) { config_handler_ = config_handler; };
/**
* Set a command handler; the function that is passed takes an
* ElementPtr, pointing to a list element, containing
* [ module_name, command_name, arg1, arg2, ... ]
* The returned ElementPtr should look like
* { "result": [ return_value, result_value ] }
* result value here is optional and depends on the command
*
* This protocol is very likely to change.
*/
void set_command_handler(ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command)) { command_handler_ = command_handler; };
private:
void read_data_definition(const std::string& filename);
std::string module_name_;
ISC::CC::Session session_;
ISC::Data::DataDefinition data_definition_;
ISC::Data::ElementPtr config_;
ISC::Data::ElementPtr(*config_handler_)(ISC::Data::ElementPtr new_config);
ISC::Data::ElementPtr(*command_handler_)(ISC::Data::ElementPtr command);
};
#endif // __CCSESSION_H
......
#define PARKINGLOT_SPECFILE_LOCATION "@abs_top_srcdir@/src/bin/parkinglot/parkinglot.spec"
......@@ -31,6 +31,7 @@
#include <dns/message.h>
#include <cc/cpp/session.h>
#include <cc/cpp/data.h>
#include "zoneset.h"
#include "parkinglot.h"
......@@ -38,10 +39,18 @@
#include "common.h"
#include <boost/foreach.hpp>
using namespace std;
const string PROGRAM = "parkinglot";
const int DNSPORT = 53;
const string PROGRAM = "ParkingLot";
const string SPECFILE = "parkinglot.spec";
const int DNSPORT = 5300;
/* need global var for config/command handlers.
* todo: turn this around, and put handlers in the parkinglot
* class itself? */
ParkingLot plot = ParkingLot(DNSPORT);
static void
usage() {
......@@ -49,6 +58,58 @@ usage() {
exit(1);
}
ISC::Data::ElementPtr
my_config_handler(ISC::Data::ElementPtr config)
{
cout << "[XX] Handle config: " << endl << config->str() << endl;
if (config->contains("zones")) {
plot.clear_zones();
BOOST_FOREACH(ISC::Data::ElementPtr zone_el, config->get("zones")->list_value()) {
plot.serve(zone_el->string_value());
}
}
if (config->contains("port")) {
// todo: what to do with port change. restart automatically?
// ignore atm
}
if (config->contains("a_records")) {
plot.clearARecords();
BOOST_FOREACH(ISC::Data::ElementPtr rel, config->get("a_records")->list_value()) {
plot.addARecord(rel->string_value());
}
}
if (config->contains("aaaa_records")) {
plot.clearAAAARecords();
BOOST_FOREACH(ISC::Data::ElementPtr rel, config->get("aaaa_records")->list_value()) {
plot.addAAAARecord(rel->string_value());
}
}
if (config->contains("ns_records")) {
plot.clearNSRecords();
BOOST_FOREACH(ISC::Data::ElementPtr rel, config->get("ns_records")->list_value()) {
plot.addNSRecord(rel->string_value());
}
}
return ISC::Data::Element::create_from_string("{ \"result\": [0] }");
}
ISC::Data::ElementPtr
my_command_handler(ISC::Data::ElementPtr command)
{
ISC::Data::ElementPtr answer = ISC::Data::Element::create_from_string("{ \"result\": [0] }");
cout << "[XX] Handle command: " << endl << command->str() << endl;
if (command->get(1)->string_value() == "print_message") {
cout << command->get(2)->string_value() << endl;
/* let's add that message to our answer as well */
cout << "[XX] answer was: " << answer->str() << endl;
answer->get("result")->add(command->get(2));
cout << "[XX] answer now: " << answer->str() << endl;
}
return answer;
}
int
main(int argc, char* argv[]) {
int ch;
......@@ -69,18 +130,15 @@ main(int argc, char* argv[]) {
usage();
// initialize parking lot
ParkingLot plot(port);
//plot = ParkingLot(port);
// initialize command channel
CommandSession cs;
CommandSession cs = CommandSession(PROGRAM, SPECFILE, my_config_handler, my_command_handler);
// main server loop
fd_set fds;
int ps = plot.getSocket();
int ss = cs.getSocket();
BOOST_FOREACH(std::string zone, cs.getZones()) {
plot.serve(zone);
}
int nfds = max(ps, ss) + 1;
int counter = 0;
......@@ -99,9 +157,9 @@ main(int argc, char* argv[]) {
plot.processMessage();
}
/* isset not really necessary, but keep it for now */
if (FD_ISSET(ss, &fds)) {
pair<string,string> cmd = cs.getCommand(counter);
plot.command(cmd);
cs.check_command();
}
}
......
......@@ -28,21 +28,60 @@
#include <dns/rrset.h>
#include <dns/message.h>
#include <cc/cpp/data.h>
#include "common.h"
#include "parkinglot.h"
#include <boost/foreach.hpp>
using namespace std;
using namespace isc::dns;
using namespace isc::dns::Rdata::IN;
using namespace isc::dns::Rdata::Generic;
using namespace ISC::Data;
void
ParkingLot::addARecord(std::string data) {
a_records.push_back(Rdata::RdataPtr(new A(data)));
}
void
ParkingLot::addAAAARecord(std::string data) {
aaaa_records.push_back(Rdata::RdataPtr(new AAAA(data)));
}
void
ParkingLot::addNSRecord(std::string data) {
ns_records.push_back(Rdata::RdataPtr(new NS(data)));
}
void
ParkingLot::setSOARecord(isc::dns::Rdata::RdataPtr soa_record) {
}
void