Commit 87a9c552 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

merged from trunk


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@938 e5f2f494-b856-4b98-b285-d166d9295462
parents a3046fe9 83ac742f
......@@ -568,7 +568,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../src/lib/cc/cpp ../src/lib/dns/cpp
INPUT = ../src/lib/cc/cpp ../src/lib/config/cpp ../src/lib/dns/cpp
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
......
{
"data_specification": {
"module_name": "ParkingLot"
"module_spec": {
"module_name": "Auth",
"config_data": [
{ "item_name": "default_name",
"item_type": "string",
"item_optional": False,
"item_default": "Hello, world!"
},
{ "item_name": "zone_list",
"item_type": "list",
"item_optional": False,
"item_default": [],
"list_item_spec":
{ "item_name": "zone_name",
"item_type": "string",
"item_optional": True,
"item_default": ""
}
}
],
"commands": [
{
"command_name": "print_message",
"command_description": "Print the given message to stdout",
"command_args": [ {
"item_name": "message",
"item_type": "string",
"item_optional": False,
"item_default": ""
} ]
},
{
"command_name": "shutdown",
"command_description": "Shut down BIND 10",
"command_args": []
}
]
}
}
......@@ -32,6 +32,7 @@
#include <dns/rrset.h>
#include <dns/rrttl.h>
#include <dns/message.h>
#include <config/ccsession.h>
#include <cc/data.h>
......@@ -46,6 +47,7 @@ using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::config;
AuthSrv::AuthSrv(int port) {
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
......@@ -120,5 +122,11 @@ AuthSrv::updateConfig(isc::data::ElementPtr config) {
// todo: what to do with port change. restart automatically?
// ignore atm
//}
return isc::data::Element::createFromString("{ \"result\": [0] }");
if (config) {
std::cout << "[XX] auth: new config " << config << std::endl;
} else {
std::cout << "[XX] auth: new config empty" << std::endl;
}
return isc::config::createAnswer(0);
}
......@@ -64,18 +64,15 @@ my_config_handler(isc::data::ElementPtr config)
isc::data::ElementPtr
my_command_handler(isc::data::ElementPtr command) {
isc::data::ElementPtr answer = isc::data::Element::createFromString("{ \"result\": [0] }");
isc::data::ElementPtr answer = isc::config::createAnswer(0);
cout << "[XX] Handle command: " << endl << command->str() << endl;
if (command->get(0)->stringValue() == "print_message")
{
cout << command->get(1)->get("message") << endl;
/* let's add that message to our answer as well */
cout << "[XX] answer was: " << answer->str() << endl;
answer->get("result")->add(command->get(1));
cout << "[XX] answer now: " << answer->str() << endl;
}
return answer;
}
......@@ -106,9 +103,9 @@ main(int argc, char* argv[]) {
} else {
specfile = std::string(AUTH_SPECFILE_LOCATION);
}
CommandSession cs = CommandSession(specfile,
my_config_handler,
my_command_handler);
isc::config::ModuleCCSession cs = isc::config::ModuleCCSession(specfile,
my_config_handler,
my_command_handler);
// main server loop
fd_set fds;
......
......@@ -106,6 +106,7 @@ class BoB:
self.verbose = verbose
self.c_channel_port = c_channel_port
self.cc_session = None
self.ccs = None
self.processes = {}
self.dead_processes = {}
self.runnable = False
......@@ -114,6 +115,8 @@ class BoB:
if self.verbose:
print("[XX] handling new config:")
print(new_config)
answer = isc.config.ccsession.create_answer(0)
return answer
# TODO
def command_handler(self, command):
......@@ -121,21 +124,27 @@ class BoB:
if self.verbose:
print("[XX] Boss got command:")
print(command)
answer = None
answer = [ 1, "Command not implemented" ]
if type(command) != list or len(command) == 0:
answer = { "result": [ 1, "bad command" ] }
answer = isc.config.ccsession.create_answer(1, "bad command")
else:
cmd = command[0]
if cmd == "shutdown":
print("[XX] got shutdown command")
self.runnable = False
answer = { "result": [ 0 ] }
answer = isc.config.ccsession.create_answer(0)
elif cmd == "print_message":
if len(command) > 1 and type(command[1]) == dict and "message" in command[1]:
print(command[1]["message"])
answer = { "result": [ 0 ] }
answer = isc.config.ccsession.create_answer(0)
elif cmd == "print_settings":
print("Full Config:")
full_config = self.ccs.get_full_config()
for item in full_config:
print(item + ": " + str(full_config[item]))
answer = isc.config.ccsession.create_answer(0)
else:
answer = { "result": [ 1, "Unknown command" ] }
answer = isc.config.ccsession.create_answer(1, "Unknown command")
return answer
def startup(self):
......@@ -190,7 +199,8 @@ class BoB:
time.sleep(1)
if self.verbose:
print("[XX] starting ccsession")
self.ccs = isc.config.CCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self.ccs.start()
if self.verbose:
print("[XX] ccsession started")
......@@ -233,7 +243,7 @@ class BoB:
cmd = { "command": ['shutdown']}
self.cc_session.group_sendmsg(cmd, 'Boss', 'Cmd-Ctrld')
self.cc_session.group_sendmsg(cmd, "Boss", "ConfigManager")
self.cc_session.group_sendmsg(cmd, "Boss", "ParkingLot")
self.cc_session.group_sendmsg(cmd, "Boss", "Auth")
def stop_process(self, process):
"""Stop the given process, friendly-like."""
......@@ -478,7 +488,7 @@ def main():
for fd in rlist + xlist:
if fd == ccs_fd:
boss_of_bind.ccs.checkCommand()
boss_of_bind.ccs.check_command()
elif fd == wakeup_fd:
os.read(wakeup_fd, 32)
......
......@@ -5,7 +5,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_srcdir@/src/bin/bind10
PATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/bin/auth:@abs_top_srcdir@/src/bin/bind-cfgd:$PATH
PATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/bin/auth:$PATH
export PATH
PYTHONPATH=@abs_top_srcdir@/src/lib/cc/python:${abs_top_src_dir}/lib/cc/python/ISC
......
{
"data_specification": {
"module_spec": {
"module_name": "Boss",
"config_data": [
{
......@@ -7,6 +7,12 @@
"item_type": "string",
"item_optional": False,
"item_default": "Hi, shane!"
},
{
"item_name": "some_int",
"item_type": "integer",
"item_optional": False,
"item_default": 1
}
],
"commands": [
......@@ -20,6 +26,11 @@
"item_default": ""
} ]
},
{
"command_name": "print_settings",
"command_description": "Print some_string and some_int to stdout",
"command_args": []
},
{
"command_name": "shutdown",
"command_description": "Shut down BIND 10",
......
......@@ -31,6 +31,7 @@ import os, time, random, re
import getpass
from hashlib import sha1
import csv
import ast
try:
from collections import OrderedDict
......@@ -85,7 +86,7 @@ class BindCmdInterpreter(Cmd):
return False
# Get all module information from cmd-ctrld
self.config_data = isc.cc.data.UIConfigData(self)
self.config_data = isc.config.UIModuleCCSession(self)
self.update_commands()
self.cmdloop()
except KeyboardInterrupt:
......@@ -150,7 +151,8 @@ class BindCmdInterpreter(Cmd):
if (len(cmd_spec) == 0):
print('can\'t get any command specification')
for module_name in cmd_spec.keys():
self.prepare_module_commands(module_name, cmd_spec[module_name])
if cmd_spec[module_name]:
self.prepare_module_commands(module_name, cmd_spec[module_name])
def send_GET(self, url, body = None):
headers = {"cookie" : self.session_id}
......@@ -315,7 +317,7 @@ class BindCmdInterpreter(Cmd):
if cmd.module == "config":
# grm text has been stripped of slashes...
my_text = self.location + "/" + cur_line.rpartition(" ")[2]
list = self.config_data.config.get_item_list(my_text.rpartition("/")[0])
list = self.config_data.get_config_item_list(my_text.rpartition("/")[0])
hints.extend([val for val in list if val.startswith(text)])
except CmdModuleNameFormatError:
if not text:
......@@ -440,17 +442,28 @@ class BindCmdInterpreter(Cmd):
line += "(modified)"
print(line)
elif cmd.command == "add":
self.config_data.add(identifier, cmd.params['value'])
self.config_data.add_value(identifier, cmd.params['value'])
elif cmd.command == "remove":
self.config_data.remove(identifier, cmd.params['value'])
self.config_data.remove_value(identifier, cmd.params['value'])
elif cmd.command == "set":
self.config_data.set(identifier, cmd.params['value'])
if 'identifier' not in cmd.params:
print("Error: missing identifier or value")
else:
parsed_value = None
try:
parsed_value = ast.literal_eval(cmd.params['value'])
except Exception as exc:
# ok could be an unquoted string, interpret as such
parsed_value = cmd.params['value']
self.config_data.set_value(identifier, parsed_value)
elif cmd.command == "unset":
self.config_data.unset(identifier)
elif cmd.command == "revert":
self.config_data.revert()
elif cmd.command == "commit":
self.config_data.commit(self)
self.config_data.commit()
elif cmd.command == "diff":
print(self.config_data.get_local_changes());
elif cmd.command == "go":
self.go(identifier)
except isc.cc.data.DataTypeError as dte:
......
......@@ -28,11 +28,15 @@ If this connection is not established,
will exit.
.\" TODO: what if msgq is running but no BindCtl or Boss groups?
.Pp
The command-line prompt shows
The
.Nm
prompt shows
.Dq "\*[Gt] " .
The prompt will also display the location if changed.
The options are based on the module in use.
The command-line usage is:
The
.Nm
usage is:
.Pp
.Ic module Ic command Op Ar "param1 = value1" Op Ar ", param2 = value2"
.Pp
......
......@@ -5,7 +5,7 @@ export PYTHON_EXEC
BINDCTL_PATH=@abs_top_srcdir@/src/bin/bindctl
PYTHONPATH=@abs_top_srcdir@/src/lib/cc/python
PYTHONPATH=@abs_top_builddir@/pyshared
export PYTHONPATH
cd ${BINDCTL_PATH}
......
......@@ -53,6 +53,9 @@ def prepare_config_commands(tool):
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "diff", desc = "Show all local changes", need_inst_param = False)
module.add_command(cmd)
cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False)
module.add_command(cmd)
......@@ -67,12 +70,17 @@ def prepare_config_commands(tool):
tool.add_module_info(module)
if __name__ == '__main__':
try:
tool = BindCmdInterpreter("localhost:8080")
prepare_config_commands(tool)
tool.run()
except Exception as e:
print(e)
print("Failed to connect with b10-cmdctl module, is it running?")
tool = BindCmdInterpreter("localhost:8080")
prepare_config_commands(tool)
tool.run()
# TODO: put below back, was removed to see errors
#if __name__ == '__main__':
#try:
#tool = BindCmdInterpreter("localhost:8080")
#prepare_config_commands(tool)
#tool.run()
#except Exception as e:
#print(e)
#print("Failed to connect with b10-cmdctl module, is it running?")
......@@ -34,10 +34,10 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
NAME_PATTERN = re.compile("^\s*(?P<name>[\w]+)(?P<blank>\s*)(?P<others>.*)$")
class BindCmdParse:
""" This class will parse the command line usr input into three part
module name, command, parameters
the first two parts are strings and parameter is one hash,
parameter part is optional
""" This class will parse the command line user input into three parts:
module name, command, parameters.
The first two parts are strings and parameter is one hash.
The parameter part is optional.
Example: zone reload, zone_name=example.com
module == zone
......
......@@ -169,8 +169,8 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
param = json.loads(post_str)
# TODO, need return some proper return code.
# currently always OK.
reply = self.server.send_command_to_module(mod, cmd, param)
print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod))
reply = self.server.send_command_to_module(mod, cmd, param)
print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod))
return rcode, reply
......@@ -189,7 +189,7 @@ class CommandControl():
self.config_data = self.get_config_data()
def get_cmd_specification(self):
return self.send_command('ConfigManager', 'get_commands')
return self.send_command('ConfigManager', 'get_commands_spec')
def get_config_data(self):
return self.send_command('ConfigManager', 'get_config')
......@@ -199,7 +199,7 @@ class CommandControl():
self.config_data = self.get_config_data()
def get_data_specification(self):
return self.send_command('ConfigManager', 'get_data_spec')
return self.send_command('ConfigManager', 'get_module_spec')
def handle_recv_msg(self):
# Handle received message, if 'shutdown' is received, return False
......
......@@ -4,7 +4,7 @@
.Os
.Sh NAME
.Nm msgq
.Nd message routing daemon for the Command channel
.Nd message routing daemon for the Command Channel
.\" TODO: spell out CC
.Sh SYNOPSIS
.Nm
......@@ -16,9 +16,36 @@
.Sh DESCRIPTION
The
.Nm
daemon handles message routing for the Command channel.
It listens on 127.0.0.1.
.\" TODO: point to Command channel specification or document some here
daemon handles message routing for the Command Channel.
.Pp
The Command Channel is a message bus and subscription manager.
Programs may subscribe to certain groups to receive messages
for that group.
Every new connection to the
.Nm
receives a unique identifier -- this is the local name.
The commands it handles are:
.Bl -tag -compact -offset indent
.It getlname
receive local name.
.It send
send a message to defined subscribers.
.It subscribe
add a subscription. This means it is a listener for messages
for a specific group.
.It unsubscribe
remove a subscription.
.El
.Pp
.Nm
listens on 127.0.0.1.
.Pp
The
.Nm
daemon may be cleanly stopped by sending the
.Dv SIGTERM
signal to the process.
This shutdown does not notify the subscribers.
.Sh OPTIONS
The arguments are as follows:
.
......@@ -36,15 +63,17 @@ Display more about what
is doing.
.El
.\" .Sh SEE ALSO
.\" TODO: point to Command channel specification or document some here.
.\" .Sh STANDARDS
.Sh HISTORY
The python version of
.Nm
was first coded in December 2009.
An older C version with different wire format was coded in September 2009.
The C version with now deprecated wire format was coded in September
2009.
.Sh AUTHORS
The
.Nm
daemon and Control channel specification
daemon and Control Channel specification
were initially designed by Michael Graff of ISC.
.\" .Sh BUGS
......@@ -487,7 +487,11 @@ MapElement::str()
ss << ", ";
}
ss << "\"" << (*it).first << "\": ";
ss << (*it).second->str();
if ((*it).second) {
ss << (*it).second->str();
} else {
ss << "None";
}
}
ss << "}";
return ss.str();
......@@ -829,13 +833,19 @@ ListElement::toWire(std::stringstream& ss, int omit_length)
(*it)->toWire(ss2, 0);
}
if (omit_length) {
ss << ss2.rdbuf();
stringbuf *ss2_buf = ss2.rdbuf();
if (ss2_buf->in_avail() > 0) {
ss << ss2_buf;
}
} else {
stringbuf *ss2_buf = ss2.rdbuf();
ss2_buf->pubseekpos(0);
ss << encode_length(ss2_buf->in_avail(), ITEM_LIST);
ss << ss2_buf;
if (ss2_buf->in_avail() > 0) {
ss << ss2_buf;
}
}
}
......@@ -873,13 +883,17 @@ MapElement::toWire(std::stringstream& ss, int omit_length)
// add length if needed
//
if (omit_length) {
ss << ss2.rdbuf();
stringbuf *ss2_buf = ss2.rdbuf();
if (ss2_buf->in_avail() > 0) {
ss << ss2_buf;
}
} else {
stringbuf *ss2_buf = ss2.rdbuf();
ss2_buf->pubseekpos(0);
ss << encode_length(ss2_buf->in_avail(), ITEM_HASH);
ss << ss2_buf;
if (ss2_buf->in_avail() > 0) {
ss << ss2_buf;
}
}
}
......
......@@ -35,7 +35,6 @@ typedef boost::shared_ptr<Element> ElementPtr;
/// is called for an Element that has a wrong type (e.g. int_value on a
/// ListElement)
///
// todo: include types and called function in the exception
class TypeError : public isc::Exception {
public:
TypeError(const char* file, size_t line, const char* what) :
......@@ -410,7 +409,7 @@ public:
using Element::setValue;
bool setValue(std::map<std::string, ElementPtr>& v) { m = v; return true; };
using Element::get;
ElementPtr get(const std::string& s) { return m[s]; };
ElementPtr get(const std::string& s) { if (contains(s)) { return m[s]; } else { return ElementPtr();} };
using Element::set;
void set(const std::string& s, ElementPtr p) { m[s] = p; };
using Element::remove;
......
......@@ -266,5 +266,13 @@ TEST(Element, to_and_from_wire) {
//EXPECT_EQ("\047\0031.2", Element::create(1.2)->toWire(0));
EXPECT_EQ("\046\0011", Element::createFromString("[ 1 ]")->toWire(1));
std::string ddef = "{\"data_specification\": {\"config_data\": [ {\"item_default\": \"Hello, world!\", \"item_name\": \"default_name\", \"item_optional\": False, \"item_type\": \"string\"}, {\"item_default\": [ ], \"item_name\": \"zone_list\", \"item_optional\": False, \"item_type\": \"list\", \"list_item_spec\": {\"item_name\": \"zone_name\", \"item_optional\": True, \"item_type\": \"string\"}} ], \"module_name\": \"Auth\"}}";
//std::string ddef = "{\"aaa\": 123, \"test\": [ ], \"zzz\": 123}";
ElementPtr ddef_el = Element::createFromString(ddef);
std::string ddef_wire = ddef_el->toWire();
ElementPtr ddef_el2 = Element::fromWire(ddef_wire);
std::string ddef2 = ddef_el2->str();
EXPECT_EQ(ddef, ddef2);
}
# data, data_definition, config_data, module_config_data and ui_config_data classes
# we might want to split these up :)
# Copyright (C) 2010 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# Helper functions for data elements as used in cc-channel and
# configuration. There is no python equivalent for the cpp Element
# class, since data elements are represented by native python types
# (int, real, bool, string, list and dict respectively)
#
import ast
class DataNotFoundError(Exception): pass
......@@ -9,6 +29,8 @@ def merge(orig, new):
"""Merges the contents of new into orig, think recursive update()
orig and new must both be dicts. If an element value is None in
new it will be removed in orig."""
if type(orig) != dict or type(new) != dict:
raise DataTypeError("Not a dict in merge()")
for kn in new.keys():
if kn in orig:
if new[kn]:
......@@ -23,6 +45,10 @@ def merge(orig, new):
def find(element, identifier):
"""Returns the subelement in the given data element, raises DataNotFoundError if not found"""
if type(identifier) != str or (type(element) != dict and identifier != ""):
raise DataTypeError("identifier in merge() is not a string")
if type(identifier) != str or (type(element) != dict and identifier != ""):
raise DataTypeError("element in merge() is not a dict")
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
......@@ -34,6 +60,17 @@ def find(element, identifier):
return cur_el
def set(element, identifier, value):
"""Sets the value at the element specified by identifier to value.
If the value is None, it is removed from the dict. If element
is not a dict, or if the identifier points to something that is
not, a DataTypeError is raised. The element is updated inline,
so if the original needs to be kept, you must make a copy before
calling set(). The updated base element is returned (so that
el.set().set().set() is possible)"""
if type(element) != dict:
raise DataTypeError("element in set() is not a dict")
if type(identifier) != str:
raise DataTypeError("identifier in set() is not a string")
id_parts = identifier.split("/")