2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

more sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrdata2@972 e5f2f494-b856-4b98-b285-d166d9295462
This commit is contained in:
JINMEI Tatuya
2010-02-25 22:36:42 +00:00
98 changed files with 4550 additions and 1617 deletions

26
README
View File

@@ -16,6 +16,9 @@ Requires autoconf 2.59 or newer.
Use automake-1.11 or better for working Python 3.1 tests.
Install with:
make install
TEST COVERAGE
@@ -36,18 +39,25 @@ Doing code coverage tests:
RUNNING
At the moment there is no install yet, you can run the bind10 parkinglot
server from the source tree:
./src/bin/bind10/bind10
You can start the BIND 10 processes by running bind10 which is
installed to the sbin directory under the installation prefix.
The default location is:
/usr/local/sbin/bind10
For development work, you can also run the bind10 services from the
source tree:
./src/bin/bind10/run_bind10.sh
(Which will use the modules and configurations also from the source
tree.)
The server will listen on port 5300 for DNS requests.
CONFIGURATION
Commands can be given through the tool bindctl;
cd src/bin/bindctl
sh bindctl
Commands can be given through the bindctl tool.
The server must be running for bindctl to work.
@@ -74,7 +84,7 @@ config commit: Commit all changes
EXAMPLE SESSION
~> sh bindctl
~> bindctl
> config show
ParkingLot/ module
> config show ParkingLot/

View File

@@ -29,6 +29,9 @@ fi
# Checks for libraries.
AC_SEARCH_LIBS(inet_pton, [nsl])
AC_SEARCH_LIBS(recvfrom, [socket])
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
@@ -148,7 +151,6 @@ AC_CONFIG_FILES([Makefile
src/bin/host/Makefile
src/bin/msgq/Makefile
src/bin/auth/Makefile
src/bin/parkinglot/Makefile
src/lib/Makefile
src/lib/cc/Makefile
src/lib/cc/cpp/Makefile
@@ -171,8 +173,9 @@ AC_CONFIG_FILES([Makefile
])
AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/bin/cfgmgr/run_b10-cfgmgr.sh
src/bin/cmdctl/b10-cmdctl.py
src/bin/cmdctl/cmdctl.py
src/bin/cmdctl/run_b10-cmdctl.sh
src/bin/cmdctl/unittest/cmdctl_test
src/bin/bind10/bind10.py
src/bin/bind10/bind10_test
src/bin/bind10/run_bind10.sh
@@ -182,7 +185,6 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/bin/msgq/msgq_test
src/bin/msgq/run_msgq.sh
src/bin/auth/config.h
src/bin/parkinglot/config.h
src/lib/config/cpp/data_def_unittests_config.h
src/lib/config/python/isc/config/config_test
src/lib/dns/cpp/gen-rdatacode.py
@@ -190,6 +192,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
chmod +x src/bin/cfgmgr/run_b10-cfgmgr.sh
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/bind10/run_bind10.sh
chmod +x src/bin/cmdctl/unittest/cmdctl_test
chmod +x src/bin/bindctl/unittest/bindctl_test
chmod +x src/bin/bindctl/bindctl
chmod +x src/bin/msgq/run_msgq.sh
chmod +x src/bin/msgq/msgq_test

View File

@@ -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

View File

@@ -0,0 +1,400 @@
// Boost.Assign library
//
// Copyright Thorsten Ottosen 2003-2004. Use, modification and
// distribution is subject to the Boost Software License, Version
// 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// For more information, see http://www.boost.org/libs/assign/
//
#ifndef BOOST_ASSIGN_LIST_INSERTER_HPP
#define BOOST_ASSIGN_LIST_INSERTER_HPP
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif
#include <boost/detail/workaround.hpp>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/config.hpp>
#include <cstddef>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/preprocessor/arithmetic/inc.hpp>
namespace boost
{
namespace assign_detail
{
template< class T >
struct repeater
{
std::size_t sz;
T val;
repeater( std::size_t sz, T r ) : sz( sz ), val( r )
{ }
};
template< class Fun >
struct fun_repeater
{
std::size_t sz;
Fun val;
fun_repeater( std::size_t sz, Fun r ) : sz( sz ), val( r )
{ }
};
template< class C >
class call_push_back
{
C& c_;
public:
call_push_back( C& c ) : c_( c )
{ }
template< class T >
void operator()( T r )
{
c_.push_back( r );
}
};
template< class C >
class call_push_front
{
C& c_;
public:
call_push_front( C& c ) : c_( c )
{ }
template< class T >
void operator()( T r )
{
c_.push_front( r );
}
};
template< class C >
class call_push
{
C& c_;
public:
call_push( C& c ) : c_( c )
{ }
template< class T >
void operator()( T r )
{
c_.push( r );
}
};
template< class C >
class call_insert
{
C& c_;
public:
call_insert( C& c ) : c_( c )
{ }
template< class T >
void operator()( T r )
{
c_.insert( r );
}
};
template< class C >
class call_add_edge
{
C& c_;
public:
call_add_edge( C& c ) : c_(c)
{ }
template< class T >
void operator()( T l, T r )
{
add_edge( l, r, c_ );
}
template< class T, class EP >
void operator()( T l, T r, const EP& ep )
{
add_edge( l, r, ep, c_ );
}
};
struct forward_n_arguments {};
} // namespace 'assign_detail'
namespace assign
{
template< class T >
inline assign_detail::repeater<T>
repeat( std::size_t sz, T r )
{
return assign_detail::repeater<T>( sz, r );
}
template< class Function >
inline assign_detail::fun_repeater<Function>
repeat_fun( std::size_t sz, Function r )
{
return assign_detail::fun_repeater<Function>( sz, r );
}
template< class Function, class Argument = assign_detail::forward_n_arguments >
class list_inserter
{
struct single_arg_type {};
struct n_arg_type {};
typedef BOOST_DEDUCED_TYPENAME mpl::if_c< is_same<Argument,assign_detail::forward_n_arguments>::value,
n_arg_type,
single_arg_type >::type arg_type;
public:
list_inserter( Function fun ) : insert_( fun )
{}
template< class Function2, class Arg >
list_inserter( const list_inserter<Function2,Arg>& r )
: insert_( r.fun_private() )
{}
list_inserter( const list_inserter& r ) : insert_( r.insert_ )
{}
list_inserter& operator()()
{
insert_( Argument() );
return *this;
}
template< class T >
list_inserter& operator=( const T& r )
{
insert_( r );
return *this;
}
template< class T >
list_inserter& operator=( assign_detail::repeater<T> r )
{
return operator,( r );
}
template< class Nullary_function >
list_inserter& operator=( const assign_detail::fun_repeater<Nullary_function>& r )
{
return operator,( r );
}
template< class T >
list_inserter& operator,( const T& r )
{
insert_( r );
return *this;
}
#if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205))
template< class T >
list_inserter& operator,( const assign_detail::repeater<T> & r )
{
return repeat( r.sz, r.val );
}
#else
template< class T >
list_inserter& operator,( assign_detail::repeater<T> r )
{
return repeat( r.sz, r.val );
}
#endif
template< class Nullary_function >
list_inserter& operator,( const assign_detail::fun_repeater<Nullary_function>& r )
{
return repeat_fun( r.sz, r.val );
}
template< class T >
list_inserter& repeat( std::size_t sz, T r )
{
std::size_t i = 0;
while( i++ != sz )
insert_( r );
return *this;
}
template< class Nullary_function >
list_inserter& repeat_fun( std::size_t sz, Nullary_function fun )
{
std::size_t i = 0;
while( i++ != sz )
insert_( fun() );
return *this;
}
template< class SinglePassIterator >
list_inserter& range( SinglePassIterator first,
SinglePassIterator last )
{
for( ; first != last; ++first )
insert_( *first );
return *this;
}
template< class SinglePassRange >
list_inserter& range( const SinglePassRange& r )
{
return range( boost::begin(r), boost::end(r) );
}
template< class T >
list_inserter& operator()( const T& t )
{
insert_( t );
return *this;
}
#ifndef BOOST_ASSIGN_MAX_PARAMS // use user's value
#define BOOST_ASSIGN_MAX_PARAMS 5
#endif
#define BOOST_ASSIGN_MAX_PARAMETERS (BOOST_ASSIGN_MAX_PARAMS - 1)
#define BOOST_ASSIGN_PARAMS1(n) BOOST_PP_ENUM_PARAMS(n, class T)
#define BOOST_ASSIGN_PARAMS2(n) BOOST_PP_ENUM_BINARY_PARAMS(n, T, const& t)
#define BOOST_ASSIGN_PARAMS3(n) BOOST_PP_ENUM_PARAMS(n, t)
#define BOOST_PP_LOCAL_LIMITS (1, BOOST_ASSIGN_MAX_PARAMETERS)
#define BOOST_PP_LOCAL_MACRO(n) \
template< class T, BOOST_ASSIGN_PARAMS1(n) > \
list_inserter& operator()(T t, BOOST_ASSIGN_PARAMS2(n) ) \
{ \
BOOST_PP_CAT(insert, BOOST_PP_INC(n))(t, BOOST_ASSIGN_PARAMS3(n), arg_type()); \
return *this; \
} \
/**/
#include BOOST_PP_LOCAL_ITERATE()
#define BOOST_PP_LOCAL_LIMITS (1, BOOST_ASSIGN_MAX_PARAMETERS)
#define BOOST_PP_LOCAL_MACRO(n) \
template< class T, BOOST_ASSIGN_PARAMS1(n) > \
void BOOST_PP_CAT(insert, BOOST_PP_INC(n))(T const& t, BOOST_ASSIGN_PARAMS2(n), single_arg_type) \
{ \
insert_( Argument(t, BOOST_ASSIGN_PARAMS3(n) )); \
} \
/**/
#include BOOST_PP_LOCAL_ITERATE()
#define BOOST_PP_LOCAL_LIMITS (1, BOOST_ASSIGN_MAX_PARAMETERS)
#define BOOST_PP_LOCAL_MACRO(n) \
template< class T, BOOST_ASSIGN_PARAMS1(n) > \
void BOOST_PP_CAT(insert, BOOST_PP_INC(n))(T const& t, BOOST_ASSIGN_PARAMS2(n), n_arg_type) \
{ \
insert_(t, BOOST_ASSIGN_PARAMS3(n) ); \
} \
/**/
#include BOOST_PP_LOCAL_ITERATE()
Function fun_private() const
{
return insert_;
}
private:
list_inserter& operator=( const list_inserter& );
Function insert_;
};
template< class Function >
inline list_inserter< Function >
make_list_inserter( Function fun )
{
return list_inserter< Function >( fun );
}
template< class Function, class Argument >
inline list_inserter<Function,Argument>
make_list_inserter( Function fun, Argument* )
{
return list_inserter<Function,Argument>( fun );
}
template< class C >
inline list_inserter< assign_detail::call_push_back<C>,
BOOST_DEDUCED_TYPENAME C::value_type >
push_back( C& c )
{
static BOOST_DEDUCED_TYPENAME C::value_type* p = 0;
return make_list_inserter( assign_detail::call_push_back<C>( c ),
p );
}
template< class C >
inline list_inserter< assign_detail::call_push_front<C>,
BOOST_DEDUCED_TYPENAME C::value_type >
push_front( C& c )
{
static BOOST_DEDUCED_TYPENAME C::value_type* p = 0;
return make_list_inserter( assign_detail::call_push_front<C>( c ),
p );
}
template< class C >
inline list_inserter< assign_detail::call_insert<C>,
BOOST_DEDUCED_TYPENAME C::value_type >
insert( C& c )
{
static BOOST_DEDUCED_TYPENAME C::value_type* p = 0;
return make_list_inserter( assign_detail::call_insert<C>( c ),
p );
}
template< class C >
inline list_inserter< assign_detail::call_push<C>,
BOOST_DEDUCED_TYPENAME C::value_type >
push( C& c )
{
static BOOST_DEDUCED_TYPENAME C::value_type* p = 0;
return make_list_inserter( assign_detail::call_push<C>( c ),
p );
}
template< class C >
inline list_inserter< assign_detail::call_add_edge<C> >
add_edge( C& c )
{
return make_list_inserter( assign_detail::call_add_edge<C>( c ) );
}
} // namespace 'assign'
} // namespace 'boost'
#undef BOOST_ASSIGN_PARAMS1
#undef BOOST_ASSIGN_PARAMS2
#undef BOOST_ASSIGN_PARAMS3
#undef BOOST_ASSIGN_MAX_PARAMETERS
#endif

View File

@@ -0,0 +1,37 @@
// Boost.Assign library
//
// Copyright Thorsten Ottosen 2003-2004. Use, modification and
// distribution is subject to the Boost Software License, Version
// 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// For more information, see http://www.boost.org/libs/assign/
//
#ifndef BOOST_ASSIGN_STD_VECTOR_HPP
#define BOOST_ASSIGN_STD_VECTOR_HPP
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif
#include <boost/assign/list_inserter.hpp>
#include <boost/config.hpp>
#include <vector>
namespace boost
{
namespace assign
{
template< class V, class A, class V2 >
inline list_inserter< assign_detail::call_push_back< std::vector<V,A> >, V >
operator+=( std::vector<V,A>& c, V2 v )
{
return push_back( c )( v );
}
}
}
#endif

View File

@@ -1 +1 @@
SUBDIRS = bind10 bindctl cfgmgr msgq host cmdctl auth parkinglot
SUBDIRS = bind10 bindctl cfgmgr msgq host cmdctl auth

View File

@@ -2,6 +2,8 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_builddir)/src/lib -I$(top_buildd
pkglibexecdir = $(libexecdir)/@PACKAGE@
CLEANFILES = *.gcno *.gcda
pkglibexec_PROGRAMS = b10-auth
b10_auth_SOURCES = auth_srv.cc auth_srv.h
b10_auth_SOURCES += common.cc common.h

View File

@@ -1,6 +1,41 @@
{
"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": []
}
]
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
bin_SCRIPTS = bind10
CLEANFILES = bind10.py
sbin_SCRIPTS = bind10
CLEANFILES = bind10
pkglibexecdir = $(libexecdir)/@PACKAGE@

View File

@@ -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)

View File

@@ -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

View File

