diff --git a/doc/Makefile.am b/doc/Makefile.am
index c4bfc24887..9ede28d74d 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -4,7 +4,7 @@ EXTRA_DIST = version.ent.in Doxyfile Doxyfile-xml
EXTRA_DIST += devel/config-backend.dox
EXTRA_DIST += devel/contribute.dox
EXTRA_DIST += devel/mainpage.dox
-EXTRA_DIST += devel/qa.dox
+EXTRA_DIST += devel/unit-tests.dox
nobase_dist_doc_DATA = examples/ddns/sample1.json
nobase_dist_doc_DATA += examples/ddns/template.json
diff --git a/doc/devel/contribute.dox b/doc/devel/contribute.dox
index 31028decaa..3f43307887 100644
--- a/doc/devel/contribute.dox
+++ b/doc/devel/contribute.dox
@@ -94,7 +94,7 @@ written and observing the test fail, you can detect the situation
where a bug in the test code will cause it to pass regardless of
the code being tested.
-See @ref qaUnitTests for instructions on how to run unit-tests.
+See @ref unitTests for instructions on how to run unit-tests.
If you happen to add new files or have modified any \c Makefile.am
files, it is also a good idea to check if you haven't broken the
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index ebe1623d94..7b203dd464 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -34,6 +34,12 @@
* @section contrib Contributor's Guide
* - @subpage contributorGuide
*
+ * @section buildingKeaWithUnitTests Building Kea with Unit tests
+ * - @subpage unitTests
+ * - @subpage unitTestsIntroduction
+ * - @subpage unitTestsEnvironmentVariables
+ * - @subpage unitTestsDatabaseConfig
+ *
* @section hooksFramework Hooks Framework
* - @subpage hooksdgDevelopersGuide
* - @subpage dhcpv4Hooks
@@ -107,9 +113,6 @@
* - @subpage configBackendAdding
* - @subpage perfdhcpInternals
*
- * @section qualityAssurance Quality Assurance
- * - @subpage qaUnitTests
- *
* @section miscellaneousTopics Miscellaneous Topics
* - @subpage logKeaLogging
* - @subpage logBasicIdeas
diff --git a/doc/devel/qa.dox b/doc/devel/qa.dox
deleted file mode 100644
index 8ad8c88840..0000000000
--- a/doc/devel/qa.dox
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-/**
-
-@page qa Kea Quality Assurance processes
-
- @section qaUnitTests Unit-tests
-
-Kea uses the Google C++ Testing Framework (also called googletest or gtest) as a
-base for our C++ unit-tests. See http://code.google.com/p/googletest/ for
-details. We used to have Python unit-tests that were inherited from BIND10
-days. Those tests are removed now, so please do not develop any new Python
-tests in Kea. If you want to write DHCP tests in Python, we encourage you to
-take a look at ISC Forge: http://kea.isc.org/wiki/IscForge. You must have \c
-gtest installed or at least extracted in a directory before compiling Kea
-unit-tests. To enable unit-tests in Kea, use:
-
-@code
-./configure --with-gtest=/path/to/your/gtest/dir
-@endcode
-
-or
-
-@code
-./configure --with-gtest-source=/path/to/your/gtest/dir
-@endcode
-
-Depending on how you compiled or installed \c gtest (e.g. from sources
-or using some package management system) one of those two switches will
-find \c gtest. After that you make run unit-tests:
-
-@code
-make check
-
-@endcode
-
-The following environment variable can affect unit-tests:
-
-- KEA_LOCKFILE_DIR - Specifies a directory where the logging system should
- create its lock file. If not specified, it is prefix/var/run/kea, where prefix
- defaults to /usr/local. This variable must not end with a slash. There is one
- special value: "none", which instructs Kea to not create lock file at
- all. This may cause issues if several processes log to the same file.
- Also see Kea User's Guide, section 15.3.
-
-- KEA_LOGGER_DESTINATION - Specifies logging destination. If not set, logged
- messages will not be recorded anywhere. There are 3 special values:
- stdout, stderr and syslog. Any other value is interpreted as a filename.
- Also see Kea User's Guide, section 15.3.
-
-- KEA_PIDFILE_DIR - Specifies the directory which should be used for PID files
- as used by dhcp::Daemon or its derivatives. If not specified, the default is
- prefix/var/run/kea, where prefix defaults to /usr/local. This variable must
- not end with a slash.
-
-- KEA_SOCKET_TEST_DIR - if set, it specifies the directory where Unix
- sockets are created. There's OS limitation on how long a Unix socket
- path can be. It is typcially slightly over 100 characters. If you
- happen to build and run unit-tests in deeply nested directories, this
- may become a problem. KEA_SOCKET_TEST_DIR can be specified to instruct
- unit-test to use a different directory. Must not end with slash (e.g.
- /tmp).
-
- */
diff --git a/doc/devel/unit-tests.dox b/doc/devel/unit-tests.dox
new file mode 100644
index 0000000000..59722bf488
--- /dev/null
+++ b/doc/devel/unit-tests.dox
@@ -0,0 +1,264 @@
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+
+ @page unitTests Building Kea with Unit Tests
+
+@section unitTestsIntroduction Introduction
+
+Kea uses the Google C++ Testing Framework (also called googletest or gtest) as a
+base for our C++ unit-tests. See http://code.google.com/p/googletest/ for
+details. We used to have Python unit-tests inherited from BIND10
+days but have been removed, so please do not write any new Kea unit
+tests in Python. (If you want to write DHCP tests in Python, we encourage you to
+take a look at ISC Forge: http://kea.isc.org/wiki/IscForge.)
+
+You must have \c gtest installed or at least extracted in a directory
+before compiling Kea unit-tests. To enable unit-tests in Kea, use:
+
+@code
+./configure --with-gtest=/path/to/your/gtest/dir
+@endcode
+
+or
+
+@code
+./configure --with-gtest-source=/path/to/your/gtest/dir
+@endcode
+
+Depending on how you compiled or installed \c gtest (e.g. from sources
+or using some package management system) one of those two switches will
+find \c gtest. After that you make and run the unit-tests with:
+
+@code
+make check
+@endcode
+
+@section unitTestsEnvironmentVariables Environment Variables
+
+The following environment variable can affect the unit tests:
+
+- KEA_LOCKFILE_DIR - Specifies a directory where the logging system should
+ create its lock file. If not specified, it is prefix/var/run/kea,
+ where prefix defaults to /usr/local. This variable must not end
+ with a slash. There is one special value, "none", which instructs Kea to
+ not create a lock file at all. This may cause issues if several processes
+ log to the same file. (Also see the Kea User's Guide, section 15.3.)
+
+- KEA_LOGGER_DESTINATION - Specifies the logging destination. If not set, logged
+ messages will not be recorded anywhere. There are three special values:
+ stdout, stderr and syslog. Any other value is interpreted as a filename.
+ (Also see Kea User's Guide, section 15.3.)
+
+- KEA_PIDFILE_DIR - Specifies the directory which should be used for PID files
+ as used by dhcp::Daemon or its derivatives. If not specified, the
+ default is prefix/var/run/kea, where prefix defaults to
+ /usr/local. This variable must not end with a slash.
+
+- KEA_SOCKET_TEST_DIR - if set, it specifies the directory where Unix
+ sockets are created. There is an operating system limitation on how
+ long a Unix socket path can be, typically slightly over 100
+ characters. If you happen to build and run unit-tests in deeply nested
+ directories, this may become a problem. KEA_SOCKET_TEST_DIR can be
+ specified to instruct unit-test to use a different directory. It must
+ not end with slash.
+
+@section unitTestsDatabaseConfig Databases Configuration for Unit Tests
+
+ With the use of databases requiring separate authorisation, there are
+ certain database-specific pre-requisites for successfully running the unit
+ tests. These are listed in the following sections.
+
+ @subsection unitTestsDatabaseUsers Database Users Required for Unit Tests
+
+ Unit tests validating database backends require that the keatest
+ database is created. This database should be empty. The unit tests
+ also require that the keatest user is created and that this user
+ is configured to access the database with a password of keatest.
+ Unit tests use these credentials to create database schema, run test cases
+ and drop the schema. Thus, the keatest user must have sufficiently
+ high privileges to create and drop tables, as well as insert and modify the
+ data within those tables.
+
+ The database backends which support read only access to the host
+ reservations databases (currently MySQL and PostgreSQL) include unit
+ tests verifying that a database user with read-only privileges can be
+ used to retrieve host reservations. Those tests require another user,
+ keatest_readonly, with SQL SELECT privilege to the keatest
+ database (i.e. without INSERT, UPDATE etc.), is also created.
+ keatest_readonly should also have the password keatest.
+
+ The following sections provide step-by-step guidelines how to setup the
+ databases for running unit tests.
+
+ @subsection mysqlUnitTestsPrerequisites MySQL Database
+
+ The steps to create the database and users are:
+
+ -# Log into MySQL as root:
+ @verbatim
+ % mysql -u root -p
+ Enter password:
+ :
+ mysql>@endverbatim\n
+ -# Create the test database. This must be called "keatest":
+ @verbatim
+ mysql> CREATE DATABASE keatest;
+ mysql>@endverbatim\n
+ -# Create the users under which the test client will connect to the database
+ (the apostrophes around the words keatest, keatest_readonly, and
+ localhost are required):
+ @verbatim
+ mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest';
+ mysql> CREATE USER 'keatest_readonly'@'localhost' IDENTIFIED BY 'keatest';
+ mysql>@endverbatim\n
+ -# Grant the created users permissions to access the keatest database
+ (again, the apostrophes around the user names and localhost
+ are required):
+ @verbatim
+ mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost';
+ mysql> GRANT SELECT ON keatest.* TO 'keatest_readonly'@'localhost';
+ mysql>@endverbatim\n
+ -# Exit MySQL:
+ @verbatim
+ mysql> quit
+ Bye
+ %@endverbatim
+
+ The unit tests are run automatically when "make check" is executed (providing
+ that Kea has been build with the \c --with-dhcp-mysql switch (see the installation
+ section in the Kea Administrator
+ Reference Manual).
+
+ @subsection pgsqlUnitTestsPrerequisites PostgreSQL Database
+
+ PostgreSQL set up differs from system to system. Please consult your
+ operating system-specific PostgreSQL documentation. The remainder of
+ that section uses Ubuntu 13.10 x64 (with PostgreSQL 9.0+) as an example.
+
+ On Ubuntu, PostgreSQL is installed (with sudo apt-get install
+ postgresql) under user postgres. To create new databases
+ or add new users, initial commands must be issued under this username:
+
+@verbatim
+$ sudo -u postgres psql postgres
+[sudo] password for thomson:
+psql (9.1.12)
+Type "help" for help.
+postgres=# CREATE USER keatest WITH PASSWORD 'keatest';
+CREATE ROLE
+postgres=# CREATE DATABASE keatest;
+CREATE DATABASE
+postgres=# GRANT ALL PRIVILEGES ON DATABASE keatest TO keatest;
+GRANT
+postgres=# \q
+@endverbatim
+
+ PostgreSQL versions earlier than 9.0 don't provide an SQL statement for granting
+ privileges on all tables in a database. In newer PostgreSQL versions, it is
+ possible to grant specific privileges on all tables within a schema.
+ However, this only affects tables which exist when the privileges are granted.
+ To ensure that the user has specific privileges to tables dynamically created
+ by the unit tests, the default schema privileges must be altered.
+
+ The following example demonstrates how to create the user keatest_readonly,
+ which has SELECT privilege to the tables within the keatest database,
+ in Postgres 9.0+. For earlier versions of Postgres, it is recommended to
+ simply grant full privileges to keatest_readonly, using the
+ same steps as for the keatest user.
+
+@verbatim
+$ psql -U postgres
+Password for user postgres:
+psql (9.1.12)
+Type "help" for help.
+
+postgres=# CREATE USER keatest_readonly WITH PASSWORD 'keatest';
+CREATE ROLE
+postgres=# \q
+
+$ psql -U keatest
+Password for user keatest:
+psql (9.1.12)
+Type "help" for help.
+
+keatest=> ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES to keatest_readonly;
+ALTER DEFAULT PRIVILEGES
+keatest=> \q
+@endverbatim
+
+ Note that the keatest user (rather than postgres) is used to grant
+ privileges to the keatest_readonly user. This ensures that the SELECT
+ privilege is granted only on the tables that the keatest user can access
+ within the public schema.
+
+ Now we should be able to log into the newly created database using both user
+ names:
+@verbatim
+$ psql -d keatest -U keatest
+Password for user keatest:
+psql (9.1.12)
+Type "help" for help.
+
+keatest=> \q
+
+$ psql -d keatest -U keatest_readonly
+Password for user keatest_readonly:
+psql (9.1.12)
+Type "help" for help.
+
+keatest=>
+@endverbatim
+
+ If instead of seeing keatest=> prompt, your login is refused with an error
+ code about failed peer or indent authentication, it means that PostgreSQL is
+ configured to check unix username and reject login attempts if PostgreSQL names
+ are different. To alter that, the PostgreSQL configuration must be changed -
+ the /etc/postgresql/9.1/main/pg_hba.conf config file
+ has to be altered. (It may be in a different location in your system.) The following
+ lines:
+
+@verbatim
+local all all peer
+host all all 127.0.0.1/32 md5
+host all all ::1/128 md5
+@endverbatim
+
+need to be replaced with:
+
+@verbatim
+local all all password
+host all all 127.0.0.1/32 password
+host all all ::1/128 password
+@endverbatim
+
+ Another possible problem is that you get no password prompt. This is
+ most probably because you have no pg_hba.conf config file
+ and everybody is by default trusted. As it has a very bad effect
+ on the security you should have been warned this is a highly unsafe
+ configuration. The solution is the same, i.e., require password or
+ md5 authentication method.
+
+ If you lose the postgres user access you can first add:
+@verbatim
+local all postgres trust
+@endverbatim
+ to trust only the local postgres user. Note the postgres user can
+ be pgsql on some systems.
+
+ Please consult your PostgreSQL user manual before applying those changes as
+ those changes may expose your other databases that you run on the same system.
+ In general case, it is a poor idea to run anything of value on a system
+ that runs tests. Use caution!
+
+ The unit tests are run automatically when "make check" is executed (providing
+ that Kea has been build with the \c --with-dhcp-pgsql switch (see the installation
+ section in the Kea Administrator
+ Reference Manual).
+
+
+ */
diff --git a/doc/guide/admin.xml b/doc/guide/admin.xml
index f7e70cafb8..c086917dbf 100644
--- a/doc/guide/admin.xml
+++ b/doc/guide/admin.xml
@@ -11,11 +11,13 @@
Databases and Database Version Numbers
- Kea stores leases in one of several supported databases.
- As future versions of Kea are released, the structure of those
- databases will change. For example, Kea currently only stores
- lease information: in the future, additional data - such as host
- reservation details - will also be stored.
+ Kea supports storing leases and host reservations (i.e. static
+ assignments of addresses, prefixes and options) in one of
+ the several supported databases. As future versions of Kea
+ are released, the structure of those databases will change.
+ For example, Kea currently only stores lease information
+ and host reservations: in the future, additional
+ data - such as subnet definitions - will also be stored.
@@ -710,6 +712,18 @@ $ kea-admin lease-upgrade cql -n database-name
+
+ Using read only databases with host reservations
+ If read only database is used for storing host reservations,
+ Kea must be explicitly configured to operate on the database in
+ the read only mode.
+ Sections and
+ describe when
+ such configuration may be reqired and how to configure Kea to
+ operate using read only host database.
+
+
+
Limitations related to the use of the SQL databases
@@ -726,6 +740,7 @@ $ kea-admin lease-upgrade cql -n database-name
+
diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml
index e3bbab5b91..d995f53e90 100644
--- a/doc/guide/dhcp4-srv.xml
+++ b/doc/guide/dhcp4-srv.xml
@@ -537,6 +537,38 @@ If a timeout is given though, it should be an integer greater than zero.
If there is no password to the account, set the password to the empty string
"". (This is also the default.)
+
+
+Using Read-Only Databases for Host Reservations
+
+In some deployments the database user whose name is specified in the database backend
+configuration may not have write privileges to the database. This is often
+required by the policy within a given network to secure the data from being
+unintentionally modified. In many cases administrators have inventory databases
+deployed, which contain substantially more information about the hosts than
+static reservations assigned to them. The inventory database can be used to create
+a view of a Kea hosts database and such view is often read only.
+
+
+Kea host database backends operate with an implicit configuration to both
+read from and write to the database. If the database user does not have
+write access to the host database, the backend will fail to start and the
+server will refuse to start (or reconfigure). However, if access to a read
+only host database is required for retrieving reservations for clients
+and/or assign specific addresses and options, it is possible to explicitly
+configure Kea to start in "read-only" mode. This is controlled by the
+readonly boolean parameter as follows:
+
+"Dhcp4": { "hosts-database": { "readonly": true, ... }, ... }
+
+Setting this parameter to false would configure the
+database backend to operate in "read-write" mode, which is also a default
+configuration if the parameter is not specified.
+
+The readonly parameter is currently only supported
+for MySQL and PostgreSQL databases.
+
+
diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml
index d7c85e4e15..0e6b319f42 100644
--- a/doc/guide/dhcp6-srv.xml
+++ b/doc/guide/dhcp6-srv.xml
@@ -477,6 +477,7 @@ If a timeout is given though, it should be an integer greater than zero.
If there is no password to the account, set the password to the empty string
"". (This is also the default.)
+
@@ -537,8 +538,40 @@ If a timeout is given though, it should be an integer greater than zero.
If there is no password to the account, set the password to the empty string
"". (This is also the default.)
+
+
+Using Read-Only Databases for Host Reservations
+
+In some deployments the database user whose name is specified in the database backend
+configuration may not have write privileges to the database. This is often
+required by the policy within a given network to secure the data from being
+unintentionally modified. In many cases administrators have inventory databases
+deployed, which contain substantially more information about the hosts than
+static reservations assigned to them. The inventory database can be used to create
+a view of a Kea hosts database and such view is often read only.
+
+
+Kea host database backends operate with an implicit configuration to both
+read from and write to the database. If the database user does not have
+write access to the host database, the backend will fail to start and the
+server will refuse to start (or reconfigure). However, if access to a read
+only host database is required for retrieving reservations for clients
+and/or assign specific addresses and options, it is possible to explicitly
+configure Kea to start in "read-only" mode. This is controlled by the
+readonly boolean parameter as follows:
+
+"Dhcp6": { "hosts-database": { "readonly": true, ... }, ... }
+
+Setting this parameter to false would configure the
+database backend to operate in "read-write" mode, which is also a default
+configuration if the parameter is not specified.
+
+The readonly parameter is currently only supported
+for MySQL and PostgreSQL databases.
--->
+
+
+
Interface selection
diff --git a/doc/guide/install.xml b/doc/guide/install.xml
index 41793080c8..0f4ad19e9a 100644
--- a/doc/guide/install.xml
+++ b/doc/guide/install.xml
@@ -459,6 +459,17 @@ Debian and Ubuntu:
Kea is built. This section covers the building of Kea with MySQL and/or PostgreSQL
and the creation of the lease database.
+
+
+
+ When unit tests are built with Kea (--with-gtest configuration option is specified),
+ the databases must be manually pre-configured for the unit tests to run.
+ The details of this configuration can be found in the
+ Kea Developer's
+ Guide.
+
+
+
Building with MySQL Support
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 8e24e6992e..43940ef68b 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -275,6 +275,12 @@
"item_type": "integer",
"item_optional": true,
"item_default": 0
+ },
+ {
+ "item_name": "readonly",
+ "item_type": "boolean",
+ "item_optional": true,
+ "item_default": false
}
]
},
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 3571b8e2ac..d824290647 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -302,6 +302,12 @@
"item_type": "integer",
"item_optional": true,
"item_default": 0
+ },
+ {
+ "item_name": "readonly",
+ "item_type": "boolean",
+ "item_optional": true,
+ "item_default": false
}
]
},
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index 104318ece2..682bc5dea0 100644
--- a/src/lib/dhcpsrv/database_backends.dox
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -83,137 +83,5 @@
- user - database user ID under which the database is accessed. If not
specified, no user ID is used - the database is assumed to be open.
- @section dhcp-backend-unittest Running Unit Tests
-
- With the use of databases requiring separate authorisation, there are
- certain database-specific pre-requisites for successfully running the unit
- tests. These are listed in the following sections.
-
- @subsection dhcp-mysql-unittest MySQL Unit Tests
-
- A database called keatest must be created. A database user, also called
- keatest (and with a password keatest) must also be created and
- be given full privileges in that database. The unit tests create the schema
- in the database before each test and delete it afterwards.
-
- In detail, the steps to create the database and user are:
-
- -# Log into MySQL as root:
- @verbatim
- % mysql -u root -p
- Enter password:
- :
- mysql>@endverbatim\n
- -# Create the test database. This must be called "keatest":
- @verbatim
- mysql> CREATE DATABASE keatest;
- mysql>@endverbatim\n
- -# Create the user under which the test client will connect to the database
- (the apostrophes around the words keatest and localhost are
- required):
- @verbatim
- mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest';
- mysql>@endverbatim\n
- -# Grant the created user permissions to access the keatest database
- (again, the apostrophes around the words keatest and localhost
- are required):
- @verbatim
- mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost';
- mysql>@endverbatim\n
- -# Exit MySQL:
- @verbatim
- mysql> quit
- Bye
- %@endverbatim
-
- The unit tests are run automatically when "make check" is executed (providing
- that Kea has been build with the \--with-dhcp-mysql switch (see the installation
- section in the Kea Administrator
- Reference Manual).
-
- @subsection dhcp-pgsql-unittest PostgreSQL Unit Tests
-
- Conceptually, the steps required to run PostgreSQL unit-tests are the same as
- in MySQL. First, a database called keatest must be created. A database
- user, also called keatest (that will be allowed to log in using password
- keatest) must be created and given full privileges in that database. The
- unit tests create the schema in the database before each test and delete it
- afterwards.
-
- PostgreSQL set up differs from system to system. Please consult your OS-specific
- PostgreSQL documentation. The remainder of that section uses Ubuntu 13.10 x64 as
- example. On Ubuntu, after installing PostgreSQL (with sudo apt-get install
- postgresql), it is installed as user postgres. To create new databases
- or add new users, initial commands must be issued as user postgres:
-
-@verbatim
-$ sudo -u postgres psql postgres
-[sudo] password for thomson:
-psql (9.1.12)
-Type "help" for help.
-postgres=# CREATE USER keatest WITH PASSWORD 'keatest';
-CREATE ROLE
-postgres=# CREATE DATABASE keatest;
-CREATE DATABASE
-postgres=# GRANT ALL PRIVILEGES ON DATABASE keatest TO keatest;
-GRANT
-postgres=# \q
-@endverbatim
-
- Now we are back to our regular, unprivileged user. Try to log into the newly
- created database using keatest credentials:
-@verbatim
-$ psql -d keatest -U keatest
-Password for user keatest:
-psql (9.1.12)
-Type "help" for help.
-
-keatest=>
-@endverbatim
-
- If instead of seeing keatest=> prompt, your login will be refused with error
- code about failed peer or indent authentication, it means that PostgreSQL is
- configured to check unix username and reject login attepts if PostgreSQL names
- are different. To alter that, PostgreSQL configuration must be changed.
- Alternatively, you may set up your environment, so the tests would be run from
- unix account keatest. /etc/postgresql/9.1/main/pg_hba.conf config file
- had to betweaked. It may be in a different location in your system. The following
- lines:
-
-@verbatim
-local all all peer
-host all all 127.0.0.1/32 md5
-host all all ::1/128 md5
-@endverbatim
-
- were replaced with:
-
-@verbatim
-local all all password
-host all all 127.0.0.1/32 password
-host all all ::1/128 password
-@endverbatim
-
- Another possible problem is to get no password prompt, in general because
- you have no pg_hba.conf config file and everybody is by default
- trusted. As it has a very bad effect on the security you should have
- been warned it is a highly unsafe config. The solution is the same,
- i.e., require password or md5 authentication method. If you lose
- the postgres user access you can add first:
-@verbatim
-local all postgres trust
-@endverbatim
- to trust only the local postgres user. Note the postgres user can
- be pgsql on some systems.
-
- Please consult your PostgreSQL user manual before applying those changes as
- those changes may expose your other databases that you run on the same system.
- In general case, it is a poor idea to run anything of value on a system
- that runs tests. Use caution!
-
- The unit tests are run automatically when "make check" is executed (providing
- that Kea has been build with the \--with-dhcp-pgsql switch (see the installation
- section in the Kea Administrator
- Reference Manual).
*/
diff --git a/src/lib/dhcpsrv/database_connection.cc b/src/lib/dhcpsrv/database_connection.cc
index 983a752d96..0aa886fa88 100644
--- a/src/lib/dhcpsrv/database_connection.cc
+++ b/src/lib/dhcpsrv/database_connection.cc
@@ -1,10 +1,11 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include
+#include
#include
#include
@@ -86,5 +87,24 @@ DatabaseConnection::redactedAccessString(const ParameterMap& parameters) {
return (access);
}
+bool
+DatabaseConnection::configuredReadOnly() const {
+ std::string readonly_value = "false";
+ try {
+ readonly_value = getParameter("readonly");
+ boost::algorithm::to_lower(readonly_value);
+ } catch (...) {
+ // Parameter "readonly" hasn't been specified so we simply use
+ // the default value of "false".
+ }
+
+ if ((readonly_value != "false") && (readonly_value != "true")) {
+ isc_throw(DbInvalidReadOnly, "invalid value '" << readonly_value
+ << "' specified for boolean parameter 'readonly'");
+ }
+
+ return (readonly_value == "true");
+}
+
};
};
diff --git a/src/lib/dhcpsrv/database_connection.h b/src/lib/dhcpsrv/database_connection.h
index 2be98baf41..e6a902babf 100644
--- a/src/lib/dhcpsrv/database_connection.h
+++ b/src/lib/dhcpsrv/database_connection.h
@@ -54,6 +54,15 @@ public:
isc::Exception(file, line, what) {}
};
+/// @brief Invalid 'readonly' value specification.
+///
+/// Thrown when the value of the 'readonly' boolean parameter is invalid.
+class DbInvalidReadOnly : public Exception {
+public:
+ DbInvalidReadOnly(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// @brief Common database connection class.
///
@@ -112,6 +121,14 @@ public:
/// @return Redacted database access string.
static std::string redactedAccessString(const ParameterMap& parameters);
+ /// @brief Convenience method checking if database should be opened with
+ /// read only access.
+ ///
+ /// @return true if "readonly" parameter is specified and set to true;
+ /// false if "readonly" parameter is not specified or it is specified
+ /// and set to false.
+ bool configuredReadOnly() const;
+
private:
/// @brief List of parameters passed in dbconfig
diff --git a/src/lib/dhcpsrv/db_exceptions.h b/src/lib/dhcpsrv/db_exceptions.h
index 664c43e144..d9cc4449dd 100644
--- a/src/lib/dhcpsrv/db_exceptions.h
+++ b/src/lib/dhcpsrv/db_exceptions.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,6 +40,13 @@ public:
isc::Exception(file, line, what) {}
};
+/// @brief Attempt to modify data in read-only database.
+class ReadOnlyDb : public Exception {
+public:
+ ReadOnlyDb(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
};
};
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 714148548c..e9ff369859 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -551,6 +551,12 @@ connection including database name and username needed to access it
A debug message issued when the server is about to obtain schema version
information from the MySQL hosts database.
+% DHCPSRV_MYSQL_HOST_DB_READONLY MySQL host database opened for read access only
+This informational message is issued when the user has configured the MySQL
+database in read-only mode. Kea will not be able to insert or modify
+host reservations but will be able to retrieve existing ones and
+assign them to the clients communicating with the server.
+
% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
The code has issued a rollback call. All outstanding transaction will
be rolled back and not committed to the database.
@@ -696,6 +702,12 @@ V6) is about to open a PostgreSQL hosts database. The parameters of the
connection including database name and username needed to access it
(but not the password if any) are logged.
+% DHCPSRV_PGSQL_HOST_DB_READONLY PostgreSQL host database opened for read access only
+This informational message is issued when the user has configured the PostgreSQL
+database in read-only mode. Kea will not be able to insert or modify
+host reservations but will be able to retrieve existing ones and
+assign them to the clients communicating with the server.
+
% DHCPSRV_PGSQL_ROLLBACK rolling back PostgreSQL database
The code has issued a rollback call. All outstanding transaction will
be rolled back and not committed to the database.
diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc
index 30de103d53..06162bdf1e 100644
--- a/src/lib/dhcpsrv/mysql_connection.cc
+++ b/src/lib/dhcpsrv/mysql_connection.cc
@@ -12,7 +12,6 @@
#include
#include
-#include
#include
#include
#include
@@ -213,22 +212,26 @@ MySqlConnection::prepareStatement(uint32_t index, const char* text) {
}
void
-MySqlConnection::prepareStatements(const TaggedStatement tagged_statements[],
- size_t num_statements) {
- // Allocate space for all statements
- statements_.clear();
- statements_.resize(num_statements, NULL);
-
- text_statements_.clear();
- text_statements_.resize(num_statements, std::string(""));
-
+MySqlConnection::prepareStatements(const TaggedStatement* start_statement,
+ const TaggedStatement* end_statement) {
// Created the MySQL prepared statements for each DML statement.
- for (int i = 0; tagged_statements[i].text != NULL; ++i) {
- prepareStatement(tagged_statements[i].index,
- tagged_statements[i].text);
+ for (const TaggedStatement* tagged_statement = start_statement;
+ tagged_statement != end_statement; ++tagged_statement) {
+ if (tagged_statement->index >= statements_.size()) {
+ statements_.resize(tagged_statement->index + 1, NULL);
+ text_statements_.resize(tagged_statement->index + 1,
+ std::string(""));
+ }
+ prepareStatement(tagged_statement->index,
+ tagged_statement->text);
}
}
+void MySqlConnection::clearStatements() {
+ statements_.clear();
+ text_statements_.clear();
+}
+
/// @brief Destructor
MySqlConnection::~MySqlConnection() {
// Free up the prepared statements, ignoring errors. (What would we do
diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h
index 8acb894577..4f84731bcd 100644
--- a/src/lib/dhcpsrv/mysql_connection.h
+++ b/src/lib/dhcpsrv/mysql_connection.h
@@ -240,15 +240,21 @@ public:
///
/// Creates the prepared statements for all of the SQL statements used
/// by the MySQL backend.
- /// @param tagged_statements an array of statements to be compiled
- /// @param num_statements number of statements in tagged_statements
+ ///
+ /// @param start_statement Pointer to the first statement in range of the
+ /// statements to be compiled.
+ /// @param end_statement Pointer to the statement marking end of the
+ /// range of statements to be compiled. This last statement is not compiled.
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
/// @throw isc::InvalidParameter 'index' is not valid for the vector. This
/// represents an internal error within the code.
- void prepareStatements(const TaggedStatement tagged_statements[],
- size_t num_statements);
+ void prepareStatements(const TaggedStatement* start_statement,
+ const TaggedStatement* end_statement);
+
+ /// @brief Clears prepared statements and text statements.
+ void clearStatements();
/// @brief Open Database
///
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc
index 72c7332b2e..622b0641bb 100644
--- a/src/lib/dhcpsrv/mysql_host_data_source.cc
+++ b/src/lib/dhcpsrv/mysql_host_data_source.cc
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -19,6 +20,7 @@
#include
#include
+#include
#include
#include
@@ -846,7 +848,7 @@ private:
size_t start_column_;
/// @brief Option id.
- uint64_t option_id_;
+ uint32_t option_id_;
/// @brief Option code.
uint16_t code_;
@@ -916,7 +918,7 @@ private:
//@}
/// @brief Option id for last processed row.
- uint64_t most_recent_option_id_;
+ uint32_t most_recent_option_id_;
};
/// @brief Pointer to the @ref OptionProcessor class.
@@ -1113,7 +1115,7 @@ public:
/// @brief Returns last fetched reservation id.
///
/// @return Reservation id or 0 if no reservation data is fetched.
- uint64_t getReservationId() const {
+ uint32_t getReservationId() const {
if (reserv_type_null_ == MLM_FALSE) {
return (reservation_id_);
}
@@ -1251,7 +1253,7 @@ public:
private:
/// @brief IPv6 reservation id.
- uint64_t reservation_id_;
+ uint32_t reservation_id_;
/// @brief IPv6 reservation type.
uint8_t reserv_type_;
@@ -1272,7 +1274,7 @@ private:
uint8_t prefix_len_;
/// @brief IAID.
- uint8_t iaid_;
+ uint32_t iaid_;
/// @name Indexes of columns holding information about IPv6 reservations.
//@{
@@ -1294,7 +1296,7 @@ private:
//@}
/// @brief Reservation id for last processed row.
- uint64_t most_recent_reservation_id_;
+ uint32_t most_recent_reservation_id_;
};
@@ -1585,7 +1587,7 @@ private:
std::vector value_;
/// @brief Option value length.
- size_t value_len_;
+ unsigned long value_len_;
/// @brief Formatted option value length.
unsigned long formatted_value_len_;
@@ -1594,7 +1596,7 @@ private:
std::string space_;
/// @brief Option space name length.
- size_t space_len_;
+ unsigned long space_len_;
/// @brief Boolean flag indicating if the option is always returned to
/// a client or only when requested.
@@ -1604,7 +1606,7 @@ private:
std::string client_class_;
/// @brief Length of the string holding client classes for the option.
- size_t client_class_len_;
+ unsigned long client_class_len_;
/// @brief Subnet identifier.
uint32_t subnet_id_;
@@ -1630,12 +1632,11 @@ public:
/// @brief Statement Tags
///
- /// The contents of the enum are indexes into the list of SQL statements
+ /// The contents of the enum are indexes into the list of SQL statements.
+ /// It is assumed that the order is such that the indicies of statements
+ /// reading the database are less than those of statements modifying the
+ /// database.
enum StatementIndex {
- INSERT_HOST, // Insert new host to collection
- INSERT_V6_RESRV, // Insert v6 reservation
- INSERT_V4_OPTION, // Insert DHCPv4 option
- INSERT_V6_OPTION, // Insert DHCPv6 option
GET_HOST_DHCPID, // Gets hosts by host identifier
GET_HOST_ADDR, // Gets hosts by IPv4 address
GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
@@ -1643,9 +1644,20 @@ public:
GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
GET_HOST_PREFIX, // Gets host by IPv6 prefix
GET_VERSION, // Obtain version number
+ INSERT_HOST, // Insert new host to collection
+ INSERT_V6_RESRV, // Insert v6 reservation
+ INSERT_V4_OPTION, // Insert DHCPv4 option
+ INSERT_V6_OPTION, // Insert DHCPv6 option
NUM_STATEMENTS // Number of statements
};
+ /// @brief Index of first statement performing write to the database.
+ ///
+ /// This value is used to mark border line between queries and other
+ /// statements and statements performing write operation on the database,
+ /// such as INSERT, DELETE, UPDATE.
+ static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
+
/// @brief Constructor.
///
/// This constructor opens database connection and initializes prepared
@@ -1751,6 +1763,15 @@ public:
StatementIndex stindex,
boost::shared_ptr exchange) const;
+ /// @brief Throws exception if database is read only.
+ ///
+ /// This method should be called by the methods which write to the
+ /// database. If the backend is operating in read-only mode this
+ /// method will throw exception.
+ ///
+ /// @throw DbReadOnly if backend is operating in read only mode.
+ void checkReadOnly() const;
+
/// @brief Pointer to the object representing an exchange which
/// can be used to retrieve hosts and DHCPv4 options.
boost::shared_ptr host_exchange_;
@@ -1776,39 +1797,18 @@ public:
/// @brief MySQL connection
MySqlConnection conn_;
+ /// @brief Indicates if the database is opened in read only mode.
+ bool is_readonly_;
};
-namespace {
+
+/// @brief Array of tagged statements.
+typedef boost::array
+TaggedStatementArray;
+
/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve hosts from the database.
-TaggedStatement tagged_statements[] = {
- // Inserts a host into the 'hosts' table.
- {MySqlHostDataSourceImpl::INSERT_HOST,
- "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
- "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
- "dhcp4_client_classes, dhcp6_client_classes) "
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
-
- // Inserts a single IPv6 reservation into 'reservations' table.
- {MySqlHostDataSourceImpl::INSERT_V6_RESRV,
- "INSERT INTO ipv6_reservations(address, prefix_len, type, "
- "dhcp6_iaid, host_id) "
- "VALUES (?,?,?,?,?)"},
-
- // Inserts a single DHCPv4 option into 'dhcp4_options' table.
- // Using fixed scope_id = 3, which associates an option with host.
- {MySqlHostDataSourceImpl::INSERT_V4_OPTION,
- "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
- "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) "
- " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
-
- // Inserts a single DHCPv6 option into 'dhcp6_options' table.
- // Using fixed scope_id = 3, which associates an option with host.
- {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
- "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
- "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
- " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
-
+TaggedStatementArray tagged_statements = { {
// Retrieves host information, IPv6 reservations and both DHCPv4 and
// DHCPv6 options associated with the host. The LEFT JOIN clause is used
// to retrieve information from 4 different tables using a single query.
@@ -1930,11 +1930,33 @@ TaggedStatement tagged_statements[] = {
{MySqlHostDataSourceImpl::GET_VERSION,
"SELECT version, minor FROM schema_version"},
- // Marks the end of the statements table.
- {MySqlHostDataSourceImpl::NUM_STATEMENTS, NULL}
-};
+ // Inserts a host into the 'hosts' table.
+ {MySqlHostDataSourceImpl::INSERT_HOST,
+ "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
-}; // end anonymouse namespace
+ // Inserts a single IPv6 reservation into 'reservations' table.
+ {MySqlHostDataSourceImpl::INSERT_V6_RESRV,
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ "dhcp6_iaid, host_id) "
+ "VALUES (?,?,?,?,?)"},
+
+ // Inserts a single DHCPv4 option into 'dhcp4_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {MySqlHostDataSourceImpl::INSERT_V4_OPTION,
+ "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
+ "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) "
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+ // Inserts a single DHCPv6 option into 'dhcp6_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
+ "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
+ "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}}
+};
MySqlHostDataSourceImpl::
MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
@@ -1944,23 +1966,43 @@ MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
DHCP4_AND_DHCP6)),
host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()),
host_option_exchange_(new MySqlOptionExchange()),
- conn_(parameters) {
+ conn_(parameters),
+ is_readonly_(false) {
// Open the database.
conn_.openDatabase();
- // Disable autocommit. To avoid a flush to disk on every commit, the global
- // parameter innodb_flush_log_at_trx_commit should be set to 2. This will
- // cause the changes to be written to the log, but flushed to disk in the
- // background every second. Setting the parameter to that value will speed
- // up the system, but at the risk of losing data if the system crashes.
- my_bool result = mysql_autocommit(conn_.mysql_, 0);
+ // Enable autocommit. In case transaction is explicitly used, this
+ // setting will be overwritten for the transaction. However, there are
+ // cases when lack of autocommit could cause transactions to hang
+ // until commit or rollback is explicitly called. This already
+ // caused issues for some unit tests which were unable to cleanup
+ // the database after the test because of pending transactions.
+ // Use of autocommit will eliminate this problem.
+ my_bool result = mysql_autocommit(conn_.mysql_, 1);
if (result != 0) {
isc_throw(DbOperationError, mysql_error(conn_.mysql_));
}
- // Prepare all statements likely to be used.
- conn_.prepareStatements(tagged_statements, NUM_STATEMENTS);
+ // Prepare query statements. Those are will be only used to retrieve
+ // information from the database, so they can be used even if the
+ // database is read only for the current user.
+ conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.begin() + WRITE_STMTS_BEGIN);
+
+ // Check if the backend is explicitly configured to operate with
+ // read only access to the database.
+ is_readonly_ = conn_.configuredReadOnly();
+
+ // If we are using read-write mode for the database we also prepare
+ // statements for INSERTS etc.
+ if (!is_readonly_) {
+ // Prepare statements for writing to the database, e.g. INSERT.
+ conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
+ tagged_statements.end());
+ } else {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_READONLY);
+ }
}
MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
@@ -2159,6 +2201,14 @@ getHost(const SubnetID& subnet_id,
return (result);
}
+void
+MySqlHostDataSourceImpl::checkReadOnly() const {
+ if (is_readonly_) {
+ isc_throw(ReadOnlyDb, "MySQL host database backend is configured to"
+ " operate in read only mode");
+ }
+}
+
MySqlHostDataSource::
MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters)
@@ -2171,6 +2221,9 @@ MySqlHostDataSource::~MySqlHostDataSource() {
void
MySqlHostDataSource::add(const HostPtr& host) {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+
// Initiate MySQL transaction as we will have to make multiple queries
// to insert host information into multiple tables. If that fails on
// any stage, the transaction will be rolled back by the destructor of
@@ -2493,12 +2546,16 @@ std::pair MySqlHostDataSource::getVersion() const {
void
MySqlHostDataSource::commit() {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
impl_->conn_.commit();
}
void
MySqlHostDataSource::rollback() {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
impl_->conn_.rollback();
}
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h
index 9ce64f2256..d5e56949a0 100644
--- a/src/lib/dhcpsrv/mysql_host_data_source.h
+++ b/src/lib/dhcpsrv/mysql_host_data_source.h
@@ -8,6 +8,7 @@
#define MYSQL_HOST_DATA_SOURCE_H
#include
+#include
#include
namespace isc {
@@ -255,7 +256,7 @@ public:
private:
/// @brief Pointer to the implementation of the @ref MySqlHostDataSource.
- MySqlHostDataSourceImpl* impl_;
+ MySqlHostDataSourceImpl* impl_;
};
}
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 410880af36..8edf4dfc5c 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,6 +13,7 @@
#include
#include
+#include
#include
#include
@@ -82,7 +83,8 @@ const size_t HOSTNAME_MAX_LEN = 255;
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;
-TaggedStatement tagged_statements[] = {
+boost::array
+tagged_statements = { {
{MySqlLeaseMgr::DELETE_LEASE4,
"DELETE FROM lease4 WHERE address = ?"},
{MySqlLeaseMgr::DELETE_LEASE4_STATE_EXPIRED,
@@ -204,9 +206,8 @@ TaggedStatement tagged_statements[] = {
"prefix_len = ?, fqdn_fwd = ?, fqdn_rev = ?, "
"hostname = ?, hwaddr = ?, hwtype = ?, hwaddr_source = ?, "
"state = ? "
- "WHERE address = ?"},
- // End of list sentinel
- {MySqlLeaseMgr::NUM_STATEMENTS, NULL}
+ "WHERE address = ?"}
+ }
};
};
@@ -1236,7 +1237,7 @@ MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters)
}
// Prepare all statements likely to be used.
- conn_.prepareStatements(tagged_statements, MySqlLeaseMgr::NUM_STATEMENTS);
+ conn_.prepareStatements(tagged_statements.begin(), tagged_statements.end());
// Create the exchange objects for use in exchanging data between the
// program and the database.
diff --git a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc
index 5386e9d2d6..2de5485175 100644
--- a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc
+++ b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc
@@ -53,7 +53,7 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
// 2. Update the copy with the passed keywords.
BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
try {
- if (param.first == "persist") {
+ if ((param.first == "persist") || (param.first == "readonly")) {
values_copy[param.first] = (param.second->boolValue() ?
"true" : "false");
@@ -72,7 +72,8 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
}
} catch (const isc::data::TypeError& ex) {
// Append position of the element.
- isc_throw(isc::data::TypeError, ex.what() << " ("
+ isc_throw(BadValue, "invalid value type specified for "
+ "parameter '" << param.first << "' ("
<< param.second->getPosition() << ")");
}
}
diff --git a/src/lib/dhcpsrv/pgsql_connection.cc b/src/lib/dhcpsrv/pgsql_connection.cc
index 9d722a458a..e79712c2fa 100644
--- a/src/lib/dhcpsrv/pgsql_connection.cc
+++ b/src/lib/dhcpsrv/pgsql_connection.cc
@@ -129,6 +129,16 @@ PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
}
}
+void
+PgSqlConnection::prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement) {
+ // Created the PostgreSQL prepared statements.
+ for (const PgSqlTaggedStatement* tagged_statement = start_statement;
+ tagged_statement != end_statement; ++tagged_statement) {
+ prepareStatement(*tagged_statement);
+ }
+}
+
void
PgSqlConnection::openDatabase() {
string dbconnparameters;
diff --git a/src/lib/dhcpsrv/pgsql_connection.h b/src/lib/dhcpsrv/pgsql_connection.h
index 92bcd4b5ed..b0d793b611 100644
--- a/src/lib/dhcpsrv/pgsql_connection.h
+++ b/src/lib/dhcpsrv/pgsql_connection.h
@@ -313,6 +313,21 @@ public:
/// failed.
void prepareStatement(const PgSqlTaggedStatement& statement);
+ /// @brief Prepare statements
+ ///
+ /// Creates the prepared statements for all of the SQL statements used
+ /// by the PostgreSQL backend.
+ ///
+ /// @param start_statement Pointer to the first statement in range of the
+ /// statements to be compiled.
+ /// @param end_statement Pointer to the statement marking end of the
+ /// range of statements to be compiled. This last statement is not compiled.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement);
+
/// @brief Open Database
///
/// Opens the database using the information supplied in the parameters
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc
index 33a75ed262..2ab5280923 100644
--- a/src/lib/dhcpsrv/pgsql_host_data_source.cc
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -19,6 +20,7 @@
#include
#include
+#include
#include
#include
@@ -35,7 +37,7 @@ namespace {
/// @brief Maximum length of option value.
/// The maximum size of the raw option data that may be read from the
-/// database.
+/// database.
const size_t OPTION_VALUE_MAX_LEN = 4096;
/// @brief Numeric value representing last supported identifier.
@@ -1103,12 +1105,11 @@ public:
/// @brief Statement Tags
///
- /// The contents of the enum are indexes into the list of SQL statements
+ /// The contents of the enum are indexes into the list of SQL statements.
+ /// It is assumed that the order is such that the indicies of statements
+ /// reading the database are less than those of statements modifying the
+ /// database.
enum StatementIndex {
- INSERT_HOST, // Insert new host to collection
- INSERT_V6_RESRV, // Insert v6 reservation
- INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
- INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
GET_HOST_DHCPID, // Gets hosts by host identifier
GET_HOST_ADDR, // Gets hosts by IPv4 address
GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
@@ -1116,9 +1117,20 @@ public:
GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
GET_HOST_PREFIX, // Gets host by IPv6 prefix
GET_VERSION, // Obtain version number
+ INSERT_HOST, // Insert new host to collection
+ INSERT_V6_RESRV, // Insert v6 reservation
+ INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
+ INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
NUM_STATEMENTS // Number of statements
};
+ /// @brief Index of first statement performing write to the database.
+ ///
+ /// This value is used to mark border line between queries and other
+ /// statements and statements performing write operation on the database,
+ /// such as INSERT, DELETE, UPDATE.
+ static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
+
/// @brief Constructor.
///
/// This constructor opens database connection and initializes prepared
@@ -1222,6 +1234,14 @@ public:
StatementIndex stindex,
boost::shared_ptr exchange) const;
+ /// @brief Throws exception if database is read only.
+ ///
+ /// This method should be called by the methods which write to the
+ /// database. If the backend is operating in read-only mode this
+ /// method will throw exception.
+ ///
+ /// @throw DbReadOnly if backend is operating in read only mode.
+ void checkReadOnly() const;
/// @brief Returns PostgreSQL schema version of the open database
///
@@ -1258,66 +1278,25 @@ public:
/// @brief MySQL connection
PgSqlConnection conn_;
+ /// @brief Indicates if the database is opened in read only mode.
+ bool is_readonly_;
};
namespace {
+/// @brief Array of tagged statements.
+typedef boost::array
+TaggedStatementArray;
+
/// @brief Prepared PosgreSQL statements used by the backend to insert and
/// retrieve reservation data from the database.
-PgSqlTaggedStatement tagged_statements[] = {
- // PgSqlHostDataSourceImpl::INSERT_HOST
- // Inserts a host into the 'hosts' table. Returns the inserted host id.
- {8,
- { OID_BYTEA, OID_INT2,
- OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
- OID_VARCHAR, OID_VARCHAR },
- "insert_host",
- "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
- " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
- " dhcp4_client_classes, dhcp6_client_classes) "
- "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
- },
-
- //PgSqlHostDataSourceImpl::INSERT_V6_RESRV
- // Inserts a single IPv6 reservation into 'reservations' table.
- {5,
- { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
- "insert_v6_resrv",
- "INSERT INTO ipv6_reservations(address, prefix_len, type, "
- " dhcp6_iaid, host_id) "
- "VALUES ($1, $2, $3, $4, $5)"
- },
-
- // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
- // Inserts a single DHCPv4 option into 'dhcp4_options' table.
- // Using fixed scope_id = 3, which associates an option with host.
- {6,
- { OID_INT2, OID_BYTEA, OID_TEXT,
- OID_VARCHAR, OID_BOOL, OID_INT8},
- "insert_v4_host_option",
- "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
- " persistent, host_id, scope_id) "
- "VALUES ($1, $2, $3, $4, $5, $6, 3)"
- },
-
- // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
- // Inserts a single DHCPv6 option into 'dhcp6_options' table.
- // Using fixed scope_id = 3, which associates an option with host.
- {6,
- { OID_INT2, OID_BYTEA, OID_TEXT,
- OID_VARCHAR, OID_BOOL, OID_INT8},
- "insert_v6_host_option",
- "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
- " persistent, host_id, scope_id) "
- "VALUES ($1, $2, $3, $4, $5, $6, 3)"
- },
-
+TaggedStatementArray tagged_statements = { {
// PgSqlHostDataSourceImpl::GET_HOST_DHCPID
// Retrieves host information, IPv6 reservations and both DHCPv4 and
// DHCPv6 options associated with the host. The LEFT JOIN clause is used
// to retrieve information from 4 different tables using a single query.
// Hence, this query returns multiple rows for a single host.
- {2,
+ {2,
{ OID_BYTEA, OID_INT2 },
"get_host_dhcpid",
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
@@ -1417,7 +1396,7 @@ PgSqlTaggedStatement tagged_statements[] = {
// are returned due to left joining IPv6 reservations and DHCPv6 options.
// The number of rows returned is multiplication of number of existing
// IPv6 reservations and DHCPv6 options.
- {2,
+ {2,
{ OID_VARCHAR, OID_INT2 },
"get_host_prefix",
"SELECT h.host_id, h.dhcp_identifier, "
@@ -1439,14 +1418,59 @@ PgSqlTaggedStatement tagged_statements[] = {
//PgSqlHostDataSourceImpl::GET_VERSION
// Retrieves MySQL schema version.
- {0,
+ {0,
{ OID_NONE },
"get_version",
"SELECT version, minor FROM schema_version"
},
- // Marks the end of the statements table.
- {0, { 0 }, NULL, NULL}
+ // PgSqlHostDataSourceImpl::INSERT_HOST
+ // Inserts a host into the 'hosts' table. Returns the inserted host id.
+ {8,
+ { OID_BYTEA, OID_INT2,
+ OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
+ OID_VARCHAR, OID_VARCHAR },
+ "insert_host",
+ "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
+ " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ " dhcp4_client_classes, dhcp6_client_classes) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
+ },
+
+ //PgSqlHostDataSourceImpl::INSERT_V6_RESRV
+ // Inserts a single IPv6 reservation into 'reservations' table.
+ {5,
+ { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
+ "insert_v6_resrv",
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ " dhcp6_iaid, host_id) "
+ "VALUES ($1, $2, $3, $4, $5)"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
+ // Inserts a single DHCPv4 option into 'dhcp4_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {6,
+ { OID_INT2, OID_BYTEA, OID_TEXT,
+ OID_VARCHAR, OID_BOOL, OID_INT8},
+ "insert_v4_host_option",
+ "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
+ " persistent, host_id, scope_id) "
+ "VALUES ($1, $2, $3, $4, $5, $6, 3)"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
+ // Inserts a single DHCPv6 option into 'dhcp6_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {6,
+ { OID_INT2, OID_BYTEA, OID_TEXT,
+ OID_VARCHAR, OID_BOOL, OID_INT8},
+ "insert_v6_host_option",
+ "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
+ " persistent, host_id, scope_id) "
+ "VALUES ($1, $2, $3, $4, $5, $6, 3)"
+ }
+}
};
}; // end anonymous namespace
@@ -1459,20 +1483,27 @@ PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
DHCP4_AND_DHCP6)),
host_ipv6_reservation_exchange_(new PgSqlIPv6ReservationExchange()),
host_option_exchange_(new PgSqlOptionExchange()),
- conn_(parameters) {
+ conn_(parameters),
+ is_readonly_(false) {
// Open the database.
conn_.openDatabase();
- int i = 0;
- for( ; tagged_statements[i].text != NULL ; ++i) {
- conn_.prepareStatement(tagged_statements[i]);
- }
+ conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.begin() + WRITE_STMTS_BEGIN);
- // Just in case somebody foo-barred things
- if (i != NUM_STATEMENTS) {
- isc_throw(DbOpenError, "Number of statements prepared: " << i
- << " does not match expected count:" << NUM_STATEMENTS);
+ // Check if the backend is explicitly configured to operate with
+ // read only access to the database.
+ is_readonly_ = conn_.configuredReadOnly();
+
+ // If we are using read-write mode for the database we also prepare
+ // statements for INSERTS etc.
+ if (!is_readonly_) {
+ conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
+ tagged_statements.end());
+
+ } else {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_READONLY);
}
}
@@ -1632,6 +1663,15 @@ std::pair PgSqlHostDataSourceImpl::getVersion() const {
return (std::make_pair(version, minor));
}
+void
+PgSqlHostDataSourceImpl::checkReadOnly() const {
+ if (is_readonly_) {
+ isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
+ " to operate in read only mode");
+ }
+}
+
+
/*********** PgSqlHostDataSource *********************/
@@ -1646,6 +1686,9 @@ PgSqlHostDataSource::~PgSqlHostDataSource() {
void
PgSqlHostDataSource::add(const HostPtr& host) {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+
// Initiate PostgreSQL transaction as we will have to make multiple queries
// to insert host information into multiple tables. If that fails on
// any stage, the transaction will be rolled back by the destructor of
@@ -1894,5 +1937,20 @@ std::pair PgSqlHostDataSource::getVersion() const {
return(impl_->getVersion());
}
+void
+PgSqlHostDataSource::commit() {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+ impl_->conn_.commit();
+}
+
+
+void
+PgSqlHostDataSource::rollback() {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+ impl_->conn_.rollback();
+}
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h
index 2d7ab6c10b..40ad840c86 100644
--- a/src/lib/dhcpsrv/pgsql_host_data_source.h
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.h
@@ -273,6 +273,16 @@ public:
/// has failed.
virtual std::pair getVersion() const;
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations.
+ virtual void commit();
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations.
+ virtual void rollback();
+
private:
/// @brief Pointer to the implementation of the @ref PgSqlHostDataSource.
diff --git a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
index 846808da1f..e71dc2530f 100644
--- a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
@@ -88,7 +88,7 @@ public:
}
// Add the keyword and value - make sure that they are quoted.
- // The parameters which are not quoted are persist and
+ // The parameters which are not quoted are persist, readonly and
// lfc-interval as they are boolean and integer respectively.
result += quote + keyval[i] + quote + colon + space;
if (!quoteValue(std::string(keyval[i]))) {
@@ -176,7 +176,8 @@ private:
/// @return true if the value of the parameter should be quoted.
bool quoteValue(const std::string& parameter) const {
return ((parameter != "persist") && (parameter != "lfc-interval") &&
- (parameter != "connect-timeout"));
+ (parameter != "connect-timeout") &&
+ (parameter != "readonly"));
}
};
@@ -560,4 +561,45 @@ TEST_F(DbAccessParserTest, getDbAccessString) {
EXPECT_EQ(dbaccess, "name=keatest type=mysql");
}
+// Check that the configuration is accepted for the valid value
+// of "readonly".
+TEST_F(DbAccessParserTest, validReadOnly) {
+ const char* config[] = {"type", "mysql",
+ "user", "keatest",
+ "password", "keatest",
+ "name", "keatest",
+ "readonly", "true",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.build(json_elements));
+
+ checkAccessString("Valid readonly parameter",
+ parser.getDbAccessParameters(),
+ config);
+}
+
+// Check that for the invalid value of the "readonly" parameter
+// an exception is thrown.
+TEST_F(DbAccessParserTest, invalidReadOnly) {
+ const char* config[] = {"type", "mysql",
+ "user", "keatest",
+ "password", "keatest",
+ "name", "keatest",
+ "readonly", "1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.build(json_elements), BadValue);
+}
+
+
}; // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
index 4e50b3f8bf..42387388ad 100644
--- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
@@ -12,8 +12,10 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -504,6 +506,55 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
LibDHCP::setRuntimeOptionDefs(defs);
}
+void
+GenericHostDataSourceTest::testReadOnlyDatabase(const char* valid_db_type) {
+ ASSERT_TRUE(hdsptr_);
+
+ // The database is initially opened in "read-write" mode. We can
+ // insert some data to the databse.
+ HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ ASSERT_TRUE(host);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // Make sure that the host has been inserted and that the data can be
+ // retrieved.
+ ConstHostPtr host_by_id = hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(host_by_id);
+ ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_id));
+
+ // Close the database connection and reopen in "read-only" mode as
+ // specified by the "VALID_READONLY_DB" parameter.
+ HostDataSourceFactory::destroy();
+ HostDataSourceFactory::create(connectionString(valid_db_type,
+ VALID_NAME,
+ VALID_HOST,
+ VALID_READONLY_USER,
+ VALID_PASSWORD,
+ VALID_READONLY_DB));
+
+ hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+
+ // Check that an attempt to insert new host would result in
+ // exception.
+ HostPtr host2 = initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+ ASSERT_TRUE(host2);
+ ASSERT_THROW(hdsptr_->add(host2), ReadOnlyDb);
+ ASSERT_THROW(hdsptr_->commit(), ReadOnlyDb);
+ ASSERT_THROW(hdsptr_->rollback(), ReadOnlyDb);
+
+ // Reading from the database should still be possible, though.
+ host_by_id = hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(host_by_id);
+ ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_id));
+}
+
void GenericHostDataSourceTest::testBasic4(const Host::IdentifierType& id) {
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
index b0ed22c2a7..30701e2621 100644
--- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
+++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
@@ -337,6 +337,23 @@ public:
/// @brief Pointer to the host data source
HostDataSourcePtr hdsptr_;
+ /// @brief Test that backend can be started in read-only mode.
+ ///
+ /// Some backends can operate when the database is read only, e.g.
+ /// host reservation tables are read only, or the database user has
+ /// read only privileges on the entire database. In such cases, the
+ /// Kea server administrator can specify in the backend configuration
+ /// that the database should be opened in read only mode, i.e.
+ /// INSERT, UPDATE, DELETE statements can't be issued. If any of the
+ /// functions updating the database is called for the backend, the
+ /// error is reported. The database running in read only mode can
+ /// be merely used to retrieve existing host reservations from the
+ /// database. This test verifies that this is the case.
+ ///
+ /// @param valid_db_type Parameter specifying type of backend to
+ /// be used, e.g. type=mysql.
+ void testReadOnlyDatabase(const char* valid_db_type);
+
/// @brief Test that checks that simple host with IPv4 reservation
/// can be inserted and later retrieved.
///
diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
index 1ef01548c1..f827f45e33 100644
--- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
@@ -63,8 +63,13 @@ public:
/// Rolls back all pending transactions. The deletion of myhdsptr_ will close
/// the database. Then reopen it and delete everything created by the test.
virtual ~MySqlHostDataSourceTest() {
- hdsptr_->rollback();
+ try {
+ hdsptr_->rollback();
+ } catch (...) {
+ // Rollback may fail if backend is in read only mode. That's ok.
+ }
HostDataSourceFactory::destroy();
+ hdsptr_.reset();
destroyMySQLSchema();
}
@@ -157,6 +162,9 @@ TEST(MySqlHostDataSource, OpenDatabase) {
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
DbInvalidTimeout);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT, INVALID_READONLY_DB)), DbInvalidReadOnly);
// Check for missing parameters
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
@@ -167,6 +175,8 @@ TEST(MySqlHostDataSource, OpenDatabase) {
destroyMySQLSchema();
}
+
+
/// @brief Check conversion functions
///
/// The server works using cltt and valid_filetime. In the database, the
@@ -208,6 +218,10 @@ TEST(MySqlConnection, checkTimeConversion) {
EXPECT_EQ(cltt, converted_cltt);
}
+TEST_F(MySqlHostDataSourceTest, testReadOnlyDatabase) {
+ testReadOnlyDatabase(MYSQL_VALID_TYPE);
+}
+
// Test verifies if a host reservation can be added and later retrieved by IPv4
// address. Host uses hw address as identifier.
TEST_F(MySqlHostDataSourceTest, basic4HWAddr) {
diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
index 8d58637c5f..29dcf29864 100644
--- a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
@@ -64,8 +64,13 @@ public:
/// close the database. Then reopen it and delete everything created by
/// the test.
virtual ~PgSqlHostDataSourceTest() {
- hdsptr_->rollback();
+ try {
+ hdsptr_->rollback();
+ } catch (...) {
+ // Rollback may fail if backend is in read only mode. That's ok.
+ }
HostDataSourceFactory::destroy();
+ hdsptr_.reset();
destroyPgSQLSchema();
}
@@ -168,6 +173,12 @@ TEST(PgSqlHostDataSource, OpenDatabase) {
destroyPgSQLSchema();
}
+
+// This test verifies that database backend can operate in Read-Only mode.
+TEST_F(PgSqlHostDataSourceTest, testReadOnlyDatabase) {
+ testReadOnlyDatabase(PGSQL_VALID_TYPE);
+}
+
// Test verifies if a host reservation can be added and later retrieved by IPv4
// address. Host uses hw address as identifier.
TEST_F(PgSqlHostDataSourceTest, basic4HWAddr) {
diff --git a/src/lib/dhcpsrv/testutils/schema.cc b/src/lib/dhcpsrv/testutils/schema.cc
index cefcbdae5c..b373363dc7 100644
--- a/src/lib/dhcpsrv/testutils/schema.cc
+++ b/src/lib/dhcpsrv/testutils/schema.cc
@@ -25,15 +25,19 @@ const char* INVALID_NAME = "name=invalidname";
const char* VALID_HOST = "host=localhost";
const char* INVALID_HOST = "host=invalidhost";
const char* VALID_USER = "user=keatest";
+const char* VALID_READONLY_USER = "user=keatest_readonly";
const char* INVALID_USER = "user=invaliduser";
const char* VALID_PASSWORD = "password=keatest";
const char* INVALID_PASSWORD = "password=invalid";
const char* VALID_TIMEOUT = "connect-timeout=10";
const char* INVALID_TIMEOUT_1 = "connect-timeout=foo";
const char* INVALID_TIMEOUT_2 = "connect-timeout=-17";
+const char* VALID_READONLY_DB = "readonly=true";
+const char* INVALID_READONLY_DB = "readonly=5";
string connectionString(const char* type, const char* name, const char* host,
- const char* user, const char* password, const char* timeout) {
+ const char* user, const char* password, const char* timeout,
+ const char* readonly_db = NULL) {
const string space = " ";
string result = "";
@@ -75,6 +79,13 @@ string connectionString(const char* type, const char* name, const char* host,
result += string(timeout);
}
+ if (readonly_db != NULL) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(readonly_db);
+ }
+
return (result);
}
diff --git a/src/lib/dhcpsrv/testutils/schema.h b/src/lib/dhcpsrv/testutils/schema.h
index 5a0471512f..14d04abb6e 100644
--- a/src/lib/dhcpsrv/testutils/schema.h
+++ b/src/lib/dhcpsrv/testutils/schema.h
@@ -21,12 +21,16 @@ extern const char* INVALID_NAME;
extern const char* VALID_HOST;
extern const char* INVALID_HOST;
extern const char* VALID_USER;
+extern const char* VALID_READONLY_USER;
extern const char* INVALID_USER;
extern const char* VALID_PASSWORD;
extern const char* INVALID_PASSWORD;
extern const char* VALID_TIMEOUT;
extern const char* INVALID_TIMEOUT_1;
extern const char* INVALID_TIMEOUT_2;
+extern const char* VALID_READONLY_DB;
+extern const char* INVALID_READONLY_DB;
+
/// @brief Given a combination of strings above, produce a connection string.
///
/// @param type type of the database
@@ -35,10 +39,12 @@ extern const char* INVALID_TIMEOUT_2;
/// @param user username used to authenticate during connection attempt
/// @param password password used to authenticate during connection attempt
/// @param timeout timeout used during connection attempt
+/// @param readonly_db specifies if database is read only
/// @return string containing all specified parameters
std::string connectionString(const char* type, const char* name = NULL,
const char* host = NULL, const char* user = NULL,
- const char* password = NULL, const char* timeout = NULL);
+ const char* password = NULL, const char* timeout = NULL,
+ const char* readonly_db = NULL);
};
};
};
diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql
index 5d6a321200..80aaf082c2 100644
--- a/src/share/database/scripts/mysql/dhcpdb_create.mysql
+++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql
@@ -465,6 +465,11 @@ ALTER TABLE dhcp6_options
ADD CONSTRAINT fk_dhcp6_option_scope FOREIGN KEY (scope_id)
REFERENCES dhcp_option_scope (scope_id);
+# Add UNSIGNED to reservation_id
+ALTER TABLE ipv6_reservations
+ MODIFY reservation_id INT UNSIGNED NOT NULL AUTO_INCREMENT;
+
+
# Update the schema version number
UPDATE schema_version
SET version = '4', minor = '2';