Commit 8c5e6ce8 authored by Thomas Markwalder's avatar Thomas Markwalder

[#32,!23] dhcp4 now parses "config-control" element

src/bin/dhcp4/dhcp4_lexer.ll
src/bin/dhcp4/dhcp4_parser.yy
    Added parsing support for config-control and config-databases

src/bin/dhcp4/json_config_parser.cc
    configureDhcp4Server() - added handler for config-control element

src/bin/dhcp4/parser_context.*
    added CONFIG_CONTROL and CONFIG_DATABASE

src/bin/dhcp4/tests/config_parser_unittest.cc
   TEST_F(Dhcp4ParserTest, configControlInfo) - new test
parent a8e05f99
This diff is collapsed.
......@@ -321,6 +321,24 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
}
}
\"config-control\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::DHCP4:
return isc::dhcp::Dhcp4Parser::make_CONFIG_CONTROL(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("config-control", driver.loc_);
}
}
\"config-databases\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::CONFIG_CONTROL:
return isc::dhcp::Dhcp4Parser::make_CONFIG_DATABASES(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("config-databases", driver.loc_);
}
}
\"readonly\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
......@@ -335,6 +353,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::OPTION_DEF:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_TYPE(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("type", driver.loc_);
......@@ -381,6 +400,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_USER(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("user", driver.loc_);
......@@ -391,6 +411,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_PASSWORD(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("password", driver.loc_);
......@@ -401,6 +422,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_HOST(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("host", driver.loc_);
......@@ -411,6 +433,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_PORT(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("port", driver.loc_);
......@@ -441,6 +464,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_CONNECT_TIMEOUT(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("connect-timeout", driver.loc_);
......@@ -451,6 +475,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_KEYSPACE(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("keyspace", driver.loc_);
......@@ -461,6 +486,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_RECONNECT_WAIT_TIME(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("reconnect-wait-time", driver.loc_);
......@@ -471,6 +497,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_REQUEST_TIMEOUT(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("request-timeout", driver.loc_);
......@@ -481,6 +508,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_TCP_KEEPALIVE(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("tcp-keepalive", driver.loc_);
......@@ -491,6 +519,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_TCP_NODELAY(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("tcp-nodelay", driver.loc_);
......@@ -501,6 +530,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_CONTACT_POINTS(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("contact-points", driver.loc_);
......@@ -511,6 +541,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
return isc::dhcp::Dhcp4Parser::make_MAX_RECONNECT_TRIES(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("max-reconnect-tries", driver.loc_);
......@@ -606,6 +637,7 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::LEASE_DATABASE:
case isc::dhcp::Parser4Context::HOSTS_DATABASE:
case isc::dhcp::Parser4Context::CONFIG_DATABASE:
case isc::dhcp::Parser4Context::OPTION_DEF:
case isc::dhcp::Parser4Context::OPTION_DATA:
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
......
......@@ -763,10 +763,7 @@ will drop its message if the received message was DHCPDISCOVER,
and will send DHCPNAK if the received message was DHCPREQUEST.
The argument includes the client and the transaction identification
information.
<<<<<<< HEAD
=======
% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 port %3 on interface %4
This debug message is printed when the server is receiving a DHCPv4o6
from the DHCPv6 server over inter-process communication.
>>>>>>> trac5404
This diff is collapsed.
This diff is collapsed.
......@@ -50,6 +50,8 @@ using namespace std;
NULL_TYPE "null"
DHCP4 "Dhcp4"
CONFIG_CONTROL "config-control"
CONFIG_DATABASES "config-databases"
INTERFACES_CONFIG "interfaces-config"
INTERFACES "interfaces"
DHCP_SOCKET_TYPE "dhcp-socket-type"
......@@ -220,6 +222,7 @@ using namespace std;
SUB_HOOKS_LIBRARY
SUB_DHCP_DDNS
SUB_LOGGING
SUB_CONFIG_CONTROL
;
%token <std::string> STRING "constant string"
......@@ -258,6 +261,7 @@ start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json
| SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library
| SUB_DHCP_DDNS { ctx.ctx_ = ctx.DHCP_DDNS; } sub_dhcp_ddns
| SUB_LOGGING { ctx.ctx_ = ctx.LOGGING; } sub_logging
| SUB_CONFIG_CONTROL { ctx.ctx_ = ctx.CONFIG_CONTROL; } sub_config_control
;
// ---- generic JSON parser ---------------------------------
......@@ -453,6 +457,7 @@ global_param: valid_lifetime
| comment
| sanity_checks
| reservations
| config_control
| unknown_map_entry
;
......@@ -2010,6 +2015,55 @@ control_agent_json_object: CONTROL_AGENT {
ctx.leave();
};
config_control: LCURLY_BRACKET {
ElementPtr m(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.back()->add(m);
ctx.stack_.push_back(m);
} config_control_params RCURLY_BRACKET {
ctx.stack_.pop_back();
};
config_control: CONFIG_CONTROL {
ElementPtr i(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("config-control", i);
ctx.stack_.push_back(i);
ctx.enter(ctx.CONFIG_CONTROL);
} COLON LCURLY_BRACKET config_control_params RCURLY_BRACKET {
// No config control params are required
ctx.stack_.pop_back();
ctx.leave();
};
sub_config_control: LCURLY_BRACKET {
// Parse the config-control map
ElementPtr m(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.push_back(m);
} config_control_params RCURLY_BRACKET {
// No config_control params are required
// parsing completed
};
// This defines that subnet can have one or more parameters.
config_control_params: config_control_param
| config_control_params COMMA config_control_param
;
// This defines a list of allowed parameters for each subnet.
config_control_param: config_databases
| unknown_map_entry
;
config_databases: CONFIG_DATABASES {
ElementPtr l(new ListElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("config-databases", l);
ctx.stack_.push_back(l);
ctx.enter(ctx.CONFIG_DATABASE);
} COLON LSQUARE_BRACKET database_list RSQUARE_BRACKET {
ctx.stack_.pop_back();
ctx.leave();
};
// --- logging entry -----------------------------------------
// This defines the top level "Logging" object. It parses
......
......@@ -28,6 +28,7 @@
#include <dhcpsrv/parsers/sanity_checks_parser.h>
#include <dhcpsrv/host_data_source_factory.h>
#include <dhcpsrv/timer_mgr.h>
#include <process/config_ctl_parser.h>
#include <hooks/hooks_parser.h>
#include <config/command_mgr.h>
#include <util/encode/hex.h>
......@@ -494,6 +495,13 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
continue;
}
if (config_pair.first == "config-control") {
process::ConfigControlParser parser;
process::ConfigControlInfoPtr config_ctl_info = parser.parse(config_pair.second);
CfgMgr::instance().getStagingCfg()->setConfigControlInfo(config_ctl_info);
continue;
}
// Timers are not used in the global scope. Their values are derived
// to specific subnets (see SimpleParser6::deriveParameters).
// decline-probation-period, dhcp4o6-port, echo-client-id,
......
// Generated 201809140750
// A Bison parser, made by GNU Bison 3.0.5.
// A Bison parser, made by GNU Bison 3.0.4.
// Locations for Bison parsers in C++
// Copyright (C) 2002-2015, 2018 Free Software Foundation, Inc.
// Copyright (C) 2002-2015 Free Software Foundation, Inc.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
......@@ -41,9 +40,9 @@
# include "position.hh"
#line 14 "dhcp4_parser.yy" // location.cc:292
#line 14 "dhcp4_parser.yy" // location.cc:296
namespace isc { namespace dhcp {
#line 46 "location.hh" // location.cc:292
#line 46 "location.hh" // location.cc:296
/// Abstract a location.
class location
{
......@@ -53,27 +52,30 @@ namespace isc { namespace dhcp {
location (const position& b, const position& e)
: begin (b)
, end (e)
{}
{
}
/// Construct a 0-width location in \a p.
explicit location (const position& p = position ())
: begin (p)
, end (p)
{}
{
}
/// Construct a 0-width location in \a f, \a l, \a c.
explicit location (std::string* f,
unsigned l = 1u,
unsigned c = 1u)
unsigned int l = 1u,
unsigned int c = 1u)
: begin (f, l, c)
, end (f, l, c)
{}
{
}
/// Initialization.
void initialize (std::string* f = YY_NULLPTR,
unsigned l = 1u,
unsigned c = 1u)
unsigned int l = 1u,
unsigned int c = 1u)
{
begin.initialize (f, l, c);
end = begin;
......@@ -171,7 +173,7 @@ namespace isc { namespace dhcp {
inline std::basic_ostream<YYChar>&
operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
{
unsigned end_col = 0 < loc.end.column ? loc.end.column - 1 : 0;
unsigned int end_col = 0 < loc.end.column ? loc.end.column - 1 : 0;
ostr << loc.begin;
if (loc.end.filename
&& (!loc.begin.filename
......@@ -184,7 +186,7 @@ namespace isc { namespace dhcp {
return ostr;
}
#line 14 "dhcp4_parser.yy" // location.cc:292
#line 14 "dhcp4_parser.yy" // location.cc:296
} } // isc::dhcp
#line 189 "location.hh" // location.cc:292
#line 192 "location.hh" // location.cc:296
#endif // !YY_PARSER4_LOCATION_HH_INCLUDED
......@@ -196,6 +196,10 @@ Parser4Context::contextName()
return ("shared-networks");
case SANITY_CHECKS:
return ("sanity-checks");
case CONFIG_CONTROL:
return ("config-control");
case CONFIG_DATABASE:
return ("config-database");
default:
return ("__unknown__");
}
......
......@@ -292,7 +292,13 @@ public:
NCR_FORMAT,
/// Used while parsing Dhcp4/dhcp-ddns/replace-client-name.
REPLACE_CLIENT_NAME
REPLACE_CLIENT_NAME,
/// Used while parsing Dhcp4/config-control
CONFIG_CONTROL,
/// Used while parsing config-control/config-databases
CONFIG_DATABASE
} ParserContext;
......
// Generated 201809140750
// A Bison parser, made by GNU Bison 3.0.5.
// A Bison parser, made by GNU Bison 3.0.4.
// Positions for Bison parsers in C++
// Copyright (C) 2002-2015, 2018 Free Software Foundation, Inc.
// Copyright (C) 2002-2015 Free Software Foundation, Inc.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
......@@ -51,27 +50,28 @@
# endif
# endif
#line 14 "dhcp4_parser.yy" // location.cc:292
#line 14 "dhcp4_parser.yy" // location.cc:296
namespace isc { namespace dhcp {
#line 56 "position.hh" // location.cc:292
#line 56 "position.hh" // location.cc:296
/// Abstract a position.
class position
{
public:
/// Construct a position.
explicit position (std::string* f = YY_NULLPTR,
unsigned l = 1u,
unsigned c = 1u)
unsigned int l = 1u,
unsigned int c = 1u)
: filename (f)
, line (l)
, column (c)
{}
{
}
/// Initialization.
void initialize (std::string* fn = YY_NULLPTR,
unsigned l = 1u,
unsigned c = 1u)
unsigned int l = 1u,
unsigned int c = 1u)
{
filename = fn;
line = l;
......@@ -100,15 +100,15 @@ namespace isc { namespace dhcp {
/// File name to which this position refers.
std::string* filename;
/// Current line number.
unsigned line;
unsigned int line;
/// Current column number.
unsigned column;
unsigned int column;
private:
/// Compute max(min, lhs+rhs) (provided min <= lhs).
static unsigned add_ (unsigned lhs, int rhs, unsigned min)
static unsigned int add_ (unsigned int lhs, int rhs, unsigned int min)
{
return (0 < rhs || -static_cast<unsigned>(rhs) < lhs
return (0 < rhs || -static_cast<unsigned int>(rhs) < lhs
? rhs + lhs
: min);
}
......@@ -174,7 +174,7 @@ namespace isc { namespace dhcp {
return ostr << pos.line << '.' << pos.column;
}
#line 14 "dhcp4_parser.yy" // location.cc:292
#line 14 "dhcp4_parser.yy" // location.cc:296
} } // isc::dhcp
#line 179 "position.hh" // location.cc:292
#line 180 "position.hh" // location.cc:296
#endif // !YY_PARSER4_POSITION_HH_INCLUDED
// Generated 201809140750
// A Bison parser, made by GNU Bison 3.0.5.
// A Bison parser, made by GNU Bison 3.0.4.
// Stack handling for Bison parsers in C++
// Copyright (C) 2002-2015, 2018 Free Software Foundation, Inc.
// Copyright (C) 2002-2015 Free Software Foundation, Inc.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
......@@ -41,10 +40,9 @@
# include <vector>
#line 14 "dhcp4_parser.yy" // stack.hh:131
#line 14 "dhcp4_parser.yy" // stack.hh:132
namespace isc { namespace dhcp {
#line 46 "stack.hh" // stack.hh:131
/// A stack with random access from its top.
#line 46 "stack.hh" // stack.hh:132
template <class T, class S = std::vector<T> >
class stack
{
......@@ -59,24 +57,20 @@ namespace isc { namespace dhcp {
seq_.reserve (200);
}
stack (unsigned n)
stack (unsigned int n)
: seq_ (n)
{}
/// Random access.
///
/// Index 0 returns the topmost element.
inline
T&
operator[] (unsigned i)
operator[] (unsigned int i)
{
return seq_[seq_.size () - 1 - i];
}
/// Random access.
///
/// Index 0 returns the topmost element.
inline
const T&
operator[] (unsigned i) const
operator[] (unsigned int i) const
{
return seq_[seq_.size () - 1 - i];
}
......@@ -84,6 +78,7 @@ namespace isc { namespace dhcp {
/// Steal the contents of \a t.
///
/// Close to move-semantics.
inline
void
push (T& t)
{
......@@ -91,8 +86,9 @@ namespace isc { namespace dhcp {
operator[](0).move (t);
}
inline
void
pop (unsigned n = 1)
pop (unsigned int n = 1)
{
for (; n; --n)
seq_.pop_back ();
......@@ -104,18 +100,21 @@ namespace isc { namespace dhcp {
seq_.clear ();
}
inline
typename S::size_type
size () const
{
return seq_.size ();
}
inline
const_iterator
begin () const
{
return seq_.rbegin ();
}
inline
const_iterator
end () const
{
......@@ -134,24 +133,25 @@ namespace isc { namespace dhcp {
class slice
{
public:
slice (const S& stack, unsigned range)
slice (const S& stack, unsigned int range)
: stack_ (stack)
, range_ (range)
{}
inline
const T&
operator [] (unsigned i) const
operator [] (unsigned int i) const
{
return stack_[range_ - i];
}
private:
const S& stack_;
unsigned range_;
unsigned int range_;
};
#line 14 "dhcp4_parser.yy" // stack.hh:131
#line 14 "dhcp4_parser.yy" // stack.hh:132
} } // isc::dhcp
#line 155 "stack.hh" // stack.hh:131
#line 156 "stack.hh" // stack.hh:132
#endif // !YY_PARSER4_STACK_HH_INCLUDED
......@@ -25,6 +25,7 @@
#include <dhcpsrv/cfg_hosts.h>
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/testutils/config_result_check.h>
#include <process/config_ctl_info.h>
#include <hooks/hooks_manager.h>
#include "marker_file.h"
......@@ -153,7 +154,7 @@ const char* PARSER_CONFIGS[] = {
" ]"
"}",
// Last Configuration for comments
// Configuration 5 for comments
"{"
" \"comment\": \"A DHCPv4 server\","
" \"interfaces-config\": {"
......@@ -227,7 +228,31 @@ const char* PARSER_CONFIGS[] = {
" \"comment\": \"No dynamic DNS\","
" \"enable-updates\": false"
" }"
"}"
"}",
// Configuration 6: config databases
"{ \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\" ] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"rebind-timer\": 2000, \n"
" \"renew-timer\": 1000, \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"mysql\", \n"
" \"name\": \"keatest1\", \n"
" \"user\": \"keatest\", \n"
" \"password\": \"keatest\" \n"
" },{ \n"
" \"type\": \"mysql\", \n"
" \"name\": \"keatest2\", \n"
" \"user\": \"keatest\", \n"
" \"password\": \"keatest\" \n"
" } \n"
" ] \n"
" } \n"
"} \n"
};
class Dhcp4ParserTest : public ::testing::Test {
......@@ -6233,4 +6258,28 @@ TEST_F(Dhcp4ParserTest, globalReservations) {
EXPECT_FALSE(hosts_cfg->get4(542, Host::IDENT_DUID, &duid[0], duid.size()));
}
// This test verifies that configuration control info gets populated.
TEST_F(Dhcp4ParserTest, configControlInfo) {
string config = PARSER_CONFIGS[6];
extractConfig(config);
configure(config, CONTROL_RESULT_SUCCESS, "");
// Make sure the config control info is there.
process::ConstConfigControlInfoPtr info =
CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
ASSERT_TRUE(info);
// Fetch the list of config dbs. It should have two entries.
const process::ConfigDbInfoList& dblist = info->getConfigDatabases();
ASSERT_EQ(2, dblist.size());
// Make sure the entries are what we expect and in the right order.
// (DbAccessParser creates access strings with the keywords in
// alphabetical order).
EXPECT_EQ("name=keatest1 password=keatest type=mysql user=keatest",