@@ -1,5 +1,5 @@
{
"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",

View File

@@ -1,3 +1,15 @@
1. Refactor the code for bindctl.
2. Update man page for bindctl provided by jreed.
3. Add more unit tests.
4. Need Review:
bindcmd.py:
apply_config_cmd()
_validate_cmd()
complete()
cmdparse.py:
_parse_params
moduleinfo.py:
get_param_name_by_position

View File

@@ -31,6 +31,7 @@ import os, time, random, re
import getpass
from hashlib import sha1
import csv
import ast
try:
from collections import OrderedDict
@@ -60,47 +61,52 @@ class BindCmdInterpreter(Cmd):
self.modules = OrderedDict()
self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl"))
self.server_port = server_port
self.connect_to_cmd_ctrld()
self._connect_to_cmd_ctrld()
self.session_id = self._get_session_id()
def connect_to_cmd_ctrld(self):
def _connect_to_cmd_ctrld(self):
'''Connect to cmdctl in SSL context. '''
try:
self.conn = http.client.HTTPSConnection(self.server_port, cert_file='bindctl.pem')
except Exception as e:
print(e)
print("can't connect to %s, please make sure cmd-ctrld is running" % self.server_port)
print(e, "can't connect to %s, please make sure cmd-ctrld is running" %
self.server_port)
def _get_session_id(self):
'''Generate one session id for the connection. '''
rand = os.urandom(16)
now = time.time()
ip = socket.gethostbyname(socket.gethostname())
session_id = sha1(("%s%s%s" %(rand, now, ip)).encode())
session_id = session_id.hexdigest()
return session_id
digest = session_id.hexdigest()
return digest
def run(self):
'''Parse commands inputted from user and send them to cmdctl. '''
try:
ret = self.login()
if not ret:
if not self.login_to_cmdctl():
return False
# Get all module information from cmd-ctrld
self.config_data = isc.cc.data.UIConfigData(self)
self.update_commands()
self.config_data = isc.config.UIModuleCCSession(self)
self._update_commands()
self.cmdloop()
except KeyboardInterrupt:
return True
def login(self):
def login_to_cmdctl(self):
'''Login to cmdctl with the username and password inputted
from user. After login sucessfully, the username and password
will be saved in 'default_user.csv', when login next time,
username and password saved in 'default_user.csv' will be used
first.
'''
csvfile = None
bsuccess = False
try:
csvfile = open('default_user.csv')
users = csv.reader(csvfile)
for row in users:
if (len(row) < 2):
continue
param = {'username': row[0], 'password' : row[1]}
response = self.send_POST('/login', param)
data = response.read().decode()
@@ -119,10 +125,13 @@ class BindCmdInterpreter(Cmd):
return True
count = 0
csvfile = None
print("[TEMP MESSAGE]: username :root password :bind10")
while count < 3:
while True:
count = count + 1
if count > 3:
print("Too many authentication failures")
return False
username = input("Username:")
passwd = getpass.getpass()
param = {'username': username, 'password' : passwd}
@@ -134,25 +143,23 @@ class BindCmdInterpreter(Cmd):
csvfile = open('default_user.csv', 'w')
writer = csv.writer(csvfile)
writer.writerow([username, passwd])
bsuccess = True
break
csvfile.close()
return True
if count == 3:
print("Too many authentication failures")
break
if csvfile:
csvfile.close()
return bsuccess
def update_commands(self):
def _update_commands(self):
'''Get all commands of modules. '''
cmd_spec = self.send_GET('/command_spec')
if (len(cmd_spec) == 0):
print('can\'t get any command specification')
if not cmd_spec:
return
for module_name in cmd_spec.keys():
self.prepare_module_commands(module_name, cmd_spec[module_name])
self._prepare_module_commands(module_name, cmd_spec[module_name])
def send_GET(self, url, body = None):
'''Send GET request to cmdctl, session id is send with the name
'cookie' in header.
'''
headers = {"cookie" : self.session_id}
self.conn.request('GET', url, body, headers)
res = self.conn.getresponse()
@@ -160,11 +167,12 @@ class BindCmdInterpreter(Cmd):
if reply_msg:
return json.loads(reply_msg.decode())
else:
return None
return {}
def send_POST(self, url, post_param = None):
'''
'''Send GET request to cmdctl, session id is send with the name
'cookie' in header.
Format: /module_name/command_name
parameters of command is encoded as a map
'''
@@ -181,13 +189,12 @@ class BindCmdInterpreter(Cmd):
self.prompt = self.location + self.prompt_end
return stop
def prepare_module_commands(self, module_name, module_commands):
def _prepare_module_commands(self, module_name, module_commands):
module = ModuleInfo(name = module_name,
desc = "same here")
for command in module_commands:
cmd = CommandInfo(name = command["command_name"],
desc = command["command_description"],
need_inst_param = False)
desc = command["command_description"])
for arg in command["command_args"]:
param = ParamInfo(name = arg["item_name"],
type = arg["item_type"],
@@ -198,7 +205,7 @@ class BindCmdInterpreter(Cmd):
module.add_command(cmd)
self.add_module_info(module)
def validate_cmd(self, cmd):
def _validate_cmd(self, cmd):
if not cmd.module in self.modules:
raise CmdUnknownModuleSyntaxError(cmd.module)
@@ -223,7 +230,6 @@ class BindCmdInterpreter(Cmd):
list(params.keys())[0])
elif params:
param_name = None
index = 0
param_count = len(params)
for name in params:
# either the name of the parameter must be known, or
@@ -248,18 +254,17 @@ class BindCmdInterpreter(Cmd):
raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, cmd.params[name])
else:
# replace the numbered items by named items
param_name = command_info.get_param_name_by_position(name+1, index, param_count)
param_name = command_info.get_param_name_by_position(name+1, param_count)
cmd.params[param_name] = cmd.params[name]
del cmd.params[name]
elif not name in all_params:
raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, name)
param_nr = 0
for name in manda_params:
if not name in params and not param_nr in params:
raise CmdMissParamSyntaxError(cmd.module, cmd.command, name)
param_nr += 1
param_nr += 1
def _handle_cmd(self, cmd):
@@ -315,7 +320,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:
@@ -383,7 +388,7 @@ class BindCmdInterpreter(Cmd):
def _parse_cmd(self, line):
try:
cmd = BindCmdParse(line)
self.validate_cmd(cmd)
self._validate_cmd(cmd)
self._handle_cmd(cmd)
except BindCtlException as e:
print("Error! ", e)
@@ -440,17 +445,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:
@@ -484,5 +500,3 @@ class BindCmdInterpreter(Cmd):
print("received reply:", data)

View File

@@ -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

View File

@@ -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}

View File

@@ -18,61 +18,97 @@ from moduleinfo import *
from bindcmd import *
import isc
import pprint
from optparse import OptionParser, OptionValueError
__version__ = 'Bindctl'
def prepare_config_commands(tool):
module = ModuleInfo(name = "config", desc = "Configuration commands")
cmd = CommandInfo(name = "show", desc = "Show configuration", need_inst_param = False)
cmd = CommandInfo(name = "show", desc = "Show configuration")
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)
cmd = CommandInfo(name = "add", desc = "Add entry to configuration list")
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)
cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
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)
cmd = CommandInfo(name = "set", desc = "Set a configuration value")
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)
cmd = CommandInfo(name = "unset", desc = "Unset a configuration value")
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)
cmd = CommandInfo(name = "diff", desc = "Show all local changes")
module.add_command(cmd)
cmd = CommandInfo(name = "commit", desc = "Commit all local changes", need_inst_param = False)
cmd = CommandInfo(name = "revert", desc = "Revert all local changes")
module.add_command(cmd)
cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part", need_inst_param = False)
cmd = CommandInfo(name = "commit", desc = "Commit all local changes")
module.add_command(cmd)
cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part")
param = ParamInfo(name = "identifier", type="string", optional=False)
cmd.add_param(param)
module.add_command(cmd)
tool.add_module_info(module)
def check_port(option, opt_str, value, parser):
if (value < 0) or (value > 65535):
raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
parser.values.port = value
def check_addr(option, opt_str, value, parser):
ipstr = value
ip_family = socket.AF_INET
if (ipstr.find(':') != -1):
ip_family = socket.AF_INET6
try:
socket.inet_pton(ip_family, ipstr)
except:
raise OptionValueError("%s invalid ip address" % ipstr)
parser.values.addr = value
def set_bindctl_options(parser):
parser.add_option('-p', '--port', dest = 'port', type = 'int',
action = 'callback', callback=check_port,
default = '8080', help = 'port for cmdctl of bind10')
parser.add_option('-a', '--address', dest = 'addr', type = 'string',
action = 'callback', callback=check_addr,
default = '127.0.0.1', help = 'IP address for cmdctl of bind10')
if __name__ == '__main__':
try:
tool = BindCmdInterpreter("localhost:8080")
parser = OptionParser(version = __version__)
set_bindctl_options(parser)
(options, args) = parser.parse_args()
server_addr = options.addr + ':' + str(options.port)
tool = BindCmdInterpreter(server_addr)
prepare_config_commands(tool)
tool.run()
except Exception as e:
print(e)
print("Failed to connect with b10-cmdctl module, is it running?")
print(e, "\nFailed to connect with b10-cmdctl module, is it running?")

View File

@@ -37,7 +37,7 @@ 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
parameters part is optional
Example: zone reload, zone_name=example.com
module == zone
@@ -52,6 +52,7 @@ class BindCmdParse:
self._parse_cmd(cmd)
def _parse_cmd(self, text_str):
'''Parse command line. '''
# Get module name
groups = NAME_PATTERN.match(text_str)
if not groups:

View File

@@ -51,10 +51,8 @@ class CommandInfo:
more parameters
"""
def __init__(self, name, desc = "", need_inst_param = True):
def __init__(self, name, desc = ""):
self.name = name
# Wether command needs parameter "instance_name"
self.need_inst_param = need_inst_param
self.desc = desc
self.params = OrderedDict()
# Set default parameter "help"
@@ -91,7 +89,7 @@ class CommandInfo:
return [name for name in all_names
if not self.params[name].is_optional]
def get_param_name_by_position(self, pos, index, param_count):
def get_param_name_by_position(self, pos, param_count):
# count mandatories back from the last
# from the last mandatory; see the number of mandatories before it
# and compare that to the number of positional arguments left to do
@@ -101,7 +99,9 @@ class CommandInfo:
# (can this be done in all cases? this is certainly not the most efficient method;
# one way to make the whole of this more consistent is to always set mandatories first, but
# that would make some commands less nice to use ("config set value location" instead of "config set location value")
if type(pos) == int:
if type(pos) != int:
raise KeyError(str(pos) + " is not an integer")
else:
if param_count == len(self.params) - 1:
i = 0
for k in self.params.keys():
@@ -131,14 +131,9 @@ class CommandInfo:
raise KeyError(str(pos) + " out of range")
else:
raise KeyError("Too many parameters")
else:
raise KeyError(str(pos) + " is not an integer")
def need_instance_param(self):
return self.need_inst_param
def command_help(self, inst_name, inst_type, inst_desc):
def command_help(self):
print("Command ", self)
print("\t\thelp (Get help for command)")
@@ -166,65 +161,39 @@ class CommandInfo:
class ModuleInfo:
"""Define the information of one module, include module name,
module supporting commands, instance name and the value type of instance name
module supporting commands.
"""
def __init__(self, name, inst_name = "", inst_type = STRING_TYPE,
inst_desc = "", desc = ""):
def __init__(self, name, desc = ""):
self.name = name
self.inst_name = inst_name
self.inst_type = inst_type
self.inst_desc = inst_desc
self.desc = desc
self.commands = OrderedDict()
self.add_command(CommandInfo(name = "help",
desc = "Get help for module",
need_inst_param = False))
desc = "Get help for module"))
def __str__(self):
return str("%s \t%s" % (self.name, self.desc))
def add_command(self, command_info):
self.commands[command_info.name] = command_info
if command_info.need_instance_param():
command_info.add_param(ParamInfo(name = self.inst_name,
type = self.inst_type,
desc = self.inst_desc))
def has_command_with_name(self, command_name):
return command_name in self.commands
def get_command_with_name(self, command_name):
return self.commands[command_name]
def get_commands(self):
return list(self.commands.values())
def get_command_names(self):
return list(self.commands.keys())
def get_instance_param_name(self):
return self.inst_name
def get_instance_param_type(self):
return self.inst_type
def module_help(self):
print("Module ", self, "\nAvailable commands:")
for k in self.commands.keys():
print("\t", self.commands[k])
def command_help(self, command):
self.commands[command].command_help(self.inst_name,
self.inst_type,
self.inst_desc)
self.commands[command].command_help()

View File

@@ -85,16 +85,6 @@ class TestCmdLex(unittest.TestCase):
self.my_assert_raise(CmdCommandNameFormatError, "zone z-d ")
self.my_assert_raise(CmdCommandNameFormatError, "zone zdd/")
self.my_assert_raise(CmdCommandNameFormatError, "zone zdd/ \"")
def testCmdParamFormatError(self):
self.my_assert_raise(CmdParamFormatError, "zone load load")
self.my_assert_raise(CmdParamFormatError, "zone load load=")
self.my_assert_raise(CmdParamFormatError, "zone load load==dd")
self.my_assert_raise(CmdParamFormatError, "zone load , zone_name=dd zone_file=d" )
self.my_assert_raise(CmdParamFormatError, "zone load zone_name=dd zone_file" )
self.my_assert_raise(CmdParamFormatError, "zone zdd \"")
class TestCmdSyntax(unittest.TestCase):
@@ -103,18 +93,21 @@ class TestCmdSyntax(unittest.TestCase):
tool = bindcmd.BindCmdInterpreter()
zone_file_param = ParamInfo(name = "zone_file")
zone_name = ParamInfo(name = 'zone_name')
load_cmd = CommandInfo(name = "load")
load_cmd.add_param(zone_file_param)
load_cmd.add_param(zone_name)
param_master = ParamInfo(name = "master", optional = True)
param_allow_update = ParamInfo(name = "allow_update", optional = True)
set_cmd = CommandInfo(name = "set")
set_cmd.add_param(param_master)
set_cmd.add_param(param_allow_update)
set_cmd.add_param(zone_name)
reload_all_cmd = CommandInfo(name = "reload_all", need_inst_param = False)
reload_all_cmd = CommandInfo(name = "reload_all")
zone_module = ModuleInfo(name = "zone", inst_name = "zone_name")
zone_module = ModuleInfo(name = "zone")
zone_module.add_command(load_cmd)
zone_module.add_command(set_cmd)
zone_module.add_command(reload_all_cmd)
@@ -129,12 +122,12 @@ class TestCmdSyntax(unittest.TestCase):
def no_assert_raise(self, cmd_line):
cmd = cmdparse.BindCmdParse(cmd_line)
self.bindcmd.validate_cmd(cmd)
self.bindcmd._validate_cmd(cmd)
def my_assert_raise(self, exception_type, cmd_line):
cmd = cmdparse.BindCmdParse(cmd_line)
self.assertRaises(exception_type, self.bindcmd.validate_cmd, cmd)
self.assertRaises(exception_type, self.bindcmd._validate_cmd, cmd)
def testValidateSuccess(self):

View File

@@ -2,7 +2,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-cfgmgr
CLEANFILES = b10-cfgmgr.py
CLEANFILES = b10-cfgmgr
b10_cfgmgrdir = @localstatedir@/@PACKAGE@
b10_cfgmgr_DATA =

View File

@@ -31,7 +31,7 @@ if __name__ == "__main__":
cm.notify_boss()
cm.run()
except isc.cc.SessionError as se:
print("[bind-cfgd] Error creating config manager, "
print("[b10-cfgmgr] Error creating config manager, "
"is the command channel daemon running?")
except KeyboardInterrupt as kie:
print("Got ctrl-c, exit")

View File

@@ -5,10 +5,10 @@ pkglibexec_SCRIPTS = b10-cmdctl
b10_cmdctldir = $(DESTDIR)$(pkgdatadir)
b10_cmdctl_DATA = passwd.csv b10-cmdctl.pem
CLEANFILES= b10-cmdctl.py
CLEANFILES= cmdctl.py
# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cmdctl: b10-cmdctl.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cmdctl.py >$@
b10-cmdctl: cmdctl.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
chmod a+x $@

View File

@@ -38,12 +38,16 @@ import pprint
import select
import csv
import random
import time
import signal
from optparse import OptionParser, OptionValueError
from hashlib import sha1
try:
import threading
except ImportError:
import dummy_threading as threading
__version__ = 'BIND10'
URL_PATTERN = re.compile('/([\w]+)(?:/([\w]+))?/?')
# If B10_FROM_SOURCE is set in the environment, we use data files
@@ -92,7 +96,16 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
return self.session_id
def _is_user_logged_in(self):
return self.session_id in self.server.user_sessions
login_time = self.server.user_sessions.get(self.session_id)
if not login_time:
return False
idle_time = time.time() - login_time
if idle_time > self.server.idle_timeout:
return False
# Update idle time
self.server.user_sessions[self.session_id] = time.time()
return True
def _parse_request_path(self):
'''Parse the url, the legal url should like /ldh or /ldh/ldh '''
@@ -103,6 +116,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
return (groups.group(1), groups.group(2))
def do_POST(self):
'''Process POST request. '''
'''Process user login and send command to proper module
The client should send its session id in header with
the name 'cookie'
@@ -112,8 +126,10 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
if self._is_session_valid():
if self.path == '/login':
rcode, reply = self._handle_login()
else:
elif self._is_user_logged_in():
rcode, reply = self._handle_post_request()
else:
rcode, reply = http.client.UNAUTHORIZED, ["please login"]
else:
rcode, reply = http.client.BAD_REQUEST, ["session isn't valid"]
@@ -127,19 +143,22 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
return http.client.OK, ["user has already login"]
is_user_valid, error_info = self._check_user_name_and_pwd()
if is_user_valid:
self.server.user_sessions.append(self.session_id)
self.server.save_user_session_id(self.session_id)
return http.client.OK, ["login success "]
else:
return http.client.UNAUTHORIZED, error_info
def _check_user_name_and_pwd(self):
'''Check user name and its password '''
length = self.headers.get('Content-Length')
if not length:
return False, ["invalid username or password"]
user_info = json.loads((self.rfile.read(int(length))).decode())
if not user_info:
return False, ["invalid username or password"]
try:
user_info = json.loads((self.rfile.read(int(length))).decode())
except:
return False, ["invalid username or password"]
user_name = user_info.get('username')
if not user_name:
return False, ["need user name"]
@@ -158,20 +177,24 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
def _handle_post_request(self):
'''Handle all the post request from client. '''
mod, cmd = self._parse_request_path()
if (not mod) or (not cmd):
return http.client.BAD_REQUEST, ['malformed url']
param = None
len = self.headers.get('Content-Length')
rcode = http.client.OK
if len:
post_str = str(self.rfile.read(int(len)).decode())
print("command parameter:%s" % post_str)
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))
try:
post_str = str(self.rfile.read(int(len)).decode())
param = json.loads(post_str)
except:
pass
return rcode, reply
reply = self.server.send_command_to_module(mod, cmd, param)
print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod))
# TODO, need set proper rcode
return http.client.OK, reply
class CommandControl():
@@ -188,71 +211,74 @@ 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', isc.config.ccsession.COMMAND_GET_COMMANDS_SPEC)
def get_config_data(self):
return self.send_command('ConfigManager', 'get_config')
'''Get config data for all modules from configmanager '''
return self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_CONFIG)
def update_config_data(self, module_name, command_name):
if module_name == 'ConfigManager' and command_name == 'set_config':
'''Get lastest config data for all modules from configmanager '''
if module_name == 'ConfigManager' and command_name == isc.config.ccsession.COMMAND_SET_CONFIG:
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', isc.config.ccsession.COMMAND_GET_MODULE_SPEC)
def handle_recv_msg(self):
# Handle received message, if 'shutdown' is received, return False
'''Handle received message, if 'shutdown' is received, return False'''
(message, env) = self.cc.group_recvmsg(True)
while message:
if 'commands_update' in message:
self.command_spec[message['commands_update'][0]] = message['commands_update'][1]
elif 'specification_update' in message:
msgvalue = message['specification_update']
self.config_spec[msgvalue[0]] = msgvalue[1]
elif 'command' in message and message['command'][0] == 'shutdown':
return False;
command, arg = isc.config.ccsession.parse_command(message)
while command:
if command == isc.config.ccsession.COMMAND_COMMANDS_UPDATE:
self.command_spec[arg[0]] = arg[1]
elif command == isc.config.ccsession.COMMAND_SPECIFICATION_UPDATE:
self.config_spec[arg[0]] = arg[1]
elif command == "shutdown":
return False
(message, env) = self.cc.group_recvmsg(True)
command, arg = isc.config.ccsession.parse_command(message)
return True
def send_command(self, module_name, command_name, params = None):
content = [command_name]
if params:
content.append(params)
msg = {'command' : content}
def send_command(self, module_name, command_name, params = None):
'''Send the command from bindctl to proper module. '''
reply = {}
print('b10-cmdctl send command \'%s\' to %s' %(command_name, module_name))
try:
msg = isc.config.ccsession.create_command(command_name, params)
self.cc.group_sendmsg(msg, module_name)
#TODO, it may be blocked, msqg need to add a new interface
# wait in timeout.
#TODO, it may be blocked, msqg need to add a new interface waiting in timeout.
answer, env = self.cc.group_recvmsg(False)
if answer and 'result' in answer.keys() and type(answer['result']) == list:
# TODO: with the new cc implementation, replace "1" by 1
if answer['result'][0] == 1:
# todo: exception
print("Error: " + str(answer['result'][1]))
return {}
else:
self.update_config_data(module_name, command_name)
if len(answer['result']) > 1:
return answer['result'][1]
return {}
else:
print("Error: unexpected answer from %s" % module_name)
if answer:
try:
rcode, arg = isc.config.ccsession.parse_answer(answer)
if rcode == 0:
self.update_config_data(module_name, command_name)
if arg != None:
return arg
else:
# todo: exception
print("Error: " + str(answer['result'][1]))
return {}
except isc.config.ccsession.ModuleCCSessionError as mcse:
print("Error in ccsession answer: %s" % str(mcse))
print(answer)
except Exception as e:
print(e)
print('b10-cmdctl fail send command \'%s\' to %s' % (command_name, module_name))
return {}
print(e, ':b10-cmdctl fail send command \'%s\' to %s' % (command_name, module_name))
return reply
class SecureHTTPServer(http.server.HTTPServer):
'''Make the server address can be reused.'''
allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass):
def __init__(self, server_address, RequestHandlerClass, idle_timeout = 1200):
'''idle_timeout: the max idle time for login'''
http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
self.user_sessions = []
self.user_sessions = {}
self.idle_timeout = idle_timeout
self.cmdctrl = CommandControl()
self.__is_shut_down = threading.Event()
self.__serving = False
@@ -260,23 +286,25 @@ class SecureHTTPServer(http.server.HTTPServer):
self._read_user_info()
def _read_user_info(self):
# Get all username and password information
'''Read all user's name and its' password from csv file.'''
csvfile = None
try:
csvfile = open(USER_INFO_FILE)
reader = csv.reader(csvfile)
for row in reader:
self.user_infos[row[0]] = [row[1], row[2]]
except Exception as e:
print("Fail to read user information ", e)
exit(1)
finally:
if csvfile:
csvfile.close()
def save_user_session_id(self, session_id):
# Record user's id and login time.
self.user_sessions[session_id] = time.time()
def get_request(self):
'''Get client request socket and wrap it in SSL context. '''
newsocket, fromaddr = self.socket.accept()
try:
connstream = ssl.wrap_socket(newsocket,
@@ -296,18 +324,18 @@ class SecureHTTPServer(http.server.HTTPServer):
'''Currently only support the following three url GET request '''
rcode, reply = http.client.NO_CONTENT, []
if not module:
rcode = http.client.OK
if id == 'command_spec':
reply = self.cmdctrl.command_spec
rcode, reply = http.client.OK, self.cmdctrl.command_spec
elif id == 'config_data':
reply = self.cmdctrl.config_data
rcode, reply = http.client.OK, self.cmdctrl.config_data
elif id == 'config_spec':
reply = self.cmdctrl.config_spec
rcode, reply = http.client.OK, self.cmdctrl.config_spec
return rcode, reply
def serve_forever(self, poll_interval = 0.5):
'''Start cmdctl as one tcp server. '''
self.__serving = True
self.__is_shut_down.clear()
while self.__serving:
@@ -328,21 +356,69 @@ class SecureHTTPServer(http.server.HTTPServer):
def send_command_to_module(self, module_name, command_name, params):
return self.cmdctrl.send_command(module_name, command_name, params)
httpd = None
def run(server_class = SecureHTTPServer, addr = 'localhost', port = 8080):
def signal_handler(signal, frame):
if httpd:
httpd.shutdown()
sys.exit(0)
def set_signal_handler():
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
def run(addr = 'localhost', port = 8080, idle_timeout = 1200):
''' Start cmdctl as one https server. '''
print("b10-cmdctl module is starting on :%s port:%d" %(addr, port))
httpd = server_class((addr, port), SecureHTTPRequestHandler)
httpd = SecureHTTPServer((addr, port), SecureHTTPRequestHandler, idle_timeout)
httpd.serve_forever()
def check_port(option, opt_str, value, parser):
if (value < 0) or (value > 65535):
raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
parser.values.port = value
def check_addr(option, opt_str, value, parser):
ipstr = value
ip_family = socket.AF_INET
if (ipstr.find(':') != -1):
ip_family = socket.AF_INET6
try:
socket.inet_pton(ip_family, ipstr)
except:
raise OptionValueError("%s invalid ip address" % ipstr)
parser.values.addr = value
def set_cmd_options(parser):
parser.add_option('-p', '--port', dest = 'port', type = 'int',
action = 'callback', callback=check_port,
default = '8080', help = 'port cmdctl will use')
parser.add_option('-a', '--address', dest = 'addr', type = 'string',
action = 'callback', callback=check_addr,
default = '127.0.0.1', help = 'IP address cmdctl will use')
parser.add_option('-i', '--idle-timeout', dest = 'idle_timeout', type = 'int',
default = '1200', help = 'login idle time out')
if __name__ == '__main__':
try:
run()
parser = OptionParser(version = __version__)
set_cmd_options(parser)
(options, args) = parser.parse_args()
set_signal_handler()
run(options.addr, options.port, options.idle_timeout)
except isc.cc.SessionError as se:
print("[b10-cmdctl] Error creating b10-cmdctl, "
"is the command channel daemon running?")
except KeyboardInterrupt:
print("exit http server")
if httpd:
httpd.shutdown()

View File

@@ -0,0 +1,12 @@
#! /bin/sh
PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
export PYTHON_EXEC
BINDCTL_TEST_PATH=@abs_top_srcdir@/src/bin/cmdctl/unittest
PYTHONPATH=@abs_top_srcdir@/src/bin/cmdctl
export PYTHONPATH
cd ${BINDCTL_TEST_PATH}
exec ${PYTHON_EXEC} -O cmdctl_test.py $*

View File

@@ -0,0 +1,251 @@
# Copyright (C) 2009 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.
import unittest
import socket
from cmdctl import *
# Rewrite the class for unittest.
class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
def __init__(self):
pass
def send_response(self, rcode):
self.rcode = rcode
def end_headers(self):
pass
def do_GET(self):
self.wfile = open('tmp.file', 'wb')
super().do_GET()
self.wfile.close()
os.remove('tmp.file')
def do_POST(self):
self.wfile = open("tmp.file", 'wb')
super().do_POST()
self.wfile.close()
os.remove('tmp.file')
class MySecureHTTPServer(SecureHTTPServer):
def __init__(self):
self.user_sessions = {}
self.idle_timeout = 1200
self.cmdctrl = MyCommandControl()
class MyCommandControl():
def __init__(self):
self.command_spec = []
self.config_spec = []
self.config_data = []
def send_command(self, mod, cmd, param):
pass
class TestSecureHTTPRequestHandler(unittest.TestCase):
def setUp(self):
self.handler = MySecureHTTPRequestHandler()
self.handler.server = MySecureHTTPServer()
self.handler.server.user_sessions = {}
self.handler.server.user_infos = {}
self.handler.headers = {}
def test_parse_request_path(self):
self.handler.path = ''
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == None) and (cmd == None))
self.handler.path = '/abc'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == 'abc') and (cmd == None))
self.handler.path = '/abc/edf'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == 'abc') and (cmd == 'edf'))
self.handler.path = '/abc/edf/ghi'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == 'abc') and (cmd == 'edf'))
def test_parse_request_path_1(self):
self.handler.path = '/ab*c'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == 'ab') and cmd == None)
self.handler.path = '/abc/ed*fdd/ddd'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == 'abc') and cmd == 'ed')
self.handler.path = '/-*/edfdd/ddd'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == None) and (cmd == None))
self.handler.path = '/-*/edfdd/ddd'
mod, cmd = self.handler._parse_request_path()
self.assertTrue((mod == None) and (cmd == None))
def test_do_GET(self):
self.handler.do_GET()
self.assertEqual(self.handler.rcode, http.client.BAD_REQUEST)
def test_do_GET_1(self):
self.handler.headers['cookie'] = 12345
self.handler.do_GET()
self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED)
def test_do_GET_2(self):
self.handler.headers['cookie'] = 12345
self.handler.server.user_sessions[12345] = time.time() + 1000000
self.handler.path = '/how/are'
self.handler.do_GET()
self.assertEqual(self.handler.rcode, http.client.NO_CONTENT)
def test_do_GET_3(self):
self.handler.headers['cookie'] = 12346
self.handler.server.user_sessions[12346] = time.time() + 1000000
path_vec = ['command_spec', 'config_data', 'config_spec']
for path in path_vec:
self.handler.path = '/' + path
self.handler.do_GET()
self.assertEqual(self.handler.rcode, http.client.OK)
def test_user_logged_in(self):
self.handler.server.user_sessions = {}
self.handler.session_id = 12345
self.assertTrue(self.handler._is_user_logged_in() == False)
self.handler.server.user_sessions[12345] = time.time()
self.assertTrue(self.handler._is_user_logged_in())
self.handler.server.user_sessions[12345] = time.time() - 1500
self.handler.idle_timeout = 1200
self.assertTrue(self.handler._is_user_logged_in() == False)
def test_check_user_name_and_pwd(self):
self.handler.headers = {}
ret, msg = self.handler._check_user_name_and_pwd()
self.assertTrue(ret == False)
self.assertEqual(msg, ['invalid username or password'])
def test_check_user_name_and_pwd_1(self):
self.handler.rfile = open("check.tmp", 'w+b')
user_info = {'username':'root', 'password':'abc123'}
len = self.handler.rfile.write(json.dumps(user_info).encode())
self.handler.headers['Content-Length'] = len
self.handler.rfile.seek(0, 0)
self.handler.server.user_infos['root'] = ['aa', 'aaa']
ret, msg = self.handler._check_user_name_and_pwd()
self.assertTrue(ret == False)
self.assertEqual(msg, ['password doesn\'t match'])
self.handler.rfile.close()
os.remove('check.tmp')
def test_check_user_name_and_pwd_2(self):
self.handler.rfile = open("check.tmp", 'w+b')
user_info = {'username':'root', 'password':'abc123'}
len = self.handler.rfile.write(json.dumps(user_info).encode())
self.handler.headers['Content-Length'] = len - 1
self.handler.rfile.seek(0, 0)
ret, msg = self.handler._check_user_name_and_pwd()
self.assertTrue(ret == False)
self.assertEqual(msg, ['invalid username or password'])
self.handler.rfile.close()
os.remove('check.tmp')
def test_check_user_name_and_pwd_3(self):
self.handler.rfile = open("check.tmp", 'w+b')
user_info = {'usernae':'root', 'password':'abc123'}
len = self.handler.rfile.write(json.dumps(user_info).encode())
self.handler.headers['Content-Length'] = len
self.handler.rfile.seek(0, 0)
ret, msg = self.handler._check_user_name_and_pwd()
self.assertTrue(ret == False)
self.assertEqual(msg, ['need user name'])
self.handler.rfile.close()
os.remove('check.tmp')
def test_check_user_name_and_pwd_4(self):
self.handler.rfile = open("check.tmp", 'w+b')
user_info = {'username':'root', 'pssword':'abc123'}
len = self.handler.rfile.write(json.dumps(user_info).encode())
self.handler.headers['Content-Length'] = len
self.handler.rfile.seek(0, 0)
self.handler.server.user_infos['root'] = ['aa', 'aaa']
ret, msg = self.handler._check_user_name_and_pwd()
self.assertTrue(ret == False)
self.assertEqual(msg, ['need password'])
self.handler.rfile.close()
os.remove('check.tmp')
def test_check_user_name_and_pwd_5(self):
self.handler.rfile = open("check.tmp", 'w+b')
user_info = {'username':'root', 'password':'abc123'}
len = self.handler.rfile.write(json.dumps(user_info).encode())
self.handler.headers['Content-Length'] = len
self.handler.rfile.seek(0, 0)
ret, msg = self.handler._check_user_name_and_pwd()
self.assertTrue(ret == False)
self.assertEqual(msg, ['user doesn\'t exist'])
self.handler.rfile.close()
os.remove('check.tmp')
def test_do_POST(self):
self.handler.headers = {}
self.handler.do_POST()
self.assertEqual(self.handler.rcode, http.client.BAD_REQUEST)
def test_do_POST_1(self):
self.handler.headers = {}
self.handler.headers['cookie'] = 12345
self.handler.path = '/'
self.handler.do_POST()
self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED)
def test_handle_post_request(self):
self.handler.path = '/cfgmgr/revert'
self.handler.headers = {}
rcode, reply = self.handler._handle_post_request()
self.assertEqual(http.client.OK, rcode)
def test_handle_post_request_1(self):
self.handler.path = '/*d/revert'
self.handler.headers = {}
rcode, reply = self.handler._handle_post_request()
self.assertEqual(http.client.BAD_REQUEST, rcode)
def test_handle_post_request_2(self):
self.handler.rfile = open("check.tmp", 'w+b')
params = {123:'param data'}
len = self.handler.rfile.write(json.dumps(params).encode())
self.handler.headers['Content-Length'] = len
self.handler.rfile.seek(0, 0)
self.handler.rfile.close()
os.remove('check.tmp')
self.handler.path = '/d/revert'
rcode, reply = self.handler._handle_post_request()
self.assertEqual(http.client.OK, rcode)
if __name__== "__main__":
unittest.main()

View File

@@ -1,5 +1,7 @@
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_builddir)/src/lib/dns/cpp -I$(top_builddir)/include/dns/cpp -I$(top_builddir)/include -I$(top_srcdir)/ext
CLEANFILES = *.gcno *.gcda
bin_PROGRAMS = host
host_SOURCES = host.cc
host_LDADD = $(top_builddir)/src/lib/dns/cpp/.libs/libdns.a

View File

@@ -2,7 +2,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = msgq
CLEANFILES = msgq.py
CLEANFILES = msgq
# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix

View File

@@ -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

View File

@@ -215,7 +215,7 @@ 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."""
other functions."""
print("[XX] got command: ")
print(routing)
cmd = routing["type"]
@@ -251,7 +251,7 @@ class MsgQ:
sock.send(msg)
def newlname(self):
"""Generate a unique conenction identifier for this socket.
"""Generate a unique connection identifier for this socket.
This is done by using an increasing counter and the current
time."""
self.connection_counter += 1

View File

@@ -5,5 +5,4 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libauth.la
libauth_la_SOURCES = data_source.h data_source.cc
libauth_la_SOURCES += data_source_static.h data_source_static.cc
#libauth_la_SOURCES += data_source_plot.h data_source_plot.cc
libauth_la_SOURCES += query.h query.cc

View File

@@ -1,187 +0,0 @@
#include <boost/foreach.hpp>
#include <dns/rrttl.h>
#include "data_source_plot.h"
namespace isc {
namespace dns {
// this implementation returns fixed records,
// and does not allow update statements
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::data;
namespace {
const Name authors_name("authors.bind");
const Name version_name("version.bind");
}
void
DataSourceParkingLot::serve(std::string zone_name) {
zones.serve(zone_name);
}
void
DataSourceParkingLot::addARecord(std::string data) {
a_records.push_back(RdataPtr(new in::A(data)));
}
void
DataSourceParkingLot::addAAAARecord(std::string data) {
aaaa_records.push_back(RdataPtr(new in::AAAA(data)));
}
void
DataSourceParkingLot::addNSRecord(std::string data) {
ns_records.push_back(RdataPtr(new generic::NS(data)));
}
void
DataSourceParkingLot::setSOARecord(RdataPtr soa_record) {
}
void
DataSourceParkingLot::setDefaultZoneData() {
clearARecords();
clearAAAARecords();
clearNSRecords();
addARecord("127.0.0.1");
addAAAARecord("::1");
addNSRecord("ns1.parking.example");
addNSRecord("ns2.parking.example");
addNSRecord("ns3.parking.example");
}
DataSourceParkingLot::DataSourceParkingLot() {
setDefaultZoneData();
soa = RdataPtr(new generic::SOA(Name("parking.example"),
Name("noc.parking.example"),
1, 1800, 900, 604800, 86400));
}
bool
DataSourceParkingLot::hasZoneFor(const Name& name, Name &zone_name)
{
if (name == authors_name) {
zone_name = authors_name;
return (true);
} else if (name == version_name) {
zone_name = version_name;
return (true);
}
return zones.findClosest(name, zone_name);
}
SearchResult
DataSourceParkingLot::findRRsets(const isc::dns::Name& zone_name,
const isc::dns::Name& name,
const isc::dns::RRClass& clas,
const isc::dns::RRType& type) const
{
SearchResult result;
if (clas == RRClass::CH()) {
if (type == RRType::TXT()) {
if (name == authors_name) {
RRsetPtr rrset = RRsetPtr(new RRset(authors_name, RRClass::CH(),
RRType::TXT(), RRTTL(0)));
rrset->addRdata(generic::TXT("Han Feng"));
rrset->addRdata(generic::TXT("Kazunori Fujiwara"));
rrset->addRdata(generic::TXT("Michael Graff"));
rrset->addRdata(generic::TXT("Evan Hunt"));
rrset->addRdata(generic::TXT("Jelte Jansen"));
rrset->addRdata(generic::TXT("Jin Jian"));
rrset->addRdata(generic::TXT("JINMEI Tatuya"));
rrset->addRdata(generic::TXT("Naoki Kambe"));
rrset->addRdata(generic::TXT("Shane Kerr"));
rrset->addRdata(generic::TXT("Zhang Likun"));
rrset->addRdata(generic::TXT("Jeremy C. Reed"));
result.addRRset(rrset);
result.setStatus(SearchResult::success);
} else if (name == version_name) {
RRsetPtr rrset = RRsetPtr(new RRset(version_name, RRClass::CH(),
RRType::TXT(), RRTTL(0)));
rrset->addRdata(generic::TXT("BIND10 0.0.1"));
result.addRRset(rrset);
result.setStatus(SearchResult::success);
} else {
result.setStatus(SearchResult::name_not_found);
}
} else if (type == RRType::NS()) {
if (name == authors_name || name == version_name) {
RRsetPtr rrset = RRsetPtr(new RRset(name, RRClass::CH(),
RRType::NS(),
RRTTL(0)));
rrset->addRdata(generic::NS(name));
result.addRRset(rrset);
result.setStatus(SearchResult::success);
} else {
result.setStatus(SearchResult::name_not_found);
}
} else {
result.setStatus(SearchResult::name_not_found);
}
} else if (clas == RRClass::IN()) {
if (zones.contains(name)) {
RRsetPtr rrset = RRsetPtr(new RRset(name, clas, type, RRTTL(3600)));
result.setStatus(SearchResult::success);
if (type == RRType::A()) {
BOOST_FOREACH(RdataPtr a, a_records) {
rrset->addRdata(a);
}
} else if (type == RRType::AAAA()) {
BOOST_FOREACH(RdataPtr aaaa, aaaa_records) {
rrset->addRdata(aaaa);
}
} else if (type == RRType::NS()) {
BOOST_FOREACH(RdataPtr ns, ns_records) {
rrset->addRdata(ns);
}
} else if (type == RRType::SOA()) {
rrset->addRdata(soa);
}
result.addRRset(rrset);
} else {
// we don't have the name itself. Do we have the zone?
if (zones.contains(zone_name)) {
result.setStatus(SearchResult::name_not_found);
} else {
result.setStatus(SearchResult::zone_not_found);
}
}
} else {
result.setStatus(SearchResult::zone_not_found);
}
return result;
}
/// Do direct 'search' in database, no extra processing,
/// and add the resulting rrsets to the specified section
/// in the given message
/// returns the status code of the searchresult
/// Once the dns logic is moved from parkinglot to this class,
/// we should probably make this private
SearchResult::status_type
DataSourceParkingLot::addToMessage(Message& msg,
const Section& section,
const Name& zone_name,
const Name& name,
const RRClass& clas,
const RRType& type) const
{
SearchResult result = findRRsets(zone_name, name, clas, type);
BOOST_FOREACH(RRsetPtr rrset, result) {
msg.addRRset(section, rrset);
}
return result.getStatus();
}
}
}

View File

@@ -1,96 +0,0 @@
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
// $Id$
#ifndef __DATA_SOURCE_SIMPLE_H
#define __DATA_SOURCE_SIMPLE_H
#include <dns/buffer.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rrset.h>
#include <dns/message.h>
#include <cc/data.h>
#include "common.h"
#include "data_source.h"
#include "zoneset.h"
namespace isc {
namespace dns {
class DataSourceParkingLot : public DataSource {
public:
DataSourceParkingLot();
void init() {};
void close() {};
bool hasZoneFor(const Name& name, Name &zone_name);
SearchResult findRRsets(const isc::dns::Name& zone_name,
const isc::dns::Name& name,
const isc::dns::RRClass& clas,
const isc::dns::RRType& type) const;
/* move these to private (or to zoneset) and the calling functions
* from parkinglot to here? */
void serve(std::string zone_name);
void clear_zones() { zones.clear_zones(); };
void clearARecords() { a_records.clear(); };
void clearAAAARecords() { aaaa_records.clear(); };
void clearNSRecords() { ns_records.clear(); };
void addARecord(std::string data);
void addAAAARecord(std::string data);
void addNSRecord(std::string data);
void setSOARecord(isc::dns::rdata::RdataPtr soa_record);
/// Do direct 'search' in database, no extra processing,
/// and add the resulting rrsets to the specified section
/// in the given message
/// Once the dns logic is moved from parkinglot to this class,
/// we should probably make this private
SearchResult::status_type addToMessage(Message& msg,
const isc::dns::Section& section,
const isc::dns::Name& zone_name,
const isc::dns::Name& name,
const isc::dns::RRClass& clas,
const isc::dns::RRType& type) const;
private:
//
void setDefaultZoneData();
std::vector<isc::dns::rdata::RdataPtr> a_records, aaaa_records, ns_records;
isc::dns::rdata::RdataPtr soa;
ZoneSet zones;
};
}
}
#endif
// Local Variables:
// mode: c++
// End:

View File

@@ -3,6 +3,8 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/ext -Wall -Werror
lib_LIBRARIES = libcc.a
libcc_a_SOURCES = data.cc data.h session.cc session.h
CLEANFILES = *.gcno *.gcda
TESTS =
if HAVE_GTEST
TESTS += run_unittests

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -1,12 +0,0 @@
{
"port": 5300,
"zones": [
{
"zone_name": "tjeb.nl"
},
{
"zone_name": "jinmei.org"
}
]
}

View File

@@ -1,46 +0,0 @@
{
"data_specification": {
"module_name": "ParkingLot",
"config_data": [
{
"item_name": "port",
"item_type": "integer",
"item_optional": false,
"item_default": 5300
},
{
"item_name": "zones",
"item_type": "list",
"item_optional": false,
"item_default": [ ],
"list_item_spec": {
"item_name": "zone_name",
"item_type": "string",
"item_optional": false,
"item_default": ""
}
}
],
"commands": [
{
"command_name": "zone_add",
"command_args": [ {
"item_name": "zone_name",
"item_type": "string",
"item_optional": false,
"item_default": ""
} ]
},
{
"command_name": "zone_delete",
"command_args": [ {
"item_name": "zone_name",
"item_type": "string",
"item_optional": false,
"item_default": ""
} ]
}
]
}
}

View File

@@ -1,5 +1,25 @@
# 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("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
@@ -43,110 +80,43 @@ def set(element, identifier, value):
else:
cur_el[id] = {}
cur_el = cur_el[id]
cur_el[id_parts[-1]] = value
# value can be an empty list or dict, so check for None eplicitely
if value != None:
cur_el[id_parts[-1]] = value
elif id_parts[-1] in cur_el:
del cur_el[id_parts[-1]]
return element
def unset(element, identifier):
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts[:-1]:
if id in cur_el.keys():
cur_el = cur_el[id]
else:
cur_el[id] = {}
cur_el = cur_el[id]
cur_el[id_parts[-1]] = None
return element
"""Removes the element at the given identifier if it exists. Raises
a DataTypeError if element is not a dict or if identifier is not
a string. Returns the base element."""
# perhaps we can simply do with set none, and remove this whole
# function
return set(element, identifier, None)
def find_no_exc(element, identifier):
"""Returns the subelement in the given data element, returns None if not found"""
"""Returns the subelement in the given data element, returns None
if not found, or if an error occurred (i.e. this function should
never raise an exception)"""
if type(identifier) != str:
return None
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts:
if type(cur_el) == dict and id in cur_el.keys():
if (type(cur_el) == dict and id in cur_el.keys()) or id=="":
cur_el = cur_el[id]
else:
return None
return cur_el
def find_spec(element, identifier):
"""find the data definition for the given identifier
returns either a map with 'item_name' etc, or a list of those"""
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts:
if type(cur_el) == dict and id in cur_el.keys():
cur_el = cur_el[id]
elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
pass
elif type(cur_el) == list:
found = False
for cur_el_item in cur_el:
if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys():
cur_el = cur_el_item
found = True
if not found:
raise DataNotFoundError(id + " in " + str(cur_el))
else:
raise DataNotFoundError(id + " in " + str(cur_el))
return cur_el
def check_type(specification, value):
"""Returns true if the value is of the correct type given the
specification"""
if type(specification) == list:
data_type = "list"
else:
data_type = specification['item_type']
if data_type == "integer" and type(value) != int:
raise DataTypeError(str(value) + " should be an integer")
elif data_type == "real" and type(value) != double:
raise DataTypeError(str(value) + " should be a real")
elif data_type == "boolean" and type(value) != boolean:
raise DataTypeError(str(value) + " should be a boolean")
elif data_type == "string" and type(value) != str:
raise DataTypeError(str(value) + " should be a string")
elif data_type == "list":
if type(value) != list:
raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__))
else:
# todo: check subtypes etc
for element in value:
check_type(specification['list_item_spec'], element)
elif data_type == "map" and type(value) != dict:
# todo: check subtypes etc
raise DataTypeError(str(value) + " should be a map")
def spec_name_list(spec, prefix="", recurse=False):
"""Returns a full list of all possible item identifiers in the
specification (part)"""
result = []
if prefix != "" and not prefix.endswith("/"):
prefix += "/"
if type(spec) == dict:
for name in spec:
result.append(prefix + name + "/")
if recurse:
result.extend(spec_name_list(spec[name],name, recurse))
elif type(spec) == list:
for list_el in spec:
if 'item_name' in list_el:
if list_el['item_type'] == dict:
if recurse:
result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
else:
name = list_el['item_name']
if list_el['item_type'] in ["list", "map"]:
name += "/"
result.append(name)
return result
def parse_value_str(value_str):
"""Parses the given string to a native python object. If the
string cannot be parsed, it is returned. If it is not a string,
None is returned"""
if type(value_str) != str:
return None
try:
return ast.literal_eval(value_str)
except ValueError as ve:
@@ -156,181 +126,3 @@ def parse_value_str(value_str):
# simply return the string itself
return value_str
class ConfigData:
def __init__(self, specification):
self.specification = specification
self.data = {}
def get_item_list(self, identifier = None):
if identifier:
spec = find_spec(self.specification, identifier)
return spec_name_list(spec, identifier + "/")
return spec_name_list(self.specification)
def get_value(self, identifier):
"""Returns a tuple where the first item is the value at the
given identifier, and the second item is a bool which is
true if the value is an unset default"""
value = find_no_exc(self.data, identifier)
if value:
return value, False
spec = find_spec(self.specification, identifier)
if spec and 'item_default' in spec:
return spec['item_default'], True
return None, False
class UIConfigData():
def __init__(self, conn, name = ''):
self.module_name = name
data_spec = self.get_data_specification(conn)
self.config = ConfigData(data_spec)
self.get_config_data(conn)
self.config_changes = {}
def get_config_data(self, conn):
self.config.data = conn.send_GET('/config_data')
def send_changes(self, conn):
conn.send_POST('/ConfigManager/set_config', self.config_changes)
# Get latest config data
self.get_config_data(conn)
self.config_changes = {}
def get_data_specification(self, conn):
return conn.send_GET('/config_spec')
def set(self, identifier, value):
# check against definition
spec = find_spec(identifier)
check_type(spec, value)
set(self.config_changes, identifier, value)
def get_value(self, identifier):
"""Returns a three-tuple, where the first item is the value
(or None), the second is a boolean specifying whether
the value is the default value, and the third is a boolean
specifying whether the value is an uncommitted change"""
value = find_no_exc(self.config_changes, identifier)
if value:
return value, False, True
value, default = self.config.get_value(identifier)
if value:
return value, default, False
return None, False, False
def get_value_map_single(self, identifier, entry):
"""Returns a single entry for a value_map, where the value is
not a part of a bigger map"""
result_part = {}
result_part['name'] = entry['item_name']
result_part['type'] = entry['item_type']
value, default, modified = self.get_value(identifier)
# should we check type and only set int, double, bool and string here?
result_part['value'] = value
result_part['default'] = default
result_part['modified'] = modified
return result_part
def get_value_map(self, identifier, entry):
"""Returns a single entry for a value_map, where the value is
a part of a bigger map"""
result_part = {}
result_part['name'] = entry['item_name']
result_part['type'] = entry['item_type']
value, default, modified = self.get_value(identifier + "/" + entry['item_name'])
# should we check type and only set int, double, bool and string here?
result_part['value'] = value
result_part['default'] = default
result_part['modified'] = modified
return result_part
def get_value_maps(self, identifier = None):
"""Returns a list of maps, containing the following values:
name: name of the entry (string)
type: string containing the type of the value (or 'module')
value: value of the entry if it is a string, int, double or bool
modified: true if the value is a local change
default: true if the value has been changed
Throws DataNotFoundError if the identifier is bad
"""
spec = find_spec(self.config.specification, identifier)
result = []
if type(spec) == dict:
# either the top-level list of modules or a spec map
if 'item_name' in spec:
result_part = self.get_value_map_single(identifier, spec)
if result_part['type'] == "list":
values = self.get_value(identifier)[0]
if values:
for value in values:
result_part2 = {}
li_spec = spec['list_item_spec']
result_part2['name'] = li_spec['item_name']
result_part2['value'] = value
result_part2['type'] = li_spec['item_type']
result_part2['default'] = False
result_part2['modified'] = False
result.append(result_part2)
else:
result.append(result_part)
else:
for name in spec:
result_part = {}
result_part['name'] = name
result_part['type'] = "module"
result_part['value'] = None
result_part['default'] = False
result_part['modified'] = False
result.append(result_part)
elif type(spec) == list:
for entry in spec:
if type(entry) == dict and 'item_name' in entry:
result.append(self.get_value_map(identifier, entry))
return result
def add(self, identifier, value_str):
data_spec = find_spec(self.config.specification, identifier)
if (type(data_spec) != dict or "list_item_spec" not in data_spec):
raise DataTypeError(identifier + " is not a list")
value = parse_value_str(value_str)
check_type(data_spec, [value])
cur_list = find_no_exc(self.config_changes, identifier)
if not cur_list:
cur_list = find_no_exc(self.config.data, identifier)
if not cur_list:
cur_list = []
if value not in cur_list:
cur_list.append(value)
set(self.config_changes, identifier, cur_list)
def remove(self, identifier, value_str):
data_spec = find_spec(self.config.specification, identifier)
if (type(data_spec) != dict or "list_item_spec" not in data_spec):
raise DataTypeError(identifier + " is not a list")
value = parse_value_str(value_str)
check_type(data_spec, [value])
cur_list = find_no_exc(self.config_changes, identifier)
if not cur_list:
cur_list = find_no_exc(self.config.data, identifier)
if not cur_list:
cur_list = []
if value in cur_list:
cur_list.remove(value)
set(self.config_changes, identifier, cur_list)
def set(self, identifier, value_str):
data_spec = find_spec(self.config.specification, identifier)
value = parse_value_str(value_str)
check_type(data_spec, value)
set(self.config_changes, identifier, value)
def unset(self, identifier):
# todo: check whether the value is optional?
unset(self.config_changes, identifier)
def revert(self):
self.config_changes = {}
def commit(self, conn):
self.send_changes(conn)

View File

@@ -0,0 +1,106 @@
# 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.
#
# Tests for the functions in data.py
#
import unittest
import os
import data
class TestData(unittest.TestCase):
def test_merge(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
d2 = { 'a': None, 'c': { 'd': None, 'e': 3, 'f': [ 1 ] } }
d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
m12 = d1
data.merge(m12, d2)
self.assertEqual(d12, m12)
self.assertRaises(data.DataTypeError, data.merge, d1, "a")
self.assertRaises(data.DataTypeError, data.merge, 1, d2)
self.assertRaises(data.DataTypeError, data.merge, None, None)
def test_find(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
self.assertEqual(data.find(d1, ''), d1)
self.assertEqual(data.find(d1, 'a'), 'a')
self.assertEqual(data.find(d1, 'c/e'), 2)
self.assertEqual(data.find(d1, 'c/more/'), { 'data': 'here' })
self.assertEqual(data.find(d1, 'c/more/data'), 'here')
self.assertRaises(data.DataNotFoundError, data.find, d1, 'c/f')
self.assertRaises(data.DataNotFoundError, data.find, d1, 'f')
self.assertRaises(data.DataTypeError, data.find, d1, 1)
self.assertRaises(data.DataTypeError, data.find, None, 1)
self.assertRaises(data.DataTypeError, data.find, "123", "123")
self.assertEqual(data.find("123", ""), "123")
def test_set(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
data.set(d1, 'a', None)
data.set(d1, 'c/d', None)
data.set(d1, 'c/e/', 3)
data.set(d1, 'c/f', [ 1 ] )
self.assertEqual(d1, d12)
self.assertRaises(data.DataTypeError, data.set, d1, 1, 2)
self.assertRaises(data.DataTypeError, data.set, 1, "", 2)
d3 = {}
e3 = data.set(d3, "does/not/exist", 123)
self.assertEqual(d3,
{ 'does': { 'not': { 'exist': 123 } } })
self.assertEqual(e3,
{ 'does': { 'not': { 'exist': 123 } } })
def test_unset(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
data.unset(d1, 'a')
data.unset(d1, 'c/d')
data.unset(d1, 'does/not/exist')
self.assertEqual(d1, { 'b': 1, 'c': { 'e': 2 } })
def test_find_no_exc(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
self.assertEqual(data.find_no_exc(d1, ''), d1)
self.assertEqual(data.find_no_exc(d1, 'a'), 'a')
self.assertEqual(data.find_no_exc(d1, 'c/e'), 2)
self.assertEqual(data.find_no_exc(d1, 'c/more/'), { 'data': 'here' })
self.assertEqual(data.find_no_exc(d1, 'c/more/data'), 'here')
self.assertEqual(data.find_no_exc(d1, 'c/f'), None)
self.assertEqual(data.find_no_exc(d1, 'f'), None)
self.assertEqual(data.find_no_exc(d1, 1), None)
self.assertEqual(data.find_no_exc(d1, 'more/data/here'), None)
self.assertEqual(data.find_no_exc(None, 1), None)
self.assertEqual(data.find_no_exc("123", ""), "123")
self.assertEqual(data.find_no_exc("123", ""), "123")
def test_parse_value_str(self):
self.assertEqual(data.parse_value_str("1"), 1)
self.assertEqual(data.parse_value_str("True"), True)
self.assertEqual(data.parse_value_str("None"), None)
self.assertEqual(data.parse_value_str("1.1"), 1.1)
self.assertEqual(data.parse_value_str("[]"), [])
self.assertEqual(data.parse_value_str("[ 1, None, 'asdf' ]"), [ 1, None, "asdf" ])
self.assertEqual(data.parse_value_str("{}"), {})
self.assertEqual(data.parse_value_str("{ 'a': 'b', 'c': 1 }"), { 'a': 'b', 'c': 1 })
self.assertEqual(data.parse_value_str("[ a c"), "[ a c")
if __name__ == '__main__':
#if not 'CONFIG_TESTDATA_PATH' in os.environ:
# print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files")
# exit(1)
unittest.main()

View File

@@ -1,12 +1,14 @@
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/ext -Wall -Werror
lib_LIBRARIES = libcfgclient.a
libcfgclient_a_SOURCES = data_def.h data_def.cc ccsession.cc ccsession.h
libcfgclient_a_SOURCES = config_data.h config_data.cc module_spec.h module_spec.cc ccsession.cc ccsession.h
CLEANFILES = *.gcno *.gcda
TESTS =
if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = data_def_unittests.cc run_unittests.cc
run_unittests_SOURCES = module_spec_unittests.cc config_data_unittests.cc run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = libcfgclient.a $(GTEST_LDADD)

View File

@@ -34,8 +34,9 @@
#include <boost/foreach.hpp>
#include <cc/data.h>
#include <data_def.h>
#include <module_spec.h>
#include <cc/session.h>
#include <exceptions/exceptions.h>
//#include "common.h"
#include "ccsession.h"
@@ -45,12 +46,101 @@ using namespace std;
using isc::data::Element;
using isc::data::ElementPtr;
using isc::data::DataDefinition;
using isc::data::ParseError;
using isc::data::DataDefinitionError;
namespace isc {
namespace config {
/// Creates a standard config/command protocol answer message
ElementPtr
createAnswer(const int rcode)
{
ElementPtr answer = Element::createFromString("{\"result\": [] }");
ElementPtr answer_content = answer->get("result");
answer_content->add(Element::create(rcode));
return answer;
}
ElementPtr
createAnswer(const int rcode, const ElementPtr arg)
{
ElementPtr answer = Element::createFromString("{\"result\": [] }");
ElementPtr answer_content = answer->get("result");
answer_content->add(Element::create(rcode));
answer_content->add(arg);
return answer;
}
ElementPtr
createAnswer(const int rcode, const std::string& arg)
{
ElementPtr answer = Element::createFromString("{\"result\": [] }");
ElementPtr answer_content = answer->get("result");
answer_content->add(Element::create(rcode));
answer_content->add(Element::create(arg));
return answer;
}
ElementPtr
parseAnswer(int &rcode, const ElementPtr msg)
{
if (!msg->contains("result")) {
// TODO: raise CCSessionError exception
dns_throw(CCSessionError, "No result in answer message");
} else {
ElementPtr result = msg->get("result");
if (result->get(0)->getType() != Element::integer) {
dns_throw(CCSessionError, "First element of result is not an rcode in answer message");
} else if (result->get(0)->intValue() != 0 && result->get(1)->getType() != Element::string) {
dns_throw(CCSessionError, "Rcode in answer message is non-zero, but other argument is not a StringElement");
}
rcode = result->get(0)->intValue();
if (result->size() > 1) {
return result->get(1);
} else {
return ElementPtr();
}
}
}
ElementPtr
createCommand(const std::string& command, ElementPtr arg)
{
ElementPtr cmd = Element::createFromString("{}");
ElementPtr cmd_parts = Element::createFromString("[]");
cmd_parts->add(Element::create(command));
if (arg) {
cmd_parts->add(arg);
}
cmd->set("command", cmd_parts);
return cmd;
}
/// Returns "" and empty ElementPtr() if this does not
/// look like a command
const std::string
parseCommand(ElementPtr& arg, const ElementPtr command)
{
if (command->getType() == Element::map &&
command->contains("command")) {
ElementPtr cmd = command->get("command");
if (cmd->getType() == Element::list &&
cmd->size() > 0 &&
cmd->get(0)->getType() == Element::string) {
if (cmd->size() > 1) {
arg = cmd->get(1);
} else {
arg = ElementPtr();
}
return cmd->get(0)->stringValue();
}
}
arg = ElementPtr();
return "";
}
void
CommandSession::read_data_definition(const std::string& filename) {
ModuleCCSession::read_module_specification(const std::string& filename) {
std::ifstream file;
// this file should be declared in a @something@ directive
@@ -61,27 +151,27 @@ CommandSession::read_data_definition(const std::string& filename) {
}
try {
data_definition_ = DataDefinition(file, true);
module_specification_ = moduleSpecFromFile(file, true);
} catch (ParseError pe) {
cout << "Error parsing definition file: " << pe.what() << endl;
cout << "Error parsing module specification file: " << pe.what() << endl;
exit(1);
} catch (DataDefinitionError dde) {
cout << "Error reading definition file: " << dde.what() << endl;
} catch (ModuleSpecError dde) {
cout << "Error reading module specification file: " << dde.what() << endl;
exit(1);
}
file.close();
}
CommandSession::CommandSession(std::string spec_file_name,
ModuleCCSession::ModuleCCSession(std::string spec_file_name,
isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config),
isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)
) throw (isc::cc::SessionError):
session_(isc::cc::Session())
{
read_data_definition(spec_file_name);
read_module_specification(spec_file_name);
sleep(1);
module_name_ = data_definition_.getDefinition()->get("data_specification")->get("module_name")->stringValue();
module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue();
config_handler_ = config_handler;
command_handler_ = command_handler;
@@ -96,7 +186,8 @@ CommandSession::CommandSession(std::string spec_file_name,
//session_.subscribe("Boss", "*");
//session_.subscribe("statistics", "*");
// send the data specification
session_.group_sendmsg(data_definition_.getDefinition(), "ConfigManager");
ElementPtr spec_msg = createCommand("module_spec", module_specification_.getFullSpec());
session_.group_sendmsg(spec_msg, "ConfigManager");
session_.group_recvmsg(env, answer, false);
// get any stored configuration from the manager
@@ -104,27 +195,53 @@ CommandSession::CommandSession(std::string spec_file_name,
ElementPtr cmd = Element::createFromString("{ \"command\": [\"get_config\", {\"module_name\":\"" + 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)->intValue() == 0 &&
answer->get("result")->size() > 1) {
config_handler(answer->get("result")->get(1));
} else {
cout << "[XX] no result in answer" << endl;
}
int rcode;
ElementPtr new_config = parseAnswer(rcode, answer);
handleConfigUpdate(new_config);
}
}
/// Validates the new config values, if they are correct,
/// call the config handler
/// If that results in success, store the new config
ElementPtr
ModuleCCSession::handleConfigUpdate(ElementPtr new_config)
{
ElementPtr answer;
ElementPtr errors = Element::createFromString("[]");
std::cout << "handleConfigUpdate " << new_config << std::endl;
if (!config_handler_) {
answer = createAnswer(1, module_name_ + " does not have a config handler");
} else if (!module_specification_.validate_config(new_config, false, errors)) {
std::stringstream ss;
ss << "Error in config validation: ";
BOOST_FOREACH(ElementPtr error, errors->listValue()) {
ss << error->stringValue();
}
answer = createAnswer(2, ss.str());
} else {
// handle config update
std::cout << "handleConfigUpdate " << new_config << std::endl;
answer = config_handler_(new_config);
int rcode;
parseAnswer(rcode, answer);
if (rcode == 0) {
config_ = new_config;
}
}
std::cout << "end handleConfigUpdate " << new_config << std::endl;
return answer;
}
int
CommandSession::getSocket()
ModuleCCSession::getSocket()
{
return (session_.getSocket());
}
int
CommandSession::check_command()
ModuleCCSession::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
@@ -132,15 +249,10 @@ CommandSession::check_command()
if (!data->getType() == Element::map || data->contains("result")) {
return 0;
}
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::createFromString("{ \"result\": [0] }");
}
ElementPtr new_config = data->get("config_update");
answer = handleConfigUpdate(new_config);
}
if (data->contains("command")) {
if (command_handler_) {
@@ -155,3 +267,5 @@ CommandSession::check_command()
return 0;
}
}
}

View File

@@ -19,24 +19,43 @@
#include <string>
#include <config/data_def.h>
#include <config/module_spec.h>
#include <cc/session.h>
#include <cc/data.h>
class CommandSession {
namespace isc {
namespace config {
///
/// \brief A standard cc session exception that is thrown if a function
/// is there is a problem with one of the messages
///
// todo: include types and called function in the exception
class CCSessionError : public isc::Exception {
public:
CCSessionError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
///
/// \brief This modules keeps a connection to the command channel,
/// holds configuration information, and handles messages from
/// the command channel
///
class ModuleCCSession {
public:
/**
* 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.
* @param spec_file_name: The name of the file containing the
* module specification.
*/
CommandSession(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
) throw (isc::cc::SessionError);
ModuleCCSession(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
) throw (isc::cc::SessionError);
int getSocket();
/**
@@ -69,19 +88,27 @@ public:
* This protocol is very likely to change.
*/
void set_command_handler(isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)) { command_handler_ = command_handler; };
const ElementPtr getConfig() { return config_; }
private:
void read_data_definition(const std::string& filename);
void read_module_specification(const std::string& filename);
std::string module_name_;
isc::cc::Session session_;
isc::data::DataDefinition data_definition_;
ModuleSpec module_specification_;
isc::data::ElementPtr config_;
ElementPtr handleConfigUpdate(ElementPtr new_config);
isc::data::ElementPtr(*config_handler_)(isc::data::ElementPtr new_config);
isc::data::ElementPtr(*command_handler_)(isc::data::ElementPtr command);
};
ElementPtr createAnswer(const int rcode);
ElementPtr createAnswer(const int rcode, const ElementPtr arg);
ElementPtr createAnswer(const int rcode, const std::string& arg);
}
}
#endif // __CCSESSION_H
// Local Variables:

View File

@@ -0,0 +1,184 @@
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
// $Id$
#include "config_data.h"
#include <boost/foreach.hpp>
#include <string>
#include <iostream>
using namespace isc::data;
namespace isc {
namespace config {
static ElementPtr
find_spec_part(ElementPtr spec, const std::string& identifier)
{
//std::cout << "[XX] find_spec_part for " << identifier << std::endl;
if (!spec) {
dns_throw(DataNotFoundError, "Empty specification");
}
//std::cout << "in: " << std::endl << spec << std::endl;
ElementPtr spec_part = spec;
if (identifier == "") {
dns_throw(DataNotFoundError, "Empty identifier");
}
std::string id = identifier;
size_t sep = id.find('/');
while(sep != std::string::npos) {
std::string part = id.substr(0, sep);
//std::cout << "[XX] id part: " << part << std::endl;
if (spec_part->getType() == Element::list) {
bool found = false;
BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) {
if (list_el->getType() == Element::map &&
list_el->contains("item_name") &&
list_el->get("item_name")->stringValue() == part) {
spec_part = list_el;
found = true;
}
}
if (!found) {
dns_throw(DataNotFoundError, identifier);
}
}
id = id.substr(sep + 1);
sep = id.find("/");
}
if (id != "" && id != "/") {
if (spec_part->getType() == Element::list) {
bool found = false;
BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) {
if (list_el->getType() == Element::map &&
list_el->contains("item_name") &&
list_el->get("item_name")->stringValue() == id) {
spec_part = list_el;
found = true;
}
}
if (!found) {
dns_throw(DataNotFoundError, identifier);
}
} else if (spec_part->getType() == Element::map) {
if (spec_part->contains("map_item_spec")) {
bool found = false;
BOOST_FOREACH(ElementPtr list_el, spec_part->get("map_item_spec")->listValue()) {
if (list_el->getType() == Element::map &&
list_el->contains("item_name") &&
list_el->get("item_name")->stringValue() == id) {
spec_part = list_el;
found = true;
}
}
if (!found) {
dns_throw(DataNotFoundError, identifier);
}
} else {
dns_throw(DataNotFoundError, identifier);
}
}
}
//std::cout << "[XX] found spec part: " << std::endl << spec_part << std::endl;
return spec_part;
}
static void
spec_name_list(ElementPtr result, ElementPtr spec_part, std::string prefix, bool recurse = false)
{
if (spec_part->getType() == Element::list) {
BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) {
if (list_el->getType() == Element::map &&
list_el->contains("item_name")) {
std::string new_prefix = prefix;
if (prefix != "") {
new_prefix += "/";
}
new_prefix += list_el->get("item_name")->stringValue();
if (recurse && list_el->get("item_type")->stringValue() == "map") {
spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse);
} else {
if (list_el->get("item_type")->stringValue() == "map" ||
list_el->get("item_type")->stringValue() == "list"
) {
new_prefix += "/";
}
result->add(Element::create(new_prefix));
}
}
}
} else if (spec_part->getType() == Element::map && spec_part->contains("map_item_spec")) {
spec_name_list(result, spec_part->get("map_item_spec"), prefix, recurse);
}
}
ElementPtr
ConfigData::getValue(const std::string& identifier)
{
bool fake;
return getValue(fake, identifier);
}
ElementPtr
ConfigData::getValue(bool& is_default, const std::string& identifier)
{
ElementPtr value = _config->find(identifier);
if (value) {
is_default = false;
} else {
ElementPtr spec_part = find_spec_part(_module_spec.getConfigSpec(), identifier);
if (spec_part->contains("item_default")) {
value = spec_part->get("item_default");
is_default = true;
} else {
is_default = false;
value = ElementPtr();
}
}
return value;
}
/// Returns an ElementPtr pointing to a ListElement containing
/// StringElements with the names of the options at the given
/// identifier. If recurse is true, maps will be expanded as well
ElementPtr
ConfigData::getItemList(const std::string& identifier, bool recurse)
{
ElementPtr result = Element::createFromString("[]");
ElementPtr spec_part = getModuleSpec().getConfigSpec();
if (identifier != "" && identifier != "/") {
spec_part = find_spec_part(spec_part, identifier);
}
spec_name_list(result, spec_part, identifier, recurse);
return result;
}
/// Returns an ElementPtr containing a MapElement with identifier->value
/// pairs.
ElementPtr
ConfigData::getFullConfig()
{
ElementPtr result = Element::createFromString("{}");
ElementPtr items = getItemList("", true);
BOOST_FOREACH(ElementPtr item, items->listValue()) {
result->set(item->stringValue(), getValue(item->stringValue()));
}
return result;
}
}
}

View File

@@ -0,0 +1,107 @@
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
// $Id$
#ifndef __CONFIG_DATA_H
#define __CONFIG_DATA_H 1
#include <string>
#include <vector>
#include <config/module_spec.h>
#include <exceptions/exceptions.h>
namespace isc {
namespace config {
/// This exception is thrown when the caller is trying to access
/// data that doesn't exist (i.e. with an identifier that does not
/// point to anything defined in the .spec file)
class DataNotFoundError : public isc::Exception {
public:
DataNotFoundError(const char* file, size_t line, const std::string& what) :
isc::Exception(file, line, what) {}
};
class ConfigData {
public:
/// Constructs a ConfigData option with no specification and an
/// empty configuration.
ConfigData() { _config = Element::createFromString("{}"); };
/// Constructs a ConfigData option with the given specification
/// and an empty configuration.
/// \param module_spec A ModuleSpec for the relevant module
ConfigData(const ModuleSpec& module_spec) : _module_spec(module_spec) { _config = Element::createFromString("{}"); }
/// Returns the value currently set for the given identifier
/// If no value is set, the default value (as specified by the
/// .spec file) is returned. If there is no value and no default,
/// an empty ElementPtr is returned.
/// Raises a DataNotFoundError if the identifier is bad.
/// \param identifier The identifier pointing to the configuration
/// value that is to be returned
ElementPtr getValue(const std::string& identifier);
/// Returns the value currently set for the given identifier
/// If no value is set, the default value (as specified by the
/// .spec file) is returned. If there is no value and no default,
/// an empty ElementPtr is returned.
/// Raises a DataNotFoundError if the identifier is bad.
/// \param is_default will be set to true if the value is taken
/// from the specifications item_default setting,
/// false otherwise
/// \param identifier The identifier pointing to the configuration
/// value that is to be returned
ElementPtr getValue(bool &is_default, const std::string& identifier);
/// Returns the ModuleSpec associated with this ConfigData object
const ModuleSpec getModuleSpec() { return _module_spec; };
/// Set the ModuleSpec associated with this ConfigData object
void setModuleSpec(ModuleSpec module_spec) { _module_spec = module_spec; };
/// Set the local configuration (i.e. all non-default values)
/// \param config An ElementPtr pointing to a MapElement containing
/// *all* non-default configuration values. Existing values
/// will be removed.
void setLocalConfig(ElementPtr config) { _config = config; }
/// Returns the local (i.e. non-default) configuration.
/// \returns An ElementPtr pointing to a MapElement containing all
/// non-default configuration options.
ElementPtr getLocalConfig() { return _config; }
/// Returns a list of all possible configuration options as specified
/// by the ModuleSpec.
/// \param identifier If given, show the items at the given identifier
/// (iff that is also a MapElement)
/// \param recurse If true, child MapElements will be traversed to
/// add their identifiers to the result list
/// \return An ElementPtr pointing to a ListElement containing
/// StringElements that specify the identifiers at the given
/// location (or all possible identifiers if identifier==""
/// and recurse==false)
ElementPtr getItemList(const std::string& identifier = "", bool recurse = false);
/// Returns all current configuration settings (both non-default and default).
/// \return An ElementPtr pointing to a MapElement containing
/// string->value elements, where the string is the
/// full identifier of the configuration option and the
/// value is an ElementPtr with the value.
ElementPtr getFullConfig();
private:
ElementPtr _config;
ModuleSpec _module_spec;
};
}
}
#endif

View File

@@ -0,0 +1,141 @@
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
// $Id$
#include <gtest/gtest.h>
#include "config_data.h"
#include "data_def_unittests_config.h"
#include <iostream>
using namespace isc::data;
using namespace isc::config;
ConfigData
setupSpec2()
{
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec22.spec");
return ConfigData(spec2);
}
TEST(ConfigData, Creation) {
ConfigData cd = setupSpec2();
EXPECT_TRUE(true);
}
TEST(ConfigData, getValue) {
ModuleSpec spec22 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec22.spec");
ConfigData cd = ConfigData(spec22);
//std::cout << "[XX] SPEC2: " << cd.getModuleSpec().getFullSpec() << std::endl;
bool is_default;
//ElementPtr value = cd.getValue(is_default, "item1");
EXPECT_EQ(9, cd.getValue("value1")->intValue());
EXPECT_EQ(9, cd.getValue(is_default, "value1")->intValue());
EXPECT_TRUE(is_default);
EXPECT_EQ(9.9, cd.getValue("value2")->doubleValue());
EXPECT_EQ(9.9, cd.getValue(is_default, "value2")->doubleValue());
EXPECT_TRUE(is_default);
EXPECT_FALSE(cd.getValue("value3")->boolValue());
EXPECT_FALSE(cd.getValue(is_default, "value3")->boolValue());
EXPECT_TRUE(is_default);
EXPECT_EQ("default_string", cd.getValue("value4")->stringValue());
EXPECT_EQ("default_string", cd.getValue(is_default, "value4")->stringValue());
EXPECT_TRUE(is_default);
EXPECT_EQ("a", cd.getValue("value5")->get(0)->stringValue());
EXPECT_EQ("a", cd.getValue(is_default, "value5")->get(0)->stringValue());
EXPECT_TRUE(is_default);
EXPECT_EQ("b", cd.getValue("value5")->get(1)->stringValue());
EXPECT_EQ("b", cd.getValue(is_default, "value5")->get(1)->stringValue());
EXPECT_EQ("b", cd.getValue(is_default, "value5/")->get(1)->stringValue());
EXPECT_TRUE(is_default);
EXPECT_EQ("{}", cd.getValue("value6")->str());
EXPECT_EQ("{}", cd.getValue(is_default, "value6")->str());
EXPECT_EQ("{}", cd.getValue(is_default, "value6/")->str());
EXPECT_TRUE(is_default);
EXPECT_EQ("[ ]", cd.getValue("value8")->str());
EXPECT_THROW(cd.getValue("")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("/")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("no_such_item")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("value6/a")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("value6/no_such_item")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError);
EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError);
ModuleSpec spec1 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec1.spec");
ConfigData cd1 = ConfigData(spec1);
EXPECT_THROW(cd1.getValue("anything")->str(), DataNotFoundError);
}
TEST(ConfigData, setLocalConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
bool is_default;
ElementPtr my_config = Element::createFromString("{\"item1\": 2}");
ElementPtr my_config2 = Element::createFromString("{\"item6\": { \"value1\": \"a\" } }");
EXPECT_EQ("{}", cd.getValue("item6")->str());
cd.setLocalConfig(my_config);
EXPECT_EQ(2, cd.getValue(is_default, "item1")->intValue());
EXPECT_FALSE(is_default);
EXPECT_EQ("{}", cd.getValue("item6")->str());
EXPECT_EQ(1.1, cd.getValue(is_default, "item2")->doubleValue());
EXPECT_TRUE(is_default);
cd.setLocalConfig(my_config2);
EXPECT_EQ("{\"value1\": \"a\"}", cd.getValue("item6")->str());
}
TEST(ConfigData, getLocalConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
EXPECT_EQ("{}", cd.getLocalConfig()->str());
ElementPtr my_config = Element::createFromString("{\"item1\": 2}");
cd.setLocalConfig(my_config);
EXPECT_EQ("{\"item1\": 2}", cd.getLocalConfig()->str());
ElementPtr my_config2 = Element::createFromString("{\"item6\": { \"value1\": \"a\" } }");
cd.setLocalConfig(my_config2);
EXPECT_EQ("{\"item6\": {\"value1\": \"a\"}}", cd.getLocalConfig()->str());
}
TEST(ConfigData, getItemList) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/\" ]", cd.getItemList()->str());
EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str());
EXPECT_EQ("[ \"item6/value1\", \"item6/value2\" ]", cd.getItemList("item6")->str());
}
TEST(ConfigData, getFullConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
EXPECT_EQ("{\"item1\": 1, \"item2\": 1.1, \"item3\": True, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None}", cd.getFullConfig()->str());
ElementPtr my_config = Element::createFromString("{\"item1\": 2}");
cd.setLocalConfig(my_config);
EXPECT_EQ("{\"item1\": 2, \"item2\": 1.1, \"item3\": True, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None}", cd.getFullConfig()->str());
ElementPtr my_config2 = Element::createFromString("{\"item6\": { \"value1\": \"a\" } }");
cd.setLocalConfig(my_config2);
EXPECT_EQ("{\"item1\": 1, \"item2\": 1.1, \"item3\": True, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None}", cd.getFullConfig()->str());
}

View File

@@ -1,88 +0,0 @@
#ifndef _DATA_DEF_H
#define _DATA_DEF_H 1
#include <cc/data.h>
#include <sstream>
namespace isc { namespace data {
///
/// A standard DataDefinition exception that is thrown when a
/// specification is not in the correct form.
///
/// TODO: use jinmei's exception class as a base and not c_str in
/// what() there
class DataDefinitionError : public std::exception {
public:
DataDefinitionError(std::string m = "Data definition is invalid") : msg(m) {}
~DataDefinitionError() throw() {}
const char* what() const throw() { return msg.c_str(); }
private:
std::string msg;
};
///
/// The \c DataDefinition class holds a data specification.
/// Each module should have a .spec file containing the specification
/// for configuration and commands for that module.
/// This class holds that specification, and provides a function to
/// validate a set of data, to see whether it conforms to the given
/// specification
///
/// The form of the specification is described in doc/ (TODO)
///
class DataDefinition {
public:
explicit DataDefinition() {};
/// Create a \c DataDefinition instance with the given data as
/// the specification
/// \param e The Element containing the data specification
explicit DataDefinition(ElementPtr e) : definition(e) {};
/// Creates a \c DataDefinition instance from the contents
/// of the file given by file_name.
/// If check is true, and the definition is not of the correct
/// form, a DataDefinitionError is thrown. If the file could
/// not be parse, a ParseError is thrown.
/// \param file_name The file to be opened and parsed
/// \param check If true, the data definition in the file is
/// checked to be of the correct form
DataDefinition(const std::string& file_name, const bool check = true)
throw(ParseError, DataDefinitionError);
// todo: make check default false, or leave out option completely and always check?
/// Creates a \c DataDefinition instance from the given input
/// stream that contains the contents of a .spec file.
/// If check is true, and the definition is not of
/// the correct form, a DataDefinitionError is thrown. If the
/// file could not be parsed, a ParseError is thrown.
/// \param in The std::istream containing the .spec file data
/// \param check If true, the data definition is checked to be
/// of the correct form
explicit DataDefinition(std::istream& in, const bool check = true)
throw(ParseError, DataDefinitionError);
/// Returns the base \c element of the data definition contained
/// by this instance
/// \return ElementPtr Shared pointer to the data definition
const ElementPtr getDefinition() { return definition; };
// returns true if the given element conforms to this data
// definition scheme
/// Validates the given data for this specification.
/// \param data The base \c Element of the data to check
/// \return true if the data conforms to the specification,
/// false otherwise.
bool validate(const ElementPtr data);
private:
bool validate_item(const ElementPtr spec, const ElementPtr data);
bool validate_spec(const ElementPtr spec, const ElementPtr data);
bool validate_spec_list(const ElementPtr spec, const ElementPtr data);
ElementPtr definition;
};
} }
#endif // _DATA_DEF_H

View File

@@ -1,5 +1,19 @@
// 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.
#include "data_def.h"
#include "module_spec.h"
#include <sstream>
#include <iostream>
@@ -8,9 +22,14 @@
#include <boost/foreach.hpp>
// todo: add more context to thrown DataDefinitionErrors?
// todo: add more context to thrown ModuleSpecErrors?
using namespace isc::data;
namespace isc {
namespace config {
//
// static functions
//
// todo: is there a direct way to get a std::string from an enum label?
static std::string
@@ -51,7 +70,7 @@ getType_value(const std::string& type_name) {
} else if (type_name == "any") {
return Element::any;
} else {
throw DataDefinitionError(type_name + " is not a valid type name");
throw ModuleSpecError(type_name + " is not a valid type name");
}
}
@@ -62,13 +81,13 @@ check_leaf_item(const ElementPtr& spec, const std::string& name, Element::types
if (spec->get(name)->getType() == type) {
return;
} else {
throw DataDefinitionError(name + " not of type " + getType_string(type));
throw ModuleSpecError(name + " not of type " + getType_string(type));
}
} else if (mandatory) {
// todo: want parent item name, and perhaps some info about location
// in list? or just catch and throw new...
// or make this part non-throwing and check return value...
throw DataDefinitionError(name + " missing in " + spec->str());
throw ModuleSpecError(name + " missing in " + spec->str());
}
}
@@ -84,7 +103,7 @@ check_config_item(const ElementPtr& spec) {
!spec->get("item_optional")->boolValue()
);
// if list, check the list definition
// if list, check the list specification
if (getType_value(spec->get("item_type")->stringValue()) == Element::list) {
check_leaf_item(spec, "list_item_spec", Element::map, true);
check_config_item(spec->get("list_item_spec"));
@@ -99,7 +118,7 @@ check_config_item(const ElementPtr& spec) {
static void
check_config_item_list(const ElementPtr& spec) {
if (spec->getType() != Element::list) {
throw DataDefinitionError("config_data is not a list of elements");
throw ModuleSpecError("config_data is not a list of elements");
}
BOOST_FOREACH(ElementPtr item, spec->listValue()) {
check_config_item(item);
@@ -116,7 +135,7 @@ check_command(const ElementPtr& spec) {
static void
check_command_list(const ElementPtr& spec) {
if (spec->getType() != Element::list) {
throw DataDefinitionError("commands is not a list of elements");
throw ModuleSpecError("commands is not a list of elements");
}
BOOST_FOREACH(ElementPtr item, spec->listValue()) {
check_command(item);
@@ -136,45 +155,106 @@ check_data_specification(const ElementPtr& spec) {
}
}
// checks whether the given element is a valid data definition
// throws a DataDefinitionError if the specification is bad
// checks whether the given element is a valid module specification
// throws a ModuleSpecError if the specification is bad
static void
check_definition(const ElementPtr& def)
check_module_specification(const ElementPtr& def)
{
if (!def->contains("data_specification")) {
throw DataDefinitionError("Data specification does not contain data_specification element");
} else {
check_data_specification(def->get("data_specification"));
check_data_specification(def);
}
//
// Public functions
//
ModuleSpec::ModuleSpec(ElementPtr module_spec_element,
const bool check)
throw(ModuleSpecError)
{
module_specification = module_spec_element;
if (check) {
check_module_specification(module_specification);
}
}
DataDefinition::DataDefinition(const std::string& file_name,
const bool check)
throw(ParseError, DataDefinitionError) {
const ElementPtr
ModuleSpec::getCommandsSpec() const
{
if (module_specification->contains("commands")) {
return module_specification->get("commands");
} else {
return ElementPtr();
}
}
const ElementPtr
ModuleSpec::getConfigSpec() const
{
if (module_specification->contains("config_data")) {
return module_specification->get("config_data");
} else {
return ElementPtr();
}
}
const std::string
ModuleSpec::getModuleName() const
{
return module_specification->get("module_name")->stringValue();
}
bool
ModuleSpec::validate_config(const ElementPtr data, const bool full)
{
ElementPtr spec = module_specification->find("config_data");
return validate_spec_list(spec, data, full, ElementPtr());
}
bool
ModuleSpec::validate_config(const ElementPtr data, const bool full, ElementPtr errors)
{
ElementPtr spec = module_specification->find("config_data");
return validate_spec_list(spec, data, full, errors);
}
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
throw(ParseError, ModuleSpecError)
{
std::ifstream file;
file.open(file_name.c_str());
if (!file) {
std::stringstream errs;
errs << "Error opening " << file_name << ": " << strerror(errno);
throw DataDefinitionError(errs.str());
throw ModuleSpecError(errs.str());
}
definition = Element::createFromString(file, file_name);
if (check) {
check_definition(definition);
ElementPtr module_spec_element = Element::createFromString(file, file_name);
if (module_spec_element->contains("module_spec")) {
return ModuleSpec(module_spec_element->get("module_spec"), check);
} else {
throw ModuleSpecError("No module_spec in specification");
}
}
DataDefinition::DataDefinition(std::istream& in, const bool check)
throw(ParseError, DataDefinitionError) {
definition = Element::createFromString(in);
// make sure the whole structure is complete and valid
if (check) {
check_definition(definition);
ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
throw(ParseError, ModuleSpecError) {
ElementPtr module_spec_element = Element::createFromString(in);
if (module_spec_element->contains("module_spec")) {
return ModuleSpec(module_spec_element->get("module_spec"), check);
} else {
throw ModuleSpecError("No module_spec in specification");
}
}
//
// private functions
//
//
// helper functions for validation
//
@@ -210,28 +290,34 @@ check_type(ElementPtr spec, ElementPtr element)
}
bool
DataDefinition::validate_item(const ElementPtr spec, const ElementPtr data) {
ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
if (!check_type(spec, data)) {
// we should do some proper error feedback here
// std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
// std::cout << spec << std::endl;
if (errors) {
errors->add(Element::create("Type mismatch"));
}
return false;
}
if (data->getType() == Element::list) {
ElementPtr list_spec = spec->get("list_item_spec");
BOOST_FOREACH(ElementPtr list_el, data->listValue()) {
if (!check_type(list_spec, list_el)) {
if (errors) {
errors->add(Element::create("Type mismatch"));
}
return false;
}
if (list_spec->get("item_type")->stringValue() == "map") {
if (!validate_item(list_spec, list_el)) {
if (!validate_item(list_spec, list_el, full, errors)) {
return false;
}
}
}
}
if (data->getType() == Element::map) {
if (!validate_spec_list(spec->get("map_item_spec"), data)) {
if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
return false;
}
}
@@ -240,18 +326,21 @@ DataDefinition::validate_item(const ElementPtr spec, const ElementPtr data) {
// spec is a map with item_name etc, data is a map
bool
DataDefinition::validate_spec(const ElementPtr spec, const ElementPtr data) {
ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
std::string item_name = spec->get("item_name")->stringValue();
bool optional = spec->get("item_optional")->boolValue();
ElementPtr data_el;
data_el = data->get(item_name);
if (data_el) {
if (!validate_item(spec, data_el)) {
if (!validate_item(spec, data_el, full, errors)) {
return false;
}
} else {
if (!optional) {
if (!optional && full) {
if (errors) {
errors->add(Element::create("Non-optional value missing"));
}
return false;
}
}
@@ -260,23 +349,16 @@ DataDefinition::validate_spec(const ElementPtr spec, const ElementPtr data) {
// spec is a list of maps, data is a map
bool
DataDefinition::validate_spec_list(const ElementPtr spec, const ElementPtr data) {
ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
ElementPtr cur_data_el;
std::string cur_item_name;
BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) {
if (!validate_spec(cur_spec_el, data)) {
if (!validate_spec(cur_spec_el, data, full, errors)) {
return false;
}
}
return true;
}
// TODO
// this function does *not* check if the specification is in correct
// form, we should do that in the constructor
bool
DataDefinition::validate(const ElementPtr data) {
ElementPtr spec = definition->find("data_specification/config_data");
return validate_spec_list(spec, data);
}
}

View File

@@ -0,0 +1,124 @@
// 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.
#ifndef _MODULE_SPEC_H
#define _MODULE_SPEC_H 1
#include <cc/data.h>
#include <sstream>
using namespace isc::data;
namespace isc { namespace config {
///
/// A standard ModuleSpec exception that is thrown when a
/// specification is not in the correct form.
///
/// TODO: use jinmei's exception class as a base and not c_str in
/// what() there
class ModuleSpecError : public std::exception {
public:
ModuleSpecError(std::string m = "Module specification is invalid") : msg(m) {}
~ModuleSpecError() throw() {}
const char* what() const throw() { return msg.c_str(); }
private:
std::string msg;
};
///
/// The \c ModuleSpec class holds a data specification.
/// Each module should have a .spec file containing the specification
/// for configuration and commands for that module.
/// This class holds that specification, and provides a function to
/// validate a set of data, to see whether it conforms to the given
/// specification
///
/// The form of the specification is described in doc/ (TODO)
///
class ModuleSpec {
public:
explicit ModuleSpec() {};
/// Create a \c ModuleSpec instance with the given data as
/// the specification
/// \param e The Element containing the data specification
explicit ModuleSpec(ElementPtr e, const bool check = true)
throw(ModuleSpecError);
/// Returns the commands part of the specification as an
/// ElementPtr, returns an empty ElementPtr if there is none
/// \return ElementPtr Shared pointer to the commands
/// part of the specification
const ElementPtr getCommandsSpec() const;
/// Returns the configuration part of the specification as an
/// ElementPtr
/// \return ElementPtr Shared pointer to the configuration
/// part of the specification
const ElementPtr getConfigSpec() const;
/// Returns the full module specification as an ElementPtr
/// \return ElementPtr Shared pointer to the specification
const ElementPtr getFullSpec() const { return module_specification; };
/// Returns the module name as specified by the specification
const std::string getModuleName() const;
// returns true if the given element conforms to this data
// configuration specification
/// Validates the given configuration data for this specification.
/// \param data The base \c Element of the data to check
/// \return true if the data conforms to the specification,
/// false otherwise.
bool validate_config(const ElementPtr data, const bool full = false);
/// errors must be of type ListElement
bool validate_config(const ElementPtr data, const bool full, ElementPtr errors);
private:
bool validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors);
bool validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors);
bool validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors);
ElementPtr module_specification;
};
/// Creates a \c ModuleSpec instance from the contents
/// of the file given by file_name.
/// If check is true, and the module specification is not of
/// the correct form, a ModuleSpecError is thrown. If the file
/// could not be parse, a ParseError is thrown.
/// \param file_name The file to be opened and parsed
/// \param check If true, the module specification in the file
/// is checked to be of the correct form
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check = true)
throw(ParseError, ModuleSpecError);
/// Creates a \c ModuleSpec instance from the given input
/// stream that contains the contents of a .spec file.
/// If check is true, and the module specification is not of
/// the correct form, a ModuleSpecError is thrown. If the
/// file could not be parsed, a ParseError is thrown.
/// \param in The std::istream containing the .spec file data
/// \param check If true, the module specification is checked
/// to be of the correct form
ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check = true)
throw(ParseError, ModuleSpecError);
} }
#endif // _DATA_DEF_H

View File

@@ -16,111 +16,119 @@
#include <gtest/gtest.h>
#include <data_def.h>
#include <module_spec.h>
#include <fstream>
#include "data_def_unittests_config.h"
using namespace isc::data;
using namespace isc::config;
std::string specfile(const std::string name) {
return std::string(TEST_DATA_PATH) + "/" + name;
}
void
data_def_error(const std::string& file,
module_spec_error(const std::string& file,
const std::string& error1,
const std::string& error2 = "",
const std::string& error3 = "")
{
EXPECT_THROW(DataDefinition(specfile(file)), DataDefinitionError);
EXPECT_THROW(moduleSpecFromFile(specfile(file)), ModuleSpecError);
try {
DataDefinition dd = DataDefinition(specfile(file));
} catch (DataDefinitionError dde) {
ModuleSpec dd = moduleSpecFromFile(specfile(file));
} catch (ModuleSpecError dde) {
std::string ddew = dde.what();
EXPECT_EQ(error1 + error2 + error3, ddew);
}
}
TEST(DataDefinition, ReadingSpecfiles) {
TEST(ModuleSpec, ReadingSpecfiles) {
// Tests whether we can open specfiles and if we get the
// right parse errors
DataDefinition dd = DataDefinition(specfile("spec1.spec"));
EXPECT_EQ(dd.getDefinition()->get("data_specification")
->get("module_name")
->stringValue(), "Spec1");
dd = DataDefinition(specfile("spec2.spec"));
EXPECT_EQ(dd.getDefinition()->get("data_specification")
->get("config_data")->size(), 6);
data_def_error("doesnotexist",
ModuleSpec dd = moduleSpecFromFile(specfile("spec1.spec"));
EXPECT_EQ(dd.getFullSpec()->get("module_name")
->stringValue(), "Spec1");
dd = moduleSpecFromFile(specfile("spec2.spec"));
EXPECT_EQ(dd.getFullSpec()->get("config_data")->size(), 6);
module_spec_error("doesnotexist",
"Error opening ",
specfile("doesnotexist"),
": No such file or directory");
dd = moduleSpecFromFile(specfile("spec2.spec"));
EXPECT_EQ("[ {\"command_args\": [ {\"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": False, \"item_type\": \"string\"} ], \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\"}, {\"command_args\": [ ], \"command_description\": \"Shut down BIND 10\", \"command_name\": \"shutdown\"} ]", dd.getCommandsSpec()->str());
EXPECT_EQ("Spec2", dd.getModuleName());
std::ifstream file;
file.open(specfile("spec1.spec").c_str());
dd = DataDefinition(file);
EXPECT_EQ(dd.getDefinition()->get("data_specification")
->get("module_name")
->stringValue(), "Spec1");
dd = moduleSpecFromFile(file);
EXPECT_EQ(dd.getFullSpec()->get("module_name")
->stringValue(), "Spec1");
EXPECT_EQ(dd.getCommandsSpec(), ElementPtr());
std::ifstream file2;
file2.open(specfile("spec8.spec").c_str());
EXPECT_THROW(moduleSpecFromFile(file2), ModuleSpecError);
}
TEST(DataDefinition, SpecfileItems) {
data_def_error("spec3.spec",
TEST(ModuleSpec, SpecfileItems) {
module_spec_error("spec3.spec",
"item_name missing in {\"item_default\": 1, \"item_optional\": False, \"item_type\": \"integer\"}");
data_def_error("spec4.spec",
module_spec_error("spec4.spec",
"item_type missing in {\"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": False}");
data_def_error("spec5.spec",
module_spec_error("spec5.spec",
"item_optional missing in {\"item_default\": 1, \"item_name\": \"item1\", \"item_type\": \"integer\"}");
data_def_error("spec6.spec",
module_spec_error("spec6.spec",
"item_default missing in {\"item_name\": \"item1\", \"item_optional\": False, \"item_type\": \"integer\"}");
data_def_error("spec9.spec",
module_spec_error("spec9.spec",
"item_default not of type integer");
data_def_error("spec10.spec",
module_spec_error("spec10.spec",
"item_default not of type real");
data_def_error("spec11.spec",
module_spec_error("spec11.spec",
"item_default not of type boolean");
data_def_error("spec12.spec",
module_spec_error("spec12.spec",
"item_default not of type string");
data_def_error("spec13.spec",
module_spec_error("spec13.spec",
"item_default not of type list");
data_def_error("spec14.spec",
module_spec_error("spec14.spec",
"item_default not of type map");
data_def_error("spec15.spec",
module_spec_error("spec15.spec",
"badname is not a valid type name");
}
TEST(DataDefinition, SpecfileConfigData)
TEST(ModuleSpec, SpecfileConfigData)
{
data_def_error("spec7.spec",
module_spec_error("spec7.spec",
"module_name missing in {}");
data_def_error("spec8.spec",
"Data specification does not contain data_specification element");
data_def_error("spec16.spec",
module_spec_error("spec8.spec",
"No module_spec in specification");
module_spec_error("spec16.spec",
"config_data is not a list of elements");
data_def_error("spec21.spec",
module_spec_error("spec21.spec",
"commands is not a list of elements");
}
TEST(DataDefinition, SpecfileCommands)
TEST(ModuleSpec, SpecfileCommands)
{
data_def_error("spec17.spec",
module_spec_error("spec17.spec",
"command_name missing in {\"command_args\": [ {\"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": False, \"item_type\": \"string\"} ], \"command_description\": \"Print the given message to stdout\"}");
data_def_error("spec18.spec",
module_spec_error("spec18.spec",
"command_args missing in {\"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\"}");
data_def_error("spec19.spec",
module_spec_error("spec19.spec",
"command_args not of type list");
data_def_error("spec20.spec",
module_spec_error("spec20.spec",
"somethingbad is not a valid type name");
/*
data_def_error("spec22.spec",
module_spec_error("spec22.spec",
"");
*/
}
bool
data_test(DataDefinition dd, const std::string& data_file_name)
data_test(ModuleSpec dd, const std::string& data_file_name)
{
std::ifstream data_file;
@@ -128,11 +136,23 @@ data_test(DataDefinition dd, const std::string& data_file_name)
ElementPtr data = Element::createFromString(data_file, data_file_name);
data_file.close();
return dd.validate(data);
return dd.validate_config(data);
}
TEST(DataDefinition, DataValidation) {
DataDefinition dd = DataDefinition(specfile("spec22.spec"));
bool
data_test_with_errors(ModuleSpec dd, const std::string& data_file_name, ElementPtr errors)
{
std::ifstream data_file;
data_file.open(specfile(data_file_name).c_str());
ElementPtr data = Element::createFromString(data_file, data_file_name);
data_file.close();
return dd.validate_config(data, true, errors);
}
TEST(ModuleSpec, DataValidation) {
ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec"));
EXPECT_TRUE(data_test(dd, "data22_1.data"));
EXPECT_FALSE(data_test(dd, "data22_2.data"));
@@ -142,4 +162,8 @@ TEST(DataDefinition, DataValidation) {
EXPECT_TRUE(data_test(dd, "data22_6.data"));
EXPECT_TRUE(data_test(dd, "data22_7.data"));
EXPECT_FALSE(data_test(dd, "data22_8.data"));
ElementPtr errors = Element::createFromString("[]");
EXPECT_FALSE(data_test_with_errors(dd, "data22_8.data", errors));
EXPECT_EQ("[ \"Type mismatch\" ]", errors->str());
}

View File

@@ -0,0 +1,128 @@
Configuration and commands in BIND10
Introduction
Overview
C++ API for modules
Python API for modules
Specification files
Introduction
------------
One of the goals of BIND 10 was to have 'live' configuration; to be able to change settings on the fly, and have the system remember those settings automatically, much like, for instance, a router operates.
In order for this to work, it is important that all modules have a way of dynamically handling configuration updates. The way this works is explained in this document.
Overview
--------
Central to the configuration part is the Configuraion Manager b10-cfgmgr. The configuration manager loads any existing configuration, receives configuration updates from user interfaces, and notifies modules about configuration updates.
UI <---UIAPI---> Configuration Manager <---ModuleAPI---> Module
<---ModuleAPI---> Module
<---ModuleAPI---> Module
When a configuration changes comes in from a UI, the configuration manager
sends a message to the channel of the module of which the configuration is a part of.
Through the Module API, the module automatically picks this up, and validates it.
If it doesn't validate, an error is sent back to the manager, and subsequently to the UI (though this is not implemented yet).
If it does, a callback function specified by the module itself is called, with the new non-default configuration values.
We are planning to do a sort of diff-like thing here, but the current version only handles full non-default configuration values.
The callback function returns a messge containing either success or failure, and on success, the new configuration is locally stored in the modules session. Plans are to make this optional, so that modules have two choices; they can have the configuration stored for random access later, or they can run through the configuration when there is a changes, modify their internal structures, and then drop the full configuration. This makes handling configuration updates more complicated, but is more efficient assuming that configuration values are much more often read than written.
Commands are handled in a similar way, but do not go through the configuration manager.
C++ API For modules
-------------------
The important class for modules is isc::config::ModuleCCSession; this is the class that manages the connection to the command channel, stores the current configuration, validates new configurations, and calls callback functions as needed.
[link to ModuleCCSession doxygen html]
Upon initialization, the module provides it with a path to a specification file. This specification file contains information about the module, the configuration option the module has, and the direct commands the modules accepts. See the chapter 'specification files' for more information on these.
The module also needs to provide two callback functions; one for handling configuration updates, and one for handling commands.
The function for handling configuration updates has the following signature:
isc::data::ElementPtr my_config_handler(isc::data::ElementPtr new_config);
[link to Element doxygen html]
The new_config is a ElementPtr pointing to a MapElement containing data in the form as specified by the specification file. It only contains non-default values, though that includes values that are set by the administrator which happens to be the same as the default).
The module can walk through this set and alter it's behaviour accordingly if necessary. It can also simply return success (see below) and reference the needed configuration values directly when necessary by calling get_config_value(std::string identifier).
The callback function must return an answer message, which is created with isc::config::createAnswer(). For successful handling of the configuration, it should return the result of createAnswer(0) (0 being the result code for success). If there is a problem, the function can return the result of createAnswer(non-zero, "string_with_error_message"). In this case, the new configuration is not stored, and the error is fed back to the configuration manager.
Direct commands work much the same way, only in this case the answer returned can also contain an ElementPtr with data specific to the command.
The checking of new commands or configuration updates must be done manually, with the checkCommand() function. If this function is called, the moduleccsession class checks the command channel for any relevant messages, and handles them accordingly.
Python API for modules
----------------------
The class to use in python modules is isc.config.ccsession.ModuleCCSession
[link to doxygen python version]
Again, the module initializes it with the path to a specification file, and two callback functions.
It works much the same as the C++ version.
The callback function for configuration updates has the form
answer my_config_handler(new_config)
Since python has dynamic typing, there is no specific class for the data that is passed to the handler, but it is a dict containing data as specified in the specification file.
There are however a few convenience functions that can be found in the isc.config.data module.
The answer can be created with isc.config.ccsession.create_answer(rcode, message), where rcode=0 is interpreted as success, and message can contain an error message for rcode!=0 results.
The function to trigger update checks is check_command()
Specification files
-------------------
There is only 1 real mandatory element in the specification, and that is the name of the module.
The simplest specification file therefore looks like this:
{
"module_spec": {
"module_name": "my_module"
}
}
This is the specification for a module that has no commands and no configuration options.
my_module is the name of the module, and also the name of the command channel it will automatically subscribe to.
To add a simple configuration option, let's say an int, we make it the following:
{
"module_spec": {
"module_name": "my_module"
"config_data": [
{ "item_name": "some_number",
"item_type": "integer",
"item_optional": False,
"item_default": 123
}
]
}
}
"config_data" contains a list of elements of the form
{ "item_name": "name"
"item_type": "integer|real|boolean|string|list|map"
"item_optional": True|False
"item_default": <depends on type>
}
You can provide as much options as you need, and can also make compound elements
through "list_item_spec" for lists and "map_item_spec" for maps. See [link] for the
full documentation on specification files.

View File

@@ -1,4 +1,4 @@
PY_MODULES= __init__.py ccsession.py datadefinition.py cfgmgr.py
PY_MODULES= __init__.py ccsession.py cfgmgr.py config_data.py module_spec.py
install-data-local:
$(mkinstalldirs) $(DESTDIR)$(pyexecdir)/isc/config

View File

@@ -1,2 +1,3 @@
from isc.config.ccsession import *
from isc.config.datadefinition import *
from isc.config.config_data import *
from isc.config.module_spec import *

View File

@@ -21,14 +21,118 @@
# modeled after ccsession.h/cc 'protocol' changes here need to be
# made there as well
"""Classes and functions for handling configuration and commands
This module provides the ModuleCCSession and UICCSession classes,
as well as a set of utility functions to create and parse messages
related to commands and configuration
Modules should use the ModuleCCSession class to connect to the
configuration manager, and receive updates and commands from
other modules.
Configuration user interfaces should use the UICCSession to connect
to b10-cmdctl, and receive and send configuration and commands
through that to the configuration manager.
"""
from isc.cc import Session
from isc.config.config_data import ConfigData, MultiConfigData
import isc
class CCSession:
class ModuleCCSessionError(Exception): pass
def parse_answer(msg):
"""Returns a tuple (rcode, value), where value depends on the
command that was called. If rcode != 0, value is a string
containing an error message"""
if type(msg) != dict:
raise ModuleCCSessionError("Answer message is not a dict: " + str(msg))
if 'result' not in msg:
raise ModuleCCSessionError("answer message does not contain 'result' element")
elif type(msg['result']) != list:
raise ModuleCCSessionError("wrong result type in answer message")
elif len(msg['result']) < 1:
raise ModuleCCSessionError("empty result list in answer message")
elif type(msg['result'][0]) != int:
raise ModuleCCSessionError("wrong rcode type in answer message")
else:
if len(msg['result']) > 1:
return msg['result'][0], msg['result'][1]
else:
return msg['result'][0], None
def create_answer(rcode, arg = None):
"""Creates an answer packet for config&commands. rcode must be an
integer. If rcode == 0, arg is an optional value that depends
on what the command or option was. If rcode != 0, arg must be
a string containing an error message"""
if type(rcode) != int:
raise ModuleCCSessionError("rcode in create_answer() must be an integer")
if rcode != 0 and type(arg) != str:
raise ModuleCCSessionError("arg in create_answer for rcode != 0 must be a string describing the error")
if arg != None:
return { 'result': [ rcode, arg ] }
else:
return { 'result': [ rcode ] }
# 'fixed' commands
"""Fixed names for command and configuration messages"""
COMMAND_COMMANDS_UPDATE = "commands_update"
COMMAND_SPECIFICATION_UPDATE = "specification_update"
COMMAND_GET_COMMANDS_SPEC = "get_commands_spec"
COMMAND_GET_CONFIG = "get_config"
COMMAND_SET_CONFIG = "set_config"
COMMAND_GET_MODULE_SPEC = "get_module_spec"
COMMAND_MODULE_SPEC = "module_spec"
def parse_command(msg):
"""Parses what may be a command message. If it looks like one,
the function returns (command, value) where command is a
string. If it is not, this function returns None, None"""
if type(msg) == dict and len(msg.items()) == 1:
cmd, value = msg.popitem()
if cmd == "command" and type(value) == list:
if len(value) == 1:
return value[0], None
elif len(value) > 1:
return value[0], value[1]
return None, None
def create_command(command_name, params = None):
"""Creates a module command message with the given command name (as
specified in the module's specification, and an optional params
object"""
# TODO: validate_command with spec
cmd = [ command_name ]
if params:
cmd.append(params)
msg = { 'command': cmd }
return msg
class ModuleCCSession(ConfigData):
"""This class maintains a connection to the command channel, as
well as configuration options for modules. The module provides
a specification file that contains the module name, configuration
options, and commands. It also gives the ModuleCCSession two callback
functions, one to call when there is a direct command to the
module, and one to update the configuration run-time. These
callbacks are called when 'check_command' is called on the
ModuleCCSession"""
def __init__(self, spec_file_name, config_handler, command_handler):
self._data_definition = isc.config.DataDefinition(spec_file_name)
self._module_name = self._data_definition.get_module_name()
"""Initialize a ModuleCCSession. This does *NOT* send the
specification and request the configuration yet. Use start()
for that once the ModuleCCSession has been initialized.
specfile_name is the path to the specification file
config_handler and command_handler are callback functions,
see set_config_handler and set_command_handler for more
information on their signatures."""
module_spec = isc.config.module_spec_from_file(spec_file_name)
ConfigData.__init__(self, module_spec)
self._module_name = module_spec.get_module_name()
self.set_config_handler(config_handler)
self.set_command_handler(command_handler)
@@ -36,65 +140,163 @@ class CCSession:
self._session = Session()
self._session.group_subscribe(self._module_name, "*")
def start(self):
"""Send the specification for this module to the configuration
manager, and request the current non-default configuration.
The config_handler will be called with that configuration"""
self.__send_spec()
self.__get_full_config()
self.__request_config()
def get_socket(self):
"""Returns the socket from the command channel session"""
"""Returns the socket from the command channel session. This can
be used in select() loops to see if there is anything on the
channel. This is not strictly necessary as long as
check_command is called periodically."""
return self._session._socket
def get_session(self):
"""Returns the command-channel session that is used, so the
application can use it directly"""
application can use it directly."""
return self._session
def close(self):
"""Close the session to the command channel"""
self._session.close()
def check_command(self):
"""Check whether there is a command on the channel.
Call the command callback function if so"""
"""Check whether there is a command or configuration update
on the channel. Call the corresponding callback function if
there is."""
msg, env = self._session.group_recvmsg(False)
answer = None
# should we default to an answer? success-by-default? unhandled error?
if msg:
if "config_update" in msg and self._config_handler:
self._config_handler(msg["config_update"])
answer = { "result": [ 0 ] }
if "command" in msg and self._command_handler:
answer = self._command_handler(msg["command"])
if answer:
self._session.group_reply(env, answer)
answer = None
try:
if "config_update" in msg:
new_config = msg["config_update"]
errors = []
if not self._config_handler:
answer = create_answer(2, self._module_name + " has no config handler")
elif not self.get_module_spec().validate_config(False, new_config, errors):
answer = create_answer(1, " ".join(errors))
else:
answer = self._config_handler(msg["config_update"])
if "command" in msg and self._command_handler:
answer = self._command_handler(msg["command"])
except Exception as exc:
answer = create_answer(1, str(exc))
if answer:
self._session.group_reply(env, answer)
def set_config_handler(self, config_handler):
"""Set the config handler for this module. The handler is a
function that takes the full configuration and handles it.
It should return either { "result": [ 0 ] } or
{ "result": [ <error_number>, "error message" ] }"""
It should return an answer created with create_answer()"""
self._config_handler = config_handler
# should we run this right now since we've changed the handler?
def set_command_handler(self, command_handler):
"""Set the command handler for this module. The handler is a
function that takes a command as defined in the .spec file
and return either { "result": [ 0, (result) ] } or
{ "result": [ <error_number>. "error message" ] }"""
and return an answer created with create_answer()"""
self._command_handler = command_handler
def __send_spec(self):
"""Sends the data specification to the configuration manager"""
self._session.group_sendmsg(self._data_definition.get_definition(), "ConfigManager")
msg = create_command(COMMAND_MODULE_SPEC, self.get_module_spec().get_full_spec())
self._session.group_sendmsg(msg, "ConfigManager")
answer, env = self._session.group_recvmsg(False)
def __get_full_config(self):
def __request_config(self):
"""Asks the configuration manager for the current configuration, and call the config handler if set"""
self._session.group_sendmsg({ "command": [ "get_config", { "module_name": self._module_name } ] }, "ConfigManager")
answer, env = self._session.group_recvmsg(False)
if "result" in answer:
if answer["result"][0] == 0 and len(answer["result"]) > 1:
new_config = answer["result"][1]
if self._data_definition.validate(new_config):
self._config = new_config;
if self._config_handler:
self._config_handler(answer["result"])
rcode, value = parse_answer(answer)
if rcode == 0:
if value != None and self.get_module_spec().validate_config(False, value):
self.set_local_config(value);
if self._config_handler:
self._config_handler(value)
else:
# log error
print("Error requesting configuration: " + value)
class UIModuleCCSession(MultiConfigData):
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
configuration settings through the b10-cmdctl module."""
def __init__(self, conn):
"""Initialize a UIModuleCCSession. The conn object that is
passed must have send_GET and send_POST functions"""
MultiConfigData.__init__(self)
self._conn = conn
self.request_specifications()
self.request_current_config()
def request_specifications(self):
"""Request the module specifications from b10-cmdctl"""
# this step should be unnecessary but is the current way cmdctl returns stuff
# so changes are needed there to make this clean (we need a command to simply get the
# full specs for everything, including commands etc, not separate gets for that)
specs = self._conn.send_GET('/config_spec')
commands = self._conn.send_GET('/commands')
for module in specs.keys():
cur_spec = { 'module_name': module }
if module in specs and specs[module]:
cur_spec['config_data'] = specs[module]
if module in commands and commands[module]:
cur_spec['commands'] = commands[module]
self.set_specification(isc.config.ModuleSpec(cur_spec))
def request_current_config(self):
"""Requests the current configuration from the configuration
manager through b10-cmdctl, and stores those as CURRENT"""
config = self._conn.send_GET('/config_data')
if 'version' not in config or config['version'] != 1:
raise Exception("Bad config version")
self._set_current_config(config)
def add_value(self, identifier, value_str):
"""Add a value to a configuration list. Raises a DataTypeError
if the value does not conform to the list_item_spec field
of the module config data specification"""
module_spec = self.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise DataTypeError(identifier + " is not a list")
value = isc.cc.data.parse_value_str(value_str)
cur_list, status = self.get_value(identifier)
if not cur_list:
cur_list = []
if value not in cur_list:
cur_list.append(value)
self.set_value(identifier, cur_list)
def remove_value(self, identifier, value_str):
"""Remove a value from a configuration list. The value string
must be a string representation of the full item. Raises
a DataTypeError if the value at the identifier is not a list,
or if the given value_str does not match the list_item_spec
"""
module_spec = self.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise DataTypeError(identifier + " is not a list")
value = isc.cc.data.parse_value_str(value_str)
isc.config.config_data.check_type(module_spec, [value])
cur_list, status = self.get_value(identifier)
#if not cur_list:
# cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
if not cur_list:
cur_list = []
if value in cur_list:
cur_list.remove(value)
self.set_value(identifier, cur_list)
def commit(self):
"""Commit all local changes, send them through b10-cmdctl to
the configuration manager"""
if self.get_local_changes():
self._conn.send_POST('/ConfigManager/set_config', [ self.get_local_changes() ])
# todo: check result
self.request_current_config()
self.clear_local_changes()

View File

@@ -1,3 +1,24 @@
# 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.
"""This is the BIND 10 configuration manager, run by b10-cfgmgr.
It stores the system configuration, and sends updates of the
configuration to the modules that need them.
"""
import isc
import signal
import ast
@@ -5,21 +26,42 @@ import pprint
import os
from isc.cc import data
class ConfigManagerDataReadError(Exception):
"""This exception is thrown when there is an error while reading
the current configuration on startup."""
pass
class ConfigManagerDataEmpty(Exception):
"""This exception is thrown when the currently stored configuration
is not found, or appears empty."""
pass
class ConfigManagerData:
"""This class hold the actual configuration information, and
reads it from and writes it to persistent storage"""
CONFIG_VERSION = 1
def __init__(self, data_path):
def __init__(self, data_path, file_name = "b10-config.db"):
"""Initialize the data for the configuration manager, and
set the version and path for the data store. Initializing
this does not yet read the database, a call to
read_from_file is needed for that."""
self.data = {}
self.data['version'] = ConfigManagerData.CONFIG_VERSION
self.data_path = data_path
self.db_filename = data_path + "/b10-config.db"
self.db_filename = data_path + os.sep + file_name
def set_data_definition(self, module_name, module_data_definition):
self.zones[module_name] = module_data_definition
self.data_definitions[module_name] = module_data_definition
def read_from_file(data_path):
config = ConfigManagerData(data_path)
def read_from_file(data_path, file_name = "b10-config.db"):
"""Read the current configuration found in the file at
data_path. If the file does not exist, a
ConfigManagerDataEmpty exception is raised. If there is a
parse error, or if the data in the file has the wrong
version, a ConfigManagerDataReadError is raised. In the first
case, it is probably safe to log and ignore. In the case of
the second exception, the best way is probably to report the
error and stop loading the system."""
config = ConfigManagerData(data_path, file_name)
try:
file = open(config.db_filename, 'r')
file_config = ast.literal_eval(file.read())
@@ -27,19 +69,20 @@ class ConfigManagerData:
file_config['version'] == ConfigManagerData.CONFIG_VERSION:
config.data = file_config
else:
# of course we can put in a migration path here for old data
print("[bind-cfgd] Old version of data found, starting with empty configuration")
# We can put in a migration path here for old data
raise ConfigManagerDataReadError("[b10-cfgmgr] Old version of data found")
file.close()
except IOError as ioe:
print("No config file found, starting with empty config")
except EOFError as eofe:
print("Config file empty, starting with empty config")
raise ConfigManagerDataEmpty("No config file found")
except:
print("Config file unreadable, starting with empty config")
raise ConfigManagerDataReadError("Config file unreadable")
return config
def write_to_file(self):
def write_to_file(self, output_file_name = None):
"""Writes the current configuration data to a file. If
output_file_name is not specified, the file used in
read_from_file is used."""
try:
tmp_filename = self.db_filename + ".tmp"
file = open(tmp_filename, 'w');
@@ -48,145 +91,256 @@ class ConfigManagerData:
file.write(s)
file.write("\n")
file.close()
os.rename(tmp_filename, self.db_filename)
if output_file_name:
os.rename(tmp_filename, output_file_name)
else:
os.rename(tmp_filename, self.db_filename)
except IOError as ioe:
print("Unable to write config file; configuration not stored")
# TODO: log this (level critical)
print("[b10-cfgmgr] Unable to write config file; configuration not stored: " + str(ioe))
except OSError as ose:
# TODO: log this (level critical)
print("[b10-cfgmgr] Unable to write config file; configuration not stored: " + str(ose))
def __eq__(self, other):
"""Returns True if the data contained is equal. data_path and
db_filename may be different."""
if type(other) != type(self):
return False
return self.data == other.data
class ConfigManager:
def __init__(self, data_path):
self.commands = {}
self.data_definitions = {}
"""Creates a configuration manager. The data_path is the path
to the directory containing the b10-config.db file.
If session is set, this will be used as the communication
channel session. If not, a new session will be created.
The ability to specify a custom session is for testing purposes
and should not be needed for normal usage."""
def __init__(self, data_path, session = None):
"""Initialize the configuration manager. The data_path string
is the path to the directory where the configuration is
stored (in <data_path>/b10-config.db). Session is an optional
cc-channel session. If this is not given, a new one is
created"""
self.data_path = data_path
self.module_specs = {}
self.config = ConfigManagerData(data_path)
self.cc = isc.cc.Session()
if session:
self.cc = session
else:
self.cc = isc.cc.Session()
self.cc.group_subscribe("ConfigManager")
self.cc.group_subscribe("Boss", "ConfigManager")
self.running = False
def notify_boss(self):
"""Notifies the Boss module that the Config Manager is running"""
self.cc.group_sendmsg({"running": "configmanager"}, "Boss")
def set_config(self, module_name, data_specification):
self.data_definitions[module_name] = data_specification
def remove_config(self, module_name):
self.data_definitions[module_name]
def set_module_spec(self, spec):
"""Adds a ModuleSpec"""
self.module_specs[spec.get_module_name()] = spec
def set_commands(self, module_name, commands):
self.commands[module_name] = commands
def remove_module_spec(self, module_name):
"""Removes the full ModuleSpec for the given module_name.
Does nothing if the module was not present."""
if module_name in self.module_specs:
del self.module_specs[module_name]
def remove_commands(self, module_name):
del self.commands[module_name]
def get_module_spec(self, module_name):
"""Returns the full ModuleSpec for the module with the given
module_name"""
if module_name in self.module_specs:
return self.module_specs[module_name]
def get_config_spec(self, name = None):
"""Returns a dict containing 'module_name': config_spec for
all modules. If name is specified, only that module will
be included"""
config_data = {}
if name:
if name in self.module_specs:
config_data[name] = self.module_specs[name].get_config_spec()
else:
for module_name in self.module_specs.keys():
config_data[module_name] = self.module_specs[module_name].get_config_spec()
return config_data
def get_commands_spec(self, name = None):
"""Returns a dict containing 'module_name': commands_spec for
all modules. If name is specified, only that module will
be included"""
commands = {}
if name:
if name in self.module_specs:
commands[name] = self.module_specs[name].get_commands_spec()
else:
for module_name in self.module_specs.keys():
commands[module_name] = self.module_specs[module_name].get_commands_spec()
return commands
def read_config(self):
print("Reading config")
self.config = ConfigManagerData.read_from_file(self.data_path)
"""Read the current configuration from the b10-config.db file
at the path specificied at init()"""
try:
self.config = ConfigManagerData.read_from_file(self.data_path)
except ConfigManagerDataEmpty:
# ok, just start with an empty config
self.config = ConfigManagerData(self.data_path)
def write_config(self):
print("Writing config")
"""Write the current configuration to the b10-config.db file
at the path specificied at init()"""
self.config.write_to_file()
def handle_msg(self, msg):
"""return answer message"""
def _handle_get_module_spec(self, cmd):
"""Private function that handles the 'get_module_spec' command"""
answer = {}
if "command" in msg:
cmd = msg["command"]
try:
if cmd[0] == "get_commands":
print("[XX] bind-cfgd got get_commands");
print("[XX] sending:")
print(self.commands)
answer["result"] = [ 0, self.commands ]
elif cmd[0] == "get_data_spec":
if len(cmd) > 1 and cmd[1]['module_name'] != '':
module_name = cmd[1]['module_name']
try:
answer["result"] = [0, self.data_definitions[module_name]]
except KeyError as ke:
answer["result"] = [1, "No specification for module " + module_name]
else:
answer["result"] = [0, self.data_definitions]
elif cmd[0] == "get_config":
# we may not have any configuration here
conf_part = None
print("[XX] bind-cfgd got command:")
print(cmd)
if len(cmd) > 1:
try:
conf_part = data.find(self.config.data, cmd[1]['module_name'])
except data.DataNotFoundError as dnfe:
pass
else:
conf_part = self.config.data
if conf_part:
answer["result"] = [ 0, conf_part ]
else:
answer["result"] = [ 0 ]
elif cmd[0] == "set_config":
if len(cmd) == 3:
# todo: use api (and check types?)
if cmd[1] != "":
conf_part = data.find_no_exc(self.config.data, cmd[1])
if not conf_part:
conf_part = data.set(self.config.data, cmd[1], "")
else:
conf_part = self.config.data
data.merge(conf_part, cmd[2])
print("[XX bind-cfgd] new config (part):")
print(conf_part)
# send out changed info
self.cc.group_sendmsg({ "config_update": conf_part }, cmd[1])
self.write_config()
answer["result"] = [ 0 ]
elif len(cmd) == 2:
print("[XX bind-cfgd] old config:")
print(self.config.data)
print("[XX bind-cfgd] updating with:")
print(cmd[1])
# TODO: ask module to check the config first...
data.merge(self.config.data, cmd[1])
print("[XX bind-cfgd] new config:")
print(self.config.data)
# send out changed info
for module in self.config.data:
self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
self.write_config()
answer["result"] = [ 0 ]
else:
answer["result"] = [ 1, "Wrong number of arguments" ]
elif cmd[0] == "shutdown":
print("[bind-cfgd] Received shutdown command")
self.running = False
if cmd != None:
if type(cmd) == dict:
if 'module_name' in cmd and cmd['module_name'] != '':
module_name = cmd['module_name']
answer = isc.config.ccsession.create_answer(0, self.get_config_spec(module_name))
else:
print("[bind-cfgd] unknown command: " + str(cmd))
answer["result"] = [ 1, "Unknown command: " + str(cmd) ]
except IndexError as ie:
print("[bind-cfgd] missing argument")
answer["result"] = [ 1, "Missing argument in command: " + str(ie) ]
raise ie
elif "data_specification" in msg:
# todo: validate? (no direct access to spec as
# todo: use DataDefinition class?
print("[XX] bind-cfgd got specification:")
print(msg["data_specification"])
spec = msg["data_specification"]
if "config_data" in spec:
self.set_config(spec["module_name"], spec["config_data"])
self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "Cmd-Ctrld")
if "commands" in spec:
self.set_commands(spec["module_name"], spec["commands"])
self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "Cmd-Ctrld")
answer["result"] = [ 0 ]
elif 'result' in msg:
answer['result'] = [0]
answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_module_spec command")
else:
answer = isc.config.ccsession.create_answer(1, "Bad get_module_spec command, argument not a dict")
else:
print("[bind-cfgd] unknown message: " + str(msg))
answer["result"] = [ 1, "Unknown module: " + str(msg) ]
answer = isc.config.ccsession.create_answer(0, self.get_config_spec())
return answer
def _handle_get_config(self, cmd):
"""Private function that handles the 'get_config' command"""
answer = {}
if cmd != None:
if type(cmd) == dict:
if 'module_name' in cmd and cmd['module_name'] != '':
module_name = cmd['module_name']
try:
answer = isc.config.ccsession.create_answer(0, data.find(self.config.data, module_name))
except data.DataNotFoundError as dnfe:
# no data is ok, that means we have nothing that
# deviates from default values
answer = isc.config.ccsession.create_answer(0, {})
else:
answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_config command")
else:
answer = isc.config.ccsession.create_answer(1, "Bad get_config command, argument not a dict")
else:
answer = isc.config.ccsession.create_answer(0, self.config.data)
return answer
def _handle_set_config(self, cmd):
"""Private function that handles the 'set_config' command"""
answer = None
if cmd == None:
return isc.config.ccsession.create_answer(1, "Wrong number of arguments")
if len(cmd) == 2:
# todo: use api (and check the data against the definition?)
module_name = cmd[0]
conf_part = data.find_no_exc(self.config.data, module_name)
if conf_part:
data.merge(conf_part, cmd[1])
self.cc.group_sendmsg({ "config_update": conf_part }, module_name)
answer, env = self.cc.group_recvmsg(False)
else:
conf_part = data.set(self.config.data, module_name, {})
data.merge(conf_part[module_name], cmd[1])
# send out changed info
self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name)
# replace 'our' answer with that of the module
answer, env = self.cc.group_recvmsg(False)
if answer:
rcode, val = isc.config.ccsession.parse_answer(answer)
if rcode == 0:
self.write_config()
elif len(cmd) == 1:
# todo: use api (and check the data against the definition?)
old_data = self.config.data.copy()
data.merge(self.config.data, cmd[0])
# send out changed info
got_error = False
err_list = []
for module in self.config.data:
if module != "version":
self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
answer, env = self.cc.group_recvmsg(False)
if answer == None:
got_error = True
err_list.append("No answer message from " + module)
else:
rcode, val = isc.config.ccsession.parse_answer(answer)
if rcode != 0:
got_error = True
err_list.append(val)
if not got_error:
self.write_config()
answer = isc.config.ccsession.create_answer(0)
else:
# TODO rollback changes that did get through, should we re-send update?
self.config.data = old_data
answer = isc.config.ccsession.create_answer(1, " ".join(err_list))
else:
print(cmd)
answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments")
if not answer:
answer = isc.config.ccsession.create_answer(1, "No answer message from " + cmd[0])
return answer
def _handle_module_spec(self, spec):
"""Private function that handles the 'module_spec' command"""
# todo: validate? (no direct access to spec as
# todo: use ModuleSpec class
# todo: error checking (like keyerrors)
answer = {}
self.set_module_spec(spec)
# We should make one general 'spec update for module' that
# passes both specification and commands at once
spec_update = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_SPECIFICATION_UPDATE,
[ spec.get_module_name(), spec.get_config_spec() ])
self.cc.group_sendmsg(spec_update, "Cmd-Ctrld")
cmds_update = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_COMMANDS_UPDATE,
[ spec.get_module_name(), spec.get_commands_spec() ])
self.cc.group_sendmsg(cmds_update, "Cmd-Ctrld")
answer = isc.config.ccsession.create_answer(0)
return answer
def handle_msg(self, msg):
"""Handle a command from the cc channel to the configuration manager"""
answer = {}
#print("[XX] got msg:")
#print(msg)
cmd, arg = isc.config.ccsession.parse_command(msg)
if cmd:
#print("[XX] cmd: " + cmd)
if cmd == isc.config.ccsession.COMMAND_GET_COMMANDS_SPEC:
answer = isc.config.ccsession.create_answer(0, self.get_commands_spec())
elif cmd == isc.config.ccsession.COMMAND_GET_MODULE_SPEC:
answer = self._handle_get_module_spec(arg)
elif cmd == isc.config.ccsession.COMMAND_GET_CONFIG:
answer = self._handle_get_config(arg)
elif cmd == isc.config.ccsession.COMMAND_SET_CONFIG:
answer = self._handle_set_config(arg)
elif cmd == "shutdown":
# TODO: logging
print("[b10-cfgmgr] Received shutdown command")
self.running = False
answer = isc.config.ccsession.create_answer(0)
elif cmd == isc.config.ccsession.COMMAND_MODULE_SPEC:
try:
answer = self._handle_module_spec(isc.config.ModuleSpec(arg))
except isc.config.ModuleSpecError as dde:
answer = isc.config.ccsession.create_answer(1, "Error in data definition: " + str(dde))
else:
answer = isc.config.ccsession.create_answer(1, "Unknown command: " + str(cmd))
else:
answer = isc.config.ccsession.create_answer(1, "Unknown message format: " + str(msg))
return answer
def run(self):
"""Runs the configuration manager."""
self.running = True
while (self.running):
msg, env = self.cc.group_recvmsg(False)
@@ -195,10 +349,3 @@ class ConfigManager:
self.cc.group_reply(env, answer)
else:
self.running = False
cm = None
def signal_handler(signal, frame):
global cm
if cm:
cm.running = False

View File

@@ -0,0 +1,356 @@
# 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.
#
# Tests for the configuration manager module
#
import unittest
import os
from isc.config.cfgmgr import *
class TestConfigManagerData(unittest.TestCase):
def setUp(self):
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
self.config_manager_data = ConfigManagerData(self.data_path)
self.assert_(self.config_manager_data)
def test_init(self):
self.assertEqual(self.config_manager_data.data['version'],
ConfigManagerData.CONFIG_VERSION)
self.assertEqual(self.config_manager_data.data_path,
self.data_path)
self.assertEqual(self.config_manager_data.db_filename,
self.data_path + os.sep + "b10-config.db")
def test_read_from_file(self):
ConfigManagerData.read_from_file(self.data_path)
self.assertRaises(ConfigManagerDataEmpty,
ConfigManagerData.read_from_file,
"doesnotexist")
self.assertRaises(ConfigManagerDataReadError,
ConfigManagerData.read_from_file,
self.data_path, "b10-config-bad1.db")
self.assertRaises(ConfigManagerDataReadError,
ConfigManagerData.read_from_file,
self.data_path, "b10-config-bad2.db")
self.assertRaises(ConfigManagerDataReadError,
ConfigManagerData.read_from_file,
self.data_path, "b10-config-bad3.db")
def test_write_to_file(self):
output_file_name = "b10-config-write-test";
self.config_manager_data.write_to_file(output_file_name)
new_config = ConfigManagerData(self.data_path, output_file_name)
self.assertEqual(self.config_manager_data, new_config)
def test_equality(self):
# tests the __eq__ function. Equality is only defined
# by equality of the .data element. If data_path or db_filename
# are different, but the contents are the same, it's still
# considered equal
cfd1 = ConfigManagerData(self.data_path)
cfd2 = ConfigManagerData(self.data_path)
self.assertEqual(cfd1, cfd2)
cfd2.data_path = "some/unknown/path"
self.assertEqual(cfd1, cfd2)
cfd2.db_filename = "bad_file.name"
self.assertEqual(cfd1, cfd2)
cfd2.data['test'] = { 'a': [ 1, 2, 3]}
self.assertNotEqual(cfd1, cfd2)
#
# We can probably use a more general version of this
#
class FakeModuleCCSession:
def __init__(self):
self.subscriptions = {}
# each entry is of the form [ channel, instance, message ]
self.message_queue = []
def group_subscribe(self, group_name, instance_name = None):
if not group_name in self.subscriptions:
self.subscriptions[group_name] = []
if instance_name:
self.subscriptions[group_name].append(instance_name)
def has_subscription(self, group_name, instance_name = None):
if group_name in self.subscriptions:
if instance_name:
return instance_name in self.subscriptions[group_name]
else:
return True
else:
return False
def group_sendmsg(self, msg, channel, target = None):
self.message_queue.append([ channel, target, msg ])
def group_reply(self, env, msg):
pass
def group_recvmsg(self, blocking):
for qm in self.message_queue:
if qm[0] in self.subscriptions and (qm[1] == None or qm[1] in self.subscriptions[qm[0]]):
self.message_queue.remove(qm)
return qm[2], {}
return None, None
def get_message(self, channel, target = None):
for qm in self.message_queue:
if qm[0] == channel and qm[1] == target:
self.message_queue.remove(qm)
return qm[2]
return None
class TestConfigManager(unittest.TestCase):
def setUp(self):
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
self.fake_session = FakeModuleCCSession()
self.cm = ConfigManager(self.data_path, self.fake_session)
self.name = "TestModule"
self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec")
def test_init(self):
self.assert_(self.cm.module_specs == {})
self.assert_(self.cm.data_path == self.data_path)
self.assert_(self.cm.config != None)
self.assert_(self.fake_session.has_subscription("ConfigManager"))
self.assert_(self.fake_session.has_subscription("Boss", "ConfigManager"))
self.assertFalse(self.cm.running)
def test_notify_boss(self):
self.cm.notify_boss()
msg = self.fake_session.get_message("Boss", None)
self.assert_(msg)
# this one is actually wrong, but 'current status quo'
self.assertEqual(msg, {"running": "configmanager"})
def test_set_module_spec(self):
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
def test_remove_module_spec(self):
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
self.cm.remove_module_spec(module_spec.get_module_name())
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
def test_get_module_spec(self):
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
module_spec2 = self.cm.get_module_spec(module_spec.get_module_name())
self.assertEqual(module_spec, module_spec2)
def test_get_config_spec(self):
config_spec = self.cm.get_config_spec()
self.assertEqual(config_spec, {})
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
config_spec = self.cm.get_config_spec()
self.assertEqual(config_spec, { 'Spec1': None })
self.cm.remove_module_spec('Spec1')
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
config_spec = self.cm.get_config_spec()
self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())
config_spec = self.cm.get_config_spec('Spec2')
self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())
def test_get_commands_spec(self):
commands_spec = self.cm.get_commands_spec()
self.assertEqual(commands_spec, {})
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
commands_spec = self.cm.get_commands_spec()
self.assertEqual(commands_spec, { 'Spec1': None })
self.cm.remove_module_spec('Spec1')
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
commands_spec = self.cm.get_commands_spec()
self.assertEqual(commands_spec['Spec2'], module_spec.get_commands_spec())
commands_spec = self.cm.get_commands_spec('Spec2')
self.assertEqual(commands_spec['Spec2'], module_spec.get_commands_spec())
def test_read_config(self):
self.assertEqual(self.cm.config.data, {'version': 1})
self.cm.read_config()
# due to what get written, the value here is what the last set_config command in test_handle_msg does
self.assertEqual(self.cm.config.data, {'TestModule': {'test': 125}, 'version': 1})
self.cm.data_path = "/no_such_path"
self.cm.read_config()
self.assertEqual(self.cm.config.data, {'version': 1})
def test_write_config(self):
# tested in ConfigManagerData tests
pass
def _handle_msg_helper(self, msg, expected_answer):
answer = self.cm.handle_msg(msg)
self.assertEqual(expected_answer, answer)
def test_handle_msg(self):
self._handle_msg_helper({}, { 'result': [ 1, 'Unknown message format: {}']})
self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']})
self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: badcommand"]})
self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]})
self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]})
self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "Spec2" } ] }, { 'result': [ 0, {} ]})
#self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] },
# {'result': [1, 'No specification for module nosuchmodule']})
self._handle_msg_helper({ "command": [ "get_module_spec", 1 ] },
{'result': [1, 'Bad get_module_spec command, argument not a dict']})
self._handle_msg_helper({ "command": [ "get_module_spec", { } ] },
{'result': [1, 'Bad module_name in get_module_spec command']})
self._handle_msg_helper({ "command": [ "get_config" ] }, { 'result': [ 0, { 'version': 1} ]})
self._handle_msg_helper({ "command": [ "get_config", { "module_name": "nosuchmodule" } ] },
{'result': [0, {}]})
self._handle_msg_helper({ "command": [ "get_config", 1 ] },
{'result': [1, 'Bad get_config command, argument not a dict']})
self._handle_msg_helper({ "command": [ "get_config", { } ] },
{'result': [1, 'Bad module_name in get_config command']})
self._handle_msg_helper({ "command": [ "set_config" ] },
{'result': [1, 'Wrong number of arguments']})
self._handle_msg_helper({ "command": [ "set_config", [{}]] },
{'result': [0]})
self.assertEqual(len(self.fake_session.message_queue), 0)
# the targets of some of these tests expect specific answers, put
# those in our fake msgq first.
my_ok_answer = { 'result': [ 0 ] }
# Send the 'ok' that cfgmgr expects back to the fake queue first
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
# then send the command
self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 123 }] ] },
my_ok_answer)
# The cfgmgr should have eaten the ok message, and sent out an update again
self.assertEqual(len(self.fake_session.message_queue), 1)
self.assertEqual({'config_update': {'test': 123}},
self.fake_session.get_message(self.name, None))
# and the queue should now be empty again
self.assertEqual(len(self.fake_session.message_queue), 0)
# below are variations of the theme above
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] },
my_ok_answer)
self.assertEqual(len(self.fake_session.message_queue), 1)
self.assertEqual({'config_update': {'test': 124}},
self.fake_session.get_message(self.name, None))
self.assertEqual(len(self.fake_session.message_queue), 0)
# This is the last 'succes' one, the value set here is what test_read_config expects
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
my_ok_answer )
self.assertEqual(len(self.fake_session.message_queue), 1)
self.assertEqual({'config_update': {'test': 125}},
self.fake_session.get_message(self.name, None))
self.assertEqual(len(self.fake_session.message_queue), 0)
self.fake_session.group_sendmsg({ 'result': "bad_answer" }, "ConfigManager")
self.assertRaises(isc.config.ccsession.ModuleCCSessionError,
self.cm.handle_msg,
{ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] } )
self.assertEqual(len(self.fake_session.message_queue), 1)
self.assertEqual({'config_update': {'test': 125}},
self.fake_session.get_message(self.name, None))
self.assertEqual(len(self.fake_session.message_queue), 0)
my_bad_answer = { 'result': [1, "bad_answer"] }
self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager")
self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
my_bad_answer )
self.assertEqual(len(self.fake_session.message_queue), 1)
self.assertEqual({'config_update': {'test': 125}},
self.fake_session.get_message(self.name, None))
self.assertEqual(len(self.fake_session.message_queue), 0)
my_bad_answer = { 'result': [1, "bad_answer"] }
self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager")
self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
my_bad_answer )
self.assertEqual(len(self.fake_session.message_queue), 1)
self.assertEqual({'config_update': {'test': 125}},
self.fake_session.get_message(self.name, None))
self.assertEqual(len(self.fake_session.message_queue), 0)
self._handle_msg_helper({ "command": [ "set_config", [ ] ] },
{'result': [1, 'Wrong number of arguments']} )
self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
{ 'result': [1, 'No answer message from TestModule']} )
self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
{ 'result': [1, 'No answer message from TestModule']} )
#self.assertEqual(len(self.fake_session.message_queue), 1)
#self.assertEqual({'config_update': {'test': 124}},
# self.fake_session.get_message(self.name, None))
#self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data)
#
self._handle_msg_helper({ "command":
["module_spec", self.spec.get_full_spec()]
},
{'result': [0]})
self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] },
{'result': [1, 'Error in data definition: no module_name in module_spec']})
self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_config_spec() } ]})
self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands_spec() } ]})
# re-add this once we have new way to propagate spec changes (1 instead of the current 2 messages)
#self.assertEqual(len(self.fake_session.message_queue), 2)
# the name here is actually wrong (and hardcoded), but needed in the current version
# TODO: fix that
#self.assertEqual({'specification_update': [ self.name, self.spec ] },
# self.fake_session.get_message("Cmd-Ctrld", None))
#self.assertEqual({'commands_update': [ self.name, self.commands ] },
# self.fake_session.get_message("Cmd-Ctrld", None))
self._handle_msg_helper({ "command":
["shutdown"]
},
{'result': [0]})
def test_run(self):
self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
self.cm.run()
pass
if __name__ == '__main__':
if not 'CONFIG_TESTDATA_PATH' in os.environ:
print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files")
exit(1)
unittest.main()

View File

@@ -0,0 +1,410 @@
# 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.
"""
Classes to store configuration data and module specifications
Used by the config manager, (python) modules, and UI's (those last
two through the classes in ccsession)
"""
import isc.cc.data
import isc.config.module_spec
class ConfigDataError(Exception): pass
def check_type(spec_part, value):
"""Does nothing if the value is of the correct type given the
specification part relevant for the value. Raises an
isc.cc.data.DataTypeError exception if not. spec_part can be
retrieved with find_spec_part()"""
if type(spec_part) == dict and 'item_type' in spec_part:
data_type = spec_part['item_type']
else:
raise isc.cc.data.DataTypeError(str("Incorrect specification part for type checking"))
if data_type == "integer" and type(value) != int:
raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
elif data_type == "real" and type(value) != float:
raise isc.cc.data.DataTypeError(str(value) + " is not a real")
elif data_type == "boolean" and type(value) != bool:
raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
elif data_type == "string" and type(value) != str:
raise isc.cc.data.DataTypeError(str(value) + " is not a string")
elif data_type == "list":
if type(value) != list:
raise isc.cc.data.DataTypeError(str(value) + " is not a list")
else:
for element in value:
check_type(spec_part['list_item_spec'], element)
elif data_type == "map" and type(value) != dict:
# todo: check types of map contents too
raise isc.cc.data.DataTypeError(str(value) + " is not a map")
def find_spec_part(element, identifier):
"""find the data definition for the given identifier
returns either a map with 'item_name' etc, or a list of those"""
if identifier == "":
return element
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts:
if type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
found = False
for cur_el_item in cur_el['map_item_spec']:
if cur_el_item['item_name'] == id:
cur_el = cur_el_item
found = True
if not found:
raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
elif type(cur_el) == list:
found = False
for cur_el_item in cur_el:
if cur_el_item['item_name'] == id:
cur_el = cur_el_item
found = True
if not found:
raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
else:
raise isc.cc.data.DataNotFoundError("Not a correct config specification")
return cur_el
def spec_name_list(spec, prefix="", recurse=False):
"""Returns a full list of all possible item identifiers in the
specification (part). Raises a ConfigDataError if spec is not
a correct spec (as returned by ModuleSpec.get_config_spec()"""
result = []
if prefix != "" and not prefix.endswith("/"):
prefix += "/"
if type(spec) == dict:
if 'map_item_spec' in spec:
for map_el in spec['map_item_spec']:
name = map_el['item_name']
if map_el['item_type'] == 'map':
name += "/"
if recurse and 'map_item_spec' in map_el:
result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse))
else:
result.append(prefix + name)
else:
for name in spec:
result.append(prefix + name + "/")
if recurse:
result.extend(spec_name_list(spec[name],name, recurse))
elif type(spec) == list:
for list_el in spec:
if 'item_name' in list_el:
if list_el['item_type'] == "map" and recurse:
result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
else:
name = list_el['item_name']
if list_el['item_type'] in ["list", "map"]:
name += "/"
result.append(prefix + name)
else:
raise ConfigDataError("Bad specication")
else:
raise ConfigDataError("Bad specication")
return result
class ConfigData:
"""This class stores the module specs and the current non-default
config values. It provides functions to get the actual value or
the default value if no non-default value has been set"""
def __init__(self, specification):
"""Initialize a ConfigData instance. If specification is not
of type ModuleSpec, a ConfigDataError is raised."""
if type(specification) != isc.config.ModuleSpec:
raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
self.specification = specification
self.data = {}
def get_value(self, identifier):
"""Returns a tuple where the first item is the value at the
given identifier, and the second item is a bool which is
true if the value is an unset default. Raises an
isc.cc.data.DataNotFoundError if the identifier is bad"""
value = isc.cc.data.find_no_exc(self.data, identifier)
if value != None:
return value, False
spec = find_spec_part(self.specification.get_config_spec(), identifier)
if spec and 'item_default' in spec:
return spec['item_default'], True
return None, False
def get_module_spec(self):
"""Returns the ModuleSpec object associated with this ConfigData"""
return self.specification
def set_local_config(self, data):
"""Set the non-default config values, as passed by cfgmgr"""
self.data = data
def get_local_config(self):
"""Returns the non-default config values in a dict"""
return self.data;
def get_item_list(self, identifier = None, recurse = False):
"""Returns a list of strings containing the full identifiers of
all 'sub'options at the given identifier. If recurse is True,
it will also add all identifiers of all children, if any"""
if identifier:
spec = find_spec_part(self.specification.get_config_spec(), identifier)
return spec_name_list(spec, identifier + "/")
return spec_name_list(self.specification.get_config_spec(), "", recurse)
def get_full_config(self):
"""Returns a dict containing identifier: value elements, for
all configuration options for this module. If there is
a local setting, that will be used. Otherwise the value
will be the default as specified by the module specification.
If there is no default and no local setting, the value will
be None"""
items = self.get_item_list(None, True)
result = {}
for item in items:
value, default = self.get_value(item)
result[item] = value
return result
class MultiConfigData:
"""This class stores the module specs, current non-default
configuration values and 'local' (uncommitted) changes for
multiple modules"""
LOCAL = 1
CURRENT = 2
DEFAULT = 3
NONE = 4
def __init__(self):
self._specifications = {}
self._current_config = {}
self._local_changes = {}
def set_specification(self, spec):
"""Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec"""
if type(spec) != isc.config.ModuleSpec:
raise ConfigDataError("not a datadef: " + str(type(spec)))
self._specifications[spec.get_module_name()] = spec
def remove_specification(self, module_name):
"""Removes the specification with the given module name. Does nothing if it wasn't there."""
if module_name in self._specifications:
del self._specifications[module_name]
def get_module_spec(self, module):
"""Returns the ModuleSpec for the module with the given name.
If there is no such module, it returns None"""
if module in self._specifications:
return self._specifications[module]
else:
return None
def find_spec_part(self, identifier):
"""Returns the specification for the item at the given
identifier, or None if not found. The first part of the
identifier (up to the first /) is interpreted as the module
name. Returns None if not found."""
if identifier[0] == '/':
identifier = identifier[1:]
module, sep, id = identifier.partition("/")
try:
return find_spec_part(self._specifications[module].get_config_spec(), id)
except isc.cc.data.DataNotFoundError as dnfe:
return None
except KeyError as ke:
return None
# this function should only be called by __request_config
def _set_current_config(self, config):
"""Replace the full current config values."""
self._current_config = config
def get_current_config(self):
"""Returns the current configuration as it is known by the
configuration manager. It is a dict where the first level is
the module name, and the value is the config values for
that module"""
return self._current_config
def get_local_changes(self):
"""Returns the local config changes, i.e. those that have not
been committed yet and are not known by the configuration
manager or the modules."""
return self._local_changes
def clear_local_changes(self):
"""Reverts all local changes"""
self._local_changes = {}
def get_local_value(self, identifier):
"""Returns a specific local (uncommitted) configuration value,
as specified by the identifier. If the local changes do not
contain a new setting for this identifier, or if the
identifier cannot be found, None is returned. See
get_value() for a general way to find a configuration value
"""
return isc.cc.data.find_no_exc(self._local_changes, identifier)
def get_current_value(self, identifier):
"""Returns the current non-default value as known by the
configuration manager, or None if it is not set.
See get_value() for a general way to find a configuration
value
"""
return isc.cc.data.find_no_exc(self._current_config, identifier)
def get_default_value(self, identifier):
"""Returns the default value for the given identifier as
specified by the module specification, or None if there is
no default or the identifier could not be found.
See get_value() for a general way to find a configuration
value
"""
if identifier[0] == '/':
identifier = identifier[1:]
module, sep, id = identifier.partition("/")
try:
spec = find_spec_part(self._specifications[module].get_config_spec(), id)
if 'item_default' in spec:
return spec['item_default']
else:
return None
except isc.cc.data.DataNotFoundError as dnfe:
return None
def get_value(self, identifier):
"""Returns a tuple containing value,status.
The value contains the configuration value for the given
identifier. The status reports where this value came from;
it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
(local change, current setting, default as specified by the
specification, or not found at all)."""
value = self.get_local_value(identifier)
if value != None:
return value, self.LOCAL
value = self.get_current_value(identifier)
if value != None:
return value, self.CURRENT
value = self.get_default_value(identifier)
if value != None:
return value, self.DEFAULT
return None, self.NONE
def get_value_maps(self, identifier = None):
"""Returns a list of dicts, containing the following values:
name: name of the entry (string)
type: string containing the type of the value (or 'module')
value: value of the entry if it is a string, int, double or bool
modified: true if the value is a local change
default: true if the value has been changed
TODO: use the consts for those last ones
Throws DataNotFoundError if the identifier is bad
"""
result = []
if not identifier:
# No identifier, so we need the list of current modules
for module in self._specifications.keys():
entry = {}
entry['name'] = module
entry['type'] = 'module'
entry['value'] = None
entry['modified'] = False
entry['default'] = False
result.append(entry)
else:
if identifier[0] == '/':
identifier = identifier[1:]
module, sep, id = identifier.partition('/')
spec = self.get_module_spec(module)
if spec:
spec_part = find_spec_part(spec.get_config_spec(), id)
if type(spec_part) == list:
for item in spec_part:
entry = {}
entry['name'] = item['item_name']
entry['type'] = item['item_type']
value, status = self.get_value("/" + identifier + "/" + item['item_name'])
entry['value'] = value
if status == self.LOCAL:
entry['modified'] = True
else:
entry['modified'] = False
if status == self.DEFAULT:
entry['default'] = False
else:
entry['default'] = False
result.append(entry)
elif type(spec_part) == dict:
item = spec_part
if item['item_type'] == 'list':
li_spec = item['list_item_spec']
item_list, status = self.get_value("/" + identifier)
if item_list != None:
for value in item_list:
result_part2 = {}
result_part2['name'] = li_spec['item_name']
result_part2['value'] = value
result_part2['type'] = li_spec['item_type']
result_part2['default'] = False
result_part2['modified'] = False
result.append(result_part2)
else:
entry = {}
entry['name'] = item['item_name']
entry['type'] = item['item_type']
#value, status = self.get_value("/" + identifier + "/" + item['item_name'])
value, status = self.get_value("/" + identifier)
entry['value'] = value
if status == self.LOCAL:
entry['modified'] = True
else:
entry['modified'] = False
if status == self.DEFAULT:
entry['default'] = False
else:
entry['default'] = False
result.append(entry)
#print(spec)
return result
def set_value(self, identifier, value):
"""Set the local value at the given identifier to value. If
there is a specification for the given identifier, the type
is checked."""
spec_part = self.find_spec_part(identifier)
if spec_part != None:
check_type(spec_part, value)
isc.cc.data.set(self._local_changes, identifier, value)
def get_config_item_list(self, identifier = None, recurse = False):
"""Returns a list of strings containing the item_names of
the child items at the given identifier. If no identifier is
specified, returns a list of module names. The first part of
the identifier (up to the first /) is interpreted as the
module name"""
if identifier and identifier != "/":
spec = self.find_spec_part(identifier)
return spec_name_list(spec, identifier + "/", recurse)
else:
if recurse:
id_list = []
for module in self._specifications.keys():
id_list.extend(spec_name_list(self.find_spec_part(module), module, recurse))
return id_list
else:
return list(self._specifications.keys())

View File

@@ -0,0 +1,383 @@
# 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.
#
# Tests for the ConfigData and MultiConfigData classes
#
import unittest
import os
from isc.config.config_data import *
from isc.config.module_spec import *
class TestConfigData(unittest.TestCase):
def setUp(self):
if 'CONFIG_TESTDATA_PATH' in os.environ:
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
else:
self.data_path = "../../../testdata"
spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.cd = ConfigData(spec)
#def test_module_spec_from_file(self):
# spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
# cd = ConfigData(spec)
# self.assertEqual(cd.specification, spec)
# self.assertEqual(cd.data, {})
# self.assertRaises(ConfigDataError, ConfigData, 1)
def test_check_type(self):
config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec()
spec_part = find_spec_part(config_spec, "value1")
check_type(spec_part, 1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
spec_part = find_spec_part(config_spec, "value2")
check_type(spec_part, 1.1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
spec_part = find_spec_part(config_spec, "value3")
check_type(spec_part, True)
check_type(spec_part, False)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
spec_part = find_spec_part(config_spec, "value4")
check_type(spec_part, "asdf")
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
spec_part = find_spec_part(config_spec, "value5")
check_type(spec_part, [1, 2])
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ "a", "b" ])
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
spec_part = find_spec_part(config_spec, "value6")
check_type(spec_part, { "value1": "aaa", "value2": 2 })
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
#self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "value1": 1 })
self.assertRaises(isc.cc.data.DataTypeError, check_type, config_spec, 1)
def test_find_spec_part(self):
config_spec = self.cd.get_module_spec().get_config_spec()
spec_part = find_spec_part(config_spec, "item1")
self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
spec_part = find_spec_part(config_spec, "/item1")
self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item")
self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item/multilevel")
self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "item6/multilevel")
self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, 1, "item6/multilevel")
spec_part = find_spec_part(config_spec, "item6/value1")
self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part)
def test_spec_name_list(self):
name_list = spec_name_list(self.cd.get_module_spec().get_config_spec())
self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True)
self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
name_list = spec_name_list(spec_part, "item6", True)
self.assertEqual(['item6/value1', 'item6/value2'], name_list)
spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
name_list = spec_name_list(spec_part, "item6", True)
self.assertEqual(['item6/value1', 'item6/value2'], name_list)
config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec()
spec_part = find_spec_part(config_spec, "value9")
name_list = spec_name_list(spec_part, "value9", True)
self.assertEqual(['value9/v91', 'value9/v92/v92a', 'value9/v92/v92b'], name_list)
name_list = spec_name_list({ "myModule": config_spec }, "", False)
self.assertEqual(['myModule/'], name_list)
name_list = spec_name_list({ "myModule": config_spec }, "", True)
self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5/', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7/', 'myModule/value8/', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list)
self.assertRaises(ConfigDataError, spec_name_list, 1)
self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ])
def test_init(self):
self.assertRaises(ConfigDataError, ConfigData, "asdf")
def test_get_value(self):
value, default = self.cd.get_value("item1")
self.assertEqual(1, value)
self.assertEqual(True, default)
value, default = self.cd.get_value("item2")
self.assertEqual(1.1, value)
self.assertEqual(True, default)
value, default = self.cd.get_value("item3")
self.assertEqual(True, value)
self.assertEqual(True, default)
value, default = self.cd.get_value("item4")
self.assertEqual("test", value)
self.assertEqual(True, default)
value, default = self.cd.get_value("item5")
self.assertEqual(["a", "b"], value)
self.assertEqual(True, default)
value, default = self.cd.get_value("item6")
self.assertEqual({}, value)
self.assertEqual(True, default)
self.assertRaises(isc.cc.data.DataNotFoundError, self.cd.get_value, "no_such_item")
value, default = self.cd.get_value("item6/value2")
self.assertEqual(None, value)
self.assertEqual(False, default)
def test_set_local_config(self):
self.cd.set_local_config({"item1": 2})
value, default = self.cd.get_value("item1")
self.assertEqual(2, value)
self.assertEqual(False, default)
def test_get_local_config(self):
local_config = self.cd.get_local_config()
self.assertEqual({}, local_config)
my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
self.cd.set_local_config(my_config)
self.assertEqual(my_config, self.cd.get_local_config())
def test_get_item_list(self):
name_list = self.cd.get_item_list()
self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
name_list = self.cd.get_item_list("", True)
self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
name_list = self.cd.get_item_list("item6", False)
self.assertEqual(['item6/value1', 'item6/value2'], name_list)
def test_get_full_config(self):
full_config = self.cd.get_full_config()
self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5/": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
self.cd.set_local_config(my_config)
full_config = self.cd.get_full_config()
self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5/": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
class TestMultiConfigData(unittest.TestCase):
def setUp(self):
if 'CONFIG_TESTDATA_PATH' in os.environ:
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
else:
self.data_path = "../../../testdata"
self.mcd = MultiConfigData()
def test_init(self):
self.assertEqual({}, self.mcd._specifications)
self.assertEqual({}, self.mcd._current_config)
self.assertEqual({}, self.mcd._local_changes)
def test_set_specification(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.mcd.set_specification(module_spec)
self.assert_(module_spec.get_module_name() in self.mcd._specifications)
self.assertEquals(module_spec, self.mcd._specifications[module_spec.get_module_name()])
self.assertRaises(ConfigDataError, self.mcd.set_specification, "asdf")
def test_get_module_spec(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.mcd.set_specification(module_spec)
module_spec2 = self.mcd.get_module_spec(module_spec.get_module_name())
self.assertEqual(module_spec, module_spec2)
module_spec3 = self.mcd.get_module_spec("no_such_module")
self.assertEqual(None, module_spec3)
def test_find_spec_part(self):
spec_part = self.mcd.find_spec_part("Spec2/item1")
self.assertEqual(None, spec_part)
spec_part = self.mcd.find_spec_part("/Spec2/item1")
self.assertEqual(None, spec_part)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
spec_part = self.mcd.find_spec_part("Spec2/item1")
self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
def test_get_current_config(self):
cf = { 'module1': { 'item1': 2, 'item2': True } }
self.mcd._set_current_config(cf);
self.assertEqual(cf, self.mcd.get_current_config())
def test_get_local_changes(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
local_changes = self.mcd.get_local_changes()
self.assertEqual({}, local_changes)
self.mcd.set_value("Spec2/item1", 2)
local_changes = self.mcd.get_local_changes()
self.assertEqual({"Spec2": { "item1": 2}}, local_changes)
def test_clear_local_changes(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
self.mcd.set_value("Spec2/item1", 2)
self.mcd.clear_local_changes()
local_changes = self.mcd.get_local_changes()
self.assertEqual({}, local_changes)
pass
def test_get_local_value(self):
value = self.mcd.get_local_value("Spec2/item1")
self.assertEqual(None, value)
self.mcd.set_value("Spec2/item1", 2)
value = self.mcd.get_local_value("Spec2/item1")
self.assertEqual(2, value)
def test_get_current_value(self):
value = self.mcd.get_current_value("Spec2/item1")
self.assertEqual(None, value)
self.mcd._current_config = { "Spec2": { "item1": 3 } }
value = self.mcd.get_current_value("Spec2/item1")
self.assertEqual(3, value)
pass
def test_get_default_value(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
value = self.mcd.get_default_value("Spec2/item1")
self.assertEqual(1, value)
value = self.mcd.get_default_value("/Spec2/item1")
self.assertEqual(1, value)
value = self.mcd.get_default_value("Spec2/item6/value1")
self.assertEqual('default', value)
value = self.mcd.get_default_value("Spec2/item6/value2")
self.assertEqual(None, value)
value = self.mcd.get_default_value("Spec2/no_such_item/asdf")
self.assertEqual(None, value)
def test_get_value(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
self.mcd.set_value("Spec2/item1", 2)
value,status = self.mcd.get_value("Spec2/item1")
self.assertEqual(2, value)
self.assertEqual(MultiConfigData.LOCAL, status)
value,status = self.mcd.get_value("Spec2/item2")
self.assertEqual(1.1, value)
self.assertEqual(MultiConfigData.DEFAULT, status)
self.mcd._current_config = { "Spec2": { "item3": False } }
value,status = self.mcd.get_value("Spec2/item3")
self.assertEqual(False, value)
self.assertEqual(MultiConfigData.CURRENT, status)
value,status = self.mcd.get_value("Spec2/no_such_item")
self.assertEqual(None, value)
self.assertEqual(MultiConfigData.NONE, status)
def test_get_value_maps(self):
maps = self.mcd.get_value_maps()
self.assertEqual([], maps)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.mcd.set_specification(module_spec)
maps = self.mcd.get_value_maps()
self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec1', 'value': None, 'modified': False}], maps)
maps = self.mcd.get_value_maps('Spec2')
self.assertEqual([], maps)
maps = self.mcd.get_value_maps('Spec1')
self.assertEqual([], maps)
self.mcd.remove_specification("Spec1")
self.mcd.remove_specification("foo")
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
maps = self.mcd.get_value_maps()
self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec2', 'value': None, 'modified': False}], maps)
self.mcd._set_current_config({ "Spec2": { "item1": 2 } })
self.mcd.set_value("Spec2/item3", False)
maps = self.mcd.get_value_maps("/Spec2")
self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
{'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
{'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
{'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
maps = self.mcd.get_value_maps("Spec2")
self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
{'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
{'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
{'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item5")
self.assertEqual([{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'a', 'modified': False},
{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'b', 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item1")
self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item2")
self.assertEqual([{'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item3")
self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}], maps)
maps = self.mcd.get_value_maps("/Spec2/item4")
self.assertEqual([{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
self.mcd.set_specification(module_spec)
maps = self.mcd.get_value_maps("/Spec24/item")
self.assertEqual([], maps)
self.mcd._set_current_config({ "Spec24": { "item": [] } })
maps = self.mcd.get_value_maps("/Spec24/item")
self.assertEqual([], maps)
def test_set_value(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
self.mcd.set_value("Spec2/item1", 2)
self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item1", "asdf")
self.mcd.set_value("Spec2/no_such_item", 4)
def test_get_config_item_list(self):
config_items = self.mcd.get_config_item_list()
self.assertEqual([], config_items)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
config_items = self.mcd.get_config_item_list()
self.assertEqual(['Spec2'], config_items)
config_items = self.mcd.get_config_item_list(None, False)
self.assertEqual(['Spec2'], config_items)
config_items = self.mcd.get_config_item_list(None, True)
self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
config_items = self.mcd.get_config_item_list("Spec2", True)
self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
config_items = self.mcd.get_config_item_list("Spec2")
self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/'], config_items)
config_items = self.mcd.get_config_item_list("Spec2", True)
self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
if __name__ == '__main__':
unittest.main()

View File

@@ -12,5 +12,8 @@ CONFIG_TESTDATA_PATH=@abs_top_srcdir@/src/lib/config/testdata
export CONFIG_TESTDATA_PATH
cd ${BIND10_PATH}
exec ${PYTHON_EXEC} -O ${CONFIG_PATH}/datadefinition_test.py $*
${PYTHON_EXEC} -O ${CONFIG_PATH}/config_data_test.py $*
${PYTHON_EXEC} -O ${CONFIG_PATH}/module_spec_test.py $*
${PYTHON_EXEC} -O ${CONFIG_PATH}/cfgmgr_test.py $*

View File

@@ -1,255 +0,0 @@
# Copyright (C) 2009 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.
#
# This class holds the data definition and validates data agains that
# definition. It is the python equivalent of data_def.h
#
import ast
import isc.cc.data
# file objects are passed around as _io.TextIOWrapper objects
# import that so we can check those types
class DataDefinitionError(Exception):
pass
class DataDefinition:
def __init__(self, spec_file, check = True):
if hasattr(spec_file, 'read'):
self._data_spec = self.__read_data_spec_file(spec_file)
elif type(spec_file) == str:
file = open(spec_file)
self._data_spec = self.__read_data_spec_file(file)
file.close()
else:
raise DataDefinitionError("Not a str or file-like object")
def validate(self, data, errors = None):
"""Check whether the given piece of data conforms to this
data definition. If so, it returns True. If not, it will
return false. If errors is given, and is an array, a string
describing the error will be appended to it. The current
version stops as soon as there is one error so this list
will not be exhaustive."""
data_def = self.get_definition()
if 'data_specification' not in data_def:
if errors:
errors.append("Data definition has no data_specification element")
return False
data_def = data_def['data_specification']
if 'config_data' not in data_def:
if errors:
errors.append("The is no config_data for this specification")
return False
errors = []
return _validate_spec_list(data_def['config_data'], data, errors)
def __read_data_spec_file(self, file, check = True):
"""Reads the data spec from the given file object.
If check is True, check whether it is of the correct form.
If it is not, an DataDefinitionError exception is raised"""
if not hasattr(file, 'read'):
raise DataDefinitionError("Not a file-like object:" + str(type(file)))
str = file.read(-1)
# TODO catch error here and reraise as a less ugly exception
data_spec = ast.literal_eval(str)
if check:
# TODO
_check(data_spec)
pass
return data_spec
def get_definition(self):
return self._data_spec
def get_module_name(self):
return self._data_spec["data_specification"]["module_name"]
def _check(data_spec):
"""Checks the full specification. This is a dict that contains the
element "data_specification", which is in itself a dict that
must contain at least a "module_name" (string) and optionally
a "config_data" and a "commands" element, both of which are lists
of dicts. Raises a DataDefinitionError if there is a problem."""
if type(data_spec) != dict:
raise DataDefinitionError("data specification not a dict")
if "data_specification" not in data_spec:
raise DataDefinitionError("no data_specification element in specification")
data_spec = data_spec["data_specification"]
if "module_name" not in data_spec:
raise DataDefinitionError("no module_name in data_specification")
if "config_data" in data_spec:
_check_config_spec(data_spec["config_data"])
if "commands" in data_spec:
_check_command_spec(data_spec["commands"])
def _check_config_spec(config_data):
# config data is a list of items represented by dicts that contain
# things like "item_name", depending on the type they can have
# specific subitems
"""Checks a list that contains the configuration part of the
specification. Raises a DataDefinitionError if there is a
problem."""
if type(config_data) != list:
raise DataDefinitionError("config_data is not a list of items")
for config_item in config_data:
_check_item_spec(config_item)
def _check_command_spec(commands):
"""Checks the list that contains a set of commands. Raises a
DataDefinitionError is there is an error"""
if type(commands) != list:
raise DataDefinitionError("commands is not a list of commands")
for command in commands:
if type(command) != dict:
raise DataDefinitionError("command in commands list is not a dict")
if "command_name" not in command:
raise DataDefinitionError("no command_name in command item")
command_name = command["command_name"]
if type(command_name) != str:
raise DataDefinitionError("command_name not a string: " + str(type(command_name)))
if "command_description" in command:
if type(command["command_description"]) != str:
raise DataDefinitionError("command_description not a string in " + command_name)
if "command_args" in command:
if type(command["command_args"]) != list:
raise DataDefinitionError("command_args is not a list in " + command_name)
for command_arg in command["command_args"]:
if type(command_arg) != dict:
raise DataDefinitionError("command argument not a dict in " + command_name)
_check_item_spec(command_arg)
else:
raise DataDefinitionError("command_args missing in " + command_name)
pass
def _check_item_spec(config_item):
"""Checks the dict that defines one config item
(i.e. containing "item_name", "item_type", etc.
Raises a DataDefinitionError if there is an error"""
if type(config_item) != dict:
raise DataDefinitionError("item spec not a dict")
if "item_name" not in config_item:
raise DataDefinitionError("no item_name in config item")
if type(config_item["item_name"]) != str:
raise DataDefinitionError("item_name is not a string: " + str(config_item["item_name"]))
item_name = config_item["item_name"]
if "item_type" not in config_item:
raise DataDefinitionError("no item_type in config item")
item_type = config_item["item_type"]
if type(item_type) != str:
raise DataDefinitionError("item_type in " + item_name + " is not a string: " + str(type(item_type)))
if item_type not in ["integer", "real", "boolean", "string", "list", "map", "any"]:
raise DataDefinitionError("unknown item_type in " + item_name + ": " + item_type)
if "item_optional" in config_item:
if type(config_item["item_optional"]) != bool:
raise DataDefinitionError("item_default in " + item_name + " is not a boolean")
if not config_item["item_optional"] and "item_default" not in config_item:
raise DataDefinitionError("no default value for non-optional item " + item_name)
else:
raise DataDefinitionError("item_optional not in item " + item_name)
if "item_default" in config_item:
item_default = config_item["item_default"]
if (item_type == "integer" and type(item_default) != int) or \
(item_type == "real" and type(item_default) != float) or \
(item_type == "boolean" and type(item_default) != bool) or \
(item_type == "string" and type(item_default) != str) or \
(item_type == "list" and type(item_default) != list) or \
(item_type == "map" and type(item_default) != dict):
raise DataDefinitionError("Wrong type for item_default in " + item_name)
# TODO: once we have check_type, run the item default through that with the list|map_item_spec
if item_type == "list":
if "list_item_spec" not in config_item:
raise DataDefinitionError("no list_item_spec in list item " + item_name)
if type(config_item["list_item_spec"]) != dict:
raise DataDefinitionError("list_item_spec in " + item_name + " is not a dict")
_check_item_spec(config_item["list_item_spec"])
if item_type == "map":
if "map_item_spec" not in config_item:
raise DataDefinitionError("no map_item_sepc in map item " + item_name)
if type(config_item["map_item_spec"]) != list:
raise DataDefinitionError("map_item_spec in " + item_name + " is not a list")
for map_item in config_item["map_item_spec"]:
if type(map_item) != dict:
raise DataDefinitionError("map_item_spec element is not a dict")
_check_item_spec(map_item)
def _validate_type(spec, value, errors):
"""Returns true if the value is of the correct type given the
specification"""
data_type = spec['item_type']
if data_type == "integer" and type(value) != int:
if errors:
errors.append(str(value) + " should be an integer")
return False
elif data_type == "real" and type(value) != float:
if errors:
errors.append(str(value) + " should be a real")
return False
elif data_type == "boolean" and type(value) != bool:
if errors:
errors.append(str(value) + " should be a boolean")
return False
elif data_type == "string" and type(value) != str:
if errors:
errors.append(str(value) + " should be a string")
return False
elif data_type == "list" and type(value) != list:
if errors:
errors.append(str(value) + " should be a list, not a " + str(value.__class__.__name__))
return False
elif data_type == "map" and type(value) != dict:
if errors:
errors.append(str(value) + " should be a map")
return False
else:
return True
def _validate_item(spec, data, errors):
if not _validate_type(spec, data, errors):
return False
elif type(data) == list:
list_spec = spec['list_item_spec']
for data_el in data:
if not _validate_type(list_spec, data_el, errors):
return False
if list_spec['item_type'] == "map":
if not _validate_item(list_spec, data_el, errors):
return False
elif type(data) == dict:
if not _validate_spec_list(spec['map_item_spec'], data, errors):
return False
return True
def _validate_spec(spec, data, errors):
item_name = spec['item_name']
item_optional = spec['item_optional']
if item_name in data:
return _validate_item(spec, data[item_name], errors)
elif not item_optional:
if errors:
errors.append("non-optional item " + item_name + " missing")
return False
else:
return True
def _validate_spec_list(data_spec, data, errors):
for spec_item in data_spec:
if not _validate_spec(spec_item, data, errors):
return False
return True

View File

@@ -1,92 +0,0 @@
# Copyright (C) 2009 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.
#
# Tests for the datadefinition module
#
import unittest
import os
from isc.config import DataDefinition, DataDefinitionError
import isc.cc.data
class TestDataDefinition(unittest.TestCase):
def setUp(self):
self.assert_('CONFIG_TESTDATA_PATH' in os.environ)
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
def spec_file(self, filename):
return(self.data_path + os.sep + filename)
def read_spec_file(self, filename):
return DataDefinition(self.spec_file(filename))
def spec1(self, dd):
data_def = dd.get_definition()
self.assert_('data_specification' in data_def)
data_spec = data_def['data_specification']
self.assert_('module_name' in data_spec)
self.assertEqual(data_spec['module_name'], "Spec1")
def test_open_file_name(self):
dd = DataDefinition(self.spec_file("spec1.spec"))
self.spec1(dd)
def test_open_file_obj(self):
file1 = open(self.spec_file("spec1.spec"))
dd = DataDefinition(file1)
self.spec1(dd)
def test_bad_specfiles(self):
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec3.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec4.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec5.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec6.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec7.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec8.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec9.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec10.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec11.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec12.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec13.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec14.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec15.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec16.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec17.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec18.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec19.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec20.spec")
self.assertRaises(DataDefinitionError, self.read_spec_file, "spec21.spec")
def validate_data(self, specfile_name, datafile_name):
dd = DataDefinition(self.spec_file(specfile_name));
data_file = open(self.spec_file(datafile_name))
data_str = data_file.read()
data = isc.cc.data.parse_value_str(data_str)
return dd.validate(data)
def test_data_validation(self):
self.assertEqual(True, self.validate_data("spec22.spec", "data22_1.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_2.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_3.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_4.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_5.data"))
self.assertEqual(True, self.validate_data("spec22.spec", "data22_6.data"))
self.assertEqual(True, self.validate_data("spec22.spec", "data22_7.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_8.data"))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,281 @@
# Copyright (C) 2009 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.
"""Module Specifications
A module specification holds the information about what configuration
a module can have, and what commands it understands. It provides
functions to read it from a .spec file, and to validate a given
set of data against the specification
"""
import ast
import isc.cc.data
# file objects are passed around as _io.TextIOWrapper objects
# import that so we can check those types
class ModuleSpecError(Exception):
"""This exception is raised it the ModuleSpec fails to initialize
or if there is a failure or parse error reading the specification
file"""
pass
def module_spec_from_file(spec_file, check = True):
"""Returns a ModuleSpec object defined by the file at spec_file.
If check is True, the contents are verified. If there is an error
in those contents, a ModuleSpecError is raised."""
module_spec = None
if hasattr(spec_file, 'read'):
module_spec = ast.literal_eval(spec_file.read(-1))
elif type(spec_file) == str:
file = open(spec_file)
module_spec = ast.literal_eval(file.read(-1))
file.close()
else:
raise ModuleSpecError("spec_file not a str or file-like object")
if 'module_spec' not in module_spec:
raise ModuleSpecError("Data definition has no module_spec element")
result = ModuleSpec(module_spec['module_spec'], check)
return result
class ModuleSpec:
def __init__(self, module_spec, check = True):
"""Initializes a ModuleSpec object from the specification in
the given module_spec (which must be a dict). If check is
True, the contents are verified. Raises a ModuleSpec error
if there is something wrong with the contents of the dict"""
if type(module_spec) != dict:
raise ModuleSpecError("module_spec is of type " + str(type(module_spec)) + ", not dict")
if check:
_check(module_spec)
self._module_spec = module_spec
def validate_config(self, full, data, errors = None):
"""Check whether the given piece of data conforms to this
data definition. If so, it returns True. If not, it will
return false. If errors is given, and is an array, a string
describing the error will be appended to it. The current
version stops as soon as there is one error so this list
will not be exhaustive. If 'full' is true, it also errors on
non-optional missing values. Set this to False if you want to
validate only a part of a configuration tree (like a list of
non-default values)"""
data_def = self.get_config_spec()
return _validate_spec_list(data_def, full, data, errors)
def get_module_name(self):
"""Returns a string containing the name of the module as
specified by the specification given at __init__"""
return self._module_spec['module_name']
def get_full_spec(self):
"""Returns a dict representation of the full module specification"""
return self._module_spec
def get_config_spec(self):
"""Returns a dict representation of the configuration data part
of the specification, or None if there is none."""
if 'config_data' in self._module_spec:
return self._module_spec['config_data']
else:
return None
def get_commands_spec(self):
"""Returns a dict representation of the commands part of the
specification, or None if there is none."""
if 'commands' in self._module_spec:
return self._module_spec['commands']
else:
return None
def __str__(self):
"""Returns a string representation of the full specification"""
return self._module_spec.__str__()
def _check(module_spec):
"""Checks the full specification. This is a dict that contains the
element "module_spec", which is in itself a dict that
must contain at least a "module_name" (string) and optionally
a "config_data" and a "commands" element, both of which are lists
of dicts. Raises a ModuleSpecError if there is a problem."""
if type(module_spec) != dict:
raise ModuleSpecError("data specification not a dict")
if "module_name" not in module_spec:
raise ModuleSpecError("no module_name in module_spec")
if "config_data" in module_spec:
_check_config_spec(module_spec["config_data"])
if "commands" in module_spec:
_check_command_spec(module_spec["commands"])
def _check_config_spec(config_data):
# config data is a list of items represented by dicts that contain
# things like "item_name", depending on the type they can have
# specific subitems
"""Checks a list that contains the configuration part of the
specification. Raises a ModuleSpecError if there is a
problem."""
if type(config_data) != list:
raise ModuleSpecError("config_data is of type " + str(type(config_data)) + ", not a list of items")
for config_item in config_data:
_check_item_spec(config_item)
def _check_command_spec(commands):
"""Checks the list that contains a set of commands. Raises a
ModuleSpecError is there is an error"""
if type(commands) != list:
raise ModuleSpecError("commands is not a list of commands")
for command in commands:
if type(command) != dict:
raise ModuleSpecError("command in commands list is not a dict")
if "command_name" not in command:
raise ModuleSpecError("no command_name in command item")
command_name = command["command_name"]
if type(command_name) != str:
raise ModuleSpecError("command_name not a string: " + str(type(command_name)))
if "command_description" in command:
if type(command["command_description"]) != str:
raise ModuleSpecError("command_description not a string in " + command_name)
if "command_args" in command:
if type(command["command_args"]) != list:
raise ModuleSpecError("command_args is not a list in " + command_name)
for command_arg in command["command_args"]:
if type(command_arg) != dict:
raise ModuleSpecError("command argument not a dict in " + command_name)
_check_item_spec(command_arg)
else:
raise ModuleSpecError("command_args missing in " + command_name)
pass
def _check_item_spec(config_item):
"""Checks the dict that defines one config item
(i.e. containing "item_name", "item_type", etc.
Raises a ModuleSpecError if there is an error"""
if type(config_item) != dict:
raise ModuleSpecError("item spec not a dict")
if "item_name" not in config_item:
raise ModuleSpecError("no item_name in config item")
if type(config_item["item_name"]) != str:
raise ModuleSpecError("item_name is not a string: " + str(config_item["item_name"]))
item_name = config_item["item_name"]
if "item_type" not in config_item:
raise ModuleSpecError("no item_type in config item")
item_type = config_item["item_type"]
if type(item_type) != str:
raise ModuleSpecError("item_type in " + item_name + " is not a string: " + str(type(item_type)))
if item_type not in ["integer", "real", "boolean", "string", "list", "map", "any"]:
raise ModuleSpecError("unknown item_type in " + item_name + ": " + item_type)
if "item_optional" in config_item:
if type(config_item["item_optional"]) != bool:
raise ModuleSpecError("item_default in " + item_name + " is not a boolean")
if not config_item["item_optional"] and "item_default" not in config_item:
raise ModuleSpecError("no default value for non-optional item " + item_name)
else:
raise ModuleSpecError("item_optional not in item " + item_name)
if "item_default" in config_item:
item_default = config_item["item_default"]
if (item_type == "integer" and type(item_default) != int) or \
(item_type == "real" and type(item_default) != float) or \
(item_type == "boolean" and type(item_default) != bool) or \
(item_type == "string" and type(item_default) != str) or \
(item_type == "list" and type(item_default) != list) or \
(item_type == "map" and type(item_default) != dict):
raise ModuleSpecError("Wrong type for item_default in " + item_name)
# TODO: once we have check_type, run the item default through that with the list|map_item_spec
if item_type == "list":
if "list_item_spec" not in config_item:
raise ModuleSpecError("no list_item_spec in list item " + item_name)
if type(config_item["list_item_spec"]) != dict:
raise ModuleSpecError("list_item_spec in " + item_name + " is not a dict")
_check_item_spec(config_item["list_item_spec"])
if item_type == "map":
if "map_item_spec" not in config_item:
raise ModuleSpecError("no map_item_sepc in map item " + item_name)
if type(config_item["map_item_spec"]) != list:
raise ModuleSpecError("map_item_spec in " + item_name + " is not a list")
for map_item in config_item["map_item_spec"]:
if type(map_item) != dict:
raise ModuleSpecError("map_item_spec element is not a dict")
_check_item_spec(map_item)
def _validate_type(spec, value, errors):
"""Returns true if the value is of the correct type given the
specification"""
data_type = spec['item_type']
if data_type == "integer" and type(value) != int:
if errors != None:
errors.append(str(value) + " should be an integer")
return False
elif data_type == "real" and type(value) != float:
if errors != None:
errors.append(str(value) + " should be a real")
return False
elif data_type == "boolean" and type(value) != bool:
if errors != None:
errors.append(str(value) + " should be a boolean")
return False
elif data_type == "string" and type(value) != str:
if errors != None:
errors.append(str(value) + " should be a string")
return False
elif data_type == "list" and type(value) != list:
if errors != None:
errors.append(str(value) + " should be a list, not a " + str(value.__class__.__name__))
return False
elif data_type == "map" and type(value) != dict:
if errors != None:
errors.append(str(value) + " should be a map")
return False
else:
return True
def _validate_item(spec, full, data, errors):
if not _validate_type(spec, data, errors):
return False
elif type(data) == list:
list_spec = spec['list_item_spec']
for data_el in data:
if not _validate_type(list_spec, data_el, errors):
return False
if list_spec['item_type'] == "map":
if not _validate_item(list_spec, full, data_el, errors):
return False
elif type(data) == dict:
if not _validate_spec_list(spec['map_item_spec'], full, data, errors):
return False
return True
def _validate_spec(spec, full, data, errors):
item_name = spec['item_name']
item_optional = spec['item_optional']
if item_name in data:
return _validate_item(spec, full, data[item_name], errors)
elif full and not item_optional:
if errors != None:
errors.append("non-optional item " + item_name + " missing")
return False
else:
return True
def _validate_spec_list(module_spec, full, data, errors):
for spec_item in module_spec:
if not _validate_spec(spec_item, full, data, errors):
return False
return True

View File

@@ -0,0 +1,92 @@
# Copyright (C) 2009 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.
#
# Tests for the module_spec module
#
import unittest
import os
from isc.config import ModuleSpec, ModuleSpecError
import isc.cc.data
class TestModuleSpec(unittest.TestCase):
def setUp(self):
if 'CONFIG_TESTDATA_PATH' in os.environ:
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
else:
self.data_path = "../../../testdata"
def spec_file(self, filename):
return(self.data_path + os.sep + filename)
def read_spec_file(self, filename):
return isc.config.module_spec_from_file(self.spec_file(filename))
def spec1(self, dd):
module_spec = dd.get_full_spec()
self.assert_('module_name' in module_spec)
self.assertEqual(module_spec['module_name'], "Spec1")
def test_open_file_name(self):
dd = self.read_spec_file("spec1.spec")
self.spec1(dd)
def test_open_file_obj(self):
file1 = open(self.spec_file("spec1.spec"))
dd = isc.config.module_spec_from_file(file1)
self.spec1(dd)
def test_bad_specfiles(self):
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec3.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec4.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec5.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec6.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec7.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec8.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec9.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec10.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec11.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec12.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec13.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec14.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec15.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec16.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec17.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec18.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec19.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec20.spec")
self.assertRaises(ModuleSpecError, self.read_spec_file, "spec21.spec")
def validate_data(self, specfile_name, datafile_name):
dd = self.read_spec_file(specfile_name);
data_file = open(self.spec_file(datafile_name))
data_str = data_file.read()
data = isc.cc.data.parse_value_str(data_str)
return dd.validate_config(True, data)
def test_data_validation(self):
self.assertEqual(True, self.validate_data("spec22.spec", "data22_1.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_2.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_3.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_4.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_5.data"))
self.assertEqual(True, self.validate_data("spec22.spec", "data22_6.data"))
self.assertEqual(True, self.validate_data("spec22.spec", "data22_7.data"))
self.assertEqual(False, self.validate_data("spec22.spec", "data22_8.data"))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1 @@
{'version': 0}

View File

@@ -0,0 +1 @@
{'version':

View File

1
src/lib/config/testdata/b10-config.db vendored Normal file
View File

@@ -0,0 +1 @@
{'TestModule': {'test': 125}, 'version': 1}

View File

@@ -4,5 +4,6 @@
"value3": True,
"value4": "foo",
"value5": [ 1, 2, 3 ],
"value6": { "v61": "bar", "v62": True }
"value6": { "v61": "bar", "v62": True },
"value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
}

View File

@@ -5,5 +5,6 @@
"value4": "foo",
"value5": [ 1, 2, 3 ],
"value6": { "v61": "bar", "v62": True },
"value7": [ 1, 2.2, "str", True ]
"value7": [ 1, 2.2, "str", True ],
"value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
}

View File

@@ -5,5 +5,6 @@
"value4": "foo",
"value5": [ 1, 2, 3 ],
"value6": { "v61": "bar", "v62": True },
"value8": [ { "a": "d" }, { "a": "e" } ]
"value8": [ { "a": "d" }, { "a": "e" } ],
"value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec1"
}
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": 1
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"commands": [
{

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"commands": [
{

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"commands": [
{

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",
@@ -49,6 +49,23 @@
}
]
}
],
"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": []
}
]
}
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"commands": [
{

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"commands": 1
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "value1",
@@ -25,7 +25,7 @@
{ "item_name": "value5",
"item_type": "list",
"item_optional": False,
"item_default": [ ],
"item_default": [ "a", "b" ],
"list_item_spec": {
"item_name": "list_element",
"item_type": "integer",
@@ -78,6 +78,36 @@
]
}
},
{ "item_name": "value9",
"item_type": "map",
"item_optional": False,
"item_default": {},
"map_item_spec": [
{ "item_name": "v91",
"item_type": "string",
"item_optional": False,
"item_default": "def"
},
{ "item_name": "v92",
"item_type": "map",
"item_optional": False,
"item_default": {},
"map_item_spec": [
{ "item_name": "v92a",
"item_type": "string",
"item_optional": False,
"item_default": "Hello"
} ,
{
"item_name": "v92b",
"item_type": "integer",
"item_optional": False,
"item_default": 47806
}
]
}
]
}
]
}
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"commands": [
{

View File

@@ -1,6 +1,6 @@
{
"data_specification": {
"module_name": "Spec2",
"module_spec": {
"module_name": "Spec3",
"config_data": [
{
"item_type": "integer",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
}
}

View File

@@ -1,5 +1,5 @@
{
"data_specification": {
"module_spec": {
"module_name": "Spec2",
"config_data": [
{ "item_name": "item1",

View File

@@ -2,6 +2,8 @@
lib_LTLIBRARIES = libexceptions.la
libexceptions_la_SOURCES = exceptions.h exceptions.cc
CLEANFILES = *.gcno *.gcda
TESTS =
if HAVE_GTEST
TESTS += run_unittests

View File

@@ -43,6 +43,16 @@ public:
/// @param what a description (type) of the exception.
Exception(const char* file, size_t line, const char* what) :
file_(file), line_(line), what_(what) {}
/// \brief Constructor for a given type for exceptions with file name and
/// file line number.
///
/// @param file the file name where the exception was thrown.
/// @param line the line in @ref file where the exception was thrown.
/// @param what a description (type) of the exception.
Exception(const char* file, size_t line, const std::string& what) :
file_(file), line_(line), what_(what) {}
/// The destructor
virtual ~Exception() throw() {}
//@}

View File

@@ -13,11 +13,14 @@
# svn commit
# need new boost stuff?
# TODO: LICENSE_1_0.txt
# add files to list 'ere
FILES="
boost/*.hpp
boost/algorithm
boost/asio
boost/assign/list_inserter.hpp
boost/assign/std/vector.hpp
boost/bind
boost/config
boost/concept